Skip to content

ci(release): publish Tauri updater latest.json on tag releases - #1372

Merged
ErikBjare merged 3 commits into
ActivityWatch:masterfrom
0xbrayo:feat/tauri-updater-latest-json
Aug 23, 2026
Merged

ci(release): publish Tauri updater latest.json on tag releases#1372
ErikBjare merged 3 commits into
ActivityWatch:masterfrom
0xbrayo:feat/tauri-updater-latest-json

Conversation

@0xbrayo

@0xbrayo 0xbrayo commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

Adds support for publishing a Tauri updater manifest (and signed updater artifacts) on tag releases from the unified release.yml workflow.

  • Pass TAURI_SIGNING_PRIVATE_KEY into the Tauri build so bundles emit .sig files when createUpdaterArtifacts is enabled in aw-tauri
  • Collect per-platform updater artifacts as activitywatch-tauri[-research]-<version>-<platform>.<ext>[.sig]
  • Generate an edition-specific manifest via scripts/package/generate_latest_json.py during the draft release job
  • Upload the manifest and updater assets alongside existing release files

Standard and Research Edition are partitioned by filename so they cannot share an updater endpoint:

Edition Manifest Updater artifacts Endpoint
Standard latest.json activitywatch-tauri-<ver>-<platform>.* https://github.com/ActivityWatch/activitywatch/releases/latest/download/latest.json
Research latest-research.json activitywatch-tauri-research-<ver>-<platform>.* https://github.com/ActivityWatch/activitywatch/releases/download/<research-tag>/latest-research.json

GitHub /releases/latest is the latest non-prerelease (standard). Research binaries must not use that URL.

Prerequisites (org secrets / aw-tauri)

This CI path is ready, but end-to-end updates also need:

  1. Repo secrets (ActivityWatch org): TAURI_SIGNING_PRIVATE_KEY (and optional TAURI_SIGNING_PRIVATE_KEY_PASSWORD)
  2. aw-tauri config with createUpdaterArtifacts: true, the matching public key, and the edition-specific endpoint above

Without signing / updater artifacts, the generate step fails intentionally (no empty manifest).

Test plan

  • Confirm workflow YAML parses (CI on this PR)
  • On a test tag (or dry-run of the generate script with staged .sig files), verify platform keys and asset URLs for both latest.json and latest-research.json
  • After secrets + aw-tauri updater config land, cut a prerelease tag and confirm the manifest + .sig assets appear on the draft release

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Tauri updater publishing to tagged releases.

  • Passes updater-signing secrets into Tauri builds and collects signed bundles under edition-specific filenames.
  • Generates standard or Research Edition updater manifests from the collected signatures.
  • Uploads updater manifests and signed artifacts with the draft GitHub release.
  • Pins checkout in the contents-write release job and safely reads tag/repository values from environment variables.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the prior shell-injection path now uses quoted environment variables, and the checkout in the contents-write release job is pinned to an immutable commit.

Important Files Changed

Filename Overview
.github/workflows/release.yml Adds signing-secret propagation, updater artifact collection, manifest generation, release uploads, a commit-pinned checkout, and safe environment-based handling of tag and repository values.
scripts/package/generate_latest_json.py Adds edition-aware discovery of signed updater bundles and generation of Tauri updater manifests with deterministic Windows bundle preference.
scripts/tests/test_generate_latest_json.py Covers version and edition handling, edition isolation, updater URLs, Windows bundle preference, manifest output, and empty-artifact rejection.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Tag["v* tag push"] --> Build["Cross-platform Tauri builds"]
  Secrets["Tauri signing secrets"] --> Build
  Build --> Signed["Signed updater bundles and .sig files"]
  Signed --> Collect["Normalize edition, platform, and architecture filenames"]
  Collect --> Download["Release job downloads build artifacts"]
  Download --> Manifest["Generate latest.json or latest-research.json"]
  Manifest --> Draft["Upload manifest and updater assets to draft release"]
  Download --> Draft
Loading

Reviews (2): Last reviewed commit: "fix(release): match known updater bundle..." | Re-trigger Greptile

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
@0xbrayo
0xbrayo marked this pull request as draft July 25, 2026 14:29
@0xbrayo

0xbrayo commented Jul 25, 2026

Copy link
Copy Markdown
Member Author

This is a prerequisite for ActivityWatch/aw-tauri#68.

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob Has this been satisfactorily resolved? I don't see one in https://github.com/ActivityWatch/activitywatch/releases/tag/v0.14.0b4, should be careful to support "editions" like the "Research Edition" (not conflating release lines).

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@TimeToBuildBob

TimeToBuildBob commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

@ErikBjare Not resolved — and the reason you don't see one in v0.14.0b4 is that this PR is still open, unmerged (authored by @0xbrayo). So no latest.json in that release is expected; it hasn't shipped yet.

The editions concern is legitimate and not yet addressed by the current implementation. generate_latest_json.py produces a single manifest per tag: it reads a --version + --tag, scans dist/ for activitywatch-tauri-<version>-<platform>.* artifacts, and writes one latest.json. There is no notion of a release line / edition in the schema — if the Research Edition ends up publishing from the same updater endpoint with a different tag or a different binary set, the manifests will collide. That needs a deliberate decision on how updater endpoints are partitioned per edition before merge.

My recommendation: keep the PR open pending (1) resolving Greptile's security feedback (tag-controlled shell interpolation in a contents-write job + unpinned checkout action), and (2) deciding the edition/endpoint partition. I'll defer to @0xbrayo on the release-line design since it's their PR.

@0xbrayo

0xbrayo commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

🤖 Claude, on behalf of @0xbrayo

@TimeToBuildBob @ErikBjare Addressed both items.

1. Greptile security

  • Tag/repo are passed via GITHUB_REF_NAME / GITHUB_REPOSITORY instead of being interpolated into the shell script.
  • The contents-write release job pins actions/checkout to the v7.0.1 SHA.

2. Edition / endpoint partition

Decision: partition by distinct GitHub asset names (and matching updater artifact prefixes). A given tag's release only carries that edition's manifest, so the two lines cannot overwrite each other or share an updater endpoint.

Edition Tag Manifest Updater artifacts Endpoint for that edition's binary
Standard vX.Y.Z latest.json activitywatch-tauri-<ver>-<platform>.* https://github.com/ActivityWatch/activitywatch/releases/latest/download/latest.json
Research vX.Y.Z-research latest-research.json activitywatch-tauri-research-<ver>-<platform>.* not /releases/latest (that is the latest non-prerelease, i.e. standard)

GitHub's /releases/latest is the latest non-prerelease, so it will never point at a -research tag. Research binaries must not fetch latest.json from that URL. For a published research release the manifest URL is:

https://github.com/ActivityWatch/activitywatch/releases/download/<research-tag>/latest-research.json

That's per-tag. If we later want a stable rolling URL for the research line (so v0.14.0b4-research clients pick up v0.14.0b5-research), we can add a floating research-latest tag that only holds latest-research.json — I didn't add that here because it would be a fake release. Happy to follow up if that's the preferred endpoint.

generate_latest_json.py also refuses to mix the two artifact prefixes, so a research generate step cannot pick up standard .sig files (and vice versa).

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

@0xbrayo's fixes look correct:

  • Shell injection: GITHUB_REF_NAME is an Actions-provided env var read via "$GITHUB_REF_NAME" (quoted expansion), not a ${{ github.ref_name }} template interpolated directly into shell source. The tag value only reaches Python as a quoted positional arg (--tag "$TAG"), so a crafted tag name cannot inject shell commands.
  • Checkout pin: SHA 3d3c42e5aac5ba805825da76410c181273ba90b1 (actions/checkout v7.0.1) in the contents-write job. ✓
  • Edition partition: Research tags (*-research) → latest-research.json + activitywatch-tauri-research-* artifacts; standard tags → latest.json + activitywatch-tauri-*. Generator refuses to match cross-edition artifact prefixes. The two lines cannot overwrite each other's release assets or share an updater endpoint.

What still needs to land externally (outside this PR):

  1. TAURI_SIGNING_PRIVATE_KEY org secret in the ActivityWatch org
  2. aw-tauri config: createUpdaterArtifacts: true, matching public key, edition-specific endpoint per edition

Without those the generate step intentionally fails (no .sig files → no manifest). Once they're in place, a prerelease tag is all that's left of the test plan.

@ErikBjare — from the CI/security side this is ready when those prerequisites are set up.

Sign aw-tauri bundles with TAURI_SIGNING_PRIVATE_KEY, collect per-platform
updater artifacts, and generate latest.json for the Tauri updater endpoint
when publishing draft releases.

URLs use github.repository so they resolve to ActivityWatch/activitywatch
on upstream runs.
…nterpolation

Pass tag/repo through Actions env vars instead of interpolating them into
shell source, pin checkout in the contents-write release job, and emit
latest.json vs latest-research.json with matching artifact prefixes so
standard and Research Edition lines cannot share an updater endpoint.
@0xbrayo
0xbrayo force-pushed the feat/tauri-updater-latest-json branch from 95e6c19 to 112659c Compare August 22, 2026 19:20
@0xbrayo

0xbrayo commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

Identical to before, dropped a merged commit for linear history.

@ErikBjare

Copy link
Copy Markdown
Member

Did an independent pass over the updated branch:

Verified:

  • Checkout pin: 3d3c42e5aac5ba805825da76410c181273ba90b1 really is actions/checkout v7.0.1 (checked against the tag ref in actions/checkout, commit "prep v7.0.1 release (#2531)"). ✓
  • Shell injection: the generate step reads GITHUB_REF_NAME/GITHUB_REPOSITORY (Actions-provided env vars) with quoted expansions, no ${{ }} interpolation into shell source. ✓
  • generate_latest_json.py tests pass locally (6/6), and the edition partition by asset name + refusal to mix prefixes looks right. ✓

One bug to fix before merge, in the "Package Tauri updater artifacts" step:

ext="${base#*.}" # everything after the first dot, e.g. "app.tar.gz"

This only works for macOS (aw-tauri.app.tar.gzapp.tar.gz). The AppImage/NSIS/MSI bundle names embed the aw-tauri version, e.g. aw-tauri_0.1.0_amd64.AppImage, so first-dot stripping yields 1.0_amd64.AppImage and the release asset becomes activitywatch-tauri-0.14.0-linux-x86_64.1.0_amd64.AppImage. It's self-consistent downstream (the manifest regex still extracts the platform key and the URL matches the actual asset name), so the updater would function, but the user-facing asset names are wrong and carry a bogus version fragment. Suggest matching known suffixes explicitly:

case "$base" in
  *.app.tar.gz)      ext="app.tar.gz" ;;
  *.AppImage.tar.gz) ext="AppImage.tar.gz" ;;
  *.AppImage)        ext="AppImage" ;;
  *.nsis.zip)        ext="nsis.zip" ;;
  *.msi.zip)         ext="msi.zip" ;;
  *.msi)             ext="msi" ;;
  *.exe)             ext="exe" ;;
  *) echo "unknown updater bundle: $base" >&2; exit 1 ;;
esac

Minor: on Windows, both NSIS and MSI .sig files can exist. Both map to platform key windows-x86_64, so whichever os.walk visits last wins in the manifest. Worth deliberately preferring one (NSIS is the Tauri v2 recommendation) rather than leaving it to walk order.

Also noting for the aw-tauri side (not this PR): tauri.conf.json currently has an empty pubkey, no createUpdaterArtifacts, and hardcodes the standard latest.json endpoint, so the research edition will need its own endpoint config there before research auto-updates can work.

Happy to merge once the ext extraction is fixed.

@ErikBjare

Copy link
Copy Markdown
Member

Prerequisite update: TAURI_SIGNING_PRIVATE_KEY + TAURI_SIGNING_PRIVATE_KEY_PASSWORD are now set as Actions secrets on this repo (fresh keypair, tauri-cli 2.11.0, key material backed up offline). The matching pubkey + conditional createUpdaterArtifacts are up as ActivityWatch/aw-tauri#240 — done there so fork PRs and local builds (no secrets) keep building plain bundles instead of failing on the missing key.

…t strip

AppImage/NSIS/MSI names embed the crate version, so stripping after the
first dot produced assets like linux-x86_64.1.0_amd64.AppImage. Match
Tauri updater suffixes explicitly, and prefer NSIS over MSI when both
signatures exist for the same Windows platform key.
@0xbrayo

0xbrayo commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

🤖 Claude, on behalf of @0xbrayo

@ErikBjare Fixed the ext extraction — the packaging step now matches known Tauri updater suffixes (app.tar.gz, AppImage.tar.gz, nsis.zip, msi.zip, …) and fails on anything else, so AppImage/NSIS/MSI names that embed the crate version no longer leak a bogus version fragment into the asset name.

Also took the Windows note: generate_latest_json.py now prefers NSIS over MSI for the same platform key regardless of os.walk order (Tauri v2 recommendation). Both artifacts still get uploaded; only the manifest entry is deterministic.

The aw-tauri pubkey / createUpdaterArtifacts / research endpoint work stays on ActivityWatch/aw-tauri#240 as you said.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Verified the two fixes from the latest push:

  • ext extraction: case statement now matches known Tauri updater suffixes explicitly (app.tar.gz, AppImage.tar.gz, AppImage, nsis.zip, msi.zip, msi, exe) and fails on anything else. No more version fragments leaking into asset names. ✓
  • Windows NSIS preference: generate_latest_json.py introduces WINDOWS_BUNDLE_RANK = {nsis.zip: 2, exe: 1, msi.zip: 0, msi: 0} and selects the highest-ranked entry per platform key, so NSIS wins over MSI regardless of os.walk order (Tauri v2 recommendation honoured). ✓

CI build jobs are still running (pending). Both confirmed addressable concerns from Erik's 19:51 review are resolved. Ready to merge once CI clears.

@ErikBjare

Copy link
Copy Markdown
Member

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

CI is now fully green (all checks pass). Greptile re-review from this morning also came back 5/5 — no new findings. Both concerns from Erik's 22 Aug review (ext extraction, Windows NSIS preference) are verified fixed.

PR is mergeable. Waiting on maintainer merge.

@ErikBjare
ErikBjare merged commit a17fa8a into ActivityWatch:master Aug 23, 2026
18 checks passed
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.

3 participants