From 6eae62a63a4ed65ce178042245304bb652ec8e52 Mon Sep 17 00:00:00 2001 From: Kris Yotam <75515498+krisyotam@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:41:47 -0500 Subject: [PATCH] ci: deploy Werc site to Vercel from SourceHut --- .build.yml | 27 ++++++++++ scripts/build_static.py | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 .build.yml create mode 100755 scripts/build_static.py diff --git a/.build.yml b/.build.yml new file mode 100644 index 0000000000000000000000000000000000000000..885317efe20df7ff89601b3606968fddb7955bb4 --- /dev/null +++ b/.build.yml @@ -0,0 +1,27 @@ +image: ubuntu/lts +secrets: + - 661293bf-f6d3-4ee1-8e74-34ea2fb70598 +environment: + VERCEL_ORG_ID: team_OoMniSEFMV1Arj9XJTTyujIC + VERCEL_PROJECT_ID: prj_Xj05rp3qZR6CY9iOHtDkuYXQfx3A +sources: + - https://git.sr.ht/~krisyotam/krisyotam.net +tasks: + - setup: | + sudo apt-get update + sudo apt-get install -y 9base python3 + curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - + sudo apt-get install -y nodejs + sudo npm install -g vercel@latest + - build: | + cd krisyotam.net + python3 scripts/build_static.py . dist + - deploy: | + cd krisyotam.net + source ~/.config/vercel/auth.env + vercel deploy dist \ + --prod \ + --yes \ + --token="$VERCEL_TOKEN" \ + --meta source=sourcehut \ + --meta commit="$(git rev-parse HEAD)" diff --git a/scripts/build_static.py b/scripts/build_static.py new file mode 100755 index 0000000000000000000000000000000000000000..3d6b13e81b4d69fd8fee1d9df087ef64676f0104 --- /dev/null +++ b/scripts/build_static.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Render the krisyotam.net Werc tree into a Vercel-ready static directory.""" + +from __future__ import annotations + +import os +from pathlib import Path +import shutil +import subprocess +import sys + + +def route_for(markdown_file: Path, site_root: Path) -> str: + relative = markdown_file.relative_to(site_root) + if relative.name == "index.md": + parent = relative.parent.as_posix() + return "/" if parent == "." else f"/{parent}/" + return f"/{relative.with_suffix('').as_posix()}" + + +def output_for(route: str, output_root: Path) -> Path: + if route == "/": + return output_root / "index.html" + return output_root / route.strip("/") / "index.html" + + +def render(repo: Path, route: str) -> bytes: + plan9 = Path("/usr/lib/plan9") + environment = os.environ.copy() + environment.update( + { + "PLAN9": str(plan9), + "PATH": ":".join( + [ + str(plan9 / "bin"), + str(repo / "bin"), + str(repo / "bin" / "contrib"), + "/bin", + "/usr/bin", + ] + ), + "SERVER_NAME": "plan9.krisyotam.com", + "SERVER_PORT": "443", + "REQUEST_METHOD": "GET", + "REQUEST_URI": route, + "PATH_INFO": route, + "QUERY_STRING": "", + "HTTP_COOKIE": "", + "HTTP_HOST": "plan9.krisyotam.com", + "HTTPS": "on", + } + ) + result = subprocess.run( + [str(plan9 / "bin" / "rc"), "./werc.rc"], + cwd=repo / "bin", + env=environment, + capture_output=True, + check=True, + timeout=20, + ) + if b"\n\n" not in result.stdout: + raise RuntimeError(f"No CGI header separator while rendering {route}") + _, body = result.stdout.split(b"\n\n", 1) + return body + + +def main() -> int: + if len(sys.argv) != 3: + print(f"usage: {sys.argv[0]} REPOSITORY OUTPUT", file=sys.stderr) + return 2 + + repo = Path(sys.argv[1]).resolve() + output_root = Path(sys.argv[2]).resolve() + site_root = repo / "sites" / "plan9.krisyotam.com" + + if output_root.exists(): + shutil.rmtree(output_root) + output_root.mkdir(parents=True) + + markdown_files = sorted( + path + for path in site_root.rglob("*.md") + if "_werc" not in path.relative_to(site_root).parts + ) + routes = [route_for(path, site_root) for path in markdown_files] + routes.extend(["/images/", "/sitemap"]) + + for route in routes: + destination = output_for(route, output_root) + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(render(repo, route)) + print(f"rendered {route} -> {destination.relative_to(output_root)}") + + (output_root / "404.html").write_bytes(render(repo, "/__not_found__")) + shutil.copytree(repo / "pub", output_root / "pub") + shutil.copytree( + repo / "images", + output_root / "images", + ignore=shutil.ignore_patterns("images"), + dirs_exist_ok=True, + ) + shutil.copy2(repo / "pub" / "favicon.ico", output_root / "favicon.ico") + shutil.copy2(site_root / "sitemap.txt", output_root / "sitemap.txt") + print(f"rendered {len(routes)} content routes and copied static assets") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())