webhook 자동 배포 설정

This commit is contained in:
2026-01-25 11:51:39 +09:00
parent 9c9968b9a7
commit b815c37064
9 changed files with 97 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
Makefile 설정 사용 예시
- 배포: make deploy
- 백엔드 로그: make logs S=backend
- 전체 로그: make logs
- 상태: make status

View File

@@ -16,3 +16,7 @@ ENV PYTHONUNBUFFERED=1
EXPOSE 8000 EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "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

View File

@@ -342,3 +342,7 @@ def api_recommend_batch_save(body: BatchSave):
return {"saved": True, "created_ids": created, "deduped_ids": deduped} return {"saved": True, "created_ids": created, "deduped_ids": deduped}
@app.get("/api/version")
def version():
import os
return {"version": os.getenv("APP_VERSION", "dev")}

5
deployer/Dockerfile Normal file
View File

@@ -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"]

29
deployer/app.py Normal file
View File

@@ -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}

View File

@@ -2,7 +2,10 @@ version: "3.8"
services: services:
backend: backend:
build: ./backend build:
context: ./backend
args:
APP_VERSION: ${APP_VERSION:-dev}
container_name: lotto-backend container_name: lotto-backend
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -45,3 +48,16 @@ services:
- /volume1/docker/webpage/travel-thumbs:/data/thumbs:ro - /volume1/docker/webpage/travel-thumbs:/data/thumbs:ro
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "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

24
script/deploy.sh Normal file
View File

@@ -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"

View File

@@ -20,3 +20,7 @@ EXPOSE 8000
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "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

View File

@@ -218,3 +218,8 @@ def get_thumb(album: str, filename: str):
# src로부터 thumb 생성/확인 (원본 확장자 유지) # src로부터 thumb 생성/확인 (원본 확장자 유지)
p = ensure_thumb(src, album) p = ensure_thumb(src, album)
return FileResponse(str(p)) return FileResponse(str(p))
@app.get("/api/version")
def version():
import os
return {"version": os.getenv("APP_VERSION", "dev")}