#!/bin/sh # Kestrel sidecar installer. # curl -fsSL https://raw.githubusercontent.com/pingnestapp/kestrel/main/install.sh | sh # # Installs the kestrel-sidecar binary to /usr/local/bin and sets it up as a # system service (systemd on Linux, launchd on macOS). The sidecar runs next # to your Frigate server; the Kestrel app pairs with it over your LAN. # # Binaries + checksums are served from GitHub Releases (no bandwidth cost). # # Environment overrides: # KESTREL_FRIGATE_URL Frigate base URL written into the service # (default http://127.0.0.1:5000 — edit later if unsure) # KESTREL_DATA_DIR sidecar data dir (default /var/lib/kestrel-sidecar) # KESTREL_REPO GitHub owner/repo (default pingnestapp/kestrel) # VERSION release tag to install, e.g. v0.1.0 (default: latest) # BASE_URL override the release asset base URL directly # PREFIX install root for testing, e.g. PREFIX=$HOME/kestrel-test. # Binary goes to $PREFIX/bin; no root needed and NO # system service is installed. set -eu REPO="${KESTREL_REPO:-pingnestapp/kestrel}" VERSION="${VERSION:-latest}" if [ "$VERSION" = "latest" ]; then REL_BASE="https://github.com/${REPO}/releases/latest/download" else REL_BASE="https://github.com/${REPO}/releases/download/${VERSION}" fi BASE_URL="${BASE_URL:-$REL_BASE}" INSTALL_URL="https://raw.githubusercontent.com/${REPO}/main/install.sh" BIN="kestrel-sidecar" FRIGATE_URL="${KESTREL_FRIGATE_URL:-http://127.0.0.1:5000}" DATA_DIR="${KESTREL_DATA_DIR:-/var/lib/kestrel-sidecar}" PREFIX="${PREFIX:-}" log() { printf '\033[36m==>\033[0m %s\n' "$1"; } warn() { printf '\033[33mwarning:\033[0m %s\n' "$1" >&2; } die() { printf '\033[31merror:\033[0m %s\n' "$1" >&2; exit 1; } install_systemd() { log "Installing systemd service (kestrel-sidecar.service)" mkdir -p "$DATA_DIR" cat > /etc/systemd/system/kestrel-sidecar.service < /Library/LaunchDaemons/dev.kestrel.sidecar.plist < Labeldev.kestrel.sidecar ProgramArguments ${BIN_DIR}/${BIN} EnvironmentVariables KESTREL_FRIGATE_URL${FRIGATE_URL} KESTREL_DATA_DIR${DATA_DIR} StandardOutPath/var/log/kestrel-sidecar.log StandardErrorPath/var/log/kestrel-sidecar.log RunAtLoad KeepAlive EOF launchctl unload /Library/LaunchDaemons/dev.kestrel.sidecar.plist 2>/dev/null || true launchctl load /Library/LaunchDaemons/dev.kestrel.sidecar.plist } main() { if [ -n "$PREFIX" ]; then BIN_DIR="${PREFIX}/bin" log "PREFIX set — installing to ${BIN_DIR}; system service installation is SKIPPED" else BIN_DIR="/usr/local/bin" [ "$(id -u)" -eq 0 ] || die "please run as root: curl -fsSL ${INSTALL_URL} | sudo sh" fi OS="$(uname -s | tr '[:upper:]' '[:lower:]')" case "$OS" in linux|darwin) ;; *) die "unsupported OS: $OS (supported: linux, macOS; or use the Docker image)" ;; esac ARCH="$(uname -m)" case "$ARCH" in x86_64|amd64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; *) die "unsupported architecture: $ARCH (supported: amd64, arm64)" ;; esac log "Detected ${OS}/${ARCH}" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT ARCHIVE="${BIN}_${OS}_${ARCH}.tar.gz" log "Downloading ${BASE_URL}/${ARCHIVE}" curl -fsSL "${BASE_URL}/${ARCHIVE}" -o "${TMP}/${ARCHIVE}" || die "download failed" curl -fsSL "${BASE_URL}/checksums.txt" -o "${TMP}/checksums.txt" || die "checksum download failed" log "Verifying checksum" if command -v sha256sum >/dev/null 2>&1; then ( cd "$TMP" && grep " ${ARCHIVE}\$" checksums.txt | sha256sum -c - >/dev/null 2>&1 ) || die "checksum verification failed" elif command -v shasum >/dev/null 2>&1; then ( cd "$TMP" && grep " ${ARCHIVE}\$" checksums.txt | shasum -a 256 -c - >/dev/null 2>&1 ) || die "checksum verification failed" else die "neither sha256sum nor shasum found; cannot verify download" fi tar -xzf "${TMP}/${ARCHIVE}" -C "$TMP" mkdir -p "$BIN_DIR" install -m 0755 "${TMP}/${BIN}" "${BIN_DIR}/${BIN}" log "Installed ${BIN_DIR}/${BIN}" if [ -n "$PREFIX" ]; then log "Test install complete (no service). Run it manually:" printf ' KESTREL_FRIGATE_URL=%s KESTREL_DATA_DIR= %s/%s\n' "$FRIGATE_URL" "$BIN_DIR" "$BIN" return fi if [ "$OS" = "linux" ]; then if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then install_systemd LOG_HINT="journalctl -u kestrel-sidecar -f" else warn "systemd not detected; binary installed but no service was set up." warn "Run it under your init system with:" warn " KESTREL_FRIGATE_URL=${FRIGATE_URL} KESTREL_DATA_DIR=${DATA_DIR} ${BIN_DIR}/${BIN}" return fi else install_launchd LOG_HINT="tail -f /var/log/kestrel-sidecar.log" fi log "Sidecar installed and running." echo echo "Next steps:" echo " 1. Frigate URL is currently '${FRIGATE_URL}'. If that's wrong, edit the" echo " service config and restart (see comments inside the unit/plist)." echo " 2. Get the pairing QR / 6-digit code:" echo " ${LOG_HINT} # printed every 5 minutes while unpaired" echo " ${BIN} pair # or fetch it on demand" echo " 3. In the Kestrel app: Add server -> scan the QR (or enter the code +" echo " this machine's LAN IP, port 8790)." echo echo "Prefer Docker instead? Uninstall this service and run:" echo " docker run -d --name kestrel-sidecar --restart unless-stopped \\" echo " --network host -v kestrel-data:/data \\" echo " -e KESTREL_FRIGATE_URL=${FRIGATE_URL} kestrel-sidecar" echo # The service may take a moment to come up; poll briefly so the pairing # code is shown reliably instead of racing a slow start. i=0 while [ "$i" -lt 10 ]; do if "${BIN_DIR}/${BIN}" pair 2>/dev/null; then break fi i=$((i + 1)) sleep 1 done if [ "$i" -ge 10 ]; then warn "sidecar not ready yet; run '${BIN} pair' in a moment to display the pairing code" fi } main "$@"