# cleanup 脚本必须兼容"手动遗留 + 自动注入"两套 marker / 命名

**日期**：2026-04-29（晚）
**触发**：本次 PR #179 合并后，跑 `cleanup-worktree.sh` 清理 site-attend-duration worktree 时发现 Caddy 块没被删干净；后续创建 quickfix 常驻 worktree 又撞上 Redis 端口已占
**适用**：所有"先有手动操作积累，后有脚本自动化"的清理类工具

## 现象

第一坑：cleanup 脚本对 Caddyfile 没动作
- 脚本只识别 setup-worktree.sh 自动注入的新格式 `# >>> ffoa-worktree:<name>:<user> >>> ... # <<< ... <<<`
- 但 site-attend-duration 的 Caddy 块是**最早手动 append 的**，marker 写的 `# worktree: <name> (<user>) — added <date>`
- 格式不一致 → cleanup grep 不到 → 块原地不动 → 域名继续解析到已不存在的端口

第二坑：setup 报 `Bind for 0.0.0.0:3303 failed: port is already allocated`
- 同一个 worktree 早期我手动起的 Redis 容器叫 `ffoa-wt-<short_name>-redis`
- setup-worktree.sh 后来改用 `ffws-wt-redis-<port>` 标准命名
- slot 检测的容器扫描只 grep `ffws-wt-pg-*`（第一版加上的），**漏掉了 redis 命名空间**
- 标准检测算法看不见老遗留容器 → slot 33 被认定空闲 → docker run -p 3303 撞实际占用

## 根因

**自动化只能识别它自己生成的产物，不能识别"自动化上线之前"由人工创造的东西**。这在 brownfield 工具化场景下是结构性问题：

1. 早期阶段：操作员手动维护资源（手写 Caddy 块、`docker run` 起容器）。命名/marker 没有约定。
2. 后来阶段：写自动化脚本，统一命名/marker。
3. 结果：环境里**两套并存**——新脚本只懂新格式，老遗留物变成"看不见的资源占用"。

## 修法（这次 PR 应用的两个原则）

### 原则 1：cleanup 脚本要识别**两类**marker / 命名

- **新格式**：精准 marker 包夹（`# >>> ... >>> ... # <<< ... <<<`）
- **老格式**：靠**已知遗留模式**做启发式匹配（`# worktree: <name>` + 后续 site block 用 awk 花括号配平删掉）

```bash
# Caddy: marker 格式 + awk 花括号配平双兜底
caddy_remove_block() {
  # 1. 新 marker 包夹删除
  sed -i "/${marker_begin}/,/${marker_end}/d" "${file}"
  # 2. 老 marker 注释 + 站点 block（awk 配平花括号）
  awk '/^# worktree:.../{skip=1} skip && /\{/{depth++} skip && /\}/{depth--; if(depth==0) {skip=0; next}} !skip' file
}
```

容器同理：
```bash
redis_candidates=("ffws-wt-redis-${port}" "ffoa-wt-${name}-redis")
for c in "${redis_candidates[@]}"; do docker rm -f "$c" 2>/dev/null; done
```

### 原则 2：slot/资源检测要用**端口为唯一真值源**，而不是命名约定

```bash
# 之前只扫 ffws-wt-pg-* 容器名；漏掉非标准命名
container_pg_ports=$(docker ps -a --format '{{.Names}}' | sed -n 's/^ffws-wt-pg-\(.*\)$/\1/p')

# 改用 docker ports 字段提取 host 侧端口；容器叫什么都看得到
container_host_ports=$(docker ps -a --format '{{.Ports}}' \
  | grep -oE '[0-9]+(\.[0-9]+){3}:[0-9]+|:::[0-9]+' \
  | awk -F: '{print $NF}')
```

**为什么端口比命名更可靠**：命名是约定（可以变），端口是物理事实（容器实际绑了就是绑了）。检测应该用最难撒谎的源。

## 通用启示

**「兼容老遗留」不是可选项，是清理类工具的基础职责**。任何"扫描 → 清理"脚本都该问自己：

1. 这个工具上线之前，相同资源是用什么形式存在的？
2. 那种形式的产物 cleanup 时能识别吗？
3. 能不能用更底层的事实源（端口、PID、文件 inode）取代上层的命名约定？

只识别新格式的 cleanup 工具，本质上是 **加大债务**——以后越积越多看不见的老东西。

## 关联文件

- `scripts/dev/cleanup-worktree.sh` —— Caddy 双格式删除 + Redis 双命名兼容
- `scripts/dev/setup-worktree.sh` —— slot 检测加 container host port 扫描
- 上一篇 `.learnings/2026-04-29-setup-worktree-manual-tails.md` —— 自动化只做一半反而更糟（同一议题的另一面）
