A Playwright job on the perf guard's production build runs axe-core's
WCAG 2.2 A/AA rules on the mail list, reading pane, composer, calendar,
contacts and Settings > Accessibility, and fails when a screen has more
serious or critical violations than scripts/a11y-guard/budget.json
allows (all zero today). Two more checks cover what axe can't see: the
keyboard focus outline stays visible in Windows contrast themes, and
single-key shortcuts don't act on the mail behind an open dialog.
axe-core is vendored next to the spec (MPL-2.0, see NOTICE), so the
check adds no dependency. `A11Y_GUARD_UPDATE=1 npm run test:a11y-guard`
rewrites the budget.
Every plugin iframe loaded /plugin-sandbox, a force-dynamic Next page.
Each frame then fetched, parsed and hydrated Next's client router on top
of React and the runtime, and the server rendered the page once per
frame, one after another when a dozen plugins started together.
Sandboxed frames have opaque origins, so the browser's code cache never
helps, and they run on the host page's main thread: with 12 plugins
the frames took 2.5-3 s to come up after the app had loaded.
The sandbox routes now return a small HTML page with the UI kit's
stylesheet and the runtime as an ES module, both built ahead of time:
- `scripts/build-plugin-sandbox.mjs` bundles the runtime with esbuild
and compiles `plugin-kit.css` with the app's Tailwind setup into
`public/plugin-sandbox-assets/`, with a manifest. Named Tabler imports
are pointed at the icons' own files, so the full icon set stays in a
chunk that only loads for an icon name the kit doesn't ship.
- `npm run build`, `npm run dev`, the Dockerfile and the perf-guard
workflow run it before `next build`.
- `lib/plugin-sandbox/sandbox-document.ts` writes the page; proxy.ts
still sets the sandbox CSP and the script carries its nonce.
- The assets are served from public/ with CORS and immutable caching.
A frame on an idle page is ready in 255 ms instead of 362 ms, eleven at
once in 1.0 s instead of 2.1 s.
Nothing showed where a published image or download came from, and the
standalone tarballs had no checksum at all. Anyone who could swap a
release asset could swap its `.sha256` with it.
- every published image (release, `-always`, beta/dev and Lite) gets a
signed build provenance attestation, pushed to GHCR next to it
- the Lite zips and standalone tarballs are attested as well
- standalone tarballs get a `.sha256`, like the Lite zips
- the tarball build no longer holds a write token: a separate release
job, which runs no checked-out code, checksums, attests and uploads
- the README says how to check with `gh attestation verify`
The Stalwart bundle's README told admins to use
`releases/latest/download/bulwark-lite-stalwart.zip` as `resourceUrl`.
Stalwart re-downloads that URL on its own schedule and checks no hash or
signature, so whatever the newest release held would run as script on
the Stalwart origin next to the web admin.
The README now installs from the tagged release, explains updating by
moving `resourceUrl` to the next tag, and describes the floating URL as
an opt-in with that risk spelled out. Release runs upload a `.sha256`
next to each Lite zip.
A manual run of the release image workflow tagged `latest` on whatever
ref it was dispatched from, and the demo deploy and every Watchtower
install follow `latest`. It now moves only for a published stable
release or a dispatch on a stable `v*` tag.
- `static-lite.yml` gave the whole workflow `contents: write`, including
the build job that runs pull-request code. The build is read-only
now; a separate release job with no checkout uploads the zips.
- `docker-publish.yml` passes the ref name through `env` instead of
interpolating it into the shell script.
The demo container followed ghcr.io/bulwarkmail/webmail:latest through
Watchtower, so a release reached it silently and there was no way to see,
trigger or undo it. It is now a deployment of this repo:
- .github/workflows/deploy-demo.yml runs after "Publish Docker Image on
Release" succeeds, or on demand with a tag
(`gh workflow run deploy-demo.yml -f tag=1.10.0`, or action=rollback).
It only talks to the server; nothing is built.
- deploy/demo-deploy.sh is the forced command behind the deploy key
(/usr/local/bin/bulwark-demo-deploy): pulls the tag, recreates the
container with the demo mounts, checks /api/health and goes back to the
image it replaced if that fails. A pre-release moves no :latest, so the
deploy after one finds the same image and does nothing.
Watchtower has been recreated without bulwark-demo; it still updates the
two rbm.systems containers.
Keep the latest Docker tags on the stable release when a GitHub release is marked as a pre-release, so auto-updating deployments on :latest do not pick up beta builds.
Adds a GitHub Action that builds the Next.js standalone output on
release and attaches architecture-specific tarballs (amd64 + arm64)
as release assets. Downstream projects can download and extract
instead of building from source.
Closes#178
Add GitHub Actions workflow to automatically build and publish Docker
images to ghcr.io on releases. Supports amd64 and arm64 architectures.
Pre-built image available at ghcr.io/root-fr/jmap-webmail.