# openchamber-prereqs.sh — bootstrap a Linux host for opencode + openchamber # # Intent: run this once on a fresh Linux host, then connect from the # OpenChamber desktop app over SSH — the app spawns openchamber itself # and inherits the pinned environment. No UI password is used. # # Run as the user who will own the install. Idempotent: safe to re-run, # every step checks before acting. No root required. # # What it does: # 1. Installs bun (if missing) # 2. Creates the node -> bun shim (what makes `env node` work) # 3. Pins PATH + OPENCHAMBER_RELAY_URL into shell rc files so # non-interactive SSH sessions (OpenChamber's exec path) inherit them # 4. Installs opencode (if missing) # 5. Installs @openchamber/web globally via bun (if missing) # 6. Syncs ~/.config/opencode/opencode.json from CONFIG_URL # 7. Verifies: versions + relay health # # Flags: --relay-url wss://host/ws (override the default relay endpoint) # --invite [port] print an openchamber:// invite link (+QR) # for importing this host into the desktop # app; starts the instance on # (default 3000) if none is running set -euo pipefail RELAY_URL="wss://code.zuccaro.me/ws" CONFIG_URL="https://git.zuccaro.me/bzuccaro/opencode-public/raw/branch/main/opencode.json" INVITE=0 INVITE_PORT=3000 while [ $# -gt 0 ]; do case "$1" in --relay-url) RELAY_URL="$2"; shift 2 ;; --invite) INVITE=1 if [ $# -ge 2 ] && [ "${2#--}" = "$2" ]; then INVITE_PORT="$2"; shift; fi shift ;; *) echo "unknown arg: $1 (usage: $0 [--relay-url wss://host/ws] [--invite [port]])" >&2; exit 1 ;; esac done info() { printf '\033[1;34m[info]\033[0m %s\n' "$*"; } ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; exit 1; } info "host: $(uname -srm)" [ "$(uname -s)" = "Linux" ] || die "this script targets Linux hosts" # ---- preflight ------------------------------------------------------------- for c in curl; do command -v "$c" >/dev/null || die "missing: $c (install it and re-run)"; done command -v unzip >/dev/null || warn "unzip not found — bun's installer needs it; install unzip first if step 1 fails" # ---- 1. bun ---------------------------------------------------------------- if command -v bun >/dev/null 2>&1; then ok "bun already installed: $(bun --version)" else info "installing bun..." curl -fsSL https://bun.sh/install | bash fi export PATH="$HOME/.bun/bin:$PATH" command -v bun >/dev/null 2>&1 || die "bun not on PATH after install" # ---- 2. node shim ---------------------------------------------------------- if [ -x "$HOME/.bun/bin/node" ]; then ok "node shim present" else ln -sf "$HOME/.bun/bin/bun" "$HOME/.bun/bin/node" ok "created node -> bun shim" fi # ---- 3. rc file pinning ---------------------------------------------------- # .bashrc: prepend to the TOP so non-interactive ssh execs (which source # .bashrc but return early on Debian-style interactive guards) still get it. # zsh/profile files: only touched if they already exist. pin_top() { # pin_top local f="$1" line="$2" touch "$f" if grep -qF "$line" "$f"; then ok "$(basename "$f"): already pinned"; return 0; fi sed -i "1i $line" "$f" ok "$(basename "$f"): pinned at top" } pin_append() { # pin_append local f="$1" line="$2" [ -f "$f" ] || { info "$(basename "$f") does not exist, skipping"; return 0; } if grep -qF "$line" "$f"; then ok "$(basename "$f"): already pinned"; return 0; fi printf '%s\n' "$line" >> "$f" ok "$(basename "$f"): appended" } PATH_LINE='export PATH="$HOME/.bun/bin:$PATH"' RELAY_LINE="export OPENCHAMBER_RELAY_URL=\"$RELAY_URL\"" if grep -q "OPENCHAMBER_RELAY_URL" ~/.bashrc 2>/dev/null; then info "relay var already pinned in .bashrc (respecting existing value)" else pin_top ~/.bashrc "$PATH_LINE" pin_top ~/.bashrc "$RELAY_LINE" fi pin_append ~/.zshenv "$PATH_LINE" pin_append ~/.zshenv "$RELAY_LINE" pin_append ~/.zshrc "$PATH_LINE" pin_append ~/.zshrc "$RELAY_LINE" pin_append ~/.profile "$PATH_LINE" pin_append ~/.profile "$RELAY_LINE" # ---- 4. opencode ----------------------------------------------------------- if command -v opencode >/dev/null 2>&1 || [ -x "$HOME/.opencode/bin/opencode" ]; then ok "opencode already installed" else info "installing opencode..." curl -fsSL https://opencode.ai/install | bash fi [ -x "$HOME/.opencode/bin/opencode" ] && export PATH="$HOME/.opencode/bin:$PATH" # ---- 5. openchamber -------------------------------------------------------- if bun pm ls -g 2>/dev/null | grep -q "@openchamber/web"; then ok "openchamber already installed: $(openchamber --version 2>/dev/null || echo '?')" else info "installing @openchamber/web globally..." bun install -g @openchamber/web fi # ---- 6. opencode config sync ------------------------------------------------ CFG_DIR="$HOME/.config/opencode" CFG_FILE="$CFG_DIR/opencode.json" TMP_CFG="$(mktemp)" curl -fsSL --max-time 60 "$CONFIG_URL" -o "$TMP_CFG" || die "could not download config from $CONFIG_URL" if [ -f "$CFG_FILE" ] && cmp -s "$TMP_CFG" "$CFG_FILE"; then ok "opencode config already up to date" rm -f "$TMP_CFG" else mkdir -p "$CFG_DIR" if [ -f "$CFG_FILE" ]; then BAK="$CFG_FILE.bak.$(date +%Y%m%d-%H%M%S)" cp "$CFG_FILE" "$BAK" info "existing config backed up to $(basename "$BAK")" fi mv "$TMP_CFG" "$CFG_FILE" ok "opencode config installed from CONFIG_URL" fi if command -v python3 >/dev/null 2>&1; then python3 -m json.tool "$CFG_FILE" >/dev/null && ok "config is valid JSON" elif command -v bun >/dev/null 2>&1; then bun -e "JSON.parse(require('fs').readFileSync('$CFG_FILE','utf8'))" && ok "config is valid JSON" fi # ---- 7. verify -------------------------------------------------------------- echo info "==== verification ====" echo "bun: $(bun --version 2>/dev/null || echo MISSING)" echo "node shim: $(env node -e 'console.log("exec-ok")' 2>/dev/null || echo BROKEN)" echo "opencode: $(opencode --version 2>/dev/null || echo MISSING)" echo "openchamber:$(openchamber --version 2>/dev/null || echo MISSING)" HEALTH_URL="$(printf '%s' "$RELAY_URL" | sed 's|^wss://|https://|; s|/ws$|/health|')" if curl -fsS --max-time 15 "$HEALTH_URL" >/dev/null 2>&1; then ok "relay reachable: $HEALTH_URL" else warn "relay health check failed: $HEALTH_URL (may not be deployed yet)" fi # ---- 8. invite link (optional) ---------------------------------------------- if [ "$INVITE" = "1" ]; then info "generating invite link (relay transport embedded)..." if openchamber connect-url --relay -p "$INVITE_PORT" --qr; then ok "paste the openchamber:// link into OpenChamber: Settings > Remote Instances > add instance" else warn "invite link not generated - an instance may already be running on port $INVITE_PORT (openchamber stop -p $INVITE_PORT, then re-run with --invite)" fi fi echo ok "prerequisites complete"