Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ jobs:
- uses: swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"
shared-key: ${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN }}-release
add-rust-environment-hash-key: false
key: ${{ hashFiles('src-tauri/Cargo.lock') }}
save-if: ${{ github.ref == 'refs/heads/main' }}

- uses: oven-sh/setup-bun@v2
with:
Expand Down Expand Up @@ -81,7 +85,7 @@ jobs:
echo "Prerelease $RELEASE_TAG will publish an unsigned Windows installer until Authenticode signing is configured."

- name: Verify updater signing key is configured
if: runner.os == 'Windows' && !contains(env.RELEASE_TAG, '-')
if: runner.os == 'Windows'
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
Expand All @@ -90,12 +94,6 @@ jobs:
exit 1
fi

- name: Allow unsigned prerelease updater artifacts
if: runner.os == 'Windows' && contains(env.RELEASE_TAG, '-')
run: |
echo "No Tauri updater signing key configured; publishing this technical-preview installer without updater signatures."
echo "USAGEBAR_ALLOW_UNSIGNED_UPDATER=1" >> "$GITHUB_ENV"

- name: Verify Windows Authenticode signing secrets are configured
if: runner.os == 'Windows'
env:
Expand Down Expand Up @@ -142,20 +140,41 @@ jobs:
releaseDraft: false
prerelease: ${{ contains(env.RELEASE_TAG, '-') }}
includeUpdaterJson: true
args: ${{ matrix.args }} ${{ contains(env.RELEASE_TAG, '-') && '--no-sign' || '' }}
args: ${{ matrix.args }}

- name: Verify updater assets uploaded
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ASSETS=$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | tr -d '\r')

if [[ "$USAGEBAR_ALLOW_UNSIGNED_UPDATER" != "1" ]]; then
printf '%s\n' "$ASSETS" | grep -Fxq 'latest.json' || { echo "Missing updater manifest: latest.json"; exit 1; }
printf '%s\n' "$ASSETS" | grep -Eq '\.sig$' || { echo "Missing updater signatures (.sig)"; exit 1; }
else
echo "Unsigned prerelease: updater manifest and signature assets are not required."
fi
printf '%s\n' "$ASSETS" | grep -Fxq 'latest.json' || { echo "Missing updater manifest: latest.json"; exit 1; }
printf '%s\n' "$ASSETS" | grep -Eq '\.sig$' || { echo "Missing updater signatures (.sig)"; exit 1; }
if [[ "$RUNNER_OS" == "Windows" ]]; then
printf '%s\n' "$ASSETS" | grep -Eq 'setup\.exe$' || { echo "Missing Windows setup executable"; exit 1; }
fi

- name: Publish updater channel manifest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANNEL_TAG="updater"
CHANNEL_DIR="$RUNNER_TEMP/usagebar-updater-channel"
mkdir -p "$CHANNEL_DIR"
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern latest.json --dir "$CHANNEL_DIR"

if ! gh release view "$CHANNEL_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$CHANNEL_TAG" \
--repo "$GITHUB_REPOSITORY" \
--prerelease \
--title "UsageBar updater channel" \
--notes "Machine-readable updater metadata. Install UsageBar from the versioned releases."
fi

gh release upload "$CHANNEL_TAG" \
--repo "$GITHUB_REPOSITORY" \
"$CHANNEL_DIR/latest.json" \
--clobber

CHANNEL_ASSET=$(gh release view "$CHANNEL_TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | tr -d '\r')
printf '%s\n' "$CHANNEL_ASSET" | grep -Fxq 'latest.json' || { echo "Updater channel is missing latest.json"; exit 1; }
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ playwright-report/
.env*
!.env.example

# Tauri updater signing keys
/usagebar.key
/usagebar.key.pub

# Agent working files
docs/choices.md
docs/breadcrumbs.md
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
### Notes

- Alpha 8 is an unsigned Windows technical preview. Windows may show `Unknown publisher` or SmartScreen warnings.
- No Tauri updater signing key is configured for this prerelease, so updater signature assets are intentionally omitted and prerelease updater checks remain disabled.
- Alpha 8 moves to a signed updater channel with in-app download, explicit restart, installation, and relaunch.
- The NSIS installer is the supported prerelease artifact; MSI remains skipped because WiX rejects semver prerelease versions.

## 0.1.0-alpha.7
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ UsageBar is still pre-release. Alpha 8 is intended to let Windows users install
- Windows is the primary tested platform for this fork. macOS and Linux remain secondary until the Windows release path is boring.
- Provider coverage is uneven: `Supported` means the Windows path is intended to work; `Experimental` means setup, API shape, or live-account validation may still change.
- Some providers report usage directly; others estimate from local history, known quota pools, telemetry logs, or manually supplied session cookies. Provider docs describe the source per integration.
- Prerelease auto-updates are intentionally conservative because GitHub's `releases/latest` alias does not resolve prereleases. Prerelease builds may open the matching GitHub release page instead of installing in-app.
- Published releases use a signed updater channel. UsageBar downloads an available update, then installs it after you select `Restart to update`.
- Authenticode-signed Windows artifacts, live Edge-account validation, and full crash-recovery expectations are full-release work, not an Alpha 8 promise.

## Architecture
Expand Down Expand Up @@ -166,7 +166,7 @@ bun run release:check -- --release-tag v0.1.0-alpha.8
bun run build:release -- --bundles nsis
```

If `TAURI_SIGNING_PRIVATE_KEY` is unset, the helper automatically adds `--no-sign` so the local build can skip Tauri updater signatures. Windows installer builds require Authenticode material by default: `WINDOWS_CERTIFICATE_BASE64` plus `WINDOWS_CERTIFICATE_PASSWORD`, `WINDOWS_CERTIFICATE`, or `WINDOWS_CERTIFICATE_THUMBPRINT`. The helper signs the final setup executable after the build so the Windows launch prompt can show the certificate publisher. The setup executable lands under `src-tauri/target/release/bundle/nsis/`.
Release builds require `TAURI_SIGNING_PRIVATE_KEY`. Pass `--no-sign` only for an explicit local installer smoke build. Windows installer builds require Authenticode material by default: `WINDOWS_CERTIFICATE_BASE64` plus `WINDOWS_CERTIFICATE_PASSWORD`, `WINDOWS_CERTIFICATE`, or `WINDOWS_CERTIFICATE_THUMBPRINT`. The helper signs the final setup executable after the build so the Windows launch prompt can show the certificate publisher. The setup executable lands under `src-tauri/target/release/bundle/nsis/`.

For Alpha 8 unsigned technical-preview builds, set `USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER=1`; those installers can show `Unknown publisher` and trigger Windows SmartScreen's "unrecognized app" warning. Stable/public-confidence Windows builds should be Authenticode-signed; see [docs/releasing.md](docs/releasing.md).

Expand Down
23 changes: 15 additions & 8 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Build the Windows installer locally before the first publish of a version:
bun run build:release -- --bundles nsis
```

If `TAURI_SIGNING_PRIVATE_KEY` is unset, the helper automatically adds `--no-sign` so local builds can still complete without Tauri updater signatures. Windows installer builds require Authenticode material by default. When that material is configured, the helper signs the final NSIS/MSI artifact after the build so the setup executable has a real publisher.
Release builds require `TAURI_SIGNING_PRIVATE_KEY`. Pass `--no-sign` only for an explicit local installer smoke build that will not support in-app updates. Windows installer builds require Authenticode material by default. When that material is configured, the helper signs the final NSIS/MSI artifact after the build so the setup executable has a real publisher.

Alpha 8 exception: unsigned Windows prerelease installers are allowed as technical-preview artifacts while Authenticode signing is deferred. Set `USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER=1` for local unsigned builds. GitHub prerelease publishes set this automatically for tags that contain a prerelease suffix such as `v0.1.0-alpha.8`. These artifacts can show `Unknown publisher`, can trigger Windows SmartScreen's "unrecognized app" warning, and must be described as unsigned in release notes.

Expand All @@ -55,7 +55,7 @@ Recommended GitHub secrets:
- `WINDOWS_CERTIFICATE_PASSWORD`: `.pfx` export password.
- `WINDOWS_TIMESTAMP_URL`: optional timestamp server; defaults to `http://timestamp.digicert.com`.

SmartScreen note: Authenticode signing is necessary but not always sufficient. EV certificates usually get immediate SmartScreen reputation. OV certificates and new certificates can still warn until Microsoft has enough reputation for the certificate or submitted binary.
SmartScreen note: Authenticode signing is necessary but not always sufficient. New OV and EV certificates can still warn until Microsoft has enough reputation for the certificate or submitted binary.

## GitHub Publish

Expand All @@ -70,13 +70,16 @@ The workflow runs the same release preflight, builds platform artifacts, and ver

- a Windows setup executable ending in `setup.exe`

Stable releases require `TAURI_SIGNING_PRIVATE_KEY` and updater signature assets. For prerelease tags, the workflow passes `--no-sign` when that key is unavailable and publishes an unsigned technical-preview installer without updater assets. Prerelease tags also set `USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER=1` so Authenticode signing remains optional.
All published releases require `TAURI_SIGNING_PRIVATE_KEY`, `latest.json`, and updater signature assets. Prerelease tags still set `USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER=1`, so Authenticode signing remains optional for technical previews.

Current updater channel note:
The release workflow creates `latest.json` automatically through `tauri-action` with `includeUpdaterJson: true`. It then verifies that the versioned release contains `latest.json` and at least one `.sig` file. The workflow copies the manifest to the fixed `updater` release and verifies that channel again. Do not create or upload `latest.json` manually. A release without the manifest or signatures stops before the updater channel is updated.

Current updater channel:

- GitHub's `releases/latest` alias only resolves stable releases, not prereleases.
- UsageBar currently keeps updater checks disabled for prerelease app versions like `0.1.0-alpha.1` and `0.1.0-beta.7`.
- Re-enable prerelease auto-updates only after moving off the stable-only alias or after shipping a stable release channel.
- The publish workflow copies each signed `latest.json` to the fixed `updater` release.
- UsageBar reads `https://github.com/luisleineweber/usagebar/releases/download/updater/latest.json`.
- The app downloads first. It installs and relaunches only after the user selects `Restart to update`.

## Alpha Gate

Expand All @@ -87,10 +90,10 @@ Before publishing Alpha 8, verify and record:
- Install, uninstall, config/data location, and first-run provider setup are documented.
- At least one supported provider works from a fresh setup path.
- Invalid credentials, offline/network failure, provider API failure, empty data, and refresh-in-progress states do not crash the app.
- README and release notes state privacy, telemetry, crash-log behavior, known limitations, and feedback/debug-info path.
- README and the linked support documentation state privacy, telemetry, crash-log behavior, known limitations, and feedback/debug-info path.
- `CHANGELOG.md` includes the exact release version with supported features and known limitations.

Use the Alpha Gate bullets above for the final local artifact or GitHub release candidate before tagging. Historical Alpha 1 smoke evidence is archived at [alpha-smoke-test-0.1.0-alpha.1.md](archive/release/alpha-smoke-test-0.1.0-alpha.1.md).
Use the Alpha Gate bullets above as verification checks for the final local artifact or GitHub release candidate before tagging. Keep `Alpha Notes` short and include only the release-specific points that remain relevant. Historical Alpha 1 smoke evidence is archived at [alpha-smoke-test-0.1.0-alpha.1.md](archive/release/alpha-smoke-test-0.1.0-alpha.1.md).

Suggested Alpha 1 release-note shape:

Expand All @@ -100,21 +103,25 @@ Suggested Alpha 1 release-note shape:
This is a public alpha for Windows users who want to test UsageBar before a full release.

### Supported

- Windows NSIS installer
- Provider setup for ...
- Manual refresh
- Local settings storage

### Known limitations

- Some providers are experimental and may need manual cookie/API-key setup
- Some costs or usage buckets may be estimated or partial
- Prerelease updates may open GitHub Releases instead of installing in-app
- UI polish, crash recovery, and signed-build coverage are not final

### Privacy

UsageBar stores app settings and app-owned provider secrets locally under `%APPDATA%\com.sunstory.usagebar` on Windows. Provider secrets saved by UsageBar are encrypted with Windows DPAPI. Provider credentials and usage payloads are not intentionally sent to UsageBar-owned services.

### Feedback

Report bugs at https://github.com/luisleineweber/usagebar/issues/new and include app version, Windows version, provider, error text, timestamp, and sanitized logs. Do not include API keys, cookies, or raw credential files.
```

Expand Down
44 changes: 25 additions & 19 deletions scripts/build-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,23 @@ if (signingKeyValue && existsSync(signingKeyValue)) {

const resolvedArgs = [...args]
if (!env.TAURI_SIGNING_PRIVATE_KEY && !resolvedArgs.includes("--no-sign")) {
resolvedArgs.push("--no-sign")
console.log("No TAURI_SIGNING_PRIVATE_KEY found; building without Tauri updater signatures.")
console.error(
"Missing TAURI_SIGNING_PRIVATE_KEY. Set the updater signing key or pass --no-sign for an explicit installer-only smoke build."
)
process.exit(1)
}

function hasWindowsSigningMaterial() {
return Boolean(
env.WINDOWS_CERTIFICATE_THUMBPRINT ||
env.WINDOWS_CERTIFICATE_BASE64 ||
env.WINDOWS_CERTIFICATE
env.WINDOWS_CERTIFICATE_THUMBPRINT || env.WINDOWS_CERTIFICATE_BASE64 || env.WINDOWS_CERTIFICATE
)
}

function allowsUnsignedWindowsInstaller() {
return env.USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER === "1" ||
return (
env.USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER === "1" ||
env.USAGEBAR_ALLOW_UNSIGNED_WINDOWS_INSTALLER?.toLowerCase() === "true"
)
}

function requestsWindowsInstaller() {
Expand Down Expand Up @@ -128,19 +130,23 @@ function signWindowsInstallerArtifacts(artifactDirs) {
console.log("Signing Windows installer artifacts after build:")

for (const artifact of artifacts) {
const signer = spawnSync("powershell", [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
windowsSignScript,
"-TargetPath",
artifact,
], {
cwd: repoRoot,
env,
stdio: "inherit",
})
const signer = spawnSync(
"powershell",
[
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
windowsSignScript,
"-TargetPath",
artifact,
],
{
cwd: repoRoot,
env,
stdio: "inherit",
}
)

if (signer.error) {
console.error("Failed to launch Windows installer signing:", signer.error)
Expand Down
18 changes: 15 additions & 3 deletions scripts/release-preflight.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ if (!semverPattern.test(version)) {
}

if (tauriConf.version !== version) {
fail(`src-tauri/tauri.conf.json version (${tauriConf.version}) does not match package.json (${version})`)
fail(
`src-tauri/tauri.conf.json version (${tauriConf.version}) does not match package.json (${version})`
)
}

if (cargoVersion !== version) {
Expand All @@ -102,8 +104,18 @@ if (tauriConf.productName !== "UsageBar") {
}

const updaterEndpoints = tauriConf.plugins?.updater?.endpoints ?? []
if (!updaterEndpoints.some((endpoint) => String(endpoint).includes("github.com/luisleineweber/usagebar/releases"))) {
fail("Updater endpoint is not pointed at luisleineweber/usagebar releases")
const expectedUpdaterEndpoint =
"https://github.com/luisleineweber/usagebar/releases/download/updater/latest.json"
if (updaterEndpoints.length !== 1 || updaterEndpoints[0] !== expectedUpdaterEndpoint) {
fail(`Updater endpoint must be ${expectedUpdaterEndpoint}`)
}

if (tauriConf.bundle?.createUpdaterArtifacts !== true) {
fail("Tauri updater artifacts must be enabled")
}

if (!tauriConf.plugins?.updater?.pubkey) {
fail("Tauri updater public key is missing")
}

if (!changelog.includes(`## ${version}`)) {
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@
},
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDVGMzA0N0Q1MzEyNDBCQ0IKUldUTEN5UXgxVWN3WDRrbkg1UW5kRFpHVXdMK25zWm5LRGlSZlR4UWdRMGFmODZab0hMYjFlLzkK",
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDA0OEFBMjdENzUyN0I2MkMKUldRc3RpZDFmYUtLQk5tRFFVbkF4NS9ha2dvdCtkV3AwWVNUK092ZEtGazNSRnlYMEVsRVJWdVoK",
"endpoints": [
"https://github.com/luisleineweber/usagebar/releases/latest/download/latest.json"
"https://github.com/luisleineweber/usagebar/releases/download/updater/latest.json"
]
}
}
Expand Down
Loading