# PAC 部署运维

> W4 末:试部署阶段手动 ssh + 跑脚本;W5+ 接 CI/CD 自动化。

## 两种部署模式

| 模式 | 适用 | 文件 |
|---|---|---|
| **systemd 裸跑** | 宿主有 node/pnpm,想直接 systemctl 管理 | `deploy.sh` + `systemd/*.service` |
| **docker-compose** | 宿主只装 docker,想完全隔离 | `docker-compose.prod.yml`(项目根) |

两种模式共用同一份 `apps/pac-service/.env` + `apps/pac-web/.env`(单一真相)。

### compose 模式启动(推荐共享服务器)

```bash
# 1. 填配置
cp apps/pac-service/.env.example apps/pac-service/.env  # POSTGRES_*/JWT_*/DEEPSEEK_*/DW_*
cp apps/pac-web/.env.example     apps/pac-web/.env      # NEXT_PUBLIC_API_BASE_URL

# 2. 起所有容器
docker compose -f docker-compose.prod.yml \
  --env-file apps/pac-service/.env \
  --env-file apps/pac-web/.env \
  up -d --build

# 3. 验证
docker compose -f docker-compose.prod.yml ps
curl http://127.0.0.1:3101/health
curl http://127.0.0.1:3100
```

日常操作:
```bash
# 更新代码 + 重建 + 验证(推荐 —— 确定性部署脚本)
# ⚠️ 不要直接用 `up -d --build`:compose 有已知缺陷(docker/compose#9308 #9259),
#    会出现"镜像建了但容器不切新镜像"(2026-06-10 实测中招)。脚本显式 build +
#    force-recreate + 部署后硬验证(镜像ID/迁移/health),任一不过即失败。
bash deploy/deploy-prod.sh

# (不推荐)手动等价步骤
git pull && docker compose -f docker-compose.prod.yml --env-file apps/pac-service/.env --env-file apps/pac-web/.env up -d --build --force-recreate

# 看日志
docker compose -f docker-compose.prod.yml logs -f pac-service

# 只重启某个服务(不重建 image)
docker compose -f docker-compose.prod.yml restart pac-service

# 进容器调试
docker compose -f docker-compose.prod.yml exec pac-service sh
```

⚠️ 改了 `NEXT_PUBLIC_*` 必须 `up -d --build`(build-time inline);改后端 .env 只要 `restart pac-service`。

---

## systemd 模式文件清单

| 文件 | 用途 |
|---|---|
| `deploy.sh` | 一键部署 — `bash deploy.sh staging|production` |
| `systemd/pac-service.service` | NestJS 后端 systemd unit 模板 |
| `systemd/pac-web.service` | Next.js 前端 systemd unit 模板 |

跟 `apps/pac-docs/content/docs/operations/deployment.mdx` 配合看 — 那份讲数据 / 监控,本目录讲部署机制。

---

## systemd 模式 SOP(staging / production 通用)

### 1. 服务器基础环境

```bash
# Ubuntu 22.04 假设
sudo apt update && sudo apt install -y curl git docker.io
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm i -g pnpm@9

# 创建专用账号
sudo useradd -m -s /bin/bash pac
sudo mkdir -p /var/log/pac && sudo chown pac:pac /var/log/pac
```

### 2. 起 Postgres + Redis(docker 跑法,简单部署够用)

```bash
sudo docker run -d --name pac-postgres --restart unless-stopped \
  -p 127.0.0.1:5532:5432 \
  -e POSTGRES_USER=pac \
  -e POSTGRES_PASSWORD=<强密码> \
  -e POSTGRES_DB=pac \
  -v pac-pg-data:/var/lib/postgresql/data \
  postgres:15

sudo docker run -d --name pac-redis --restart unless-stopped \
  -p 127.0.0.1:6479:6379 \
  -v pac-redis-data:/data \
  redis:7-alpine
```

生产建议用托管 RDS / 云 Redis(本地 docker 数据风险)。

### 3. 拉代码 + 配 env

```bash
sudo mkdir -p /opt/pac && sudo chown pac:pac /opt/pac
sudo -u pac git clone <your-repo> /opt/pac

# 后端 env
sudo -u pac cp /opt/pac/apps/pac-service/.env.example /opt/pac/apps/pac-service/.env
sudo -u pac vim /opt/pac/apps/pac-service/.env
# → 改 NODE_ENV / DATABASE_URL / REDIS_URL / JWT_* / DW_* / DEEPSEEK_API_KEY
# → staging 还要 PAC_ENABLE_MOCK_LOGIN=true / PAC_INCREMENTAL_CRON=30 3 * * *
# → production 要 PAC_ENABLE_MOCK_LOGIN=false / 自己的 prod 配置
sudo chmod 600 /opt/pac/apps/pac-service/.env

# 前端 env(NEXT_PUBLIC_API_BASE_URL build-time 嵌入)
sudo -u pac vim /opt/pac/apps/pac-web/.env
# NEXT_PUBLIC_API_BASE_URL=https://pac-staging.your-domain.com
# (staging / prod 必须各 build 一次,产物不可互换)
```

### 4. 安装 systemd unit

```bash
sudo cp /opt/pac/deploy/systemd/pac-service.service /etc/systemd/system/
sudo cp /opt/pac/deploy/systemd/pac-web.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable pac-service pac-web
```

允许 `pac` 用户 systemctl restart 这两个 service(deploy.sh 需要):

```bash
sudo visudo -f /etc/sudoers.d/pac
# 加一行:
# pac ALL=(root) NOPASSWD: /bin/systemctl restart pac-service, /bin/systemctl restart pac-web
```

### 5. 首次部署

```bash
sudo -u pac bash /opt/pac/deploy/deploy.sh staging
# 或
sudo -u pac bash /opt/pac/deploy/deploy.sh production
```

### 6. 验证

```bash
curl http://localhost:3101/health         # 应返 {"status":"ok"} 或类似
curl http://localhost:3100                # Next.js 首页 HTML
journalctl -u pac-service -n 50 -f        # 看日志
journalctl -u pac-web -n 50 -f
```

浏览器开 `https://<your-domain>/plans` — staging 应看到"快速登录"对话框。

---

## 日常部署(代码改了重新部署)

```bash
ssh deploy@<server>
sudo -u pac bash /opt/pac/deploy/deploy.sh staging
```

5-10 分钟完成,期间 service 短暂中断(restart 瞬时)。零停机部署需要 blue-green,W5+ 上。

---

## 数据冷启 / 首次 DW 灌库

```bash
sudo -u pac bash -c 'cd /opt/pac/apps/pac-service && pnpm cold-import -- --dir=./data/jvs-dw'
# 生产首跑前:务必把 manifest.yaml 里 cohort 的 LIMIT 100 OFFSET 100 去掉(13 万患者全量)
```

完成后:
```bash
sudo -u pac bash -c 'cd /opt/pac/apps/pac-service && pnpm recompute-persona -- --host=jvs-dw'
sudo -u pac bash -c 'cd /opt/pac/apps/pac-service && pnpm recompute-plans -- --host=jvs-dw'
```

之后 cron(02:30)自动维护增量,见 `apps/pac-docs/content/docs/operations/deployment.mdx` §三。

---

## 回滚

```bash
cd /opt/pac
git log --oneline -10                       # 看历史
git checkout <last-known-good-sha>
sudo -u pac bash /opt/pac/deploy/deploy.sh staging   # 同 deploy 流程,只是 git pull 被 checkout 替换
```

⚠️ **DB schema 不会回退** — Prisma migration 是只前向,回滚老代码可能跟新 schema 不兼容。
真要回滚 schema 得手写补 migration。生产 schema 变更前评估前向兼容。

---

## 监控 / 告警

详见 `apps/pac-docs/content/docs/operations/deployment.mdx` §四。关键:
- `journalctl -u pac-service -f | grep -E "ERROR|dw-lag"` 看错误 + 滞后告警
- `psql -U pac -d pac -c "SELECT status, count(*) FROM sync_logs WHERE started_at > now()-'1d'::interval GROUP BY status;"` 看同步成功率
- `/admin/queues` Bull Board 看队列
