Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Baudbot is hardened infrastructure for running always-on AI agents. Source is ad
```
bin/ security & operations scripts
baudbot CLI (attach, sessions, update, deploy)
deploy.sh stages source → /tmp → agent runtime (run as admin)
deploy.sh deploys a prepared source tree → agent runtime
update-release.sh temp checkout → git-free /opt release snapshot → deploy
rollback-release.sh rollback to previous or specified /opt release snapshot
security-audit.sh security posture audit
setup-firewall.sh iptables per-UID egress allowlist
baudbot-safe-bash shell command deny list (installed to /usr/local/bin)
Expand Down Expand Up @@ -60,17 +62,27 @@ See [CONFIGURATION.md](CONFIGURATION.md) for all env vars and how to obtain them

## Architecture: Source / Runtime Separation

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`.
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`.

Live operations are now release-based under `/opt/baudbot` (git-free):

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

`baudbot update` flow:
1) clone target ref into `/tmp/baudbot-update.*`
2) run preflight checks in temp checkout
3) publish git-free snapshot to `/opt/baudbot/releases/<sha>`
4) deploy runtime files from snapshot
5) restart + health check
6) atomically switch `/opt/baudbot/current`

`baudbot rollback previous|<sha>` re-deploys an existing snapshot and flips `current`/`previous` without network access.

Agent runtime layout:
```
/home/baudbot_agent/
Expand All @@ -97,10 +109,16 @@ sudo ~/baudbot/install.sh

# Edit source files directly in ~/baudbot/

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

# Launch agent
# For operational updates from git (recommended for live bot):
sudo baudbot update

# Roll back live bot to previous snapshot if needed:
sudo baudbot rollback previous

# Launch agent directly (debug/dev)
sudo -u baudbot_agent ~/runtime/start.sh

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

```bash
# All tests (8 suites)
# All tests (10 suites)
bin/test.sh

# Only JS/TS tests
Expand Down
19 changes: 16 additions & 3 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ The agent also uses an SSH key (`~/.ssh/id_ed25519`) for git push. Setup generat
| `BAUDBOT_AGENT_HOME` | Agent's home directory | `/home/$BAUDBOT_AGENT_USER` |
| `BAUDBOT_SOURCE_DIR` | Path to admin-owned source repo | *(empty — set this to enable source repo write protection)* |

### Release Updater / Rollback (CLI env overrides)

These are **command-time overrides** for `baudbot update` / `baudbot rollback` (or the underlying scripts). They are not required in `~/.config/.env`.

| Variable | Description | Default |
|----------|-------------|---------|
| `BAUDBOT_RELEASE_ROOT` | Root directory for git-free release snapshots | `/opt/baudbot` |
| `BAUDBOT_RELEASES_DIR` | Release snapshot directory | `$BAUDBOT_RELEASE_ROOT/releases` |
| `BAUDBOT_CURRENT_LINK` | Active release symlink | `$BAUDBOT_RELEASE_ROOT/current` |
| `BAUDBOT_PREVIOUS_LINK` | Previous release symlink | `$BAUDBOT_RELEASE_ROOT/previous` |
| `BAUDBOT_UPDATE_REPO` | Update source repo URL/path override | auto-detected / remembered |
| `BAUDBOT_UPDATE_BRANCH` | Update source branch override | remembered / `main` |

### Control Plane

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

```bash
# Restart the agent to pick up changes
sudo -u baudbot_agent pkill -u baudbot_agent
sudo -u baudbot_agent ~/runtime/start.sh
# Re-deploy config and restart cleanly
sudo baudbot deploy
sudo baudbot restart
```

The bridge and all sub-agents load `~/.config/.env` on startup. If varlock is installed, variables are validated against `.env.schema` before injection.
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,18 @@ The installer detects your distro, installs dependencies, creates the agent user
<summary>Manual setup (without installer)</summary>

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

# Add secrets
sudo -u baudbot_agent vim ~/.config/.env
sudo baudbot config

# Deploy source to agent runtime
~/baudbot/bin/deploy.sh
# Deploy config/source snapshot to runtime
sudo baudbot deploy

# Launch
sudo -u baudbot_agent ~/runtime/start.sh
```

See [CONFIGURATION.md](CONFIGURATION.md) for the full list of secrets and how to obtain them.
</details>

Expand Down Expand Up @@ -133,10 +132,16 @@ admin_user (your account)
│ ├── pi/extensions/ 🔒 tool-guard, auto-name, etc.
│ ├── pi/skills/ agent skill templates
│ ├── slack-bridge/ 🔒 bridge + security module
│ └── setup.sh / start.sh system setup + launcher
│ └── setup.sh / start.sh system setup + launcher

root-owned operational releases (git-free)
├── /opt/baudbot/
│ ├── releases/<sha>/ immutable snapshot (no .git)
│ ├── current -> releases/<sha> active release symlink
│ └── previous -> releases/<sha> previous release symlink

baudbot_agent (unprivileged uid)
├── ~/runtime/ deployed copies of bin/, bridge
├── ~/runtime/ deployed copies used at runtime
├── ~/.pi/agent/
│ ├── extensions/ deployed extensions (read-only)
│ ├── skills/ agent-owned (can modify)
Expand All @@ -147,7 +152,7 @@ baudbot_agent (unprivileged uid)
└── ~/.config/.env secrets (600 perms)
```

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.
`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.

## Control Plane

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

```bash
# Deploy after editing source
~/baudbot/bin/deploy.sh
# Update live bot from upstream (temp checkout -> /opt release snapshot -> deploy)
sudo baudbot update

# Roll back live bot to the previous snapshot
sudo baudbot rollback previous

# Deploy source/config from current active release
sudo baudbot deploy

# Launch agent (tmux for persistence)
tmux new-window -n baudbot 'sudo -u baudbot_agent ~/runtime/start.sh'
Expand All @@ -198,11 +209,10 @@ sudo ~/baudbot/bin/uninstall.sh # for real
# Check deployed version
sudo -u baudbot_agent cat ~/.pi/agent/baudbot-version.json
```

## Tests

```bash
# All tests across 8 suites
# All tests across 10 suites
bin/test.sh

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

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

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

## Security stack

| Layer | What | Survives prompt injection? |
|-------|------|---------------------------|
| **Source isolation** | Source repo is admin-owned. Agent has zero read access. Deploy is one-way. | ✅ Filesystem |
| **Source isolation** | Source repo is admin-owned. Agent has zero read access. Live `/opt/baudbot/releases/*` snapshots are git-free immutable artifacts. | ✅ Filesystem |
| **iptables egress** | Per-UID port allowlist (80/443/22/53 + DB ports). Blocks non-standard ports, listeners, raw sockets. | ✅ Kernel |
| **Process isolation** | `/proc` mounted `hidepid=2`. Agent can't see other PIDs. | ✅ Kernel |
| **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 |
Expand Down
31 changes: 9 additions & 22 deletions bin/baudbot
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ usage() {
echo " doctor Health check (perms, firewall, secrets, deps)"
echo " audit Security posture audit"
echo " test Run test suite"
echo " update Pull latest and redeploy"
echo " update Build/test in temp checkout, publish git-free release, deploy"
echo " rollback Re-deploy previous (or specified) git-free release snapshot"
echo " uninstall Remove everything"
echo ""
echo -e "${BOLD}Options:${RESET}"
Expand Down Expand Up @@ -255,27 +256,13 @@ case "${1:-}" in
update)
shift
require_root "update"
echo "=== Pulling latest ==="
cd "$BAUDBOT_ROOT"
git pull origin main
echo ""
echo "=== Deploying ==="
"$BAUDBOT_ROOT/bin/deploy.sh" "$@"
echo ""
# Restart if currently running
if has_systemd && systemctl is-active baudbot &>/dev/null; then
echo "=== Restarting ==="
systemctl restart baudbot
# Wait for ExecStartPre hooks + process init
sleep 3
if systemctl is-active baudbot &>/dev/null; then
echo "✅ Updated and running"
else
echo "⚠️ Deployed but agent didn't restart — check: baudbot logs"
fi
else
echo "✅ Updated. Start with: sudo baudbot start"
fi
exec "$BAUDBOT_ROOT/bin/update-release.sh" "$@"
;;

rollback)
shift
require_root "rollback"
exec "$BAUDBOT_ROOT/bin/rollback-release.sh" "$@"
;;

uninstall)
Expand Down
8 changes: 7 additions & 1 deletion bin/ci/setup-arch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_admin/.baudbot/.env
grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_agent/.config/.env
grep -q "SLACK_BOT_TOKEN=xoxb-test" /home/baudbot_agent/.config/.env
grep -q "BAUDBOT_SOURCE_DIR=" /home/baudbot_agent/.config/.env
# CLI installed
# CLI installed and points at /opt release snapshot
test -L /usr/local/bin/baudbot
test -d /opt/baudbot/releases
test -L /opt/baudbot/current
CLI_TARGET=$(readlink -f /usr/local/bin/baudbot)
echo "$CLI_TARGET" | grep -qE '^/opt/baudbot/releases/.+/bin/baudbot$'
# /opt releases must be git-free
! find /opt/baudbot/releases -type d -name .git -print -quit | grep -q .
baudbot --version
HELP_OUT=$(baudbot --help)
echo "$HELP_OUT" | grep -q "baudbot"
Expand Down
8 changes: 7 additions & 1 deletion bin/ci/setup-ubuntu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_admin/.baudbot/.env
grep -q "ANTHROPIC_API_KEY=sk-ant-testkey" /home/baudbot_agent/.config/.env
grep -q "SLACK_BOT_TOKEN=xoxb-test" /home/baudbot_agent/.config/.env
grep -q "BAUDBOT_SOURCE_DIR=" /home/baudbot_agent/.config/.env
# CLI installed
# CLI installed and points at /opt release snapshot
test -L /usr/local/bin/baudbot
test -d /opt/baudbot/releases
test -L /opt/baudbot/current
CLI_TARGET=$(readlink -f /usr/local/bin/baudbot)
echo "$CLI_TARGET" | grep -qE '^/opt/baudbot/releases/.+/bin/baudbot$'
# /opt releases must be git-free
! find /opt/baudbot/releases -type d -name .git -print -quit | grep -q .
baudbot --version
HELP_OUT=$(baudbot --help)
echo "$HELP_OUT" | grep -q "baudbot"
Expand Down
29 changes: 22 additions & 7 deletions bin/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ ADMIN_CONFIG="$DEPLOY_HOME/.baudbot/.env"
if [ -f "$ADMIN_CONFIG" ]; then
if [ "$DRY_RUN" -eq 0 ]; then
as_agent bash -c "mkdir -p '$BAUDBOT_HOME/.config'"
cp "$ADMIN_CONFIG" "$BAUDBOT_HOME/.config/.env"
chown "$AGENT_USER:$AGENT_USER" "$BAUDBOT_HOME/.config/.env"
chmod 600 "$BAUDBOT_HOME/.config/.env"
# Stream directly to agent-owned target to avoid staging secrets in /tmp.
as_agent bash -c "cat > '$BAUDBOT_HOME/.config/.env'" < "$ADMIN_CONFIG"
as_agent chmod 600 "$BAUDBOT_HOME/.config/.env"
log "✓ .env → ~/.config/.env (600)"
else
log "would copy: $ADMIN_CONFIG → ~/.config/.env"
Expand All @@ -313,10 +313,25 @@ VERSION_FILE="$VERSION_DIR/baudbot-version.json"
MANIFEST_FILE="$VERSION_DIR/baudbot-manifest.json"

if [ "$DRY_RUN" -eq 0 ]; then
# Get git info from source (admin can read it)
GIT_SHA=$(cd "$BAUDBOT_SRC" && git rev-parse HEAD 2>/dev/null || echo "unknown")
GIT_SHA_SHORT=$(cd "$BAUDBOT_SRC" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_BRANCH=$(cd "$BAUDBOT_SRC" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
# Get source metadata from git (dev checkout) or release metadata (git-free /opt release).
RELEASE_META_FILE="$BAUDBOT_SRC/baudbot-release.json"
GIT_SHA=""
GIT_SHA_SHORT=""
GIT_BRANCH=""

if (cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1); then
GIT_SHA=$(cd "$BAUDBOT_SRC" && git rev-parse HEAD 2>/dev/null || echo "unknown")
GIT_SHA_SHORT=$(cd "$BAUDBOT_SRC" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_BRANCH=$(cd "$BAUDBOT_SRC" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
elif [ -f "$RELEASE_META_FILE" ]; then
GIT_SHA=$(grep '"sha"' "$RELEASE_META_FILE" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || true)
GIT_SHA_SHORT=$(grep '"short"' "$RELEASE_META_FILE" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || true)
GIT_BRANCH=$(grep '"branch"' "$RELEASE_META_FILE" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/' || true)
fi

[ -n "$GIT_SHA" ] || GIT_SHA="unknown"
[ -n "$GIT_SHA_SHORT" ] || GIT_SHA_SHORT="unknown"
[ -n "$GIT_BRANCH" ] || GIT_BRANCH="unknown"
DEPLOY_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Write version file via agent
Expand Down
Loading