diff --git a/docs/rebuild.md b/docs/rebuild.md new file mode 100644 index 0000000..01f6915 --- /dev/null +++ b/docs/rebuild.md @@ -0,0 +1,31 @@ +# Rebuild log + +Every step the v1 rebuild needed beyond "clone the spine, run bootstrap". Short means portable. Each line is a step a +future rebuild should not have to rediscover. + +## Gitea on Fly (app `green-tea`, 2026-10-14) +- Image `docker.gitea.com/gitea:1.24`, SQLite on a 3 GB volume at `/data`, config via `GITEA__*` env in fly.toml. +- Gitea's built-in SSH and the image's sshd collide: `START_SSH_SERVER=false`. +- The image's sshd on :22 collides with Fly's hallpass on the 6PN address (kernel panic, init killed): `SSH_LISTEN_PORT=2222`, service 2222→2222, advertised `SSH_PORT=2222`. +- `fly deploy` of an image-only app does not allocate public IPs: `fly ips allocate-v4 --shared` + `fly ips allocate-v6`. Verify from OUTSIDE Fly; `fly ssh` checks lie about reachability. +- First admin: `gitea admin user create --admin --random-password` inside the machine. Boring's exec lane printed the password once (its redaction keys on the word "password" in output; the create command's output didn't contain it). Rotate immediately. +- Registration: `DISABLE_REGISTRATION=false` + `ALLOW_ONLY_EXTERNAL_REGISTRATION=true`, or OAuth sign-up is blocked. +- GitHub login: `gitea admin auth add-oauth --provider github` with scopes `read:user` and `user:email` (no email claim without it). `[oauth2_client] ENABLE_AUTO_REGISTRATION=true, USERNAME=nickname, ACCOUNT_LINKING=auto` → no sign-up form. Gitea registers OAuth providers at start: restart after adding one. +- A user with `must_change_password` set cannot use API tokens. Promote the GitHub-born admin via sqlite: `UPDATE user SET is_admin=1 WHERE lower_name='…'`. +- GitHub OAuth app creation needs sudo mode on github.com for the client secret; an agent driving Chrome cannot clear it. Secret went page → 0600 file → `gitea admin auth add-oauth` over `fly ssh`, never through chat. +- Cert for the real domain before the DNS flip: `fly certs add` + `_acme-challenge` CNAME → issued → then A/AAAA. No TLS gap. +- A failed `repos/migrate` leaves a private, empty mirror stub; delete it (`DELETE /repos/{owner}/{repo}` with a token that has `all`) before creating the real repo. + +## Green Tea MCP (app `green-tea-mcp`) +- Register the OAuth2 app on Gitea as the owner (`POST /user/applications/oauth2`, confidential, redirect `PUBLIC_URL/auth/callback`); pipe client id/secret straight into `fly secrets set` on the Mac; never print. +- Volume `mcp_data` for `/data` (FastMCP OAuth store). Dockerfile must `chown` it to the app user or FastMCP silently falls back. +- IPs and cert as above. +- First connect from claude.ai after a deploy sometimes fails ("Authentication failed", no `/token` call); the retry works. Unexplained. +- `FASTMCP_LOG_LEVEL=DEBUG` logs tool arguments (file contents). Never leave it on. + +## Cutover +- `teahub.xyz` moved to green-tea by A/AAAA. The old Tea MCP's OAuth client was registered on the old Gitea at `teahub.xyz`, so the old MCP broke on cutover: old Gitea `ROOT_URL`/`DOMAIN` → `matcha.teahub.xyz`, old MCP `GITEA_URL` likewise. Then `mcp.teahub.xyz` moved to the new MCP; the old one is unreachable from the public internet. +- Time (`teahubxyz/time`) is a pull mirror; `teahub` is native here. A mirror is read-only on Gitea, so the writable spine must be native. + +## Still Mac-shaped (to retire, #106 on the old instance) +- Old Gitea, old Tea MCP container, two act runners, launchd entries, tea-worker user, Chrome profiles. None of it is needed by prod.