~kris/9p

krisyotam.net

6eae62a63a4ed65ce178042245304bb652ec8e52 — Kris Yotam a month ago f269e2c
ci: deploy Werc site to Vercel from SourceHut
2 files changed, 136 insertions(+), 0 deletions(-)

A .build.yml
A scripts/build_static.py
A .build.yml => .build.yml +27 -0
@@ 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)"

A scripts/build_static.py => scripts/build_static.py +109 -0
@@ 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())