Production Release Checklist
Everything that must be done before tagging v0.7.0 and shipping to users. Items are ordered by dependency — complete them top to bottom.
1. Generate Tauri Signing Keypair
Status: BLOCKING — without this, auto-updater is dead. No user will ever receive an update.
The Tauri updater requires an Ed25519 keypair. The private key signs every release bundle, and the public key is embedded in the app binary so it can verify updates.
# Install the Tauri CLI (if not already installed)
cargo install tauri-cli --locked
# Generate the keypair
cargo tauri signer generate -w ~/.tauri/librefang.key
The command will output:
Your public key was generated successfully:
dW50cnVzdGVkIGNvb... <-- COPY THIS
Your private key was saved to: ~/.tauri/librefang.key
Save both values. You need them for steps 2 and 3.
2. Set the Public Key in tauri.conf.json
Status: BLOCKING — the placeholder must be replaced before building.
Open crates/librefang-desktop/tauri.conf.json and replace:
"pubkey": "PLACEHOLDER_REPLACE_WITH_GENERATED_PUBKEY"
with the actual public key string from step 1:
"pubkey": "dW50cnVzdGVkIGNvb..."
3. Add GitHub Repository Secrets
Status: BLOCKING — CI/CD release workflow will fail without these.
Go to GitHub repo → Settings → Secrets and variables → Actions → New repository secret and add:
| Secret Name | Value | Required |
|---|---|---|
TAURI_SIGNING_PRIVATE_KEY | Contents of ~/.tauri/librefang.key | Yes |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD | Password you set during keygen (or empty string) | Yes |
Optional — macOS Code Signing
Without these, macOS users will see "app from unidentified developer" warnings. Requires an Apple Developer account ($99/year).
| Secret Name | Value |
|---|---|
APPLE_CERTIFICATE | Base64-encoded .p12 certificate file |
APPLE_CERTIFICATE_PASSWORD | Password for the .p12 file |
APPLE_SIGNING_IDENTITY | e.g. Developer ID Application: Your Name (TEAMID) |
APPLE_ID | Your Apple ID email |
APPLE_PASSWORD | App-specific password from appleid.apple.com |
APPLE_TEAM_ID | Your 10-character Team ID |
To generate the base64 certificate:
base64 -i Certificates.p12 | pbcopy
Optional — Windows Code Signing
Without this, Windows SmartScreen may warn users. Requires an EV code signing certificate.
Set certificateThumbprint in tauri.conf.json under bundle.windows and add the certificate to the Windows runner in CI.
4. Create Icon Assets
Status: VERIFY — icons may be placeholders.
The following icon files must exist in crates/librefang-desktop/icons/:
| File | Size | Usage |
|---|---|---|
icon.png | 1024x1024 | Source icon, macOS .icns generation |
icon.ico | multi-size | Windows taskbar, installer |
32x32.png | 32x32 | System tray, small contexts |
128x128.png | 128x128 | Application lists |
128x128@2x.png | 256x256 | HiDPI/Retina displays |
Verify they are real branded icons (not Tauri defaults). Generate from a single source SVG:
# Using ImageMagick
convert icon.svg -resize 1024x1024 icon.png
convert icon.svg -resize 32x32 32x32.png
convert icon.svg -resize 128x128 128x128.png
convert icon.svg -resize 256x256 128x128@2x.png
convert icon.svg -resize 256x256 -define icon:auto-resize=256,128,64,48,32,16 icon.ico
5. Set Up the librefang.ai Install Domain
Status: VERIFY — users should be able to run curl -fsSL https://librefang.ai/install.sh | sh.
Options:
- GitHub Pages: Point
librefang.aito a GitHub Pages site that redirects/install.shtoscripts/install.shand/install.ps1toscripts/install.ps1from the repo's latest release. - Cloudflare Workers / Vercel: Serve the install scripts with proper
Content-Type: text/plainheaders. - Raw GitHub redirect: Use
librefang.aito route to the repo-hosted installer artifacts if needed (less reliable).
The install scripts reference:
https://librefang.ai/install.sh→ servesscripts/install.shhttps://librefang.ai/install.ps1→ servesscripts/install.ps1
Once the domain is set up, users can install via:
curl -fsSL https://librefang.ai/install.sh | sh
6. Verify Dockerfile Builds
Status: VERIFY — the Dockerfile must produce a working image.
docker build -t librefang:local .
docker run --rm librefang:local --version
docker run --rm -p 4545:4545 -v librefang-data:/data librefang:local start
Confirm:
- Binary runs and prints version
startcommand boots the kernel and API server- Port 4545 is accessible
/datavolume persists between container restarts
7. Verify Install Scripts Locally
Status: VERIFY before release.
Linux/macOS
# Test against a real GitHub release (after first tag)
bash scripts/install.sh
# Or test syntax only
bash -n scripts/install.sh
shellcheck scripts/install.sh
Windows (PowerShell)
# Test against a real GitHub release (after first tag)
powershell -ExecutionPolicy Bypass -File scripts/install.ps1
# Or syntax check only
pwsh -NoProfile -Command "Get-Content scripts/install.ps1 | Out-Null"
Docker smoke test
docker build -f scripts/docker/install-smoke.Dockerfile .
8. Write CHANGELOG.md for v0.7.0
Status: VERIFY — confirm it covers all shipped features.
The release workflow includes a link to CHANGELOG.md in every GitHub release body. Ensure it exists at the repo root and covers:
- All 15 crates and what they do
- Key features: 44 channels, 60 tools, 49 drivers, 130+ models
- Security systems (9 SOTA + 7 critical fixes)
- Desktop app with auto-updater
- Migration path from OpenClaw
- Docker and CLI install options
9. First Release — Tag and Push
Once steps 1-8 are complete:
# Ensure version matches everywhere
grep '"version"' crates/librefang-desktop/tauri.conf.json
grep '^version' Cargo.toml
# Commit any final changes
git add -A
git commit -m "chore: prepare v0.7.0 release"
# Tag and push
git tag v0.7.0
git push origin main --tags
This triggers the release workflow which:
- Builds desktop installers for 4 targets (Linux, macOS x86, macOS ARM, Windows)
- Generates signed
latest.jsonfor the auto-updater - Builds CLI binaries for 5 targets
- Builds and pushes multi-arch Docker image
- Creates a GitHub Release with all artifacts
10. Post-Release Verification
After the release workflow completes (~15-30 min):
GitHub Release Page
-
.msiand.exepresent (Windows desktop) -
.dmgpresent (macOS desktop) -
.AppImageand.debpresent (Linux desktop) -
latest.jsonpresent (auto-updater manifest) - CLI
.tar.gzarchives present (5 targets) - CLI
.zippresent (Windows) - SHA256 checksum files present for each CLI archive
Auto-Updater Manifest
Visit: https://github.com/librefang/librefang/releases/latest/download/latest.json
- JSON is valid
- Contains
signaturefields (not empty strings) - Contains download URLs for all platforms
- Version matches the tag
Docker Image
docker pull ghcr.io/librefang/librefang:latest
docker pull ghcr.io/librefang/librefang:0.7.0
# Verify both architectures
docker run --rm ghcr.io/librefang/librefang:latest --version
Container Image Vulnerability Scan
Every runtime image is scanned with Trivy before its digest is promoted to a shared tag.
Scope. Two workflows run the same scan through the shared composite action at .github/actions/trivy-image-scan:
| Workflow | What is scanned | When |
|---|---|---|
docker-build.yml | The linux/amd64 runtime image, after the /api/health boot probe | Push to main that touches the Dockerfile or its inputs |
release.yml (docker-scan) | Each native release digest — linux/amd64 on ubuntu-24.04, linux/arm64 on ubuntu-24.04-arm | Every release tag, between the per-arch push and the manifest publish |
Each scan covers OS packages (Debian packages from the node:*-bookworm-slim base) and language packages (the bundled Node.js and Python dependencies) — --pkg-types os,library — with secret scanning left on for the final image.
Neither --severity nor --ignore-unfixed is passed, so no filter is applied at all and the report contains every finding at every severity, fixed and unfixed alike.
Enforcement threshold. The gate ships report-only and is not currently enforced.
The runtime image carries a large pre-existing backlog — issue #6694 measured 10 critical and 95 high findings on 2026-08-01 — so an armed gate would fail main and every release on its first run.
That measurement used Trivy 0.57.0 rather than the 0.72.0 this pipeline pins, and has not been repeated, so read it as the order of magnitude that justifies report-only rather than as the count the first run will report.
Arming it is a two-line change, and both lines matter.
The threshold itself lives in one place — the fail-on input of .github/actions/trivy-image-scan/action.yml, which neither workflow overrides:
off → critical → high
The second line is the continue-on-error: true on the scan step of the docker-scan job in .github/workflows/release.yml.
While the gate is report-only that flag is what makes report-only true on the release path: a scanner outage — a cold arm64 database download, a GHCR hiccup, a Trivy asset 404 — fails the step, and without it a failed docker-scan would skip docker-manifest and take the whole tag promotion with it.
None of those failures says anything about image security, so today the scan cannot block a release for any reason, and that is the intended behaviour of a gate that is not enforced.
The step is still marked failed in the run, so an outage stays visible rather than passing as a clean scan.
Delete that flag at the same time as you move fail-on, or you will arm the threshold while leaving the scan unable to block anything.
The main scan in docker-build.yml deliberately has no such flag: nothing is gated on it, so a scanner failure there turns the workflow red as a signal and costs nothing.
Once armed, a job fails when the image has any fixable vulnerability at or above the threshold, or any embedded secret at all.
Restricting the decision to fixable findings is not --ignore-unfixed: unfixed findings stay in the report, the artifacts, and the job summary, and only the pass/fail decision is narrowed, because an unfixed CVE gives a maintainer nothing to act on.
What the gate actually blocks. It is a tag-promotion gate, not a publish gate.
The release pipeline builds each architecture with push-by-digest, so both per-arch digests are already in GHCR — untagged — before docker-scan runs; that is how the two native build jobs hand their results to the manifest step.
A failed scan blocks docker-manifest, and therefore publish_arch_repo, deploy_fly, deploy_render, and sync_aur_docker.
The consequence is that no user-facing tag (:VERSION, :latest, :lts) is ever created or moved onto a digest that failed the gate, and no downstream deployment consumes it.
An untagged digest left behind in GHCR is not pullable by name.
Where the reports are retained. Three places, with artifacts requesting 30 days — the effective figure is whatever the repository's artifact retention limit allows, since upload-artifact silently clamps a longer request:
- Workflow artifacts named
trivy-main-linux-amd64(main) andtrivy-release-linux-amd64/trivy-release-linux-arm64(releases), each holding the raw Trivy JSON, the SARIF encoding of the same scan, and a-summary.jsonrecording the platform digest, scanner version, database timestamp, threshold, and verdict. - The job summary on the run, which names the platform digest, the Trivy and vulnerability-database versions, per-severity totals with fixable counts, and a table of every CRITICAL / HIGH finding with its package, installed version, fixed version, CVE id, and severity.
- GitHub code scanning, under the categories
trivy-main-linux-amd64,trivy-release-linux-amd64, andtrivy-release-linux-arm64— one category per scanned image, because code scanning replaces the previous results for a category and a shared one would discard an architecture.
Scanner failures are not clean scans. The vulnerability database is refreshed in a step of its own, retried three times, and the scan itself then runs with --skip-db-update.
A database that never downloaded therefore ends the job with its own distinct Scanner infrastructure failure error instead of producing a finding-free report that looks like a clean image.
The same discrimination applies to a missing, empty, or truncated report, and to a report identifying no operating system — the runtime image is Debian-based, so that combination means the scanner failed rather than that the image is clean.
The decision function is pinned by a fixture corpus in scripts/check-image-vulns.py --self-test, run by the Image Vulnerability Gate Self-Test job in ci.yml on every PR.
Desktop App Auto-Update (test with v0.7.1)
- Install v0.7.0 from the release
- Tag v0.7.1 and push
- Wait for release workflow to complete
- Open the v0.7.0 app — after 10 seconds it should:
- Show "LibreFang Updating..." notification
- Download and install v0.7.1
- Restart automatically to v0.7.1
- Right-click tray → "Check for Updates" → should show "Up to Date"
Install Scripts
# Linux/macOS
curl -fsSL https://librefang.ai/install.sh | sh
librefang --version # Should print v0.7.0
# Windows PowerShell
irm https://librefang.ai/install.ps1 | iex
librefang --version
Quick Reference — What Blocks What
Step 1 (keygen) ──┬──> Step 2 (pubkey in config)
└──> Step 3 (secrets in GitHub)
│
Step 4 (icons) ──────────┤
Step 5 (domain) ─────────┤
Step 6 (Dockerfile) ─────┤
Step 7 (install scripts) ┤
Step 8 (CHANGELOG) ──────┘
│
v
Step 9 (tag + push)
│
v
Step 10 (verify)
Steps 4-8 can be done in parallel. Steps 1-3 are sequential and must be done first.