Files
teahub/pages

Teahub pages (green-tea-pages)

The pages convention for orgs on prod: an org's public site repo is served at its domain. PAGES_SITES in fly.toml maps hostnames to org/site. The server clones each repo over https (no token), serves static files (directory → index.html, missing → 404.html), and re-pulls every 5 minutes or on POST /_hook/<org>/site (a Gitea webhook on the repo, push events, no secret: the hook only triggers a pull of public content).

Adding a site: a line in PAGES_SITES + fly deploy (operator lane), fly certs add <host>, the _acme-challenge CNAME, then A/AAAA to the app's IPs (fly ips list), and a push webhook on the site repo.

Live: persimmons.app → persimmons/app (repo root, as its pages/page.conf says), phly.cloud → phly/site, supertestnet.lol → supertestnet/app#pages, yolopreneur.lol and *.yolopreneur.lol → teahubxyz/time#gh-pages/ventures (PAGES_WILDCARDS); gandora.yolopreneur.lol → gandora/site (a venture in its own repo: an exact host in PAGES_SITES wins over the wildcard, so a venture graduates by adding one line). A site can be any public repo, org/repo#branch/subdir; it doesn't have to be called site. A product with a backend serves its own pages instead (phast.app is phast-app), so there is never a copy to drift.

Files without an extension that start with <!doctype html or <html are served as HTML (/signin).

A _headers file at a site's root adds response headers, Cloudflare Pages style: a path line (exact, or ending in * for a prefix), then indented Name: value lines; a later matching rule wins for the same header, and it can replace the default cache-control. _headers itself is never served. supertestnet.lol uses it for its CSP.

A private copy on hb-5

The same server.py also runs on hb-5 for sites that stay off the internet (launchd xyz.teahub.local-pages, 127.0.0.1:8093, checkout in ~/local-pages/teahub). There it reads repositories straight from the old local Gitea's storage (GITEA_URL=file:///Users/git/gitea/data/git/repositories, with safe.directory passed through GIT_CONFIG_* for each repo), binds loopback (PAGES_HOST=127.0.0.1) and pulls every 30 s. First site: maths.localhost:8093 → matcha/maths#main/site/public. Chrome resolves any *.localhost to the machine itself.