hydra

Terminal replacement for Loopback — virtual audio devices and routing on macOS, from a ratatui TUI.
Log | Files | Refs | README | LICENSE

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:
Ascripts/Info.plist | 30++++++++++++++++++++++++++++++
Ascripts/bundle.sh | 48++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/hydrad.entitlements | 10++++++++++
Ascripts/install-agent.sh | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
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."