Compare commits
3 Commits
v2026.07.0
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
3b20b75007 | |
|
|
9e88e65757 | |
|
|
3a4600ce5b |
249
deploy-ai.sh
249
deploy-ai.sh
|
|
@ -24,13 +24,38 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 颜色和日志
|
# 颜色和日志
|
||||||
# ============================================================
|
# ============================================================
|
||||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RED='\033[0;31m'; NC='\033[0m'
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; RED='\033[0;31m'; MAGENTA='\033[0;35m'; BOLD='\033[1m'; NC='\033[0m'
|
||||||
log() { printf "${GREEN}[✓]${NC} %s\n" "$1"; }
|
log() { printf "${GREEN}[✓]${NC} %s\n" "$1"; }
|
||||||
warn() { printf "${YELLOW}[!]${NC} %s\n" "$1"; }
|
warn() { printf "${YELLOW}[!]${NC} %s\n" "$1"; }
|
||||||
info() { printf "${CYAN}[i]${NC} %s\n" "$1"; }
|
info() { printf "${CYAN}[i]${NC} %s\n" "$1"; }
|
||||||
error() { printf "${RED}[✗]${NC} %s\n" "$1"; }
|
error() { printf "${RED}[✗]${NC} %s\n" "$1"; }
|
||||||
timing(){ printf " ${CYAN}⏱${NC} %s\n" "$1"; }
|
timing(){ printf " ${CYAN}⏱${NC} %s\n" "$1"; }
|
||||||
|
|
||||||
|
# 阶段显示函数
|
||||||
|
phase_num=0
|
||||||
|
phase_start_time=0
|
||||||
|
phase() {
|
||||||
|
local phase_name="$1"
|
||||||
|
phase_num=$((phase_num + 1))
|
||||||
|
phase_start_time=$(date +%s)
|
||||||
|
echo ""
|
||||||
|
printf "${MAGENTA}${BOLD}════════════════════════════════════════════════════════════${NC}\n"
|
||||||
|
printf "${MAGENTA}${BOLD} [阶段 ${phase_num}] ${phase_name}${NC}\n"
|
||||||
|
printf "${MAGENTA}${BOLD}════════════════════════════════════════════════════════════${NC}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
phase_done() {
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local duration=$((end_time - phase_start_time))
|
||||||
|
printf "${GREEN}${BOLD} ✓ 阶段 ${phase_num} 完成 (${duration}s)${NC}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
phase_fail() {
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local duration=$((end_time - phase_start_time))
|
||||||
|
printf "${RED}${BOLD} ✗ 阶段 ${phase_num} 失败 (${duration}s)${NC}\n"
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 检测 macOS 和 Linux sed 差异
|
# 检测 macOS 和 Linux sed 差异
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -359,6 +384,35 @@ sync_shared() {
|
||||||
ssh txjp "cd ${SHARED_REMOTE} && [ -f package.json ] && npm install --prefer-offline 2>&1 | tail -3" || warn "shared 依赖安装失败,部分站点可能构建异常"
|
ssh txjp "cd ${SHARED_REMOTE} && [ -f package.json ] && npm install --prefer-offline 2>&1 | tail -3" || warn "shared 依赖安装失败,部分站点可能构建异常"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# JWT_SECRET 一致性检查(所有 .tlyq.ai 子站点必须共用同一个密钥)
|
||||||
|
# 从共享文件读取权威值,校验站点 .env,不一致时自动修正
|
||||||
|
check_jwt_secret_consistency() {
|
||||||
|
local remote_dir="$1"
|
||||||
|
local jwt_secret
|
||||||
|
jwt_secret=$(ssh txjp "cat /root/docker/.shared_jwt_secret 2>/dev/null" 2>/dev/null || echo "")
|
||||||
|
if [[ -z "$jwt_secret" ]]; then
|
||||||
|
jwt_secret=$(openssl rand -hex 32)
|
||||||
|
ssh txjp "cat > /root/docker/.shared_jwt_secret && chmod 600 /root/docker/.shared_jwt_secret" << EOF
|
||||||
|
${jwt_secret}
|
||||||
|
EOF
|
||||||
|
info "已生成共享 JWT_SECRET(所有站点共用)"
|
||||||
|
fi
|
||||||
|
local current_jwt
|
||||||
|
current_jwt=$(ssh txjp "grep '^JWT_SECRET=' ${remote_dir}/.env 2>/dev/null | cut -d= -f2" 2>/dev/null || echo "")
|
||||||
|
if [[ -z "$current_jwt" ]]; then
|
||||||
|
ssh txjp "cat >> ${remote_dir}/.env" << EOF
|
||||||
|
JWT_SECRET=${jwt_secret}
|
||||||
|
EOF
|
||||||
|
info "已为站点写入 JWT_SECRET"
|
||||||
|
elif [[ "$current_jwt" != "$jwt_secret" ]]; then
|
||||||
|
warn "检测到 JWT_SECRET 与共享密钥不一致,已自动修正"
|
||||||
|
ssh txjp "sed -i '/^JWT_SECRET=/d' ${remote_dir}/.env"
|
||||||
|
ssh txjp "cat >> ${remote_dir}/.env" << EOF
|
||||||
|
JWT_SECRET=${jwt_secret}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
build_on_server() {
|
build_on_server() {
|
||||||
local extra_env="${1:-}"
|
local extra_env="${1:-}"
|
||||||
|
|
||||||
|
|
@ -418,7 +472,7 @@ build_on_server() {
|
||||||
--exclude='node_modules' --exclude='.next' --exclude='out' \
|
--exclude='node_modules' --exclude='.next' --exclude='out' \
|
||||||
--exclude='data' --exclude='./reports/*.docx' --exclude='docs' \
|
--exclude='data' --exclude='./reports/*.docx' --exclude='docs' \
|
||||||
--exclude='.env' --exclude='.env.local' --exclude='.env.development' --exclude='.env.production' \
|
--exclude='.env' --exclude='.env.local' --exclude='.env.development' --exclude='.env.production' \
|
||||||
--exclude='.git' --exclude='._*' . || {
|
--exclude='.git' --exclude='._*' --exclude='shared' . || {
|
||||||
error "打包失败"
|
error "打包失败"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
@ -443,10 +497,17 @@ build_on_server() {
|
||||||
rsync -a --delete \
|
rsync -a --delete \
|
||||||
--exclude='node_modules' --exclude='.next' --exclude='data' --exclude='/reports/*.docx' --exclude='docs' \
|
--exclude='node_modules' --exclude='.next' --exclude='data' --exclude='/reports/*.docx' --exclude='docs' \
|
||||||
--exclude='.env' --exclude='.env.local' --exclude='.env.development' --exclude='.env.production' \
|
--exclude='.env' --exclude='.env.local' --exclude='.env.development' --exclude='.env.production' \
|
||||||
--exclude='._*' \
|
--exclude='._*' --exclude='shared' \
|
||||||
/tmp/deploy_${SITE}/ ${REMOTE_DIR}/ && \
|
/tmp/deploy_${SITE}/ ${REMOTE_DIR}/ && \
|
||||||
rm -rf /tmp/deploy_${SITE}"
|
rm -rf /tmp/deploy_${SITE}"
|
||||||
|
|
||||||
|
# 确保 shared 符号链接正确(打包时已排除,需要重新创建)
|
||||||
|
ssh txjp "cd $REMOTE_DIR && if [ ! -L shared ]; then
|
||||||
|
rm -rf shared 2>/dev/null || true
|
||||||
|
ln -s ../shared shared
|
||||||
|
echo 'shared 符号链接已创建'
|
||||||
|
fi"
|
||||||
|
|
||||||
# 首次:上传 docker-compose.yml(新增 .next 卷挂载),重启容器应用
|
# 首次:上传 docker-compose.yml(新增 .next 卷挂载),重启容器应用
|
||||||
ssh txjp "cd $REMOTE_DIR && docker compose up -d"
|
ssh txjp "cd $REMOTE_DIR && docker compose up -d"
|
||||||
|
|
||||||
|
|
@ -546,33 +607,8 @@ build_on_server() {
|
||||||
# 先确保 .env 文件存在且权限为 600(防止后续写入期间权限暴露)
|
# 先确保 .env 文件存在且权限为 600(防止后续写入期间权限暴露)
|
||||||
ssh txjp "touch $REMOTE_DIR/.env && chmod 600 $REMOTE_DIR/.env" 2>/dev/null
|
ssh txjp "touch $REMOTE_DIR/.env && chmod 600 $REMOTE_DIR/.env" 2>/dev/null
|
||||||
|
|
||||||
# JWT_SECRET:所有 .tlyq.ai 子站点必须共用同一个密钥(方案 B:校验+按需修复)
|
# JWT_SECRET 一致性检查(所有 .tlyq.ai 子站点必须共用同一个密钥)
|
||||||
# 从服务器共享位置读取权威值(首次自动生成并持久化)
|
check_jwt_secret_consistency "$REMOTE_DIR"
|
||||||
JWT_SECRET=$(ssh txjp "cat /root/docker/.shared_jwt_secret 2>/dev/null" 2>/dev/null || echo "")
|
|
||||||
if [[ -z "$JWT_SECRET" ]]; then
|
|
||||||
JWT_SECRET=$(openssl rand -hex 32)
|
|
||||||
# 部署前备份旧共享文件(如存在)
|
|
||||||
ssh txjp "cp /root/docker/.shared_jwt_secret /root/docker/.shared_jwt_secret.bak.\$(date +%Y%m%d) 2>/dev/null || true"
|
|
||||||
ssh txjp "cat > /root/docker/.shared_jwt_secret && chmod 600 /root/docker/.shared_jwt_secret" << EOF
|
|
||||||
${JWT_SECRET}
|
|
||||||
EOF
|
|
||||||
info "已生成共享 JWT_SECRET(所有站点共用)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 校验站点 .env 中的 JWT_SECRET,不一致时自动修正(防御手动修改)
|
|
||||||
local current_jwt
|
|
||||||
current_jwt=$(ssh txjp "grep '^JWT_SECRET=' $REMOTE_DIR/.env 2>/dev/null | cut -d= -f2" 2>/dev/null || echo "")
|
|
||||||
if [[ -z "$current_jwt" ]]; then
|
|
||||||
ssh txjp "cat >> $REMOTE_DIR/.env" << EOF
|
|
||||||
JWT_SECRET=${JWT_SECRET}
|
|
||||||
EOF
|
|
||||||
elif [[ "$current_jwt" != "$JWT_SECRET" ]]; then
|
|
||||||
warn "检测到 JWT_SECRET 与共享密钥不一致,已自动修正"
|
|
||||||
ssh txjp "sed -i '/^JWT_SECRET=/d' $REMOTE_DIR/.env" # 删旧行(命令不含密钥)
|
|
||||||
ssh txjp "cat >> $REMOTE_DIR/.env" << EOF # heredoc 写新行
|
|
||||||
JWT_SECRET=${JWT_SECRET}
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
# OIDC_CLIENT_SECRET:从服务器现有 .env 读取(Authelia 配置存储的是 bcrypt 哈希,不可逆用)
|
# OIDC_CLIENT_SECRET:从服务器现有 .env 读取(Authelia 配置存储的是 bcrypt 哈希,不可逆用)
|
||||||
# 全程在服务器侧执行,避免 secret 跨 SSH 边界传输
|
# 全程在服务器侧执行,避免 secret 跨 SSH 边界传输
|
||||||
if [[ "$SITE" == "oa" || "$SITE" == "assets" || "$SITE" == "issue" || "$SITE" == "monitor" ]]; then
|
if [[ "$SITE" == "oa" || "$SITE" == "assets" || "$SITE" == "issue" || "$SITE" == "monitor" ]]; then
|
||||||
|
|
@ -610,6 +646,23 @@ EOF
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# INTERNAL_API_KEY:所有 .tlyq.ai 子站点必须共用同一个密钥
|
||||||
|
if ! ssh txjp "grep -q '^INTERNAL_API_KEY=' $REMOTE_DIR/.env 2>/dev/null"; then
|
||||||
|
local shared_key
|
||||||
|
shared_key=$(ssh txjp "cat /root/docker/.shared_internal_key 2>/dev/null" 2>/dev/null || echo "")
|
||||||
|
if [[ -z "$shared_key" ]]; then
|
||||||
|
shared_key=$(openssl rand -hex 16)
|
||||||
|
ssh txjp "cat > /root/docker/.shared_internal_key" << EOF
|
||||||
|
${shared_key}
|
||||||
|
EOF
|
||||||
|
info "已生成共享 INTERNAL_API_KEY"
|
||||||
|
fi
|
||||||
|
ssh txjp "cat >> $REMOTE_DIR/.env" << EOF
|
||||||
|
INTERNAL_API_KEY=${shared_key}
|
||||||
|
EOF
|
||||||
|
info "已为站点写入 INTERNAL_API_KEY"
|
||||||
|
fi
|
||||||
|
|
||||||
# 清理可能被上传的本地环境变量文件(.env.local 等会覆盖 .env 中的生产配置)
|
# 清理可能被上传的本地环境变量文件(.env.local 等会覆盖 .env 中的生产配置)
|
||||||
ssh txjp "rm -f $REMOTE_DIR/.env.local $REMOTE_DIR/.env.development $REMOTE_DIR/.env.production 2>/dev/null; echo '已清理本地环境文件'"
|
ssh txjp "rm -f $REMOTE_DIR/.env.local $REMOTE_DIR/.env.development $REMOTE_DIR/.env.production 2>/dev/null; echo '已清理本地环境文件'"
|
||||||
|
|
||||||
|
|
@ -715,30 +768,67 @@ EOF
|
||||||
# 执行部署
|
# 执行部署
|
||||||
# ============================================================
|
# ============================================================
|
||||||
echo ""
|
echo ""
|
||||||
|
printf "${BOLD}${CYAN}=========================================${NC}\n"
|
||||||
|
printf "${BOLD}${CYAN} 开始部署: ${SITE}.tlyq.ai${NC}\n"
|
||||||
|
printf "${BOLD}${CYAN}=========================================${NC}\n"
|
||||||
|
DEPLOY_START=$(date +%s)
|
||||||
case "$SITE" in
|
case "$SITE" in
|
||||||
www)
|
www)
|
||||||
|
phase "打包配置文件"
|
||||||
scp /Users/niuniu/programs/docker/www-ai/docker-compose.yml txjp:$REMOTE_DIR/docker-compose.yml
|
scp /Users/niuniu/programs/docker/www-ai/docker-compose.yml txjp:$REMOTE_DIR/docker-compose.yml
|
||||||
scp /Users/niuniu/programs/docker/www-ai/nginx.conf txjp:$REMOTE_DIR/nginx.conf
|
scp /Users/niuniu/programs/docker/www-ai/nginx.conf txjp:$REMOTE_DIR/nginx.conf
|
||||||
build_on_server || exit 1 ;;
|
phase_done
|
||||||
|
|
||||||
|
phase "构建与部署"
|
||||||
|
build_on_server || { phase_fail; exit 1; }
|
||||||
|
phase_done ;;
|
||||||
cloud)
|
cloud)
|
||||||
info "上传静态文件..."
|
phase "上传静态文件"
|
||||||
scp "$LOCAL_DIR"/index.html txjp:$REMOTE_DIR/index.html || { error "cloud index.html 上传失败"; exit 1; }
|
info "上传 index.html..."
|
||||||
|
scp "$LOCAL_DIR"/index.html txjp:$REMOTE_DIR/index.html || { error "cloud index.html 上传失败"; phase_fail; exit 1; }
|
||||||
scp "$LOCAL_DIR"/nginx.conf txjp:$REMOTE_DIR/nginx.conf 2>/dev/null || true
|
scp "$LOCAL_DIR"/nginx.conf txjp:$REMOTE_DIR/nginx.conf 2>/dev/null || true
|
||||||
scp /Users/niuniu/programs/docker/cloud-ai/docker-compose.yml txjp:$REMOTE_DIR/docker-compose.yml 2>/dev/null || true
|
scp /Users/niuniu/programs/docker/cloud-ai/docker-compose.yml txjp:$REMOTE_DIR/docker-compose.yml 2>/dev/null || true
|
||||||
ssh txjp "docker restart ${SITE}-ai" ;;
|
phase_done
|
||||||
|
|
||||||
|
phase "重启容器"
|
||||||
|
ssh txjp "docker restart ${SITE}-ai"
|
||||||
|
phase_done ;;
|
||||||
token)
|
token)
|
||||||
info "上传静态文件..."
|
phase "上传静态文件"
|
||||||
scp "$LOCAL_DIR"/index.html txjp:$REMOTE_DIR/index.html || { error "token index.html 上传失败"; exit 1; }
|
info "上传 index.html..."
|
||||||
|
scp "$LOCAL_DIR"/index.html txjp:$REMOTE_DIR/index.html || { error "token index.html 上传失败"; phase_fail; exit 1; }
|
||||||
scp "$LOCAL_DIR"/nginx.conf txjp:$REMOTE_DIR/nginx.conf 2>/dev/null || true
|
scp "$LOCAL_DIR"/nginx.conf txjp:$REMOTE_DIR/nginx.conf 2>/dev/null || true
|
||||||
scp /Users/niuniu/programs/docker/token-ai/docker-compose.yml txjp:$REMOTE_DIR/docker-compose.yml 2>/dev/null || true
|
scp /Users/niuniu/programs/docker/token-ai/docker-compose.yml txjp:$REMOTE_DIR/docker-compose.yml 2>/dev/null || true
|
||||||
ssh txjp "docker restart ${SITE}-ai" ;;
|
phase_done
|
||||||
|
|
||||||
|
phase "重启容器"
|
||||||
|
ssh txjp "docker restart ${SITE}-ai"
|
||||||
|
phase_done ;;
|
||||||
issue)
|
issue)
|
||||||
build_on_server "http://assets-ai:3000/api" || exit 1
|
phase "构建与部署"
|
||||||
info "部署 nginx 路由..."
|
build_on_server "http://assets-ai:3000/api" || { phase_fail; exit 1; }
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
phase "部署 nginx 路由"
|
||||||
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/issue-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/issue-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
||||||
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
# 注入 LLDAP admin 密码(LDAP 直接登录需要)
|
||||||
|
phase "注入 LLDAP admin 密码"
|
||||||
|
info "注入 LLDAP admin 密码到 issue-ai .env..."
|
||||||
|
ssh txjp "
|
||||||
|
LLDAP_ADMIN_PASS=\$(docker exec lldap printenv LLDAP_ADMIN_PASSWORD 2>/dev/null || echo '')
|
||||||
|
if [ -n \"\$LLDAP_ADMIN_PASS\" ]; then
|
||||||
|
grep -q '^LLDAP_ADMIN_PASSWORD=' $REMOTE_DIR/.env 2>/dev/null \
|
||||||
|
&& sed -i \"s|^LLDAP_ADMIN_PASSWORD=.*|LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN_PASS|\" $REMOTE_DIR/.env \
|
||||||
|
|| echo \"LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN_PASS\" >> $REMOTE_DIR/.env
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
phase_done
|
||||||
|
|
||||||
# 验证 issue→assets API 连通性(带重试,应对同时部署时目标容器暂不可达)
|
# 验证 issue→assets API 连通性(带重试,应对同时部署时目标容器暂不可达)
|
||||||
|
phase "检查 API 连通性"
|
||||||
info "检查 issue→assets API 连通性..."
|
info "检查 issue→assets API 连通性..."
|
||||||
ssh txjp 'cat > /tmp/check-conn.js' << 'CHECKEOF'
|
ssh txjp 'cat > /tmp/check-conn.js' << 'CHECKEOF'
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
|
|
@ -766,14 +856,33 @@ CHECKEOF
|
||||||
echo " $CONN_CHECK"
|
echo " $CONN_CHECK"
|
||||||
else
|
else
|
||||||
log "issue→assets API 连通性正常"
|
log "issue→assets API 连通性正常"
|
||||||
fi ;;
|
fi
|
||||||
|
phase_done ;;
|
||||||
assets)
|
assets)
|
||||||
build_on_server || exit 1
|
phase "构建与部署"
|
||||||
info "部署 nginx 路由..."
|
build_on_server || { phase_fail; exit 1; }
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
phase "部署 nginx 路由"
|
||||||
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/assets-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/assets-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
||||||
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
# 注入 LLDAP admin 密码(LDAP 直接登录需要)
|
||||||
|
phase "注入 LLDAP admin 密码"
|
||||||
|
info "注入 LLDAP admin 密码到 assets-ai .env..."
|
||||||
|
ssh txjp "
|
||||||
|
LLDAP_ADMIN_PASS=\$(docker exec lldap printenv LLDAP_ADMIN_PASSWORD 2>/dev/null || echo '')
|
||||||
|
if [ -n \"\$LLDAP_ADMIN_PASS\" ]; then
|
||||||
|
grep -q '^LLDAP_ADMIN_PASSWORD=' $REMOTE_DIR/.env 2>/dev/null \
|
||||||
|
&& sed -i \"s|^LLDAP_ADMIN_PASSWORD=.*|LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN_PASS|\" $REMOTE_DIR/.env \
|
||||||
|
|| echo \"LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN_PASS\" >> $REMOTE_DIR/.env
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
phase_done
|
||||||
|
|
||||||
# 验证 assets→issue API 连通性(带重试,应对同时部署时目标容器暂不可达)
|
# 验证 assets→issue API 连通性(带重试,应对同时部署时目标容器暂不可达)
|
||||||
|
phase "检查 API 连通性"
|
||||||
info "检查 assets→issue API 连通性..."
|
info "检查 assets→issue API 连通性..."
|
||||||
ssh txjp 'cat > /tmp/check-conn.js' << 'CHECKEOF'
|
ssh txjp 'cat > /tmp/check-conn.js' << 'CHECKEOF'
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
|
|
@ -801,9 +910,14 @@ CHECKEOF
|
||||||
echo " $CONN_CHECK"
|
echo " $CONN_CHECK"
|
||||||
else
|
else
|
||||||
log "assets→issue API 连通性正常"
|
log "assets→issue API 连通性正常"
|
||||||
fi ;;
|
fi
|
||||||
|
phase_done ;;
|
||||||
oa)
|
oa)
|
||||||
build_on_server || exit 1
|
phase "构建与部署"
|
||||||
|
build_on_server || { phase_fail; exit 1; }
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
phase "配置环境变量"
|
||||||
# 注入 LLDAP admin 密码(OA OIDC callback 需要验证 LLDAP 用户存在性)
|
# 注入 LLDAP admin 密码(OA OIDC callback 需要验证 LLDAP 用户存在性)
|
||||||
info "注入 LLDAP admin 密码到 OA .env..."
|
info "注入 LLDAP admin 密码到 OA .env..."
|
||||||
ssh txjp "
|
ssh txjp "
|
||||||
|
|
@ -814,15 +928,20 @@ CHECKEOF
|
||||||
|| echo \"LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN_PASS\" >> $REMOTE_DIR/.env
|
|| echo \"LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN_PASS\" >> $REMOTE_DIR/.env
|
||||||
fi
|
fi
|
||||||
"
|
"
|
||||||
info "部署 nginx 配置..."
|
phase_done
|
||||||
|
|
||||||
|
phase "部署 nginx 配置"
|
||||||
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/oa-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/oa-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
||||||
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
phase "同步数据"
|
||||||
# 批量同步 LLDAP email 至各站点(新服务器首次部署填充历史数据)
|
# 批量同步 LLDAP email 至各站点(新服务器首次部署填充历史数据)
|
||||||
info "同步 LLDAP 邮箱至各站点..."
|
info "同步 LLDAP 邮箱至各站点..."
|
||||||
ssh txjp "node /root/docker/oa-ai/scripts/sync-emails-to-sites.js 2>/dev/null" || warn "邮箱同步失败(不影响 OA 主流程)"
|
ssh txjp "node /root/docker/oa-ai/scripts/sync-emails-to-sites.js 2>/dev/null" || warn "邮箱同步失败(不影响 OA 主流程)"
|
||||||
;;
|
phase_done ;;
|
||||||
ldap)
|
ldap)
|
||||||
info "部署 LLDAP..."
|
phase "部署 LLDAP"
|
||||||
scp "$LOCAL_DIR/docker-compose.yml" txjp:$REMOTE_DIR/docker-compose.yml
|
scp "$LOCAL_DIR/docker-compose.yml" txjp:$REMOTE_DIR/docker-compose.yml
|
||||||
scp "$LOCAL_DIR/Dockerfile" txjp:$REMOTE_DIR/Dockerfile
|
scp "$LOCAL_DIR/Dockerfile" txjp:$REMOTE_DIR/Dockerfile
|
||||||
ssh txjp "cd $REMOTE_DIR && \
|
ssh txjp "cd $REMOTE_DIR && \
|
||||||
|
|
@ -850,28 +969,35 @@ CHECKEOF
|
||||||
chmod 600 .env && \
|
chmod 600 .env && \
|
||||||
sed -i '/3890:3890/d' docker-compose.yml 2>/dev/null || true && \
|
sed -i '/3890:3890/d' docker-compose.yml 2>/dev/null || true && \
|
||||||
docker compose up -d --build && docker compose restart"
|
docker compose up -d --build && docker compose restart"
|
||||||
log "LLDAP 已部署" ;;
|
log "LLDAP 已部署"
|
||||||
|
phase_done ;;
|
||||||
gitea)
|
gitea)
|
||||||
info "部署 Gitea..."
|
phase "部署 Gitea"
|
||||||
scp "$LOCAL_DIR/docker-compose.yml" txjp:$REMOTE_DIR/docker-compose.yml
|
scp "$LOCAL_DIR/docker-compose.yml" txjp:$REMOTE_DIR/docker-compose.yml
|
||||||
# 同步证书目录(若本地存在)
|
# 同步证书目录(若本地存在)
|
||||||
[ -d "$LOCAL_DIR/cert" ] && scp -r "$LOCAL_DIR"/cert/* txjp:$REMOTE_DIR/cert/ 2>/dev/null || true
|
[ -d "$LOCAL_DIR/cert" ] && scp -r "$LOCAL_DIR"/cert/* txjp:$REMOTE_DIR/cert/ 2>/dev/null || true
|
||||||
ssh txjp "cd $REMOTE_DIR && docker compose up -d && docker restart nginx-ai"
|
ssh txjp "cd $REMOTE_DIR && docker compose up -d && docker restart nginx-ai"
|
||||||
log "Gitea 已部署" ;;
|
log "Gitea 已部署"
|
||||||
|
phase_done ;;
|
||||||
sso)
|
sso)
|
||||||
deploy_sso ;;
|
phase "部署 Authelia SSO"
|
||||||
|
deploy_sso
|
||||||
|
phase_done ;;
|
||||||
monitor)
|
monitor)
|
||||||
build_on_server || exit 1
|
phase "构建与部署"
|
||||||
info "部署 nginx 配置..."
|
build_on_server || { phase_fail; exit 1; }
|
||||||
|
phase_done
|
||||||
|
|
||||||
|
phase "部署 nginx 配置"
|
||||||
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/monitor-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
ssh txjp "cp $REMOTE_DIR/../nginx-proxy-ai/conf.d/monitor-ai.conf /root/docker/nginx-proxy-ai/conf.d/ 2>/dev/null || true
|
||||||
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload" ;;
|
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload"
|
||||||
|
phase_done ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 运行时合规检查(LESSONS-LEARNED)
|
# 运行时合规检查(LESSONS-LEARNED)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
echo ""
|
phase "运行时合规检查"
|
||||||
info "运行时合规检查..."
|
|
||||||
|
|
||||||
# LL #7: SSL 证书覆盖目标域名(仅 tlyq.ai 站点)
|
# LL #7: SSL 证书覆盖目标域名(仅 tlyq.ai 站点)
|
||||||
if [[ "$SITE" != "ldap" ]]; then
|
if [[ "$SITE" != "ldap" ]]; then
|
||||||
|
|
@ -925,8 +1051,7 @@ fi
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 验证
|
# 验证
|
||||||
# ============================================================
|
# ============================================================
|
||||||
echo ""
|
phase "验证部署"
|
||||||
log "验证部署..."
|
|
||||||
BUILD_END=$(date +%s)
|
BUILD_END=$(date +%s)
|
||||||
TOTAL_DUR=$((BUILD_END - BUILD_START))
|
TOTAL_DUR=$((BUILD_END - BUILD_START))
|
||||||
|
|
||||||
|
|
@ -950,9 +1075,15 @@ else
|
||||||
STATUS=$(ssh txjp "curl -s -o /dev/null -w '%{http_code}' '$URL' 2>/dev/null" 2>/dev/null || echo "???")
|
STATUS=$(ssh txjp "curl -s -o /dev/null -w '%{http_code}' '$URL' 2>/dev/null" 2>/dev/null || echo "???")
|
||||||
fi
|
fi
|
||||||
if [[ "$STATUS" == "200" || "$STATUS" == "307" || "$STATUS" == "301" ]]; then
|
if [[ "$STATUS" == "200" || "$STATUS" == "307" || "$STATUS" == "301" ]]; then
|
||||||
log "部署成功!总耗时: ${TOTAL_DUR}s | 访问 $URL"
|
phase_done
|
||||||
|
echo ""
|
||||||
|
printf "${GREEN}${BOLD}════════════════════════════════════════════════════════════${NC}\n"
|
||||||
|
printf "${GREEN}${BOLD} 🎉 部署成功!${NC}\n"
|
||||||
|
printf "${GREEN}${BOLD} 总耗时: ${TOTAL_DUR}s | 访问: $URL${NC}\n"
|
||||||
|
printf "${GREEN}${BOLD}════════════════════════════════════════════════════════════${NC}\n"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
|
phase_done
|
||||||
warn "返回状态码: $STATUS,请检查"
|
warn "返回状态码: $STATUS,请检查"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue