pages: _headers support, supertestnet.lol; decisions: domain audit, supertestnet back

This commit is contained in:
teahubxyz committed 2026-10-14 15:33:58 -07:00
1 parent f6051c0f76
commit 2bbed297b8
4 files changed
+40 -5

No files matched your search

+1
View File
@@ -43,3 +43,4 @@ Format: date · decision · why. Add at the bottom. Never edit an old entry; sup
- 2026-10-14 · **The portfolio lists the operator's own products too, and phly.cloud is back.** Teahub, Persimmons, Phly, Phast app, MacMCP and Time join the ledger at yolopreneur.lol (25 entries). They are listed like well-known.online: `url` is the product's own address, `ventures/<slug>/index.html` forwards `<slug>.yolopreneur.lol` there, and a first-screen shot (960×600) sits beside it. phly.cloud had been dark since the edge went. Its site, `matcha/phly` with history, is now org `phly`, repo `phly/site`, served by pages (cert issued, DNS moved, push webhook). The favicon pointed at an expired Boring blob link and is now the mascot itself. Still dark and not listed: supertestnet.lol, unicorn.ceo, and the `*.teahub.xyz` sites (maths, notplanned, dreams, oracle, greentea); their bytes are on hb-5 (`~/sites`, the old Gitea). Why: the operator asked for the products in the list; one list, same card for everything.
- 2026-10-14 · **Receipt: temporary admin tokens for the phast/persimmons/phly work.** `teahubxyz` token 35 (`fix-phast-20261014`, minted on the Fly box for the pushes, the OAuth app and the repo archives) was deleted by id at 22:27 UTC, and the table listing shows only 25 (`time`). Token 36 (`receipt-20261014`) was minted only to push this line, then deleted the same way. Neither was printed. Why: the rule above.
- 2026-10-14 · **One tree: lineage is the factory's shape.** The factory, its ventures and their agents are one tree, read three ways at the same path: keys (lineage), context (`AGENTS.md` · `SKILLS.md` · `DESIGN.md`, inherited down the path) and presence (pages; NIP-05/mailr names). Every org runs the same template at every depth, from its own seed (BIP-85 from its parent's root). Edges are *derived* (the parent holds the child's seed) or *adopted* (the child holds its own root and the parent vouches with a birth certificate); graduation flips derived → adopted. Belts are badges, per seat × skill, and `SKILLS.md` now tracks them per seat. Model only: minting a real root is the operator's call. Details and a spec v0.1 proposal in `docs/lineage.md`. Why: the operator saw the meta repo's nesting, lineage's key tree and the yolo portfolio as one shape, liked graduation as an edge flip, and set belts = badges (#8).
- 2026-10-14 · **supertestnet.lol is back, and every Cloudflare domain is in the portfolio.** Cloudflare has one account with 8 zones; Boring's token and the dashboard list the same ones. All are now accounted for: macmcp.xyz, persimmons.app, phast.app, phly.cloud, supertestnet.lol, teahub.xyz and well-known.online are listed, and yolopreneur.lol is the list itself. Super Testnet, a browser-local Bitcoin lab, moved from the old Gitea with its history to org `supertestnet`, with repos `app` and `inscriboor` (the signing extension). Pages serves `supertestnet/app#pages`, which is the build that was live. Pages now reads a `_headers` file (Cloudflare Pages style), so the site keeps the CSP and frame headers its Node server used to send. The ledger has 26 entries, and the hero no longer says every venture came off the clock. unicorn.ceo is not the operator's domain; it was only an idea. Four records still point at the dead edge (66.241.124.48 / 2a09:8280:1::19c:1dd0:0): `matcha.`, `maths.`, `notplanned.` and `🫖.teahub.xyz`. That's harmless while the suspended `teahub-gitea` app holds those IPs and certs, but the records must go before that app is deleted. Of the four, only maths.teahub.xyz (the zeta-ball pictures) has its own content; its files are in hb-5 `~/sites/maths` and `matcha/maths`. Temporary token 37 (`supertestnet-20261014`, `teahubxyz`) did the org, repos, hooks and pushes, and is deleted by id right after this push; the table is checked. Why: the operator asked that every domain be in the list.
+5 -1
View File
@@ -9,9 +9,13 @@ Adding a site: a line in `PAGES_SITES` + `fly deploy` (operator lane), `fly cert
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`,
yolopreneur.lol and
supertestnet.lol → `supertestnet/app#pages`, yolopreneur.lol and
`*.yolopreneur.lol` → `teahubxyz/time#gh-pages/ventures` (`PAGES_WILDCARDS`). 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.
+1 -1
View File
@@ -6,7 +6,7 @@ primary_region = "iad"
[env]
GITEA_URL = "https://teahub.xyz"
PAGES_SITES = '{"persimmons.app":"persimmons/app","phly.cloud":"phly/site","yolopreneur.lol":"teahubxyz/time#gh-pages/ventures"}'
PAGES_SITES = '{"persimmons.app":"persimmons/app","phly.cloud":"phly/site","supertestnet.lol":"supertestnet/app#pages","yolopreneur.lol":"teahubxyz/time#gh-pages/ventures"}'
PAGES_WILDCARDS = '{"yolopreneur.lol":"teahubxyz/time#gh-pages/ventures"}'
PAGES_PULL_SECONDS = "300"
+33 -3
View File
@@ -5,8 +5,10 @@ token). A clone lives under /data/<org>__<repo>__<branch>; `POST /_hook/<org>/<r
periodic loop pulls it. WILDCARDS maps a zone to a site whose top-level directories are its subdomains:
`{"yolopreneur.lol": "teahubxyz/time#gh-pages/ventures"}` serves `<sub>.yolopreneur.lol` from `ventures/<sub>/`, and
`yolopreneur.lol/<sub>/` redirects there. Static files only:
a path maps to the file, a directory to its index.html, anything missing to 404.html or a plain 404. No listing, no
execution, nothing writes except git. stdlib only.
a path maps to the file, a directory to its index.html, anything missing to 404.html or a plain 404. A `_headers` file
at a site's root adds response headers, Cloudflare Pages style (a path line, exact or ending in `*`, then indented
`Name: value` lines; a later rule wins for the same header). No listing, no execution, nothing writes except git.
stdlib only.
"""
import http.server
import json
@@ -73,6 +75,29 @@ def loop() -> None:
time.sleep(PULL_EVERY)
def extra_headers(root: Path, path: str) -> dict[str, str]:
"""Headers from `<root>/_headers` whose rule matches `path`."""
try:
lines = (root / "_headers").read_text().splitlines()
except OSError:
return {}
rules: list[tuple[str, list[tuple[str, str]]]] = []
for line in lines:
if not line.strip() or line.lstrip().startswith("#"):
continue
if not line[0].isspace():
rules.append((line.strip(), []))
elif rules and ":" in line:
name, _, value = line.strip().partition(":")
rules[-1][1].append((name.strip(), value.strip()))
out: dict[str, tuple[str, str]] = {}
for pattern, headers in rules:
if pattern == path or (pattern.endswith("*") and path.startswith(pattern[:-1])):
for name, value in headers:
out[name.lower()] = (name, value)
return dict(out.values())
class Handler(http.server.BaseHTTPRequestHandler):
server_version = "teahub-pages"
@@ -146,7 +171,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
return
target = target / "index.html"
status = 200
if not target.is_file() or ".git" in target.parts:
if not target.is_file() or ".git" in target.parts or target.name == "_headers":
status, target = 404, root / "404.html"
if not target.is_file():
self.send_error(404)
@@ -156,11 +181,16 @@ class Handler(http.server.BaseHTTPRequestHandler):
lead = data[:64].lstrip().lower() # not `head`: that name is the HEAD-request flag
if not target.suffix and (lead.startswith(b"<!doctype html") or lead.startswith(b"<html")):
ctype = TYPES[".html"] # extensionless pages (e.g. /signin) are HTML, not downloads
extra = extra_headers(root, path)
self.send_response(status)
self.send_header("content-type", ctype)
self.send_header("content-length", str(len(data)))
if not any(n.lower() == "cache-control" for n in extra):
self.send_header("cache-control", "public, max-age=60")
self.send_header("x-content-type-options", "nosniff")
for name, value in extra.items():
if name.lower() not in ("content-type", "content-length", "x-content-type-options"):
self.send_header(name, value)
self.end_headers()
if not head:
self.wfile.write(data)