commit f6f94739d09834986837d563529fbca7b76c5559
parent f81577cea054c009d7545b755b1f119e34f55668
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date: Sun, 31 May 2026 11:15:48 -0500
P1: add signing scripts lost in an earlier cancelled batch
scripts/bundle.sh (build + ad-hoc/Dev-ID codesign Hydra.app with the
audio-input entitlement + NSAudioCaptureUsageDescription Info.plist),
scripts/install-agent.sh (GUI-session LaunchAgent), plus Info.plist and
hydrad.entitlements. Verified: bundle signs clean, identity com.ganten.hydra,
entitlement embedded, bundled daemon launches and serves enumeration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat:
4 files changed, 141 insertions(+), 0 deletions(-)
diff --git a/scripts/Info.plist b/scripts/Info.plist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleIdentifier</key>
+ <string>com.ganten.hydra</string>
+ <key>CFBundleName</key>
+ <string>Hydra</string>
+ <key>CFBundleDisplayName</key>
+ <string>Hydra</string>
+ <key>CFBundleExecutable</key>
+ <string>hydrad</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>0.1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>14.4</string>
+ <!-- Background agent: no Dock icon, no menu bar. -->
+ <key>LSUIElement</key>
+ <true/>
+ <!-- Shown in the TCC consent dialog when Hydra first taps an app. -->
+ <key>NSAudioCaptureUsageDescription</key>
+ <string>Hydra captures audio from apps so it can route it to virtual devices and your speakers.</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Hydra captures audio from apps and inputs so it can route them.</string>
+</dict>
+</plist>
diff --git a/scripts/bundle.sh b/scripts/bundle.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+# bundle.sh — assemble and code-sign Hydra.app around the daemon.
+#
+# CoreAudio process taps require a TCC-authorizable identity: a bundle with an
+# Info.plist (NSAudioCaptureUsageDescription) signed with the audio-input entitlement.
+# A bare `cargo run` binary can't be authorized, so the daemon must run from this bundle.
+#
+# ./scripts/bundle.sh [debug|release] (default: release)
+#
+# Produces dist/Hydra.app. Sign ad-hoc by default; set HYDRA_SIGN_ID to a real identity
+# (e.g. "Apple Development: you@example.com") for a stable identity that doesn't churn
+# the TCC grant on every rebuild.
+set -euo pipefail
+
+PROFILE="${1:-release}"
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+SIGN_ID="${HYDRA_SIGN_ID:--}" # '-' = ad-hoc
+APP="$ROOT/dist/Hydra.app"
+
+echo "• building hydrad ($PROFILE)"
+if [ "$PROFILE" = "release" ]; then
+ cargo build --manifest-path "$ROOT/Cargo.toml" -p hydrad --release
+ BIN="$ROOT/target/release/hydrad"
+else
+ cargo build --manifest-path "$ROOT/Cargo.toml" -p hydrad
+ BIN="$ROOT/target/debug/hydrad"
+fi
+
+echo "• assembling $APP"
+rm -rf "$APP"
+mkdir -p "$APP/Contents/MacOS"
+cp "$BIN" "$APP/Contents/MacOS/hydrad"
+cp "$ROOT/scripts/Info.plist" "$APP/Contents/Info.plist"
+
+echo "• signing (identity: $SIGN_ID)"
+codesign --force --options runtime \
+ --sign "$SIGN_ID" \
+ --identifier com.ganten.hydra \
+ --entitlements "$ROOT/scripts/hydrad.entitlements" \
+ "$APP"
+
+echo "• verifying"
+codesign --verify --verbose "$APP"
+echo
+echo "✓ built $APP"
+echo " daemon executable: $APP/Contents/MacOS/hydrad"
+echo " install as a LaunchAgent: ./scripts/install-agent.sh"
+echo " or run directly: '$APP/Contents/MacOS/hydrad'"
diff --git a/scripts/hydrad.entitlements b/scripts/hydrad.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <!-- Required for Core Audio process taps (per-app capture). Without this entitlement
+ the tap is created but CoreAudio refuses to clock its IO. -->
+ <key>com.apple.security.device.audio-input</key>
+ <true/>
+</dict>
+</plist>
diff --git a/scripts/install-agent.sh b/scripts/install-agent.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+# install-agent.sh — run hydrad as a per-user LaunchAgent (GUI session).
+#
+# The daemon MUST run in the user's GUI login session, not as a system LaunchDaemon:
+# process taps and their TCC consent are bound to that session. RunAtLoad + KeepAlive
+# keep routing alive across logins and restarts.
+#
+# Run ./scripts/bundle.sh first so dist/Hydra.app exists.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+APP="$ROOT/dist/Hydra.app"
+EXEC="$APP/Contents/MacOS/hydrad"
+LABEL="com.ganten.hydrad"
+PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
+
+if [ ! -x "$EXEC" ]; then
+ echo "error: $EXEC not found — run ./scripts/bundle.sh first." >&2
+ exit 1
+fi
+
+echo "• writing $PLIST"
+mkdir -p "$HOME/Library/LaunchAgents"
+cat > "$PLIST" <<PLIST_EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>$LABEL</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>$EXEC</string>
+ </array>
+ <key>RunAtLoad</key>
+ <true/>
+ <key>KeepAlive</key>
+ <true/>
+ <key>StandardErrorPath</key>
+ <string>/tmp/hydrad.err.log</string>
+ <key>StandardOutPath</key>
+ <string>/tmp/hydrad.out.log</string>
+</dict>
+</plist>
+PLIST_EOF
+
+echo "• (re)loading agent"
+launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
+launchctl bootstrap "gui/$(id -u)" "$PLIST"
+launchctl kickstart -k "gui/$(id -u)/$LABEL"
+
+echo "✓ $LABEL loaded. Logs: /tmp/hydrad.err.log"
+echo " First time you start a route, macOS will prompt for Audio Recording access."