Skip to content
Merged
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
59 changes: 57 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,22 @@ jobs:
APPLE_API_ISSUER: ${{ env.APPLE_API_ISSUER }}
APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }}
APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
npm ci
# createUpdaterArtifacts (tauri.conf.json) requires the updater signing
# key. Forks without the secret still build — just without in-app
# updater artifacts (they can use the DMG / Homebrew).
UPDATER_OFF=()
if [[ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]]; then
echo "TAURI_SIGNING_PRIVATE_KEY missing — building without updater artifacts"
UPDATER_OFF=(--config '{"bundle":{"createUpdaterArtifacts":false}}')
fi
if [[ "${SIGNED:-0}" == "1" ]]; then
npm run tauri build -- --bundles app,dmg --target ${{ matrix.target }}
npm run tauri build -- --bundles app,dmg --target ${{ matrix.target }} "${UPDATER_OFF[@]}"
else
npm run tauri build -- --bundles app --target ${{ matrix.target }}
npm run tauri build -- --bundles app --target ${{ matrix.target }} "${UPDATER_OFF[@]}"
fi

- name: Package .app and DMG
Expand Down Expand Up @@ -177,6 +187,26 @@ jobs:
else
echo "No DMG produced (unsigned build or bundle skipped)"
fi

# Updater artifacts (in-app self-updater). Renamed per-arch so the two
# matrix legs don't collide as release assets; latest.json points here.
UPD=""
while IFS= read -r candidate; do
UPD="$candidate"
break
done < <(find apps/microbridge-ui/src-tauri/target -type f -name '*.app.tar.gz' 2>/dev/null || true)
SIG=""
while IFS= read -r candidate; do
SIG="$candidate"
break
done < <(find apps/microbridge-ui/src-tauri/target -type f -name '*.app.tar.gz.sig' 2>/dev/null || true)
if [[ -n "$UPD" && -n "$SIG" ]]; then
cp "$UPD" "ui-out/Microbridge-${{ matrix.target }}.app.tar.gz"
cp "$SIG" "ui-out/Microbridge-${{ matrix.target }}.app.tar.gz.sig"
echo "Packed updater artifact for ${{ matrix.target }}"
else
echo "No updater artifact produced (updater disabled or signing key absent)"
fi
ls -la ui-out

- uses: actions/upload-artifact@v7
Expand All @@ -198,6 +228,31 @@ jobs:
mkdir -p release-assets
find artifacts -type f \( -name '*.tar.gz' -o -name '*.dmg' \) -exec cp {} release-assets/ \;
ls -la release-assets
- name: Generate updater manifest (latest.json)
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
BASE="https://github.com/DevVig/microbridge/releases/download/${TAG}"
SIG_AARCH="$(find artifacts -type f -name 'Microbridge-aarch64-apple-darwin.app.tar.gz.sig' | head -1)"
SIG_X86="$(find artifacts -type f -name 'Microbridge-x86_64-apple-darwin.app.tar.gz.sig' | head -1)"
Comment on lines +237 to +238
if [[ -z "$SIG_AARCH" || -z "$SIG_X86" ]]; then
echo "Updater signatures missing — skipping latest.json (no in-app update for this release)"
exit 0
fi
PUB_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
jq -n \
--arg version "$VERSION" \
--arg notes "Microbridge ${TAG}. See the GitHub release notes for details." \
--arg pub_date "$PUB_DATE" \
--arg sig_a "$(cat "$SIG_AARCH")" \
--arg url_a "${BASE}/Microbridge-aarch64-apple-darwin.app.tar.gz" \
--arg sig_x "$(cat "$SIG_X86")" \
--arg url_x "${BASE}/Microbridge-x86_64-apple-darwin.app.tar.gz" \
'{version: $version, notes: $notes, pub_date: $pub_date, platforms: {"darwin-aarch64": {signature: $sig_a, url: $url_a}, "darwin-x86_64": {signature: $sig_x, url: $url_x}}}' \
> release-assets/latest.json
echo "Wrote latest.json:"
cat release-assets/latest.json
- uses: softprops/action-gh-release@v3
with:
generate_release_notes: true
Expand Down
10 changes: 10 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ Or open the DMG from the GitHub Release page and drag Microbridge into
Applications, then install/start the daemon via Homebrew or the daemon
archive.

**In-app updates (direct installs).** A DMG/manual install updates itself:
right-click the menu bar icon → **Check for Updates…**, or turn on *Settings →
Updates → check automatically at launch* (off by default). The app downloads
the signed update, verifies it, and relaunches. This is the only network call
Microbridge makes, and only when you ask — the daemon stays zero-network.

Homebrew installs are managed by brew instead: the app detects the brew
marker and points you at `brew upgrade microbridge` rather than self-replacing,
so the formula version and the on-disk app never drift apart.

**Note:** Homebrew installs **prebuilt** release binaries (not a from-source
Tauri build). The formula checksums are refreshed by CI after each `v*` tag.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The Micro's best feature — bidirectional Agent Keys — currently works throug
## Design principles

1. **Invisible footprint.** Event-driven end to end: no polling loops, no heartbeat timers. Idle CPU is 0.0% and idle RSS targets single-digit megabytes. If Microbridge is noticeable in Activity Monitor, that is a bug — the [footprint budget](docs/architecture.md#footprint-budget) is a spec, not an aspiration.
2. **Zero network.** No telemetry, no update pings, no cloud. The daemon's only I/O is a local Unix socket and the USB device. It links no HTTP clientauditable in `Cargo.lock`.
2. **Zero-network daemon.** No telemetry, no cloud. The always-resident daemon's only I/O is a local Unix socket and the USB device — it links no HTTP client, auditable in `Cargo.lock`. The menu bar app doesn't phone home either, with one opt-in exception: an update check *you* trigger (or enable to run once at launch). No background polling, no automatic pings.
3. **Rust core, any-language adapters.** The always-resident part is a single static Rust binary. First-party adapters compile into it (in-process, ~zero overhead). Community adapters are separate processes speaking [newline-delimited JSON](docs/protocol.md) — write one in whatever you like.
4. **The menu bar app is the product UI.** Configure keys, lighting, and adapters there. The daemon keeps the hardware alive underneath; `microbridgectl` is a support/debug escape hatch.

Expand Down
1 change: 1 addition & 0 deletions apps/microbridge-ui/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
Expand Down
Loading