- Mask sleep.target, suspend.target, hibernate.target, hybrid-sleep.target - Configure systemd-logind to: - Ignore lid switch events - Ignore power key presses - Disable idle action - Set runtime directory size - Restart systemd-logind to apply changes Servers running Officer shouldn't go to sleep when idle. This prevents unexpected suspends during setup or normal operation.
777 lines
24 KiB
Bash
Executable File
777 lines
24 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
|
|
|
|
# ─── 4b. Disable auto-suspend (server doesn't need to sleep) ──────────────────
|
|
echo ""
|
|
echo "── Auto-suspend (disable for server) ──"
|
|
|
|
# Disable system suspend/hibernate
|
|
if systemctl is-enabled sleep.target 2>/dev/null | grep -q "enabled\|masked"; then
|
|
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
|
|
ok "Masked sleep/suspend targets"
|
|
else
|
|
skip "sleep targets already masked"
|
|
fi
|
|
|
|
# Configure logind to ignore idle
|
|
sudo tee /etc/systemd/logind.conf > /dev/null << 'LOGIND_EOF'
|
|
[Login]
|
|
HandleLidSwitch=ignore
|
|
HandleLidSwitchExternalPower=ignore
|
|
HandlePowerKey=ignore
|
|
IdleAction=none
|
|
RuntimeDirectorySize=10%
|
|
LOGIND_EOF
|
|
|
|
sudo systemctl restart systemd-logind
|
|
ok "Configured logind to disable auto-suspend"
|
|
|
|
# ─── 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..."
|
|
case $PM in
|
|
apt)
|
|
echo " Setting up NodeSource repository..."
|
|
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
|
|
;;
|
|
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
|
|
else
|
|
echo " Node.js not found — installing v22..."
|
|
case $PM in
|
|
apt)
|
|
echo " Setting up NodeSource repository (this may take a moment)..."
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
echo " Installing nodejs from apt..."
|
|
sudo apt-get install -y nodejs
|
|
if has node; then
|
|
ok "node v$(node -v) installed from NodeSource"
|
|
else
|
|
fail "Node.js install failed"
|
|
exit 1
|
|
fi
|
|
;;
|
|
pacman)
|
|
echo " Installing nodejs from pacman..."
|
|
install_pkg nodejs npm
|
|
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; exit 1; fi
|
|
;;
|
|
brew)
|
|
echo " Installing node from brew..."
|
|
install_pkg node
|
|
if has node; then ok "node v$(node -v) installed"; else fail "node install failed"; exit 1; 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
|
|
# Use explicit path to system npm if available, to avoid nvm conflicts
|
|
SYSTEM_NPM="/usr/bin/npm"
|
|
if [ ! -f "$SYSTEM_NPM" ]; then
|
|
SYSTEM_NPM="$(which npm)"
|
|
fi
|
|
|
|
# Set npm prefix to system location so all users can access packages
|
|
echo " Configuring npm global prefix to /usr/local..."
|
|
sudo "$SYSTEM_NPM" config set prefix /usr/local
|
|
ok "npm prefix set to /usr/local"
|
|
|
|
# Pi (coding agent)
|
|
if has pi; then
|
|
skip "pi (@mariozechner/pi-coding-agent)"
|
|
else
|
|
echo " Installing pi..."
|
|
sudo "$SYSTEM_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 "$SYSTEM_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
|
|
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 ""
|