---
date: 2026-05-09
title: 新机器初次部署 SOP 两个隐性踩坑
tags: [deploy, postgres, prisma, git, credentials, learnings]
related_pr: 272
related_issue: 273
---

# 新机器初次部署 SOP 两个隐性踩坑

PR #272（dev → test 重命名）的 Phase B 在新机器 `170.106.161.71` 实施时撞到。两个都跟"代码改动"无关，纯部署 SOP 缺口——下次任何新机器初次部署都会再撞。已建 issue #273 跟踪自动化。

---

## ERR-1：新 PG 容器缺 `citext` 扩展，`prisma db push` 立刻挂

### 现象
全新 `ffws-test-postgres`，跑 `npm run db:push`：

```
Error: ERROR: type "citext" does not exist
   step=CreateTable { table_id: TableId(133) }
```

### 根因
Prisma schema 用 `@db.Citext`（不区分大小写文本，常见于 email 唯一性）。`citext` 是 PG 扩展，**不在 Prisma 迁移管辖**——Prisma 不会 auto `CREATE EXTENSION`。

dev 上是早期人工装的（`SELECT extname FROM pg_extension` 查 `ffws-dev-postgres` 只有 `plpgsql + citext` 两个）；新建容器没继承。

### 修复（手工，一次性）
```bash
docker exec ${CONTAINER_PREFIX}-postgres psql \
  -U $(docker exec ${CONTAINER_PREFIX}-postgres printenv POSTGRES_USER) \
  -d $(docker exec ${CONTAINER_PREFIX}-postgres printenv POSTGRES_DB) \
  -c "CREATE EXTENSION IF NOT EXISTS citext;"
```

### 复发预防
要进 `deploy.sh up`：启 PG → wait healthy → idempotent `CREATE EXTENSION IF NOT EXISTS citext;`。详见 issue #273。

---

## ERR-2：`deploy.sh deploy` 内部 `git pull` 静默 fatal + 整个流程仍 exit 0

### 现象
新机器 clone 完用 anon URL（避免 token 落 `.git/config`）。跑 `deploy.sh test deploy`：

```
[1/8] 更新代码
fatal: could not read Username for 'http://43.130.59.228': No such device or address
```

deploy.sh **整个流程仍 exit 0**，PM2 list 空——以为成功才发现没起。

### 根因
- 私有 Gitea HTTP 远程 fetch 要 credential helper
- deploy.sh 的 [1/8] 步骤错误传递有问题：git pull fatal 后 deploy.sh 没中断（疑 `set -e` 没覆盖、或 `|| true` 吞错）

### 修复（手工，一次性）
```bash
git config --global credential.helper store
umask 077
echo "http://hongwei.zhang:${GITEA_API_TOKEN}@43.130.59.228" > ~/.git-credentials
chmod 600 ~/.git-credentials
```

### 复发预防（issue #273 跟踪）
1. `setup-production.sh` 接 `GITEA_USER` + `GITEA_TOKEN` env 自动配 git credential
2. `deploy.sh deploy` 入口 `git ls-remote origin >/dev/null 2>&1` fail-fast
3. 审 deploy.sh 各 step 错误传递（不只 git pull）

---

## 元教训：rename PR 的"碰撞扫描"必须扫现有同名物

PR #272 plan-mode 阶段只扫了"哪些 dev 引用要改成 test"，没扫"目标命名是否已被占用"。Phase B 才发现 `/srv/apps/ffworkspace-test/` 已被 UAT 占（`docs/ops/01-server-infrastructure.md` 早就写了，但 plan agent 只 grep 含 dev 字样的，没 grep 含 test 字样的现有资源）。

**下次任何 mass rename PR**，plan 阶段必跑：

```bash
# 扫现有资源是否已用目标名（避免 rename 撞 existing）
ssh ubuntu@<server> 'ls /srv/apps/ | grep -i <new-name>'
grep -rn "<new-domain>" docs/ ops/  # 文档里有没有别人在用
docker ps --format "{{.Names}}" | grep -i <new-prefix>  # 容器名碰撞
nginx -T | grep -i <new-domain>  # nginx site 碰撞
```

不只扫"要改的"，还要扫"会碰撞的"。
