#!/usr/bin/env bash # # teslausb-companion installer. # # Run on a TeslaUSB Pi as root: # curl -sSf https://teslausb.home.etma.dk | bash # # Optional env vars: # BASE_URL fetch assets from this host (default: https://teslausb.home.etma.dk) # COMPANION_UNATTENDED set to 1 to skip the interactive confirmation # set -euo pipefail BASE_URL="${BASE_URL:-https://teslausb.home.etma.dk}" DATA_DIR="/mutable/companion" BIN_PATH="${DATA_DIR}/bin/teslausb-companion" FFMPEG_DIR="/backingfiles/companion-bin" # static binaries on the big xfs partition TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } # --- 1. Sanity checks -------------------------------------------------------- [[ $EUID -eq 0 ]] || die "must run as root (use 'ssh root@ ...' or sudo)" [[ -x /root/bin/remountfs_rw ]] || die "this doesn't look like a TeslaUSB Pi (missing /root/bin/remountfs_rw)" [[ -d /mutable ]] || die "missing /mutable — TeslaUSB partition layout not detected" [[ -d /backingfiles ]] || die "missing /backingfiles — TeslaUSB partition layout not detected" for tool in curl awk sha256sum systemctl nginx install; do command -v "$tool" >/dev/null || die "missing required tool: $tool" done # --- 2. Detect architecture -------------------------------------------------- ARCH=$(uname -m) case "$ARCH" in armv6l|armv7l) BIN_ASSET="teslausb-companion-armv6" ;; aarch64) die "aarch64 not yet supported (TeslaUSB image is 32-bit). File an issue if you've moved to a 64-bit teslausb image." ;; *) die "unsupported architecture: $ARCH" ;; esac # --- 3. Show plan and confirm ------------------------------------------------ log "fetching version…" VERSION=$(curl -sSf "$BASE_URL/version" || echo "unknown") log "teslausb-companion installer" log " source: $BASE_URL" log " version: $VERSION" log " architecture: $ARCH → $BIN_ASSET" log " install to: $BIN_PATH" log " service: teslausb-companion.service (systemd)" log " reverse proxy: nginx /companion/ (basic-auth via existing teslausb htpasswd)" if [[ "${COMPANION_UNATTENDED:-0}" != 1 && -t 0 ]]; then read -rp "continue? [y/N] " ans [[ "$ans" =~ ^[Yy]$ ]] || die "aborted" fi # --- 4. Download + verify ---------------------------------------------------- log "downloading assets…" curl -sSfL "$BASE_URL/sha256sums" -o "$TMP_DIR/sha256sums" curl -sSfL "$BASE_URL/$BIN_ASSET" -o "$TMP_DIR/teslausb-companion" curl -sSfL "$BASE_URL/teslausb-companion.service" -o "$TMP_DIR/teslausb-companion.service" curl -sSfL "$BASE_URL/nginx-companion.conf" -o "$TMP_DIR/nginx-companion.conf" curl -sSfL "$BASE_URL/ffmpeg" -o "$TMP_DIR/ffmpeg" curl -sSfL "$BASE_URL/ffprobe" -o "$TMP_DIR/ffprobe" log "verifying checksums…" ( cd "$TMP_DIR" # sha256sums file lists assets under their served names; rewrite the binary # entry to match the local filename so sha256sum -c can verify it. grep -E "($BIN_ASSET|teslausb-companion\.service|nginx-companion\.conf|ffmpeg|ffprobe)\$" sha256sums \ | sed "s| $BIN_ASSET\$| teslausb-companion|" \ | sha256sum -c --status ) || die "checksum mismatch — refusing to install" # --- 5. Drop assets into /mutable ------------------------------------------- log "installing binary to $BIN_PATH" mkdir -p "$DATA_DIR/bin" "$DATA_DIR/state" # Stage + atomically rename so an upgrade-in-place doesn't hit ETXTBSY against # the currently-running binary. rename(2) swaps the path; the live process # keeps the old inode until it exits. install -m 0755 "$TMP_DIR/teslausb-companion" "$BIN_PATH.new" mv -f "$BIN_PATH.new" "$BIN_PATH" install -m 0644 "$TMP_DIR/teslausb-companion.service" "$DATA_DIR/teslausb-companion.service" install -m 0644 "$TMP_DIR/nginx-companion.conf" "$DATA_DIR/nginx-companion.conf" log "installing static ffmpeg + ffprobe to $FFMPEG_DIR (on /backingfiles)" # Static binaries — no Mesa/LLVM deps, don't touch the tiny root partition. mkdir -p "$FFMPEG_DIR" install -m 0755 "$TMP_DIR/ffmpeg" "$FFMPEG_DIR/ffmpeg.new" && mv -f "$FFMPEG_DIR/ffmpeg.new" "$FFMPEG_DIR/ffmpeg" install -m 0755 "$TMP_DIR/ffprobe" "$FFMPEG_DIR/ffprobe.new" && mv -f "$FFMPEG_DIR/ffprobe.new" "$FFMPEG_DIR/ffprobe" # --- 6. Briefly remount / RW to drop the root-fs pieces --------------------- log "remounting / read-write" /root/bin/remountfs_rw log "installing systemd unit" install -m 0644 "$DATA_DIR/teslausb-companion.service" /etc/systemd/system/teslausb-companion.service log "wiring nginx include" install -m 0644 "$DATA_DIR/nginx-companion.conf" /etc/nginx/companion-location.conf NGINX_SITE_LINK=$(find /etc/nginx/sites-enabled -maxdepth 1 \( -type f -o -type l \) | head -1) [[ -n "$NGINX_SITE_LINK" ]] || die "no nginx site found in /etc/nginx/sites-enabled" # Edit the symlink TARGET, not the symlink itself — otherwise the mv below # replaces the symlink with a regular file. On TeslaUSB, sites-enabled/default # is a symlink to sites-available/teslausb.nginx. NGINX_SITE=$(readlink -f "$NGINX_SITE_LINK") if ! grep -q "# teslausb-companion" "$NGINX_SITE"; then # Insert the include before the closing '}' of the first top-level block. awk ' /^}[[:space:]]*$/ && !done { print " include /etc/nginx/companion-location.conf; # teslausb-companion" done = 1 } { print } ' "$NGINX_SITE" > "$NGINX_SITE.new" mv "$NGINX_SITE.new" "$NGINX_SITE" fi nginx -t if [[ -x /root/bin/remountfs_ro ]]; then log "remounting / read-only" /root/bin/remountfs_ro else warn "/root/bin/remountfs_ro not present — leaving / in its current mount state" fi # --- 7. Start / reload + smoke test ----------------------------------------- log "starting services" systemctl daemon-reload systemctl enable teslausb-companion # Use restart (not enable --now) so a re-install actually loads the new binary # instead of leaving the previous process serving the old one. systemctl restart teslausb-companion systemctl reload nginx # Kick archiveloop so the cam-disk mount happens promptly — companion's # first-run logic set the pending flag, so the next mount window installs the # default (silent) chime onto the cam disk. if [[ -x /root/bin/force_sync.sh ]]; then log "kicking force_sync.sh so the silent chime lands on the cam disk soon" /root/bin/force_sync.sh >/dev/null 2>&1 & fi log "verifying health endpoint" sleep 1 if curl -sSf http://127.0.0.1:8472/companion/health >/dev/null; then log "✓ teslausb-companion is running ($VERSION)" log "" log " Local: http://127.0.0.1:8472/companion/health" log " Through nginx (basic-auth): http://$(hostname).local/companion/health" else die "health endpoint did not respond — check 'journalctl -u teslausb-companion -e'" fi