pages: wildcard zones — <sub>.zone serves dir/<sub>, apex /<sub>/ redirects there

This commit is contained in:
teahubxyz committed 2026-10-14 08:15:01 +00:00
1 parent 36b18eb8cc
commit e35578c73e
1 file changed
+33 -8
+33 -8
View File
@@ -2,7 +2,9 @@
SITES maps hostnames to `org/repo`, or `org/repo#branch/subdir`, on GITEA (public repos only; cloned over https, no
token). A clone lives under /data/<org>__<repo>__<branch>; `POST /_hook/<org>/<repo>` (a Gitea webhook) or the
periodic loop pulls it. Static files only:
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.
"""
@@ -19,6 +21,7 @@ GITEA = os.environ.get("GITEA_URL", "https://teahub.xyz").rstrip("/")
DATA = Path(os.environ.get("PAGES_DATA", "/data"))
SITES = json.loads(os.environ.get("PAGES_SITES") or "{}") # {"persimmons.app": "persimmons/site", ...}
PULL_EVERY = int(os.environ.get("PAGES_PULL_SECONDS") or 300)
WILDCARDS = json.loads(os.environ.get("PAGES_WILDCARDS") or "{}") # {"zone": "org/repo#branch/dir"}: <sub>.zone -> dir/<sub>
TYPES = {".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "text/javascript; charset=utf-8",
".mjs": "text/javascript; charset=utf-8", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png",
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".ico": "image/x-icon",
@@ -65,7 +68,7 @@ def sync(site: str) -> str:
def loop() -> None:
while True:
for repo in set(SITES.values()):
for repo in set(SITES.values()) | set(WILDCARDS.values()):
print(repo, sync(repo), flush=True)
time.sleep(PULL_EVERY)
@@ -76,13 +79,24 @@ class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, fmt, *args): # one line per request, no client address
print(f"{self.headers.get('host', '-')} {self.command} {self.path.split('?')[0]} {args[1] if len(args) > 1 else ''}", flush=True)
def site(self) -> str | None:
host = (self.headers.get("host") or "").split(":")[0].lower()
return SITES.get(host) or SITES.get(host[4:] if host.startswith("www.") else "")
def host(self) -> str:
return (self.headers.get("host") or "").split(":")[0].lower()
def site(self) -> tuple[str, str] | None:
"""(site, subdir) for this request's host: an exact site, its www, or a wildcard subdomain."""
host = self.host()
s = SITES.get(host) or SITES.get(host[4:] if host.startswith("www.") else "")
if s:
return s, ""
sub, _, zone = host.partition(".")
w = WILDCARDS.get(zone)
if w and sub and sub != "www" and "/" not in sub and ".." not in sub:
return w, sub
return None
def do_POST(self):
parts = urlsplit(self.path).path.strip("/").split("/")
hit = [s for s in SITES.values() if len(parts) == 3 and parts[0] == "_hook" and parse(s)[0] == f"{parts[1]}/{parts[2]}"]
hit = [s for s in [*SITES.values(), *WILDCARDS.values()] if len(parts) == 3 and parts[0] == "_hook" and parse(s)[0] == f"{parts[1]}/{parts[2]}"]
if hit:
self.rfile.read(int(self.headers.get("content-length") or 0))
outs = [sync(s) for s in dict.fromkeys(hit)] # every site on that repo (any branch)
@@ -104,12 +118,23 @@ class Handler(http.server.BaseHTTPRequestHandler):
if not head:
self.wfile.write(body)
return
repo = self.site()
if not repo:
found = self.site()
if not found:
self.send_error(404, "no site for this host")
return
repo, sub = found
root = site_root(repo).resolve()
rel = unquote(path).lstrip("/")
# on a wildcard zone's apex, /<sub>/... lives at <sub>.zone: send people there
head = rel.split("/", 1)[0]
if not sub and self.host() in WILDCARDS and head and (root / head).is_dir() and ".." not in rel:
self.send_response(301); self.send_header("location", f"https://{head}.{self.host()}/" + rel[len(head):].lstrip("/")); self.end_headers()
return
if sub:
root = (root / sub).resolve()
if not root.is_dir():
self.send_error(404, "no such venture")
return
target = (root / rel).resolve() if rel else root
if root not in target.parents and target != root:
self.send_error(404)