update
This commit is contained in:
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
**/node_modules
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
frontend/dist
|
||||||
|
storage
|
||||||
|
guides.db
|
||||||
|
.git
|
||||||
|
.claude-data
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ node_modules/
|
|||||||
frontend/dist/
|
frontend/dist/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
.claude-data/
|
||||||
|
|||||||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Stage 1: Frontend bauen
|
||||||
|
FROM node:20-alpine AS frontend
|
||||||
|
WORKDIR /build
|
||||||
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY frontend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: Runtime
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
poppler-utils \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangoft2-1.0-0 \
|
||||||
|
libharfbuzz0b \
|
||||||
|
libcairo2 \
|
||||||
|
libgdk-pixbuf-2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
fonts-dejavu \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
gnupg \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g @anthropic-ai/claude-code \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN useradd -m -u 1000 app
|
||||||
|
|
||||||
|
COPY backend/requirements.txt /app/backend/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r /app/backend/requirements.txt
|
||||||
|
|
||||||
|
COPY --chown=app:app backend/ /app/backend/
|
||||||
|
COPY --chown=app:app templates/ /app/templates/
|
||||||
|
COPY --chown=app:app --from=frontend /build/dist /app/frontend/dist
|
||||||
|
|
||||||
|
RUN chown app:app /app
|
||||||
|
|
||||||
|
USER app
|
||||||
|
WORKDIR /app/backend
|
||||||
|
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
23
Makefile
23
Makefile
@@ -1,4 +1,12 @@
|
|||||||
.PHONY: install dev prod stop remove
|
.PHONY: install dev prod stop logs remove auth
|
||||||
|
|
||||||
|
COMPOSE = docker compose
|
||||||
|
|
||||||
|
auth:
|
||||||
|
@mkdir -p .claude-data storage
|
||||||
|
@cp -aT /root/.claude .claude-data
|
||||||
|
@chown -R 1000:1000 .claude-data storage
|
||||||
|
@echo "Claude-Auth nach .claude-data synct, storage chowned auf uid 1000."
|
||||||
|
|
||||||
install:
|
install:
|
||||||
sudo apt install -y poppler-utils libpango-1.0-0 libcairo2 libgdk-pixbuf-2.0-0 libffi-dev
|
sudo apt install -y poppler-utils libpango-1.0-0 libcairo2 libgdk-pixbuf-2.0-0 libffi-dev
|
||||||
@@ -11,18 +19,19 @@ dev:
|
|||||||
@cd backend && uvicorn main:app --reload --port 8000 &
|
@cd backend && uvicorn main:app --reload --port 8000 &
|
||||||
@cd frontend && npx vite --port 5173
|
@cd frontend && npx vite --port 5173
|
||||||
|
|
||||||
prod:
|
prod: auth
|
||||||
@echo "Backend: http://localhost:8000"
|
$(COMPOSE) up -d --build
|
||||||
@cd frontend && npx vite build
|
|
||||||
@cd backend && uvicorn main:app --host 0.0.0.0 --port 8000
|
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
-@pkill -f "uvicorn main:app" 2>/dev/null
|
-@pkill -f "uvicorn main:app" 2>/dev/null
|
||||||
-@pkill -f "vite --port 5173" 2>/dev/null
|
-@pkill -f "vite --port 5173" 2>/dev/null
|
||||||
|
$(COMPOSE) down
|
||||||
@echo "Server gestoppt."
|
@echo "Server gestoppt."
|
||||||
|
|
||||||
|
logs:
|
||||||
|
$(COMPOSE) logs -f
|
||||||
|
|
||||||
remove: stop
|
remove: stop
|
||||||
@echo "Lösche Datenbank und generierte Dateien..."
|
@echo "Lösche Datenbank und generierte Dateien..."
|
||||||
rm -f guides.db
|
rm -rf storage/*
|
||||||
rm -rf storage/html/* storage/pdf/*
|
|
||||||
@echo "Fertig."
|
@echo "Fertig."
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from pathlib import Path
|
|||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
TEMPLATES_DIR = PROJECT_ROOT / "templates"
|
TEMPLATES_DIR = PROJECT_ROOT / "templates"
|
||||||
STORAGE_DIR = PROJECT_ROOT / "storage"
|
STORAGE_DIR = PROJECT_ROOT / "storage"
|
||||||
DB_PATH = PROJECT_ROOT / "guides.db"
|
FRONTEND_DIST = PROJECT_ROOT / "frontend" / "dist"
|
||||||
|
DB_PATH = STORAGE_DIR / "guides.db"
|
||||||
|
|
||||||
ALLOWED_FORMATS = [
|
ALLOWED_FORMATS = [
|
||||||
"OnePager",
|
"OnePager",
|
||||||
@@ -24,15 +25,15 @@ FORMAT_META = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GENERATION_TIMEOUTS = {
|
GENERATION_TIMEOUTS = {
|
||||||
"OnePager": 600,
|
"OnePager": 900,
|
||||||
"Cheatsheet": 600,
|
"Cheatsheet": 900,
|
||||||
"MiniGuide": 600,
|
"MiniGuide": 1200,
|
||||||
"BeginnerGuide": 900,
|
"BeginnerGuide": 1800,
|
||||||
"IntermediateGuide": 1200,
|
"IntermediateGuide": 2400,
|
||||||
"ExtendedGuide": 1500,
|
"ExtendedGuide": 3000,
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_CONCURRENT_GENERATIONS = 10
|
MAX_CONCURRENT_GENERATIONS = 6
|
||||||
MAX_ITERATIONS = {
|
MAX_ITERATIONS = {
|
||||||
"OnePager": 3,
|
"OnePager": 3,
|
||||||
"Cheatsheet": 3,
|
"Cheatsheet": 3,
|
||||||
|
|||||||
@@ -45,11 +45,19 @@ async def _run_claude(guide_id: str, prompt: str, timeout: int) -> tuple[int, st
|
|||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
_active_processes[guide_id] = process
|
_active_processes[guide_id] = process
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
stdout, stderr = await asyncio.wait_for(
|
stdout, stderr = await asyncio.wait_for(
|
||||||
process.communicate(input=prompt.encode("utf-8")),
|
process.communicate(input=prompt.encode("utf-8")),
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
process.kill()
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(process.wait(), timeout=5)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
return process.returncode, stdout.decode("utf-8", errors="replace"), stderr.decode("utf-8", errors="replace")
|
return process.returncode, stdout.decode("utf-8", errors="replace"), stderr.decode("utf-8", errors="replace")
|
||||||
finally:
|
finally:
|
||||||
_active_processes.pop(guide_id, None)
|
_active_processes.pop(guide_id, None)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from config import STORAGE_DIR
|
from config import FRONTEND_DIST, STORAGE_DIR
|
||||||
from database import init_db, close_db
|
from database import init_db, close_db
|
||||||
from routes import router
|
from routes import router
|
||||||
|
|
||||||
@@ -20,11 +20,7 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(title="Guides Generator", lifespan=lifespan)
|
app = FastAPI(title="Guides Generator", lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|
||||||
|
if FRONTEND_DIST.exists():
|
||||||
|
app.mount("/", StaticFiles(directory=FRONTEND_DIST, html=True), name="frontend")
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
fastapi
|
fastapi
|
||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
aiosqlite
|
aiosqlite
|
||||||
|
weasyprint
|
||||||
|
pdf2image
|
||||||
|
|||||||
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
services:
|
||||||
|
guides:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
container_name: guides
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
volumes:
|
||||||
|
- ./storage:/app/storage
|
||||||
|
- ./.claude-data:/home/app/.claude
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.guidesapp.rule=Host(`guides.marha.de`)"
|
||||||
|
- "traefik.http.routers.guidesapp.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.guidesapp.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.guidesapp.loadbalancer.server.port=8000"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
Reference in New Issue
Block a user