diff --git a/README.md b/README.md index e69de29..51c02e7 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,5 @@ +Makefile 설정 사용 예시 +- 배포: make deploy +- 백엔드 로그: make logs S=backend +- 전체 로그: make logs +- 상태: make status diff --git a/backend/Dockerfile b/backend/Dockerfile index 98f817e..b198bd9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -16,3 +16,7 @@ ENV PYTHONUNBUFFERED=1 EXPOSE 8000 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + +ARG APP_VERSION=dev +ENV APP_VERSION=$APP_VERSION +LABEL org.opencontainers.image.version=$APP_VERSION diff --git a/backend/app/main.py b/backend/app/main.py index cadaa9f..742b5f9 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -342,3 +342,7 @@ def api_recommend_batch_save(body: BatchSave): return {"saved": True, "created_ids": created, "deduped_ids": deduped} +@app.get("/api/version") +def version(): + import os + return {"version": os.getenv("APP_VERSION", "dev")} diff --git a/deployer/Dockerfile b/deployer/Dockerfile new file mode 100644 index 0000000..79ca7f5 --- /dev/null +++ b/deployer/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.12-slim +WORKDIR /app +RUN pip install --no-cache-dir fastapi uvicorn +COPY app.py /app/app.py +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "9000"] diff --git a/deployer/app.py b/deployer/app.py new file mode 100644 index 0000000..2d72f39 --- /dev/null +++ b/deployer/app.py @@ -0,0 +1,29 @@ +import os, hmac, hashlib, subprocess +from fastapi import FastAPI, Request, HTTPException + +app = FastAPI() +SECRET = os.getenv("WEBHOOK_SECRET", "") + +def verify(sig: str, body: bytes) -> bool: + # Gitea: X-Gitea-Signature = sha256=... + if not SECRET: + return False + mac = hmac.new(SECRET.encode(), msg=body, digestmod=hashlib.sha256).hexdigest() + expected = f"sha256={mac}" + return hmac.compare_digest(expected, sig) + +@app.post("/webhook") +async def webhook(req: Request): + body = await req.body() + sig = req.headers.get("X-Gitea-Signature", "") + + if not verify(sig, body): + raise HTTPException(401, "bad signature") + + # 배포 스크립트 실행 + # (컨테이너에 /scripts 가 마운트되어 있어야 함) + p = subprocess.run(["/scripts/deploy.sh"], capture_output=True, text=True) + if p.returncode != 0: + raise HTTPException(500, f"deploy failed:\n{p.stdout}\n{p.stderr}") + + return {"ok": True, "out": p.stdout} diff --git a/docker-compose.yml b/docker-compose.yml index 450a1b5..6729189 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,10 @@ version: "3.8" services: backend: - build: ./backend + build: + context: ./backend + args: + APP_VERSION: ${APP_VERSION:-dev} container_name: lotto-backend restart: unless-stopped ports: @@ -45,3 +48,16 @@ services: - /volume1/docker/webpage/travel-thumbs:/data/thumbs:ro extra_hosts: - "host.docker.internal:host-gateway" + + deployer: + build: ./deployer + container_name: webpage-deployer + restart: unless-stopped + ports: + - "19010:9000" # 외부 노출 필요 없으면 내부만 (리버스프록시로 /webhook만 노출 추천) + environment: + - WEBHOOK_SECRET=여기에_긴_랜덤값 + volumes: + - /volume1/workspace/web-page-backend:/repo:rw + - /volume1/docker/webpage:/runtime:rw + - /volume1/docker/webpage/scripts:/scripts:ro diff --git a/script/deploy.sh b/script/deploy.sh new file mode 100644 index 0000000..ab7f82b --- /dev/null +++ b/script/deploy.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -euo pipefail + +SRC="/repo" +DST="/runtime" + +cd "$SRC" +git fetch --all --prune +git pull --ff-only + +# 릴리즈 백업(롤백용): 아래 5번과 연결 +TAG="$(date +%Y%m%d-%H%M%S)" +mkdir -p "$DST/.releases/$TAG" +rsync -a --delete \ + --exclude ".releases" \ + "$DST/" "$DST/.releases/$TAG/" + +# 소스 → 운영 반영 (네가 이미 만든 deploy-nas.sh가 있으면 그걸 호출해도 됨) +# 예: repo/scripts/deploy-nas.sh가 운영으로 복사/동기화하는 로직이라면: +bash "$SRC/scripts/deploy-nas.sh" + +cd "$DST" +docker compose up -d --build backend travel-proxy frontend +echo "DEPLOY_OK $TAG" diff --git a/travel-proxy/Dockerfile b/travel-proxy/Dockerfile index 6ce96cd..b307412 100644 --- a/travel-proxy/Dockerfile +++ b/travel-proxy/Dockerfile @@ -20,3 +20,7 @@ EXPOSE 8000 ENV PYTHONUNBUFFERED=1 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + +ARG APP_VERSION=dev +ENV APP_VERSION=$APP_VERSION +LABEL org.opencontainers.image.version=$APP_VERSION diff --git a/travel-proxy/app/main.py b/travel-proxy/app/main.py index 72c0c3f..20c0d35 100644 --- a/travel-proxy/app/main.py +++ b/travel-proxy/app/main.py @@ -218,3 +218,8 @@ def get_thumb(album: str, filename: str): # src로부터 thumb 생성/확인 (원본 확장자 유지) p = ensure_thumb(src, album) return FileResponse(str(p)) + +@app.get("/api/version") +def version(): + import os + return {"version": os.getenv("APP_VERSION", "dev")}