Skip to content

Commit 339734d

Browse files
authored
deploy: move live updates to git-free /opt release snapshots (#56)
1 parent d176e76 commit 339734d

15 files changed

Lines changed: 1187 additions & 74 deletions

AGENTS.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ Baudbot is hardened infrastructure for running always-on AI agents. Source is ad
77
```
88
bin/ security & operations scripts
99
baudbot CLI (attach, sessions, update, deploy)
10-
deploy.sh stages source → /tmp → agent runtime (run as admin)
10+
deploy.sh deploys a prepared source tree → agent runtime
11+
update-release.sh temp checkout → git-free /opt release snapshot → deploy
12+
rollback-release.sh rollback to previous or specified /opt release snapshot
1113
security-audit.sh security posture audit
1214
setup-firewall.sh iptables per-UID egress allowlist
1315
baudbot-safe-bash shell command deny list (installed to /usr/local/bin)
@@ -60,17 +62,27 @@ See [CONFIGURATION.md](CONFIGURATION.md) for all env vars and how to obtain them
6062

6163
## Architecture: Source / Runtime Separation
6264

63-
The admin owns the source (`~/baudbot/`). The agent (`baudbot_agent` user) owns the runtime. The agent **cannot read the source repo** — admin home is `700`.
65+
The admin owns source checkouts (for example `~/baudbot/`). The agent (`baudbot_agent` user) owns runtime state. The agent **cannot read the source repo** — admin home is `700`.
66+
67+
Live operations are now release-based under `/opt/baudbot` (git-free):
6468

65-
Deploy is a one-way push:
6669
```
67-
admin: ~/baudbot/bin/deploy.sh
68-
→ stages to /tmp/baudbot-deploy.XXXXXX (world-readable)
69-
→ copies as baudbot_agent via sudo -u
70-
→ stamps baudbot-version.json + baudbot-manifest.json (SHA256 hashes)
71-
→ cleans up staging dir
70+
/opt/baudbot/
71+
├── releases/<sha>/ immutable snapshot (no .git)
72+
├── current -> releases/<sha> active release symlink
73+
└── previous -> releases/<sha> previous release symlink (for rollback)
7274
```
7375

76+
`baudbot update` flow:
77+
1) clone target ref into `/tmp/baudbot-update.*`
78+
2) run preflight checks in temp checkout
79+
3) publish git-free snapshot to `/opt/baudbot/releases/<sha>`
80+
4) deploy runtime files from snapshot
81+
5) restart + health check
82+
6) atomically switch `/opt/baudbot/current`
83+
84+
`baudbot rollback previous|<sha>` re-deploys an existing snapshot and flips `current`/`previous` without network access.
85+
7486
Agent runtime layout:
7587
```
7688
/home/baudbot_agent/
@@ -97,10 +109,16 @@ sudo ~/baudbot/install.sh
97109

98110
# Edit source files directly in ~/baudbot/
99111

100-
# Deploy to agent runtime
112+
# For source-only changes (extensions/skills/bridge), deploy directly:
101113
~/baudbot/bin/deploy.sh
102114

103-
# Launch agent
115+
# For operational updates from git (recommended for live bot):
116+
sudo baudbot update
117+
118+
# Roll back live bot to previous snapshot if needed:
119+
sudo baudbot rollback previous
120+
121+
# Launch agent directly (debug/dev)
104122
sudo -u baudbot_agent ~/runtime/start.sh
105123

106124
# Or in tmux
@@ -110,7 +128,7 @@ tmux new-window -n baudbot 'sudo -u baudbot_agent ~/runtime/start.sh'
110128
## Running Tests
111129

112130
```bash
113-
# All tests (8 suites)
131+
# All tests (10 suites)
114132
bin/test.sh
115133

116134
# Only JS/TS tests

CONFIGURATION.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ The agent also uses an SSH key (`~/.ssh/id_ed25519`) for git push. Setup generat
7676
| `BAUDBOT_AGENT_HOME` | Agent's home directory | `/home/$BAUDBOT_AGENT_USER` |
7777
| `BAUDBOT_SOURCE_DIR` | Path to admin-owned source repo | *(empty — set this to enable source repo write protection)* |
7878

79+
### Release Updater / Rollback (CLI env overrides)
80+
81+
These are **command-time overrides** for `baudbot update` / `baudbot rollback` (or the underlying scripts). They are not required in `~/.config/.env`.
82+
83+
| Variable | Description | Default |
84+
|----------|-------------|---------|
85+
| `BAUDBOT_RELEASE_ROOT` | Root directory for git-free release snapshots | `/opt/baudbot` |
86+
| `BAUDBOT_RELEASES_DIR` | Release snapshot directory | `$BAUDBOT_RELEASE_ROOT/releases` |
87+
| `BAUDBOT_CURRENT_LINK` | Active release symlink | `$BAUDBOT_RELEASE_ROOT/current` |
88+
| `BAUDBOT_PREVIOUS_LINK` | Previous release symlink | `$BAUDBOT_RELEASE_ROOT/previous` |
89+
| `BAUDBOT_UPDATE_REPO` | Update source repo URL/path override | auto-detected / remembered |
90+
| `BAUDBOT_UPDATE_BRANCH` | Update source branch override | remembered / `main` |
91+
7992
### Control Plane
8093

8194
The control plane runs as the admin user, not `baudbot_agent`. These env vars are for the admin's environment.
@@ -151,9 +164,9 @@ BAUDBOT_SOURCE_DIR=/home/your_username/baudbot
151164
After editing `~/.config/.env`:
152165

153166
```bash
154-
# Restart the agent to pick up changes
155-
sudo -u baudbot_agent pkill -u baudbot_agent
156-
sudo -u baudbot_agent ~/runtime/start.sh
167+
# Re-deploy config and restart cleanly
168+
sudo baudbot deploy
169+
sudo baudbot restart
157170
```
158171

159172
The bridge and all sub-agents load `~/.config/.env` on startup. If varlock is installed, variables are validated against `.env.schema` before injection.

README.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,18 @@ The installer detects your distro, installs dependencies, creates the agent user
4545
<summary>Manual setup (without installer)</summary>
4646

4747
```bash
48-
# Creates user, firewall, permissions (run as root)
48+
# Creates user, firewall, /opt release layout, permissions (run as root)
4949
sudo bash ~/baudbot/setup.sh <admin_username>
5050

5151
# Add secrets
52-
sudo -u baudbot_agent vim ~/.config/.env
52+
sudo baudbot config
5353

54-
# Deploy source to agent runtime
55-
~/baudbot/bin/deploy.sh
54+
# Deploy config/source snapshot to runtime
55+
sudo baudbot deploy
5656

5757
# Launch
5858
sudo -u baudbot_agent ~/runtime/start.sh
5959
```
60-
6160
See [CONFIGURATION.md](CONFIGURATION.md) for the full list of secrets and how to obtain them.
6261
</details>
6362

@@ -133,10 +132,16 @@ admin_user (your account)
133132
│ ├── pi/extensions/ 🔒 tool-guard, auto-name, etc.
134133
│ ├── pi/skills/ agent skill templates
135134
│ ├── slack-bridge/ 🔒 bridge + security module
136-
│ └── setup.sh / start.sh system setup + launcher
135+
│ └── setup.sh / start.sh system setup + launcher
136+
137+
root-owned operational releases (git-free)
138+
├── /opt/baudbot/
139+
│ ├── releases/<sha>/ immutable snapshot (no .git)
140+
│ ├── current -> releases/<sha> active release symlink
141+
│ └── previous -> releases/<sha> previous release symlink
137142
138143
baudbot_agent (unprivileged uid)
139-
├── ~/runtime/ deployed copies of bin/, bridge
144+
├── ~/runtime/ deployed copies used at runtime
140145
├── ~/.pi/agent/
141146
│ ├── extensions/ deployed extensions (read-only)
142147
│ ├── skills/ agent-owned (can modify)
@@ -147,7 +152,7 @@ baudbot_agent (unprivileged uid)
147152
└── ~/.config/.env secrets (600 perms)
148153
```
149154

150-
Deploy is a one-way push: `~/baudbot/bin/deploy.sh` stages source to `/tmp`, copies as `baudbot_agent` via `sudo -u`, stamps an integrity manifest, and cleans up.
155+
`baudbot update` creates a temp checkout (`/tmp/baudbot-update.*`), runs preflight checks, publishes a git-free snapshot to `/opt/baudbot/releases/<sha>`, deploys runtime files, then atomically switches `/opt/baudbot/current` on success.
151156

152157
## Control Plane
153158

@@ -175,8 +180,14 @@ curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:28800/config # config
175180
## Operations
176181

177182
```bash
178-
# Deploy after editing source
179-
~/baudbot/bin/deploy.sh
183+
# Update live bot from upstream (temp checkout -> /opt release snapshot -> deploy)
184+
sudo baudbot update
185+
186+
# Roll back live bot to the previous snapshot
187+
sudo baudbot rollback previous
188+
189+
# Deploy source/config from current active release
190+
sudo baudbot deploy
180191

181192
# Launch agent (tmux for persistence)
182193
tmux new-window -n baudbot 'sudo -u baudbot_agent ~/runtime/start.sh'
@@ -198,11 +209,10 @@ sudo ~/baudbot/bin/uninstall.sh # for real
198209
# Check deployed version
199210
sudo -u baudbot_agent cat ~/.pi/agent/baudbot-version.json
200211
```
201-
202212
## Tests
203213

204214
```bash
205-
# All tests across 8 suites
215+
# All tests across 10 suites
206216
bin/test.sh
207217

208218
# JS/TS only
@@ -221,15 +231,15 @@ An agent role is a skill file. Baudbot ships three but you can add more.
221231

222232
1. Create `pi/skills/my-agent/SKILL.md` with role instructions.
223233
2. Add a tmux session spawn for the new agent in `pi/skills/control-agent/SKILL.md` (the control agent manages sub-agent lifecycle).
224-
3. Deploy: `~/baudbot/bin/deploy.sh`
234+
3. Deploy: `sudo baudbot deploy`
225235

226236
See `pi/skills/dev-agent/SKILL.md` for the pattern.
227237

228238
## Security stack
229239

230240
| Layer | What | Survives prompt injection? |
231241
|-------|------|---------------------------|
232-
| **Source isolation** | Source repo is admin-owned. Agent has zero read access. Deploy is one-way. | ✅ Filesystem |
242+
| **Source isolation** | Source repo is admin-owned. Agent has zero read access. Live `/opt/baudbot/releases/*` snapshots are git-free immutable artifacts. | ✅ Filesystem |
233243
| **iptables egress** | Per-UID port allowlist (80/443/22/53 + DB ports). Blocks non-standard ports, listeners, raw sockets. | ✅ Kernel |
234244
| **Process isolation** | `/proc` mounted `hidepid=2`. Agent can't see other PIDs. | ✅ Kernel |
235245
| **File permissions** | Security-critical files deployed `chmod a-w`. Agent can't modify `tool-guard.ts`, `security.mjs`, etc. even via `sed` or `python`. | ✅ Filesystem |

bin/baudbot

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ usage() {
6060
echo " doctor Health check (perms, firewall, secrets, deps)"
6161
echo " audit Security posture audit"
6262
echo " test Run test suite"
63-
echo " update Pull latest and redeploy"
63+
echo " update Build/test in temp checkout, publish git-free release, deploy"
64+
echo " rollback Re-deploy previous (or specified) git-free release snapshot"
6465
echo " uninstall Remove everything"
6566
echo ""
6667
echo -e "${BOLD}Options:${RESET}"
@@ -255,27 +256,13 @@ case "${1:-}" in
255256
update)
256257
shift
257258
require_root "update"
258-
echo "=== Pulling latest ==="
259-
cd "$BAUDBOT_ROOT"
260-
git pull origin main
261-
echo ""
262-
echo "=== Deploying ==="
263-
"$BAUDBOT_ROOT/bin/deploy.sh" "$@"
264-
echo ""
265-
# Restart if currently running
266-
if has_systemd && systemctl is-active baudbot &>/dev/null; then
267-
echo "=== Restarting ==="
268-
systemctl restart baudbot
269-
# Wait for ExecStartPre hooks + process init
270-
sleep 3
271-
if systemctl is-active baudbot &>/dev/null; then
272-
echo "✅ Updated and running"
273-
else
274-
echo "⚠️ Deployed but agent didn't restart — check: baudbot logs"
275-
fi
276-
else
277-
echo "✅ Updated. Start with: sudo baudbot start"
278-
fi
259+
exec "$BAUDBOT_ROOT/bin/update-release.sh" "$@"
260+
;;
261+
262+
rollback)
263+
shift
264+
require_root "rollback"
265+
exec "$BAUDBOT_ROOT/bin/rollback-release.sh" "$@"
279266
;;
280267

281268
uninstall)

bin/ci/setup-arch.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@ grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_admin/.baudbot/.env
3838
grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_agent/.config/.env
3939
grep -q "SLACK_BOT_TOKEN=xoxb-test" /home/baudbot_agent/.config/.env
4040
grep -q "BAUDBOT_SOURCE_DIR=" /home/baudbot_agent/.config/.env
41-
# CLI installed
41+
# CLI installed and points at /opt release snapshot
4242
test -L /usr/local/bin/baudbot
43+
test -d /opt/baudbot/releases
44+
test -L /opt/baudbot/current
45+
CLI_TARGET=$(readlink -f /usr/local/bin/baudbot)
46+
echo "$CLI_TARGET" | grep -qE '^/opt/baudbot/releases/.+/bin/baudbot$'
47+
# /opt releases must be git-free
48+
! find /opt/baudbot/releases -type d -name .git -print -quit | grep -q .
4349
baudbot --version
4450
HELP_OUT=$(baudbot --help)
4551
echo "$HELP_OUT" | grep -q "baudbot"

bin/ci/setup-ubuntu.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_admin/.baudbot/.env
4949
grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_agent/.config/.env
5050
grep -q "SLACK_BOT_TOKEN=xoxb-test" /home/baudbot_agent/.config/.env
5151
grep -q "BAUDBOT_SOURCE_DIR=" /home/baudbot_agent/.config/.env
52-
# CLI installed
52+
# CLI installed and points at /opt release snapshot
5353
test -L /usr/local/bin/baudbot
54+
test -d /opt/baudbot/releases
55+
test -L /opt/baudbot/current
56+
CLI_TARGET=$(readlink -f /usr/local/bin/baudbot)
57+
echo "$CLI_TARGET" | grep -qE '^/opt/baudbot/releases/.+/bin/baudbot$'
58+
# /opt releases must be git-free
59+
! find /opt/baudbot/releases -type d -name .git -print -quit | grep -q .
5460
baudbot --version
5561
HELP_OUT=$(baudbot --help)
5662
echo "$HELP_OUT" | grep -q "baudbot"

bin/deploy.sh

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ ADMIN_CONFIG="$DEPLOY_HOME/.baudbot/.env"
288288
if [ -f "$ADMIN_CONFIG" ]; then
289289
if [ "$DRY_RUN" -eq 0 ]; then
290290
as_agent bash -c "mkdir -p '$BAUDBOT_HOME/.config'"
291-
cp "$ADMIN_CONFIG" "$BAUDBOT_HOME/.config/.env"
292-
chown "$AGENT_USER:$AGENT_USER" "$BAUDBOT_HOME/.config/.env"
293-
chmod 600 "$BAUDBOT_HOME/.config/.env"
291+
# Stream directly to agent-owned target to avoid staging secrets in /tmp.
292+
as_agent bash -c "cat > '$BAUDBOT_HOME/.config/.env'" < "$ADMIN_CONFIG"
293+
as_agent chmod 600 "$BAUDBOT_HOME/.config/.env"
294294
log "✓ .env → ~/.config/.env (600)"
295295
else
296296
log "would copy: $ADMIN_CONFIG → ~/.config/.env"
@@ -313,10 +313,25 @@ VERSION_FILE="$VERSION_DIR/baudbot-version.json"
313313
MANIFEST_FILE="$VERSION_DIR/baudbot-manifest.json"
314314

315315
if [ "$DRY_RUN" -eq 0 ]; then
316-
# Get git info from source (admin can read it)
317-
GIT_SHA=$(cd "$BAUDBOT_SRC" && git rev-parse HEAD 2>/dev/null || echo "unknown")
318-
GIT_SHA_SHORT=$(cd "$BAUDBOT_SRC" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
319-
GIT_BRANCH=$(cd "$BAUDBOT_SRC" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
316+
# Get source metadata from git (dev checkout) or release metadata (git-free /opt release).
317+
RELEASE_META_FILE="$BAUDBOT_SRC/baudbot-release.json"
318+
GIT_SHA=""
319+
GIT_SHA_SHORT=""
320+
GIT_BRANCH=""
321+
322+
if (cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1); then
323+
GIT_SHA=$(cd "$BAUDBOT_SRC" && git rev-parse HEAD 2>/dev/null || echo "unknown")
324+
GIT_SHA_SHORT=$(cd "$BAUDBOT_SRC" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
325+
GIT_BRANCH=$(cd "$BAUDBOT_SRC" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
326+
elif [ -f "$RELEASE_META_FILE" ]; then
327+
GIT_SHA=$(grep '"sha"' "$RELEASE_META_FILE" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || true)
328+
GIT_SHA_SHORT=$(grep '"short"' "$RELEASE_META_FILE" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || true)
329+
GIT_BRANCH=$(grep '"branch"' "$RELEASE_META_FILE" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || true)
330+
fi
331+
332+
[ -n "$GIT_SHA" ] || GIT_SHA="unknown"
333+
[ -n "$GIT_SHA_SHORT" ] || GIT_SHA_SHORT="unknown"
334+
[ -n "$GIT_BRANCH" ] || GIT_BRANCH="unknown"
320335
DEPLOY_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
321336

322337
# Write version file via agent

0 commit comments

Comments
 (0)