-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·375 lines (316 loc) · 13.8 KB
/
install.sh
File metadata and controls
executable file
·375 lines (316 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#!/bin/bash
# Baudbot Interactive Installer
#
# 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:
# sudo ./install.sh
#
# What this does:
# 1. Detects distro, installs system prerequisites
# 2. Clones the repo (or uses existing clone)
# 3. Runs setup.sh (user, Node.js, firewall, etc.)
# 4. Walks you through secrets interactively
# 5. Deploys and launches the agent
#
# Must run as root. Tested on Ubuntu 24.04 and Arch Linux.
set -euo pipefail
EXPERIMENTAL=0
while [ "$#" -gt 0 ]; do
case "$1" in
--experimental)
EXPERIMENTAL=1
shift
;;
-h|--help)
echo "Usage: $0 [--experimental]"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--experimental]"
exit 1
;;
esac
done
# ── Formatting ───────────────────────────────────────────────────────────────
BOLD='\033[1m'
DIM='\033[2m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
RESET='\033[0m'
info() { echo -e "${BOLD}${GREEN}▸${RESET} $1"; }
warn() { echo -e "${BOLD}${YELLOW}▸${RESET} $1"; }
err() { echo -e "${BOLD}${RED}✗${RESET} $1" >&2; }
ask() { echo -en "${BOLD}${CYAN}?${RESET} $1"; }
dim() { echo -e "${DIM}$1${RESET}"; }
header() { echo -e "\n${BOLD}── $1 ──${RESET}\n"; }
die() { err "$1"; exit 1; }
# ── Preflight ────────────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
die "Must run as root. Try: sudo $0"
fi
if [[ ! "$(uname -s)" =~ Linux ]]; then
die "Baudbot requires Linux (kernel-level isolation). macOS/Windows are not supported."
fi
echo ""
echo -e "${BOLD}🤖 Baudbot Installer${RESET}"
if [ "$EXPERIMENTAL" -eq 1 ]; then
echo -e "${YELLOW}Experimental mode enabled: optional risky integrations may be installed.${RESET}"
fi
echo ""
# ── Detect distro ────────────────────────────────────────────────────────────
header "System"
DISTRO="unknown"
if [ -f /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release
case "$ID" in
ubuntu|debian) DISTRO="ubuntu" ;;
arch|archarm) DISTRO="arch" ;;
*)
if [ -n "${ID_LIKE:-}" ]; then
case "$ID_LIKE" in
*debian*|*ubuntu*) DISTRO="ubuntu" ;;
*arch*) DISTRO="arch" ;;
esac
fi
;;
esac
fi
if [ "$DISTRO" = "unknown" ]; then
die "Unsupported distro. Baudbot is tested on Ubuntu 24.04 and Arch Linux."
fi
info "Detected: ${BOLD}$PRETTY_NAME${RESET} ($DISTRO)"
# ── Detect admin user ────────────────────────────────────────────────────────
# If run via sudo, SUDO_USER is the real user. Otherwise detect or ask.
ADMIN_USER="${SUDO_USER:-}"
if [ -z "$ADMIN_USER" ] || [ "$ADMIN_USER" = "root" ]; then
# Try to find a non-root user with a home directory as the default candidate
CANDIDATE=""
while IFS=: read -r username _ uid _ _ home _; do
if [ "$uid" -ge 1000 ] && [ "$uid" -lt 60000 ] && [ -d "$home" ] && [ "$username" != "baudbot_agent" ]; then
CANDIDATE="$username"
break
fi
done < /etc/passwd
# Fall back to current user (typically root when running directly as root)
CANDIDATE="${CANDIDATE:-$(whoami)}"
ask "Admin username [${CANDIDATE}]: "
read -r input
ADMIN_USER="${input:-$CANDIDATE}"
fi
if ! id "$ADMIN_USER" &>/dev/null; then
die "User '$ADMIN_USER' does not exist."
fi
ADMIN_HOME=$(getent passwd "$ADMIN_USER" | cut -d: -f6)
info "Admin user: ${BOLD}$ADMIN_USER${RESET} ($ADMIN_HOME)"
# ── Install prerequisites ────────────────────────────────────────────────────
header "Prerequisites"
apt_background_procs_ubuntu() {
# Ignore unattended-upgrade-shutdown --wait-for-signal. It can remain active
# with no apt/dpkg lock contention and causes false waits on fresh Ubuntu VMs.
pgrep -f -a '(apt.systemd.daily|apt-get|dpkg|unattended-upgrade)' 2>/dev/null \
| grep -v 'unattended-upgrade-shutdown --wait-for-signal' || true
}
install_prereqs_ubuntu() {
# Wait for unattended-upgrades (common on fresh VMs)
if [ -n "$(apt_background_procs_ubuntu)" ]; then
info "Waiting for background apt to finish..."
for _ in $(seq 1 60); do
if [ -z "$(apt_background_procs_ubuntu)" ]; then
break
fi
sleep 2
done
fi
for attempt in $(seq 1 5); do
if DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=120 update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=120 install -y -qq git curl tmux iptables docker.io gh jq sudo 2>&1 | tail -3; then
return 0
fi
if [ "$attempt" -eq 5 ]; then
err "apt failed after $attempt attempts"
apt_background_procs_ubuntu >&2 || true
return 1
fi
warn "apt busy (attempt $attempt/5), retrying in 5s..."
sleep 5
done
}
install_prereqs_arch() {
pacman -Syu --noconfirm --needed git curl tmux iptables docker github-cli jq sudo 2>&1 | tail -5
}
info "Installing: git, curl, tmux, iptables, docker, gh, jq, sudo"
"install_prereqs_$DISTRO"
info "Prerequisites installed"
# ── Clone or locate repo ────────────────────────────────────────────────────
header "Source"
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 checkout/snapshot
REPO_DIR="$SCRIPT_DIR"
info "Using existing source: $REPO_DIR"
else
# Need to locate or clone source
if [ -d "$REPO_DIR/.git" ]; then
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"
sudo -u "$ADMIN_USER" git clone "$REPO_URL" "$REPO_DIR" 2>&1 | tail -1
fi
fi
if [ ! -f "$REPO_DIR/setup.sh" ]; then
die "setup.sh not found in $REPO_DIR — bad clone?"
fi
info "Source ready: $REPO_DIR"
# ── Run setup.sh ─────────────────────────────────────────────────────────────
header "Setup"
info "Running setup.sh (user, Node.js, firewall, permissions)..."
info "This takes 1–2 minutes."
echo ""
if [ "$EXPERIMENTAL" -eq 1 ]; then
BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" BAUDBOT_RUNTIME_NODE_VERSION="${BAUDBOT_RUNTIME_NODE_VERSION:-}" \
bash "$REPO_DIR/setup.sh" --experimental "$ADMIN_USER"
else
BAUDBOT_PI_VERSION="${BAUDBOT_PI_VERSION:-}" BAUDBOT_RUNTIME_NODE_VERSION="${BAUDBOT_RUNTIME_NODE_VERSION:-}" \
bash "$REPO_DIR/setup.sh" "$ADMIN_USER"
fi
echo ""
info "Core setup complete"
# ── Configure secrets ────────────────────────────────────────────────────────
header "Secrets"
BAUDBOT_HOME="/home/baudbot_agent"
ENV_FILE="$BAUDBOT_HOME/.config/.env"
# Run baudbot config to collect secrets into ~/.baudbot/.env on the admin user.
# config.sh handles prompting, validation, and writing to the admin config dir.
BAUDBOT_CONFIG_USER="$ADMIN_USER" BAUDBOT_EXPERIMENTAL="$EXPERIMENTAL" bash "$REPO_DIR/bin/config.sh"
# Publish and deploy the initial git-free release from the local checkout.
# This also copies ~/.baudbot/.env → agent's ~/.config/.env with correct perms.
header "Deploy"
BOOTSTRAP_BRANCH=$(sudo -u "$ADMIN_USER" git -C "$REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
BAUDBOT_ROOT="$REPO_DIR" BAUDBOT_CONFIG_USER="$ADMIN_USER" BAUDBOT_EXPERIMENTAL="$EXPERIMENTAL" \
bash "$REPO_DIR/bin/update-release.sh" --repo "$REPO_DIR" --branch "$BOOTSTRAP_BRANCH" --skip-preflight --skip-restart
# ── Launch ───────────────────────────────────────────────────────────────────
header "Launch"
# Check if we have the minimum required secrets (read from deployed .env)
MISSING=""
HAS_LLM=false
if [ -f "$ENV_FILE" ]; then
for k in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENCODE_ZEN_API_KEY; do
if grep -q "^${k}=.\+" "$ENV_FILE" 2>/dev/null; then HAS_LLM=true; break; fi
done
fi
# Also check auth.json for OAuth subscription credentials
AUTH_JSON="$BAUDBOT_HOME/.pi/agent/auth.json"
if [ "$HAS_LLM" = false ] && [ -f "$AUTH_JSON" ] && command -v jq &>/dev/null; then
for oauth_provider in "openai-codex" "anthropic"; do
if jq -e --arg p "$oauth_provider" '.[$p]' "$AUTH_JSON" &>/dev/null; then
HAS_LLM=true
break
fi
done
fi
if [ "$HAS_LLM" = false ]; then
MISSING+=" - LLM key (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, or OPENCODE_ZEN_API_KEY) or subscription login (sudo baudbot login)\n"
fi
HAS_SOCKET=false
HAS_BROKER=false
has_env_any() {
local preferred_key="$1"
local legacy_key="$2"
grep -q "^${preferred_key}=.\+" "$ENV_FILE" 2>/dev/null || grep -q "^${legacy_key}=.\+" "$ENV_FILE" 2>/dev/null
}
if has_env_any GATEWAY_BOT_TOKEN SLACK_BOT_TOKEN \
&& has_env_any GATEWAY_APP_TOKEN SLACK_APP_TOKEN; then
HAS_SOCKET=true
fi
if has_env_any GATEWAY_BROKER_URL SLACK_BROKER_URL \
&& (has_env_any GATEWAY_BROKER_ORG_ID SLACK_BROKER_ORG_ID || has_env_any GATEWAY_BROKER_WORKSPACE_ID SLACK_BROKER_WORKSPACE_ID) \
&& has_env_any GATEWAY_BROKER_SERVER_PRIVATE_KEY SLACK_BROKER_SERVER_PRIVATE_KEY \
&& has_env_any GATEWAY_BROKER_SERVER_PUBLIC_KEY SLACK_BROKER_SERVER_PUBLIC_KEY \
&& has_env_any GATEWAY_BROKER_SERVER_SIGNING_PRIVATE_KEY SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY \
&& has_env_any GATEWAY_BROKER_PUBLIC_KEY SLACK_BROKER_PUBLIC_KEY \
&& has_env_any GATEWAY_BROKER_SIGNING_PUBLIC_KEY SLACK_BROKER_SIGNING_PUBLIC_KEY; then
HAS_BROKER=true
fi
if [ "$HAS_SOCKET" = false ] && [ "$HAS_BROKER" = false ]; then
MISSING+=" - Gateway bridge integration (either GATEWAY_BOT_TOKEN + GATEWAY_APP_TOKEN, legacy SLACK_BOT_TOKEN + SLACK_APP_TOKEN, or broker registration via 'sudo baudbot broker register')\n"
fi
if ! has_env_any GATEWAY_ALLOWED_USERS SLACK_ALLOWED_USERS; then
warn "GATEWAY_ALLOWED_USERS/SLACK_ALLOWED_USERS not set — all workspace members will be allowed"
fi
if [ -n "$MISSING" ]; then
warn "Missing required secrets — skipping launch:"
echo -e "$MISSING"
echo -e "Add them with ${BOLD}baudbot config${RESET} and deploy with ${BOLD}baudbot deploy${RESET}"
echo ""
else
ask "Start the agent now? [Y/n]: "
read -r launch
if [ -z "$launch" ] || [[ "$launch" =~ ^[Yy] ]]; then
info "Launching agent..."
if command -v systemctl &>/dev/null && [ -d /run/systemd/system ]; then
systemctl start baudbot 2>/dev/null || true
sleep 2
if systemctl is-active baudbot &>/dev/null 2>&1; then
info "Agent is running ✓"
else
warn "Agent didn't start — check: baudbot logs"
fi
else
sudo -u baudbot_agent tmux new-session -d -s baudbot "$BAUDBOT_HOME/runtime/start.sh" 2>/dev/null || true
sleep 2
if sudo -u baudbot_agent tmux has-session -t baudbot 2>/dev/null; then
info "Agent is running ✓"
else
warn "Agent didn't start — try: baudbot start --direct"
fi
fi
else
info "Skipped. Start later with:"
echo -e " ${DIM}sudo baudbot start${RESET}"
fi
fi
# ── Done ─────────────────────────────────────────────────────────────────────
header "Done"
SSH_PUB="$BAUDBOT_HOME/.ssh/id_ed25519.pub"
echo -e "🐝 ${BOLD}Baudbot is installed.${RESET}"
echo ""
echo -e " ${BOLD}Start agent:${RESET} sudo baudbot start"
echo -e " ${BOLD}Agent status:${RESET} sudo baudbot status"
echo -e " ${BOLD}View logs:${RESET} sudo baudbot logs"
echo -e " ${BOLD}Edit secrets:${RESET} sudo baudbot config && sudo baudbot deploy"
echo -e " ${BOLD}Deploy changes:${RESET} sudo baudbot deploy"
echo -e " ${BOLD}Health check:${RESET} sudo baudbot doctor"
echo -e " ${BOLD}Security audit:${RESET} sudo baudbot audit"
echo ""
if [ -f "$SSH_PUB" ]; then
echo -e " ${YELLOW}⚠${RESET} Add the agent's SSH key to GitHub:"
echo -e " $(cat "$SSH_PUB")"
echo -e " ${DIM}https://github.com/settings/keys${RESET}"
echo ""
fi
echo -e " ${YELLOW}⚠${RESET} Authenticate GitHub CLI:"
echo -e " sudo -u baudbot_agent gh auth login"
echo ""
echo -e " ${DIM}Full configuration reference: $REPO_DIR/CONFIGURATION.md${RESET}"
echo ""