Download site generator
cmd/build-release-site produces the static download page and versioned
artifact tree served from GitHub Pages. It reads a release-metadata YAML,
copies (and SHA256-verifies) the launcher and bundle artifacts into a
bootstrap/<path>/ and bundles/<path>/ layout, and renders an
index.html for the current release plus a releases/index.html listing
older entries.
If cmd/build-bundle produces the artifacts, cmd/build-release-site
publishes them.
Build it
make build-release-site # → dist/build-release-site
Run it
./dist/build-release-site \
--metadata site/releases.example.yaml \
--output dist/release-site
Or, with the example metadata:
make release-site
Both flags are required. The output directory is removed and recreated on every run.
What it generates
dist/release-site/
├── index.html # current release landing page
├── releases/
│ └── index.html # all releases, current first
├── metadata.json # machine-readable manifest (schema_version 1)
├── bootstrap/
│ └── <path>/
│ └── aether-ops-bootstrap
└── bundles/
└── <path>/
├── bundle.tar.zst
└── bundle.tar.zst.sha256
metadata.json mirrors the input metadata but with public-facing fields
only (URLs resolved against site.base_url_path, computed SHA256s,
artifact paths). It's the contract for any consumer that wants to discover
releases without scraping HTML.
Metadata schema
The input is a single YAML file. Schema version 1:
schema_version: 1
site:
title: Aether Ops Bootstrap Downloads
base_url_path: /aether-ops-bootstrap
description: Public download page for the bootstrap launcher and offline bundle artifacts.
releases:
- id: "2026-04-21-example"
published_at: "2026-04-21"
current: true # exactly one release should set this
bootstrap:
label: Bootstrap Launcher
version: "0.1.43"
path: "0.1.43" # subdirectory under bootstrap/
filename: aether-ops-bootstrap
source: ../dist/aether-ops-bootstrap
commit: "341787b"
release_summary: >- # one-sentence headline (optional)
Replace this with a one- or two-sentence summary of the launcher change.
release_notes: # bullet list rendered inside a
- First bullet. # collapsible "Show change details"
- Second bullet. # disclosure on the download page.
bundle:
label: Offline Bundle
version: "2026.04.1"
path: "2026.04.1" # subdirectory under bundles/
filename: bundle.tar.zst
source: ../dist/bundle.tar.zst
sha256_source: ../dist/bundle.tar.zst.sha256
build_commit: "341787b"
release_summary: >-
Replace this with a one- or two-sentence summary of the bundle change.
release_notes:
- First bullet.
- Second bullet.
components: # rendered under the artifact card
- name: aether-ops
version: v0.1.49
- name: aether-onramp
commit: eb0b89bdeb00342deab732b73c0187175a0a7265
- name: rke2
version: v1.33.1+rke2r1
source paths are resolved relative to the metadata file's directory.
If sha256_source is provided, the value is verified against a freshly
computed hash of source; if omitted, the generator computes and emits
the hash itself.
components is an optional list of bundled-or-related software whose
identity matters for an operator deciding whether to upgrade. Each entry
needs a name plus at least one of version or commit. Long commit
hashes are auto-shortened to 7 characters in the rendered HTML, but the
full value is preserved in metadata.json. The same field is available
on bootstrap for symmetry, though it's typically left empty there.
External releases
Set external: true on a release to reference artifacts that already
live on the published site (e.g. older releases whose source files you
no longer have locally). External entries:
- skip the
sourcecopy and SHA computation - require an explicit
sha256on each artifact (used in metadata + UI) - still use
pathto construct the public URL - contribute to
metadata.jsonand the release-history page
Because --output is wiped on every run, syncing the generator's output
to a host that uses rsync --delete (or any "replace tree" deploy)
would remove the external artifact files. Use a deploy that preserves
files outside the freshly-generated set (e.g. rsync without --delete,
or only sync the new release's subdirectories plus index.html,
releases/index.html, and metadata.json).
The canonical example lives at
site/releases.example.yaml.
Typical workflow
make package— produce the launcher and bundle indist/.- Update
site/releases.yaml(your real, non-example metadata) with the new release block. Setcurrent: trueon it; clearcurrenton the previous entry. ./dist/build-release-site --metadata site/releases.yaml --output dist/release-site.- Publish
dist/release-site/(commit to thegh-pagesbranch, sync to a static host, etc.).
The generator is fully deterministic given the same inputs — re-running it on the same metadata produces a byte-identical tree.