refactor: deploy工具适配systemd service模式-移除.env依赖

This commit is contained in:
2026-05-25 14:46:07 +08:00
parent 216dd365f6
commit 4240bb6970
2 changed files with 104 additions and 45 deletions

View File

@@ -14,7 +14,8 @@
# --tail [N] 实时跟踪日志 # --tail [N] 实时跟踪日志
# --status 查看服务状态 # --status 查看服务状态
# --post-logs 部署后显示日志 # --post-logs 部署后显示日志
# --init-env 初始化远程 .env # --init-env 初始化 systemd service 模板
# --deploy-service 上传 systemd service 文件到服务器
# =========================================== # ===========================================
set -euo pipefail set -euo pipefail
@@ -32,7 +33,7 @@ APP_ENV="production"
DRY_RUN=false; SKIP_CONFIRM=false; SKIP_TESTS=false DRY_RUN=false; SKIP_CONFIRM=false; SKIP_TESTS=false
ROLLBACK=false; LIST_BACKUPS=false; VIEW_LOGS=false; TAIL_LOGS=false ROLLBACK=false; LIST_BACKUPS=false; VIEW_LOGS=false; TAIL_LOGS=false
LOG_LINES=50; VIEW_STATUS=false; POST_DEPLOY_LOGS=false; INIT_ENV=false LOG_LINES=50; VIEW_STATUS=false; POST_DEPLOY_LOGS=false; INIT_ENV=false
BACKUP_PATH="" DEPLOY_SERVICE=false; BACKUP_PATH=""
# ---------- 选项解析 ---------- # ---------- 选项解析 ----------
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
@@ -49,6 +50,7 @@ while [[ $# -gt 0 ]]; do
--status) VIEW_STATUS=true ;; --status) VIEW_STATUS=true ;;
--post-logs) POST_DEPLOY_LOGS=true ;; --post-logs) POST_DEPLOY_LOGS=true ;;
--init-env) INIT_ENV=true ;; --init-env) INIT_ENV=true ;;
--deploy-service) DEPLOY_SERVICE=true ;;
--help|-h) --help|-h)
echo "用法: $0 [development|production] [options]" echo "用法: $0 [development|production] [options]"
echo "选项:" echo "选项:"
@@ -61,7 +63,8 @@ while [[ $# -gt 0 ]]; do
echo " --tail [N] 实时跟踪日志" echo " --tail [N] 实时跟踪日志"
echo " --status 查看服务状态" echo " --status 查看服务状态"
echo " --post-logs 部署后显示日志" echo " --post-logs 部署后显示日志"
echo " --init-env 初始化远程 .env" echo " --init-env 初始化 systemd service 模板"
echo " --deploy-service 上传 systemd service 文件到服务器"
exit 0 ;; exit 0 ;;
*) *)
if [[ "$1" == "development" || "$1" == "production" ]]; then if [[ "$1" == "development" || "$1" == "production" ]]; then
@@ -98,6 +101,7 @@ ssh_connect
if [ "$LIST_BACKUPS" = true ]; then list_backups; exit 0; fi if [ "$LIST_BACKUPS" = true ]; then list_backups; exit 0; fi
if [ "$INIT_ENV" = true ]; then init_env; exit 0; fi if [ "$INIT_ENV" = true ]; then init_env; exit 0; fi
if [ "$DEPLOY_SERVICE" = true ]; then deploy_service_file; exit 0; fi
if [ "$VIEW_LOGS" = true ]; then view_logs; exit 0; fi if [ "$VIEW_LOGS" = true ]; then view_logs; exit 0; fi
if [ "$TAIL_LOGS" = true ]; then tail_logs; exit 0; fi if [ "$TAIL_LOGS" = true ]; then tail_logs; exit 0; fi
if [ "$VIEW_STATUS" = true ]; then status; exit 0; fi if [ "$VIEW_STATUS" = true ]; then status; exit 0; fi
@@ -107,19 +111,20 @@ if [ "$ROLLBACK" = true ]; then rollback; exit 0; fi
show_deploy_info show_deploy_info
confirm_deploy confirm_deploy
check_dependencies check_dependencies
check_env_file check_service_file
check_ssl_expiry
build_project build_project
backup_old_version backup_old_version
cleanup_old_backups 5 cleanup_old_backups 5
upload_binary upload_binary
upload_dir "配置文件" config
upload_dir "迁移文件" migrations upload_dir "迁移文件" migrations
upload_script "测试脚本" test_deployment.sh test_deployment.sh upload_script "测试脚本" test_deployment.sh test_deployment.sh
upload_script "清理脚本" scripts/cleanup_refresh_tokens.sh scripts/cleanup_refresh_tokens.sh upload_script "清理脚本" scripts/cleanup_refresh_tokens.sh scripts/cleanup_refresh_tokens.sh
upload_script "备份脚本" scripts/backup-db.sh scripts/backup-db.sh upload_script "备份脚本" scripts/backup-db.sh scripts/backup-db.sh
deploy_service_file
restart_service restart_service
run_migrations run_migrations

View File

@@ -62,71 +62,101 @@ EOF
curl -s -X POST "${WEBHOOK_URL}" -H "Content-Type: application/json" -d "${payload}" >/dev/null 2>&1 || true curl -s -X POST "${WEBHOOK_URL}" -H "Content-Type: application/json" -d "${payload}" >/dev/null 2>&1 || true
} }
# ---------- 环境配置 ---------- # ---------- 环境配置systemd service----------
check_env_file() { get_service_file_local() { echo "${SCRIPT_DIR}/deploy/rust-backend-${APP_ENV}.service"; }
log_step "检查远程 .env 文件..."
[ "$DRY_RUN" = true ] && { log_dry "test -f ${REMOTE_DIR}/.env"; return 0; }
if ! remote "test -f '${REMOTE_DIR}/.env'" 2>/dev/null; then check_service_file() {
log_error ".env 文件不存在: ${REMOTE_DIR}/.env"; exit 1 log_step "检查远程 systemd service 文件..."
[ "$DRY_RUN" = true ] && { log_dry "检查 ${SERVICE_NAME}"; return 0; }
if ! remote "systemctl cat '${SERVICE_NAME}'" &>/dev/null; then
log_error "systemd service ${SERVICE_NAME} 不存在,请先用 --deploy-service 安装"
exit 1
fi fi
local env_content; env_content=$(remote "cat '${REMOTE_DIR}/.env'" 2>/dev/null || echo "") local content; content=$(remote "systemctl cat '${SERVICE_NAME}'" 2>/dev/null || echo "")
[ -z "$env_content" ] && { log_error ".env 文件为空"; exit 1; } [ -z "$content" ] && { log_error "service 内容为空"; exit 1; }
local missing=0 ph=0 commented=0 local missing=0 ph=0
local required_vars=("JWT_SECRET" "WECHAT_SECRET" "WECHAT_APPID" "DATABASE_URL") local required_vars=("DATABASE_URL" "JWT_SECRET" "WECHAT_APPID" "WECHAT_SECRET")
[ "${APP_ENV}" = "production" ] && required_vars+=("SSL_KEY_PATH" "SSL_CERT_PATH") [ "${APP_ENV}" = "production" ] && required_vars+=("SSL_KEY_PATH" "SSL_CERT_PATH" "ALIPAY_APP_ID")
for var in "${required_vars[@]}"; do for var in "${required_vars[@]}"; do
local val; val=$(echo "$env_content" | grep -E "^${var}=" | head -1 | sed "s/^${var}=//") local val; val=$(echo "$content" | grep -E "^Environment=${var}=" | head -1 | sed "s/^Environment=${var}=//")
if [ -z "$val" ]; then if [ -z "$val" ]; then
log_error " 缺少: ${var}="; missing=$((missing+1)) log_error " 缺少: Environment=${var}="; missing=$((missing+1))
elif echo "$val" | grep -qiE "^(your_|placeholder|change_me|xxx|test_)"; then elif echo "$val" | grep -qiE "^(your_|placeholder|change_me|xxx|test_)"; then
log_warn " 占位符: ${var}=${val}"; ph=$((ph+1)) log_warn " 占位符: ${var}=${val}"; ph=$((ph+1))
fi fi
echo "$env_content" | grep -qE "^#\s*${var}=" && { log_warn " 已注释: # ${var}="; commented=$((commented+1)); }
done done
[ $missing -gt 0 ] && { log_error ".env 检查失败:${missing} 个缺失"; exit 1; } [ $missing -gt 0 ] && { log_error "service 配置检查失败:${missing} 个缺失"; exit 1; }
[ $ph -gt 0 ] || [ $commented -gt 0 ] && log_warn ".env 存在 ${ph} 个占位符${commented} 个注释行,建议修正" [ $ph -gt 0 ] && log_warn "service 存在 ${ph} 个占位符,部署后可能无法正常运行"
log_info ".env 文件检查通过" log_info "service 配置检查通过"
}
deploy_service_file() {
log_step "部署 systemd service 文件..."
[ "$DRY_RUN" = true ] && { log_dry "上传 ${SERVICE_FILE_LOCAL} → /etc/systemd/system/${SERVICE_NAME}"; return 0; }
if [ ! -f "$(get_service_file_local)" ]; then
log_error "本地 service 文件不存在: ${SERVICE_FILE_LOCAL}"
log_info "可通过 --init-env 生成模板"
exit 1
fi
rsync -avzP "$(get_service_file_local)" "${REMOTE_USER}@${REMOTE_HOST}:/etc/systemd/system/${SERVICE_NAME}"
remote "systemctl daemon-reload"
log_info "service 文件部署完成,使用 systemctl restart ${SERVICE_NAME} 重启"
} }
init_env() { init_env() {
log_step "初始化远程 .env 文件..." log_step "初始化 systemd service 文件(从模板生成)..."
[ "$DRY_RUN" = true ] && { log_dry " ${REMOTE_HOST}:${REMOTE_DIR}/.env 生成配置"; return 0; } [ "$DRY_RUN" = true ] && { log_dry "生成 ${SERVICE_FILE_LOCAL}"; return 0; }
local exists=false if [ -f "$(get_service_file_local)" ]; then
remote "test -f '${REMOTE_DIR}/.env'" 2>/dev/null && exists=true log_warn "${SERVICE_FILE_LOCAL} 已存在,继续将覆盖!"
if [ "$exists" = true ]; then
log_warn ".env 已存在,继续将覆盖!"
read -p "是否覆盖? [y/N] " -n 1 -r; echo read -p "是否覆盖? [y/N] " -n 1 -r; echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { log_warn "已取消"; return 0; } [[ ! $REPLY =~ ^[Yy]$ ]] && { log_warn "已取消"; return 0; }
remote "cp '${REMOTE_DIR}/.env' '${REMOTE_DIR}/.env.backup.$(date +%Y%m%d_%H%M%S)'" 2>/dev/null || true cp "$(get_service_file_local)" "${SERVICE_FILE_LOCAL}.bak.$(date +%Y%m%d_%H%M%S)"
fi fi
local jwt_secret local jwt_secret
jwt_secret=$(openssl rand -base64 32 2>/dev/null | tr -d '\n' || echo "change-me-$(date +%s)-$$") jwt_secret=$(openssl rand -base64 32 2>/dev/null | tr -d '\n' || echo "change-me-$(date +%s)-$$")
cat <<ENV_EOF | remote "cat > '${REMOTE_DIR}/.env' && chmod 600 '${REMOTE_DIR}/.env'" mkdir -p "${SCRIPT_DIR}/deploy"
# ASD Rust Backend - ${APP_ENV} 环境配置 cat > "$(get_service_file_local)" << SERVEOF
# 由 deploy.sh --init-env 自动生成于 $(date '+%Y-%m-%d %H:%M:%S') [Unit]
APP_ENV=${APP_ENV} Description=Rust Backend ${APP_ENV^} Service
DATABASE_URL=请替换为实际数据库连接字符串 After=network.target postgresql.service
WECHAT_APPID=请替换为实际微信 AppID Wants=network.target
WECHAT_SECRET=请替换为实际微信 Secret
JWT_SECRET=${jwt_secret}
RUST_LOG=info
FREE_USER_DATA_LIMIT=20
# ===== 支付宝支付配置(可选) =====
# ALIPAY_APP_ID=...
# ALIPAY_GATEWAY=...
ENV_EOF
log_info ".env 已生成,文件权限 600" [Service]
Type=simple
User=root
Group=root
WorkingDirectory=${REMOTE_DIR}
ExecStart=${REMOTE_DIR}/rust-backend
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=DATABASE_URL=请替换为实际数据库连接
Environment=WECHAT_APPID=请替换为实际微信 AppID
Environment=WECHAT_SECRET=请替换为实际微信 Secret
Environment=JWT_SECRET=${jwt_secret}
Environment=SSL_KEY_PATH=
Environment=SSL_CERT_PATH=
Environment=RUST_LOG=info
Environment=APP_VERSION=0.3.0
Environment=FREE_USER_DATA_LIMIT=20
SERVEOF
chmod 600 "$(get_service_file_local)"
log_info "service 模板已生成: ${SERVICE_FILE_LOCAL}"
log_warn "请手动填写 DATABASE_URL / WECHAT_APPID / WECHAT_SECRET" log_warn "请手动填写 DATABASE_URL / WECHAT_APPID / WECHAT_SECRET"
log_info "然后使用 ./deploy.sh ${APP_ENV} --deploy-service 上传"
} }
# ---------- 编译 ---------- # ---------- 编译 ----------
@@ -266,6 +296,11 @@ run_migrations() {
restart_service() { restart_service() {
log_step "重启服务 '${SERVICE_NAME}'..." log_step "重启服务 '${SERVICE_NAME}'..."
[ "$DRY_RUN" = true ] && { log_dry "systemctl restart ${SERVICE_NAME}"; return 0; } [ "$DRY_RUN" = true ] && { log_dry "systemctl restart ${SERVICE_NAME}"; return 0; }
# 部署前先确保 service 文件存在
if ! remote "systemctl cat '${SERVICE_NAME}'" &>/dev/null; then
log_warn "${SERVICE_NAME} 未安装,尝试部署 service 文件..."
deploy_service_file
fi
remote "systemctl restart ${SERVICE_NAME}" && log_info "服务重启成功" || log_error "服务重启失败!" remote "systemctl restart ${SERVICE_NAME}" && log_info "服务重启成功" || log_error "服务重启失败!"
sleep 3 sleep 3
} }
@@ -363,6 +398,25 @@ show_deploy_info() {
echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}========================================${NC}"
} }
# ---------- SSL 证书检查 ----------
check_ssl_expiry() {
[ "${APP_ENV}" != "production" ] && return 0 # 非生产环境跳过
log_step "检查 SSL 证书到期时间..."
[ "$DRY_RUN" = true ] && { log_dry "openssl x509 -checkend 2592000"; return 0; }
# 从 OpenResty 容器内读取证书
local cert_info; cert_info=$(remote "docker exec 1Panel-openresty-ABu5 openssl x509 -in /www/sites/${TEST_DOMAIN#https://}/ssl/fullchain.pem -noout -enddate -checkend 2592000 2>/dev/null" || echo "")
if echo "$cert_info" | grep -q "Certificate will expire"; then
local expiry; expiry=$(remote "docker exec 1Panel-openresty-ABu5 openssl x509 -in /www/sites/${TEST_DOMAIN#https://}/ssl/fullchain.pem -noout -enddate 2>/dev/null | cut -d= -f2" || echo "未知")
local days_left; days_left=$(remote "docker exec 1Panel-openresty-ABu5 openssl x509 -in /www/sites/${TEST_DOMAIN#https://}/ssl/fullchain.pem -noout -checkend 86400 2>/dev/null | grep -c 'will expire'" || echo "0")
log_warn "SSL 证书将于 ${expiry} 到期(不足 30 天)!"
[ "$days_left" -gt "0" ] && log_warn " 警告:证书将在 1 天内到期,请立即续期!"
else
log_info "SSL 证书有效期超过 30 天 ✅"
fi
}
# ---------- 依赖检查 ---------- # ---------- 依赖检查 ----------
check_dependencies() { check_dependencies() {
log_step "检查依赖..." log_step "检查依赖..."