# 文档站自动部署配置指南 ## 当前状态 ✅ **已完成配置:** - Gitea Webhook 已创建(ID: 3),指向 `http://106.54.23.149:9000/deploy` - Jenkins Pipeline Job `lawless-docs-deploy` 已创建 - Jenkinsfile 已定义构建和部署流程 - 部署脚本已更新支持远程部署 ❌ **需要手动完成:** - 在服务器上部署 Webhook 接收器 - 配置 Jenkins SSH 凭据 --- ## 方案一:Webhook 直接部署(推荐) ### 1. 在服务器上部署 Webhook 接收器 SSH 到服务器 `106.54.23.149`,执行以下命令: ```bash # 创建目录 sudo mkdir -p /opt/docs-webhook sudo chown -R ubuntu:ubuntu /opt/docs-webhook # 创建 webhook 服务器 cat > /opt/docs-webhook/webhook-server.py << 'PYTHON_EOF' #!/usr/bin/env python3 """ Gitea Webhook 接收器 - 自动部署文档站 """ import os import sys import json import hmac import hashlib import subprocess import tempfile import shutil from http.server import HTTPServer, BaseHTTPRequestHandler from datetime import datetime # 配置 WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'lawless-docs-deploy') DEPLOY_DIR = os.environ.get('DEPLOY_DIR', '/var/www/docs.xuqinmin.com/site') REPO_URL = os.environ.get('REPO_URL', 'https://xuqinmin.com/xuqinmin12/lawless.git') BRANCH = os.environ.get('BRANCH', 'main') PORT = int(os.environ.get('WEBHOOK_PORT', '9000')) def log(message): print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}", flush=True) def verify_signature(payload, signature): if not signature: return False expected = hmac.new( WEBHOOK_SECRET.encode(), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) def deploy(): log("Starting deployment...") tmp_dir = tempfile.mkdtemp() try: log("Cloning repository...") subprocess.run( ['git', 'clone', '--depth', '1', '--branch', BRANCH, REPO_URL, 'repo'], cwd=tmp_dir, check=True, capture_output=True ) repo_dir = os.path.join(tmp_dir, 'repo') log("Installing dependencies...") subprocess.run( ['pip3', 'install', 'mkdocs-material', '--quiet'], cwd=repo_dir, capture_output=True ) log("Building docs...") subprocess.run( ['mkdocs', 'build', '--clean', '--site-dir', 'site'], cwd=repo_dir, check=True, capture_output=True ) site_dir = os.path.join(repo_dir, 'site') log(f"Deploying to {DEPLOY_DIR}...") os.makedirs(DEPLOY_DIR, exist_ok=True) subprocess.run( ['sudo', 'rsync', '-avz', '--delete', site_dir + '/', DEPLOY_DIR + '/'], check=True, capture_output=True ) log("Deployment completed successfully!") return True except Exception as e: log(f"Deployment failed: {e}") return False finally: shutil.rmtree(tmp_dir, ignore_errors=True) class WebhookHandler(BaseHTTPRequestHandler): def do_POST(self): if self.path == '/deploy': content_length = int(self.headers.get('Content-Length', 0)) payload = self.rfile.read(content_length) signature = self.headers.get('X-Gitea-Signature', '') if not verify_signature(payload, signature): self.send_response(403) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Invalid signature') return try: data = json.loads(payload) ref = data.get('ref', '') if ref == f'refs/heads/{BRANCH}': log(f"Push to {BRANCH} detected, triggering deploy...") import threading threading.Thread(target=deploy).start() self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Deployment triggered') else: log(f"Ignoring push to {ref}") self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Ignored') except json.JSONDecodeError: self.send_response(400) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Invalid JSON') elif self.path == '/health': self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'OK') else: self.send_response(404) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(b'Not Found') def log_message(self, format, *args): log(f"{self.client_address[0]} - {format % args}") def main(): log(f"Starting webhook server on port {PORT}...") log(f"Deploy directory: {DEPLOY_DIR}") log(f"Repository: {REPO_URL}") log(f"Branch: {BRANCH}") server = HTTPServer(('0.0.0.0', PORT), WebhookHandler) try: server.serve_forever() except KeyboardInterrupt: log("Shutting down server...") server.shutdown() if __name__ == '__main__': main() PYTHON_EOF # 创建 systemd 服务 sudo cat > /etc/systemd/system/docs-webhook.service << 'SERVICE_EOF' [Unit] Description=Docs Webhook Server After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/docs-webhook Environment=WEBHOOK_SECRET=lawless-docs-deploy Environment=DEPLOY_DIR=/var/www/docs.xuqinmin.com/site Environment=WEBHOOK_PORT=9000 ExecStart=/usr/bin/python3 /opt/docs-webhook/webhook-server.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target SERVICE_EOF # 启用并启动服务 sudo systemctl daemon-reload sudo systemctl enable docs-webhook sudo systemctl start docs-webhook # 检查状态 sudo systemctl status docs-webhook ``` ### 2. 开放防火墙端口 ```bash # 如果使用 ufw sudo ufw allow 9000/tcp # 如果使用 iptables sudo iptables -A INPUT -p tcp --dport 9000 -j ACCEPT ``` ### 3. 测试 Webhook ```bash # 测试健康检查 curl http://106.54.23.149:9000/health # 查看日志 sudo journalctl -u docs-webhook -f ``` ### 4. 测试部署 推送代码到 Gitea 仓库的 `main` 分支,观察日志: ```bash # 在本地修改并推送 echo "# Test" >> docs/test.md git add docs/test.md git commit -m "test: 触发自动部署" git push origin main # 在服务器上查看日志 sudo journalctl -u docs-webhook -f ``` --- ## 方案二:Jenkins Pipeline 部署 如果 Webhook 方案不工作,可以使用 Jenkins Pipeline。 ### 1. 配置 Jenkins SSH 凭据 在 Jenkins → 凭据 → 系统 → 全局凭据中添加: | ID | 类型 | 说明 | |----|------|------| | `jenkins-ssh-key` | SSH Username with private key | Username: `git`,私钥:Jenkins 服务器的 SSH 私钥 | | `gitea-access-token` | Username with password | Username: `xuqinmin12`,Password: Gitea API Token | ### 2. Jenkinsfile 配置 ```groovy pipeline { agent any environment { PROD_HOST = '106.54.23.149' PROD_USER = 'ubuntu' DEPLOY_DIR = '/var/www/docs.xuqinmin.com/site' } triggers { pollSCM('H/5 * * * *') } stages { stage('Checkout') { steps { checkout([ $class: 'GitSCM', branches: [[name: '*/main']], userRemoteConfigs: [[ url: 'ssh://git@xuqinmin.com:2222/xuqinmin12/lawless.git', credentialsId: 'jenkins-ssh-key' ]] ]) } } stage('Build Docs') { steps { sh 'pip3 install mkdocs-material --quiet || true' sh 'mkdocs build --clean --site-dir site' } } stage('Deploy') { steps { withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) { sh ''' ssh -i $SSH_KEY -o StrictHostKeyChecking=no $PROD_USER@$PROD_HOST \ "sudo mkdir -p $DEPLOY_DIR && sudo chown -R $PROD_USER:$PROD_USER /var/www/docs.xuqinmin.com" rsync -avz --delete -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \ site/ $PROD_USER@$PROD_HOST:$DEPLOY_DIR/ ''' } } } } } ``` --- ## 验证部署 部署完成后,访问 https://docs.xuqinmin.com/ 验证文档站是否更新。 ### 查看部署日志 ```bash # Webhook 方案 sudo journalctl -u docs-webhook -f # Jenkins 方案 # 访问 https://jenkins.xuqinmin.com/job/lawless-docs-deploy/ ``` --- ## 故障排查 ### Webhook 未触发 1. 检查 Gitea Webhook 日志(Recent Deliveries) 2. 检查服务器防火墙是否开放 9000 端口 3. 检查 webhook 服务是否运行:`sudo systemctl status docs-webhook` ### 部署失败 1. 检查 webhook 日志:`sudo journalctl -u docs-webhook -f` 2. 检查服务器磁盘空间:`df -h` 3. 检查 nginx 配置:`sudo nginx -t` ### 文档未更新 1. 清除浏览器缓存 2. 检查 nginx 配置:`sudo nginx -t` 3. 重启 nginx:`sudo systemctl restart nginx`