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