scripts/setup/ is now what the new installer is being built in — machine-setup/ for the box, officer-setup.sh for the platform on top — and everything being replaced moved to scripts/setup-old/. It still works and is still what to run. Three things the move broke, and what each needed: starship.toml is not an old-setup artifact. os-user-shell.ts reads it at RUNTIME to seed a member's ~/.config/starship.toml when their Linux account is provisioned, and line 125 reads it inside a try whose catch returns "could not read the shell templates" — so account provisioning would have failed outright, not degraded. Moved back to scripts/setup/, which is where it belongs anyway (one file, both audiences) and which leaves the code correct with no edit. package.json's `setup` script pointed at a path that no longer exists. It now points at officer-setup.sh, where the installer is going, rather than at setup-old/ which is temporary. officer-setup.sh was created empty. An empty script exits 0, so `bun setup` would have reported success while doing nothing — worse than the broken path it replaced. It now explains that it is not written yet and exits 1, naming the setup-old script to run meanwhile. Also brought .tmux.conf and ufw-docker-rules.conf in beside machine-setup.sh, which reads both from SCRIPT_DIR and had been silently skipping them since the script was vendored. ssh-keys.zip deliberately stays out: it is key material, and *.zip is ignored. Comments in os-user-claude.ts, app-store/preflight.ts and two docs still name the old scripts/setup/setup.sh path. Left alone on purpose — repointing them at setup-old/ only to repoint them again when officer-setup.sh lands is churn. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
259 lines
9.6 KiB
Bash
Executable File
259 lines
9.6 KiB
Bash
Executable File
#!/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 ""
|