Skip to content

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

Open
0xbrayo wants to merge 2 commits into
ActivityWatch:masterfrom
0xbrayo:feat/tauri-updater-latest-json
Open

ci(release): publish Tauri updater latest.json on tag releases#1372
0xbrayo wants to merge 2 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 generation and publication of signed Tauri updater metadata during tag releases.

  • Supplies Tauri updater-signing credentials to build jobs.
  • Collects signed updater bundles under normalized per-platform names.
  • Generates latest.json from collected signatures and publishes it with updater assets.

Confidence Score: 3/5

This PR should not merge until tag metadata is moved out of executable shell source; the checkout action should also be pinned for release-path hardening.

Any repository writer can push a matching tag, and the release job substitutes that tag directly into shell commands running in a contents-write workflow, making command execution reachable before draft release publication.

Files Needing Attention: .github/workflows/release.yml

Security Review

The changed release shell embeds a tag name directly into executable shell source, allowing a specially named v* tag pushed by a repository writer to run commands in a contents-write job. The newly added checkout also uses a mutable action tag in that privileged release path.

Important Files Changed

Filename Overview
.github/workflows/release.yml Adds updater signing, artifact collection, manifest generation, and release upload, but directly embeds tag-controlled text into a privileged shell step and adds a mutable checkout dependency.
scripts/package/generate_latest_json.py Builds a Tauri updater manifest from staged signature files and rejects empty manifests; no independently publishable defect was established in the configured release path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  T["Push v* tag"] --> B["Build Tauri matrix"]
  B --> S["Sign updater bundles"]
  S --> P["Normalize updater artifacts"]
  P --> D["Download build artifacts"]
  T --> M["Interpolate tag metadata"]
  M --> G["Generate latest.json"]
  D --> G
  G --> R["Create draft GitHub release"]
Loading

Reviews (1): Last reviewed commit: "ci(release): publish Tauri updater lates..." | 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.

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