add microphone capture and stable code signing for TCC persistence

Adds --capture-mic/--mic-output for a second, independently-captured audio
track (mic vs system, written to separate outputs to avoid interleaving
corruption). Embeds Info.plist at link time so the binary carries a stable
CFBundleIdentifier and the usage-description keys TCC requires, and adds
scripts/build-signed.sh + scripts/create-signing-identity.sh so a rebuilt
binary keeps the same signing identity instead of losing granted
permissions on every rebuild.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sttlab-tech
2026-08-09 13:10:08 +02:00
parent 56ac954369
commit 678557caf7
10 changed files with 678 additions and 9 deletions
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# Builds audiotee, signs it with a stable identity, and installs it to a
# fixed path. Both steps matter for Core Audio process tap / microphone TCC
# permissions to survive across rebuilds — see CONTEXT.md §6.2 and §8:
#
# - SwiftPM ad-hoc-signs debug/release builds by default. Ad-hoc signatures
# are keyed off the binary's own hash, so every rebuild looks like a new
# app to TCC and permission has to be re-granted.
# - TCC has also been observed keying on binary path, so builds are installed
# to a fixed location outside .build/.
#
# Usage:
# scripts/build-signed.sh # build, sign, install to ~/bin
# scripts/build-signed.sh --reset-tcc # also reset TCC state for this
# # binary, useful after changing
# # Info.plist or the signing identity
#
# Requires a self-signed code-signing certificate in your keychain. If you
# don't have one yet:
# 1. Open Keychain Access
# 2. Keychain Access menu > Certificate Assistant > Create a Certificate...
# 3. Name it (e.g. "audiotee-dev"), Identity Type: Self Signed Root,
# Certificate Type: Code Signing
# 4. Create it, then in Keychain Access double-click it, expand "Trust",
# and set "Code Signing" to "Always Trust"
# Override auto-detection with: AUDIOTEE_SIGNING_IDENTITY="Your Cert Name"
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
BUNDLE_ID="com.stephanetailland.audiotee"
INSTALL_DIR="${AUDIOTEE_INSTALL_DIR:-$HOME/bin}"
INSTALL_PATH="$INSTALL_DIR/audiotee"
RESET_TCC=false
for arg in "$@"; do
case "$arg" in
--reset-tcc) RESET_TCC=true ;;
*)
echo "Unknown argument: $arg" >&2
exit 1
;;
esac
done
if [[ -n "${AUDIOTEE_SIGNING_IDENTITY:-}" ]]; then
IDENTITY="$AUDIOTEE_SIGNING_IDENTITY"
else
# Real identity lines look like ` 1) <hash> "Name"`; the "N valid
# identities found" summary line has no ")" and must not be counted.
IDENTITY_LINES="$(security find-identity -v -p codesigning | grep '^ *[0-9]*)' || true)"
IDENTITY_COUNT="$(printf '%s\n' "$IDENTITY_LINES" | grep -c . || true)"
if [[ "$IDENTITY_COUNT" -eq 0 ]]; then
echo "Error: no code-signing identity found in your keychain." >&2
echo "See the comment at the top of this script for how to create one." >&2
exit 1
elif [[ "$IDENTITY_COUNT" -gt 1 ]]; then
echo "Error: multiple code-signing identities found. Set AUDIOTEE_SIGNING_IDENTITY" >&2
echo "to the one to use:" >&2
echo "$IDENTITY_LINES" >&2
exit 1
fi
IDENTITY="$(printf '%s\n' "$IDENTITY_LINES" | sed -n 's/.*"\(.*\)"/\1/p')"
fi
echo "Building (release)..."
swift build -c release
BUILT_BINARY="$REPO_ROOT/.build/release/audiotee"
echo "Signing with identity: $IDENTITY"
codesign --force --sign "$IDENTITY" --identifier "$BUNDLE_ID" "$BUILT_BINARY"
mkdir -p "$INSTALL_DIR"
cp "$BUILT_BINARY" "$INSTALL_PATH"
echo "Installed to $INSTALL_PATH"
codesign -dvvv "$INSTALL_PATH"
if [[ "$RESET_TCC" == true ]]; then
echo "Resetting TCC state for $BUNDLE_ID..."
tccutil reset SystemAudioCaptureRequests "$BUNDLE_ID" || true
tccutil reset Microphone "$BUNDLE_ID" || true
fi