From 4e404f17c842b996c62a953ecdd3839d842e8ba0 Mon Sep 17 00:00:00 2001 From: andrepadez Date: Wed, 12 Aug 2026 07:24:19 +0200 Subject: [PATCH] split the optional host dependencies out of setup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8 Rust, 9 PulseAudio, 10 cliamp, 13 yt-dlp and 17 the remote desktop move to scripts/setup/setup-sidecars.sh, which nothing invokes — running it is a deliberate act. They are what the optional, sidecar-backed features need on the host, not what the app needs to serve itself. 11 Neovim, 12 the shell extras and 14 the npm globals are gone entirely. The host provisioning already installs node, npm, pm2, Claude Code, Neovim and the shell, and two installers racing for the same binaries is worse than one. That makes node, npm, pm2 and the agent CLIs prerequisites of this script rather than products of it, so the verification block still checks claude and pm2 — section 19 warns and skips rather than failing when pm2 is absent, which would otherwise finish "successfully" with nothing listening. eza is the one casualty: the provisioning installs lazygit, starship, oh-my-zsh and nvim, but not that. Section numbers keep their gaps so the two files read against each other. One line survives from the removed section 14 — the ~/.local/bin PATH export, which section 19's `has pm2` and the agent's claude lookup both still depend on. Co-Authored-By: Claude Opus 5 --- scripts/setup/setup-sidecars.sh | 258 ++++++++++++++++++++++ scripts/setup/setup.sh | 376 ++++---------------------------- 2 files changed, 301 insertions(+), 333 deletions(-) create mode 100755 scripts/setup/setup-sidecars.sh diff --git a/scripts/setup/setup-sidecars.sh b/scripts/setup/setup-sidecars.sh new file mode 100755 index 00000000..f1301128 --- /dev/null +++ b/scripts/setup/setup-sidecars.sh @@ -0,0 +1,258 @@ +#!/bin/bash +# Officer — host dependencies for the optional, sidecar-backed features. +# +# Usage: +# bash scripts/setup/setup-sidecars.sh +# +# WHAT THIS IS +# Everything here was part of setup.sh and is not any more. setup.sh installs what the app needs to +# serve itself; this installs what a handful of *optional* features need on the host, and it is never +# invoked by setup.sh — running it is a deliberate act. +# +# The sections keep the numbering they had in setup.sh so the two files can be read against each +# other: +# +# 1 (was 8) Rust +# 2 (was 9) PulseAudio + audio dev headers +# 3 (was 10) cliamp +# 4 (was 13) yt-dlp +# 5 (was 17) Remote desktop (delegates to setup-desktop.sh) +# +# There is no `light`/`full` profile here. In setup.sh these sections were the ones `light` skipped, +# so gating them again would only mean "run this script and have it do nothing" — running it at all +# IS the opt-in. +# +# ORDER MATTERS: section 3 needs Go, which setup.sh installs. Run setup.sh first. +# Section 5 rewrites GRUB and switches the display manager — it takes effect on the next reboot. + +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; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# setup.sh installed Go and rustup into the user's home and exported them for its own run only. A +# fresh shell has neither on PATH, which would make `has go` false (silently skipping the cliamp +# build) and `has rustc` false (re-running rustup over an existing toolchain). Put them back. +export PATH="$HOME/.local/go/bin:$HOME/.cargo/bin:$PATH" + +# ─── 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 — optional host dependencies ($PM)" +echo "═══════════════════════════════════════════" + +# ─── 1. Rust (was setup.sh section 8) ───────────────────────────────────────── +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 + +# ─── 2. PulseAudio (headless audio for cliamp) (was section 9) ──────────────── +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 + +# Vorbis/OGG/FLAC dev headers (needed by cliamp's Go dependencies) +case $PM in + apt) + for pkg in libvorbis-dev libogg-dev libflac-dev; do + if dpkg -s "$pkg" &>/dev/null 2>&1; then skip "$pkg"; else PULSE_PKGS+=("$pkg"); fi + done + ;; + pacman) + for pkg in libvorbis libogg flac; do + if pacman -Qi "$pkg" &>/dev/null 2>&1; then skip "$pkg"; else PULSE_PKGS+=("$pkg"); fi + done + ;; + brew) ;; # not needed on macOS +esac + +if [ ${#PULSE_PKGS[@]} -gt 0 ]; then + install_pkg "${PULSE_PKGS[@]}" + ok "Installed: ${PULSE_PKGS[*]}" +fi + +# ─── 3. cliamp (music player) (was section 10) ──────────────────────────────── +echo "" +echo "── cliamp ──" + +# Export GOPATH (not just PATH) so `go install` lands in GOPATH_BIN — even when Go was already present +# this run and install_go (which sets GOPATH) never ran. Otherwise go uses its default ~/go/bin and the +# check below wrongly reports a build failure. +export GOPATH="${GOPATH:-$HOME/.local/go-path}" +GOPATH_BIN="$GOPATH/bin" +export PATH="$GOPATH_BIN:$PATH" + +if has cliamp; then + skip "cliamp ($(command -v cliamp))" +else + if ! has go; then + warn "Go not installed — skipping cliamp build (run setup.sh first)" + 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 + warn "cliamp build failed" + fi + fi +fi + +# ─── 4. yt-dlp (video/audio download) (was section 13) ──────────────────────── +echo "" +echo "── yt-dlp ──" + +# Always install/upgrade via pip to get the latest version (apt repos are outdated). +# Remove apt version first if present, then install via pip to /usr/local/bin. +if has pip3; then + # Remove outdated apt version if installed + case $PM in + apt) + if dpkg -s yt-dlp &>/dev/null 2>&1; then + echo " Removing outdated apt version..." + sudo apt remove -y yt-dlp > /dev/null 2>&1 + fi + ;; + esac + echo " Installing/upgrading yt-dlp via pip..." + sudo pip3 install --break-system-packages --upgrade yt-dlp 2>/dev/null + if has yt-dlp; then ok "yt-dlp $(yt-dlp --version) installed"; else warn "yt-dlp pip install failed"; fi +else + case $PM in + apt) install_pkg yt-dlp 2>/dev/null && ok "yt-dlp installed (apt — may be outdated)" || warn "yt-dlp not available" ;; + pacman) install_pkg yt-dlp && ok "yt-dlp installed" ;; + brew) install_pkg yt-dlp && ok "yt-dlp installed" ;; + esac +fi + +# ─── 5. remote desktop (Ubuntu Desktop + VNC) (was section 17) ──────────────── +echo "" +echo "── Remote Desktop (Ubuntu Desktop + VNC) ──" + +# No "already installed" guard here on purpose. This used to skip on `dpkg -s ubuntu-desktop`, which +# treats one package being present as proof the whole remote desktop is configured — and those are very +# different things. A host can have ubuntu-desktop and still be missing every part that makes the mirror +# work: GDM auto-login, the forced Xorg session, the captured EDID and its kernel command line, the +# login-time mode setter. That was not hypothetical; it was this machine on 2026-08-02, where the guard +# reported "skip" while five of setup-desktop.sh's steps had never run and /desktop could not survive a +# reboot. setup-desktop.sh is idempotent throughout — every step either no-ops or is individually +# guarded — so letting it run each time converges a partially configured host instead of trusting a +# proxy for state it never actually checked. +# +# This is by far the most expensive section — it pulls the whole ubuntu-desktop meta-package, rewrites +# /etc/default/grub and switches the display manager. +case $PM in + apt) + bash "$SCRIPT_DIR/setup-desktop.sh" + ;; + *) + warn "Remote desktop setup is Ubuntu/Debian only — skipping" + ;; +esac + +# ─── verification ───────────────────────────────────────────────────────────── +echo "" +echo "═══════════════════════════════════════════" +echo " Verification" +echo "═══════════════════════════════════════════" +echo "" + +check() { + if has "$1"; then ok "$1"; else fail "$1 — NOT FOUND"; fi +} + +echo "Rust:" +check rustc +check cargo + +echo "" +echo "Audio (cliamp):" +check pulseaudio +check parec +check pactl +check cliamp + +echo "" +echo "Download:" +check yt-dlp + +echo "" +echo "═══════════════════════════════════════════" +echo " Done" +echo "═══════════════════════════════════════════" +echo "" +echo "Notes:" +echo " • PulseAudio null sink starts automatically with the server" +echo " • Make sure ~/.cargo/bin is in your PATH for Rust tools" +echo " • Make sure ~/.local/go-path/bin is in your PATH for Go-installed tools (cliamp)" +echo " • REBOOT to switch into the GNOME-on-Xorg session the remote desktop mirrors" +echo "" diff --git a/scripts/setup/setup.sh b/scripts/setup/setup.sh index a8e27af9..7e769b4a 100755 --- a/scripts/setup/setup.sh +++ b/scripts/setup/setup.sh @@ -13,13 +13,24 @@ # Claude/opencode chat — on a Linux host. Installs only what those need: node, bun, ffmpeg, # Postgres, pm2 and the two agent CLIs, then starts ecosystem.light.config.cjs. # -# Skipped by `light`: archive extras, the sudoers entry and auto-suspend disabling, Go, Rust, -# PulseAudio, cliamp, Neovim, the shell extras (oh-my-zsh/eza/lazygit), yt-dlp, and -# the remote desktop. Of the Docker services only Postgres is brought up. +# Skipped by `light`: archive extras, the sudoers entry and auto-suspend disabling, and Go. +# Of the Docker services only Postgres is brought up. # # The app itself is identical — every API route stays mounted, so the features whose sidecars # are not running report themselves unavailable rather than disappearing. A profile changes # which processes start, not which code ships. +# +# NOT INSTALLED HERE — and the gaps in the section numbers are where these used to be +# Moved to scripts/setup/setup-sidecars.sh, which nothing below invokes; run it deliberately, and +# only after this script: 8 Rust, 9 PulseAudio, 10 cliamp, 13 yt-dlp, 17 remote desktop. +# +# Removed outright, because the host provisioning already installs them and two installers racing +# for the same binaries is worse than one: 11 Neovim, 12 shell extras (oh-my-zsh/eza/lazygit), +# 14 npm globals (the ~/.local npm prefix, Claude Code, pm2). +# +# That makes node, npm, pm2 and the agent CLIs PREREQUISITES of this script rather than products of +# it. Section 19 warns and skips rather than failing if pm2 is absent, so a host that never ran the +# provisioning will finish "successfully" with nothing listening — check the verification block. set -e @@ -138,7 +149,7 @@ if has make && has gcc; then skip "build tools (make, gcc, g++)"; else esac fi -# pkg-config — needed by cgo-based Go packages (e.g. ebitengine/oto for cliamp) +# pkg-config — needed by cgo-based Go packages (e.g. ebitengine/oto for cliamp, in setup-sidecars.sh) if has pkg-config; then skip "pkg-config"; else case $PM in apt) CORE_PKGS+=(pkg-config) ;; @@ -518,8 +529,9 @@ fi # prompt to every account, so leaving starship out of light meant every member's shell fell back to the plain # one on exactly the installs most likely to have members. # -# One static binary and one config file. oh-my-zsh, eza and lazygit stay in section 12, where `light` skips -# them: those are host comforts, and the shell template treats each as optional. +# One static binary and one config file, which is why it survived the cull that removed the rest of the +# terminal tooling: oh-my-zsh, eza and lazygit are host comforts the provisioning installs, and the shell +# template treats each as optional. Starship it does not — the prompt would visibly degrade. echo "" echo "── Prompt (starship) ──" @@ -532,9 +544,7 @@ else fi # Deploy starship config. Unconditionally cp'ing here overwrote a customised ~/.config/starship.toml on -# every run, silently — the nvim step below already gets this right by guarding on the config's -# existence, so this was just inconsistent. Converge when there is nothing to lose, keep what the user -# wrote when there is. +# every run, silently. Converge when there is nothing to lose, keep what the user wrote when there is. mkdir -p "$HOME/.config" STARSHIP_DEST="$HOME/.config/starship.toml" if [ ! -f "$STARSHIP_DEST" ]; then @@ -547,13 +557,11 @@ else fi -# Sections 7-13 are one block because `light` skips all of them. Go and PulseAudio exist to build and -# feed cliamp; Rust has no consumer left in the tree; Neovim, the shell tooling and yt-dlp are host -# comforts and capability dependencies rather than anything the app needs to serve a file browser, a -# terminal and a chat. +# Go is a host comfort rather than anything the app needs to serve a file browser, a terminal and a +# chat, so `light` skips it. It is the only section left in this block — 8-13 were removed or moved. if is_light; then echo "" - omit "Go, Rust, PulseAudio, cliamp, Neovim, shell extras (oh-my-zsh/eza/lazygit), yt-dlp" + omit "Go" else # ─── 7. Go ───────────────────────────────────────────────────────────────────── @@ -607,279 +615,18 @@ else if has go; then ok "go $(go version | awk '{print $3}') installed"; else warn "go not found — install manually from https://go.dev/dl/"; fi fi -# ─── 8. Rust ────────────────────────────────────────────────────────────────── -echo "" -echo "── Rust ──" +# 8 Rust, 9 PulseAudio, 10 cliamp and 13 yt-dlp are in setup-sidecars.sh. +# 11 Neovim, 12 shell extras (oh-my-zsh/eza/lazygit) and 14 npm globals are gone entirely — the host +# provisioning owns node, npm, pm2, Claude Code, Neovim and the shell, and this script duplicating +# them meant two installers racing for the same binaries. -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 +fi # end of the light-profile skip, which is now section 7 alone -# ─── 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 - -# Vorbis/OGG/FLAC dev headers (needed by cliamp's Go dependencies) -case $PM in - apt) - for pkg in libvorbis-dev libogg-dev libflac-dev; do - if dpkg -s "$pkg" &>/dev/null 2>&1; then skip "$pkg"; else PULSE_PKGS+=("$pkg"); fi - done - ;; - pacman) - for pkg in libvorbis libogg flac; do - if pacman -Qi "$pkg" &>/dev/null 2>&1; then skip "$pkg"; else PULSE_PKGS+=("$pkg"); fi - done - ;; - 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 ──" - -# Export GOPATH (not just PATH) so `go install` lands in GOPATH_BIN — even when Go was already present -# this run and install_go (which sets GOPATH) never ran. Otherwise go uses its default ~/go/bin and the -# check below wrongly reports a build failure. -export GOPATH="${GOPATH:-$HOME/.local/go-path}" -GOPATH_BIN="$GOPATH/bin" -export PATH="$GOPATH_BIN:$PATH" - -if has cliamp; then - skip "cliamp ($(command -v 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 - warn "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 (starship is section 6b, outside the light skip) ───── -echo "" -echo "── Terminal tools (oh-my-zsh, eza, lazygit) ──" - - -# 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 -if has eza; then - skip "eza" -else - case $PM in - apt) - echo " Fetching latest eza version..." - EZA_VERSION=$(curl -fsSL "https://api.github.com/repos/eza-community/eza/releases/latest" | jq -r '.tag_name' | sed 's/^v//') - if [ -z "$EZA_VERSION" ]; then warn "Could not fetch eza version — skipping"; else - 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 -f /tmp/eza.tar.gz - fi - ;; - pacman) install_pkg eza ;; - brew) install_pkg eza ;; - esac - if has eza; then ok "eza installed"; else warn "eza install failed"; fi -fi - -# lazygit -if has lazygit; then - skip "lazygit" -else - case $PM in - apt) - echo " Fetching latest lazygit version..." - LAZYGIT_VERSION=$(curl -fsSL "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | jq -r '.tag_name' | sed 's/^v//') - if [ -z "$LAZYGIT_VERSION" ]; then warn "Could not fetch lazygit version — skipping"; else - 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 -f /tmp/lazygit.tar.gz /tmp/LICENSE /tmp/README.md - fi - ;; - 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) ──" - -# Always install/upgrade via pip to get the latest version (apt repos are outdated). -# Remove apt version first if present, then install via pip to /usr/local/bin. -if has pip3; then - # Remove outdated apt version if installed - case $PM in - apt) - if dpkg -s yt-dlp &>/dev/null 2>&1; then - echo " Removing outdated apt version..." - sudo apt remove -y yt-dlp > /dev/null 2>&1 - fi - ;; - esac - echo " Installing/upgrading yt-dlp via pip..." - sudo pip3 install --break-system-packages --upgrade yt-dlp 2>/dev/null - if has yt-dlp; then ok "yt-dlp $(yt-dlp --version) installed"; else warn "yt-dlp pip install failed"; fi -else - case $PM in - apt) install_pkg yt-dlp 2>/dev/null && ok "yt-dlp installed (apt — may be outdated)" || warn "yt-dlp not available" ;; - pacman) install_pkg yt-dlp && ok "yt-dlp installed" ;; - brew) install_pkg yt-dlp && ok "yt-dlp installed" ;; - esac -fi - -fi # end of the light-profile skip for sections 7-13 - -# ─── 14. npm global packages (user-local) ─────────────────────────────────── -echo "" -echo "── npm global packages (user-local) ──" - -# Ensure ~/.local/bin is in PATH for this session +# Kept from the removed section 14: nothing here installs into ~/.local/bin any more, but section 19 +# still asks `has pm2` and the agent still resolves `claude` off PATH. A host that installed either +# user-locally would otherwise look like it has neither. export PATH="$HOME/.local/bin:$PATH" -if ! has npm; then - warn "npm not found — skipping global package installs" -else - # Set npm prefix to user-local so no sudo is needed for installs/updates - echo " Configuring npm global prefix to ~/.local..." - npm config set prefix "$HOME/.local" - ok "npm prefix set to $HOME/.local" - - # Claude Code (uses Anthropic's own installer for auto-update support) - if has claude; then - skip "claude (claude-code)" - else - echo " Installing claude-code via Anthropic installer..." - curl -fsSL https://claude.ai/install.sh | bash # bash, not sh: a piped script ignores its shebang and install.sh is bash - if has claude; then ok "claude-code installed"; else warn "claude-code install failed"; fi - fi - - # No /usr/local/bin/claude symlink. That existed because the sidecar hardcoded that path, which in - # turn came from the bwrap-sandboxed architecture — the jail ro-bound /usr and could not see the - # installer's real target in ~/.local/bin. The sandbox is gone and claude-manager.ts now resolves the - # CLI itself: $CLAUDE_BIN, then PATH, then ~/.local/bin/claude, /usr/local/bin/claude and - # /opt/homebrew/bin/claude. The installer above puts it in ~/.local/bin, which is both on PATH and the - # first candidate, so the symlink was satisfying a requirement that no longer exists — at the cost of - # a sudo-owned link into /usr/local/bin, a directory macOS does not even ship. - - # pm2 (process manager) - if has pm2; then - skip "pm2" - else - echo " Installing pm2..." - npm install -g pm2 - if has pm2; then ok "pm2 installed"; else warn "pm2 install failed"; fi - fi -fi - # ─── 15. bun install (project dependencies) ────────────────────────────────── echo "" echo "── Project dependencies ──" @@ -1001,34 +748,7 @@ ENVFILE ok ".env written to $PROJECT_DIR/.env" fi -# ─── 17. remote desktop (Ubuntu Desktop + VNC) ─────────────────────────────── -echo "" -echo "── Remote Desktop (Ubuntu Desktop + VNC) ──" - -# No "already installed" guard here on purpose. This used to skip on `dpkg -s ubuntu-desktop`, which -# treats one package being present as proof the whole remote desktop is configured — and those are very -# different things. A host can have ubuntu-desktop and still be missing every part that makes the mirror -# work: GDM auto-login, the forced Xorg session, the captured EDID and its kernel command line, the -# login-time mode setter. That was not hypothetical; it was this machine on 2026-08-02, where the guard -# reported "skip" while five of setup-desktop.sh's steps had never run and /desktop could not survive a -# reboot. setup-desktop.sh is idempotent throughout — every step either no-ops or is individually -# guarded — so letting it run each time converges a partially configured host instead of trusting a -# proxy for state it never actually checked. -# The light profile does not run officer-vnc, so there is nothing to mirror. This is the single most -# expensive section — it pulls the whole ubuntu-desktop meta-package — and the one most clearly outside -# "file browser, terminal, chat". -if is_light; then - omit "remote desktop (ubuntu-desktop, GDM, x11vnc, Brave)" -else - case $PM in - apt) - bash "$SCRIPT_DIR/setup-desktop.sh" - ;; - *) - warn "Remote desktop setup is Ubuntu/Debian only — skipping" - ;; - esac -fi +# 17 remote desktop is in setup-sidecars.sh. # ─── 18. project initialization ────────────────────────────────────────────── echo "" @@ -1116,17 +836,13 @@ check gcc echo "" echo "Dev tools:" -# Only the ones the light profile actually installs are checked under it — reporting Go and cliamp as -# NOT FOUND on an install that deliberately skipped them makes a clean run look broken. +# Only what this script still installs is checked. Reporting Go as NOT FOUND on a light install that +# deliberately skipped it makes a clean run look broken; so does checking for nvim, lazygit and eza, +# which this script no longer owns at all. if ! is_light; then check go - check rustc - check cargo - check nvim - check starship - check lazygit - check eza fi +check starship check zsh check rg check fd @@ -1137,21 +853,15 @@ check tree check btop check sqlite3 -if ! is_light; then - echo "" - echo "Audio (cliamp):" - check pulseaudio - check parec - check pactl - check cliamp -fi - +# Neither of these is installed here any more — they come from the host provisioning. They are still +# checked because section 19 and every chat turn depend on them, and "NOT FOUND" here is the only +# warning you get before the services silently do not start. echo "" -echo "AI agents:" +echo "AI agents (from host provisioning):" check claude echo "" -echo "Process manager:" +echo "Process manager (from host provisioning):" check pm2 echo "" @@ -1161,7 +871,6 @@ check 7z check unrar check pgrep check fuser -if ! is_light; then check yt-dlp; fi echo "" echo "═══════════════════════════════════════════" @@ -1189,8 +898,9 @@ fi echo "" echo "Notes:" -echo " • PulseAudio null sink starts automatically with the server" echo " • Make sure ~/.local/go/bin and ~/.local/go-path/bin are in your PATH for Go tools" -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 " • node, npm, pm2 and the agent CLIs come from the host provisioning, not from here" +echo " • Rust, PulseAudio, cliamp, yt-dlp and the remote desktop are NOT installed by this script:" +echo " run 'bash scripts/setup/setup-sidecars.sh' if you want them" echo ""