refactor: deploy工具适配systemd service模式-移除.env依赖
This commit is contained in:
15
deploy.sh
15
deploy.sh
@@ -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
|
||||||
|
|
||||||
|
|||||||
134
lib/common.sh
134
lib/common.sh
@@ -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 "检查依赖..."
|
||||||
|
|||||||
Reference in New Issue
Block a user