fix: deploy-ai.sh 全面审查修复(15项)
- C1: SSO 首次部署自动生成 JWKS 密钥 - C2: 首次部署自动生成 JWT_SECRET / OIDC_CLIENT_SECRET / LOCALADMIN_PASSWORD - H1: LDAP 密码 sed 改用 | 分隔符 - H2: LLDAP 密钥写入改用 printf - H3: OIDC client_secret 从 Authelia 配置自动注入 - H4: to_prod/to_local 改为白名单模式 - M1: 构建失败 return 1 阻断部署 - M2: 部署后清理远程 /tmp 临时文件 - M4: 移除构建日志本地保存 - L1: 修复缩进不一致 - L2: AUTHELIA_URL 仅对 OIDC 站点注入 - L3: monitor nginx reload 前添加 nginx -t - L5: curl -k 添加说明注释 - L6: 磁盘紧急清理时截断容器日志
This commit is contained in:
parent
1232efa191
commit
cd552a1a4d
103
deploy-ai.sh
103
deploy-ai.sh
|
|
@ -66,9 +66,9 @@ echo " 3) token.tlyq.ai (Token工厂)"
|
|||
echo " 4) issue.tlyq.ai (工单系统)"
|
||||
echo " 5) assets.tlyq.ai (资产管理系统)"
|
||||
echo " 6) oa.tlyq.ai (OA 统一门户 — 含 nginx 配置)"
|
||||
echo " 7) ldap.tlyq.ai (LLDAP 用户目录服务)"
|
||||
echo " 8) git.tlyq.ai (Gitea 代码托管)"
|
||||
echo " 9) sso.tlyq.ai (Authelia SSO — 统一认证中心)"
|
||||
echo " 7) ldap.tlyq.ai (LLDAP 用户目录服务)"
|
||||
echo " 8) git.tlyq.ai (Gitea 代码托管)"
|
||||
echo " 9) sso.tlyq.ai (Authelia SSO — 统一认证中心)"
|
||||
echo " 10) monitor.tlyq.ai (告警监控中心)"
|
||||
echo ""
|
||||
printf "请输入编号 (1/2/3/4/5/6/7/8/9/10): "
|
||||
|
|
@ -82,8 +82,8 @@ case "$choice" in
|
|||
4) SITE="issue"; LOCAL_DIR="/Users/niuniu/programs/docker/issue-ai"; REMOTE_DIR="/root/docker/issue-ai"; CONTAINER="issue-ai" ;;
|
||||
5) SITE="assets"; LOCAL_DIR="/Users/niuniu/programs/docker/assets-ai"; REMOTE_DIR="/root/docker/assets-ai"; CONTAINER="assets-ai" ;;
|
||||
6) SITE="oa"; LOCAL_DIR="/Users/niuniu/programs/docker/oa-ai"; REMOTE_DIR="/root/docker/oa-ai"; CONTAINER="oa-ai" ;;
|
||||
7) SITE="ldap"; LOCAL_DIR="/Users/niuniu/programs/docker/ldap-ai"; REMOTE_DIR="/root/docker/ldap-ai"; CONTAINER="lldap" ;;
|
||||
8) SITE="gitea"; LOCAL_DIR="/Users/niuniu/programs/docker/gitea-ai"; REMOTE_DIR="/root/docker/gitea-ai"; CONTAINER="gitea-ai" ;;
|
||||
7) SITE="ldap"; LOCAL_DIR="/Users/niuniu/programs/docker/ldap-ai"; REMOTE_DIR="/root/docker/ldap-ai"; CONTAINER="lldap" ;;
|
||||
8) SITE="gitea"; LOCAL_DIR="/Users/niuniu/programs/docker/gitea-ai"; REMOTE_DIR="/root/docker/gitea-ai"; CONTAINER="gitea-ai" ;;
|
||||
9) SITE="sso"; LOCAL_DIR="/Users/niuniu/programs/docker/sso-ai"; REMOTE_DIR="/root/docker/sso-ai"; CONTAINER="authelia" ;;
|
||||
10) SITE="monitor"; LOCAL_DIR="/Users/niuniu/programs/docker/monitor-ai"; REMOTE_DIR="/root/docker/monitor-ai"; CONTAINER="monitor-ai" ;;
|
||||
*) echo "无效选择"; exit 1 ;;
|
||||
|
|
@ -161,8 +161,8 @@ replace_oa_urls() {
|
|||
fi
|
||||
}
|
||||
|
||||
to_prod() { [[ "$SITE" != "issue" && "$SITE" != "assets" && "$SITE" != "oa" ]] && replace_url prod; }
|
||||
to_local() { [[ "$SITE" != "issue" && "$SITE" != "assets" && "$SITE" != "oa" ]] && replace_url local; }
|
||||
to_prod() { [[ "$SITE" == "www" || "$SITE" == "cloud" || "$SITE" == "token" ]] && replace_url prod; }
|
||||
to_local() { [[ "$SITE" == "www" || "$SITE" == "cloud" || "$SITE" == "token" ]] && replace_url local; }
|
||||
trap 'to_local; [[ "$SITE" == "oa" ]] && replace_oa_urls local' EXIT
|
||||
[[ "$SITE" == "oa" ]] && replace_oa_urls prod
|
||||
[[ "$SITE" != "issue" && "$SITE" != "assets" && "$SITE" != "oa" ]] && to_prod
|
||||
|
|
@ -209,7 +209,7 @@ ENVEOF
|
|||
log "注入 LDAP 密码和 JWKS 密钥..."
|
||||
ssh txjp "cd /root/docker/sso-ai && \
|
||||
LLDAP_PASSWORD=\$(docker exec lldap printenv LLDAP_ADMIN_PASSWORD) && \
|
||||
sed -i \"s/PRODUCTION_LDAP_PASSWORD/\$LLDAP_PASSWORD/\" config/authelia/configuration.yml 2>/dev/null || true && \
|
||||
sed -i \"s|PRODUCTION_LDAP_PASSWORD|\$LLDAP_PASSWORD|\" config/authelia/configuration.yml 2>/dev/null || true && \
|
||||
# 如果 JWKS key 仍是占位符,从现有配置或生成新密钥
|
||||
if grep -q 'OIDC_JWKS_KEY_PLACEHOLDER' config/authelia/configuration.yml; then
|
||||
EXISTING_KEY=\$(cat /root/docker/sso-ai/config/authelia/configuration.yml.bak 2>/dev/null | sed -n '/key: |/,/-----END/{p}' || true)
|
||||
|
|
@ -433,10 +433,12 @@ build_on_server() {
|
|||
|| echo 'NEXT_PUBLIC_ASSETS_URL=https://assets.tlyq.ai' >> $REMOTE_DIR/.env"
|
||||
fi
|
||||
|
||||
# 确保 AUTHELIA_URL 已设置(退出登录 cookie domain 依赖此变量)
|
||||
ssh txjp "grep -q 'AUTHELIA_URL' $REMOTE_DIR/.env 2>/dev/null \
|
||||
&& sed -i 's|AUTHELIA_URL=.*|AUTHELIA_URL=https://sso.tlyq.ai|' $REMOTE_DIR/.env 2>/dev/null \
|
||||
|| echo 'AUTHELIA_URL=https://sso.tlyq.ai' >> $REMOTE_DIR/.env"
|
||||
# 确保 AUTHELIA_URL 已设置(仅对使用 OIDC 的站点)
|
||||
if [[ "$SITE" == "oa" || "$SITE" == "assets" || "$SITE" == "issue" || "$SITE" == "monitor" ]]; then
|
||||
ssh txjp "grep -q 'AUTHELIA_URL' $REMOTE_DIR/.env 2>/dev/null \
|
||||
&& sed -i 's|AUTHELIA_URL=.*|AUTHELIA_URL=https://sso.tlyq.ai|' $REMOTE_DIR/.env 2>/dev/null \
|
||||
|| echo 'AUTHELIA_URL=https://sso.tlyq.ai' >> $REMOTE_DIR/.env"
|
||||
fi
|
||||
|
||||
# OIDC 环境变量配置(oa/assets/issue/monitor 站点)
|
||||
if [[ "$SITE" == "oa" || "$SITE" == "assets" || "$SITE" == "issue" || "$SITE" == "monitor" ]]; then
|
||||
|
|
@ -487,6 +489,36 @@ build_on_server() {
|
|||
done < /tmp/.env.prod.template && rm -f /tmp/.env.prod.template"
|
||||
fi
|
||||
|
||||
# 自动生成缺失的密钥(首次部署场景)
|
||||
# JWT_SECRET:所有站点都需要
|
||||
if ! ssh txjp "grep -q '^JWT_SECRET=' $REMOTE_DIR/.env 2>/dev/null"; then
|
||||
JWT_GEN=$(openssl rand -hex 32)
|
||||
ssh txjp "echo 'JWT_SECRET=${JWT_GEN}' >> $REMOTE_DIR/.env"
|
||||
info "已自动生成 JWT_SECRET"
|
||||
fi
|
||||
# OIDC_CLIENT_SECRET:从 Authelia 配置读取或警告
|
||||
if [[ "$SITE" == "oa" || "$SITE" == "assets" || "$SITE" == "issue" || "$SITE" == "monitor" ]]; then
|
||||
if ! ssh txjp "grep -q '^OIDC_CLIENT_SECRET=' $REMOTE_DIR/.env 2>/dev/null"; then
|
||||
# 尝试从 Authelia 配置读取对应的 client_secret 哈希
|
||||
OIDC_SECRET_KEY="${SITE}_OIDC_SECRET"
|
||||
OIDC_SECRET_VAL=$(ssh txjp "grep -A1 'client_id:.*${OIDC_ID}' /root/docker/sso-ai/config/authelia/configuration.yml 2>/dev/null | grep client_secret | head -1 | sed \"s/.*client_secret: *'\\(.*\\)'/\\1/\" || true" 2>/dev/null)
|
||||
if [[ -n "$OIDC_SECRET_VAL" && "$OIDC_SECRET_VAL" != *"PLACEHOLDER"* ]]; then
|
||||
ssh txjp "echo 'OIDC_CLIENT_SECRET=${OIDC_SECRET_VAL}' >> $REMOTE_DIR/.env"
|
||||
info "已从 Authelia 配置注入 OIDC_CLIENT_SECRET"
|
||||
else
|
||||
warn "OIDC_CLIENT_SECRET 未设置,请从 Authelia 配置获取并手动配置"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
# LOCALADMIN_PASSWORD:monitor 专属
|
||||
if [[ "$SITE" == "monitor" ]]; then
|
||||
if ! ssh txjp "grep -q '^LOCALADMIN_PASSWORD=' $REMOTE_DIR/.env 2>/dev/null"; then
|
||||
LOCALADMIN_GEN=$(openssl rand -hex 16)
|
||||
ssh txjp "echo 'LOCALADMIN_PASSWORD=${LOCALADMIN_GEN}' >> $REMOTE_DIR/.env"
|
||||
info "已自动生成 LOCALADMIN_PASSWORD"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 清理可能被上传的本地环境变量文件(.env.local 等会覆盖 .env 中的生产配置)
|
||||
ssh txjp "rm -f $REMOTE_DIR/.env.local $REMOTE_DIR/.env.development $REMOTE_DIR/.env.production 2>/dev/null; echo '已清理本地环境文件'"
|
||||
|
||||
|
|
@ -499,7 +531,6 @@ build_on_server() {
|
|||
# 使用 tee 避免管道导致退出码丢失,并过滤无用输出
|
||||
ssh txjp "cd ${REMOTE_DIR} && npm run build 2>&1" | \
|
||||
grep -vE "^(info|warn|npm warn|audited|packages|funding|vulnerability|npm notice|New major)" | \
|
||||
tee /tmp/build_output.txt | \
|
||||
tail -15
|
||||
build_exit_code=${PIPESTATUS[0]}
|
||||
build_end=$(date +%s)
|
||||
|
|
@ -507,7 +538,8 @@ build_on_server() {
|
|||
timing "构建耗时: ${build_dur}s"
|
||||
|
||||
if [[ "$build_exit_code" -ne 0 ]]; then
|
||||
warn "构建可能有警告,请检查上面的输出"
|
||||
error "构建失败(退出码: $build_exit_code),中止部署"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 清理 standalone 中的 musl 原生模块(Alpine builder 残留,Debian runner 不兼容)
|
||||
|
|
@ -550,6 +582,8 @@ build_on_server() {
|
|||
if [ "$DISK_USAGE" -gt 95 ]; then
|
||||
warn "磁盘使用率 ${DISK_USAGE}%,执行紧急清理"
|
||||
ssh txjp "docker builder prune -f 2>/dev/null || true"
|
||||
# 截断容器日志(占磁盘大户)
|
||||
ssh txjp "truncate -s 0 /var/lib/docker/containers/*/\*-json.log 2>/dev/null || true"
|
||||
elif [ "$DISK_USAGE" -gt 85 ]; then
|
||||
info "磁盘使用率 ${DISK_USAGE}%,执行激进清理(6小时)"
|
||||
ssh txjp "docker builder prune -f --filter 'until=6h' 2>/dev/null || true"
|
||||
|
|
@ -563,6 +597,9 @@ build_on_server() {
|
|||
|
||||
# 8. 保存快照
|
||||
ssh txjp "echo '$local_md5' > ${SNAPSHOT_FILE}"
|
||||
|
||||
# 9. 清理远程临时文件(源码包、连通性检查脚本)
|
||||
ssh txjp "rm -f /tmp/${SITE}.tar.gz /tmp/shared-deploy.tar.gz /tmp/check-conn.js /tmp/.env.prod.template 2>/dev/null || true"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -659,19 +696,24 @@ CHECKEOF
|
|||
info "同步 LLDAP 邮箱至各站点..."
|
||||
ssh txjp "node /root/docker/oa-ai/scripts/sync-emails-to-sites.js 2>/dev/null" || warn "邮箱同步失败(不影响 OA 主流程)"
|
||||
;;
|
||||
ldap)
|
||||
info "部署 LLDAP..."
|
||||
scp "$LOCAL_DIR/docker-compose.yml" txjp:$REMOTE_DIR/docker-compose.yml
|
||||
scp "$LOCAL_DIR/Dockerfile" txjp:$REMOTE_DIR/Dockerfile
|
||||
ssh txjp "cd $REMOTE_DIR && \
|
||||
ldap)
|
||||
info "部署 LLDAP..."
|
||||
scp "$LOCAL_DIR/docker-compose.yml" txjp:$REMOTE_DIR/docker-compose.yml
|
||||
scp "$LOCAL_DIR/Dockerfile" txjp:$REMOTE_DIR/Dockerfile
|
||||
ssh txjp "cd $REMOTE_DIR && \
|
||||
if [ ! -f .env ]; then \
|
||||
echo 'LLDAP_JWT_SECRET='\$(docker exec lldap printenv LLDAP_JWT_SECRET 2>/dev/null) > .env && \
|
||||
echo 'LLDAP_LDAP_USER_PASS='\$(docker exec lldap printenv LLDAP_LDAP_USER_PASS 2>/dev/null) >> .env && \
|
||||
echo 'LLDAP_LDAP_BASE_DN=dc=tlyq,dc=ai' >> .env && \
|
||||
echo 'LLDAP_HTTP_PORT=17170' >> .env && \
|
||||
echo 'LLDAP_LDAP_PORT=3890' >> .env && \
|
||||
echo 'LLDAP_DATABASE_PATH=/data/users.db' >> .env && \
|
||||
echo 'LLDAP_ADMIN_PASSWORD='\$(docker exec lldap printenv LLDAP_ADMIN_PASSWORD 2>/dev/null) >> .env && \
|
||||
LLDAP_JWT=\$(docker exec lldap printenv LLDAP_JWT_SECRET 2>/dev/null) && \
|
||||
LLDAP_PASS=\$(docker exec lldap printenv LLDAP_LDAP_USER_PASS 2>/dev/null) && \
|
||||
LLDAP_ADMIN=\$(docker exec lldap printenv LLDAP_ADMIN_PASSWORD 2>/dev/null) && \
|
||||
printf '%s\n' \
|
||||
\"LLDAP_JWT_SECRET=\$LLDAP_JWT\" \
|
||||
\"LLDAP_LDAP_USER_PASS=\$LLDAP_PASS\" \
|
||||
\"LLDAP_LDAP_BASE_DN=dc=tlyq,dc=ai\" \
|
||||
\"LLDAP_HTTP_PORT=17170\" \
|
||||
\"LLDAP_LDAP_PORT=3890\" \
|
||||
\"LLDAP_DATABASE_PATH=/data/users.db\" \
|
||||
\"LLDAP_ADMIN_PASSWORD=\$LLDAP_ADMIN\" \
|
||||
> .env && \
|
||||
fi && \
|
||||
sed -i '/3890:3890/d' docker-compose.yml 2>/dev/null || true && \
|
||||
docker compose up -d --build && docker compose restart"
|
||||
|
|
@ -687,7 +729,7 @@ CHECKEOF
|
|||
build_on_server || exit 1
|
||||
info "部署 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
|
||||
docker exec nginx-ai nginx -s reload 2>/dev/null || true" ;;
|
||||
docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload" ;;
|
||||
esac
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -705,9 +747,9 @@ case "$SITE" in
|
|||
token) URL="https://token.tlyq.ai" ;;
|
||||
issue) URL="https://issue.tlyq.ai" ;;
|
||||
assets) URL="https://assets.tlyq.ai" ;;
|
||||
oa) URL="https://oa.tlyq.ai" ;;
|
||||
ldap) URL="http://localhost:6178" ;;
|
||||
gitea) URL="https://git.tlyq.ai" ;;
|
||||
oa) URL="https://oa.tlyq.ai" ;;
|
||||
ldap) URL="http://localhost:6178" ;;
|
||||
gitea) URL="https://git.tlyq.ai" ;;
|
||||
sso) URL="https://sso.tlyq.ai" ;;
|
||||
monitor) URL="https://monitor.tlyq.ai" ;;
|
||||
esac
|
||||
|
|
@ -715,6 +757,7 @@ esac
|
|||
if [[ "$SITE" == "ldap" ]]; then
|
||||
STATUS=$(ssh txjp "docker exec lldap wget -q -O - http://127.0.0.1:17170 > /dev/null 2>&1 && echo 200 || echo 000" 2>/dev/null || echo "???")
|
||||
else
|
||||
# -k: 服务器本地使用自签名证书,必须跳过 TLS 验证
|
||||
STATUS=$(ssh txjp "curl -s -o /dev/null -w '%{http_code}' -k '$URL' 2>/dev/null" 2>/dev/null || echo "???")
|
||||
fi
|
||||
if [[ "$STATUS" == "200" || "$STATUS" == "307" || "$STATUS" == "301" ]]; then
|
||||
|
|
|
|||
Loading…
Reference in New Issue