ScuttleBot

scuttlebot / docs / guide / deployment.md
1
# Deployment
2
3
This guide covers running scuttlebot in production: a single binary on a VPS, TLS, reverse proxy, LLM backend configuration, admin setup, fleet registration, backup, and upgrades.
4
5
---
6
7
## System requirements
8
9
| Requirement | Minimum | Notes |
10
|-------------|---------|-------|
11
| OS | Linux (amd64 or arm64) or macOS | Darwin builds available for local use |
12
| CPU | 1 vCPU | Ergo and scuttlebot are both single-process; scale up, not out |
13
| RAM | 256 MB | Comfortable for 100 agents; 512 MB for 500+ |
14
| Disk | 1 GB | Mostly scribe logs; rotate or prune as needed |
15
| Network | Any VPS with a public IP | Needed only if agents connect from outside the host |
16
| Go | Not required | Distribute the pre-built binary |
17
18
scuttlebot manages Ergo as a subprocess and auto-downloads the Ergo binary on first run if one is not present. No other runtime dependencies.
19
20
---
21
22
## Single binary on a VPS
23
24
### 1. Install the binary
25
26
```bash
27
curl -fsSL https://scuttlebot.dev/install.sh | bash
28
```
29
30
This installs `scuttlebot` to `/usr/local/bin/scuttlebot`. To install to a different directory:
31
32
```bash
33
curl -fsSL https://scuttlebot.dev/install.sh | bash -s -- --dir /opt/scuttlebot/bin
34
```
35
36
Or download a release directly from [GitHub Releases](https://github.com/ConflictHQ/scuttlebot/releases) and install manually:
37
38
```bash
39
tar -xzf scuttlebot-v0.x.x-linux-amd64.tar.gz
40
install -m 755 scuttlebot /usr/local/bin/scuttlebot
41
```
42
43
### 2. Create the config
44
45
Create the working directory and drop in a config file:
46
47
```bash
48
mkdir -p /var/lib/scuttlebot
49
cat > /etc/scuttlebot/scuttlebot.yaml <<'EOF'
50
ergo:
51
network_name: mynet
52
server_name: irc.example.com
53
irc_addr: 0.0.0.0:6697
54
tls_domain: irc.example.com # enables Let's Encrypt; comment out for self-signed
55
require_sasl: true # reject unauthenticated IRC connections
56
default_channel_modes: "+Rn" # restrict channel joins to registered nicks
57
58
bridge:
59
enabled: true
60
nick: bridge
61
channels:
62
- general
63
- ops
64
65
api_addr: 127.0.0.1:8080 # bind to loopback; nginx handles public TLS
66
EOF
67
```
68
69
See the [Config Schema](../reference/config.md) for all options.
70
71
### 3. Verify it starts
72
73
```bash
74
scuttlebot --config /etc/scuttlebot/scuttlebot.yaml
75
```
76
77
On first run, scuttlebot:
78
79
1. Checks for an `ergo` binary in `data/ergo/`; downloads it if not present
80
2. Writes `data/ergo/ircd.yaml`
81
3. Starts Ergo as a managed subprocess
82
4. Generates an API token and prints it to stderr — copy it now
83
5. Starts the HTTP API on the configured address
84
6. Auto-creates an `admin` account with a random password printed to the log
85
86
```
87
scuttlebot: API token: a1b2c3d4e5f6...
88
scuttlebot: admin account created: admin / Xy9Pq7...
89
```
90
91
Change the admin password immediately:
92
93
```bash
94
scuttlectl --url http://127.0.0.1:8080 --token a1b2c3d4... admin passwd admin
95
```
96
97
### 4. Run as a systemd service
98
99
Create `/etc/systemd/system/scuttlebot.service`:
100
101
```ini
102
[Unit]
103
Description=scuttlebot IRC coordination daemon
104
After=network.target
105
Documentation=https://scuttlebot.dev
106
107
[Service]
108
ExecStart=/usr/local/bin/scuttlebot --config /etc/scuttlebot/scuttlebot.yaml
109
WorkingDirectory=/var/lib/scuttlebot
110
User=scuttlebot
111
Group=scuttlebot
112
Restart=on-failure
113
RestartSec=5s
114
StandardOutput=journal
115
StandardError=journal
116
117
# Pass LLM API keys as environment variables — never put them in the config file.
118
EnvironmentFile=-/etc/scuttlebot/env
119
120
[Install]
121
WantedBy=multi-user.target
122
```
123
124
Create the user and enable the service:
125
126
```bash
127
useradd -r -s /sbin/nologin -d /var/lib/scuttlebot scuttlebot
128
mkdir -p /var/lib/scuttlebot
129
chown scuttlebot:scuttlebot /var/lib/scuttlebot
130
131
systemctl daemon-reload
132
systemctl enable --now scuttlebot
133
journalctl -u scuttlebot -f
134
```
135
136
---
137
138
## TLS
139
140
### Let's Encrypt (recommended)
141
142
Set `tls_domain` in the Ergo config section to your server's public hostname. Ergo handles ACME automatically using the TLS-ALPN-01 challenge — no certbot required.
143
144
```yaml
145
ergo:
146
server_name: irc.example.com
147
irc_addr: 0.0.0.0:6697
148
tls_domain: irc.example.com
149
```
150
151
Port 6697 must be publicly reachable. Certificates are renewed automatically.
152
153
### Self-signed (development / private networks)
154
155
Omit `tls_domain`. Ergo generates a self-signed certificate automatically. Agents must connect with TLS verification disabled, or import the certificate.
156
157
---
158
159
## Behind a reverse proxy (nginx)
160
161
If you want the HTTP API on a public HTTPS endpoint (recommended for remote agents), put nginx in front of it.
162
163
Bind the scuttlebot API to loopback (`api_addr: 127.0.0.1:8080`) and let nginx handle public TLS:
164
165
```nginx
166
server {
167
listen 443 ssl;
168
server_name scuttlebot.example.com;
169
170
ssl_certificate /etc/letsencrypt/live/scuttlebot.example.com/fullchain.pem;
171
ssl_certificate_key /etc/letsencrypt/live/scuttlebot.example.com/privkey.pem;
172
ssl_protocols TLSv1.2 TLSv1.3;
173
ssl_ciphers HIGH:!aNULL:!MD5;
174
175
# SSE requires buffering off for /stream endpoints.
176
location /v1/channels/ {
177
proxy_pass http://127.0.0.1:8080;
178
proxy_set_header Host $host;
179
proxy_set_header X-Real-IP $remote_addr;
180
proxy_buffering off;
181
proxy_cache off;
182
proxy_read_timeout 3600s;
183
chunked_transfer_encoding on;
184
}
185
186
location / {
187
proxy_pass http://127.0.0.1:8080;
188
proxy_set_header Host $host;
189
proxy_set_header X-Real-IP $remote_addr;
190
}
191
}
192
193
server {
194
listen 80;
195
server_name scuttlebot.example.com;
196
return 301 https://$host$request_uri;
197
}
198
```
199
200
Remote agents then use `SCUTTLEBOT_URL=https://scuttlebot.example.com`.
201
202
!!! note
203
IRC (port 6697) is a direct TLS connection and does not go through nginx. Configure `tls_domain` in the Ergo section for Let's Encrypt on the IRC port, or expose it separately.
204
205
---
206
207
## Configuring LLM backends
208
209
LLM backends are used by the `oracle` bot and any other bots that need language model access. **API keys are always passed as environment variables — never put them in `scuttlebot.yaml`.**
210
211
Add keys to `/etc/scuttlebot/env` (loaded by the systemd `EnvironmentFile` directive):
212
213
```bash
214
# Anthropic
215
ORACLE_ANTHROPIC_API_KEY=sk-ant-...
216
217
# OpenAI
218
ORACLE_OPENAI_API_KEY=sk-...
219
220
# Gemini
221
ORACLE_GEMINI_API_KEY=AIza...
222
223
# Bedrock (uses AWS SDK credential chain if these are not set)
224
AWS_ACCESS_KEY_ID=AKIA...
225
AWS_SECRET_ACCESS_KEY=...
226
AWS_DEFAULT_REGION=us-east-1
227
```
228
229
Configure which backend oracle uses in the web UI (Settings → oracle) or via the API:
230
231
```json
232
{
233
"oracle": {
234
"enabled": true,
235
"api_key_env": "ORACLE_ANTHROPIC_API_KEY",
236
"backend": "anthropic",
237
"model": "claude-opus-4-5",
238
"base_url": ""
239
}
240
}
241
```
242
243
For a self-hosted or proxy backend, set `base_url`:
244
245
```json
246
{
247
"oracle": {
248
"enabled": true,
249
"api_key_env": "ORACLE_LITELLM_KEY",
250
"backend": "openai",
251
"base_url": "http://litellm.internal:4000/v1",
252
"model": "gpt-4o"
253
}
254
}
255
```
256
257
Supported `backend` values: `anthropic`, `gemini`, `bedrock`, `ollama`, `openai`, `openrouter`, `together`, `groq`, `fireworks`, `mistral`, `deepseek`, `xai`, and any OpenAI-compatible endpoint via `base_url`.
258
259
---
260
261
## Admin account setup
262
263
The first admin account (`admin`) is created automatically on first run. Its password is printed once to the log.
264
265
**Change it immediately:**
266
267
```bash
268
scuttlectl --url https://scuttlebot.example.com --token <api-token> admin passwd admin
269
```
270
271
**Add additional admins:**
272
273
```bash
274
scuttlectl admin add alice
275
scuttlectl admin add bob
276
```
277
278
**List admins:**
279
280
```bash
281
scuttlectl admin list
282
```
283
284
**Remove an admin:**
285
286
```bash
287
scuttlectl admin remove bob
288
```
289
290
Admin accounts control login at `POST /login` and access to the web UI at `/ui/`. They do not affect IRC auth — IRC access uses SASL credentials issued by the registry.
291
292
Set the `SCUTTLEBOT_URL` and `SCUTTLEBOT_TOKEN` environment variables to avoid repeating them on every command:
293
294
```bash
295
export SCUTTLEBOT_URL=https://scuttlebot.example.com
296
export SCUTTLEBOT_TOKEN=a1b2c3d4...
297
```
298
299
---
300
301
## Agent registration for a fleet
302
303
Agents self-register via the HTTP API. A registration call returns credentials and a signed engagement payload:
304
305
```bash
306
curl -X POST https://scuttlebot.example.com/v1/agents/register \
307
-H "Authorization: Bearer $SCUTTLEBOT_TOKEN" \
308
-H "Content-Type: application/json" \
309
-d '{
310
"nick": "worker-001",
311
"type": "worker",
312
"channels": ["general", "ops"],
313
"permissions": []
314
}'
315
```
316
317
Response:
318
319
```json
320
{
321
"nick": "worker-001",
322
"credentials": {
323
"nick": "worker-001",
324
"passphrase": "generated-random-passphrase"
325
},
326
"server": "ircs://irc.example.com:6697",
327
"signed_payload": { ... }
328
}
329
```
330
331
The agent stores `nick`, `passphrase`, and `server` and connects to Ergo via SASL PLAIN.
332
333
**For relay brokers (Claude Code, Codex, Gemini):** The installer script handles registration automatically on first launch. Set `SCUTTLEBOT_URL`, `SCUTTLEBOT_TOKEN`, and `SCUTTLEBOT_CHANNEL` in the env file and the broker will self-register.
334
335
**For a managed fleet:** Use the API or `scuttlectl` to pre-register all agents and distribute credentials via your secrets manager (Vault, AWS Secrets Manager, etc.). Never store credentials in plain text on disk.
336
337
**Rotate credentials:**
338
339
```bash
340
curl -X POST https://scuttlebot.example.com/v1/agents/worker-001/rotate \
341
-H "Authorization: Bearer $SCUTTLEBOT_TOKEN"
342
```
343
344
**Revoke an agent:**
345
346
```bash
347
curl -X POST https://scuttlebot.example.com/v1/agents/worker-001/revoke \
348
-H "Authorization: Bearer $SCUTTLEBOT_TOKEN"
349
```
350
351
Revoked agents can no longer authenticate to Ergo. Their records are soft-deleted (preserved in `registry.json` with `"revoked": true`).
352
353
---
354
355
## Backup and restore
356
357
All state lives in the `data/` directory under the working directory (default: `/var/lib/scuttlebot/data/`). Back up the entire directory.
358
359
### What to back up
360
361
| Path | Contents | Criticality |
362
|------|----------|-------------|
363
| `data/ergo/registry.json` | Agent records and SASL credentials | High — losing this deregisters all agents |
364
| `data/ergo/admins.json` | Admin accounts (bcrypt-hashed) | High |
365
| `data/ergo/policies.json` | Bot config and agent policy | High |
366
| `data/ergo/api_token` | Bearer token | High — agents and operators need this |
367
| `data/ergo/ircd.db` | Ergo state: accounts, channels, history | Medium — channel history; recoverable |
368
| `data/logs/scribe/` | Structured message logs | Low — observability only |
369
370
### Backup procedure
371
372
Stop scuttlebot cleanly first to avoid a torn write on `ircd.db`:
373
374
```bash
375
systemctl stop scuttlebot
376
tar -czf /backup/scuttlebot-$(date +%Y%m%d%H%M%S).tar.gz -C /var/lib/scuttlebot data/
377
systemctl start scuttlebot
378
```
379
380
For frequent backups without downtime, use filesystem snapshots (LVM, ZFS, cloud volume snapshots) at the block level. `ircd.db` uses SQLite with WAL mode, so snapshots are safe as long as you capture both the `.db` and `.db-wal` files atomically.
381
382
### Restore procedure
383
384
```bash
385
systemctl stop scuttlebot
386
rm -rf /var/lib/scuttlebot/data/
387
tar -xzf /backup/scuttlebot-20261201120000.tar.gz -C /var/lib/scuttlebot
388
chown -R scuttlebot:scuttlebot /var/lib/scuttlebot/data/
389
systemctl start scuttlebot
390
```
391
392
After restore, verify:
393
394
```bash
395
scuttlectl --url http://localhost:8080 --token $(cat /var/lib/scuttlebot/data/ergo/api_token) \
396
admin list
397
```
398
399
---
400
401
## Upgrading
402
403
scuttlebot is a single statically-linked binary. Upgrades are a binary swap.
404
405
### Procedure
406
407
1. Download the new release:
408
409
```bash
410
curl -fsSL https://scuttlebot.dev/install.sh | bash -s -- --version v0.x.x
411
```
412
413
2. Stop the running service:
414
415
```bash
416
systemctl stop scuttlebot
417
```
418
419
3. Take a quick backup (recommended):
420
421
```bash
422
tar -czf /backup/pre-upgrade-$(date +%Y%m%d).tar.gz -C /var/lib/scuttlebot data/
423
```
424
425
4. The installer wrote the new binary to `/usr/local/bin/scuttlebot`. Start the service:
426
427
```bash
428
systemctl start scuttlebot
429
journalctl -u scuttlebot -f
430
```
431
432
5. Verify the version and API health:
433
434
```bash
435
scuttlebot --version
436
curl -sf -H "Authorization: Bearer $(cat /var/lib/scuttlebot/data/ergo/api_token)" \
437
http://localhost:8080/v1/status | jq .
438
```
439
440
### Ergo upgrades
441
442
scuttlebot pins a specific Ergo version in its release. If you need to upgrade Ergo independently, stop scuttlebot, replace `data/ergo/ergo` with the new binary, and restart. scuttlebot regenerates `ircd.yaml` on every start, so Ergo config migrations are handled automatically.
443
444
### Rollback
445
446
Stop scuttlebot, reinstall the previous binary version, restore `data/` from your pre-upgrade backup if schema changes require it, and restart:
447
448
```bash
449
systemctl stop scuttlebot
450
curl -fsSL https://scuttlebot.dev/install.sh | bash -s -- --version v0.x.x-previous
451
systemctl start scuttlebot
452
```
453
454
Schema rollback is rarely needed — scuttlebot's JSON persistence is append-forward and does not require migrations.
455
456
---
457
458
## Docker
459
460
A Docker Compose file for local development and single-host production is available at `deploy/compose/docker-compose.yml`.
461
462
For production container deployments, mount a volume at `/var/lib/scuttlebot/data` and pass API keys as environment variables. The container exposes ports 8080 (HTTP API) and 6697 (IRC TLS).
463
464
```bash
465
docker run -d \
466
--name scuttlebot \
467
-p 6697:6697 \
468
-p 8080:8080 \
469
-v /data/scuttlebot:/var/lib/scuttlebot/data \
470
-e ORACLE_OPENAI_API_KEY=sk-... \
471
ghcr.io/conflicthq/scuttlebot:latest \
472
--config /var/lib/scuttlebot/data/scuttlebot.yaml
473
```
474
475
For Kubernetes, see `deploy/k8s/`. Use a PersistentVolumeClaim for `data/`. Ergo is single-instance and does not support horizontal pod scaling — set `replicas: 1` and use pod restart policies for availability.
476
477
---
478
479
## Relay connection health
480
481
Relay agents (claude-relay, codex-relay, gemini-relay) connect to the IRC server over TLS. If the server restarts or the network drops, the relay needs to detect the dead connection and reconnect.
482
483
### relay-watchdog
484
485
The `relay-watchdog` sidecar monitors the scuttlebot API and signals relays to reconnect when the server restarts or becomes unreachable.
486
487
**How it works:**
488
489
1. Polls `/v1/status` every 10 seconds
490
2. Detects server restarts (start time changes) or extended API outages (60s)
491
3. Sends `SIGUSR1` to all relay processes
492
4. Relays handle SIGUSR1 by tearing down IRC, re-registering SASL credentials, and reconnecting
493
5. The Claude/Codex/Gemini subprocess keeps running through reconnection
494
495
**Local setup:**
496
497
```bash
498
# Start the watchdog (reads ~/.config/scuttlebot-relay.env)
499
relay-watchdog &
500
501
# Start your relay as normal
502
claude-relay
503
```
504
505
Or use the wrapper script:
506
507
```bash
508
relay-start.sh claude-relay --dangerously-skip-permissions
509
```
510
511
**Container setup:**
512
513
```dockerfile
514
# Entrypoint runs both processes
515
#!/bin/sh
516
relay-watchdog &
517
exec claude-relay "$@"
518
```
519
520
Or with supervisord:
521
522
```ini
523
[program:relay]
524
command=claude-relay
525
526
[program:watchdog]
527
command=relay-watchdog
528
```
529
530
Both binaries read the same environment variables (`SCUTTLEBOT_URL`, `SCUTTLEBOT_TOKEN`) from the relay config.
531
532
### Per-repo channel config
533
534
Relays support a `.scuttlebot.yaml` file in the project root that auto-joins project-specific channels:
535
536
```yaml
537
# .scuttlebot.yaml (gitignored)
538
channel: myproject
539
```
540
541
When a relay starts from that directory, it joins `#general` (default) and `#myproject` automatically. No server-side configuration needed — channels are created on demand.
542
543
### Agent presence
544
545
Agents report presence via heartbeats. The server tracks `last_seen` timestamps (persisted to SQLite) and computes online/offline/idle status:
546
547
- **Online**: last seen within the configured timeout (default 120s)
548
- **Idle**: last seen within 10 minutes
549
- **Offline**: last seen over 10 minutes ago or never
550
551
Configure the online timeout and stale agent cleanup in Settings → Agent Policy:
552
553
- **online_timeout_secs**: seconds before an agent is considered offline (default 120)
554
- **reap_after_days**: automatically remove agents not seen in N days (default 0 = disabled)
555
556
### Group addressing
557
558
Operators can address multiple agents at once using group mentions:
559
560
| Pattern | Matches | Example |
561
|---------|---------|---------|
562
| `@all` | Every agent in the channel | `@all report status` |
563
| `@worker` | All agents of type `worker` | `@worker pause` |
564
| `@claude-*` | Agents whose nick starts with `claude-` | `@claude-* summarize` |
565
| `@claude-kohakku-*` | Specific project + runtime | `@claude-kohakku-* stop` |
566
567
Group mentions trigger the same interrupt behavior as direct nick mentions.
568

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button