mirror of
https://github.com/KingHWJ/WeReadBoard.git
synced 2026-11-03 18:24:58 +08:00
ci(deploy): 改为 GitHub Webhook 触发部署
This commit is contained in:
1 parent
69bfe3ce0e
commit
469630e7da
11 files changed
+267
-78
No files matched your search
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""接收 GitHub push Webhook,并触发服务器上的固定部署脚本。"""
|
||||
|
||||
import hmac
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
from hashlib import sha256
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
|
||||
DEFAULT_PROJECT_DIR = "/opt/weread-board/WeReadBoard"
|
||||
DEFAULT_DEPLOY_SCRIPT = "deploy.sh"
|
||||
DEFAULT_REPOSITORY = "KingHWJ/WeReadBoard"
|
||||
DEFAULT_BRANCH = "master"
|
||||
|
||||
|
||||
def create_app(config=None):
|
||||
app = Flask(__name__)
|
||||
app.config.from_mapping(
|
||||
WEBHOOK_SECRET=os.environ.get("GITHUB_WEBHOOK_SECRET", ""),
|
||||
REPOSITORY=os.environ.get("GITHUB_REPOSITORY", DEFAULT_REPOSITORY),
|
||||
BRANCH=DEFAULT_BRANCH,
|
||||
PROJECT_DIR=os.environ.get("DEPLOY_PROJECT_DIR", DEFAULT_PROJECT_DIR),
|
||||
DEPLOY_SCRIPT=os.environ.get("DEPLOY_SCRIPT", DEFAULT_DEPLOY_SCRIPT),
|
||||
)
|
||||
if config:
|
||||
app.config.update(config)
|
||||
if not app.config["WEBHOOK_SECRET"]:
|
||||
raise RuntimeError("GITHUB_WEBHOOK_SECRET 未配置")
|
||||
if app.config["BRANCH"] != DEFAULT_BRANCH:
|
||||
raise RuntimeError("Webhook 只允许部署 master 分支")
|
||||
|
||||
deployment_lock = threading.Lock()
|
||||
deployment_process = None
|
||||
|
||||
def signature_valid(body, signature):
|
||||
if not signature or not signature.startswith("sha256="):
|
||||
return False
|
||||
expected = "sha256=" + hmac.new(
|
||||
app.config["WEBHOOK_SECRET"].encode("utf-8"), body, sha256
|
||||
).hexdigest()
|
||||
return hmac.compare_digest(expected, signature)
|
||||
|
||||
def start_deployment():
|
||||
nonlocal deployment_process
|
||||
with deployment_lock:
|
||||
if deployment_process is not None and deployment_process.poll() is None:
|
||||
return False
|
||||
env = os.environ.copy()
|
||||
env["DEPLOY_BRANCH"] = DEFAULT_BRANCH
|
||||
script = app.config["DEPLOY_SCRIPT"]
|
||||
if not os.path.isabs(script):
|
||||
script = os.path.join(app.config["PROJECT_DIR"], script)
|
||||
deployment_process = subprocess.Popen(
|
||||
[script],
|
||||
cwd=app.config["PROJECT_DIR"],
|
||||
env=env,
|
||||
start_new_session=True,
|
||||
)
|
||||
return True
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return jsonify(ok=True)
|
||||
|
||||
@app.post("/github/webhook")
|
||||
def github_webhook():
|
||||
body = request.get_data(cache=True)
|
||||
if not signature_valid(body, request.headers.get("X-Hub-Signature-256", "")):
|
||||
return jsonify(error="签名无效"), 401
|
||||
if request.headers.get("X-GitHub-Event") != "push":
|
||||
return jsonify(error="只接受 push 事件"), 202
|
||||
|
||||
payload = request.get_json(silent=True) or {}
|
||||
repository = (payload.get("repository") or {}).get("full_name")
|
||||
if repository != app.config["REPOSITORY"]:
|
||||
return jsonify(error="仓库不匹配"), 403
|
||||
if payload.get("ref") != f"refs/heads/{app.config['BRANCH']}" or payload.get("deleted"):
|
||||
return jsonify(error="分支不匹配"), 202
|
||||
|
||||
if not start_deployment():
|
||||
return jsonify(status="already_deploying"), 202
|
||||
return jsonify(status="accepted"), 202
|
||||
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
application = create_app()
|
||||
application.run(
|
||||
host=os.environ.get("WEBHOOK_HOST", "127.0.0.1"),
|
||||
port=int(os.environ.get("WEBHOOK_PORT", "9001")),
|
||||
)
|
||||
Reference in new issue
Block a user