feat(deploy): 添加开发环境 systemd 服务,改进部署文档
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,3 +3,7 @@
|
|||||||
|
|
||||||
# But track these
|
# But track these
|
||||||
!AGENTS.md
|
!AGENTS.md
|
||||||
|
!*.service
|
||||||
|
!*.sh
|
||||||
|
!deploy.sh
|
||||||
|
!test_deployment.sh
|
||||||
78
AGENTS.md
78
AGENTS.md
@@ -346,6 +346,8 @@ let new_expires = base_time + days(pkg_days);
|
|||||||
| `--dry-run` | 预览模式,不执行实际操作 |
|
| `--dry-run` | 预览模式,不执行实际操作 |
|
||||||
| `--yes, -y` | 跳过确认提示 |
|
| `--yes, -y` | 跳过确认提示 |
|
||||||
| `--skip-tests` | 跳过部署后测试 |
|
| `--skip-tests` | 跳过部署后测试 |
|
||||||
|
| `--rollback` | 回滚到上一个备份版本 |
|
||||||
|
| `--backup-list` | 列出可用备份 |
|
||||||
| `--help, -h` | 显示帮助信息 |
|
| `--help, -h` | 显示帮助信息 |
|
||||||
|
|
||||||
**示例:**
|
**示例:**
|
||||||
@@ -353,9 +355,66 @@ let new_expires = base_time + days(pkg_days);
|
|||||||
./deploy.sh production --dry-run # 预览生产部署
|
./deploy.sh production --dry-run # 预览生产部署
|
||||||
./deploy.sh production --yes # 无需确认直接部署
|
./deploy.sh production --yes # 无需确认直接部署
|
||||||
./deploy.sh development --skip-tests # 跳过测试
|
./deploy.sh development --skip-tests # 跳过测试
|
||||||
|
./deploy.sh production --rollback # 回滚到上一个版本
|
||||||
|
./deploy.sh production --backup-list # 列出可用备份
|
||||||
```
|
```
|
||||||
|
|
||||||
**deploy.sh 功能**:依赖检查 → 编译 → 备份旧版本 → 上传二进制/配置 → 重启服务 → 部署后测试
|
**deploy.sh 功能**:依赖检查 → 编译 → 备份旧版本 → 上传二进制/配置 → 重启服务 → 部署后测试 → 记录日志 → Webhook 通知
|
||||||
|
|
||||||
|
**回滚:**
|
||||||
|
```bash
|
||||||
|
./deploy.sh production --rollback # 回滚生产环境
|
||||||
|
```
|
||||||
|
|
||||||
|
**Webhook 通知(可选):**
|
||||||
|
```bash
|
||||||
|
WEBHOOK_URL="https://example.com/webhook" ./deploy.sh production
|
||||||
|
```
|
||||||
|
|
||||||
|
**部署日志位置**:`/root/rust/rust_backend/deploy.log`
|
||||||
|
|
||||||
|
### 部署规范
|
||||||
|
|
||||||
|
> **重要**:所有部署操作**必须使用 `deploy.sh`**,禁止手动操作服务器文件。
|
||||||
|
|
||||||
|
**禁止事项:**
|
||||||
|
- ❌ 直接 SSH 到服务器手动上传文件
|
||||||
|
- ❌ 直接 `scp` 或 `rsync` 二进制文件到服务器
|
||||||
|
- ❌ 直接 `systemctl restart` 服务而不通过脚本
|
||||||
|
- ❌ 直接修改服务器上的配置文件
|
||||||
|
|
||||||
|
**deploy.sh 已包含的安全保障:**
|
||||||
|
- ✅ 部署前自动备份旧版本
|
||||||
|
- ✅ 自动检查依赖
|
||||||
|
- ✅ 自动测试
|
||||||
|
- ✅ 部署日志记录
|
||||||
|
|
||||||
|
### 首次部署准备
|
||||||
|
|
||||||
|
首次在服务器上部署时,需要手动创建 systemd 服务文件和环境配置:
|
||||||
|
|
||||||
|
**1. 创建 systemd 服务文件:**
|
||||||
|
|
||||||
|
开发环境服务:`/etc/systemd/system/rust-backend-dev.service`
|
||||||
|
|
||||||
|
生产环境服务:`/etc/systemd/system/rust-backend.service`
|
||||||
|
|
||||||
|
参考本地文件:`rust-backend.service`(生产)、`rust-backend-dev.service`(开发)
|
||||||
|
|
||||||
|
**2. 创建 .env 环境配置:**
|
||||||
|
|
||||||
|
开发环境:`/root/rust/rust_backend_dev/.env`
|
||||||
|
生产环境:`/root/rust/rust_backend/.env`
|
||||||
|
|
||||||
|
参考 `.env.example` 或询问运维获取。
|
||||||
|
|
||||||
|
**3. 启用服务:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable rust-backend-dev.service # 开发环境
|
||||||
|
systemctl enable rust-backend.service # 生产环境
|
||||||
|
```
|
||||||
|
|
||||||
### 前端部署
|
### 前端部署
|
||||||
|
|
||||||
@@ -370,9 +429,22 @@ const CURRENT_ENV: 'development' | 'production' = 'production'; // 发布前切
|
|||||||
|
|
||||||
### 部署后测试
|
### 部署后测试
|
||||||
|
|
||||||
自动执行 `test_deployment.sh`,测试内容:
|
部署后验证是否成功,**直接运行** `test_deployment.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置测试域名
|
||||||
|
export TEST_DOMAIN="https://xmclassmate.top/dev" # 开发环境
|
||||||
|
export TEST_DOMAIN="https://xmclassmate.top" # 生产环境
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
./test_deployment.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:`deploy.sh` 会自动执行此测试,但也可手动单独运行验证。
|
||||||
|
|
||||||
|
测试内容:
|
||||||
- 本地后端健康检查
|
- 本地后端健康检查
|
||||||
- 域名访问检查
|
- API 端点检查
|
||||||
- 静态文件检查
|
- 静态文件检查
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
147
deploy.sh
147
deploy.sh
@@ -9,6 +9,8 @@
|
|||||||
# --dry-run 预览模式,不执行实际操作
|
# --dry-run 预览模式,不执行实际操作
|
||||||
# --yes, -y 跳过确认提示
|
# --yes, -y 跳过确认提示
|
||||||
# --skip-tests 跳过部署后测试
|
# --skip-tests 跳过部署后测试
|
||||||
|
# --rollback 回滚到上一个备份版本
|
||||||
|
# --backup-list 列出可用备份
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -22,6 +24,8 @@ APP_ENV="${1:-production}"
|
|||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
SKIP_CONFIRM=false
|
SKIP_CONFIRM=false
|
||||||
SKIP_TESTS=false
|
SKIP_TESTS=false
|
||||||
|
ROLLBACK=false
|
||||||
|
LIST_BACKUPS=false
|
||||||
|
|
||||||
# 解析选项
|
# 解析选项
|
||||||
shift 2>/dev/null || true
|
shift 2>/dev/null || true
|
||||||
@@ -36,6 +40,12 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--skip-tests)
|
--skip-tests)
|
||||||
SKIP_TESTS=true
|
SKIP_TESTS=true
|
||||||
;;
|
;;
|
||||||
|
--rollback)
|
||||||
|
ROLLBACK=true
|
||||||
|
;;
|
||||||
|
--backup-list)
|
||||||
|
LIST_BACKUPS=true
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
echo "用法: $0 [development|production] [options]"
|
echo "用法: $0 [development|production] [options]"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -43,6 +53,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
echo " --dry-run 预览模式,不执行实际操作"
|
echo " --dry-run 预览模式,不执行实际操作"
|
||||||
echo " --yes, -y 跳过确认提示"
|
echo " --yes, -y 跳过确认提示"
|
||||||
echo " --skip-tests 跳过部署后测试"
|
echo " --skip-tests 跳过部署后测试"
|
||||||
|
echo " --rollback 回滚到上一个备份版本"
|
||||||
|
echo " --backup-list 列出可用备份"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -98,6 +110,126 @@ get_git_info() {
|
|||||||
echo "$git_version"
|
echo "$git_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 部署日志路径
|
||||||
|
get_deploy_log() {
|
||||||
|
echo "${REMOTE_DIR}/deploy.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 记录部署日志
|
||||||
|
log_to_file() {
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
local log_entry="[${timestamp}] ${APP_ENV} ${GIT_VERSION} $1"
|
||||||
|
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "echo '${log_entry}' >> $(get_deploy_log)" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Webhook 通知
|
||||||
|
send_webhook() {
|
||||||
|
local status="$1"
|
||||||
|
local message="$2"
|
||||||
|
|
||||||
|
if [ -z "${WEBHOOK_URL:-}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local payload
|
||||||
|
payload=$(cat <<EOF
|
||||||
|
{
|
||||||
|
"app": "rust-backend",
|
||||||
|
"env": "${APP_ENV}",
|
||||||
|
"status": "${status}",
|
||||||
|
"version": "${GIT_VERSION}",
|
||||||
|
"message": "${message}",
|
||||||
|
"timestamp": "$(date -Iseconds)"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log_dry "curl -X POST '${WEBHOOK_URL}' -d '${payload}'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -s -X POST "${WEBHOOK_URL}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "${payload}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# 列出备份
|
||||||
|
list_backups() {
|
||||||
|
log_step "可用备份列表..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'ls -la ${REMOTE_DIR}/${PROJECT_NAME}.backup.*'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "ls -la ${REMOTE_DIR}/${PROJECT_NAME}.backup.*" 2>/dev/null || echo "无备份文件"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 回滚
|
||||||
|
rollback() {
|
||||||
|
log_step "查找最新备份..."
|
||||||
|
local latest_backup
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
latest_backup="${REMOTE_DIR}/${PROJECT_NAME}.backup.20240101_120000"
|
||||||
|
log_dry "latest_backup=${latest_backup}"
|
||||||
|
else
|
||||||
|
latest_backup=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||||
|
"ls -t ${REMOTE_DIR}/${PROJECT_NAME}.backup.* 2>/dev/null | head -1" || echo "")
|
||||||
|
|
||||||
|
if [ -z "$latest_backup" ]; then
|
||||||
|
log_error "没有找到可用的备份"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}========== 回滚确认 ==========${NC}"
|
||||||
|
echo -e " 将回滚到: ${latest_backup}"
|
||||||
|
echo -e " 服务: ${SERVICE_NAME}"
|
||||||
|
echo -e "================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$SKIP_CONFIRM" = false ]; then
|
||||||
|
read -p "确认回滚? [y/N] " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
log_warn "回滚已取消"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_step "停止服务..."
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "systemctl stop ${SERVICE_NAME}" 2>/dev/null || log_warn "停止服务失败,继续尝试..."
|
||||||
|
|
||||||
|
log_step "恢复备份..."
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'cp ${latest_backup} ${REMOTE_DIR}/${PROJECT_NAME}'"
|
||||||
|
else
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cp ${latest_backup} ${REMOTE_DIR}/${PROJECT_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_step "启动服务..."
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'systemctl start ${SERVICE_NAME}'"
|
||||||
|
else
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "systemctl start ${SERVICE_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "回滚完成"
|
||||||
|
log_to_file "ROLLBACK to $(basename $latest_backup)"
|
||||||
|
send_webhook "rollback" "回滚完成: $(basename $latest_backup)"
|
||||||
|
}
|
||||||
|
|
||||||
# 检查依赖
|
# 检查依赖
|
||||||
check_dependencies() {
|
check_dependencies() {
|
||||||
log_step "检查依赖..."
|
log_step "检查依赖..."
|
||||||
@@ -323,6 +455,18 @@ trap cleanup EXIT
|
|||||||
main() {
|
main() {
|
||||||
GIT_VERSION=$(get_git_info)
|
GIT_VERSION=$(get_git_info)
|
||||||
|
|
||||||
|
if [ "$LIST_BACKUPS" = true ]; then
|
||||||
|
ssh_connect
|
||||||
|
list_backups
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ROLLBACK" = true ]; then
|
||||||
|
ssh_connect
|
||||||
|
rollback
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
show_deploy_info
|
show_deploy_info
|
||||||
confirm_deploy
|
confirm_deploy
|
||||||
|
|
||||||
@@ -336,6 +480,9 @@ main() {
|
|||||||
restart_service
|
restart_service
|
||||||
run_tests
|
run_tests
|
||||||
|
|
||||||
|
log_to_file "DEPLOY success"
|
||||||
|
send_webhook "success" "部署完成"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
log_info "========== 部署完成! =========="
|
log_info "========== 部署完成! =========="
|
||||||
echo -e " Git: ${GIT_VERSION}"
|
echo -e " Git: ${GIT_VERSION}"
|
||||||
|
|||||||
28
rust-backend-dev.service
Normal file
28
rust-backend-dev.service
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rust Backend Development Service
|
||||||
|
After=network.target postgresql.service
|
||||||
|
Wants=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
WorkingDirectory=/root/rust/rust_backend_dev
|
||||||
|
ExecStart=/root/rust/rust_backend_dev/rust-backend
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
EnvironmentFile=/root/rust/rust_backend_dev/.env
|
||||||
|
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=read-only
|
||||||
|
ReadWritePaths=/root/rust/rust_backend_dev
|
||||||
|
|
||||||
|
SyslogIdentifier=rust-backend-dev
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user