diff --git a/openchamber-boostrap.sh b/openchamber-boostrap.sh new file mode 100644 index 0000000..563d9ce --- /dev/null +++ b/openchamber-boostrap.sh @@ -0,0 +1,176 @@ +# 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" \ No newline at end of file