chore: disable old K3s Actions; add webhook deploy scripts
This commit is contained in:
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# push → Gitea Webhook → 本脚本:仅部署 dev 到本机 Docker Compose
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_DIR="${APP_DIR:-/opt/AirShelf}"
|
||||||
|
BRANCH="${BRANCH:-dev}"
|
||||||
|
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
|
||||||
|
ENV_FILE="${ENV_FILE:-core/backend/.env}"
|
||||||
|
LOG_DIR="${LOG_DIR:-/var/log/airshelf}"
|
||||||
|
LOCK_FILE="${LOCK_FILE:-/tmp/airshelf-deploy-dev.lock}"
|
||||||
|
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
LOG_FILE="${LOG_DIR}/deploy-dev.log"
|
||||||
|
|
||||||
|
exec 9>"$LOCK_FILE"
|
||||||
|
if ! flock -n 9; then
|
||||||
|
echo "$(date '+%F %T') another deploy is running, skip" | tee -a "$LOG_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "======== $(date '+%F %T') deploy start ========"
|
||||||
|
cd "$APP_DIR"
|
||||||
|
|
||||||
|
if [[ ! -d .git ]]; then
|
||||||
|
echo "ERROR: $APP_DIR is not a git repo. Clone from your Gitea first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .env 在仓库里被跟踪,pull 会覆盖服务器上的生产配置;先备份再还原
|
||||||
|
ENV_BACKUP=""
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
ENV_BACKUP="$(mktemp)"
|
||||||
|
cp -a "$ENV_FILE" "$ENV_BACKUP"
|
||||||
|
echo "backed up $ENV_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch --prune origin
|
||||||
|
git checkout "$BRANCH"
|
||||||
|
git reset --hard "origin/$BRANCH"
|
||||||
|
|
||||||
|
if [[ -n "$ENV_BACKUP" ]]; then
|
||||||
|
cp -a "$ENV_BACKUP" "$ENV_FILE"
|
||||||
|
rm -f "$ENV_BACKUP"
|
||||||
|
echo "restored $ENV_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose -f "$COMPOSE_FILE" up -d --build
|
||||||
|
|
||||||
|
echo "======== $(date '+%F %T') deploy ok ========"
|
||||||
|
} >>"$LOG_FILE" 2>&1
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=AirShelf Gitea deploy webhook
|
||||||
|
After=network.target docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Environment=WEBHOOK_SECRET=CHANGE_ME
|
||||||
|
Environment=WEBHOOK_HOST=127.0.0.1
|
||||||
|
Environment=WEBHOOK_PORT=9000
|
||||||
|
Environment=DEPLOY_SH=/opt/AirShelf/scripts/deploy-dev.sh
|
||||||
|
ExecStart=/usr/bin/python3 /opt/AirShelf/scripts/webhook/receiver.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Gitea push webhook → only refs/heads/dev → scripts/deploy-dev.sh"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
SECRET = os.environ.get("WEBHOOK_SECRET", "").encode()
|
||||||
|
DEPLOY_SH = os.environ.get("DEPLOY_SH", "/opt/AirShelf/scripts/deploy-dev.sh")
|
||||||
|
HOST = os.environ.get("WEBHOOK_HOST", "0.0.0.0")
|
||||||
|
PORT = int(os.environ.get("WEBHOOK_PORT", "9000"))
|
||||||
|
|
||||||
|
|
||||||
|
def verify(sig: str | None, body: bytes) -> bool:
|
||||||
|
if not SECRET or not sig:
|
||||||
|
return False
|
||||||
|
digest = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
|
||||||
|
return hmac.compare_digest(digest, sig.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def run_deploy() -> None:
|
||||||
|
subprocess.run([DEPLOY_SH], check=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def log_message(self, fmt: str, *args) -> None:
|
||||||
|
print(f"[webhook] {self.address_string()} - {fmt % args}")
|
||||||
|
|
||||||
|
def do_POST(self) -> None:
|
||||||
|
if self.path not in ("/hooks/deploy-dev", "/deploy-dev", "/"):
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
length = int(self.headers.get("Content-Length", "0"))
|
||||||
|
body = self.rfile.read(length)
|
||||||
|
sig = self.headers.get("X-Gitea-Signature") or self.headers.get("X-Hub-Signature-256", "")
|
||||||
|
if sig.startswith("sha256="):
|
||||||
|
sig = sig[7:]
|
||||||
|
if not verify(sig, body):
|
||||||
|
self.send_response(401)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"invalid signature")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
payload = json.loads(body.decode("utf-8") or "{}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
self.send_response(400)
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
ref = payload.get("ref", "")
|
||||||
|
if ref != "refs/heads/dev":
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(f"ignored ref={ref}".encode())
|
||||||
|
return
|
||||||
|
threading.Thread(target=run_deploy, daemon=True).start()
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"deploy-dev accepted")
|
||||||
|
|
||||||
|
def do_GET(self) -> None:
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"airshelf webhook ok")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if not SECRET:
|
||||||
|
raise SystemExit("WEBHOOK_SECRET is required")
|
||||||
|
print(f"listening on {HOST}:{PORT}, deploy={DEPLOY_SH}")
|
||||||
|
HTTPServer((HOST, PORT), Handler).serve_forever()
|
||||||
Reference in New Issue
Block a user