#!/bin/bash # Officer — full host dependency setup # Run once on a fresh Ubuntu/Debian host before launching the server. # Usage: bash scripts/setup.sh set -e GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' ok() { echo -e " ${GREEN}✓${NC} $1"; } warn() { echo -e " ${YELLOW}!${NC} $1"; } fail() { echo -e " ${RED}✗${NC} $1"; } skip() { echo -e " - $1 (already installed)"; } has() { command -v "$1" &>/dev/null; } # ─── detect package manager ──────────────────────────────────────────────────── if has apt; then PM=apt elif has pacman; then PM=pacman elif has brew; then PM=brew else fail "No supported package manager found (apt, pacman, brew)" exit 1 fi install_pkg() { case $PM in apt) sudo apt install -y "$@" ;; pacman) sudo pacman -S --noconfirm "$@" ;; brew) brew install "$@" ;; esac } echo "" echo "═══════════════════════════════════════════" echo " Officer — dependency setup ($PM)" echo "═══════════════════════════════════════════" # ─── 1. core system packages ─────────────────────────────────────────────────── echo "" echo "── Core system packages ──" CORE_PKGS=() # git if has git; then skip "git"; else CORE_PKGS+=(git); fi # zip / unzip if has zip; then skip "zip"; else CORE_PKGS+=(zip); fi if has unzip; then skip "unzip"; else CORE_PKGS+=(unzip); fi # curl / wget (usually present but just in case) if has curl; then skip "curl"; else CORE_PKGS+=(curl); fi # psmisc (fuser) and procps (pgrep) if has fuser; then skip "fuser (psmisc)"; else case $PM in apt|pacman) CORE_PKGS+=(psmisc) ;; brew) skip "fuser (not needed on macOS)" ;; esac fi if has pgrep; then skip "pgrep (procps)"; else case $PM in apt) CORE_PKGS+=(procps) ;; pacman) CORE_PKGS+=(procps-ng) ;; brew) skip "pgrep (built-in on macOS)" ;; esac fi # script (bsdutils on apt, util-linux on pacman, built-in on macOS) if has script; then skip "script (bsdutils)"; else case $PM in apt) CORE_PKGS+=(bsdutils) ;; pacman) CORE_PKGS+=(util-linux) ;; brew) skip "script (built-in on macOS)" ;; esac fi if [ ${#CORE_PKGS[@]} -gt 0 ]; then install_pkg "${CORE_PKGS[@]}" ok "Installed: ${CORE_PKGS[*]}" fi # ─── 2. archive extras (optional but useful) ────────────────────────────────── echo "" echo "── Archive utilities (optional) ──" ARCHIVE_PKGS=() # p7zip if has 7z; then skip "7z (p7zip)"; else case $PM in apt) ARCHIVE_PKGS+=(p7zip-full) ;; pacman) ARCHIVE_PKGS+=(p7zip) ;; brew) ARCHIVE_PKGS+=(p7zip) ;; esac fi # unrar if has unrar; then skip "unrar"; else case $PM in apt) ARCHIVE_PKGS+=(unrar) ;; pacman) ARCHIVE_PKGS+=(unrar) ;; brew) ARCHIVE_PKGS+=(unrar) ;; esac fi if [ ${#ARCHIVE_PKGS[@]} -gt 0 ]; then install_pkg "${ARCHIVE_PKGS[@]}" || warn "Some archive packages may need non-free repos" ok "Installed: ${ARCHIVE_PKGS[*]}" fi # ─── 3. ffmpeg ───────────────────────────────────────────────────────────────── echo "" echo "── FFmpeg ──" if has ffmpeg; then skip "ffmpeg ($(ffmpeg -version 2>&1 | head -1 | awk '{print $3}'))" else install_pkg ffmpeg ok "ffmpeg installed" fi # ─── 4. docker ───────────────────────────────────────────────────────────────── echo "" echo "── Docker ──" if has docker; then skip "docker ($(docker --version 2>/dev/null | awk '{print $3}' | tr -d ','))" else case $PM in apt) warn "Installing docker.io from apt" sudo apt install -y docker.io sudo usermod -aG docker "$USER" warn "You may need to log out/in for docker group to take effect" ;; pacman) sudo pacman -S --noconfirm docker sudo systemctl enable --now docker sudo usermod -aG docker "$USER" ;; brew) warn "Install Docker Desktop from https://www.docker.com/products/docker-desktop" ;; esac if has docker; then ok "docker installed"; else warn "docker not found — install manually"; fi fi # ─── 5. Node.js 22 ──────────────────────────────────────────────────────────── echo "" echo "── Node.js 22 ──" if has node; then NODE_VER=$(node -v 2>/dev/null | tr -d 'v') NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1) if [ "$NODE_MAJOR" = "22" ]; then skip "node v$NODE_VER" else warn "Node $NODE_VER found but v22 is required" warn "Use nvm: nvm install 22 && nvm use 22" fi else warn "Node.js not found — install v22 via nvm:" warn " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash" warn " nvm install 22" fi # ─── 6. Bun ─────────────────────────────────────────────────────────────────── echo "" echo "── Bun ──" if has bun; then skip "bun ($(bun --version 2>/dev/null))" else curl -fsSL https://bun.sh/install | bash export BUN_INSTALL="$HOME/.bun" export PATH="$BUN_INSTALL/bin:$PATH" if has bun; then ok "bun installed"; else fail "bun install failed"; fi fi # ─── 7. Go ───────────────────────────────────────────────────────────────────── echo "" echo "── Go ──" if has go; then skip "go ($(go version 2>/dev/null | awk '{print $3}'))" else install_pkg golang-go 2>/dev/null || install_pkg go 2>/dev/null || install_pkg golang 2>/dev/null if has go; then ok "go installed"; else warn "go not found — install manually from https://go.dev/dl/"; fi fi # ─── 8. PulseAudio (headless audio for cliamp) ──────────────────────────────── echo "" echo "── PulseAudio (headless audio) ──" PULSE_PKGS=() if has pulseaudio; then skip "pulseaudio"; else case $PM in apt) PULSE_PKGS+=(pulseaudio) ;; pacman) PULSE_PKGS+=(pulseaudio) ;; brew) warn "PulseAudio: brew install pulseaudio (cliamp audio won't work without it)" ;; esac fi # pulseaudio-utils provides parec and pactl if has parec && has pactl; then skip "pulseaudio-utils (parec, pactl)"; else case $PM in apt) PULSE_PKGS+=(pulseaudio-utils) ;; pacman) ;; # included in pulseaudio package brew) ;; # included in pulseaudio formula esac fi # ALSA dev headers (needed to compile cliamp's Go audio library) case $PM in apt) if dpkg -s libasound2-dev &>/dev/null 2>&1; then skip "libasound2-dev"; else PULSE_PKGS+=(libasound2-dev); fi ;; pacman) if pacman -Qi alsa-lib &>/dev/null 2>&1; then skip "alsa-lib"; else PULSE_PKGS+=(alsa-lib); fi ;; brew) ;; # not needed on macOS esac if [ ${#PULSE_PKGS[@]} -gt 0 ]; then install_pkg "${PULSE_PKGS[@]}" ok "Installed: ${PULSE_PKGS[*]}" fi # ─── 9. cliamp (music player) ───────────────────────────────────────────────── echo "" echo "── cliamp ──" GOPATH_BIN="${GOPATH:-$HOME/go}/bin" export PATH="$GOPATH_BIN:$PATH" if has cliamp; then skip "cliamp ($(which cliamp))" else if ! has go; then warn "Go not installed — skipping cliamp build" else echo " Building cliamp from source..." TMPDIR=$(mktemp -d) git clone --depth=1 https://github.com/bjarneo/cliamp.git "$TMPDIR/cliamp" (cd "$TMPDIR/cliamp" && go install .) rm -rf "$TMPDIR" if [ -f "$GOPATH_BIN/cliamp" ]; then ok "cliamp installed at $GOPATH_BIN/cliamp" else fail "cliamp build failed" fi fi fi # ─── 10. yt-dlp (optional — video/audio download) ───────────────────────────── echo "" echo "── yt-dlp (optional) ──" if has yt-dlp; then skip "yt-dlp" else case $PM in apt) install_pkg yt-dlp 2>/dev/null && ok "yt-dlp installed" || warn "yt-dlp not in apt repos — install via pip or GitHub" ;; pacman) install_pkg yt-dlp && ok "yt-dlp installed" ;; brew) install_pkg yt-dlp && ok "yt-dlp installed" ;; esac fi # ─── 11. npm dependencies ───────────────────────────────────────────────────── echo "" echo "── npm global packages ──" if has npm; then if has pi; then skip "pi (@mariozechner/pi-coding-agent)" else npm install -g @mariozechner/pi-coding-agent if has pi; then ok "pi installed"; else warn "pi install failed"; fi fi else warn "npm not found — skipping global package installs" fi # ─── 12. bun install (project dependencies) ─────────────────────────────────── echo "" echo "── Project dependencies ──" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" if has bun && [ -f "$PROJECT_DIR/package.json" ]; then echo " Running bun install..." (cd "$PROJECT_DIR" && bun install) ok "Project dependencies installed" else warn "Skipping bun install (bun not found or not in project dir)" fi # ─── verification ───────────────────────────────────────────────────────────── echo "" echo "═══════════════════════════════════════════" echo " Verification" echo "═══════════════════════════════════════════" echo "" check() { if has "$1"; then ok "$1"; else fail "$1 — NOT FOUND"; fi } echo "Required:" check git check node check bun check npm check docker check ffmpeg check zip check script echo "" echo "Audio (cliamp):" check go check pulseaudio check parec check pactl check cliamp echo "" echo "Optional:" check unzip check 7z check unrar check pgrep check fuser check yt-dlp check pi echo "" echo "Installable from Settings UI:" for tool in claude opencode; do if has "$tool"; then ok "$tool"; else echo " - $tool (install from Settings > Applications)"; fi done echo "" echo "═══════════════════════════════════════════" echo " Setup complete!" echo "═══════════════════════════════════════════" echo "" echo "Notes:" echo " • PulseAudio null sink starts automatically with the server" echo " • Make sure ~/go/bin is in your PATH for cliamp" echo " • Claude, opencode, sharp, whisper-cpp, mlx-audio" echo " can be installed from the Settings > Applications page" echo ""