ci: IDC 서버 자동배포 파이프라인 구축
- Dockerfile: Turborepo 멀티스테이지 빌드 (Next.js standalone) - docker-compose.prod.yml: PostgreSQL/Redis/Nginx/Web/Admin 프로덕션 스택 - deploy/poll-deploy.sh: cron 기반 자동배포 (매분 Gitea 폴링) - deploy/nginx/default.conf: 리버스 프록시 설정 - next.config.ts: output standalone 추가 - .env.production.example: 환경변수 템플릿 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Re:Link Gitea Webhook Receiver
|
||||
Listens for push events and triggers deployment.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from datetime import datetime
|
||||
|
||||
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "relink-deploy-secret")
|
||||
DEPLOY_SCRIPT = os.path.expanduser("~/relink/deploy.sh")
|
||||
DEPLOY_BRANCH = "main"
|
||||
PORT = int(os.environ.get("WEBHOOK_PORT", "9000"))
|
||||
LOG_FILE = os.path.expanduser("~/relink/webhook.log")
|
||||
|
||||
|
||||
def log(msg: str):
|
||||
line = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
|
||||
print(line, flush=True)
|
||||
with open(LOG_FILE, "a") as f:
|
||||
f.write(line + "\n")
|
||||
|
||||
|
||||
def verify_signature(payload: bytes, signature: str) -> bool:
|
||||
if not signature:
|
||||
return False
|
||||
expected = hmac.new(
|
||||
WEBHOOK_SECRET.encode(), payload, hashlib.sha256
|
||||
).hexdigest()
|
||||
return hmac.compare_digest(f"sha256={expected}", signature)
|
||||
|
||||
|
||||
class WebhookHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
if self.path != "/deploy":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
payload = self.rfile.read(content_length)
|
||||
|
||||
# Verify signature
|
||||
signature = self.headers.get("X-Gitea-Signature", "")
|
||||
if WEBHOOK_SECRET and not verify_signature(payload, signature):
|
||||
log("REJECTED: Invalid signature")
|
||||
self.send_response(403)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Invalid signature")
|
||||
return
|
||||
|
||||
try:
|
||||
data = json.loads(payload)
|
||||
except json.JSONDecodeError:
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
ref = data.get("ref", "")
|
||||
if ref != f"refs/heads/{DEPLOY_BRANCH}":
|
||||
log(f"SKIPPED: Push to {ref} (not {DEPLOY_BRANCH})")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Skipped: not target branch")
|
||||
return
|
||||
|
||||
pusher = data.get("pusher", {}).get("login", "unknown")
|
||||
commits = len(data.get("commits", []))
|
||||
log(f"DEPLOY TRIGGERED: {pusher} pushed {commits} commit(s) to {DEPLOY_BRANCH}")
|
||||
|
||||
# Run deploy script asynchronously
|
||||
try:
|
||||
subprocess.Popen(
|
||||
["bash", DEPLOY_SCRIPT],
|
||||
stdout=open(LOG_FILE, "a"),
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True,
|
||||
)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Deploy started")
|
||||
log("Deploy script launched")
|
||||
except Exception as e:
|
||||
log(f"ERROR: Failed to start deploy: {e}")
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
self.wfile.write(f"Deploy failed: {e}".encode())
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/health":
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"ok")
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress default logging
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
log(f"Webhook receiver starting on port {PORT}")
|
||||
server = HTTPServer(("0.0.0.0", PORT), WebhookHandler)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
log("Webhook receiver stopped")
|
||||
server.server_close()
|
||||
Reference in New Issue
Block a user