Commit c1659544 by luoqi

chore(deploy): 新环境上线脚本化(gen-env / first-import)+ README 精简 + ASR 默认不部署

- deploy/gen-env.sh:生成 .env —— JWT 密钥就地强随机、生产开关一把设、
  业务值从环境变量注入、缺失报 __FILL_ME__;防覆盖已有(FORCE=1)。
- deploy/first-import.sh:数据首灌一条龙(seed → 存量 cold-import → 重算画像/计划),
  支持 DRY_RUN、ALLOW_PARTIAL 守卫。
- deploy/README.md:精简为维护部署手册(日常/新环境/运维/回滚/监控 5 节),
  systemd 长流程折叠;明确代码根任意目录(脚本相对定位,不写死 /opt/pac)。
- docker-compose.prod.yml:pac-asr 加 profile=asr → 默认不启(省 2G,可 --profile asr 恢复)。
- deploy-prod.sh:默认 SERVICES 去掉 pac-asr,改 WITH_ASR=1 才带上。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent 6e42a683
......@@ -24,7 +24,9 @@ main() {
local COMPOSE=(docker compose -f docker-compose.prod.yml
--env-file apps/pac-service/.env --env-file apps/pac-web/.env)
local SERVICES=(pac-migrate pac-service pac-web pac-asr)
local SERVICES=(pac-migrate pac-service pac-web)
# 语音 ASR 可选(compose profile=asr),默认不部署;要语音助手用 WITH_ASR=1 bash deploy/deploy-prod.sh
[[ "${WITH_ASR:-0}" == "1" ]] && SERVICES+=(pac-asr)
if [[ "${1:-}" != "--no-pull" ]]; then
log "git pull --ff-only"
......
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# first-import.sh — 新环境数据首灌(compose 模式)
# seed(Host 配置) → cold-import(存量) → recompute(画像 + 计划)
#
# 前置:deploy/deploy-prod.sh 已跑过(容器在跑、migration 已 deploy、.env 已配全)。
# 增量不用管:pac-service 进程内调度器会按各 manifest 的 incremental_cron 自动注册。
#
# 用法:
# bash deploy/first-import.sh # host=jvs-dw 全量首灌
# HOST=jvs-dw DRY_RUN=1 bash deploy/first-import.sh # 只 dry-run 存量(不写库,验翻译)
# HOST=jvs-dw ALLOW_PARTIAL=1 bash deploy/first-import.sh # 明知 manifest 有 LIMIT 仍继续(试灌)
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail
ROOT="${PAC_REPO_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
cd "$ROOT"
HOST="${HOST:-jvs-dw}"
COMPOSE=(docker compose -f docker-compose.prod.yml)
EX() { "${COMPOSE[@]}" exec -T pac-service "$@"; }
echo "▶ 数据首灌 host=$HOST dry_run=${DRY_RUN:-0}"
# 0) 前置:pac-service 在跑
if ! "${COMPOSE[@]}" ps pac-service 2>/dev/null | grep -qiE 'up|running'; then
echo "✗ pac-service 没在跑。先 bash deploy/deploy-prod.sh。" >&2
exit 1
fi
# 1) manifest 存在 + 全量提醒(cohort 试跑限量没去掉会只灌部分患者)
if ! EX sh -c "test -f ./data/$HOST/manifest.yaml"; then
echo "✗ 容器内无 data/$HOST/manifest.yaml(镜像里没这个 host 的配置)。" >&2
exit 1
fi
if EX sh -c "grep -nqiE 'limit|offset' ./data/$HOST/manifest.yaml"; then
echo "⚠ manifest 里发现 LIMIT/OFFSET —— 可能是 cohort 试跑限量,全量首灌前应去掉:"
EX sh -c "grep -niE 'limit|offset' ./data/$HOST/manifest.yaml" || true
if [[ "${ALLOW_PARTIAL:-0}" != "1" && "${DRY_RUN:-0}" != "1" ]]; then
echo "✗ 已中止,防止只灌部分数据。确认要限量试灌请加 ALLOW_PARTIAL=1;或去掉限量后重跑。" >&2
exit 1
fi
fi
# 2) seed —— Host 行(appId/secret、orgAliases、clinicNames)。upsert,可重复跑。
echo "▶ [1/4] seed Host 配置(upsert)…"
EX pnpm prisma:seed
# 3) 存量 cold-import
if [[ "${DRY_RUN:-0}" == "1" ]]; then
echo "▶ [2/4] cold-import --dry-run(只读 + 翻译预览,不写库)…"
EX node --max-old-space-size=8192 dist/cli/cold-import.cli.js --dir="./data/$HOST" --dry-run
echo "✓ dry-run 完成。去掉 DRY_RUN 再跑真灌。"
exit 0
fi
echo "▶ [2/4] cold-import 全量(耗时随数据量;jvs-dw 13 万患者约数十分钟)…"
EX node --max-old-space-size=8192 dist/cli/cold-import.cli.js --dir="./data/$HOST"
# 4) 重算画像 + 计划(存量灌完必须补,否则患者进不了召回池)
echo "▶ [3/4] recompute-persona…"
EX node --max-old-space-size=8192 dist/cli/recompute-persona.cli.js --host="$HOST"
echo "▶ [4/4] recompute-plans…"
EX node --max-old-space-size=8192 dist/cli/recompute-plans.cli.js --host="$HOST"
echo
echo "✓ 首灌完成。增量无需手动 —— 进程内调度器已按 manifest 注册 cron。"
echo " 验证增量已注册: ${COMPOSE[*]} logs pac-service | grep '注册 host=$HOST'"
echo " 验证数据在库: 次日看 PAC哨兵 日报,或 /admin/queues"
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# gen-env.sh — 生成生产/staging 环境文件(pac-service/.env + pac-web/.env)
#
# 做的事:
# 1. 从 .env.example 复制出 .env(已存在则拒绝,防清掉已配好的密钥;FORCE=1 强制)
# 2. 就地强随机生成 JWT_SECRET / JWT_REFRESH_SECRET(不外传,不复用其他环境)
# 3. 一把设好生产硬开关(NODE_ENV / mock 关 / 滞后阈值 / 监控 cron 等)
# 4. 业务值从环境变量注入(见下);未提供的留占位 __FILL_ME__ 并在末尾报告
# 5. chmod 600,并列出仍需手填的项
#
# 用法(在目标服务器仓库根或 deploy/ 下跑):
# PAC_DOMAIN=pac.example.com \
# POSTGRES_PASSWORD='...' REDIS_URL='redis://...:6379' \
# DW_CLICKHOUSE_URL='http://...:8123' DW_CLICKHOUSE_USERNAME='jvs_pac' DW_CLICKHOUSE_PASSWORD='...' \
# DEEPSEEK_API_KEY='sk-...' ALERT_WEBHOOK_URL='https://qyapi.weixin.qq.com/...' \
# ENV_KIND=production \
# bash deploy/gen-env.sh
#
# 只想先生成密钥+开关、业务值随后手填:直接 `bash deploy/gen-env.sh`,末尾按 __FILL_ME__ 补。
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail
ROOT="${PAC_REPO_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
SVC="$ROOT/apps/pac-service/.env"
SVC_EXAMPLE="$ROOT/apps/pac-service/.env.example"
WEB="$ROOT/apps/pac-web/.env"
ENV_KIND="${ENV_KIND:-production}" # production | staging
PH="__FILL_ME__"
command -v openssl >/dev/null || { echo "✗ 需要 openssl(生成密钥)" >&2; exit 1; }
command -v perl >/dev/null || { echo "✗ 需要 perl(安全写值)" >&2; exit 1; }
[[ -f "$SVC_EXAMPLE" ]] || { echo "✗ 找不到 $SVC_EXAMPLE" >&2; exit 1; }
if [[ -f "$SVC" && "${FORCE:-0}" != "1" ]]; then
echo "✋ $SVC 已存在。要重建请先备份(cp .env .env.bak)并加 FORCE=1 重跑。" >&2
exit 1
fi
cp "$SVC_EXAMPLE" "$SVC"
# set_kv KEY VALUE FILE —— 值经 %ENV 传入,perl 不解释特殊字符(URL/密码里的 /&@:? 都安全)
set_kv() {
local key="$1" val="$2" file="$3"
if grep -qE "^${key}=" "$file"; then
_K="$key" _V="$val" perl -i -pe 's/^\Q$ENV{_K}\E=.*/"$ENV{_K}=$ENV{_V}"/e' "$file"
else
printf '%s=%s\n' "$key" "$val" >> "$file"
fi
}
# ── ① 就地强随机密钥 ──
set_kv JWT_SECRET "$(openssl rand -hex 32)" "$SVC"
set_kv JWT_REFRESH_SECRET "$(openssl rand -hex 32)" "$SVC"
# ── ② 生产硬开关 ──
set_kv NODE_ENV "$ENV_KIND" "$SVC"
set_kv LOG_LEVEL info "$SVC"
if [[ "$ENV_KIND" == "production" ]]; then
set_kv PAC_ENABLE_MOCK_LOGIN false "$SVC" # ⭐ 生产必关:开着=任何人可假冒任意客服
else
set_kv PAC_ENABLE_MOCK_LOGIN true "$SVC" # staging 演示要开
fi
set_kv PAC_LAG_WARN_HOURS 24 "$SVC"
set_kv PAC_LAG_ERROR_HOURS 48 "$SVC"
set_kv PAC_LAG_MONITOR_CRON "0 * * * *" "$SVC"
set_kv PAC_STALE_SCAN_CRON "0 2 * * *" "$SVC"
set_kv PAC_INCREMENTAL_CRON "" "$SVC" # 留空=自动发现各 manifest 的 incremental_cron
set_kv PAC_WX_AIBOT_DEMO_PUSH "" "$SVC" # 生产必空:不暴露裸推送端点
# ── ③ 业务值:环境变量有则注入,无则占位 ──
inject() { # inject KEY ENVVAR_VALUE
local key="$1" v="${2:-}"
set_kv "$key" "${v:-$PH}" "$SVC"
}
inject POSTGRES_PASSWORD "${POSTGRES_PASSWORD:-}"
inject REDIS_URL "${REDIS_URL:-}"
inject DW_CLICKHOUSE_URL "${DW_CLICKHOUSE_URL:-}"
inject DW_CLICKHOUSE_DATABASE "${DW_CLICKHOUSE_DATABASE:-dw_group}"
inject DW_CLICKHOUSE_USERNAME "${DW_CLICKHOUSE_USERNAME:-}"
inject DW_CLICKHOUSE_PASSWORD "${DW_CLICKHOUSE_PASSWORD:-}"
inject DEEPSEEK_API_KEY "${DEEPSEEK_API_KEY:-}"
inject ALERT_WEBHOOK_URL "${ALERT_WEBHOOK_URL:-}"
# DATABASE_URL:显式给了就用;否则若给了库密码,按 compose 内部网络(host=postgres)拼一个
if [[ -n "${DATABASE_URL:-}" ]]; then
set_kv DATABASE_URL "$DATABASE_URL" "$SVC"
elif [[ -n "${POSTGRES_PASSWORD:-}" ]]; then
set_kv DATABASE_URL "postgresql://pac:${POSTGRES_PASSWORD}@postgres:5432/pac?schema=public" "$SVC"
else
set_kv DATABASE_URL "$PH" "$SVC"
fi
# 域名:一处给,三处用(后端 CORS/深链前缀 + 前端 API base)
if [[ -n "${PAC_DOMAIN:-}" ]]; then
set_kv CORS_ORIGINS "https://${PAC_DOMAIN}" "$SVC"
set_kv PAC_WEB_BASE_URL "https://${PAC_DOMAIN}" "$SVC"
printf 'NEXT_PUBLIC_API_BASE_URL=https://%s\n' "$PAC_DOMAIN" > "$WEB"
else
set_kv CORS_ORIGINS "$PH" "$SVC"
set_kv PAC_WEB_BASE_URL "$PH" "$SVC"
printf 'NEXT_PUBLIC_API_BASE_URL=%s\n' "$PH" > "$WEB"
fi
chmod 600 "$SVC" "$WEB"
echo "✓ 已生成:"
echo " $SVC"
echo " $WEB"
echo "✓ JWT 密钥已就地强随机;生产开关已设(NODE_ENV=$ENV_KIND, mock=$([[ $ENV_KIND == production ]] && echo false || echo true))"
echo
MISSING=$(grep -nE "$PH" "$SVC" "$WEB" || true)
if [[ -n "$MISSING" ]]; then
echo "⚠ 仍需手填(占位 $PH),补完才能上线:"
echo "$MISSING" | sed 's/^/ /'
echo
echo " 可重跑本脚本并用环境变量注入(需 FORCE=1),或直接编辑上面文件。"
else
echo "✓ 无占位残留,业务值已全部注入。"
fi
echo
echo "下一步:bash deploy/deploy-prod.sh → bash deploy/first-import.sh(见 README「新环境上线」)"
......@@ -86,7 +86,11 @@ services:
# 语音听写 ASR — SenseVoice-small(CPU)。模型不进镜像:
# 服务器准备 ${PAC_MODELS_DIR:-../pac-models}/sensevoice/{model.int8.onnx,tokens.txt}
# ⚠️ 可选服务,默认【不启】(profile=asr)。只有用语音助手才需要:
# 启用 → docker compose --profile asr -f docker-compose.prod.yml up -d
# 不启用时 pac-service 的 PAC_ASR_URL 指向的服务不在,transcribe 端点会失败(不影响其它功能/启动)。
pac-asr:
profiles: ["asr"]
build:
context: .
dockerfile: apps/asr-sensevoice/Dockerfile
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment