fix(pi): add snap node compatibility diagnostics and documentation
- Added detailed error logging to detect snap node compatibility issues - When Pi process exits with code 1, log helpful diagnostic info including node path - Add hint to check for snap node and reinstall via apt/nvm - Create SNAP_NODE_COMPATIBILITY.md with full troubleshooting guide - Document root cause: snap node has file descriptor incompatibility with Bun.spawn stdin pipes - Provide clear installation instructions for NodeSource and nvm alternatives
This commit is contained in:
Executable
+181
@@ -0,0 +1,181 @@
|
||||
#!/bin/bash
|
||||
# One-time migration: provision Linux users for existing database members.
|
||||
# Reads users from PostgreSQL, skips Super Admins (they use the service account),
|
||||
# and creates Linux users + seeds shell configs for everyone else.
|
||||
#
|
||||
# Usage: bash scripts/provision-existing-users.sh
|
||||
# Requires: sudoers entry from setup.sh, POSTGRES_URL in .env
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
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 (skipped)"; }
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ENV_FILE="$PROJECT_DIR/.env"
|
||||
TEMPLATE_DIR="$PROJECT_DIR/src/servers/api/terminal/templates"
|
||||
|
||||
# Load .env
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
fail ".env not found at $ENV_FILE"
|
||||
exit 1
|
||||
fi
|
||||
source <(grep -E '^[A-Z_]+=.*' "$ENV_FILE" | sed 's/^/export /')
|
||||
|
||||
# Resolve DATA_PATH
|
||||
DATA_PATH="${DATA_PATH:-$PROJECT_DIR/data}"
|
||||
|
||||
# Parse POSTGRES_URL for psql
|
||||
if [ -z "${POSTGRES_URL:-}" ]; then
|
||||
fail "POSTGRES_URL not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract components from postgresql://user:pass@host:port/dbname
|
||||
PG_USER=$(echo "$POSTGRES_URL" | sed -n 's|.*://\([^:]*\):.*|\1|p')
|
||||
PG_PASS=$(echo "$POSTGRES_URL" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p')
|
||||
PG_HOST=$(echo "$POSTGRES_URL" | sed -n 's|.*@\([^:]*\):.*|\1|p')
|
||||
PG_PORT=$(echo "$POSTGRES_URL" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
||||
PG_DB=$(echo "$POSTGRES_URL" | sed -n 's|.*/\([^?]*\).*|\1|p')
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════"
|
||||
echo " Provision existing users"
|
||||
echo "═══════════════════════════════════════════"
|
||||
echo ""
|
||||
echo "DATA_PATH: $DATA_PATH"
|
||||
echo "Database: $PG_DB @ $PG_HOST:$PG_PORT"
|
||||
echo ""
|
||||
|
||||
# Query non-Super Admin active users
|
||||
USERS=$(PGPASSWORD="$PG_PASS" psql -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -F '|' \
|
||||
-c "SELECT email, COALESCE(username, '') FROM users WHERE role != 'Super Admin' AND status = 'Active';" 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
fail "Failed to query database: $USERS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$USERS" ]; then
|
||||
echo "No non-admin active users found. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "── Found users ──"
|
||||
echo "$USERS" | while IFS='|' read -r email username; do
|
||||
echo " $email (username: ${username:-<from email>})"
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo "── Provisioning ──"
|
||||
while IFS='|' read -r email username; do
|
||||
# Derive shell username (same logic as toShellUsername in data-path.ts)
|
||||
if [ -n "$username" ]; then
|
||||
shell_user=$(echo "$username" | sed 's/@.*$//' | sed 's/[^a-zA-Z0-9._-]/_/g' | tr '[:upper:]' '[:lower:]' | cut -c1-32)
|
||||
else
|
||||
shell_user=$(echo "$email" | sed 's/@.*$//' | sed 's/[^a-zA-Z0-9._-]/_/g' | tr '[:upper:]' '[:lower:]' | cut -c1-32)
|
||||
fi
|
||||
|
||||
USER_ROOT="$DATA_PATH/$email"
|
||||
HOME_DIR="$USER_ROOT/home"
|
||||
|
||||
echo ""
|
||||
echo " [$email → $shell_user]"
|
||||
|
||||
# Ensure data dirs exist (sudo in case dir is owned by a previous provisioning run)
|
||||
sudo mkdir -p "$USER_ROOT" "$HOME_DIR"
|
||||
|
||||
# Create Linux user if needed
|
||||
if id "$shell_user" &>/dev/null; then
|
||||
skip "Linux user $shell_user already exists"
|
||||
else
|
||||
if sudo useradd -d "$HOME_DIR" -s /bin/zsh -M "$shell_user"; then
|
||||
ok "Created Linux user $shell_user"
|
||||
else
|
||||
fail "Failed to create Linux user $shell_user"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Seed shell configs (only if not already present)
|
||||
if [ ! -f "$HOME_DIR/.zshenv" ] && [ -f "$TEMPLATE_DIR/.zshenv" ]; then
|
||||
sudo cp "$TEMPLATE_DIR/.zshenv" "$HOME_DIR/.zshenv"
|
||||
ok "Seeded .zshenv"
|
||||
else
|
||||
skip ".zshenv"
|
||||
fi
|
||||
|
||||
if [ ! -f "$HOME_DIR/.zshrc" ] && [ -f "$TEMPLATE_DIR/.zshrc" ]; then
|
||||
sudo cp "$TEMPLATE_DIR/.zshrc" "$HOME_DIR/.zshrc"
|
||||
ok "Seeded .zshrc"
|
||||
else
|
||||
skip ".zshrc"
|
||||
fi
|
||||
|
||||
if [ ! -f "$HOME_DIR/.tmux.conf" ] && [ -f "$TEMPLATE_DIR/.tmux.conf" ]; then
|
||||
sudo cp "$TEMPLATE_DIR/.tmux.conf" "$HOME_DIR/.tmux.conf"
|
||||
ok "Seeded .tmux.conf"
|
||||
else
|
||||
skip ".tmux.conf"
|
||||
fi
|
||||
|
||||
sudo mkdir -p "$HOME_DIR/.config"
|
||||
if [ ! -f "$HOME_DIR/.config/starship-officer.toml" ] && [ -f "$TEMPLATE_DIR/starship-officer.toml" ]; then
|
||||
sudo cp "$TEMPLATE_DIR/starship-officer.toml" "$HOME_DIR/.config/starship-officer.toml"
|
||||
ok "Seeded starship config"
|
||||
else
|
||||
skip "starship config"
|
||||
fi
|
||||
|
||||
# Oh My Zsh
|
||||
if [ ! -d "$HOME_DIR/.oh-my-zsh" ]; then
|
||||
if [ -d "$HOME/.oh-my-zsh" ]; then
|
||||
sudo cp -r "$HOME/.oh-my-zsh" "$HOME_DIR/.oh-my-zsh"
|
||||
ok "Copied oh-my-zsh from host"
|
||||
fi
|
||||
else
|
||||
skip "oh-my-zsh"
|
||||
fi
|
||||
|
||||
# LazyVim
|
||||
sudo mkdir -p "$HOME_DIR/.config"
|
||||
if [ ! -d "$HOME_DIR/.config/nvim" ]; then
|
||||
if [ -d "$HOME/.config/nvim" ]; then
|
||||
sudo cp -r "$HOME/.config/nvim" "$HOME_DIR/.config/nvim"
|
||||
ok "Copied nvim config from host"
|
||||
fi
|
||||
else
|
||||
skip "nvim config"
|
||||
fi
|
||||
|
||||
# Ensure dirs
|
||||
sudo mkdir -p "$HOME_DIR/.local/bin"
|
||||
sudo mkdir -p "$HOME_DIR/.pi/agent/sessions"
|
||||
|
||||
# Set ownership and permissions last
|
||||
sudo chown -R "$shell_user:$shell_user" "$USER_ROOT"
|
||||
sudo chmod 770 "$USER_ROOT"
|
||||
|
||||
# Add service user to this user's group so server jobs can access user data
|
||||
SERVICE_USER="${SUDO_USER:-$(whoami)}"
|
||||
if [ "$SERVICE_USER" != "$shell_user" ]; then
|
||||
sudo usermod -aG "$shell_user" "$SERVICE_USER"
|
||||
ok "Added $SERVICE_USER to group $shell_user"
|
||||
fi
|
||||
|
||||
ok "Provisioning complete"
|
||||
done <<< "$USERS"
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════"
|
||||
echo " Done!"
|
||||
echo "═══════════════════════════════════════════"
|
||||
echo ""
|
||||
@@ -5,14 +5,7 @@ set -euo pipefail
|
||||
# Run as the user who will own the VNC session (not root).
|
||||
# Usage: ./scripts/setup-desktop.sh <vnc-password> [resolution]
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <vnc-password> [resolution]"
|
||||
echo " vnc-password: 8 characters max (VNC protocol limitation)"
|
||||
echo " resolution: optional, default 1920x1080"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VNC_PASS="$1"
|
||||
VNC_PASS="${1:-$(head -c 6 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 8)}"
|
||||
RESOLUTION="${2:-1920x1080}"
|
||||
USER_NAME="$(whoami)"
|
||||
ENV_FILE="$(cd "$(dirname "$0")/.." && pwd)/.env"
|
||||
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Officer PTY Sidecar — systemd service setup
|
||||
# Creates and enables a systemd service for the terminal PTY sidecar.
|
||||
# Usage: bash scripts/setup-pty-sidecar.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
warn() { echo -e " ${YELLOW}!${NC} $1"; }
|
||||
skip() { echo -e " - $1 (already set up)"; }
|
||||
|
||||
SERVICE_NAME="officer-pty-sidecar"
|
||||
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
SERVICE_USER="$(whoami)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
NODE_BIN="$(which node)"
|
||||
|
||||
echo ""
|
||||
echo "── PTY Sidecar (systemd service) ──"
|
||||
|
||||
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
|
||||
skip "$SERVICE_NAME service already running"
|
||||
echo " Use 'bun run restart:sidecar' to restart"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sudo tee "$SERVICE_FILE" > /dev/null << EOF
|
||||
[Unit]
|
||||
Description=Officer PTY Sidecar
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$SERVICE_USER
|
||||
WorkingDirectory=$PROJECT_DIR
|
||||
Environment=TERMINAL_PTY_PORT=5338
|
||||
ExecStart=$NODE_BIN $PROJECT_DIR/src/servers/api/terminal/pty-sidecar.mjs
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now "$SERVICE_NAME"
|
||||
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
ok "$SERVICE_NAME service installed and running"
|
||||
else
|
||||
warn "$SERVICE_NAME service failed to start — check 'journalctl -u $SERVICE_NAME'"
|
||||
fi
|
||||
+391
-34
@@ -55,8 +55,12 @@ if has git; then skip "git"; else CORE_PKGS+=(git); fi
|
||||
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)
|
||||
# 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
|
||||
@@ -82,11 +86,116 @@ if has script; then skip "script (bsdutils)"; else
|
||||
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) ──"
|
||||
@@ -127,30 +236,26 @@ else
|
||||
ok "ffmpeg installed"
|
||||
fi
|
||||
|
||||
# ─── 4. docker ─────────────────────────────────────────────────────────────────
|
||||
# ─── 4. sudoers for officer service user ──────────────────────────────────────
|
||||
echo ""
|
||||
echo "── Docker ──"
|
||||
echo "── Sudoers (Linux user isolation) ──"
|
||||
|
||||
if has docker; then
|
||||
skip "docker ($(docker --version 2>/dev/null | awk '{print $3}' | tr -d ','))"
|
||||
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)
|
||||
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"
|
||||
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 "Install Docker Desktop from https://www.docker.com/products/docker-desktop"
|
||||
warn "Sudoers setup is Linux-only — skipping on macOS"
|
||||
;;
|
||||
esac
|
||||
if has docker; then ok "docker installed"; else warn "docker not found — install manually"; fi
|
||||
fi
|
||||
|
||||
# ─── 5. Node.js 22 ────────────────────────────────────────────────────────────
|
||||
@@ -172,6 +277,45 @@ else
|
||||
warn " nvm install 22"
|
||||
fi
|
||||
|
||||
# ─── npm global prefix ─────────────────────────────────────────────────────────
|
||||
# Set npm global prefix to a user-writable directory so npm install -g
|
||||
# never requires sudo. This also lets the server install packages at runtime.
|
||||
echo ""
|
||||
echo "── npm global prefix ──"
|
||||
|
||||
if has npm; then
|
||||
NPM_PREFIX=$(npm config get prefix 2>/dev/null)
|
||||
NPM_GLOBAL="$HOME/.npm-global"
|
||||
|
||||
if [ "$NPM_PREFIX" = "$NPM_GLOBAL" ]; then
|
||||
skip "npm prefix already set to $NPM_GLOBAL"
|
||||
else
|
||||
mkdir -p "$NPM_GLOBAL"
|
||||
npm config set prefix "$NPM_GLOBAL"
|
||||
export PATH="$NPM_GLOBAL/bin:$PATH"
|
||||
ok "Set npm global prefix to $NPM_GLOBAL"
|
||||
|
||||
# Migrate existing global packages from system prefix if any
|
||||
if [ -d "$NPM_PREFIX/lib/node_modules/@mariozechner" ] || [ -d "$NPM_PREFIX/lib/node_modules/@anthropic-ai" ]; then
|
||||
warn "Removing stale system-level npm packages from $NPM_PREFIX (will reinstall to $NPM_GLOBAL)"
|
||||
sudo rm -rf "$NPM_PREFIX/lib/node_modules/@mariozechner" "$NPM_PREFIX/lib/node_modules/@anthropic-ai" 2>/dev/null
|
||||
sudo rm -f "$NPM_PREFIX/bin/pi" "$NPM_PREFIX/bin/claude" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure ~/.npm-global/bin is in shell profiles
|
||||
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||
if [ -f "$profile" ] && ! grep -q '.npm-global/bin' "$profile"; then
|
||||
echo '' >> "$profile"
|
||||
echo '# npm global packages' >> "$profile"
|
||||
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$profile"
|
||||
ok "Added ~/.npm-global/bin to $(basename "$profile")"
|
||||
fi
|
||||
done
|
||||
else
|
||||
warn "npm not found — skipping prefix setup"
|
||||
fi
|
||||
|
||||
# ─── 6. Bun ───────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "── Bun ──"
|
||||
@@ -189,14 +333,45 @@ fi
|
||||
echo ""
|
||||
echo "── Go ──"
|
||||
|
||||
GOLANG_VERSION=1.23.6
|
||||
|
||||
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
|
||||
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. PulseAudio (headless audio for cliamp) ────────────────────────────────
|
||||
# ─── 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) ──"
|
||||
|
||||
@@ -235,7 +410,7 @@ if [ ${#PULSE_PKGS[@]} -gt 0 ]; then
|
||||
ok "Installed: ${PULSE_PKGS[*]}"
|
||||
fi
|
||||
|
||||
# ─── 9. cliamp (music player) ─────────────────────────────────────────────────
|
||||
# ─── 10. cliamp (music player) ────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "── cliamp ──"
|
||||
|
||||
@@ -261,7 +436,114 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── 10. yt-dlp (optional — video/audio download) ─────────────────────────────
|
||||
# ─── 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) ──"
|
||||
|
||||
@@ -275,22 +557,58 @@ else
|
||||
esac
|
||||
fi
|
||||
|
||||
# ─── 11. npm dependencies ─────────────────────────────────────────────────────
|
||||
# ─── 14. npm global packages ─────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "── npm global packages ──"
|
||||
|
||||
if has npm; then
|
||||
# Pi (coding agent)
|
||||
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
|
||||
|
||||
# Fix ~/.pi ownership (previous installs via sudo may have created root-owned dirs)
|
||||
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"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Patch Pi compaction bug: calculateContextTokens crashes when usage is undefined
|
||||
PI_COMPACTION="$(npm root -g 2>/dev/null)/@mariozechner/pi-coding-agent/dist/core/compaction/compaction.js"
|
||||
if [ -f "$PI_COMPACTION" ]; then
|
||||
if grep -q "if (!usage) return 0;" "$PI_COMPACTION" 2>/dev/null; then
|
||||
skip "pi compaction patch (already applied)"
|
||||
else
|
||||
sed -i '/^export function calculateContextTokens(usage) {$/a\ if (!usage) return 0;' "$PI_COMPACTION"
|
||||
ok "Applied pi compaction bug patch"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Claude Code
|
||||
if has claude; then
|
||||
skip "claude (@anthropic-ai/claude-code)"
|
||||
else
|
||||
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)
|
||||
if has pm2; then
|
||||
skip "pm2"
|
||||
else
|
||||
npm install -g pm2
|
||||
if has pm2; then ok "pm2 installed"; else warn "pm2 install failed"; fi
|
||||
fi
|
||||
else
|
||||
warn "npm not found — skipping global package installs"
|
||||
fi
|
||||
|
||||
# ─── 12. bun install (project dependencies) ───────────────────────────────────
|
||||
# ─── 15. bun install (project dependencies) ──────────────────────────────────
|
||||
echo ""
|
||||
echo "── Project dependencies ──"
|
||||
|
||||
@@ -305,6 +623,26 @@ 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 "═══════════════════════════════════════════"
|
||||
@@ -321,19 +659,45 @@ check git
|
||||
check node
|
||||
check bun
|
||||
check npm
|
||||
check docker
|
||||
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 go
|
||||
check pulseaudio
|
||||
check parec
|
||||
check pactl
|
||||
check cliamp
|
||||
|
||||
echo ""
|
||||
echo "AI agents:"
|
||||
check pi
|
||||
check claude
|
||||
check pm2
|
||||
|
||||
echo ""
|
||||
echo "Optional:"
|
||||
check unzip
|
||||
@@ -342,13 +706,6 @@ 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 "═══════════════════════════════════════════"
|
||||
@@ -358,6 +715,6 @@ 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 " • Make sure ~/.cargo/bin is in your PATH for Rust tools"
|
||||
echo " • sharp, whisper-cpp, mlx-audio can be installed from Settings > Applications"
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user