hydra

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

make-release.sh (6710B)


      1 #!/usr/bin/env bash
      2 # make-release.sh — build a distributable Hydra release: a .dmg containing the prebuilt
      3 # binaries + installer, plus a source tarball. Hydra is a CLI-first tool, so the .dmg is a
      4 # tidy "download, open, run ./install.sh" payload — NOT a drag-to-Applications app.
      5 #
      6 #   ./scripts/make-release.sh [version]      # version defaults to the workspace Cargo.toml
      7 #
      8 # Output (in dist/release/):
      9 #   Hydra-<ver>.dmg        prebuilt arm64+x86_64 binaries + driver + install.sh + themes
     10 #   hydra-<ver>-src.tar.gz pristine source (for the build-it-yourself crowd)
     11 #
     12 # NOT notarized (no Apple Developer ID). On another Mac, Gatekeeper will require
     13 # right-click → Open, and the entitled daemon / HAL driver may need manual TCC approval.
     14 # Set HYDRA_SIGN_ID to a Developer ID and this same layout becomes notarizable later.
     15 set -euo pipefail
     16 
     17 ROOT="$(cd "$(dirname "$0")/.." && pwd)"
     18 VER="${1:-$(grep -m1 '^version' "$ROOT/Cargo.toml" | sed 's/.*"\(.*\)".*/\1/')}"
     19 OUT="$ROOT/dist/release"
     20 STAGE="$OUT/Hydra-$VER"
     21 
     22 say() { printf '\n\033[1;36m▸ %s\033[0m\n' "$1"; }
     23 
     24 say "Hydra release $VER"
     25 rm -rf "$OUT"
     26 mkdir -p "$STAGE"
     27 
     28 say "1/5  Building release binaries (universal where possible)"
     29 # Build both arches so the download runs on Apple Silicon + Intel. If a target isn't
     30 # installed, fall back to the host arch only (noted in the README inside the dmg).
     31 ARCHES=()
     32 for t in aarch64-apple-darwin x86_64-apple-darwin; do
     33     if rustup target list --installed 2>/dev/null | grep -qx "$t"; then ARCHES+=("$t"); fi
     34 done
     35 if [ ${#ARCHES[@]} -eq 0 ]; then
     36     cargo build --manifest-path "$ROOT/Cargo.toml" --release -p hydra -p hydrad
     37     lipo_or_copy() { cp "$ROOT/target/release/$1" "$STAGE/bin/$1"; }
     38 else
     39     for t in "${ARCHES[@]}"; do
     40         cargo build --manifest-path "$ROOT/Cargo.toml" --release --target "$t" -p hydra -p hydrad
     41     done
     42     lipo_or_copy() {
     43         local out="$STAGE/bin/$1"; local ins=()
     44         for t in "${ARCHES[@]}"; do ins+=("$ROOT/target/$t/release/$1"); done
     45         if [ ${#ins[@]} -gt 1 ]; then lipo -create "${ins[@]}" -output "$out"; else cp "${ins[0]}" "$out"; fi
     46     }
     47 fi
     48 mkdir -p "$STAGE/bin"
     49 lipo_or_copy hydra
     50 lipo_or_copy hydrad
     51 
     52 say "2/5  Building the driver"
     53 "$ROOT/scripts/build-driver.sh" >/dev/null
     54 cp -R "$ROOT/driver/build/Hydra.driver" "$STAGE/Hydra.driver"
     55 
     56 say "3/5  Staging installer + assets"
     57 # A self-contained installer that uses the prebuilt bin/ instead of rebuilding from source.
     58 cp "$ROOT/scripts/install-driver.sh" "$STAGE/"
     59 cp "$ROOT/scripts/install-agent.sh" "$STAGE/"
     60 cp "$ROOT/scripts/Info.plist" "$ROOT/scripts/hydrad.entitlements" "$STAGE/"
     61 cp -R "$ROOT/themes" "$STAGE/themes"
     62 cp "$ROOT/LICENSE" "$STAGE/LICENSE" 2>/dev/null || true
     63 # The release-flavoured installer: signs the prebuilt daemon into Hydra.app, loads the
     64 # agent, installs the driver, symlinks `hydra`. No cargo/rustc needed on the user's machine.
     65 cat > "$STAGE/install.sh" <<'INSTALL'
     66 #!/usr/bin/env bash
     67 # Hydra installer (prebuilt release). Run from the mounted .dmg / unzipped folder.
     68 #   ./install.sh            full install (driver needs sudo; restarts coreaudiod ~1s)
     69 #   ./install.sh --no-driver
     70 set -euo pipefail
     71 HERE="$(cd "$(dirname "$0")" && pwd)"
     72 WITH_DRIVER=1; [ "${1:-}" = "--no-driver" ] && WITH_DRIVER=0
     73 SIGN_ID="${HYDRA_SIGN_ID:--}"
     74 say() { printf '\n\033[1;36m▸ %s\033[0m\n' "$1"; }
     75 
     76 say "Assembling + signing Hydra.app"
     77 APP="$HOME/Applications/Hydra.app"
     78 mkdir -p "$APP/Contents/MacOS"
     79 cp "$HERE/bin/hydrad" "$APP/Contents/MacOS/hydrad"
     80 cp "$HERE/Info.plist" "$APP/Contents/Info.plist"
     81 codesign --force --options runtime --sign "$SIGN_ID" --identifier com.ganten.hydra \
     82     --entitlements "$HERE/hydrad.entitlements" "$APP"
     83 
     84 say "Installing the 'hydra' command to /usr/local/bin (sudo)"
     85 sudo install -m 755 "$HERE/bin/hydra" /usr/local/bin/hydra
     86 
     87 say "Loading the daemon LaunchAgent"
     88 LABEL="com.ganten.hydrad"; PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
     89 mkdir -p "$HOME/Library/LaunchAgents"
     90 cat > "$PLIST" <<PLIST_EOF
     91 <?xml version="1.0" encoding="UTF-8"?>
     92 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
     93 <plist version="1.0"><dict>
     94   <key>Label</key><string>$LABEL</string>
     95   <key>ProgramArguments</key><array><string>$APP/Contents/MacOS/hydrad</string></array>
     96   <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
     97   <key>StandardErrorPath</key><string>/tmp/hydrad.err.log</string>
     98   <key>StandardOutPath</key><string>/tmp/hydrad.out.log</string>
     99 </dict></plist>
    100 PLIST_EOF
    101 launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
    102 launchctl bootstrap "gui/$(id -u)" "$PLIST"
    103 launchctl kickstart -k "gui/$(id -u)/$LABEL"
    104 
    105 say "Installing themes to ~/.config/hydra/themes"
    106 mkdir -p "$HOME/.config/hydra/themes"
    107 cp "$HERE/themes/"*.toml "$HOME/.config/hydra/themes/" 2>/dev/null || true
    108 
    109 if [ "$WITH_DRIVER" = "1" ]; then
    110     say "Installing the virtual-audio driver (sudo; restarts coreaudiod ~1s)"
    111     HAL="/Library/Audio/Plug-Ins/HAL"
    112     sudo rm -rf "$HAL/Hydra.driver"
    113     sudo cp -R "$HERE/Hydra.driver" "$HAL/Hydra.driver"
    114     MAN="/Library/Application Support/hydra"
    115     sudo mkdir -p "$MAN"; sudo chgrp admin "$MAN"; sudo chmod 775 "$MAN"
    116     sudo killall coreaudiod 2>/dev/null || true
    117 fi
    118 
    119 cat <<DONE
    120 
    121 ✓ Hydra installed.  Launch the control panel:  hydra
    122   In another app, pick "Hydra" as the microphone to receive routed audio.
    123   Uninstall: see the project README.
    124 DONE
    125 INSTALL
    126 chmod +x "$STAGE/install.sh" "$STAGE/install-driver.sh" "$STAGE/install-agent.sh"
    127 
    128 # A short read-me-first inside the download.
    129 cat > "$STAGE/README.txt" <<TXT
    130 Hydra $VER — terminal-native virtual audio router for macOS
    131 
    132 INSTALL
    133   1. Open Terminal, cd into this folder.
    134   2. ./install.sh            (the driver step needs your admin password and
    135                               briefly restarts audio; or ./install.sh --no-driver)
    136   3. Run:  hydra
    137 
    138 NOTE: this build is NOT notarized. macOS Gatekeeper may warn; if a binary is
    139 blocked, right-click → Open once, or clear quarantine:
    140   xattr -dr com.apple.quarantine .
    141 
    142 macOS 14.4+ required (Core Audio process taps). GPL-3.0-or-later. Source:
    143 https://ganten.neocities.org/git/  (or the bundled hydra-$VER-src.tar.gz)
    144 TXT
    145 
    146 say "4/5  Building the .dmg"
    147 DMG="$OUT/Hydra-$VER.dmg"
    148 hdiutil create -volname "Hydra $VER" -srcfolder "$STAGE" -ov -format UDZO "$DMG" >/dev/null
    149 echo "  → $DMG"
    150 
    151 say "5/5  Source tarball"
    152 TARBALL="$OUT/hydra-$VER-src.tar.gz"
    153 git -C "$ROOT" archive --format=tar.gz --prefix="hydra-$VER/" -o "$TARBALL" HEAD
    154 echo "  → $TARBALL"
    155 
    156 say "Done — release artifacts in $OUT"
    157 ls -lh "$OUT" | grep -E "\.dmg|\.tar\.gz"
    158 echo
    159 echo "Checksums:"
    160 ( cd "$OUT" && shasum -a 256 "Hydra-$VER.dmg" "hydra-$VER-src.tar.gz" )