pages: wildcard zones — <sub>.zone serves dir/<sub>, apex /<sub>/ redirects there
This commit is contained in:
1 parent
36b18eb8cc
commit
e35578c73e
1 file changed
+33
-8
+33
-8
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user