# Litestream 0.3 + MinIO 必须走 YAML config，query-string 参数不传到 bucket-region 探测

> **日期**: 2026-05-14
> **类型**: 工具链 bug / S3-API 兼容性陷阱

## 现象

internal-app-platform Phase 0 接 Litestream 备份 SQLite → MinIO，sidecar 容器：

```bash
docker run --rm \
  -e LITESTREAM_ACCESS_KEY_ID=ffoa_internal_apps \
  -e LITESTREAM_SECRET_ACCESS_KEY=<48char> \
  litestream/litestream:0.3 \
  replicate /data/app.db \
    "s3://internal-apps-backups/lijian/hello/app.db?endpoint=http://minio:9000&force-path-style=true"
```

**永远报**：
```
ERROR monitor error db=/data/app.db replica=s3
  error="cannot lookup bucket region: InvalidAccessKeyId:
         The AWS Access Key Id you provided does not exist in our records"
```

**同样的 access-key-id + secret + endpoint，用 `awscli s3 ls` 直接成功**——所以不是凭据问题。

## 根因

Litestream 0.3 在初次写之前需要 `GetBucketLocation` 探测 region。**这次探测调用走的 HTTP 路径绕过了 query-string 解析的 `force-path-style=true` / `endpoint=...`**——它走默认 AWS SDK 行为：把 bucket 当 hostname（`<bucket>.s3.amazonaws.com`），并且**忽略我们注入的 access key**（疑似 SDK 在 region-lookup phase 用 anonymous 模式或 default chain）。

MinIO 收到的请求里要么没 Authorization header，要么签名走的是与配置不符的 host → 直接 `InvalidAccessKeyId`。

## 解决

**用 YAML config 文件**而不是 query-string 单命令：

```yaml
# /etc/litestream.yml
dbs:
  - path: /data/app.db
    replicas:
      - type: s3
        bucket: internal-apps-backups
        path: lijian/hello/app.db
        endpoint: http://minio:9000
        region: us-east-1
        force-path-style: true
        access-key-id: ...
        secret-access-key: ...
```

```bash
docker run -d -v /etc/litestream.yml:/etc/litestream.yml:ro ... \
  litestream/litestream:0.3 \
  replicate -config /etc/litestream.yml
```

YAML config 的 `force-path-style` 和 `region` 在 SDK 初始化时就生效，**region-lookup 步骤完全跳过**——直接走配置里的 region。

## 适用面

- 任何 **Litestream 0.3.x + MinIO** 配置都要用 YAML，不要用 query-string 模式
- Litestream 0.5+ 不存在此问题（语法重写）
- 类似的 S3-SDK + 非 AWS 后端（Wasabi / Backblaze / R2 等）若遇到 region-lookup InvalidAccessKeyId，第一反应就是切 YAML config

## 排查 checklist

1. 用 `awscli s3 ls` 同 endpoint 同 creds 能通过？→ 凭据 OK
2. Litestream 报 `InvalidAccessKeyId`？→ 99% query-string 不传递问题
3. mc / 直 curl S3 API 与 Litestream 行为不一致 → SDK 路径不同

## 部署集成

`scripts/internal-app-platform/deploy-container.sh` 生成 per-app YAML 配置到
`/srv/internal-apps/data/{emp}/{app}/.litestream.yml`（0600 权限），sidecar
挂载只读 + `replicate -config /etc/litestream.yml`。同样模式用在 restore 路径。

## 状态

- ✅ deploy-container.sh 两处（replicate + restore）改 YAML config
- ✅ 实测 sidecar 不再报 InvalidAccessKeyId；snapshot + WAL 真到 MinIO
- ✅ MinIO 桶里看到结构正确的 generations/.../snapshots/...lz4 + wal/...lz4

## 已知 limitation（Phase 0 不修，Phase 1 处理）

**多 generation 冷恢复选错 generation**：Litestream 0.3 `restore -if-replica-exists`
没指定 `-generation` 时按 lexicographic order 挑第一个，**不是按时间挑最新**。
重复 `rm -rf DATA_DIR` 测试会创建多个 generation（每个 generation 是一次新 DB
chain），冷 restore 命中最老的那个。

**正常使用不受影响**（也是 Phase 0 PoC 验收"数据重启不丢"覆盖的场景）：
容器重启时 /data 保留 → Litestream 看到 existing DB + WAL → 在同一 generation 继续
追加 → 数据无丢失。

Phase 1 修法：deploy 前调 `litestream generations` 列所有 generation，按
`backup_created_at` 元数据挑最新的，传 `-generation <id>` 给 restore。
或升级到 Litestream 0.5+（CLI 重写，restore 默认行为可能更合理）。
