diff --git a/AGENTS.md b/AGENTS.md index a5e34e0..647679f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -186,7 +186,7 @@ bin/ci/droplet.sh destroy "$DROPLET_ID" "$SSH_KEY_ID" Droplets take ~15s to create, ~10s for SSH, ~90s for full setup+tests. Always destroy after — they cost ~$0.003 per run but add up if forgotten. -The CI scripts (`bin/ci/setup-ubuntu.sh`, `bin/ci/setup-arch.sh`) run `install.sh` with simulated input, verify the result, then run the full test suite. Use them as-is or SSH in and test manually. +The CI scripts (`bin/ci/setup-ubuntu.sh`, `bin/ci/setup-arch.sh`) run the bootstrap flow (`bootstrap.sh` → `baudbot install`) with simulated input, verify the result, then run the full test suite. Use them as-is or SSH in and test manually. ## Security Notes diff --git a/README.md b/README.md index f234b8b..d1d0a23 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,10 @@ Baudbot is designed as shared engineering infrastructure, not a single-user desk ## Quick Start ```bash -git clone https://github.com/modem-dev/baudbot.git ~/baudbot -sudo ~/baudbot/install.sh +curl -fsSL https://raw.githubusercontent.com/modem-dev/baudbot/main/bootstrap.sh | bash +baudbot install ``` -Installer handles setup, dependencies, agent user creation, firewall setup, and initial configuration prompts. - After install: ```bash diff --git a/bin/baudbot b/bin/baudbot index d44a5ed..f8b6361 100755 --- a/bin/baudbot +++ b/bin/baudbot @@ -52,6 +52,7 @@ usage() { echo " sessions List agent tmux and pi sessions" echo "" echo -e "${BOLD}Setup:${RESET}" + echo " install Bootstrap install from GitHub (download script, then escalate)" echo " setup One-time system setup (user, deps, firewall, systemd)" echo " config Interactive secrets and config setup" echo " deploy Deploy source + config to agent runtime" @@ -76,6 +77,72 @@ require_root() { fi } +escalate_prefix() { + if [ "$(id -u)" -eq 0 ]; then + return 0 + fi + + if command -v sudo >/dev/null 2>&1; then + echo "sudo" + return 0 + fi + + if command -v doas >/dev/null 2>&1; then + echo "doas" + return 0 + fi + + echo "" + return 1 +} + +download_file() { + local url="$1" + local dest="$2" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$dest" + return 0 + fi + + if command -v wget >/dev/null 2>&1; then + wget -q "$url" -O "$dest" + return 0 + fi + + echo "❌ install requires curl or wget to download installer" + exit 1 +} + +bootstrap_install() { + local install_url="${BAUDBOT_INSTALL_SCRIPT_URL:-https://raw.githubusercontent.com/modem-dev/baudbot/main/install.sh}" + local install_script + local escalator="" + + install_script="$(mktemp /tmp/baudbot-install.XXXXXX.sh)" + trap 'rm -f "$install_script"' RETURN + + download_file "$install_url" "$install_script" + chmod 700 "$install_script" + + echo "Downloaded installer: $install_script" + + if [ "$(id -u)" -eq 0 ]; then + bash "$install_script" "$@" + return 0 + fi + + escalator="$(escalate_prefix || true)" + if [ -z "$escalator" ]; then + echo "❌ install requires root privileges but no sudo/doas was found" + echo "Re-run as root, or install sudo/doas first." + return 1 + fi + + echo "Escalating with $escalator for system setup..." + "$escalator" bash "$install_script" "$@" +} + # Detect systemd has_systemd() { command -v systemctl &>/dev/null && [ -d /run/systemd/system ] @@ -129,6 +196,11 @@ print_deployed_version() { } case "${1:-}" in + install) + shift + bootstrap_install "$@" + ;; + start) shift if [ "${1:-}" = "--direct" ]; then diff --git a/bin/ci/setup-arch.sh b/bin/ci/setup-arch.sh index 5e4a2d5..c42895c 100755 --- a/bin/ci/setup-arch.sh +++ b/bin/ci/setup-arch.sh @@ -18,12 +18,17 @@ tar xzf /tmp/baudbot-src.tar.gz chown -R baudbot_admin:baudbot_admin /home/baudbot_admin/ sudo -u baudbot_admin bash -c 'cd ~/baudbot && git init -q && git config user.email "ci@test" && git config user.name "CI" && git add -A && git commit -q -m "init"' -echo "=== Running install.sh ===" +echo "=== Running bootstrap + baudbot install ===" +# Bootstrap installs /usr/local/bin/baudbot, then baudbot install runs install.sh. +# Use file:// URLs so CI tests the uploaded source bundle (not GitHub main). +BAUDBOT_CLI_URL="file:///home/baudbot_admin/baudbot/bin/baudbot" \ +BAUDBOT_BOOTSTRAP_TARGET="/usr/local/bin/baudbot" \ + bash /home/baudbot_admin/baudbot/bootstrap.sh # Simulate interactive input: admin user, required secrets, skip optionals, decline launch # Prompts: admin user, Anthropic, OpenAI(skip), Gemini(skip), OpenCode(skip), # Slack bot, Slack app, Slack users, AgentMail(skip), email(skip), Sentry(skip), Kernel(skip), launch(n) printf 'baudbot_admin\nsk-ant-testkey\n\n\n\nxoxb-test\nxapp-test\nU01TEST\n\n\n\n\nn\n' \ - | bash /home/baudbot_admin/baudbot/install.sh + | BAUDBOT_INSTALL_SCRIPT_URL="file:///home/baudbot_admin/baudbot/install.sh" baudbot install echo "=== Verifying install ===" # .env exists with correct permissions @@ -55,7 +60,7 @@ echo "$HELP_OUT" | grep -q "baudbot" test -x /home/baudbot_agent/.varlock/bin/varlock # Agent can load env (smoke test — varlock validates schema + .env) sudo -u baudbot_agent bash -c 'export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" && cd ~ && varlock load --path ~/.config/' -echo " ✓ install.sh verification passed" +echo " ✓ bootstrap + install verification passed" echo "=== Installing test dependencies ===" export PATH="/home/baudbot_agent/opt/node-v22.14.0-linux-x64/bin:$PATH" diff --git a/bin/ci/setup-ubuntu.sh b/bin/ci/setup-ubuntu.sh index 7f8a872..aa5dbc3 100755 --- a/bin/ci/setup-ubuntu.sh +++ b/bin/ci/setup-ubuntu.sh @@ -29,12 +29,17 @@ tar xzf /tmp/baudbot-src.tar.gz chown -R baudbot_admin:baudbot_admin /home/baudbot_admin/ sudo -u baudbot_admin bash -c 'cd ~/baudbot && git init -q && git config user.email "ci@test" && git config user.name "CI" && git add -A && git commit -q -m "init"' -echo "=== Running install.sh ===" +echo "=== Running bootstrap + baudbot install ===" +# Bootstrap installs /usr/local/bin/baudbot, then baudbot install runs install.sh. +# Use file:// URLs so CI tests the uploaded source bundle (not GitHub main). +BAUDBOT_CLI_URL="file:///home/baudbot_admin/baudbot/bin/baudbot" \ +BAUDBOT_BOOTSTRAP_TARGET="/usr/local/bin/baudbot" \ + bash /home/baudbot_admin/baudbot/bootstrap.sh # Simulate interactive input: admin user, required secrets, skip optionals, decline launch # Prompts: admin user, Anthropic, OpenAI(skip), Gemini(skip), OpenCode(skip), # Slack bot, Slack app, Slack users, AgentMail(skip), email(skip), Sentry(skip), Kernel(skip), launch(n) printf 'baudbot_admin\nsk-ant-testkey\n\n\n\nxoxb-test\nxapp-test\nU01TEST\n\n\n\n\nn\n' \ - | bash /home/baudbot_admin/baudbot/install.sh + | BAUDBOT_INSTALL_SCRIPT_URL="file:///home/baudbot_admin/baudbot/install.sh" baudbot install echo "=== Verifying install ===" # .env exists with correct permissions @@ -66,7 +71,7 @@ echo "$HELP_OUT" | grep -q "baudbot" test -x /home/baudbot_agent/.varlock/bin/varlock # Agent can load env (smoke test — varlock validates schema + .env) sudo -u baudbot_agent bash -c 'export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH" && cd ~ && varlock load --path ~/.config/' -echo " ✓ install.sh verification passed" +echo " ✓ bootstrap + install verification passed" echo "=== Installing test dependencies ===" export PATH="/home/baudbot_agent/opt/node-v22.14.0-linux-x64/bin:$PATH" diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..8d91d07 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Baudbot bootstrap installer (non-root entrypoint) +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/modem-dev/baudbot/main/bootstrap.sh | bash +# baudbot install +# +# What this does: +# 1) Downloads the bootstrap baudbot CLI from GitHub +# 2) Installs it to /usr/local/bin/baudbot (using sudo/doas if needed) +# 3) Prints next step: `baudbot install` + +set -euo pipefail + +BAUDBOT_CLI_URL="${BAUDBOT_CLI_URL:-https://raw.githubusercontent.com/modem-dev/baudbot/main/bin/baudbot}" +BAUDBOT_TARGET_BIN="${BAUDBOT_BOOTSTRAP_TARGET:-/usr/local/bin/baudbot}" +TARGET_DIR="$(dirname "$BAUDBOT_TARGET_BIN")" +TMP_CLI="$(mktemp /tmp/baudbot-cli.XXXXXX)" + +cleanup() { + rm -f "$TMP_CLI" +} +trap cleanup EXIT + +download_file() { + local url="$1" + local dest="$2" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$dest" + return 0 + fi + + if command -v wget >/dev/null 2>&1; then + wget -q "$url" -O "$dest" + return 0 + fi + + echo "❌ bootstrap requires curl or wget" >&2 + exit 1 +} + +escalate_prefix() { + if [ "$(id -u)" -eq 0 ]; then + return 0 + fi + + if command -v sudo >/dev/null 2>&1; then + echo "sudo" + return 0 + fi + + if command -v doas >/dev/null 2>&1; then + echo "doas" + return 0 + fi + + echo "" + return 1 +} + +echo "==> Downloading baudbot bootstrap CLI" +download_file "$BAUDBOT_CLI_URL" "$TMP_CLI" +chmod 0755 "$TMP_CLI" + +if [ -w "$TARGET_DIR" ]; then + mkdir -p "$TARGET_DIR" + install -m 0755 "$TMP_CLI" "$BAUDBOT_TARGET_BIN" +else + ESCALATOR="$(escalate_prefix || true)" + if [ -z "$ESCALATOR" ]; then + echo "❌ cannot write to $TARGET_DIR and no sudo/doas found" >&2 + echo "Try running as root, or set BAUDBOT_BOOTSTRAP_TARGET to a user-writable path." >&2 + exit 1 + fi + + echo "==> Installing to $BAUDBOT_TARGET_BIN using $ESCALATOR" + "$ESCALATOR" mkdir -p "$TARGET_DIR" + "$ESCALATOR" install -m 0755 "$TMP_CLI" "$BAUDBOT_TARGET_BIN" +fi + +echo "✅ Installed baudbot bootstrap CLI to $BAUDBOT_TARGET_BIN" +echo "" +echo "Next step:" +echo " baudbot install" diff --git a/install.sh b/install.sh index 2e5364b..8865cea 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,11 @@ #!/bin/bash # Baudbot Interactive Installer # -# One-command setup: +# One-command setup (bootstrap CLI): +# curl -fsSL https://raw.githubusercontent.com/modem-dev/baudbot/main/bootstrap.sh | bash +# baudbot install +# +# Or if you prefer source checkout first: # git clone https://github.com/modem-dev/baudbot.git ~/baudbot && sudo ~/baudbot/install.sh # # Or if already cloned: @@ -143,18 +147,26 @@ info "Prerequisites installed" header "Source" -REPO_DIR="$ADMIN_HOME/baudbot" +REPO_DIR="${BAUDBOT_REPO_DIR:-$ADMIN_HOME/baudbot}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || echo "")" if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/setup.sh" ] && [ -f "$SCRIPT_DIR/bin/deploy.sh" ]; then - # Running from an existing clone + # Running from an existing checkout/snapshot REPO_DIR="$SCRIPT_DIR" - info "Using existing clone: $REPO_DIR" + info "Using existing source: $REPO_DIR" else - # Need to clone + # Need to locate or clone source if [ -d "$REPO_DIR/.git" ]; then - info "Repo already exists at $REPO_DIR, pulling latest..." - sudo -u "$ADMIN_USER" git -C "$REPO_DIR" pull --ff-only 2>&1 | tail -1 + if sudo -u "$ADMIN_USER" git -C "$REPO_DIR" remote get-url origin >/dev/null 2>&1; then + info "Repo already exists at $REPO_DIR, pulling latest..." + sudo -u "$ADMIN_USER" git -C "$REPO_DIR" pull --ff-only 2>&1 | tail -1 + elif [ -f "$REPO_DIR/setup.sh" ] && [ -f "$REPO_DIR/bin/deploy.sh" ]; then + info "Repo exists at $REPO_DIR (no origin remote), using local source snapshot" + else + die "Repo at $REPO_DIR has no origin and missing setup files" + fi + elif [ -f "$REPO_DIR/setup.sh" ] && [ -f "$REPO_DIR/bin/deploy.sh" ]; then + info "Using local source snapshot: $REPO_DIR" else REPO_URL="https://github.com/modem-dev/baudbot.git" info "Cloning $REPO_URL → $REPO_DIR"