feat: 添加自动发布脚本 (webhook + deploy.sh + systemd)
This commit is contained in:
parent
ea53873fa4
commit
b9dc180f86
123
deploy/README.md
Normal file
123
deploy/README.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# RuoYi 自动发布配置指南
|
||||
|
||||
## 原理
|
||||
|
||||
```
|
||||
你 push 代码到 Gitee
|
||||
↓
|
||||
Gitee 发送 Webhook 到服务器 :9000/webhook
|
||||
↓
|
||||
webhook.py 验证签名 + 过滤 master 分支
|
||||
↓
|
||||
触发 deploy.sh: git pull → mvn build → 停旧 → 启新
|
||||
```
|
||||
|
||||
## 第一步:上传文件到服务器
|
||||
|
||||
在本地 `deploy/` 目录执行:
|
||||
|
||||
```bash
|
||||
scp deploy.sh root@47.94.224.255:/opt/ruoyi/
|
||||
scp webhook.py root@47.94.224.255:/opt/ruoyi/
|
||||
scp ruoyi-webhook.service root@47.94.224.255:/etc/systemd/system/
|
||||
```
|
||||
|
||||
## 第二步:SSH 登录服务器配置
|
||||
|
||||
```bash
|
||||
ssh root@47.94.224.255
|
||||
```
|
||||
|
||||
### 2.1 设置脚本权限
|
||||
|
||||
```bash
|
||||
chmod +x /opt/ruoyi/deploy.sh
|
||||
chmod +x /opt/ruoyi/webhook.py
|
||||
```
|
||||
|
||||
### 2.2 启动 Webhook 服务
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable ruoyi-webhook
|
||||
systemctl start ruoyi-webhook
|
||||
systemctl status ruoyi-webhook
|
||||
```
|
||||
|
||||
### 2.3 验证 Webhook 运行
|
||||
|
||||
```bash
|
||||
curl http://localhost:9000/health
|
||||
# 应返回: OK
|
||||
```
|
||||
|
||||
### 2.4 开放防火墙端口(如有)
|
||||
|
||||
```bash
|
||||
# 检查防火墙
|
||||
firewall-cmd --state 2>/dev/null || echo "防火墙未运行"
|
||||
|
||||
# 如果需要开放
|
||||
firewall-cmd --add-port=9000/tcp --permanent
|
||||
firewall-cmd --reload
|
||||
```
|
||||
|
||||
**阿里云安全组也需要放行 9000 端口**(入方向)。
|
||||
|
||||
## 第三步:配置 Gitee Webhook
|
||||
|
||||
1. 打开 https://gitee.com/zhangYuXinGit/system
|
||||
2. 进入 **管理 → WebHooks**
|
||||
3. 点击 "添加 WebHook"
|
||||
4. 填写:
|
||||
- **URL**: `http://47.94.224.255:9000/webhook`
|
||||
- **密码**: `ruoyi-deploy-2024`
|
||||
- **事件**: 勾选 "Push"(只选 Push 就够了)
|
||||
5. 点击 "添加",然后点击 "测试" 验证连接
|
||||
|
||||
## 第四步:测试自动发布
|
||||
|
||||
在本地修改代码并推送:
|
||||
|
||||
```bash
|
||||
cd E:\java\project\system
|
||||
# 修改一些代码...
|
||||
git add .
|
||||
git commit -m "test: 测试自动发布"
|
||||
git push
|
||||
```
|
||||
|
||||
然后在服务器查看部署日志:
|
||||
|
||||
```bash
|
||||
ssh root@47.94.224.255 "tail -f /opt/ruoyi/deploy.log"
|
||||
```
|
||||
|
||||
## 常用运维命令
|
||||
|
||||
```bash
|
||||
# 查看 webhook 日志
|
||||
tail -f /opt/ruoyi/webhook.log
|
||||
|
||||
# 查看部署日志
|
||||
tail -f /opt/ruoyi/deploy.log
|
||||
|
||||
# 查看应用日志
|
||||
tail -f /opt/ruoyi/app.log
|
||||
|
||||
# 重启 webhook 服务
|
||||
systemctl restart ruoyi-webhook
|
||||
|
||||
# 手动触发部署(不走 webhook)
|
||||
ssh root@47.94.224.255 "/opt/ruoyi/deploy.sh"
|
||||
|
||||
# 停止自动发布
|
||||
systemctl stop ruoyi-webhook
|
||||
systemctl disable ruoyi-webhook
|
||||
```
|
||||
|
||||
## 安全建议
|
||||
|
||||
- 生产环境建议将 Webhook URL 改为 HTTPS
|
||||
- 可修改 `webhook.py` 中的 `WEBHOOK_SECRET` 更换密钥
|
||||
- 可在阿里云安全组中限制 9000 端口仅允许 Gitee 的 IP 访问
|
||||
81
deploy/deploy.sh
Normal file
81
deploy/deploy.sh
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/bin/bash
|
||||
#===========================================================================
|
||||
# RuoYi 自动部署脚本
|
||||
# 触发方式: Gitee Webhook → webhook.py → deploy.sh
|
||||
#===========================================================================
|
||||
|
||||
set -e
|
||||
|
||||
APP_DIR=/opt/ruoyi
|
||||
JAR_FILE=$APP_DIR/ruoyi-admin/target/ruoyi-admin.jar
|
||||
LOG_FILE=$APP_DIR/app.log
|
||||
DEPLOY_LOG=$APP_DIR/deploy.log
|
||||
PROFILE=druid
|
||||
PORT=8080
|
||||
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') [DEPLOY] $1" | tee -a "$DEPLOY_LOG"
|
||||
}
|
||||
|
||||
log "========== 开始部署 =========="
|
||||
|
||||
# 1. 拉取最新代码
|
||||
cd "$APP_DIR"
|
||||
log "拉取最新代码..."
|
||||
git pull origin master 2>&1 | tee -a "$DEPLOY_LOG"
|
||||
|
||||
# 2. Maven 构建
|
||||
log "开始 Maven 构建..."
|
||||
mvn clean package -DskipTests -pl ruoyi-admin -am -q 2>&1 | tee -a "$DEPLOY_LOG"
|
||||
|
||||
if [ ! -f "$JAR_FILE" ]; then
|
||||
log "构建失败: JAR 文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "构建成功"
|
||||
|
||||
# 3. 停止旧进程
|
||||
OLD_PID=$(ps aux | grep ruoyi-admin.jar | grep -v grep | awk '{print $2}')
|
||||
if [ -n "$OLD_PID" ]; then
|
||||
log "停止旧进程 PID: $OLD_PID"
|
||||
kill "$OLD_PID"
|
||||
sleep 3
|
||||
if ps -p "$OLD_PID" > /dev/null 2>&1; then
|
||||
kill -9 "$OLD_PID"
|
||||
log "强制停止 PID: $OLD_PID"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. 启动新进程
|
||||
log "启动新进程..."
|
||||
nohup java -Xms128m -Xmx384m -jar "$JAR_FILE" \
|
||||
--spring.profiles.active="$PROFILE" \
|
||||
--server.port="$PORT" \
|
||||
> "$LOG_FILE" 2>&1 &
|
||||
|
||||
NEW_PID=$!
|
||||
log "新进程 PID: $NEW_PID"
|
||||
|
||||
# 5. 等待启动
|
||||
sleep 5
|
||||
if ps -p "$NEW_PID" > /dev/null 2>&1; then
|
||||
log "应用启动中,等待就绪..."
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if grep -q "Started RuoYiApplication" "$LOG_FILE" 2>/dev/null; then
|
||||
log "部署成功! 应用已就绪"
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
else
|
||||
log "启动失败! 查看日志: tail -50 $LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 6. 健康检查
|
||||
sleep 3
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT 2>/dev/null || echo "000")
|
||||
log "健康检查 HTTP $HTTP_CODE"
|
||||
|
||||
log "========== 部署结束 =========="
|
||||
16
deploy/ruoyi-webhook.service
Normal file
16
deploy/ruoyi-webhook.service
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=RuoYi Webhook Deploy Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/ruoyi
|
||||
ExecStart=/usr/bin/python3 /opt/ruoyi/webhook.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=append:/opt/ruoyi/webhook.log
|
||||
StandardError=append:/opt/ruoyi/webhook.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
144
deploy/webhook.py
Normal file
144
deploy/webhook.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
RuoYi Gitee Webhook 接收服务
|
||||
|
||||
接收 Gitee 推送事件,触发自动部署。
|
||||
启动: python3 webhook.py
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from datetime import datetime
|
||||
|
||||
# ============ 配置 ============
|
||||
LISTEN_HOST = "0.0.0.0"
|
||||
LISTEN_PORT = 9000
|
||||
|
||||
# Gitee Webhook 密码(请在 Gitee 仓库设置中配置相同的密钥)
|
||||
WEBHOOK_SECRET = "ruoyi-deploy-2024"
|
||||
|
||||
# 部署脚本路径
|
||||
DEPLOY_SCRIPT = "/opt/ruoyi/deploy.sh"
|
||||
|
||||
# 部署锁文件,防止并发部署
|
||||
LOCK_FILE = "/tmp/ruoyi-deploy.lock"
|
||||
|
||||
# ============ 日志 ============
|
||||
def log(msg):
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
line = f"[{timestamp}] [WEBHOOK] {msg}"
|
||||
print(line, flush=True)
|
||||
|
||||
|
||||
class WebhookHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_POST(self):
|
||||
if self.path != "/webhook":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
# 读取请求体
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
# 验证签名
|
||||
signature = self.headers.get("X-Gitee-Token", "")
|
||||
if signature != WEBHOOK_SECRET:
|
||||
log(f"签名验证失败: {signature[:20]}...")
|
||||
self.send_response(403)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Forbidden")
|
||||
return
|
||||
|
||||
# 解析事件
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
log("JSON 解析失败")
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
event = self.headers.get("X-Gitee-Event", "unknown")
|
||||
ref = data.get("ref", "")
|
||||
pusher = data.get("pusher", {}).get("name", "unknown")
|
||||
|
||||
log(f"收到事件: {event} | 分支: {ref} | 推送者: {pusher}")
|
||||
|
||||
# 只处理 master 分支的 push 事件
|
||||
if event != "push_hooks" or ref != "refs/heads/master":
|
||||
log(f"忽略事件: {event} / {ref}")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Ignored")
|
||||
return
|
||||
|
||||
# 检查锁
|
||||
if self._is_locked():
|
||||
log("部署正在进行中,忽略重复触发")
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Deploy already in progress")
|
||||
return
|
||||
|
||||
# 异步执行部署
|
||||
self._lock()
|
||||
subprocess.Popen(
|
||||
["/bin/bash", DEPLOY_SCRIPT],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Deploy started")
|
||||
|
||||
def do_GET(self):
|
||||
"""健康检查"""
|
||||
if self.path == "/health":
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(b"OK")
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def _is_locked(self):
|
||||
try:
|
||||
with open(LOCK_FILE, "r") as f:
|
||||
lock_time = float(f.read().strip())
|
||||
# 锁超过 30 分钟自动过期
|
||||
if time.time() - lock_time > 1800:
|
||||
return False
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def _lock(self):
|
||||
with open(LOCK_FILE, "w") as f:
|
||||
f.write(str(time.time()))
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""重写默认日志,改为简洁格式"""
|
||||
log(f"{self.client_address[0]} - {format % args}")
|
||||
|
||||
|
||||
def main():
|
||||
log(f"Webhook 服务启动在 http://{LISTEN_HOST}:{LISTEN_PORT}/webhook")
|
||||
log(f"密钥: {WEBHOOK_SECRET[:4]}****")
|
||||
|
||||
server = HTTPServer((LISTEN_HOST, LISTEN_PORT), WebhookHandler)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
log("服务已停止")
|
||||
server.server_close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user