Преглед изворни кода

feat: Pi 4 deploy workflow

- scripts/deploy.sh: pull + build + migrate + restart docker
  (reads GOGS_TOKEN from /opt/timelapse/.env — not in git)
- scripts/webhook-server.js: Node HTTP server nhận Gogs push webhook
- Gogs webhook đã tạo (ID: 1)
kingkong пре 2 месеци
родитељ
комит
b9787bcb53
2 измењених фајлова са 130 додато и 0 уклоњено
  1. 66 0
      scripts/deploy.sh
  2. 64 0
      scripts/webhook-server.js

+ 66 - 0
scripts/deploy.sh

@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+# deploy.sh — chạy trên Pi 4 khi nhận webhook từ Gogs
+# Credentials đọc từ /opt/timelapse/.env (KHÔNG commit .env lên git)
+
+set -euo pipefail
+
+APP_DIR="${APP_DIR:-/opt/timelapse}"
+BRANCH="${1:-main}"
+
+# Load env vars từ local file (không có trong git)
+ENV_FILE="$APP_DIR/.env"
+if [[ -f "$ENV_FILE" ]]; then
+  set -a
+  source "$ENV_FILE"
+  set +a
+fi
+
+log() { echo "[$(date '+%H:%M:%S')] $*"; }
+
+# Build authenticated git URL nếu có Gogs token
+REPO_URL="${GOGS_REPO_URL:-https://git.k9tech.space/kingkong/timelapse-2.git}"
+if [[ -n "${GOGS_TOKEN:-}" ]]; then
+  REPO_URL="${REPO_URL/https:\/\//https://${GOGS_TOKEN}@}"
+fi
+
+deploy() {
+  log "Deploying from $REPO_URL (branch: $BRANCH)"
+
+  mkdir -p "$(dirname "$APP_DIR")"
+
+  if [[ -d "$APP_DIR/.git" ]]; then
+    cd "$APP_DIR"
+    git remote set-url origin "$REPO_URL"
+    git fetch origin
+    git checkout "$BRANCH"
+    git pull origin "$BRANCH"
+  else
+    log "Cloning fresh..."
+    git clone --branch "$BRANCH" "$REPO_URL" "$APP_DIR"
+  fi
+
+  log "Installing deps..."
+  cd "$APP_DIR"
+  npm install --workspaces --include-workspace-root --ignore-scripts
+
+  log "Building..."
+  npm run build --workspace=packages/shared-types
+  npm run build --workspace=apps/api-server
+  npm run build --workspace=apps/web-dashboard
+
+  log "Running migrations..."
+  npm run migrate --workspace=apps/api-server || log "Migration skipped (no DB or error)"
+
+  log "Restarting Docker Compose..."
+  cd "$APP_DIR"
+  docker compose down
+  docker compose up -d --build postgres redis
+
+  sleep 8
+
+  docker compose up -d api-server web-dashboard worker || log "Some services skipped"
+
+  log "Deploy complete!"
+}
+
+deploy

+ 64 - 0
scripts/webhook-server.js

@@ -0,0 +1,64 @@
+#!/usr/bin/env node
+// webhook-server.js — nhận webhook từ Gogs và trigger deploy
+// Run: node webhook-server.js [port]
+//
+// Gogs webhook URL: http://<pi-ip>:9000/webhook
+
+const http = require('http')
+
+const PORT = process.env['WEBHOOK_PORT'] || 9000
+const DEPLOY_SCRIPT = process.env['DEPLOY_SCRIPT'] || '/opt/timelapse/scripts/deploy.sh'
+const WEBHOOK_SECRET = process.env['WEBHOOK_SECRET'] || ''
+
+function verifySignature(body, signature) {
+  if (!WEBHOOK_SECRET) return true
+  const crypto = require('crypto')
+  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET)
+  hmac.update(body)
+  const expected = 'sha256=' + hmac.digest('hex')
+  return crypto.timingSafeEqual(Buffer.from(signature || ''), Buffer.from(expected))
+}
+
+const server = http.createServer((req, res) => {
+  if (req.method !== 'POST' || req.url !== '/webhook') {
+    res.writeHead(404)
+    res.end('Not found')
+    return
+  }
+
+  let body = ''
+  req.on('data', chunk => { body += chunk.toString() })
+  req.on('end', () => {
+    const signature = req.headers['x-gogs-signature'] || req.headers['x-hub-signature-256'] || ''
+    if (!verifySignature(body, signature)) {
+      res.writeHead(401)
+      res.end('Unauthorized')
+      return
+    }
+
+    let payload
+    try { payload = JSON.parse(body) } catch { payload = {} }
+
+    const branch = payload.ref ? payload.ref.replace('refs/heads/', '') : 'main'
+    console.log(`[${new Date().toISOString()}] Webhook received — branch: ${branch}`)
+
+    res.writeHead(200, { 'Content-Type': 'application/json' })
+    res.end(JSON.stringify({ ok: true, branch }))
+
+    // Trigger deploy async
+    const { spawn } = require('child_process')
+    const deploy = spawn('bash', [DEPLOY_SCRIPT, branch], {
+      cwd: '/opt/timelapse',
+      env: { ...process.env, DEPLOY_BRANCH: branch },
+    })
+    deploy.stdout.on('data', d => process.stdout.write(d))
+    deploy.stderr.on('data', d => process.stderr.write(d))
+    deploy.on('exit', code => {
+      console.log(`[${new Date().toISOString()}] Deploy exited with code ${code}`)
+    })
+  })
+})
+
+server.listen(PORT, () => {
+  console.log(`Webhook server listening on http://0.0.0.0:${PORT}/webhook`)
+})