Realliance brand restyle, brand sync from v0.1.0, CI on the forge #1

Merged
chris merged 5 commits from brand into main 2026-11-05 00:04:09 +00:00
Owner

The Realliance brand restyle, the brand sync from the signed realliance/brand tag v0.1.0, and CI on the forge. Every commit here carries a signature. The restyle commit got its signature on the same tree, so its content, author, and message stay the same.

What changes

  • Restyle (previewed earlier): self-hosted Atkinson Hyperlegible + Fredoka, Mocha with Latte under prefers-color-scheme, the ReA logo and favicons, and the same content and structure. terminated.html gets the new style with the same text and links. The site makes no third-party loads, and the live CSP does not change.
  • Brand sync: scripts/sync-brand.sh --tag v0.1.0 checks the tag signature against brand/release-signers.* and writes static/brand/, brand/map.json and brand.lock. build.ts resolves {{brand:<name>}} tokens and fails on an unknown name or a stray /brand/ path.
  • Reproducible build: the build names member images by a content hash, pins the three unversioned deno.land/x imports, and runs with --frozen.
  • CI on the forge (.forgejo/workflows/ci.yaml, runner general):
    • The pinned, sha256-checked Deno 2.8.3 and crane 0.22.1 build the site.
    • One deterministic layer goes onto the pinned nginx:stable-alpine base (nginx 1.30.6), with port 8080 and the source and revision labels. The whole build runs in a job-local registry, with no Docker and no credentials.
    • scripts/check-image.sh checks the result against the base.
    • Push events publish sha-<7>, plus X.Y.Z on a tag, to git.realliance.net/realliance/realliance.net. CI never moves a tag.
  • Removed: Dockerfile, .dockerignore, .github/ and .renovaterc.json.

Decisions

  • D3: the restyle commit had no signature. It now has one, on the same tree.
  • D4: no Docker anywhere. The scripts are the build, and every one of them runs locally (just image).
  • D5: append and mutate run against crane registry serve, and the image leaves as an OCI layout, so the pushed digest is the built one.
  • D6: the runtime matches today's, except for the restyle and the nginx base. nginx.conf is byte-identical to the live image's. Entrypoint, Cmd, StopSignal and User match. ExposedPorts becomes 8080/tcp, the port nginx listens on.
  • D7: reproducible. Two clean builds of one commit give one digest.
  • D8: brand files come only from a signed tag, checked against the signers committed here.
  • D9: the registry token, a repo secret, reaches every realliance package, because Forgejo 15 has no repo-scoped tokens. The defences are the digest pin in the cluster, no Renovate automerge, and CI never moving a tag.
  • D13: the forge's job token cannot push packages. A probe run got 401 Unauthorized: reqPackageAccess.

Testing

  • scripts/ci.sh build, run twice from git clean -xfd, printed check-image: PASS both times, with the same digest both times. Peak RSS was about 275 MB.
  • The built config, compared with the live quay.io/realliance/realliance.net:2.3: Entrypoint, Cmd, StopSignal and User match, and only ExposedPorts changes (80 to 8080). The other differences come from the newer base.
  • Never moving a tag, tested against scratch local registries:
    • a sha-<7> tag that already held another image made the job exit 1 with refusing to move, and the tag stayed where it had been;
    • an empty registry took the push, and the remote digest equals the built one;
    • a tag build reused the existing sha-<7>;
    • publish refused v2.4.0 as not X.Y.Z.
  • check-image.sh against the live Quay image fails, as it should.
  • The runner builds the same digest as a laptop. The first PR run did not, because the runner's work volume has setgid directories. Since d0eb467 the layer clears s/t bits, check-image.sh asserts 0644 files and 0755 directories, and run 11 built sha256:0d938cd3…074a, the laptop's digest for the same commit.
  • The PR runs build and check the image and push nothing.

Never approve workflow runs from fork pull requests.

The Realliance brand restyle, the brand sync from the signed `realliance/brand` tag `v0.1.0`, and CI on the forge. Every commit here carries a signature. The restyle commit got its signature on the same tree, so its content, author, and message stay the same. ## What changes - **Restyle** (previewed earlier): self-hosted Atkinson Hyperlegible + Fredoka, Mocha with Latte under `prefers-color-scheme`, the ReA logo and favicons, and the same content and structure. `terminated.html` gets the new style with the same text and links. The site makes no third-party loads, and the live CSP does not change. - **Brand sync:** `scripts/sync-brand.sh --tag v0.1.0` checks the tag signature against `brand/release-signers.*` and writes `static/brand/`, `brand/map.json` and `brand.lock`. `build.ts` resolves `{{brand:<name>}}` tokens and fails on an unknown name or a stray `/brand/` path. - **Reproducible build:** the build names member images by a content hash, pins the three unversioned `deno.land/x` imports, and runs with `--frozen`. - **CI on the forge** (`.forgejo/workflows/ci.yaml`, runner `general`): - The pinned, sha256-checked Deno 2.8.3 and crane 0.22.1 build the site. - One deterministic layer goes onto the pinned `nginx:stable-alpine` base (nginx 1.30.6), with port 8080 and the source and revision labels. The whole build runs in a job-local registry, with no Docker and no credentials. - `scripts/check-image.sh` checks the result against the base. - Push events publish `sha-<7>`, plus `X.Y.Z` on a tag, to `git.realliance.net/realliance/realliance.net`. CI never moves a tag. - **Removed:** `Dockerfile`, `.dockerignore`, `.github/` and `.renovaterc.json`. ## Decisions - D3: the restyle commit had no signature. It now has one, on the same tree. - D4: no Docker anywhere. The scripts are the build, and every one of them runs locally (`just image`). - D5: append and mutate run against `crane registry serve`, and the image leaves as an OCI layout, so the pushed digest is the built one. - D6: the runtime matches today's, except for the restyle and the nginx base. `nginx.conf` is byte-identical to the live image's. `Entrypoint`, `Cmd`, `StopSignal` and `User` match. `ExposedPorts` becomes `8080/tcp`, the port nginx listens on. - D7: reproducible. Two clean builds of one commit give one digest. - D8: brand files come only from a signed tag, checked against the signers committed here. - D9: the registry token, a repo secret, reaches every `realliance` package, because Forgejo 15 has no repo-scoped tokens. The defences are the digest pin in the cluster, no Renovate `automerge`, and CI never moving a tag. - D13: the forge's job token cannot push packages. A probe run got `401 Unauthorized: reqPackageAccess`. ## Testing - `scripts/ci.sh build`, run twice from `git clean -xfd`, printed `check-image: PASS` both times, with the same digest both times. Peak RSS was about 275 MB. - The built config, compared with the live `quay.io/realliance/realliance.net:2.3`: `Entrypoint`, `Cmd`, `StopSignal` and `User` match, and only `ExposedPorts` changes (80 to 8080). The other differences come from the newer base. - Never moving a tag, tested against scratch local registries: - a `sha-<7>` tag that already held another image made the job exit 1 with `refusing to move`, and the tag stayed where it had been; - an empty registry took the push, and the remote digest equals the built one; - a tag build reused the existing `sha-<7>`; - publish refused `v2.4.0` as not `X.Y.Z`. - `check-image.sh` against the live Quay image fails, as it should. - The runner builds the same digest as a laptop. The first PR run did not, because the runner's work volume has `setgid` directories. Since d0eb467 the layer clears s/t bits, `check-image.sh` asserts 0644 files and 0755 directories, and run 11 built `sha256:0d938cd3…074a`, the laptop's digest for the same commit. - The PR runs build and check the image and push nothing. Never approve workflow runs from fork pull requests.
Self-hosted Atkinson Hyperlegible and Fredoka, Catppuccin Mocha/Latte Sapphire
on prefers-color-scheme, 8/12px corners, new ReA logo and favicons.
Adds scripts/sync-brand.sh + brand.lock (stub until realliance/brand is tagged).
Fixes the build on current Deno (pinned std/npm imports, lockfile, pinned image)
and the page clipping caused by '* { overflow: hidden }' with h-screen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
Finish sync-brand.sh --tag (signer check, manifest sha256, staged copy), add
--check --release, drop --stub. Sources use {{brand:name}} tokens that build.ts
resolves through brand/map.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
Member images are named by the first 16 hex of the sha256 of their encoded
bytes instead of crypto.randomUUID(), so two builds of one commit give the
same files. The three unversioned deno.land/x imports are pinned to what
deno.lock already resolved (pug@v0.1.6, twd@v0.4.8, recursive_readdir@v2.0.0),
deno.lock is refreshed once for them, and the build runs with --frozen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
CI on the forge: Deno + crane image build on the general runner; drop Docker and GitHub CI
All checks were successful
ci / build (pull_request) Successful in 8s
d084002d85
.forgejo/workflows/ci.yaml runs on the forge's `general` runner. Every run
builds (scripts/ci.sh build): pinned, sha256-checked Deno and crane
(scripts/ci-tools.sh), the brand check, the frozen Deno build, then
scripts/build-image.sh appends one deterministic layer (the site plus
nginx.conf) to the pinned nginx:stable-alpine base and sets port 8080 and the
source/revision labels, all against a job-local `crane registry serve`, and
scripts/check-image.sh checks the result against the base. Push events then
publish (scripts/ci.sh publish) sha-<7>, plus X.Y.Z on a tag, to
git.realliance.net/realliance/realliance.net with SITE_REGISTRY_TOKEN, and
never move an existing tag.

The base moves to the current nginx stable-alpine (1.30.6), pinned by digest.
Dockerfile, .dockerignore, .github/ and .renovaterc.json go: there is no
Docker in the build and neither GitHub Actions nor Renovate reaches the forge.
The README covers developing, the image, CI and releasing; `just image` runs
the CI build locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
build-image: clear setgid/sticky bits in the layer; check-image asserts 0644/0755
All checks were successful
ci / build (pull_request) Successful in 8s
ci / build (push) Successful in 12s
d0eb467cae
The first PR run built a different digest from a laptop build of the same
commit: every file matched, but the runner's work volume has setgid
directories (fsGroup), and `--mode='a+rX,u+w,go-w'` keeps a directory's
setgid bit. `a-st` clears it. check-image now fails on any entry that is not
a 0644 file or a 0755 directory, so mode drift cannot pass silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWhaDeswMqYvQaVdpL8oit
chris merged commit d0eb467cae into main 2026-11-05 00:04:09 +00:00
chris deleted branch brand 2026-11-05 00:04:09 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
realliance/realliance.net!1
No description provided.