Commit 3cd375119f

3cd375119f0ebe4fe3e851e547d0cd5ff8683c82

parent: fc693369d1

Unsigned ci/build: success

cmc <hello@cleberg.net> · 2026-08-31 05:06 UTC

Add a powered-by-orgo footer note, versioned at build time (!1)

Adds `Powered by orgo <version>` to the site footer, linking to
https://gitbay.org/krz/orgo. Every HTML page extends `base.html`, so all
198 carry it.

orgo exposes no version to templates and `[site]` in `orgo.toml` rejects
unknown keys, so the version is substituted into the built HTML by
`build.py`, which reads it from `orgo --version` after the build. The
template holds the current version as a seed and fallback for builds that
bypass `build.py` (`orgo serve`).

The rewrite runs on every page rather than only the re-rendered ones: the
build cache keys on the cache format version, not orgo's release version,
so pages carried over from a build by an older orgo would otherwise keep
printing it. A build where the note matches nothing exits 1.

Verified: clean prod build (199 pages, 0 unresolved links) substitutes on
198 pages; a page hand-edited to an old version is corrected on a
fully-cached rebuild; removing the note fails the build.

Layout: unified · split

.gitbay/ci.yml +16 −8
@@ -45,13 +45,20 @@ jobs:
45 # orgo is installed from crates.io and takes minutes to compile, so it is 45 # orgo is installed from crates.io and takes minutes to compile, so it is
46 # rebuilt only when this version changes. Nothing bumps it automatically: 46 # rebuilt only when this version changes. Nothing bumps it automatically:
47 # raise it by hand, which is also how you find out orgo broke the site. 47 # raise it by hand, which is also how you find out orgo broke the site.
48 #
49 # --root pins where it lands, like ruff and lychee below. Without it cargo
50 # installs under whatever cargo home the runner sets, which is not $HOME:
51 # the binary goes somewhere this file does not name, and the next run's
52 # `cargo install` declines to repeat itself because its metadata already
53 # records the version as installed.
48 - | 54 - |
49 set -eu 55 set -eu
50 v=0.22.0 56 v=0.22.0
51 if ! "$HOME/.cargo/bin/orgo" --version 2>/dev/null | grep -qx "orgo $v"; then 57 if ! "$HOME/tools/orgo/bin/orgo" --version 2>/dev/null | grep -qx "orgo $v"; then
52 "$HOME/.cargo/bin/cargo" install orgo --version "$v" --locked 58 "$HOME/.cargo/bin/cargo" install orgo --version "$v" --locked \
59 --root "$HOME/tools/orgo"
53 fi 60 fi
54 "$HOME/.cargo/bin/orgo" --version 61 "$HOME/tools/orgo/bin/orgo" --version
55 python3 --version 62 python3 --version
56 63
57 # build.py deploys with rsync when DEPLOY=true and ENV=prod. This build 64 # build.py deploys with rsync when DEPLOY=true and ENV=prod. This build
@@ -74,7 +81,7 @@ jobs:
74 exit 1 81 exit 1
75 EOF 82 EOF
76 chmod +x .ci-bin/rsync 83 chmod +x .ci-bin/rsync
77 PATH="$PWD/.ci-bin:$HOME/.cargo/bin:$PATH" ENV=prod BUILD=true python3 build.py 84 PATH="$PWD/.ci-bin:$HOME/tools/orgo/bin:$PATH" ENV=prod BUILD=true python3 build.py
78 85
79 - | 86 - |
80 set -eu 87 set -eu
@@ -99,10 +106,11 @@ jobs:
99 | sh -s -- -y --no-modify-path --profile minimal 106 | sh -s -- -y --no-modify-path --profile minimal
100 fi 107 fi
101 v=0.22.0 108 v=0.22.0
102 if ! "$HOME/.cargo/bin/orgo" --version 2>/dev/null | grep -qx "orgo $v"; then 109 if ! "$HOME/tools/orgo/bin/orgo" --version 2>/dev/null | grep -qx "orgo $v"; then
103 "$HOME/.cargo/bin/cargo" install orgo --version "$v" --locked 110 "$HOME/.cargo/bin/cargo" install orgo --version "$v" --locked \
111 --root "$HOME/tools/orgo"
104 fi 112 fi
105 "$HOME/.cargo/bin/orgo" --version 113 "$HOME/tools/orgo/bin/orgo" --version
106 114
107 # Prebuilt release binary, checksum-verified, kept in $HOME between runs. 115 # Prebuilt release binary, checksum-verified, kept in $HOME between runs.
108 - | 116 - |
@@ -125,7 +133,7 @@ jobs:
125 # URLs to /img/, which is served from a different docroot and would look 133 # URLs to /img/, which is served from a different docroot and would look
126 # like 198 broken links here. The development output keeps them absolute, 134 # like 198 broken links here. The development output keeps them absolute,
127 # so the image host gets checked for real. 135 # so the image host gets checked for real.
128 - $HOME/.cargo/bin/orgo build content -o .build-dev --strict 136 - $HOME/tools/orgo/bin/orgo build content -o .build-dev --strict
129 137
130 # Internal links are the half worth gating on: they are entirely within 138 # Internal links are the half worth gating on: they are entirely within
131 # this repo, so a failure is always a real defect and always fixable here. 139 # this repo, so a failure is always a real defect and always fixable here.
README.md +3 −2
@@ -66,8 +66,9 @@ For users employing Doom Emacs, open any repository Org file using
66 66
67## Building and Publishing the Site 67## Building and Publishing the Site
68 68
69The `build.py` script wraps the build: it runs orgo, and then either 69The `build.py` script wraps the build: it runs orgo, rewrites the footer's
70deploys the result or serves it locally. 70orgo version to match the orgo that ran, and then either deploys the result
71or serves it locally.
71 72
72Environment variables control what it does, and all default to off: 73Environment variables control what it does, and all default to off:
73 74
build.py +59
@@ -4,6 +4,7 @@ This script automates the process of building and deploying the website.
4It handles tasks such as: 4It handles tasks such as:
5 5
6- Running orgo to generate site content. 6- Running orgo to generate site content.
7- Rewriting the footer's orgo version to match the orgo that built the site.
7- Rewriting image URLs for the onion service (production only). 8- Rewriting image URLs for the onion service (production only).
8- Optionally deploying the built site to a remote server. 9- Optionally deploying the built site to a remote server.
9- Starting a local development server for previewing changes. 10- Starting a local development server for previewing changes.
@@ -77,6 +78,63 @@ def rewrite_img_urls(build_dir=".build"):
77 print(f"Rewrote {count} img.cleberg.net references to /img/") 78 print(f"Rewrote {count} img.cleberg.net references to /img/")
78 79
79 80
81# The footer's "Powered by orgo <version>" note. Matching the whole phrase rewrites a
82# page carrying an old version as readily as one still carrying the template's
83# placeholder, and leaves prose that ends a paragraph with a link to orgo alone.
84ORGO_NOTE = re.compile(
85 r'(Powered by <a href="https://gitbay\.org/krz/orgo">orgo</a> )[^<]*(</p>)'
86)
87
88
89def orgo_version():
90 """The version reported by the orgo on PATH, e.g. "0.22.0"."""
91 result = subprocess.run(
92 ["orgo", "--version"], capture_output=True, text=True, check=False
93 )
94 if result.returncode != 0:
95 print("Could not read the orgo version:", file=sys.stderr)
96 print(result.stderr, file=sys.stderr)
97 sys.exit(1)
98 # `orgo --version` prints "orgo X.Y.Z". Anything else means the output format
99 # changed, and guessing at it would ship a wrong version to every page.
100 parts = result.stdout.split()
101 if len(parts) != 2 or parts[0] != "orgo":
102 print(f"Unexpected `orgo --version` output: {result.stdout!r}", file=sys.stderr)
103 sys.exit(1)
104 return parts[1]
105
106
107def rewrite_orgo_version(build_dir):
108 """Point the footer's orgo version at the orgo that just built the site.
109
110 Runs over every page rather than only the re-rendered ones, because the build cache
111 keys on a cache *format* version and not on orgo's release version: after a version
112 bump that leaves the format alone, the pages carried over from the previous build
113 would otherwise keep printing the old one.
114
115 The whole point of this is that the note cannot go stale, so a footer that no longer
116 matches is a failure and not a no-op — matching nothing anywhere means the note was
117 removed or its markup changed, and a silent pass would ship the template's
118 placeholder to every page.
119 """
120 version = orgo_version()
121 count = 0
122 for html in Path(build_dir).rglob("*.html"):
123 text = html.read_text(encoding="utf-8")
124 new_text, n = ORGO_NOTE.subn(rf"\g<1>{version}\g<2>", text)
125 if n and new_text != text:
126 html.write_text(new_text, encoding="utf-8")
127 count += n
128 if count == 0:
129 print(
130 "No 'Powered by orgo' note found in the build — the footer in "
131 "content/templates/base.html no longer matches ORGO_NOTE",
132 file=sys.stderr,
133 )
134 sys.exit(1)
135 print(f"Set the footer orgo version to {version} on {count} pages")
136
137
80def run_orgo_build(build_dir): 138def run_orgo_build(build_dir):
81 """ 139 """
82 Build the site with orgo. 140 Build the site with orgo.
@@ -164,6 +222,7 @@ def main():
164 222
165 if os.environ.get("BUILD", "").casefold() == "true": 223 if os.environ.get("BUILD", "").casefold() == "true":
166 run_orgo_build(build_dir) 224 run_orgo_build(build_dir)
225 rewrite_orgo_version(build_dir)
167 # The onion needs same-origin images; dev previews keep the absolute URLs. 226 # The onion needs same-origin images; dev previews keep the absolute URLs.
168 # Runs over every page, not just the re-rendered ones, so a page carried over 227 # Runs over every page, not just the re-rendered ones, so a page carried over
169 # from an earlier build is rewritten too. 228 # from an earlier build is rewritten too.
content/templates/base.html +1
@@ -93,6 +93,7 @@
93<a href="{{ root }}tips/index.html">Tips</a> &middot; 93<a href="{{ root }}tips/index.html">Tips</a> &middot;
94<a href="https://iheartrss.com/">I &hearts; RSS</a> 94<a href="https://iheartrss.com/">I &hearts; RSS</a>
95<p>[ <a href="https://krz.sh">krz</a> &middot; <a href="https://audit-labs.dev">audit labs</a> ]</p> 95<p>[ <a href="https://krz.sh">krz</a> &middot; <a href="https://audit-labs.dev">audit labs</a> ]</p>
96<p>Powered by <a href="https://gitbay.org/krz/orgo">orgo</a> ORGO_VERSION</p>
96</footer> 97</footer>
97</body> 98</body>
98</html> 99</html>