lawless-design/docs/AUTO_DEPLOY_SETUP.md
2026-07-09 14:39:17 +08:00

9.4 KiB

文档站自动部署配置指南

当前状态

已完成配置:

  • 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,执行以下命令:

# 创建目录
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. 开放防火墙端口

# 如果使用 ufw
sudo ufw allow 9000/tcp

# 如果使用 iptables
sudo iptables -A INPUT -p tcp --dport 9000 -j ACCEPT

3. 测试 Webhook

# 测试健康检查
curl http://106.54.23.149:9000/health

# 查看日志
sudo journalctl -u docs-webhook -f

4. 测试部署

推送代码到 Gitea 仓库的 main 分支,观察日志:

# 在本地修改并推送
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 配置

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/ 验证文档站是否更新。

查看部署日志

# 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. 重启 nginxsudo systemctl restart nginx