176 lines
7.0 KiB
Bash
176 lines
7.0 KiB
Bash
# 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 <port>
|
|
# (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 <file> <line>
|
|
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 <file> <line>
|
|
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" |