Changes touching this path
- publish-r2: mirror release artifacts to Cloudflare R2 for anonymous download (#807)
The repo is private permanently (ADR 0045), so Release assets 404 for an
unauthenticated fetch — which is what broke `curl | sh`. This adds the anonymous
path that replaces them: every asset the Release publishes is mirrored to
dl.millerbyte.com/<tag>/, served from R2 with no credentials.
The bucket, its scoped credential and the dl.millerbyte.com custom domain are
provisioned by scripts/setup-artifacts.js (scripts @ 736f6e8), and the anonymous
fetch is already proven live against that bucket.
A *publish* job, not post-announce, and the ordering is load-bearing: publish jobs
run after `host` uploads the Release and `announce` waits for them, so R2 is
populated before the post-announce smoke test. Listed post-announce instead, #808's
`curl | sh` gate would race the very artifacts it fetches.
The job body is hand-owned in its own file so `dist generate` cannot clobber it —
the same shape as smoke-install.yml (#258). It carries a workflow_dispatch tag input
so it can be exercised against an existing release rather than first running during a
real one, verifies every mirrored asset anonymously against the Release's own sha256,
and warns (not fails) if the edge is not caching.
⚠ dist-workspace.toml also documents that it must be edited with `dist generate`,
never `dist init` — init rewrites the config from its own defaults and strips every
comment, which today destroyed the #270 native-ARM64-runner rationale and the whole
ADR 0045 attestations block before they were restored.
5b710e89 · dbf3dbe6… - installer: serve the R2 base first, and turn the curl | sh gate back on (#808)
The hero one-liner has been returning 404 in production. The site's /install.sh proxy
fetched the installer from GitHub anonymously, and the repo is private permanently
(ADR 0045), so there was nothing to fetch. #807 put the artifacts on R2; this points
the installer at them.
The proxy now reads upstream from dl.millerbyte.com and rewrites the generated
ARTIFACT_DOWNLOAD_URLS default to list the R2 base FIRST and GitHub second. dist's
installers already try each base in order, so the fallback costs nothing and keeps
working for anyone with credentials.
⭐ The rewrite derives the version from the line it is rewriting. The anchor already
contains the release tag, so capturing it means the site needs no version knowledge,
no 'latest' pointer to resolve and no credential — a test moves the tag and the R2
base follows.
⚠ The two installers genuinely differ and one regex would have silently matched
neither: sh builds a space-separated string, ps1 builds an array. The ps1 anchor is
pinned to the ASSIGNMENT rather than the URL, because the same URL also appears in
that script's help text — a loose match would rewrite the comment, leave the real
download list GitHub-only, and still pass every check that greps for the R2 base.
The rewrite THROWS if the anchor is absent. A silent no-op is the current bug wearing
a different hat: it would ship an installer pointing only at URLs that 404 for exactly
the audience the one-liner exists for, while the route returns 200 and the bytes look
like a real script. Nine tests cover both installers, both drift cases, and the
help-text trap; the anchors were also validated against the real 54 KB v0.4.2 scripts.
publish-r2 now also writes unversioned installer copies to /installers/. That is the
one deliberately mutable object in the bucket — every v{VERSION}/ prefix stays
immutable and year-cached, but 'latest' has to move or the one-liner pins itself to
whichever release shipped first. It carries max-age=300, matching the proxy.
smoke-install.yml loses its `if: false` and now defaults to the hero path rather than
a GitHub-direct URL. Testing a URL no user is told to run proved nothing; this
exercises the whole chain — proxy, rewrite, R2. The skip comment is kept as history,
because why it was disabled is why the gate is worth having.
6bf1df11 · dbf3dbe6…diff
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.