Files
pit-router/webhook-server.py
yunxiafei 03a68c982e feat: 添加登录页面,支持外网访问 Web UI
- 新增 /web/login 登录页面
- 修改路由使用 optional JWT 认证
- 前端自动检测 token 并跳转登录
- 更新 .gitignore 排除 venv
2026-03-15 07:28:20 +08:00

157 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
智队中枢 Webhook 服务
接收 Gitea Webhook 请求,触发自动更新
"""
import os
import hmac
import hashlib
import subprocess
import logging
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
# 配置
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'pit-router-webhook-secret-2026')
UPDATE_SCRIPT = '/www/wwwroot/pit-router/auto-update.sh'
LOG_FILE = '/var/log/pit-router-webhook.log'
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def verify_signature(payload: bytes, signature: str) -> bool:
"""验证 Gitea Webhook 签名"""
if not signature:
return False
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
def log_request(event: str, data: dict):
"""记录请求日志"""
ref = data.get('ref', '')
commits = data.get('commits', [])
pusher = data.get('pusher', {}).get('login', 'unknown')
logger.info(f"Event: {event} | Ref: {ref} | Pusher: {pusher} | Commits: {len(commits)}")
for commit in commits[:3]: # 只记录前3个 commit
msg = commit.get('message', '').split('\n')[0][:50]
logger.info(f" - {commit.get('id', '')[:7]}: {msg}")
@app.route('/health', methods=['GET'])
def health():
"""健康检查端点"""
return jsonify({'status': 'ok', 'service': 'pit-router-webhook'})
@app.route('/webhook/pit-router', methods=['POST'])
def handle_webhook():
"""处理 Gitea Webhook"""
try:
# 1. 验证签名
signature = request.headers.get('X-Gitea-Signature', '')
if not verify_signature(request.data, signature):
logger.warning("Invalid signature")
return jsonify({'error': 'Invalid signature'}), 401
# 2. 解析事件
event = request.headers.get('X-Gitea-Event', '')
data = request.json or {}
# 记录请求
log_request(event, data)
# 3. 只处理 push 事件
if event != 'push':
logger.info(f"Event '{event}' ignored")
return jsonify({'message': f'Event {event} ignored'}), 200
# 4. 只处理 main 分支
ref = data.get('ref', '')
if ref != 'refs/heads/main':
logger.info(f"Branch '{ref}' ignored")
return jsonify({'message': f'Branch {ref} ignored'}), 200
# 5. 执行更新脚本
logger.info("🚀 Triggering auto-update...")
result = subprocess.run(
[UPDATE_SCRIPT],
capture_output=True,
text=True,
timeout=600 # 10 分钟超时
)
# 记录输出
if result.stdout:
logger.info(f"Update output:\n{result.stdout[-2000:]}") # 只记录最后 2000 字符
if result.stderr:
logger.error(f"Update error:\n{result.stderr[-1000:]}")
if result.returncode == 0:
logger.info("✅ Update completed successfully")
return jsonify({
'status': 'success',
'message': 'Update completed',
'timestamp': datetime.utcnow().isoformat()
})
else:
logger.error(f"❌ Update failed with code {result.returncode}")
return jsonify({
'status': 'error',
'message': 'Update failed',
'details': result.stderr[-500:] if result.stderr else 'Unknown error'
}), 500
except subprocess.TimeoutExpired:
logger.error("Update timeout")
return jsonify({'error': 'Update timeout'}), 500
except Exception as e:
logger.exception(f"Unexpected error: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/webhook/test', methods=['GET'])
def test_webhook():
"""测试端点(手动触发更新)"""
try:
logger.info("🧪 Manual test trigger")
result = subprocess.run(
[UPDATE_SCRIPT],
capture_output=True,
text=True,
timeout=600
)
return jsonify({
'status': 'success' if result.returncode == 0 else 'error',
'output': result.stdout[-1000:],
'error': result.stderr[-500:] if result.stderr else None
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
logger.info("🚀 Starting PIT Router Webhook Server on port 5001")
app.run(host='0.0.0.0', port=5001, debug=False)