Skip to content

Commit 216220d

Browse files
author
AndreyMarchuk
committed
agent: add cli session shim and remote install/repair workflows
1 parent d8bbcee commit 216220d

34 files changed

Lines changed: 5437 additions & 61 deletions

.env.schema

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ SLACK_ALLOWED_USERS=
5454
# @sensitive=false @type=number
5555
BAUDBOT_EXPERIMENTAL=0
5656

57+
# ── Dev Agent Backend ────────────────────────────────────────────────────────
58+
59+
# Default backend for spawning dev agents.
60+
# Control-agent may override per-task.
61+
# Options: pi, claude-code, codex, auto
62+
# @sensitive=false @type=string
63+
DEV_AGENT_BACKEND=pi
64+
5765
# ── Email Monitor (experimental-only) ───────────────────────────────────────
5866

5967
# AgentMail API key (only used when BAUDBOT_EXPERIMENTAL=1)

CONFIGURATION.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ Email tooling is disabled by default. To enable it, run setup/install in experim
6868

6969
## Optional Variables
7070

71+
### Dev Agent Backend
72+
73+
| Variable | Description | Default |
74+
|----------|-------------|---------|
75+
| `DEV_AGENT_BACKEND` | Default backend for spawning dev agents (`pi`, `claude-code`, `codex`, `auto`) | `pi` |
76+
7177
### Sentry Integration
7278

7379
| Variable | Description | How to get it |
@@ -149,6 +155,28 @@ Set during `setup.sh` / `baudbot install` via env vars:
149155
| `GIT_USER_NAME` | Git commit author name | `baudbot-agent` |
150156
| `GIT_USER_EMAIL` | Git commit author email | `baudbot-agent@users.noreply.github.com` |
151157

158+
### Remote CLI (operator-local, not runtime)
159+
160+
These apply only to `baudbot remote ...` when run from your local operator machine. They are not part of agent runtime `.env` and should not be written to `/home/baudbot_agent/.config/.env`.
161+
162+
| Variable | Description | Default |
163+
|----------|-------------|---------|
164+
| `BAUDBOT_REMOTE_DIR` | Local state directory for remote targets/checkpoints/keys | `~/.baudbot/remote` |
165+
| `HETZNER_API_TOKEN` | Hetzner token fallback for `--hetzner-token` | *(empty)* |
166+
| `TAILSCALE_AUTHKEY` | Tailscale auth key fallback for `--tailscale-auth-key` | *(empty)* |
167+
| `REMOTE_BOOTSTRAP_URL` | Bootstrap script URL used by remote install step | `https://github.com/modem-dev/baudbot/main/bootstrap.sh` |
168+
| `REMOTE_TAILSCALE_INSTALL_URL` | Tailscale install script URL used by remote workflow | `https://tailscale.com/install.sh` |
169+
| `REMOTE_TAILSCALE_WAIT_ATTEMPTS` | Tailscale readiness polling attempts after `tailscale up` | `40` |
170+
| `REMOTE_TAILSCALE_WAIT_INTERVAL_SEC` | Delay between Tailscale readiness polls | `3` |
171+
| `REMOTE_CHECKPOINT_MAX_RETRIES` | Retries per install checkpoint before interactive escalation | `3` |
172+
| `REMOTE_HETZNER_SERVER_TYPE` | Hetzner default server type for remote install | `cpx11` |
173+
| `REMOTE_HETZNER_IMAGE` | Hetzner default image for remote install | `ubuntu-24.04` |
174+
| `REMOTE_HETZNER_LOCATION` | Hetzner default location for remote install | `ash` |
175+
| `REMOTE_HETZNER_WAIT_TIMEOUT_SEC` | Timeout while waiting for server running state | `600` |
176+
| `REMOTE_HETZNER_WAIT_INTERVAL_SEC` | Poll interval while waiting for server running state | `5` |
177+
| `REMOTE_SSH_REACHABLE_ATTEMPTS` | SSH readiness attempts per checkpoint | `40` |
178+
| `REMOTE_SSH_REACHABLE_INTERVAL_SEC` | Delay between SSH readiness attempts | `3` |
179+
152180
### Heartbeat
153181

154182
| Variable | Description | Default |

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ Upgrade later:
8383
sudo baudbot update
8484
```
8585

86+
Remote provisioning/install and repair (operator-run from your local machine):
87+
88+
```bash
89+
# Provision on Hetzner and install Baudbot
90+
baudbot remote install --mode hetzner --target team-bot
91+
92+
# Install on an existing host
93+
baudbot remote install --mode host --target team-bot --host 203.0.113.10 --ssh-user root
94+
95+
# Install + connect host to Tailscale
96+
baudbot remote install --mode host --target team-bot --host 203.0.113.10 --tailscale
97+
98+
# Resume an interrupted run
99+
baudbot remote resume team-bot
100+
101+
# Guided repair for an existing target
102+
baudbot remote repair --target team-bot
103+
```
104+
105+
`baudbot remote` persists checkpoints in `~/.baudbot/remote/targets/*.json`, so interrupted installs can resume from the next incomplete checkpoint.
106+
86107
Install with a specific pi version (optional):
87108

88109
```bash

bin/baudbot

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ usage() {
138138
echo " install Bootstrap install from GitHub (download script, then escalate)"
139139
echo " setup One-time system setup (user, deps, firewall, systemd; --experimental enables risky integrations)"
140140
echo " config Interactive secrets and config setup"
141+
echo " remote Remote install/repair workflows (Hetzner or existing host)"
141142
echo " env Manage env vars and backend source (set/get/sync/backend)"
142143
echo " deploy Deploy source + config to agent runtime"
143144
echo " broker Slack broker commands (register workspace linkage)"
@@ -411,6 +412,11 @@ case "${1:-}" in
411412
exec "$BAUDBOT_ROOT/bin/config.sh" "$@"
412413
;;
413414

415+
remote)
416+
shift
417+
exec "$BAUDBOT_ROOT/bin/remote.sh" "$@"
418+
;;
419+
414420
env)
415421
shift
416422
exec "$BAUDBOT_ROOT/bin/env.sh" "$@"

bin/baudbot.test.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,35 @@ EOF
191191
)
192192
}
193193

194+
test_remote_dispatches_to_remote_script() {
195+
(
196+
set -euo pipefail
197+
local tmp out
198+
tmp="$(mktemp -d /tmp/baudbot-cli-test.XXXXXX)"
199+
trap 'rm -rf "$tmp"' EXIT
200+
201+
mkdir -p "$tmp/bin/lib"
202+
printf '{"version":"1.2.3"}\n' > "$tmp/package.json"
203+
cat > "$tmp/bin/lib/baudbot-runtime.sh" <<'EOF'
204+
#!/bin/bash
205+
cmd_status() { :; }
206+
cmd_logs() { :; }
207+
cmd_sessions() { :; }
208+
cmd_attach() { :; }
209+
has_systemd() { return 0; }
210+
EOF
211+
212+
cat > "$tmp/bin/remote.sh" <<'EOF'
213+
#!/bin/bash
214+
echo "remote-dispatch-ok:$*"
215+
EOF
216+
chmod +x "$tmp/bin/remote.sh"
217+
218+
out="$(BAUDBOT_ROOT="$tmp" bash "$CLI" remote list)"
219+
[ "$out" = "remote-dispatch-ok:list" ]
220+
)
221+
}
222+
194223
echo "=== baudbot cli tests ==="
195224
echo ""
196225

@@ -199,6 +228,7 @@ run_test "status dispatches via runtime module" test_status_dispatches_via_runti
199228
run_test "attach requires root" test_attach_requires_root
200229
run_test "broker register requires root" test_broker_register_requires_root
201230
run_test "restart kills bridge tmux then restarts systemd" test_restart_restarts_systemd_and_kills_bridge_tmux
231+
run_test "remote command dispatches to remote.sh" test_remote_dispatches_to_remote_script
202232

203233
echo ""
204234
echo "=== $PASSED/$TOTAL passed, $FAILED failed ==="

bin/broker-register.test.mjs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ test("registerWithBroker sends registration_token when provided", async () => {
189189
});
190190
});
191191

192-
test("runRegistration integration path succeeds against live local HTTP server", async () => {
192+
test("runRegistration integration path succeeds against live local HTTP server", async (t) => {
193193
const brokerPubkey = Buffer.alloc(32, 5).toString("base64");
194194
const brokerSigningPubkey = Buffer.alloc(32, 6).toString("base64");
195195

@@ -222,7 +222,21 @@ test("runRegistration integration path succeeds against live local HTTP server",
222222
res.end(JSON.stringify({ ok: false, error: "not found" }));
223223
});
224224

225-
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
225+
try {
226+
await new Promise((resolve, reject) => {
227+
server.once("error", reject);
228+
server.listen(0, "127.0.0.1", resolve);
229+
});
230+
} catch (error) {
231+
if (error && typeof error === "object" && "code" in error) {
232+
const code = String(error.code || "");
233+
if (code === "EPERM" || code === "EACCES") {
234+
t.skip("Localhost bind is not permitted in this environment");
235+
return;
236+
}
237+
}
238+
throw error;
239+
}
226240
const address = server.address();
227241
const brokerUrl = `http://127.0.0.1:${address.port}`;
228242

0 commit comments

Comments
 (0)