# infra/ — 主机基础设施配置的版本化真源

这里放**跑在内网主机（`172.239.57.41` / `product-demo.tvustream.com`）上、但属于本项目**的配置文件副本。

## 为什么有这个目录

在此之前，这些文件**只活在主机上，没有任何版本管理**——丢了要靠人回忆重建，被改了也没人知道改过什么。2026-07-31 修复 `:3001` 网页 UI 时新增了 `gitea-proxy`，这个缺口就摆到台面上了（那次改动的完整链路真源 = [`docs/GITEA_ENTRYPOINTS.md`](../docs/GITEA_ENTRYPOINTS.md)；⛔ 原先这里写的 `[[INFRA-F89]]` 已删档，2026-08-14 改为指活源）。同样的毛病 deploy-hook 的 `server.py` 也有，见下方「尚未纳入」。

## 真源约定

**本仓库是真源，主机是部署产物。**

`verify-host-config.sh` 发现不一致时，默认动作是**查清主机为什么被直接改了，然后把改动回抄进仓库并 commit**——而不是拿仓库副本去覆盖主机。后者可能抹掉别人为救火做的紧急改动。

## 目录

| 路径 | 主机位置 | 作用 |
|---|---|---|
| `gitea-proxy/Caddyfile` | `/data/nancy_zeng/gitea-proxy/Caddyfile` | 占住 `:3001`，剥掉 `/gitea` 前缀后转给 Gitea |
| `gitea-proxy/docker-compose.yml` | `/data/nancy_zeng/gitea-proxy/docker-compose.yml` | 该代理的容器定义（`caddy:2-alpine`，加入 `gitea_default` 网络） |

拓扑图与它为什么存在，见 [`docs/DEPLOY.md`](../docs/DEPLOY.md)；完整来龙去脉（含被否的四个解、v1 失败教训）见 [`STATUS-CHANGELOG.md`](../docs/internal/STATUS-CHANGELOG.md) 2026-07-31 session L 段。

**主机侧回滚源**：动 `gitea-proxy` 这一层之前，Gitea 原本那份 compose 备份在主机
`/data/nancy_zeng/gitea/docker-compose.yml.pre-f89-20260731-084832`。它是「让出 `:3001`」这个改动的唯一回滚材料，写在这里是因为**主机上的备份文件本身没有版本管理**，路径丢了就等于回滚源丢了。⚠️ 同时记住这条改动本身的教训：**回滚材料齐全 ≠ 回滚动作安全** —— 撤回一层反代与修复它是同一类风险（一样重建容器、一样没兜底），判据要比「执行动作的风险」而不是「目标状态的熟悉度」。

更早一层（2026-07-30 给 Gitea 加 TLS + 把 `ROOT_URL` 从 IP 改成域名那次，原 INFRA-F74，**2026-08-14 该 entry 删档时把这三条路径搬到这里 —— 此前它们全仓只有那条 entry 一家**）留下的三个备份也仍在主机：

- `/data/nancy_zeng/web/Caddyfile.pre-f74-20260730-041017` —— 加 `handle_path /gitea/*` 路由前的主 Caddyfile
- `/data/nancy_zeng/gitea/docker-compose.yml.pre-f74-20260730-041017` —— 改 `GITEA__server__ROOT_URL` / `DOMAIN` 前的 compose
- `/data/nancy_zeng/…/deploy-hook/docker-compose.yml.pre-f74cred-20260730-041849` —— 删掉那行被注释掉的失效明文凭据前的备份

**2026-08-27 新增一个，但它的性质与上面三条不同**：`/data/nancy_zeng/deploy-hook/.env.pre-rotate-20260827`（600）—— 轮换 `GIT_AUTH` 前那份 `.env` 的快照。⚠️ **它不是回滚源**：里面那份凭据（token 后八位 `d5a4b706`，Gitea 上名为 `synced2caddy`、id 9）**当天已吊销**，实测拿它 `git ls-remote` 得 `rc=128`。⇒ 它现在只是「改动前长什么样」的留痕，随时可删，保留纯粹是沿用本节其余几条的 `暂缓删除` 惯例。**要删就直接 `rm`，不需要先做什么。**

⚠️ 顺带记一条**改这个文件时会咬人**的机制（2026-08-27 实测）：`.env` 被 `docker-compose.yml` 用 `env_file:` 消费，而 **`env_file` 只在建容器时求值** ⇒ 改完 `.env` 后跑 `docker restart deploy-hook` **不会生效**，容器会带着旧值起来、而且一切看起来正常（`server.py` 里是 `auth_args = [...] if GIT_AUTH else []`，凭据坏了只是**静默降级成无凭据 git**，不报错）。必须 `docker compose up -d --force-recreate`，并且**正面验**新凭据能真的拉到私有仓，不能拿「容器起来了 / 日志没红」当验证。

⚠️ **别按原计划「观察一段时间后删」**：INFRA-F89 之后它们是「将来拿到 SSH 才用得上」的唯一回滚源，保留成本为零 —— owner 已改判为**暂缓删除**。要删得先有替代的回滚材料。

## 核对 drift

```bash
pnpm run verify:host-infra      # 或 bash infra/verify-host-config.sh
```

**退出码刻意三分，别在调用处合并**：

| 码 | 含义 |
|---|---|
| `0` | 核过了，全部一致 |
| `1` | 核过了，**有 drift** ← 真问题 |
| `2` | **没核到**（连不上主机 / 文件读不到）← 不是"通过"，只是没验 |

把 `2` 当 `0` 处理会造出假绿：SSH 一坏就永远"通过"。

**为什么不挂 CI / pre-commit**：核对需要主机 SSH 凭据，CI runner 没有；挂 pre-commit 会让每次提交都等一次网络往返，且离线时必然失败。所以它是**手动/收尾时跑**的核对手段，触发时机：改过主机配置之后 · 排查主机异常时 · 版本发布前的收尾检查。

## 从零重建 gitea-proxy

主机上那份没了的话：

```bash
ssh nancy_zeng@172.239.57.41 'mkdir -p /data/nancy_zeng/gitea-proxy'
scp infra/gitea-proxy/Caddyfile infra/gitea-proxy/docker-compose.yml \
    nancy_zeng@172.239.57.41:/data/nancy_zeng/gitea-proxy/
ssh nancy_zeng@172.239.57.41 'cd /data/nancy_zeng/gitea-proxy && docker compose up -d'
```

前提：Gitea 容器在 `gitea_default` 网络里、且其 web 端口是容器内 `3000`（当前 compose 里映射为 `127.0.0.1:3003:3000`）。验证四路：`:3001` 首页 · `:3001/gitea/assets/js/index.js` · `https://…/gitea/` · `git ls-remote :3001`。

## 尚未纳入（有意留白，不是遗漏）

- **deploy-hook 的 `server.py`** —— 同样只活在主机（`INFRA-F72` 残余）。没有顺手拉进来，是因为它是**别人链路上的源码**、体量与依赖都更大，且**必须先确认里面没有凭据**（该 hook 用的 `GIT_AUTH` 在主机 `.env` 里，但要逐行看过才能确认源码本身干净）。要纳入就在 `verify-host-config.sh` 的 `PAIRS` 里加一行。
- **Gitea 与主 Caddy 自己的 `docker-compose.yml` / `Caddyfile`** —— 它们承载的不止本项目（主 Caddy 上还有 `/microapps/`、`/Mediahub/`、`/email-template/` 等别的项目），把它们的真源声称在本仓库会越界。当前只在 [`docs/DEPLOY.md`](../docs/DEPLOY.md) 里留逐字记录。

## 这不是什么

**不是自动化 IaC。** 这里没有"改了仓库就自动部署到主机"的链路——部署仍是手动 `scp` + `docker compose up -d`。它解决的是「配置只存在一份、丢了没法重建、改了没人知道」，不解决「部署过程可重复」。真要上 IaC 是另一件事，未做、未拍。
