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

341 行
9.4 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 文档站自动部署配置指南
## 当前状态
**已完成配置:**
- 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`