ScuttleBot
security: loopback defaults, require_sasl, default_channel_modes API and MCP servers now bind to 127.0.0.1 by default (were :8080/:8081). Container deployments set SCUTTLEBOT_API_ADDR=:8080 via Dockerfile ENV. Docker Compose and K8s already had explicit overrides — unaffected. New Ergo config options surfaced in ircdconfig.go template: - ergo.require_sasl: reject unauthenticated IRC connections at connect time - ergo.default_channel_modes: channel modes on creation (default +n, set +Rn to restrict joins to registered nicks) Also fixes carried forward from docs accuracy pass: - relays.md: Codex install section had copy-paste error (claude paths) - config docs: warden fields corrected, datastore section reflects implementation - deployment.md: production example includes require_sasl and +Rn modes
c669cc37ee4a8f5e69ca628ac3ef342c1f1677ffd624d88a786243b4493ecd59
| --- deploy/docker/Dockerfile | ||
| +++ deploy/docker/Dockerfile | ||
| @@ -16,8 +16,14 @@ | ||
| 16 | 16 | |
| 17 | 17 | RUN apk add --no-cache ca-certificates tzdata |
| 18 | 18 | |
| 19 | 19 | COPY --from=builder /scuttlebot /usr/local/bin/scuttlebot |
| 20 | 20 | |
| 21 | +# In a container, bind to all interfaces so port mapping works. | |
| 22 | +# Override with SCUTTLEBOT_API_ADDR / SCUTTLEBOT_MCP_ADDR if needed. | |
| 23 | +ENV SCUTTLEBOT_API_ADDR=:8080 | |
| 24 | +ENV SCUTTLEBOT_MCP_ADDR=:8081 | |
| 25 | + | |
| 21 | 26 | EXPOSE 8080 |
| 27 | +EXPOSE 8081 | |
| 22 | 28 | |
| 23 | 29 | ENTRYPOINT ["scuttlebot"] |
| 24 | 30 |
| --- deploy/docker/Dockerfile | |
| +++ deploy/docker/Dockerfile | |
| @@ -16,8 +16,14 @@ | |
| 16 | |
| 17 | RUN apk add --no-cache ca-certificates tzdata |
| 18 | |
| 19 | COPY --from=builder /scuttlebot /usr/local/bin/scuttlebot |
| 20 | |
| 21 | EXPOSE 8080 |
| 22 | |
| 23 | ENTRYPOINT ["scuttlebot"] |
| 24 |
| --- deploy/docker/Dockerfile | |
| +++ deploy/docker/Dockerfile | |
| @@ -16,8 +16,14 @@ | |
| 16 | |
| 17 | RUN apk add --no-cache ca-certificates tzdata |
| 18 | |
| 19 | COPY --from=builder /scuttlebot /usr/local/bin/scuttlebot |
| 20 | |
| 21 | # In a container, bind to all interfaces so port mapping works. |
| 22 | # Override with SCUTTLEBOT_API_ADDR / SCUTTLEBOT_MCP_ADDR if needed. |
| 23 | ENV SCUTTLEBOT_API_ADDR=:8080 |
| 24 | ENV SCUTTLEBOT_MCP_ADDR=:8081 |
| 25 | |
| 26 | EXPOSE 8080 |
| 27 | EXPOSE 8081 |
| 28 | |
| 29 | ENTRYPOINT ["scuttlebot"] |
| 30 |
| --- deploy/standalone/scuttlebot.yaml.example | ||
| +++ deploy/standalone/scuttlebot.yaml.example | ||
| @@ -4,19 +4,25 @@ | ||
| 4 | 4 | ergo: |
| 5 | 5 | # binary_path: ergo # path to ergo binary; auto-downloaded if not found |
| 6 | 6 | # data_dir: ./data/ergo # where ergo stores ircd.db and generated config |
| 7 | 7 | # network_name: scuttlebot # IRC network name |
| 8 | 8 | # server_name: irc.scuttlebot.local |
| 9 | - # irc_addr: 127.0.0.1:6667 # IRC listen address | |
| 10 | - # api_addr: 127.0.0.1:8089 # ergo HTTP API address | |
| 9 | + # irc_addr: 127.0.0.1:6667 # IRC listen address (set to :6667 to accept external connections) | |
| 10 | + # api_addr: 127.0.0.1:8089 # ergo HTTP API — keep on loopback | |
| 11 | 11 | # api_token: "" # auto-generated if blank |
| 12 | + | |
| 13 | + # Security for public deployments: | |
| 14 | + # require_sasl: true # reject IRC connections that don't authenticate | |
| 15 | + # default_channel_modes: "+Rn" # restrict channel joins to registered nicks | |
| 16 | + | |
| 12 | 17 | history: |
| 13 | 18 | # enabled: false |
| 14 | 19 | # postgres_dsn: postgres://user:pass@localhost/scuttlebot?sslmode=disable |
| 15 | 20 | |
| 16 | 21 | datastore: |
| 17 | 22 | # driver: sqlite # "sqlite" or "postgres" |
| 18 | 23 | # dsn: ./data/scuttlebot.db |
| 19 | 24 | |
| 20 | -# api_addr: :8080 # scuttlebot REST API listen address | |
| 25 | +# api_addr: 127.0.0.1:8080 # bind to loopback; use a reverse proxy for external access | |
| 26 | +# mcp_addr: 127.0.0.1:8081 | |
| 21 | 27 | |
| 22 | 28 | # topology: (see scuttlebot.yaml for full annotated example) |
| 23 | 29 |
| --- deploy/standalone/scuttlebot.yaml.example | |
| +++ deploy/standalone/scuttlebot.yaml.example | |
| @@ -4,19 +4,25 @@ | |
| 4 | ergo: |
| 5 | # binary_path: ergo # path to ergo binary; auto-downloaded if not found |
| 6 | # data_dir: ./data/ergo # where ergo stores ircd.db and generated config |
| 7 | # network_name: scuttlebot # IRC network name |
| 8 | # server_name: irc.scuttlebot.local |
| 9 | # irc_addr: 127.0.0.1:6667 # IRC listen address |
| 10 | # api_addr: 127.0.0.1:8089 # ergo HTTP API address |
| 11 | # api_token: "" # auto-generated if blank |
| 12 | history: |
| 13 | # enabled: false |
| 14 | # postgres_dsn: postgres://user:pass@localhost/scuttlebot?sslmode=disable |
| 15 | |
| 16 | datastore: |
| 17 | # driver: sqlite # "sqlite" or "postgres" |
| 18 | # dsn: ./data/scuttlebot.db |
| 19 | |
| 20 | # api_addr: :8080 # scuttlebot REST API listen address |
| 21 | |
| 22 | # topology: (see scuttlebot.yaml for full annotated example) |
| 23 |
| --- deploy/standalone/scuttlebot.yaml.example | |
| +++ deploy/standalone/scuttlebot.yaml.example | |
| @@ -4,19 +4,25 @@ | |
| 4 | ergo: |
| 5 | # binary_path: ergo # path to ergo binary; auto-downloaded if not found |
| 6 | # data_dir: ./data/ergo # where ergo stores ircd.db and generated config |
| 7 | # network_name: scuttlebot # IRC network name |
| 8 | # server_name: irc.scuttlebot.local |
| 9 | # irc_addr: 127.0.0.1:6667 # IRC listen address (set to :6667 to accept external connections) |
| 10 | # api_addr: 127.0.0.1:8089 # ergo HTTP API — keep on loopback |
| 11 | # api_token: "" # auto-generated if blank |
| 12 | |
| 13 | # Security for public deployments: |
| 14 | # require_sasl: true # reject IRC connections that don't authenticate |
| 15 | # default_channel_modes: "+Rn" # restrict channel joins to registered nicks |
| 16 | |
| 17 | history: |
| 18 | # enabled: false |
| 19 | # postgres_dsn: postgres://user:pass@localhost/scuttlebot?sslmode=disable |
| 20 | |
| 21 | datastore: |
| 22 | # driver: sqlite # "sqlite" or "postgres" |
| 23 | # dsn: ./data/scuttlebot.db |
| 24 | |
| 25 | # api_addr: 127.0.0.1:8080 # bind to loopback; use a reverse proxy for external access |
| 26 | # mcp_addr: 127.0.0.1:8081 |
| 27 | |
| 28 | # topology: (see scuttlebot.yaml for full annotated example) |
| 29 |
| --- docs/architecture/overview.md | ||
| +++ docs/architecture/overview.md | ||
| @@ -80,11 +80,11 @@ | ||
| 80 | 80 | |
| 81 | 81 | 1. Reads `scuttlebot.yaml` (all fields optional; defaults apply) |
| 82 | 82 | 2. Downloads an Ergo binary if one is not present |
| 83 | 83 | 3. Writes Ergo's `ircd.yaml` from scuttlebot's config |
| 84 | 84 | 4. Starts Ergo as a subprocess and monitors it |
| 85 | -5. Starts the HTTP API on `:8080` | |
| 85 | +5. Starts the HTTP API on `127.0.0.1:8080` | |
| 86 | 86 | 6. Starts enabled system bots via the bot manager |
| 87 | 87 | 7. Prints the API token to stderr (stable across restarts once written to disk) |
| 88 | 88 | |
| 89 | 89 | ### Ergo IRC server (`internal/ergo/`) |
| 90 | 90 | |
| 91 | 91 |
| --- docs/architecture/overview.md | |
| +++ docs/architecture/overview.md | |
| @@ -80,11 +80,11 @@ | |
| 80 | |
| 81 | 1. Reads `scuttlebot.yaml` (all fields optional; defaults apply) |
| 82 | 2. Downloads an Ergo binary if one is not present |
| 83 | 3. Writes Ergo's `ircd.yaml` from scuttlebot's config |
| 84 | 4. Starts Ergo as a subprocess and monitors it |
| 85 | 5. Starts the HTTP API on `:8080` |
| 86 | 6. Starts enabled system bots via the bot manager |
| 87 | 7. Prints the API token to stderr (stable across restarts once written to disk) |
| 88 | |
| 89 | ### Ergo IRC server (`internal/ergo/`) |
| 90 | |
| 91 |
| --- docs/architecture/overview.md | |
| +++ docs/architecture/overview.md | |
| @@ -80,11 +80,11 @@ | |
| 80 | |
| 81 | 1. Reads `scuttlebot.yaml` (all fields optional; defaults apply) |
| 82 | 2. Downloads an Ergo binary if one is not present |
| 83 | 3. Writes Ergo's `ircd.yaml` from scuttlebot's config |
| 84 | 4. Starts Ergo as a subprocess and monitors it |
| 85 | 5. Starts the HTTP API on `127.0.0.1:8080` |
| 86 | 6. Starts enabled system bots via the bot manager |
| 87 | 7. Prints the API token to stderr (stable across restarts once written to disk) |
| 88 | |
| 89 | ### Ergo IRC server (`internal/ergo/`) |
| 90 | |
| 91 |
| --- docs/getting-started/configuration.md | ||
| +++ docs/getting-started/configuration.md | ||
| @@ -32,12 +32,12 @@ | ||
| 32 | 32 | |
| 33 | 33 | ## Top-level fields |
| 34 | 34 | |
| 35 | 35 | | Field | Type | Default | Description | |
| 36 | 36 | |-------|------|---------|-------------| |
| 37 | -| `api_addr` | string | `:8080` | Listen address for scuttlebot's HTTP API and web UI. Overridden by `SCUTTLEBOT_API_ADDR`. When `tls.domain` is set this is ignored — HTTPS runs on `:443` and HTTP on `:80`. | | |
| 38 | -| `mcp_addr` | string | `:8081` | Listen address for the MCP server. Overridden by `SCUTTLEBOT_MCP_ADDR`. | | |
| 37 | +| `api_addr` | string | `127.0.0.1:8080` | Listen address for scuttlebot's HTTP API and web UI. Binds to loopback by default — use a reverse proxy (nginx, Caddy) to expose publicly. Overridden by `SCUTTLEBOT_API_ADDR`. When `tls.domain` is set this is ignored — HTTPS runs on `:443` and HTTP on `:80`. | | |
| 38 | +| `mcp_addr` | string | `127.0.0.1:8081` | Listen address for the MCP server. Binds to loopback by default. Overridden by `SCUTTLEBOT_MCP_ADDR`. | | |
| 39 | 39 | |
| 40 | 40 | --- |
| 41 | 41 | |
| 42 | 42 | ## `ergo` |
| 43 | 43 | |
| @@ -66,10 +66,12 @@ | ||
| 66 | 66 | | `network_name` | string | `scuttlebot` | Human-readable IRC network name displayed in clients. Overridden by `SCUTTLEBOT_ERGO_NETWORK_NAME`. | |
| 67 | 67 | | `server_name` | string | `irc.scuttlebot.local` | IRC server hostname (shown in `/whois` etc). Overridden by `SCUTTLEBOT_ERGO_SERVER_NAME`. | |
| 68 | 68 | | `irc_addr` | string | `127.0.0.1:6667` | Address ergo listens for IRC connections. Loopback by default — agents connect here. Overridden by `SCUTTLEBOT_ERGO_IRC_ADDR`. | |
| 69 | 69 | | `api_addr` | string | `127.0.0.1:8089` | Address of ergo's HTTP management API. loopback only by default. Overridden by `SCUTTLEBOT_ERGO_API_ADDR`. | |
| 70 | 70 | | `api_token` | string | *(auto-generated)* | Bearer token for ergo's HTTP API. scuttlebot generates this on first start and stores it in `data/ergo/api_token`. Overridden by `SCUTTLEBOT_ERGO_API_TOKEN`. | |
| 71 | +| `require_sasl` | bool | `false` | Require SASL authentication for all IRC connections. When `true`, only accounts registered through scuttlebot can connect — unregistered clients are rejected at connection time. Recommended for public deployments. | | |
| 72 | +| `default_channel_modes` | string | `+n` | Channel modes applied when a new channel is created. `+n` prevents external messages. Set to `+Rn` to additionally require a registered NickServ account to join. | | |
| 71 | 73 | |
| 72 | 74 | ### `ergo.history` |
| 73 | 75 | |
| 74 | 76 | Persistent message history is stored by ergo (separate from scribe's structured log). |
| 75 | 77 | |
| @@ -85,22 +87,24 @@ | ||
| 85 | 87 | |
| 86 | 88 | --- |
| 87 | 89 | |
| 88 | 90 | ## `datastore` |
| 89 | 91 | |
| 90 | -scuttlebot's own state database — stores agent registry, admin accounts, and audit log. Separate from ergo's `ircd.db`. | |
| 92 | +scuttlebot's own persistent state store — agent registry, admin accounts, and policies. When configured, this supersedes the default JSON file storage in `data/`. | |
| 91 | 93 | |
| 92 | 94 | ```yaml |
| 93 | 95 | datastore: |
| 94 | 96 | driver: sqlite |
| 95 | 97 | dsn: ./data/scuttlebot.db |
| 96 | 98 | ``` |
| 97 | 99 | |
| 98 | 100 | | Field | Type | Default | Description | |
| 99 | 101 | |-------|------|---------|-------------| |
| 100 | -| `driver` | string | `sqlite` | `"sqlite"` or `"postgres"`. Overridden by `SCUTTLEBOT_DB_DRIVER`. | | |
| 102 | +| `driver` | string | — | `"sqlite"` or `"postgres"`. Leave empty to use JSON files (default). Overridden by `SCUTTLEBOT_DB_DRIVER`. | | |
| 101 | 103 | | `dsn` | string | `./data/scuttlebot.db` | Data source name. For SQLite: path to the `.db` file. For PostgreSQL: a standard `postgres://` connection string. Overridden by `SCUTTLEBOT_DB_DSN`. | |
| 104 | + | |
| 105 | +When `driver` is unset (the default), state is stored as JSON files (`registry.json`, `admins.json`, `policies.json`) in the Ergo data directory. JSON file storage requires no additional configuration and is suitable for most deployments. Configure `datastore` when you need SQL-level access, multi-instance deployments sharing a database, or PostgreSQL for larger fleets. | |
| 102 | 106 | |
| 103 | 107 | --- |
| 104 | 108 | |
| 105 | 109 | ## `bridge` |
| 106 | 110 | |
| @@ -145,11 +149,11 @@ | ||
| 145 | 149 | | `email` | string | — | Email address for Let's Encrypt expiry notifications. | |
| 146 | 150 | | `cert_dir` | string | `{ergo.data_dir}/certs` | Directory to cache the certificate. | |
| 147 | 151 | | `allow_insecure` | bool | `true` | Keep HTTP running on `:80` alongside HTTPS. The ACME HTTP-01 challenge always runs on `:80` regardless of this setting. | |
| 148 | 152 | |
| 149 | 153 | !!! note "Local dev" |
| 150 | - Leave `tls.domain` empty for local development. The HTTP API on `:8080` is used instead. | |
| 154 | + Leave `tls.domain` empty for local development. The HTTP API on `127.0.0.1:8080` is used instead. | |
| 151 | 155 | |
| 152 | 156 | --- |
| 153 | 157 | |
| 154 | 158 | ## `llm` |
| 155 | 159 | |
| @@ -287,25 +291,29 @@ | ||
| 287 | 291 | |
| 288 | 292 | ```yaml |
| 289 | 293 | # scuttlebot.yaml |
| 290 | 294 | |
| 291 | 295 | # HTTP API and web UI |
| 292 | -api_addr: :8080 | |
| 296 | +api_addr: 127.0.0.1:8080 | |
| 293 | 297 | |
| 294 | 298 | # MCP server |
| 295 | -mcp_addr: :8081 | |
| 299 | +mcp_addr: 127.0.0.1:8081 | |
| 296 | 300 | |
| 297 | 301 | ergo: |
| 298 | 302 | # Manage ergo as a subprocess (default). |
| 299 | 303 | # Set external: true if ergo runs separately (Docker, etc.) |
| 300 | 304 | external: false |
| 301 | 305 | network_name: myfleet |
| 302 | 306 | server_name: irc.myfleet.internal |
| 303 | - irc_addr: 127.0.0.1:6667 | |
| 304 | - api_addr: 127.0.0.1:8089 | |
| 307 | + irc_addr: 127.0.0.1:6667 # set to :6667 or :6697 to expose IRC publicly | |
| 308 | + api_addr: 127.0.0.1:8089 # keep on loopback — no auth layer on this port | |
| 305 | 309 | # api_token is auto-generated on first start |
| 306 | 310 | |
| 311 | + # Security (recommended for public deployments): | |
| 312 | + require_sasl: false # set to true to reject unauthenticated IRC connections | |
| 313 | + default_channel_modes: "+n" # set to "+Rn" to restrict joins to registered nicks | |
| 314 | + | |
| 307 | 315 | # Optional: persistent IRC history in PostgreSQL |
| 308 | 316 | history: |
| 309 | 317 | enabled: true |
| 310 | 318 | postgres_dsn: postgres://scuttlebot:secret@localhost/scuttlebot?sslmode=disable |
| 311 | 319 | |
| 312 | 320 |
| --- docs/getting-started/configuration.md | |
| +++ docs/getting-started/configuration.md | |
| @@ -32,12 +32,12 @@ | |
| 32 | |
| 33 | ## Top-level fields |
| 34 | |
| 35 | | Field | Type | Default | Description | |
| 36 | |-------|------|---------|-------------| |
| 37 | | `api_addr` | string | `:8080` | Listen address for scuttlebot's HTTP API and web UI. Overridden by `SCUTTLEBOT_API_ADDR`. When `tls.domain` is set this is ignored — HTTPS runs on `:443` and HTTP on `:80`. | |
| 38 | | `mcp_addr` | string | `:8081` | Listen address for the MCP server. Overridden by `SCUTTLEBOT_MCP_ADDR`. | |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## `ergo` |
| 43 | |
| @@ -66,10 +66,12 @@ | |
| 66 | | `network_name` | string | `scuttlebot` | Human-readable IRC network name displayed in clients. Overridden by `SCUTTLEBOT_ERGO_NETWORK_NAME`. | |
| 67 | | `server_name` | string | `irc.scuttlebot.local` | IRC server hostname (shown in `/whois` etc). Overridden by `SCUTTLEBOT_ERGO_SERVER_NAME`. | |
| 68 | | `irc_addr` | string | `127.0.0.1:6667` | Address ergo listens for IRC connections. Loopback by default — agents connect here. Overridden by `SCUTTLEBOT_ERGO_IRC_ADDR`. | |
| 69 | | `api_addr` | string | `127.0.0.1:8089` | Address of ergo's HTTP management API. loopback only by default. Overridden by `SCUTTLEBOT_ERGO_API_ADDR`. | |
| 70 | | `api_token` | string | *(auto-generated)* | Bearer token for ergo's HTTP API. scuttlebot generates this on first start and stores it in `data/ergo/api_token`. Overridden by `SCUTTLEBOT_ERGO_API_TOKEN`. | |
| 71 | |
| 72 | ### `ergo.history` |
| 73 | |
| 74 | Persistent message history is stored by ergo (separate from scribe's structured log). |
| 75 | |
| @@ -85,22 +87,24 @@ | |
| 85 | |
| 86 | --- |
| 87 | |
| 88 | ## `datastore` |
| 89 | |
| 90 | scuttlebot's own state database — stores agent registry, admin accounts, and audit log. Separate from ergo's `ircd.db`. |
| 91 | |
| 92 | ```yaml |
| 93 | datastore: |
| 94 | driver: sqlite |
| 95 | dsn: ./data/scuttlebot.db |
| 96 | ``` |
| 97 | |
| 98 | | Field | Type | Default | Description | |
| 99 | |-------|------|---------|-------------| |
| 100 | | `driver` | string | `sqlite` | `"sqlite"` or `"postgres"`. Overridden by `SCUTTLEBOT_DB_DRIVER`. | |
| 101 | | `dsn` | string | `./data/scuttlebot.db` | Data source name. For SQLite: path to the `.db` file. For PostgreSQL: a standard `postgres://` connection string. Overridden by `SCUTTLEBOT_DB_DSN`. | |
| 102 | |
| 103 | --- |
| 104 | |
| 105 | ## `bridge` |
| 106 | |
| @@ -145,11 +149,11 @@ | |
| 145 | | `email` | string | — | Email address for Let's Encrypt expiry notifications. | |
| 146 | | `cert_dir` | string | `{ergo.data_dir}/certs` | Directory to cache the certificate. | |
| 147 | | `allow_insecure` | bool | `true` | Keep HTTP running on `:80` alongside HTTPS. The ACME HTTP-01 challenge always runs on `:80` regardless of this setting. | |
| 148 | |
| 149 | !!! note "Local dev" |
| 150 | Leave `tls.domain` empty for local development. The HTTP API on `:8080` is used instead. |
| 151 | |
| 152 | --- |
| 153 | |
| 154 | ## `llm` |
| 155 | |
| @@ -287,25 +291,29 @@ | |
| 287 | |
| 288 | ```yaml |
| 289 | # scuttlebot.yaml |
| 290 | |
| 291 | # HTTP API and web UI |
| 292 | api_addr: :8080 |
| 293 | |
| 294 | # MCP server |
| 295 | mcp_addr: :8081 |
| 296 | |
| 297 | ergo: |
| 298 | # Manage ergo as a subprocess (default). |
| 299 | # Set external: true if ergo runs separately (Docker, etc.) |
| 300 | external: false |
| 301 | network_name: myfleet |
| 302 | server_name: irc.myfleet.internal |
| 303 | irc_addr: 127.0.0.1:6667 |
| 304 | api_addr: 127.0.0.1:8089 |
| 305 | # api_token is auto-generated on first start |
| 306 | |
| 307 | # Optional: persistent IRC history in PostgreSQL |
| 308 | history: |
| 309 | enabled: true |
| 310 | postgres_dsn: postgres://scuttlebot:secret@localhost/scuttlebot?sslmode=disable |
| 311 | |
| 312 |
| --- docs/getting-started/configuration.md | |
| +++ docs/getting-started/configuration.md | |
| @@ -32,12 +32,12 @@ | |
| 32 | |
| 33 | ## Top-level fields |
| 34 | |
| 35 | | Field | Type | Default | Description | |
| 36 | |-------|------|---------|-------------| |
| 37 | | `api_addr` | string | `127.0.0.1:8080` | Listen address for scuttlebot's HTTP API and web UI. Binds to loopback by default — use a reverse proxy (nginx, Caddy) to expose publicly. Overridden by `SCUTTLEBOT_API_ADDR`. When `tls.domain` is set this is ignored — HTTPS runs on `:443` and HTTP on `:80`. | |
| 38 | | `mcp_addr` | string | `127.0.0.1:8081` | Listen address for the MCP server. Binds to loopback by default. Overridden by `SCUTTLEBOT_MCP_ADDR`. | |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## `ergo` |
| 43 | |
| @@ -66,10 +66,12 @@ | |
| 66 | | `network_name` | string | `scuttlebot` | Human-readable IRC network name displayed in clients. Overridden by `SCUTTLEBOT_ERGO_NETWORK_NAME`. | |
| 67 | | `server_name` | string | `irc.scuttlebot.local` | IRC server hostname (shown in `/whois` etc). Overridden by `SCUTTLEBOT_ERGO_SERVER_NAME`. | |
| 68 | | `irc_addr` | string | `127.0.0.1:6667` | Address ergo listens for IRC connections. Loopback by default — agents connect here. Overridden by `SCUTTLEBOT_ERGO_IRC_ADDR`. | |
| 69 | | `api_addr` | string | `127.0.0.1:8089` | Address of ergo's HTTP management API. loopback only by default. Overridden by `SCUTTLEBOT_ERGO_API_ADDR`. | |
| 70 | | `api_token` | string | *(auto-generated)* | Bearer token for ergo's HTTP API. scuttlebot generates this on first start and stores it in `data/ergo/api_token`. Overridden by `SCUTTLEBOT_ERGO_API_TOKEN`. | |
| 71 | | `require_sasl` | bool | `false` | Require SASL authentication for all IRC connections. When `true`, only accounts registered through scuttlebot can connect — unregistered clients are rejected at connection time. Recommended for public deployments. | |
| 72 | | `default_channel_modes` | string | `+n` | Channel modes applied when a new channel is created. `+n` prevents external messages. Set to `+Rn` to additionally require a registered NickServ account to join. | |
| 73 | |
| 74 | ### `ergo.history` |
| 75 | |
| 76 | Persistent message history is stored by ergo (separate from scribe's structured log). |
| 77 | |
| @@ -85,22 +87,24 @@ | |
| 87 | |
| 88 | --- |
| 89 | |
| 90 | ## `datastore` |
| 91 | |
| 92 | scuttlebot's own persistent state store — agent registry, admin accounts, and policies. When configured, this supersedes the default JSON file storage in `data/`. |
| 93 | |
| 94 | ```yaml |
| 95 | datastore: |
| 96 | driver: sqlite |
| 97 | dsn: ./data/scuttlebot.db |
| 98 | ``` |
| 99 | |
| 100 | | Field | Type | Default | Description | |
| 101 | |-------|------|---------|-------------| |
| 102 | | `driver` | string | — | `"sqlite"` or `"postgres"`. Leave empty to use JSON files (default). Overridden by `SCUTTLEBOT_DB_DRIVER`. | |
| 103 | | `dsn` | string | `./data/scuttlebot.db` | Data source name. For SQLite: path to the `.db` file. For PostgreSQL: a standard `postgres://` connection string. Overridden by `SCUTTLEBOT_DB_DSN`. | |
| 104 | |
| 105 | When `driver` is unset (the default), state is stored as JSON files (`registry.json`, `admins.json`, `policies.json`) in the Ergo data directory. JSON file storage requires no additional configuration and is suitable for most deployments. Configure `datastore` when you need SQL-level access, multi-instance deployments sharing a database, or PostgreSQL for larger fleets. |
| 106 | |
| 107 | --- |
| 108 | |
| 109 | ## `bridge` |
| 110 | |
| @@ -145,11 +149,11 @@ | |
| 149 | | `email` | string | — | Email address for Let's Encrypt expiry notifications. | |
| 150 | | `cert_dir` | string | `{ergo.data_dir}/certs` | Directory to cache the certificate. | |
| 151 | | `allow_insecure` | bool | `true` | Keep HTTP running on `:80` alongside HTTPS. The ACME HTTP-01 challenge always runs on `:80` regardless of this setting. | |
| 152 | |
| 153 | !!! note "Local dev" |
| 154 | Leave `tls.domain` empty for local development. The HTTP API on `127.0.0.1:8080` is used instead. |
| 155 | |
| 156 | --- |
| 157 | |
| 158 | ## `llm` |
| 159 | |
| @@ -287,25 +291,29 @@ | |
| 291 | |
| 292 | ```yaml |
| 293 | # scuttlebot.yaml |
| 294 | |
| 295 | # HTTP API and web UI |
| 296 | api_addr: 127.0.0.1:8080 |
| 297 | |
| 298 | # MCP server |
| 299 | mcp_addr: 127.0.0.1:8081 |
| 300 | |
| 301 | ergo: |
| 302 | # Manage ergo as a subprocess (default). |
| 303 | # Set external: true if ergo runs separately (Docker, etc.) |
| 304 | external: false |
| 305 | network_name: myfleet |
| 306 | server_name: irc.myfleet.internal |
| 307 | irc_addr: 127.0.0.1:6667 # set to :6667 or :6697 to expose IRC publicly |
| 308 | api_addr: 127.0.0.1:8089 # keep on loopback — no auth layer on this port |
| 309 | # api_token is auto-generated on first start |
| 310 | |
| 311 | # Security (recommended for public deployments): |
| 312 | require_sasl: false # set to true to reject unauthenticated IRC connections |
| 313 | default_channel_modes: "+n" # set to "+Rn" to restrict joins to registered nicks |
| 314 | |
| 315 | # Optional: persistent IRC history in PostgreSQL |
| 316 | history: |
| 317 | enabled: true |
| 318 | postgres_dsn: postgres://scuttlebot:secret@localhost/scuttlebot?sslmode=disable |
| 319 | |
| 320 |
| --- docs/getting-started/quickstart.md | ||
| +++ docs/getting-started/quickstart.md | ||
| @@ -41,11 +41,11 @@ | ||
| 41 | 41 | ``` |
| 42 | 42 | |
| 43 | 43 | The wizard walks through: |
| 44 | 44 | |
| 45 | 45 | - IRC network name and server hostname |
| 46 | -- HTTP API listen address (default: `:8080`) | |
| 46 | +- HTTP API listen address (default: `127.0.0.1:8080`) | |
| 47 | 47 | - TLS / Let's Encrypt (skip for local dev) |
| 48 | 48 | - Web chat bridge channels (default: `#general`) |
| 49 | 49 | - LLM backends for oracle, sentinel, and steward (optional — skip if you don't need AI bots) |
| 50 | 50 | - Scribe message logging |
| 51 | 51 | |
| @@ -76,11 +76,11 @@ | ||
| 76 | 76 | On first start scuttlebot: |
| 77 | 77 | |
| 78 | 78 | 1. Downloads the `ergo` IRC binary if it is not already on PATH |
| 79 | 79 | 2. Generates an Ergo config, starts the embedded IRC server on `127.0.0.1:6667` |
| 80 | 80 | 3. Registers all built-in bot accounts with NickServ |
| 81 | -4. Starts the HTTP API on `:8080` | |
| 81 | +4. Starts the HTTP API on `127.0.0.1:8080` | |
| 82 | 82 | 5. Writes a bearer token to `data/ergo/api_token` |
| 83 | 83 | |
| 84 | 84 | You should see the API respond within a few seconds: |
| 85 | 85 | |
| 86 | 86 | ```bash |
| 87 | 87 |
| --- docs/getting-started/quickstart.md | |
| +++ docs/getting-started/quickstart.md | |
| @@ -41,11 +41,11 @@ | |
| 41 | ``` |
| 42 | |
| 43 | The wizard walks through: |
| 44 | |
| 45 | - IRC network name and server hostname |
| 46 | - HTTP API listen address (default: `:8080`) |
| 47 | - TLS / Let's Encrypt (skip for local dev) |
| 48 | - Web chat bridge channels (default: `#general`) |
| 49 | - LLM backends for oracle, sentinel, and steward (optional — skip if you don't need AI bots) |
| 50 | - Scribe message logging |
| 51 | |
| @@ -76,11 +76,11 @@ | |
| 76 | On first start scuttlebot: |
| 77 | |
| 78 | 1. Downloads the `ergo` IRC binary if it is not already on PATH |
| 79 | 2. Generates an Ergo config, starts the embedded IRC server on `127.0.0.1:6667` |
| 80 | 3. Registers all built-in bot accounts with NickServ |
| 81 | 4. Starts the HTTP API on `:8080` |
| 82 | 5. Writes a bearer token to `data/ergo/api_token` |
| 83 | |
| 84 | You should see the API respond within a few seconds: |
| 85 | |
| 86 | ```bash |
| 87 |
| --- docs/getting-started/quickstart.md | |
| +++ docs/getting-started/quickstart.md | |
| @@ -41,11 +41,11 @@ | |
| 41 | ``` |
| 42 | |
| 43 | The wizard walks through: |
| 44 | |
| 45 | - IRC network name and server hostname |
| 46 | - HTTP API listen address (default: `127.0.0.1:8080`) |
| 47 | - TLS / Let's Encrypt (skip for local dev) |
| 48 | - Web chat bridge channels (default: `#general`) |
| 49 | - LLM backends for oracle, sentinel, and steward (optional — skip if you don't need AI bots) |
| 50 | - Scribe message logging |
| 51 | |
| @@ -76,11 +76,11 @@ | |
| 76 | On first start scuttlebot: |
| 77 | |
| 78 | 1. Downloads the `ergo` IRC binary if it is not already on PATH |
| 79 | 2. Generates an Ergo config, starts the embedded IRC server on `127.0.0.1:6667` |
| 80 | 3. Registers all built-in bot accounts with NickServ |
| 81 | 4. Starts the HTTP API on `127.0.0.1:8080` |
| 82 | 5. Writes a bearer token to `data/ergo/api_token` |
| 83 | |
| 84 | You should see the API respond within a few seconds: |
| 85 | |
| 86 | ```bash |
| 87 |
| --- docs/guide/deployment.md | ||
| +++ docs/guide/deployment.md | ||
| @@ -50,10 +50,12 @@ | ||
| 50 | 50 | ergo: |
| 51 | 51 | network_name: mynet |
| 52 | 52 | server_name: irc.example.com |
| 53 | 53 | irc_addr: 0.0.0.0:6697 |
| 54 | 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 | |
| 55 | 57 | |
| 56 | 58 | bridge: |
| 57 | 59 | enabled: true |
| 58 | 60 | nick: bridge |
| 59 | 61 | channels: |
| 60 | 62 |
| --- docs/guide/deployment.md | |
| +++ docs/guide/deployment.md | |
| @@ -50,10 +50,12 @@ | |
| 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 | |
| 56 | bridge: |
| 57 | enabled: true |
| 58 | nick: bridge |
| 59 | channels: |
| 60 |
| --- docs/guide/deployment.md | |
| +++ docs/guide/deployment.md | |
| @@ -50,10 +50,12 @@ | |
| 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 |
| --- docs/guide/relays.md | ||
| +++ docs/guide/relays.md | ||
| @@ -207,20 +207,20 @@ | ||
| 207 | 207 | ``` |
| 208 | 208 | |
| 209 | 209 | === "Codex" |
| 210 | 210 | |
| 211 | 211 | ```bash |
| 212 | - bash skills/scuttlebot-relay/scripts/install-claude-relay.sh \ | |
| 212 | + bash skills/openai-relay/scripts/install-codex-relay.sh \ | |
| 213 | 213 | --url http://localhost:8080 \ |
| 214 | 214 | --token "$(./run.sh token)" \ |
| 215 | 215 | --channel general |
| 216 | 216 | ``` |
| 217 | 217 | |
| 218 | 218 | After install: |
| 219 | 219 | |
| 220 | 220 | ```bash |
| 221 | - ~/.local/bin/claude-relay # same wrapper pattern | |
| 221 | + ~/.local/bin/codex-relay | |
| 222 | 222 | ``` |
| 223 | 223 | |
| 224 | 224 | === "Gemini" |
| 225 | 225 | |
| 226 | 226 | ```bash |
| 227 | 227 |
| --- docs/guide/relays.md | |
| +++ docs/guide/relays.md | |
| @@ -207,20 +207,20 @@ | |
| 207 | ``` |
| 208 | |
| 209 | === "Codex" |
| 210 | |
| 211 | ```bash |
| 212 | bash skills/scuttlebot-relay/scripts/install-claude-relay.sh \ |
| 213 | --url http://localhost:8080 \ |
| 214 | --token "$(./run.sh token)" \ |
| 215 | --channel general |
| 216 | ``` |
| 217 | |
| 218 | After install: |
| 219 | |
| 220 | ```bash |
| 221 | ~/.local/bin/claude-relay # same wrapper pattern |
| 222 | ``` |
| 223 | |
| 224 | === "Gemini" |
| 225 | |
| 226 | ```bash |
| 227 |
| --- docs/guide/relays.md | |
| +++ docs/guide/relays.md | |
| @@ -207,20 +207,20 @@ | |
| 207 | ``` |
| 208 | |
| 209 | === "Codex" |
| 210 | |
| 211 | ```bash |
| 212 | bash skills/openai-relay/scripts/install-codex-relay.sh \ |
| 213 | --url http://localhost:8080 \ |
| 214 | --token "$(./run.sh token)" \ |
| 215 | --channel general |
| 216 | ``` |
| 217 | |
| 218 | After install: |
| 219 | |
| 220 | ```bash |
| 221 | ~/.local/bin/codex-relay |
| 222 | ``` |
| 223 | |
| 224 | === "Gemini" |
| 225 | |
| 226 | ```bash |
| 227 |
| --- docs/reference/api.md | ||
| +++ docs/reference/api.md | ||
| @@ -1,8 +1,8 @@ | ||
| 1 | 1 | # HTTP API Reference |
| 2 | 2 | |
| 3 | -scuttlebot exposes a REST API at the address configured in `api_addr` (default `:8080`). | |
| 3 | +scuttlebot exposes a REST API at the address configured in `api_addr` (default `127.0.0.1:8080`). | |
| 4 | 4 | |
| 5 | 5 | All `/v1/` endpoints require a valid **Bearer token** in the `Authorization` header, except for the SSE stream endpoint which uses a `?token=` query parameter (browser `EventSource` cannot send headers). |
| 6 | 6 | |
| 7 | 7 | The API token is written to `data/ergo/api_token` on every daemon start. |
| 8 | 8 | |
| 9 | 9 |
| --- docs/reference/api.md | |
| +++ docs/reference/api.md | |
| @@ -1,8 +1,8 @@ | |
| 1 | # HTTP API Reference |
| 2 | |
| 3 | scuttlebot exposes a REST API at the address configured in `api_addr` (default `:8080`). |
| 4 | |
| 5 | All `/v1/` endpoints require a valid **Bearer token** in the `Authorization` header, except for the SSE stream endpoint which uses a `?token=` query parameter (browser `EventSource` cannot send headers). |
| 6 | |
| 7 | The API token is written to `data/ergo/api_token` on every daemon start. |
| 8 | |
| 9 |
| --- docs/reference/api.md | |
| +++ docs/reference/api.md | |
| @@ -1,8 +1,8 @@ | |
| 1 | # HTTP API Reference |
| 2 | |
| 3 | scuttlebot exposes a REST API at the address configured in `api_addr` (default `127.0.0.1:8080`). |
| 4 | |
| 5 | All `/v1/` endpoints require a valid **Bearer token** in the `Authorization` header, except for the SSE stream endpoint which uses a `?token=` query parameter (browser `EventSource` cannot send headers). |
| 6 | |
| 7 | The API token is written to `data/ergo/api_token` on every daemon start. |
| 8 | |
| 9 |
| --- docs/reference/cli.md | ||
| +++ docs/reference/cli.md | ||
| @@ -451,12 +451,12 @@ | ||
| 451 | 451 | |
| 452 | 452 | 1. Loads and validates `scuttlebot.yaml` |
| 453 | 453 | 2. Downloads ergo if not found (unless `ergo.external: true`) |
| 454 | 454 | 3. Generates an Ergo config and starts the IRC server |
| 455 | 455 | 4. Registers built-in bot NickServ accounts |
| 456 | -5. Starts the HTTP API on `api_addr` (default `:8080`) | |
| 457 | -6. Starts the MCP server on `mcp_addr` (default `:8081`) | |
| 456 | +5. Starts the HTTP API on `api_addr` (default `127.0.0.1:8080`) | |
| 457 | +6. Starts the MCP server on `mcp_addr` (default `127.0.0.1:8081`) | |
| 458 | 458 | 7. Writes the API token to `data/ergo/api_token` |
| 459 | 459 | 8. Starts all enabled bots |
| 460 | 460 | |
| 461 | 461 | --- |
| 462 | 462 | |
| 463 | 463 |
| --- docs/reference/cli.md | |
| +++ docs/reference/cli.md | |
| @@ -451,12 +451,12 @@ | |
| 451 | |
| 452 | 1. Loads and validates `scuttlebot.yaml` |
| 453 | 2. Downloads ergo if not found (unless `ergo.external: true`) |
| 454 | 3. Generates an Ergo config and starts the IRC server |
| 455 | 4. Registers built-in bot NickServ accounts |
| 456 | 5. Starts the HTTP API on `api_addr` (default `:8080`) |
| 457 | 6. Starts the MCP server on `mcp_addr` (default `:8081`) |
| 458 | 7. Writes the API token to `data/ergo/api_token` |
| 459 | 8. Starts all enabled bots |
| 460 | |
| 461 | --- |
| 462 | |
| 463 |
| --- docs/reference/cli.md | |
| +++ docs/reference/cli.md | |
| @@ -451,12 +451,12 @@ | |
| 451 | |
| 452 | 1. Loads and validates `scuttlebot.yaml` |
| 453 | 2. Downloads ergo if not found (unless `ergo.external: true`) |
| 454 | 3. Generates an Ergo config and starts the IRC server |
| 455 | 4. Registers built-in bot NickServ accounts |
| 456 | 5. Starts the HTTP API on `api_addr` (default `127.0.0.1:8080`) |
| 457 | 6. Starts the MCP server on `mcp_addr` (default `127.0.0.1:8081`) |
| 458 | 7. Writes the API token to `data/ergo/api_token` |
| 459 | 8. Starts all enabled bots |
| 460 | |
| 461 | --- |
| 462 | |
| 463 |
| --- docs/reference/config.md | ||
| +++ docs/reference/config.md | ||
| @@ -6,12 +6,12 @@ | ||
| 6 | 6 | |
| 7 | 7 | ## Top-level |
| 8 | 8 | |
| 9 | 9 | | Field | Type | Default | Env override | |
| 10 | 10 | |-------|------|---------|--------------| |
| 11 | -| `api_addr` | string | `:8080` | `SCUTTLEBOT_API_ADDR` | | |
| 12 | -| `mcp_addr` | string | `:8081` | `SCUTTLEBOT_MCP_ADDR` | | |
| 11 | +| `api_addr` | string | `127.0.0.1:8080` | `SCUTTLEBOT_API_ADDR` | | |
| 12 | +| `mcp_addr` | string | `127.0.0.1:8081` | `SCUTTLEBOT_MCP_ADDR` | | |
| 13 | 13 | |
| 14 | 14 | --- |
| 15 | 15 | |
| 16 | 16 | ## `ergo` |
| 17 | 17 | |
| @@ -24,10 +24,12 @@ | ||
| 24 | 24 | | `server_name` | string | `irc.scuttlebot.local` | `SCUTTLEBOT_ERGO_SERVER_NAME` | |
| 25 | 25 | | `irc_addr` | string | `127.0.0.1:6667` | `SCUTTLEBOT_ERGO_IRC_ADDR` | |
| 26 | 26 | | `api_addr` | string | `127.0.0.1:8089` | `SCUTTLEBOT_ERGO_API_ADDR` | |
| 27 | 27 | | `api_token` | string | *(auto-generated)* | `SCUTTLEBOT_ERGO_API_TOKEN` | |
| 28 | 28 | | `tls_domain` | string | — | — | |
| 29 | +| `require_sasl` | bool | `false` | — | | |
| 30 | +| `default_channel_modes` | string | `+n` | — | | |
| 29 | 31 | |
| 30 | 32 | ### `ergo.history` |
| 31 | 33 | |
| 32 | 34 | | Field | Type | Default | |
| 33 | 35 | |-------|------|---------| |
| @@ -127,12 +129,12 @@ | ||
| 127 | 129 | ### `bots.warden` |
| 128 | 130 | |
| 129 | 131 | | Field | Type | Default | |
| 130 | 132 | |-------|------|---------| |
| 131 | 133 | | `enabled` | bool | `true` | |
| 132 | -| `max_joins_per_minute` | int | `10` | | |
| 133 | -| `max_messages_per_minute` | int | `60` | | |
| 134 | + | |
| 135 | +Rate limits are fixed at 5 messages/second sustained with a burst of 10. They are not configurable via YAML. | |
| 134 | 136 | |
| 135 | 137 | ### `bots.herald` |
| 136 | 138 | |
| 137 | 139 | | Field | Type | Default | |
| 138 | 140 | |-------|------|---------| |
| @@ -157,12 +159,12 @@ | ||
| 157 | 159 | --- |
| 158 | 160 | |
| 159 | 161 | ## Full skeleton |
| 160 | 162 | |
| 161 | 163 | ```yaml |
| 162 | -api_addr: :8080 | |
| 163 | -mcp_addr: :8081 | |
| 164 | +api_addr: 127.0.0.1:8080 | |
| 165 | +mcp_addr: 127.0.0.1:8081 | |
| 164 | 166 | |
| 165 | 167 | ergo: |
| 166 | 168 | external: false |
| 167 | 169 | binary_path: ergo |
| 168 | 170 | data_dir: ./data/ergo |
| 169 | 171 |
| --- docs/reference/config.md | |
| +++ docs/reference/config.md | |
| @@ -6,12 +6,12 @@ | |
| 6 | |
| 7 | ## Top-level |
| 8 | |
| 9 | | Field | Type | Default | Env override | |
| 10 | |-------|------|---------|--------------| |
| 11 | | `api_addr` | string | `:8080` | `SCUTTLEBOT_API_ADDR` | |
| 12 | | `mcp_addr` | string | `:8081` | `SCUTTLEBOT_MCP_ADDR` | |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## `ergo` |
| 17 | |
| @@ -24,10 +24,12 @@ | |
| 24 | | `server_name` | string | `irc.scuttlebot.local` | `SCUTTLEBOT_ERGO_SERVER_NAME` | |
| 25 | | `irc_addr` | string | `127.0.0.1:6667` | `SCUTTLEBOT_ERGO_IRC_ADDR` | |
| 26 | | `api_addr` | string | `127.0.0.1:8089` | `SCUTTLEBOT_ERGO_API_ADDR` | |
| 27 | | `api_token` | string | *(auto-generated)* | `SCUTTLEBOT_ERGO_API_TOKEN` | |
| 28 | | `tls_domain` | string | — | — | |
| 29 | |
| 30 | ### `ergo.history` |
| 31 | |
| 32 | | Field | Type | Default | |
| 33 | |-------|------|---------| |
| @@ -127,12 +129,12 @@ | |
| 127 | ### `bots.warden` |
| 128 | |
| 129 | | Field | Type | Default | |
| 130 | |-------|------|---------| |
| 131 | | `enabled` | bool | `true` | |
| 132 | | `max_joins_per_minute` | int | `10` | |
| 133 | | `max_messages_per_minute` | int | `60` | |
| 134 | |
| 135 | ### `bots.herald` |
| 136 | |
| 137 | | Field | Type | Default | |
| 138 | |-------|------|---------| |
| @@ -157,12 +159,12 @@ | |
| 157 | --- |
| 158 | |
| 159 | ## Full skeleton |
| 160 | |
| 161 | ```yaml |
| 162 | api_addr: :8080 |
| 163 | mcp_addr: :8081 |
| 164 | |
| 165 | ergo: |
| 166 | external: false |
| 167 | binary_path: ergo |
| 168 | data_dir: ./data/ergo |
| 169 |
| --- docs/reference/config.md | |
| +++ docs/reference/config.md | |
| @@ -6,12 +6,12 @@ | |
| 6 | |
| 7 | ## Top-level |
| 8 | |
| 9 | | Field | Type | Default | Env override | |
| 10 | |-------|------|---------|--------------| |
| 11 | | `api_addr` | string | `127.0.0.1:8080` | `SCUTTLEBOT_API_ADDR` | |
| 12 | | `mcp_addr` | string | `127.0.0.1:8081` | `SCUTTLEBOT_MCP_ADDR` | |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## `ergo` |
| 17 | |
| @@ -24,10 +24,12 @@ | |
| 24 | | `server_name` | string | `irc.scuttlebot.local` | `SCUTTLEBOT_ERGO_SERVER_NAME` | |
| 25 | | `irc_addr` | string | `127.0.0.1:6667` | `SCUTTLEBOT_ERGO_IRC_ADDR` | |
| 26 | | `api_addr` | string | `127.0.0.1:8089` | `SCUTTLEBOT_ERGO_API_ADDR` | |
| 27 | | `api_token` | string | *(auto-generated)* | `SCUTTLEBOT_ERGO_API_TOKEN` | |
| 28 | | `tls_domain` | string | — | — | |
| 29 | | `require_sasl` | bool | `false` | — | |
| 30 | | `default_channel_modes` | string | `+n` | — | |
| 31 | |
| 32 | ### `ergo.history` |
| 33 | |
| 34 | | Field | Type | Default | |
| 35 | |-------|------|---------| |
| @@ -127,12 +129,12 @@ | |
| 129 | ### `bots.warden` |
| 130 | |
| 131 | | Field | Type | Default | |
| 132 | |-------|------|---------| |
| 133 | | `enabled` | bool | `true` | |
| 134 | |
| 135 | Rate limits are fixed at 5 messages/second sustained with a burst of 10. They are not configurable via YAML. |
| 136 | |
| 137 | ### `bots.herald` |
| 138 | |
| 139 | | Field | Type | Default | |
| 140 | |-------|------|---------| |
| @@ -157,12 +159,12 @@ | |
| 159 | --- |
| 160 | |
| 161 | ## Full skeleton |
| 162 | |
| 163 | ```yaml |
| 164 | api_addr: 127.0.0.1:8080 |
| 165 | mcp_addr: 127.0.0.1:8081 |
| 166 | |
| 167 | ergo: |
| 168 | external: false |
| 169 | binary_path: ergo |
| 170 | data_dir: ./data/ergo |
| 171 |
| --- docs/reference/mcp.md | ||
| +++ docs/reference/mcp.md | ||
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | # MCP Server |
| 2 | 2 | |
| 3 | 3 | scuttlebot exposes a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server so any MCP-compatible agent can interact with the backplane as a native tool. |
| 4 | 4 | |
| 5 | 5 | **Transport:** HTTP POST at `/mcp` — JSON-RPC 2.0 over HTTP. |
| 6 | -**Address:** `mcp_addr` in `scuttlebot.yaml` (default `:8081`). | |
| 6 | +**Address:** `mcp_addr` in `scuttlebot.yaml` (default `127.0.0.1:8081`). | |
| 7 | 7 | **Auth:** Bearer token in the `Authorization` header (same token as the REST API). |
| 8 | 8 | |
| 9 | 9 | --- |
| 10 | 10 | |
| 11 | 11 | ## Connecting |
| @@ -12,11 +12,11 @@ | ||
| 12 | 12 | |
| 13 | 13 | Point your MCP client at the server address: |
| 14 | 14 | |
| 15 | 15 | ```bash |
| 16 | 16 | # scuttlebot.yaml |
| 17 | -mcp_addr: ":8081" | |
| 17 | +mcp_addr: "127.0.0.1:8081" # loopback by default; set to :8081 for external access | |
| 18 | 18 | ``` |
| 19 | 19 | |
| 20 | 20 | For Claude Code, add to `.mcp.json`: |
| 21 | 21 | |
| 22 | 22 | ```json |
| 23 | 23 |
| --- docs/reference/mcp.md | |
| +++ docs/reference/mcp.md | |
| @@ -1,11 +1,11 @@ | |
| 1 | # MCP Server |
| 2 | |
| 3 | scuttlebot exposes a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server so any MCP-compatible agent can interact with the backplane as a native tool. |
| 4 | |
| 5 | **Transport:** HTTP POST at `/mcp` — JSON-RPC 2.0 over HTTP. |
| 6 | **Address:** `mcp_addr` in `scuttlebot.yaml` (default `:8081`). |
| 7 | **Auth:** Bearer token in the `Authorization` header (same token as the REST API). |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Connecting |
| @@ -12,11 +12,11 @@ | |
| 12 | |
| 13 | Point your MCP client at the server address: |
| 14 | |
| 15 | ```bash |
| 16 | # scuttlebot.yaml |
| 17 | mcp_addr: ":8081" |
| 18 | ``` |
| 19 | |
| 20 | For Claude Code, add to `.mcp.json`: |
| 21 | |
| 22 | ```json |
| 23 |
| --- docs/reference/mcp.md | |
| +++ docs/reference/mcp.md | |
| @@ -1,11 +1,11 @@ | |
| 1 | # MCP Server |
| 2 | |
| 3 | scuttlebot exposes a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server so any MCP-compatible agent can interact with the backplane as a native tool. |
| 4 | |
| 5 | **Transport:** HTTP POST at `/mcp` — JSON-RPC 2.0 over HTTP. |
| 6 | **Address:** `mcp_addr` in `scuttlebot.yaml` (default `127.0.0.1:8081`). |
| 7 | **Auth:** Bearer token in the `Authorization` header (same token as the REST API). |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Connecting |
| @@ -12,11 +12,11 @@ | |
| 12 | |
| 13 | Point your MCP client at the server address: |
| 14 | |
| 15 | ```bash |
| 16 | # scuttlebot.yaml |
| 17 | mcp_addr: "127.0.0.1:8081" # loopback by default; set to :8081 for external access |
| 18 | ``` |
| 19 | |
| 20 | For Claude Code, add to `.mcp.json`: |
| 21 | |
| 22 | ```json |
| 23 |
| --- internal/ergo/ircdconfig.go | ||
| +++ internal/ergo/ircdconfig.go | ||
| @@ -49,15 +49,16 @@ | ||
| 49 | 49 | allow-before-connect: true |
| 50 | 50 | throttling: |
| 51 | 51 | enabled: false |
| 52 | 52 | authentication-enabled: true |
| 53 | 53 | require-sasl: |
| 54 | - enabled: false | |
| 54 | + enabled: {{.RequireSASL}} | |
| 55 | 55 | |
| 56 | 56 | channels: |
| 57 | 57 | registration: |
| 58 | 58 | enabled: true |
| 59 | + default-modes: "{{.DefaultChannelModes}}" | |
| 59 | 60 | |
| 60 | 61 | history: |
| 61 | 62 | enabled: {{.HistoryEnabled}} |
| 62 | 63 | {{- if .HistoryEnabled}} |
| 63 | 64 | channel-length: 2048 |
| @@ -95,38 +96,42 @@ | ||
| 95 | 96 | topiclen: 512 |
| 96 | 97 | |
| 97 | 98 | `)) |
| 98 | 99 | |
| 99 | 100 | type ircdTemplateData struct { |
| 100 | - NetworkName string | |
| 101 | - ServerName string | |
| 102 | - IRCAddr string | |
| 103 | - DataDir string | |
| 104 | - APIAddr string | |
| 105 | - APIToken string | |
| 106 | - HistoryEnabled bool | |
| 107 | - PostgresDSN string | |
| 108 | - MySQLEnabled bool | |
| 109 | - MySQL config.MySQLConfig | |
| 101 | + NetworkName string | |
| 102 | + ServerName string | |
| 103 | + IRCAddr string | |
| 104 | + DataDir string | |
| 105 | + APIAddr string | |
| 106 | + APIToken string | |
| 107 | + HistoryEnabled bool | |
| 108 | + PostgresDSN string | |
| 109 | + MySQLEnabled bool | |
| 110 | + MySQL config.MySQLConfig | |
| 111 | + RequireSASL bool | |
| 112 | + DefaultChannelModes string | |
| 110 | 113 | } |
| 111 | 114 | |
| 112 | 115 | // GenerateConfig renders an ircd.yaml from the given ErgoConfig. |
| 113 | 116 | func GenerateConfig(cfg config.ErgoConfig) ([]byte, error) { |
| 114 | 117 | data := ircdTemplateData{ |
| 115 | - NetworkName: cfg.NetworkName, | |
| 116 | - ServerName: cfg.ServerName, | |
| 117 | - IRCAddr: cfg.IRCAddr, | |
| 118 | - DataDir: cfg.DataDir, | |
| 119 | - APIAddr: cfg.APIAddr, | |
| 120 | - APIToken: cfg.APIToken, | |
| 121 | - HistoryEnabled: cfg.History.Enabled, | |
| 122 | - PostgresDSN: cfg.History.PostgresDSN, | |
| 123 | - MySQLEnabled: cfg.History.MySQL.Host != "" && cfg.History.PostgresDSN == "", | |
| 124 | - MySQL: cfg.History.MySQL, | |
| 118 | + NetworkName: cfg.NetworkName, | |
| 119 | + ServerName: cfg.ServerName, | |
| 120 | + IRCAddr: cfg.IRCAddr, | |
| 121 | + DataDir: cfg.DataDir, | |
| 122 | + APIAddr: cfg.APIAddr, | |
| 123 | + APIToken: cfg.APIToken, | |
| 124 | + HistoryEnabled: cfg.History.Enabled, | |
| 125 | + PostgresDSN: cfg.History.PostgresDSN, | |
| 126 | + MySQLEnabled: cfg.History.MySQL.Host != "" && cfg.History.PostgresDSN == "", | |
| 127 | + MySQL: cfg.History.MySQL, | |
| 128 | + RequireSASL: cfg.RequireSASL, | |
| 129 | + DefaultChannelModes: cfg.DefaultChannelModes, | |
| 125 | 130 | } |
| 126 | 131 | |
| 127 | 132 | var buf bytes.Buffer |
| 128 | 133 | if err := ircdTemplate.Execute(&buf, data); err != nil { |
| 129 | 134 | return nil, err |
| 130 | 135 | } |
| 131 | 136 | return buf.Bytes(), nil |
| 132 | 137 | } |
| 133 | 138 |
| --- internal/ergo/ircdconfig.go | |
| +++ internal/ergo/ircdconfig.go | |
| @@ -49,15 +49,16 @@ | |
| 49 | allow-before-connect: true |
| 50 | throttling: |
| 51 | enabled: false |
| 52 | authentication-enabled: true |
| 53 | require-sasl: |
| 54 | enabled: false |
| 55 | |
| 56 | channels: |
| 57 | registration: |
| 58 | enabled: true |
| 59 | |
| 60 | history: |
| 61 | enabled: {{.HistoryEnabled}} |
| 62 | {{- if .HistoryEnabled}} |
| 63 | channel-length: 2048 |
| @@ -95,38 +96,42 @@ | |
| 95 | topiclen: 512 |
| 96 | |
| 97 | `)) |
| 98 | |
| 99 | type ircdTemplateData struct { |
| 100 | NetworkName string |
| 101 | ServerName string |
| 102 | IRCAddr string |
| 103 | DataDir string |
| 104 | APIAddr string |
| 105 | APIToken string |
| 106 | HistoryEnabled bool |
| 107 | PostgresDSN string |
| 108 | MySQLEnabled bool |
| 109 | MySQL config.MySQLConfig |
| 110 | } |
| 111 | |
| 112 | // GenerateConfig renders an ircd.yaml from the given ErgoConfig. |
| 113 | func GenerateConfig(cfg config.ErgoConfig) ([]byte, error) { |
| 114 | data := ircdTemplateData{ |
| 115 | NetworkName: cfg.NetworkName, |
| 116 | ServerName: cfg.ServerName, |
| 117 | IRCAddr: cfg.IRCAddr, |
| 118 | DataDir: cfg.DataDir, |
| 119 | APIAddr: cfg.APIAddr, |
| 120 | APIToken: cfg.APIToken, |
| 121 | HistoryEnabled: cfg.History.Enabled, |
| 122 | PostgresDSN: cfg.History.PostgresDSN, |
| 123 | MySQLEnabled: cfg.History.MySQL.Host != "" && cfg.History.PostgresDSN == "", |
| 124 | MySQL: cfg.History.MySQL, |
| 125 | } |
| 126 | |
| 127 | var buf bytes.Buffer |
| 128 | if err := ircdTemplate.Execute(&buf, data); err != nil { |
| 129 | return nil, err |
| 130 | } |
| 131 | return buf.Bytes(), nil |
| 132 | } |
| 133 |
| --- internal/ergo/ircdconfig.go | |
| +++ internal/ergo/ircdconfig.go | |
| @@ -49,15 +49,16 @@ | |
| 49 | allow-before-connect: true |
| 50 | throttling: |
| 51 | enabled: false |
| 52 | authentication-enabled: true |
| 53 | require-sasl: |
| 54 | enabled: {{.RequireSASL}} |
| 55 | |
| 56 | channels: |
| 57 | registration: |
| 58 | enabled: true |
| 59 | default-modes: "{{.DefaultChannelModes}}" |
| 60 | |
| 61 | history: |
| 62 | enabled: {{.HistoryEnabled}} |
| 63 | {{- if .HistoryEnabled}} |
| 64 | channel-length: 2048 |
| @@ -95,38 +96,42 @@ | |
| 96 | topiclen: 512 |
| 97 | |
| 98 | `)) |
| 99 | |
| 100 | type ircdTemplateData struct { |
| 101 | NetworkName string |
| 102 | ServerName string |
| 103 | IRCAddr string |
| 104 | DataDir string |
| 105 | APIAddr string |
| 106 | APIToken string |
| 107 | HistoryEnabled bool |
| 108 | PostgresDSN string |
| 109 | MySQLEnabled bool |
| 110 | MySQL config.MySQLConfig |
| 111 | RequireSASL bool |
| 112 | DefaultChannelModes string |
| 113 | } |
| 114 | |
| 115 | // GenerateConfig renders an ircd.yaml from the given ErgoConfig. |
| 116 | func GenerateConfig(cfg config.ErgoConfig) ([]byte, error) { |
| 117 | data := ircdTemplateData{ |
| 118 | NetworkName: cfg.NetworkName, |
| 119 | ServerName: cfg.ServerName, |
| 120 | IRCAddr: cfg.IRCAddr, |
| 121 | DataDir: cfg.DataDir, |
| 122 | APIAddr: cfg.APIAddr, |
| 123 | APIToken: cfg.APIToken, |
| 124 | HistoryEnabled: cfg.History.Enabled, |
| 125 | PostgresDSN: cfg.History.PostgresDSN, |
| 126 | MySQLEnabled: cfg.History.MySQL.Host != "" && cfg.History.PostgresDSN == "", |
| 127 | MySQL: cfg.History.MySQL, |
| 128 | RequireSASL: cfg.RequireSASL, |
| 129 | DefaultChannelModes: cfg.DefaultChannelModes, |
| 130 | } |
| 131 | |
| 132 | var buf bytes.Buffer |
| 133 | if err := ircdTemplate.Execute(&buf, data); err != nil { |
| 134 | return nil, err |
| 135 | } |
| 136 | return buf.Bytes(), nil |
| 137 | } |
| 138 |
| --- internal/ergo/ircdconfig_test.go | ||
| +++ internal/ergo/ircdconfig_test.go | ||
| @@ -93,5 +93,47 @@ | ||
| 93 | 93 | yaml := string(data) |
| 94 | 94 | if strings.Contains(yaml, "postgres") { |
| 95 | 95 | t.Error("postgres config should not appear when history disabled") |
| 96 | 96 | } |
| 97 | 97 | } |
| 98 | + | |
| 99 | +func TestGenerateConfigRequireSASL(t *testing.T) { | |
| 100 | + cfg := config.ErgoConfig{ | |
| 101 | + NetworkName: "testnet", | |
| 102 | + ServerName: "irc.test.local", | |
| 103 | + IRCAddr: "127.0.0.1:6667", | |
| 104 | + APIAddr: "127.0.0.1:8089", | |
| 105 | + APIToken: "tok", | |
| 106 | + RequireSASL: true, | |
| 107 | + } | |
| 108 | + | |
| 109 | + data, err := ergo.GenerateConfig(cfg) | |
| 110 | + if err != nil { | |
| 111 | + t.Fatalf("GenerateConfig: %v", err) | |
| 112 | + } | |
| 113 | + | |
| 114 | + yaml := string(data) | |
| 115 | + if !strings.Contains(yaml, "enabled: true") { | |
| 116 | + t.Error("require-sasl.enabled should be true") | |
| 117 | + } | |
| 118 | +} | |
| 119 | + | |
| 120 | +func TestGenerateConfigDefaultChannelModes(t *testing.T) { | |
| 121 | + cfg := config.ErgoConfig{ | |
| 122 | + NetworkName: "testnet", | |
| 123 | + ServerName: "irc.test.local", | |
| 124 | + IRCAddr: "127.0.0.1:6667", | |
| 125 | + APIAddr: "127.0.0.1:8089", | |
| 126 | + APIToken: "tok", | |
| 127 | + DefaultChannelModes: "+Rn", | |
| 128 | + } | |
| 129 | + | |
| 130 | + data, err := ergo.GenerateConfig(cfg) | |
| 131 | + if err != nil { | |
| 132 | + t.Fatalf("GenerateConfig: %v", err) | |
| 133 | + } | |
| 134 | + | |
| 135 | + yaml := string(data) | |
| 136 | + if !strings.Contains(yaml, `default-modes: "+Rn"`) { | |
| 137 | + t.Errorf("default-modes not set correctly\ngot:\n%s", yaml) | |
| 138 | + } | |
| 139 | +} | |
| 98 | 140 |
| --- internal/ergo/ircdconfig_test.go | |
| +++ internal/ergo/ircdconfig_test.go | |
| @@ -93,5 +93,47 @@ | |
| 93 | yaml := string(data) |
| 94 | if strings.Contains(yaml, "postgres") { |
| 95 | t.Error("postgres config should not appear when history disabled") |
| 96 | } |
| 97 | } |
| 98 |
| --- internal/ergo/ircdconfig_test.go | |
| +++ internal/ergo/ircdconfig_test.go | |
| @@ -93,5 +93,47 @@ | |
| 93 | yaml := string(data) |
| 94 | if strings.Contains(yaml, "postgres") { |
| 95 | t.Error("postgres config should not appear when history disabled") |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | func TestGenerateConfigRequireSASL(t *testing.T) { |
| 100 | cfg := config.ErgoConfig{ |
| 101 | NetworkName: "testnet", |
| 102 | ServerName: "irc.test.local", |
| 103 | IRCAddr: "127.0.0.1:6667", |
| 104 | APIAddr: "127.0.0.1:8089", |
| 105 | APIToken: "tok", |
| 106 | RequireSASL: true, |
| 107 | } |
| 108 | |
| 109 | data, err := ergo.GenerateConfig(cfg) |
| 110 | if err != nil { |
| 111 | t.Fatalf("GenerateConfig: %v", err) |
| 112 | } |
| 113 | |
| 114 | yaml := string(data) |
| 115 | if !strings.Contains(yaml, "enabled: true") { |
| 116 | t.Error("require-sasl.enabled should be true") |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | func TestGenerateConfigDefaultChannelModes(t *testing.T) { |
| 121 | cfg := config.ErgoConfig{ |
| 122 | NetworkName: "testnet", |
| 123 | ServerName: "irc.test.local", |
| 124 | IRCAddr: "127.0.0.1:6667", |
| 125 | APIAddr: "127.0.0.1:8089", |
| 126 | APIToken: "tok", |
| 127 | DefaultChannelModes: "+Rn", |
| 128 | } |
| 129 | |
| 130 | data, err := ergo.GenerateConfig(cfg) |
| 131 | if err != nil { |
| 132 | t.Fatalf("GenerateConfig: %v", err) |
| 133 | } |
| 134 | |
| 135 | yaml := string(data) |
| 136 | if !strings.Contains(yaml, `default-modes: "+Rn"`) { |
| 137 | t.Errorf("default-modes not set correctly\ngot:\n%s", yaml) |
| 138 | } |
| 139 | } |
| 140 |