#!/bin/sh # muxdeck installer # # curl -fsSL https://raw.githubusercontent.com/josecarlosrivas/muxdeck/main/install.sh | sh # # Environment overrides (all optional): # MUXDECK_BIN_DIR install location (default /usr/local/bin) # MUXDECK_MODE run | service | cloud | binary | update (default: ask on a TTY, else binary) # MUXDECK_PORT port for service/cloud mode (default 8300) # MUXDECK_CLOUD_URL account server for cloud mode (default https://cloud.muxdeck.app) set -eu REPO="josecarlosrivas/muxdeck" BIN_DIR="${MUXDECK_BIN_DIR:-/usr/local/bin}" MODE="${MUXDECK_MODE:-}" PORT="${MUXDECK_PORT:-8300}" CLOUD_URL="${MUXDECK_CLOUD_URL:-https://cloud.muxdeck.app}" say() { printf '%s\n' "$*"; } err() { printf 'muxdeck install: %s\n' "$*" >&2; exit 1; } # --- platform detection --- os=$(uname -s); arch=$(uname -m) case "$os" in Linux) os=linux ;; Darwin) os=darwin ;; *) err "unsupported OS: $os (linux and macOS only)" ;; esac case "$arch" in x86_64|amd64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; *) err "unsupported architecture: $arch" ;; esac # --- tmux check --- if command -v tmux >/dev/null 2>&1; then say "+ tmux found: $(tmux -V)" else say "! tmux is NOT installed — muxdeck requires it." case "$os" in linux) say " install it first, e.g.: sudo apt install tmux (or dnf / pacman / apk)" ;; darwin) say " install it first, e.g.: brew install tmux" ;; esac say " continuing — muxdeck will work as soon as tmux is present." fi # --- download --- asset="muxdeck-$os-$arch" tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT say "+ downloading $asset (latest release)" if ! curl -fsSL -o "$tmp/muxdeck" "https://github.com/$REPO/releases/latest/download/$asset"; then if command -v gh >/dev/null 2>&1; then say " direct download failed, retrying via gh" gh release download -R "$REPO" -p "$asset" -O "$tmp/muxdeck" || err "download failed" else err "download failed (https://github.com/$REPO/releases)" fi fi chmod +x "$tmp/muxdeck" # --- install --- prev_version=$("$BIN_DIR/muxdeck" -version 2>/dev/null || true) # BIN_DIR may not exist (fresh macOS has no /usr/local/bin); install(1) won't create it. if [ ! -d "$BIN_DIR" ]; then say "+ creating $BIN_DIR" mkdir -p "$BIN_DIR" 2>/dev/null || sudo mkdir -p "$BIN_DIR" fi if [ -w "$BIN_DIR" ]; then install -m 755 "$tmp/muxdeck" "$BIN_DIR/muxdeck" else say "+ installing to $BIN_DIR (needs sudo)" sudo install -m 755 "$tmp/muxdeck" "$BIN_DIR/muxdeck" fi say "+ installed: $("$BIN_DIR/muxdeck" -version 2>/dev/null || echo "muxdeck (pre-0.3 release)") at $BIN_DIR/muxdeck" # --- mode selection --- if [ -z "$MODE" ] && [ -r /dev/tty ]; then say "" say "How do you want to run muxdeck?" say " 1) Try it now — foreground on http://127.0.0.1:$PORT, ^C to stop" say " 2) Service — starts at boot, listens on all interfaces, token-protected" say " 3) Cloud — starts at boot on loopback, reachable through your muxdeck cloud relay" say " 4) Nothing — just the binary, I'll run it myself" if [ -n "$prev_version" ]; then say " 5) Update — you had $prev_version; keep its setup, restart the service" fi printf 'Choose [1-5, default 1]: ' read -r choice [token] — systemd unit or launchd agent, # running as the invoking user, starting at boot. install_service() { addr=$1; svc_token=${2:-} env_line="MUXDECK_ADDR=$addr" [ -n "$svc_token" ] && env_line="$env_line MUXDECK_TOKEN=$svc_token" if [ "$os" = linux ]; then command -v systemctl >/dev/null 2>&1 || err "service mode needs systemd on Linux" unit=/etc/systemd/system/muxdeck.service [ -e "$unit" ] && say "! $unit already exists — overwriting" sudo tee "$unit" >/dev/null <' printf '%s\n' '' printf '%s\n' '' printf '%s\n' ' Labelcom.muxdeck.agent' printf ' ProgramArguments%s/muxdeck\n' "$BIN_DIR" printf '%s\n' ' EnvironmentVariables' printf ' MUXDECK_ADDR%s\n' "$addr" [ -n "$svc_token" ] && printf ' MUXDECK_TOKEN%s\n' "$svc_token" printf '%s\n' ' ' printf '%s\n' ' RunAtLoad' printf '%s\n' ' KeepAliveSuccessfulExit' printf '%s\n' '' } >"$plist" launchctl bootout "gui/$(id -u)/com.muxdeck.agent" 2>/dev/null || true launchctl bootstrap "gui/$(id -u)" "$plist" say "+ launchd agent 'com.muxdeck.agent' is running" fi } case "$MODE" in binary) say "" say "Run 'muxdeck' for a local instance, 'muxdeck -h' for all options." say "Binding beyond localhost (-addr 0.0.0.0:$PORT) auto-generates an access" say "code on startup; use -token for a fixed secret or -no-auth to disable." ;; run) say "" exec "$BIN_DIR/muxdeck" -addr "127.0.0.1:$PORT" ;; service) token=$(gen_token) install_service "0.0.0.0:$PORT" "$token" say "" say "Access token: $token (kept in the service definition; change it there)" say "Listening on port $PORT on all interfaces. Likely addresses:" if [ "$os" = linux ]; then hostname -I 2>/dev/null | tr ' ' '\n' | sed "/^$/d; s|^| http://|; s|\$|:$PORT|" || true else for ifc in en0 en1; do ipconfig getifaddr "$ifc" 2>/dev/null || true; done \ | sed "s|^| http://|; s|\$|:$PORT|" fi say "" say "How you expose it is up to you: keep it LAN/VPN-only, front it with a" say "reverse proxy, or use built-in TLS (-tls-cert/-tls-key). HTTPS is required" say "for installing the PWA on phones/tablets." ;; update) new_version=$("$BIN_DIR/muxdeck" -version 2>/dev/null || echo "muxdeck (unknown)") say "" if [ -z "$prev_version" ]; then say "No previous install was found — this was a fresh install of $new_version." say "Re-run the installer to pick a run mode, or just run 'muxdeck'." elif [ "$prev_version" = "$new_version" ]; then say "Already current: $new_version (binary refreshed in place)." else say "Updated: $prev_version -> $new_version." fi # The binary is swapped; a running service keeps the old one until # restarted. Restart it where we can tell which shape this machine has. restart_cmd="" if [ "$os" = linux ]; then if systemctl is-enabled muxdeck >/dev/null 2>&1; then restart_cmd="sudo systemctl restart muxdeck" elif systemctl --user is-enabled muxdeck >/dev/null 2>&1; then restart_cmd="systemctl --user restart muxdeck" fi else if launchctl print "gui/$(id -u)/com.muxdeck.agent" >/dev/null 2>&1; then restart_cmd="launchctl kickstart -k gui/$(id -u)/com.muxdeck.agent" fi fi if [ -n "$restart_cmd" ]; then printf 'Restart the muxdeck service now? [Y/n]: ' if read -r yn 2>/dev/null /dev/null; do i=$((i+1)); [ "$i" -ge 30 ] && err "daemon did not come up on 127.0.0.1:$PORT" sleep 1 done say "" # The CLI's own after-claim instructions are for manual setups; keep its # claim-code line and speak the cloud flow ourselves. setup_out=$(MUXDECK_URL="http://127.0.0.1:$PORT" "$BIN_DIR/muxdeck" relay setup "$CLOUD_URL" 2>&1) || { printf '%s\n' "$setup_out"; err "relay setup failed" } printf '%s\n' "$setup_out" | grep -i "claim code" || printf '%s\n' "$setup_out" say "" say "Claim this machine: open $CLOUD_URL, sign in, and enter the code" say "under Daemons. The installer finishes the connection for you." if [ -r /dev/tty ]; then printf 'Press Enter once you have claimed the code ... ' read -r _