Skip to content

Release pipeline, packaging and distribution docs - #1

Merged
zknpr merged 3 commits into
mainfrom
release/pipeline
Oct 9, 2026
Merged

zknpr merged 3 commits into
mainfrom
release/pipeline

Conversation

@zknpr

@zknpr zknpr commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Problem

There was no repeatable way to produce release packages. The private beta's installers, notice bundle and MPL source archive were assembled by hand on separate machines. The readiness doc therefore left several binary-distribution gates open:

  • no CI build;
  • notices that existed only as separate downloads;
  • an AppImage with uninventoried bundled libraries;
  • unsigned or ad-hoc packages with no stated policy;
  • no install or uninstall guidance.

Solution

A release workflow that builds each package on its own OS from the committed viewer pin and verifies it. Pushing a v* tag also drafts a GitHub release carrying everything the licenses and users need. Publishing stays manual.

Signing decisions for this release:

  • macOS: ad-hoc signature with the hardened runtime. No Developer ID or notarization, by choice.
  • Windows: unsigned. The SignPath Foundation only signs projects that have already released, so the first release must ship unsigned before applying.
  • Linux: .deb only; no AppImage.

Architecture

.github/workflows/release.yml runs a three-way matrix: macos-latest (DMG), ubuntu-24.04 (.deb) and windows-latest (NSIS). Each build job:

  1. checks that the three version files agree, and match the tag on a tag build;
  2. fetches the viewer pin from the public extension repository by SHA, syncs this platform's viewer and native runtime, and verifies them against the manifest. On macOS the fresh sync must reproduce the committed viewer-dist/ exactly;
  3. runs npm test and cargo test --lib;
  4. builds, then checks the package:
    • macOS: strict codesign verification, notices and tjs inside the app, hdiutil verify;
    • Linux: notices and tjs in the .deb, and a declared libc6 floor that every packaged ELF must satisfy;
    • Windows: notices and tjs.exe inside the installer;
  5. stages the package under the name scripts/release/assemble.mjs assigns it.

The release job runs only for a tag. It downloads the three packages and adds THIRD_PARTY_NOTICES.txt and the MPL sources. assemble refuses a missing or unexpected asset, then writes release-manifest.json, SHA256SUMS and the notes. Every asset gets a build-provenance attestation, and the job creates a draft release.

Per-file changes

  • .github/workflows/release.yml (new): the pipeline above. Pull requests that touch the pipeline run the build jobs only.
  • scripts/release/version.mjs (new): enforces version agreement across package.json, Cargo.toml and tauri.conf.json, plus the tag.
  • scripts/release/mpl-sources.mjs (new): collects every MPL-obligating crate in the lock graph. MIT OR MPL-2.0 doesn't count; shapes it can't parse are included anyway. Each .crate is downloaded and its SHA256 checked against Cargo.lock.
  • scripts/release/assemble.mjs (new): owns the asset names and writes the manifest, checksums and notes.
  • scripts/release/release.test.mjs (new): 9 tests, now part of npm test.
  • src-tauri/tauri.conf.json: bundles LICENSE.md, THIRD_PARTY_NOTICES.txt and third-party/ into every package.
  • src-tauri/tauri.linux.conf.json: builds .deb only and declares libc6 (>= 2.39): the native tjs engine needs GLIBC_2.38 and the app built on 24.04 needs 2.39.
  • src-tauri/tauri.macos.conf.json (new): signingIdentity: "-", so Tauri ad-hoc signs the app, hardened runtime included.
  • .gitattributes (new): LF checkouts on every platform.
  • src-tauri/src/lib.rs: the useHttpsScheme lockstep test also reads the new macOS config.
  • README.md: a new Install section: verification, the macOS Open Anyway step, SmartScreen and Smart App Control on Windows, the Linux glibc floor, and uninstall steps for each platform. The build section drops the manual codesign step.
  • docs/code-signing-policy.md (new): the SignPath-required page. It covers signing status, what signing will and won't cover, roles, and the privacy statement, including the WebView2 bootstrapper download.
  • docs/release-readiness.md, third-party/README.md: updated gates and the release procedure.

Security

  • No cache anywhere in the release workflow, so a poisoned cache cannot reach a release.
  • Actions pinned by SHA to their current Node-24 majors.
  • No checkout credentials: persist-credentials: false everywhere.
  • No interpolation in shell code: every ${{ }} reaches the shell through env:.
  • Least privilege: the workflow defaults to contents: read. Only the tag-only release job gets contents: write, id-token: write and attestations: write.
  • Fork safety: pull requests use pull_request, never pull_request_target, and the build jobs use no secrets.
  • Tamper evidence: MPL sources are refused if they differ from Cargo.lock. The assembler refuses a polluted dist directory, so nothing unexpected gets attached.
  • Hardened runtime: this is new on macOS; the beta's manual ad-hoc signature lacked it. A QA build signed this way opened a database on the native engine with one sidecar and no console errors, rejections or CSP violations.

Test plan

  • npm test: 23/23, including the 9 new release-script tests: version drift, MPL selection, tampered crate, polluted dist, CRLF lock parsing.
  • scripts/release/mpl-sources.mjs against crates.io: the five MPL crates (cssparser, cssparser-macros, dtoa-short, option-ext, selectors) downloaded and matched Cargo.lock.
  • cargo test --lib: 136 passed. Clippy clean.
  • Local macOS release build with the new config:
    • the .app passes codesign --verify --deep --strict (flags=adhoc,runtime);
    • the notices are inside the app;
    • the DMG passes hdiutil verify, and the app inside it passes strict verification.
  • Hardened-runtime QA bundle against a real database: native engine, one sidecar, no errors.
  • glibc-floor comparison exercised on the real Linux tjs (GLIBC_2.38), including a fail case.
  • Release workflow on all three runners: macOS, Linux and Windows all pass. The first run caught three defects, fixed in d495101:
    • the Windows checkout converted lib.rs to CRLF and broke two source-scanning tests; .gitattributes now forces LF;
    • the app built on Ubuntu 24.04 needs GLIBC_2.39, above the declared 2.38; the floor is now 2.39;
    • the .deb is named sq-lite-explorer, so the README's uninstall command was wrong.
  • CI packages inspected locally:
    • DMG: passes hdiutil verify; the app strict-verifies with adhoc,runtime and has notices and tjs inside.
    • .deb: control file, libc6 (>= 2.39) covering the GLIBC_2.39 its binaries need, description, notices and tjs.
    • NSIS: notices, tjs.exe and query-plan.dll; the app exe reports ProductName and FileVersion.
  • Release-job logic run locally on those three CI packages:
    • MPL sources verified;
    • assemble accepted exactly the build jobs' asset names;
    • shasum -c SHA256SUMS passes for all six files;
    • the manifest and notes are correct.
  • The tag-only release job's own steps (attestation, gh release create --draft): not exercisable before a tag is pushed.
  • Installed-package QA on the drafted packages, downloaded-package Gatekeeper and SmartScreen, and Smart App Control behaviour: candidate checks after the first draft.

zknpr added 2 commits October 8, 2026 23:21
- release.yml: builds the macOS DMG, Linux .deb and Windows NSIS installer
  on their own runners from the viewer pin, with no cache. On macOS a fresh
  sync must reproduce the committed viewer-dist. Each job runs the tests and
  checks its package: strict codesign, a glibc floor covering every ELF in
  the .deb, and notices plus the native engine inside the installer. A v*
  tag also drafts the release with notices, MPL sources, SHA256SUMS and
  build-provenance attestations. Publishing stays manual.
- scripts/release: version agreement across the three version files and
  the tag, MPL-2.0 crate collection verified against Cargo.lock, and an
  assembler that owns asset names and refuses a missing or unexpected asset.
- Packaging: THIRD_PARTY_NOTICES.txt, LICENSE.md and third-party/ ship
  inside every package. Linux builds only .deb and declares libc6 >= 2.38,
  which the native engine needs. tauri.macos.conf.json signs ad-hoc with the
  hardened runtime, so local and CI bundles pass strict verification without
  a manual codesign step.
- README: an Install section with checksum and attestation verification,
  the macOS Open Anyway step, SmartScreen and Smart App Control on Windows,
  the Linux glibc floor, and uninstall steps for each platform.
- docs/code-signing-policy.md: the signing status of each package, what
  SignPath signing will and will not cover (the upstream tjs.exe stays
  unsigned), roles, and the privacy statement.
- release-readiness: the pipeline, notices, AppImage and glibc gates, plus
  the release procedure.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T21:42:54.902775Z d495101 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: be5f950f-3387-406d-a0a5-11ca9105c19f
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a4a09c15c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md
[code signing policy](docs/code-signing-policy.md). Verify a download before
installing it:

```sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add native checksum commands for macOS and Windows

For users following the new macOS or Windows installation paths on a stock system, sha256sum is unavailable, so the prescribed checksum verification cannot be run as written. Provide platform-specific alternatives such as shasum -a 256 on macOS and Get-FileHash on Windows, or explicitly document the additional GNU tooling required.

Useful? React with 👍 / 👎.

REF_NAME: ${{ github.ref_name }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
cp THIRD_PARTY_NOTICES.txt dist/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Regenerate release-specific third-party notices

For this v0.2.0 release and every future version, copying the static notice ships text headed “0.2.0 private beta” that directs recipients to v0.2.0-beta.1 for the MPL source archive, even though the workflow attaches the corresponding archive to the current tag. Generate or validate the notice against REF_NAME before packaging it so installed copies identify the actual release and source location.

Useful? React with 👍 / 👎.

GH_TOKEN: ${{ github.token }}
REF_NAME: ${{ github.ref_name }}
run: |
gh release create "$REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --draft --verify-tag \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark prerelease tags as prereleases

When a supported tag such as v0.3.0-beta.1 triggers this workflow, the draft is created without --prerelease; publishing it according to the documented procedure therefore exposes the beta as an ordinary release and may let it become Latest. The gh release create manual documents --prerelease as the flag that marks this state, so add it conditionally when the validated version contains a prerelease component.

Useful? React with 👍 / 👎.


| Package | Signature |
| --- | --- |
| macOS `.dmg` | Ad-hoc signature with the hardened runtime. Not signed with an Apple Developer ID and not notarized. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Describe the DMG itself as unsigned

The policy currently reports the .dmg as ad-hoc signed, but with signingIdentity: "-" Tauri deliberately skips signing the disk image and only ad-hoc signs the contained .app; the workflow likewise runs codesign on the app and only hdiutil verify on the DMG. The Tauri DMG bundler explicitly skips self-signing DMGs for this identity, so describe the DMG as unsigned and the enclosed app as ad-hoc signed to avoid overstating the package’s signature status.

Useful? React with 👍 / 👎.

Found by the first run of the release workflow:

- Windows: two source-scanning shell tests failed because the runner's
  checkout converted lib.rs to CRLF, so "#[cfg(test)]\nmod tests" was never
  found. .gitattributes now checks out every text file as LF on every
  platform, which also keeps the sha256-manifested viewer-dist byte-exact.
  The index holds no CRLF, so no content changes.
- Linux: the glibc-floor check caught the app binary built on Ubuntu 24.04
  needing GLIBC_2.39, above the declared 2.38. The .deb now declares
  libc6 (>= 2.39); Ubuntu 24.04 and Debian 13 still qualify.
- The .deb is named sq-lite-explorer (Tauri derives it from the product
  name), so the README's uninstall command was wrong. Add a short and long
  description in place of the control file's "(none)".
@zknpr
zknpr merged commit d6af718 into main Oct 9, 2026
6 checks passed
@zknpr
zknpr deleted the release/pipeline branch October 9, 2026 07:09
zknpr added a commit that referenced this pull request Oct 9, 2026
- THIRD_PARTY_NOTICES.txt still read "0.2.0 private beta" and sent MPL
  source requests to releases/tag/v0.2.0-beta.1. That tag never existed in
  this repository, so every package would ship a broken source pointer. The
  header is now version-neutral and points at the release page, which
  carries mpl-dependency-sources.tar.gz.
- A prerelease tag drafted an ordinary release, which publishing could make
  Latest. The manifest now records prerelease, and the workflow adds
  --prerelease from it.
- The DMG was described as ad-hoc signed. Only the app inside is (Tauri
  skips the disk image for identity "-"; codesign reports the DMG "not
  signed at all"). The docs and the manifest now say so.
- sha256sum does not exist on Windows. The notes now give shasum for macOS,
  sha256sum for Linux and a PowerShell Get-FileHash check for Windows. That
  command comes from one exported function, and the Windows build job runs
  that exact string against its own installer: True for the real hash,
  False for a wrong one.

Regression tests for the notices, prerelease, DMG wording and checksum
commands failed before this change and pass after it.
zknpr added a commit that referenced this pull request Oct 9, 2026
* release: 0.3.0

Version bump for the first public release. Changes since the 0.2.0 private beta: the navigation pin and CSP base-uri/form-action/frame-ancestors, the hardened-runtime ad-hoc macOS signature, notices bundled in every package, a .deb-only Linux release with a glibc 2.39 floor, and the release workflow.

* fix(release): address the Codex review findings on #1 and #2

- THIRD_PARTY_NOTICES.txt still read "0.2.0 private beta" and sent MPL
  source requests to releases/tag/v0.2.0-beta.1. That tag never existed in
  this repository, so every package would ship a broken source pointer. The
  header is now version-neutral and points at the release page, which
  carries mpl-dependency-sources.tar.gz.
- A prerelease tag drafted an ordinary release, which publishing could make
  Latest. The manifest now records prerelease, and the workflow adds
  --prerelease from it.
- The DMG was described as ad-hoc signed. Only the app inside is (Tauri
  skips the disk image for identity "-"; codesign reports the DMG "not
  signed at all"). The docs and the manifest now say so.
- sha256sum does not exist on Windows. The notes now give shasum for macOS,
  sha256sum for Linux and a PowerShell Get-FileHash check for Windows. That
  command comes from one exported function, and the Windows build job runs
  that exact string against its own installer: True for the real hash,
  False for a wrong one.

Regression tests for the notices, prerelease, DMG wording and checksum
commands failed before this change and pass after it.

* ci(release): fail when the third-party notices drift from the release graph

THIRD_PARTY_NOTICES.txt and third-party/inventory.json were generated once
and nothing regenerated or checked them. A dependency bump would have
shipped incomplete notices in every package without anything failing.

scripts/release/notices.mjs checks that:
- the inventory's Cargo components are exactly the packages a release
  resolves. This uses `cargo metadata` with default features. The raw lock
  would wrongly demand the 26 qa-webdriver-only crates, which no release
  builds;
- the inventory was built for viewer-dist's pin;
- every inventoried component has a section in the notices.

It runs in CI, so a dependency-bump PR goes red, and in every release build.
Today it passes: the release resolves 442 packages and the inventory lists
exactly those 442. With one shipped crate removed from the inventory it
fails and names the crate.

ci.yml also moves to the Node-24 action SHAs release.yml already uses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant