diff --git a/.github/workflows/static-lite.yml b/.github/workflows/static-lite.yml index ca07462ed..5eba3ef9b 100644 --- a/.github/workflows/static-lite.yml +++ b/.github/workflows/static-lite.yml @@ -7,10 +7,10 @@ name: Build Static Lite # # Two targets are built side by side: # - static: bulwark-lite-.zip for any static web host; -# - stalwart: bulwark-lite-stalwart.zip, a Stalwart `Application` bundle. Its -# release asset name carries no version, so -# releases/latest/download/bulwark-lite-stalwart.zip is a stable -# resourceUrl that follows new releases. +# - stalwart: bulwark-lite-stalwart.zip, a Stalwart `Application` bundle. +# Its LITE-README points `resourceUrl` at the tagged release. +# +# Each zip is published with a `.sha256` next to it. # # scripts/lite/prepare.mjs deletes the server-only trees in place; that is # safe here because the Actions checkout is disposable (CI=true is the guard). @@ -121,12 +121,16 @@ jobs: path: zips merge-multiple: true + - name: Checksum zips + working-directory: zips + run: for zip in *.zip; do sha256sum "$zip" > "$zip.sha256"; done + - name: Upload release assets env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} TAG_NAME: ${{ github.event.release.tag_name }} - run: gh release upload "$TAG_NAME" zips/*.zip --clobber + run: gh release upload "$TAG_NAME" zips/*.zip zips/*.zip.sha256 --clobber smoke: # Boots the demo export on a plain static server and drives it with diff --git a/lib/__tests__/lite-scripts.test.ts b/lib/__tests__/lite-scripts.test.ts index 8f301e8f4..ba7cc3448 100644 --- a/lib/__tests__/lite-scripts.test.ts +++ b/lib/__tests__/lite-scripts.test.ts @@ -625,7 +625,10 @@ describe('Stalwart target: the entry document', () => { it('documents the install, reserved prefixes, updates and the redirect URI', () => { const readme = buildStalwartReadme({ version: '1.10.0', commit: 'abc1234', locales: ['en'], buildId: 'b' }); - expect(readme).toContain(`releases/latest/download/${STALWART_ZIP_NAME}`); + // A tagged release by default: Stalwart verifies nothing it downloads. + expect(readme).toContain(`resourceUrl='https://github.com/bulwarkmail/webmail/releases/download/v`); + expect(readme).not.toContain(`resourceUrl='https://github.com/bulwarkmail/webmail/releases/latest`); + expect(readme).toContain(`sha256sum -c ${STALWART_ZIP_NAME}.sha256`); expect(readme).toContain('stalwart-cli create Application'); // The CLI wants JSON (verified against stalwart-cli 1.0.12), and nothing is mounted before UpdateApps. expect(readme).toContain(`--field 'urlPrefix={"/webmail":true}'`); diff --git a/scripts/lite/lib.mjs b/scripts/lite/lib.mjs index 047a265b6..b5e70faed 100644 --- a/scripts/lite/lib.mjs +++ b/scripts/lite/lib.mjs @@ -776,7 +776,10 @@ export function parseLiteTarget(argv = [], env = {}) { return target; } -/** Release asset name; `releases/latest/download/` is the stable resourceUrl. */ +/** + * Release asset name. It carries no version, so `releases/latest/download/` + * exists too, but the README points `resourceUrl` at a tagged release. + */ export const STALWART_ZIP_NAME = "bulwark-lite-stalwart.zip"; /** The mount prefix the docs and the one-line install use. */ @@ -1359,7 +1362,8 @@ export function collectZipEntries(dir) { /** LITE-README.md for the Stalwart bundle. */ export function buildStalwartReadme({ version, commit, locales, buildId, demoMode = false }) { - const url = `https://github.com/bulwarkmail/webmail/releases/latest/download/${STALWART_ZIP_NAME}`; + const url = `https://github.com/bulwarkmail/webmail/releases/download/v${version}/${STALWART_ZIP_NAME}`; + const latestUrl = `https://github.com/bulwarkmail/webmail/releases/latest/download/${STALWART_ZIP_NAME}`; return `# Bulwark Lite ${version} (${commit}) for Stalwart Bulwark Webmail as a Stalwart \`Application\`: Stalwart downloads this zip, @@ -1425,11 +1429,18 @@ admin's own \`admin\` and \`account\`. ## Updates -Stalwart keeps the downloaded zip for \`autoUpdateFrequency\` (default 90 -days) and downloads \`resourceUrl\` again at the first restart or -\`UpdateApps\` after that, so the \`latest/download\` URL above follows new -releases on that schedule. \`UpdateApps\` always downloads again: run it to -update right away. A failed download keeps the previous bundle online. +The resource URL above names one release, so what Stalwart serves only +changes when you change it: to update, point \`resourceUrl\` at the new +release's tag and run \`UpdateApps\` (it always downloads again; a failed +download keeps the previous bundle online). Every release also carries +\`${STALWART_ZIP_NAME}.sha256\`; check a download with +\`sha256sum -c ${STALWART_ZIP_NAME}.sha256\` before you switch. + +The bundle runs as script on your Stalwart origin, next to the web admin. +Stalwart checks no hash or signature when it downloads, so a floating URL +such as \`${latestUrl}\` would run whatever the newest release holds, at the +next restart or \`UpdateApps\` after \`autoUpdateFrequency\` (default 90 +days). Use it only if you accept that in exchange for automatic updates. Open tabs move to the new build on their next navigation (one full page load); a freshly opened tab gets it immediately. Nothing stays stuck on old @@ -1437,9 +1448,6 @@ files although Stalwart sends a year-long cache header for every file but index.html: every file the app fetches without a content hash carries the build id. -To pin a version, point \`resourceUrl\` at a tagged release instead: -\`https://github.com/bulwarkmail/webmail/releases/download/v${version}/${STALWART_ZIP_NAME}\`. - ## Sign-in Passwords (and TOTP codes) go to Stalwart's \`/api/auth\` and are exchanged at