BREAKING: Officer now uses system Node.js via NodeSource instead of nvm. Changes: - Removed nvm sourcing from setup.sh - Updated Node installation to use NodeSource repository - All npm global packages installed system-wide with sudo - Updated PTY sidecar setup to use /usr/bin/node (system node) - Added Pi validation (test --list-models) - System packages now available to all users automatically Benefits: - Multi-user friendly: all users get same Node version - No per-user environment setup needed - Simpler troubleshooting (one node version) - Services use consistent node binary - Prevents snap node incompatibility issues Fixes: - 'node not found' for secondary users - systemd services finding correct node - Pi installation consistency across users New Files: - SETUP_GUIDE.md: Comprehensive installation guide - SETUP_ANALYSIS.md: Technical analysis of previous issues Migration: - Remove nvm if installed (optional) - Run: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - - Run: sudo apt-get install -y nodejs - Run: bash scripts/setup.sh
728 lines
22 KiB
Bash
Executable File
728 lines
22 KiB
Bash
Executable File
#!/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
|
|
if has curl; then skip "curl"; else CORE_PKGS+=(curl); fi
|
|
if has wget; then skip "wget"; else CORE_PKGS+=(wget); fi
|
|
|
|
# zsh
|
|
if has zsh; then skip "zsh"; else CORE_PKGS+=(zsh); 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
|
|
|
|
# build tools (make, gcc, g++) — needed for native npm modules like node-pty
|
|
if has make && has gcc; then skip "build tools (make, gcc, g++)"; else
|
|
case $PM in
|
|
apt) CORE_PKGS+=(build-essential) ;;
|
|
pacman) CORE_PKGS+=(base-devel) ;;
|
|
brew) warn "Install Xcode command line tools: xcode-select --install" ;;
|
|
esac
|
|
fi
|
|
|
|
# python3 + pip + venv
|
|
if has python3; then skip "python3"; else
|
|
case $PM in
|
|
apt) CORE_PKGS+=(python3 python3-pip python3-venv) ;;
|
|
pacman) CORE_PKGS+=(python python-pip) ;;
|
|
brew) CORE_PKGS+=(python3) ;;
|
|
esac
|
|
fi
|
|
# ensure pip/venv even if python3 already exists (apt splits them)
|
|
if has python3 && [ "$PM" = "apt" ]; then
|
|
if ! dpkg -s python3-pip &>/dev/null 2>&1; then CORE_PKGS+=(python3-pip); fi
|
|
if ! dpkg -s python3-venv &>/dev/null 2>&1; then CORE_PKGS+=(python3-venv); fi
|
|
fi
|
|
|
|
# shell utilities
|
|
for tool in tree btop tmux jq htop lsof duf; do
|
|
if has "$tool"; then skip "$tool"; else CORE_PKGS+=("$tool"); fi
|
|
done
|
|
|
|
# sqlite3
|
|
if has sqlite3; then skip "sqlite3"; else
|
|
case $PM in
|
|
apt) CORE_PKGS+=(sqlite3) ;;
|
|
pacman) CORE_PKGS+=(sqlite) ;;
|
|
brew) CORE_PKGS+=(sqlite) ;;
|
|
esac
|
|
fi
|
|
|
|
# ripgrep
|
|
if has rg; then skip "ripgrep"; else
|
|
case $PM in
|
|
apt) CORE_PKGS+=(ripgrep) ;;
|
|
pacman) CORE_PKGS+=(ripgrep) ;;
|
|
brew) CORE_PKGS+=(ripgrep) ;;
|
|
esac
|
|
fi
|
|
|
|
# fd-find
|
|
if has fd || has fdfind; then skip "fd-find"; else
|
|
case $PM in
|
|
apt) CORE_PKGS+=(fd-find) ;;
|
|
pacman) CORE_PKGS+=(fd) ;;
|
|
brew) CORE_PKGS+=(fd) ;;
|
|
esac
|
|
fi
|
|
|
|
# net-tools, less, file, man-db
|
|
case $PM in
|
|
apt)
|
|
for pkg in net-tools less file man-db; do
|
|
if dpkg -s "$pkg" &>/dev/null 2>&1; then skip "$pkg"; else CORE_PKGS+=("$pkg"); fi
|
|
done
|
|
;;
|
|
pacman)
|
|
for pkg in net-tools less file man-db; do
|
|
if pacman -Qi "$pkg" &>/dev/null 2>&1; then skip "$pkg"; else CORE_PKGS+=("$pkg"); fi
|
|
done
|
|
;;
|
|
brew)
|
|
skip "net-tools, less, file, man (built-in on macOS)"
|
|
;;
|
|
esac
|
|
|
|
# locales
|
|
case $PM in
|
|
apt)
|
|
if dpkg -s locales &>/dev/null 2>&1; then skip "locales"; else CORE_PKGS+=(locales); fi
|
|
;;
|
|
esac
|
|
|
|
# ca-certificates
|
|
case $PM in
|
|
apt)
|
|
if dpkg -s ca-certificates &>/dev/null 2>&1; then skip "ca-certificates"; else CORE_PKGS+=(ca-certificates); fi
|
|
;;
|
|
esac
|
|
|
|
if [ ${#CORE_PKGS[@]} -gt 0 ]; then
|
|
install_pkg "${CORE_PKGS[@]}"
|
|
ok "Installed: ${CORE_PKGS[*]}"
|
|
fi
|
|
|
|
# locale generation (ensure en_US.UTF-8)
|
|
case $PM in
|
|
apt)
|
|
if ! locale -a 2>/dev/null | grep -q "en_US.utf8"; then
|
|
sudo sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen
|
|
sudo locale-gen
|
|
ok "Generated en_US.UTF-8 locale"
|
|
else
|
|
skip "en_US.UTF-8 locale"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# symlink fdfind → fd (apt installs as fdfind)
|
|
if has fdfind && ! has fd; then
|
|
sudo ln -sf "$(which fdfind)" /usr/local/bin/fd
|
|
ok "Symlinked fdfind → fd"
|
|
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. sudoers for officer service user ──────────────────────────────────────
|
|
echo ""
|
|
echo "── Sudoers (Linux user isolation) ──"
|
|
|
|
SERVICE_USER="$(whoami)"
|
|
SUDOERS_FILE="/etc/sudoers.d/officer-service"
|
|
|
|
if [ -f "$SUDOERS_FILE" ] && grep -q "$SERVICE_USER" "$SUDOERS_FILE" 2>/dev/null; then
|
|
skip "sudoers entry for $SERVICE_USER"
|
|
else
|
|
case $PM in
|
|
apt|pacman)
|
|
echo "$SERVICE_USER ALL=(ALL) NOPASSWD: ALL" | sudo tee "$SUDOERS_FILE" > /dev/null
|
|
sudo chmod 0440 "$SUDOERS_FILE"
|
|
ok "Created sudoers entry for $SERVICE_USER at $SUDOERS_FILE"
|
|
;;
|
|
brew)
|
|
warn "Sudoers setup is Linux-only — skipping on macOS"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# ─── 5. Node.js 22 ────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "── Node.js 22 (system-wide) ──"
|
|
|
|
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 (system-wide)"
|
|
else
|
|
warn "Node $NODE_VER found but v22 is required — upgrading via NodeSource..."
|
|
case $PM in
|
|
apt)
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
sudo apt-get install -y nodejs
|
|
if has node && [ "$(node -v | cut -d. -f1 | tr -d 'v')" = "22" ]; then
|
|
ok "Upgraded to node v$(node -v)"
|
|
else
|
|
fail "Node.js upgrade failed"
|
|
fi
|
|
;;
|
|
*)
|
|
warn "Manual upgrade needed for $PM: install Node.js 22 from official sources"
|
|
;;
|
|
esac
|
|
fi
|
|
else
|
|
warn "Node.js not found — installing v22 via NodeSource..."
|
|
case $PM in
|
|
apt)
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
sudo apt-get install -y nodejs
|
|
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; fi
|
|
;;
|
|
pacman)
|
|
install_pkg nodejs npm
|
|
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; fi
|
|
;;
|
|
brew)
|
|
install_pkg node
|
|
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# ─── npm global packages (system-wide) ─────────────────────────────────────────
|
|
# Install npm global packages to system /usr/local so all users can access them.
|
|
# This is safer for multi-user production setups.
|
|
echo ""
|
|
echo "── npm global packages (system-wide) ──"
|
|
|
|
if ! has npm; then
|
|
warn "npm not found — skipping global package installs"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify npm is pointing to system node (not user-local nvm)
|
|
NPM_BIN=$(which npm)
|
|
if echo "$NPM_BIN" | grep -q '.nvm'; then
|
|
warn "npm is from nvm ($NPM_BIN), but setup.sh expects system npm"
|
|
warn "This is fine — packages will be installed to your nvm, but won't be available to other users"
|
|
warn "For production: all users should use the same system Node.js (no nvm)"
|
|
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 ──"
|
|
|
|
GOLANG_VERSION=1.23.6
|
|
|
|
if has go; then
|
|
skip "go ($(go version 2>/dev/null | awk '{print $3}'))"
|
|
else
|
|
case $PM in
|
|
apt|pacman)
|
|
echo " Installing Go ${GOLANG_VERSION} from official tarball..."
|
|
ARCH=$(uname -m)
|
|
case $ARCH in
|
|
x86_64) GO_ARCH=amd64 ;;
|
|
aarch64) GO_ARCH=arm64 ;;
|
|
*) GO_ARCH=amd64 ;;
|
|
esac
|
|
curl -fsSL "https://go.dev/dl/go${GOLANG_VERSION}.linux-${GO_ARCH}.tar.gz" -o /tmp/go.tar.gz
|
|
sudo tar -C /usr/local -xzf /tmp/go.tar.gz
|
|
rm /tmp/go.tar.gz
|
|
export PATH="/usr/local/go/bin:$PATH"
|
|
;;
|
|
brew)
|
|
brew install go
|
|
;;
|
|
esac
|
|
if has go; then ok "go installed"; else warn "go not found — install manually from https://go.dev/dl/"; fi
|
|
fi
|
|
|
|
# ─── 8. Rust ──────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "── Rust ──"
|
|
|
|
if has rustc && has cargo; then
|
|
skip "rust ($(rustc --version 2>/dev/null | awk '{print $2}'))"
|
|
else
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
if has rustc; then ok "rust installed"; else warn "rust install failed"; fi
|
|
fi
|
|
|
|
# ─── 9. 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
|
|
|
|
# ─── 10. 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
|
|
|
|
# ─── 11. Neovim ──────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "── Neovim ──"
|
|
|
|
if has nvim; then
|
|
skip "neovim ($(nvim --version 2>/dev/null | head -1))"
|
|
else
|
|
case $PM in
|
|
apt)
|
|
echo " Installing Neovim from GitHub releases..."
|
|
ARCH=$(uname -m)
|
|
case $ARCH in
|
|
x86_64) NVIM_ARCH=x86_64 ;;
|
|
aarch64) NVIM_ARCH=aarch64 ;;
|
|
*) NVIM_ARCH=x86_64 ;;
|
|
esac
|
|
curl -fsSL "https://github.com/neovim/neovim/releases/latest/download/nvim-linux-${NVIM_ARCH}.tar.gz" -o /tmp/nvim.tar.gz
|
|
sudo tar -C /opt -xzf /tmp/nvim.tar.gz
|
|
sudo ln -sf "/opt/nvim-linux-${NVIM_ARCH}/bin/nvim" /usr/local/bin/nvim
|
|
rm /tmp/nvim.tar.gz
|
|
;;
|
|
pacman) install_pkg neovim ;;
|
|
brew) install_pkg neovim ;;
|
|
esac
|
|
if has nvim; then ok "neovim installed"; else warn "neovim install failed"; fi
|
|
fi
|
|
|
|
# LazyVim starter config
|
|
if [ -d "$HOME/.config/nvim" ]; then
|
|
skip "nvim config (already exists at ~/.config/nvim)"
|
|
else
|
|
echo " Installing LazyVim starter config..."
|
|
git clone --depth 1 https://github.com/LazyVim/starter "$HOME/.config/nvim"
|
|
rm -rf "$HOME/.config/nvim/.git"
|
|
ok "LazyVim starter installed at ~/.config/nvim"
|
|
fi
|
|
|
|
# ─── 12. Terminal tools ──────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "── Terminal tools (starship, oh-my-zsh, eza, lazygit) ──"
|
|
|
|
# Starship prompt
|
|
if has starship; then
|
|
skip "starship"
|
|
else
|
|
curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b /usr/local/bin
|
|
if has starship; then ok "starship installed"; else warn "starship install failed"; fi
|
|
fi
|
|
|
|
# Oh-My-Zsh
|
|
if [ -d "$HOME/.oh-my-zsh" ]; then
|
|
skip "oh-my-zsh (already at ~/.oh-my-zsh)"
|
|
else
|
|
git clone --depth 1 https://github.com/ohmyzsh/ohmyzsh.git "$HOME/.oh-my-zsh"
|
|
ok "oh-my-zsh installed at ~/.oh-my-zsh"
|
|
fi
|
|
|
|
# eza
|
|
EZA_VERSION=0.18.15
|
|
if has eza; then
|
|
skip "eza"
|
|
else
|
|
case $PM in
|
|
apt)
|
|
ARCH=$(uname -m)
|
|
case $ARCH in
|
|
x86_64) EZA_ARCH=x86_64 ;;
|
|
aarch64) EZA_ARCH=aarch64 ;;
|
|
*) EZA_ARCH=x86_64 ;;
|
|
esac
|
|
curl -fsSL "https://github.com/eza-community/eza/releases/download/v${EZA_VERSION}/eza_${EZA_ARCH}-unknown-linux-gnu.tar.gz" -o /tmp/eza.tar.gz
|
|
tar -xzf /tmp/eza.tar.gz -C /tmp
|
|
sudo mv /tmp/eza /usr/local/bin/eza
|
|
sudo chmod +x /usr/local/bin/eza
|
|
rm -rf /tmp/eza.tar.gz
|
|
;;
|
|
pacman) install_pkg eza ;;
|
|
brew) install_pkg eza ;;
|
|
esac
|
|
if has eza; then ok "eza installed"; else warn "eza install failed"; fi
|
|
fi
|
|
|
|
# lazygit
|
|
LAZYGIT_VERSION=0.44.1
|
|
if has lazygit; then
|
|
skip "lazygit"
|
|
else
|
|
case $PM in
|
|
apt)
|
|
ARCH=$(uname -m)
|
|
case $ARCH in
|
|
x86_64) LG_ARCH=x86_64 ;;
|
|
aarch64) LG_ARCH=arm64 ;;
|
|
*) LG_ARCH=x86_64 ;;
|
|
esac
|
|
curl -fsSL "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_${LG_ARCH}.tar.gz" -o /tmp/lazygit.tar.gz
|
|
tar -xzf /tmp/lazygit.tar.gz -C /tmp
|
|
sudo mv /tmp/lazygit /usr/local/bin/lazygit
|
|
sudo chmod +x /usr/local/bin/lazygit
|
|
rm -rf /tmp/lazygit.tar.gz /tmp/LICENSE /tmp/README.md
|
|
;;
|
|
pacman) install_pkg lazygit ;;
|
|
brew) install_pkg lazygit ;;
|
|
esac
|
|
if has lazygit; then ok "lazygit installed"; else warn "lazygit install failed"; fi
|
|
fi
|
|
|
|
# ─── 13. 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
|
|
|
|
# ─── 14. npm global packages (system-wide) ──────────────────────────────────
|
|
echo ""
|
|
echo "── npm global packages (system-wide) ──"
|
|
|
|
if ! has npm; then
|
|
warn "npm not found — skipping global package installs"
|
|
else
|
|
# Pi (coding agent)
|
|
if has pi; then
|
|
skip "pi (@mariozechner/pi-coding-agent)"
|
|
else
|
|
echo " Installing pi..."
|
|
sudo npm install -g @mariozechner/pi-coding-agent
|
|
if has pi; then ok "pi installed"; else warn "pi install failed"; fi
|
|
fi
|
|
|
|
# Validate Pi works
|
|
if has pi; then
|
|
if pi --list-models > /dev/null 2>&1; then
|
|
ok "pi validated (--list-models works)"
|
|
else
|
|
warn "pi installed but --list-models failed — check API keys in ~/.pi/agent/auth.json"
|
|
fi
|
|
fi
|
|
|
|
# Fix ~/.pi ownership if needed (in case of mixed user/sudo installs)
|
|
if [ -d "$HOME/.pi" ]; then
|
|
if find "$HOME/.pi" -not -user "$USER" -print -quit 2>/dev/null | grep -q .; then
|
|
sudo chown -R "$USER:$(id -gn)" "$HOME/.pi"
|
|
ok "Fixed ~/.pi ownership to $USER"
|
|
fi
|
|
fi
|
|
|
|
# Claude Code
|
|
if has claude; then
|
|
skip "claude (@anthropic-ai/claude-code)"
|
|
else
|
|
echo " Installing claude-code..."
|
|
sudo npm install -g @anthropic-ai/claude-code
|
|
if has claude; then ok "claude-code installed"; else warn "claude-code install failed"; fi
|
|
fi
|
|
|
|
# pm2 (process manager) — now optional since we use systemd services
|
|
if has pm2; then
|
|
skip "pm2 (optional, using systemd services instead)"
|
|
else
|
|
warn "pm2 optional (Officer uses systemd services for main app and sidecar)"
|
|
fi
|
|
else
|
|
warn "npm not found — skipping global package installs"
|
|
fi
|
|
|
|
# ─── 15. 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
|
|
|
|
# ─── 16. remote desktop (XFCE + VNC) ────────────────────────────────────────
|
|
echo ""
|
|
echo "── Remote Desktop (XFCE + VNC) ──"
|
|
|
|
if systemctl is-active --quiet officer-vnc 2>/dev/null; then
|
|
skip "remote desktop (officer-vnc service already running)"
|
|
else
|
|
case $PM in
|
|
apt)
|
|
bash "$SCRIPT_DIR/setup-desktop.sh"
|
|
;;
|
|
*)
|
|
warn "Remote desktop setup is Ubuntu/Debian only — skipping"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# ─── 17. PTY sidecar (systemd service) ──────────────────────────────────────
|
|
bash "$SCRIPT_DIR/setup-pty-sidecar.sh"
|
|
|
|
# ─── 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 ffmpeg
|
|
check zip
|
|
check script
|
|
check python3
|
|
check make
|
|
check gcc
|
|
|
|
echo ""
|
|
echo "Dev tools:"
|
|
check go
|
|
check rustc
|
|
check cargo
|
|
check nvim
|
|
check zsh
|
|
check starship
|
|
check lazygit
|
|
check eza
|
|
check rg
|
|
check fd
|
|
check jq
|
|
check htop
|
|
check tmux
|
|
check tree
|
|
check btop
|
|
check sqlite3
|
|
|
|
echo ""
|
|
echo "Audio (cliamp):"
|
|
check pulseaudio
|
|
check parec
|
|
check pactl
|
|
check cliamp
|
|
|
|
echo ""
|
|
echo "AI agents:"
|
|
check pi
|
|
check claude
|
|
check pm2
|
|
|
|
echo ""
|
|
echo "Optional:"
|
|
check unzip
|
|
check 7z
|
|
check unrar
|
|
check pgrep
|
|
check fuser
|
|
check yt-dlp
|
|
|
|
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 " • Make sure ~/.cargo/bin is in your PATH for Rust tools"
|
|
echo " • sharp, whisper-cpp, mlx-audio can be installed from Settings > Applications"
|
|
echo ""
|