Being signed in to teahub.xyz, or connected to Tea MCP in chat, is the gate, as the operator put it. This takes out 9fece6d's browser sign-in: GET /domains is open and bounded again (only teahub.xyz may read it, 8 searches a minute per client and 20 in all, answers kept ten minutes), and the same search is the tea_domains tool, behind the connector's sign-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CYHXFvWMCs1GUmRmpySqQ6
6.7 KiB
6.7 KiB
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:28.0.0(1.24 until 2026-10-15), SQLite on a 3 GB volume at/data, config viaGITEA__*env ingitea/fly.tomlin this repo. - Upgrading: snapshot the
gitea_datavolume, change the tag ingitea/fly.toml,fly deploy -a green-teafromgitea/, read the migration lines in the log. A migrated database refuses an older image, so the way back is the snapshot. Rehearse first on a copy of/data(sqlite.backupplus the repos) in Docker. - Look:
gitea/custom(home and dashboard templates, colours, the 🍵 mark, the dashboard's Domains tab) goes onto the volume withgitea/sync-custom.sh, then a restart. The name and meta tags are env ingitea/fly.toml(GITEA____APP_NAMEfor the top-levelAPP_NAME;[ui.meta]isGITEA__ui_0X2E_meta__*). Gitea 28 sets a CSP in a meta tag (script-src * 'nonce-...'), so custom scripts must be files, not inline. - 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, advertisedSSH_PORT=2222. fly deployof an image-only app does not allocate public IPs:fly ips allocate-v4 --shared+fly ips allocate-v6. Verify from OUTSIDE Fly;fly sshchecks lie about reachability.- First admin:
gitea admin user create --admin --random-passwordinside 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 githubwith scopesread:useranduser: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_passwordset 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-oauthoverfly ssh, never through chat. - Cert for the real domain before the DNS flip:
fly certs add+_acme-challengeCNAME → issued → then A/AAAA. No TLS gap. - A failed
repos/migrateleaves a private, empty mirror stub; delete it (DELETE /repos/{owner}/{repo}with a token that hasall) before creating the real repo. - Temp admin tokens:
gitea admin user generate-access-token --username … --scopes … --rawoverfly ssh.delete:repositoryis not a scope;write:repositorycovers DELETE. Tokens made this way show in the user's settings; revoke when done.
Green Tea MCP (app green-tea-mcp)
- Register the OAuth2 app on Gitea as the owner (
POST /user/applications/oauth2, confidential, redirectPUBLIC_URL/auth/callback); pipe client id/secret straight intofly secrets seton the Mac; never print. - Volume
mcp_datafor/data(FastMCP OAuth store). Dockerfile mustchownit 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
/tokencall); the retry works. Unexplained. FASTMCP_LOG_LEVEL=DEBUGlogs tool arguments (file contents). Never leave it on.tea_domainsandGET /domains(the dashboard's Domains tab) need the secretsCLOUDFLARE_API_TOKEN(Registrar read) andCLOUDFLARE_ACCOUNT_ID; without them they say domain search is not set up.
Actions runner on Fly (app green-tea-runner, 2026-10-14)
- Image
docker.gitea.com/act_runner:5.0.0-dind(the0.2.xtags no longer exist;-dind-rootlessnever gets a daemon up in a Fly VM). Registration token:gitea actions generate-runner-tokenoverfly ssh, set asGITEA_RUNNER_REGISTRATION_TOKEN. - Fly's rootfs is overlayfs, so Docker cannot nest overlay on
/var/lib/docker: every job container fails with "failed to mount … overlay". Fix: mount the volume at/dataand in[experimental] entrypointsymlink/var/lib/docker → /data/dockerbeforeexec s6-svscan /etc/s6(the image's entrypoint). - Labels
ubuntu-latest:docker://node:22-bookworm.actions/checkout@v5,setup-node@v4resolve from github.com by default. - Logs:
GET /api/v1/repos/{o}/{r}/actions/jobs/{job_id}/logswith a token (the web…/runs/N/jobs/0/logsroute 404s for the API).
Time on prod (2026-10-14)
- Migrated once with
POST /repos/migrate(service: github, issues, labels, milestones, PRs, releases;mirror: false). Issue numbers survive. The earlier git-only mirror had to be deleted first (a mirror is read-only; the factory's own repo must be native). - Gitea reads
.github/workflows/; nothing moved.permissions:blocks are ignored.vars.*andsecrets.*work. The Actions user isgitea-actions; Gitea fires events for its writes (GitHub does not), so every event-driven workflow filters that login. - The Actions token is not a repo admin: it cannot read
/collaborators/{u}/permission(403) or org membership. Trust in Time = repo owner,TIME_TRUSTED(repo variable, comma-separated — org owners go here), or a collaborator (GET /collaborators/{u}→ 204). - API differences that bit:
limitnotper_page; nosorton issues;pull_requestkey always present (null); label add/remove by id; issue creation labels by id;POST /pulls/N/merge {"Do":"squash"};POST /branches;/pulls/N.diff; commit statuses only (no check-runs), each with astatusfield. - Secrets on prod so far: none beyond infra.
VENICE_API_KEYonteahubxyz/timeis the first product secret and needs a decision entry when added.
Cutover
teahub.xyzmoved to green-tea by A/AAAA. The old Tea MCP's OAuth client was registered on the old Gitea atteahub.xyz, so the old MCP broke on cutover: old GiteaROOT_URL/DOMAIN→matcha.teahub.xyz, old MCPGITEA_URLlikewise. Thenmcp.teahub.xyzmoved to the new MCP; the old one is unreachable from the public internet.teahubis native here;timeis now native too (see above). GitHubteahubxyz/timeis archived with a pointer.
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.