0.3.0
[0.3.0] — 2026-07-14
Adds a macOS menubar companion app, daemon-side plumbing to push live
drive updates over Server-Sent Events, and a VitePress product site
at airlock.emdzej.pl. No changes to the
existing 0.2.0 REST API — the companion app is a strict additive
layer.
Added — Mac companion app (companion/mac/)
- New menubar-only
AirlockCompanion.app(Swift Package +
LSUIElement). Discovers airlock instances via the new
_airlock._tcpBonjour service; lists their drives; per-drive
actions Mount / Mount and Open in Finder / Unmount /
Reveal in Finder / Copy SMB URL / Eject drive from
airlock; per-host Eject all drives and Open web UI. - Silent mount via
NetFSMountURLSyncfromNetFS.framework—
same DiskArbitration path Finder uses, but no window pops on
mount.NSWorkspace.open(smb://…)was the first draft; it worked
but always opened a window, making "Mount" and "Mount and Open"
indistinguishable. - Auto-unmount reconciliation: when a drive is ejected on the
daemon side (web UI, GPIO button, format), the Mac's local SMB
mount is dropped automatically. Skipped when the host is
currently offline so a Pi reboot doesn't yank live mounts. - Preferences window:
Auto-mount all discovered drives,
Open in Finder after mounting,Start at login(via
SMAppService.mainAppon macOS 13+), plus a footer with the app
version + a GitHub link. - Persistent host store: airlocks the app has seen appear in the
menu even when offline, withoffline · 2h ago. Prune after 30
days of not-seen. Restores drive list from cache on launch so the
menu is populated before the first SSE reconnect finishes. - Coloured host status dot: green live · grey offline · red
error, with the last error inline. - Live updates via SSE with exponential-backoff reconnect and
URLSessionDataDelegate-based frame parsing (bypasses the
URLSession.bytes buffering issue on macOS 13/14). - XcodeGen
project.ymlfor developers who want Xcode.
.xcodeprojstays gitignored. - Distributable DMG via
package.sh. The .app inside is
ad-hoc signed (codesign --force --deep --sign -) so macOS 15
Sequoia's stricter Gatekeeper — which no longer honours the old
right-click Open bypass on unsigned apps — treats it as a normal
first-run prompt instead of "damaged and can't be opened." Not
notarized (no Apple Developer Account); users still need one
xattr -dr com.apple.quarantine /Applications/AirlockCompanion.app
after copying, documented in the companion guide.
Added — daemon
GET /api/events— Server-Sent Events stream broadcasting the
current drive list on every mount-manager change. Initial snapshot
on connect, heartbeat comment lines every 30 s, event types are
wrapped in an envelope ({"type":"drives","drives":[...]}) so we
can add more event kinds without a breaking wire change.mount.Manager.AddListenerso multiple downstream consumers can
subscribe to drive-change events. The Samba writer and the SSE
broadcaster both hook in via this now instead of wrapping the
primary listener.- Additional
_airlock._tcpBonjour advertisement (port 80, TXT
api=1 path=/). Companion apps browse for exactly that instead of
filtering_smb._tcp/_http._tcp.
Added — release workflow
- New
companion-macjob onmacos-14. StampsInfo.plistwith
the release tag, builds the .app viaswift build, packages a
DMG viahdiutil, attachesAirlockCompanion-<tag>.dmg+
sha256 to the same GitHub release the daemon binary and pi-gen
image are already on.
Added — product site (docs/)
- airlock.emdzej.pl — VitePress site
built from the existingdocs/folder. New landing hero page, a
product overview, and a dedicated macOS companion guide sit next
to the existing user guide and install guide (same source, no
duplication). .github/workflows/site.ymlbuilds and publishes to GitHub Pages
on every push tomainthat touchesdocs/**, using
actions/deploy-pages@v4. Custom domain viadocs/public/CNAME;
DNS + Pages source (GitHub Actions) configured out-of-band on
the repo.
Version surfaces
- Web UI footer already links
Version 0.3.0→ the repo. - Companion app's Preferences window shows
Airlock Companion 0.3.0
at the bottom with a matching GitHub link. - Daemon
main.versionbumped to0.3.0; release workflow
overrides via-ldflags -X main.version=<tag>for tagged builds.
Full Changelog: 0.2.0...0.3.0