diff --git a/.env.example b/.env.example index f576b8b..fe440c9 100644 --- a/.env.example +++ b/.env.example @@ -47,6 +47,12 @@ ROOT_DOMAIN=example.com # Used in stack.yml.example for configuring e PERSONAL_CLAW_TELEGRAM_BOT_TOKEN= WORK_CLAW_TELEGRAM_BOT_TOKEN= +# ── Per-Claw Matrix Bots ───────────────────────────────────── +# Convention: _MATRIX_BOT_TOKEN +# *Each* claw MUST use a unique bot token to avoid polling conflicts. + +CLAW_NAME_MATRIX_ACCESS_TOKEN= + # ── Local Browser Node (docker/local-browser-node) ────────────── # Claw name in stack.yml for the local browser node to connect to # Also requires Cloudflare Access Token. diff --git a/build/pre-deploy.mjs b/build/pre-deploy.mjs index 3082952..173e5f9 100644 --- a/build/pre-deploy.mjs +++ b/build/pre-deploy.mjs @@ -294,8 +294,18 @@ function validateClaw(name, claw) { } const telegram = claw.telegram; - if (!telegram?.bot_token) { - warn(`Claw '${name}' has no telegram.bot_token — Telegram will be disabled`); + if (telegram?.enabled && !telegram?.bot_token) { + warn(`Claw '${name}' has telegram.enabled: true but no bot_token — Telegram may not work without a token (env var fallback allowed)`); + } + + const matrix = claw.matrix; + if (matrix?.enabled) { + if (!matrix.homeserver) { + fatal(`Claw '${name}' has matrix.enabled: true but matrix.homeserver is not set`); + } + if (!matrix.access_token) { + fatal(`Claw '${name}' has matrix.enabled: true but matrix.access_token is empty — set the access token env var in .env`); + } } } @@ -356,6 +366,10 @@ function computeDerivedValues(claws, stack, host, previousDeploy) { claw.llemtry_url = logUrl ? logUrl + "/llemtry" : ""; claw.enable_events_logging = stack.logging?.events || false; claw.enable_llemtry_logging = stack.logging?.llemtry || false; + // telegram_enabled / matrix_enabled are flat booleans for Handlebars — avoids + // empty-string output when the section is absent from stack.yml. + claw.telegram_enabled = claw.telegram?.enabled === true; + claw.matrix_enabled = claw.matrix?.enabled === true; } return autoTokens; @@ -640,7 +654,6 @@ async function main() { LOG_WORKER_TOKEN: "log_worker_token", EVENTS_URL: "events_url", LLEMTRY_URL: "llemtry_url", - ADMIN_TELEGRAM_ID: "telegram.allow_from", }; // Check against first claw (these vars are stack-wide, same for all claws) const firstClaw = Object.values(claws)[0]; @@ -650,7 +663,13 @@ async function main() { return !val && val !== 0 && val !== false; }) .map(([envVar]) => envVar); - writeFileSync(join(DEPLOY_DIR, "openclaw-stack", "empty-env-vars"), emptyVars.join("\n") + "\n"); + // Channel vars are per-claw: only emitted in docker-compose.yml when the channel is enabled. + // Always pre-resolve them regardless of any claw's config, so openclaw.jsonc ${VAR} + // substitution succeeds on claws where the channel is disabled and these env vars are absent + // from the container environment. Checking only the first claw would break multi-claw + // stacks where claws have heterogeneous channel config. + const alwaysResolveVars = ["ADMIN_TELEGRAM_ID", "MATRIX_HOMESERVER", "MATRIX_ACCESS_TOKEN"]; + writeFileSync(join(DEPLOY_DIR, "openclaw-stack", "empty-env-vars"), [...emptyVars, ...alwaysResolveVars].join("\n") + "\n"); // 7d-post. Resolve {{INSTALL_DIR}} in host/ files (cron configs, logrotate) const installDir = String(stack.install_dir || "/home/openclaw"); diff --git a/deploy/host/backup.sh b/deploy/host/backup.sh index 67a4ccb..462adc6 100755 --- a/deploy/host/backup.sh +++ b/deploy/host/backup.sh @@ -32,13 +32,16 @@ while IFS= read -r install_dir; do # Create backup of this claw's config and data. # Include all workspace dirs (main=workspace, agents=workspace-). + # Include matrix/ when present — contains sync state and E2EE crypto keys. # || true: continue to other instances if one fails (error still printed) workspace_dirs=$(cd "${inst_dir}" && ls -d .openclaw/workspace .openclaw/workspace-* 2>/dev/null || true) + matrix_dir=$(cd "${inst_dir}" && ls -d .openclaw/matrix 2>/dev/null || true) tar -czf "${BACKUP_FILE}" \ -C "${inst_dir}" \ .openclaw/openclaw.json \ .openclaw/credentials \ ${workspace_dirs} \ + ${matrix_dir} \ || true # Set ownership so container can also access backups if needed diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index 0825c72..e425a98 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -100,7 +100,42 @@ echo "prefix=$npm_global" > /home/node/.npmrc export PATH="$npm_global/bin:$PATH" echo "[entrypoint] npm global prefix set to $npm_global" -# ── 1h. Auto-generate gateway shims from sandbox-toolkit.yaml ────── + +# ── 1h. Patch matrix plugin: keyed-async-queue subpath regression ── +# TODO: remove once openclaw/openclaw#32772 is fixed +# OpenClaw 2026.3.2 broke the matrix plugin — send-queue.ts imports +# "openclaw/plugin-sdk/keyed-async-queue" but jiti doesn't resolve the +# subpath. Patch to use the barrel export. Version-gated so it no-ops +# once upstream ships a fix. +SEND_QUEUE="/app/extensions/matrix/src/matrix/send-queue.ts" +OPENCLAW_VERSION=$(node -e "console.log(require('/app/package.json').version)" 2>/dev/null || echo "unknown") +if [ "$OPENCLAW_VERSION" = "2026.3.2" ] && [ -f "$SEND_QUEUE" ] && grep -q '"openclaw/plugin-sdk/keyed-async-queue"' "$SEND_QUEUE"; then + sed -i 's|"openclaw/plugin-sdk/keyed-async-queue"|"openclaw/plugin-sdk"|' "$SEND_QUEUE" + echo "[entrypoint] Patched matrix plugin keyed-async-queue import (openclaw/openclaw#32772)" +elif [ "$OPENCLAW_VERSION" != "2026.3.2" ] && [ -f "$SEND_QUEUE" ]; then + echo "[entrypoint] OpenClaw $OPENCLAW_VERSION — matrix keyed-async-queue patch no longer needed, consider removing" +fi + +# ── 1i. Install matrix plugin dependencies ───────────────────────── +# TODO: remove once openclaw/openclaw#16031 is fixed +# The bundled matrix plugin at /app/extensions/matrix declares deps in +# package.json but they aren't installed in the Docker image. Install +# them on first boot if the node_modules dir is missing. +MATRIX_EXT="/app/extensions/matrix" +if [ "${MATRIX_ENABLED:-false}" = "true" ] && [ -f "$MATRIX_EXT/package.json" ] && \ + { [ ! -d "$MATRIX_EXT/node_modules" ] || [ ! -d "$MATRIX_EXT/node_modules/@vector-im/matrix-bot-sdk" ]; }; then + echo "[entrypoint] Installing matrix plugin dependencies..." + cd "$MATRIX_EXT" + if npm install --omit=dev 2>&1; then + echo "[entrypoint] Matrix plugin dependencies installed" + else + echo "[entrypoint] WARNING: matrix plugin dependency install failed" >&2 + fi + cd /app +fi + + +# ── 1j. Auto-generate gateway shims from sandbox-toolkit.yaml ────── # Shims satisfy the gateway's load-time skill binary preflight checks. # Real binaries live in sandbox images — shims are gateway-only (not bind-mounted). SKILL_BINS="/opt/skill-bins" diff --git a/deploy/openclaw-stack/resolve-config-vars.mjs b/deploy/openclaw-stack/resolve-config-vars.mjs index 1eac576..0d6107f 100644 --- a/deploy/openclaw-stack/resolve-config-vars.mjs +++ b/deploy/openclaw-stack/resolve-config-vars.mjs @@ -12,6 +12,7 @@ import { readFileSync, existsSync } from "fs"; import { join, dirname } from "path"; import yaml from "js-yaml"; +import { parse as parseJsonc } from "jsonc-parser"; const [configFile, clawName] = process.argv.slice(2); if (!configFile || !clawName) { @@ -53,7 +54,8 @@ for (const entry of service[1]?.environment || []) { // Resolve ${VAR} and ${VAR:-default} in the raw text (preserves comments, formatting) let content = readFileSync(configFile, "utf-8"); -content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => { + +function resolveExpr(expr) { const defaultMatch = expr.match(/^([^:]+):-(.*)$/); if (defaultMatch) { const key = defaultMatch[1]; @@ -61,6 +63,46 @@ content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => { return (key in env && env[key] !== "") ? env[key] : defaultVal; } return (expr in env) ? env[expr] : ""; +} + +// When a "${VAR}" is the entire JSON value (quoted), coerce booleans and numbers +// so "enabled": "${MATRIX_ENABLED}" becomes "enabled": true, not "enabled": "true". +content = content.replace(/"(\$\{([^}]+)\})"/g, (_match, _fullRef, expr) => { + const val = resolveExpr(expr); + if (val === "true" || val === "false") return val; + if (val !== "" && !isNaN(val) && !isNaN(parseFloat(val))) return val; + return `"${val}"`; }); +// Resolve remaining ${VAR} refs (inside longer strings, unquoted positions) +content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => resolveExpr(expr)); + +// Strip disabled channel blocks from the resolved config. +// Removing the block entirely prevents the channel from appearing in the Control UI +// (same as unconfigured channels like WhatsApp, iMessage, etc.). +// The local .jsonc source of truth retains the full config with comments. +const stripTelegram = env.TELEGRAM_ENABLED === "false"; +const stripMatrix = env.MATRIX_ENABLED === "false"; + +if (stripTelegram || stripMatrix) { + const config = parseJsonc(content, [], { allowTrailingComma: true }); + if (config?.channels) { + if (stripTelegram) delete config.channels.telegram; + if (stripMatrix) delete config.channels.matrix; + } + content = JSON.stringify(config, null, 2) + "\n"; +} + +// Drop blank IDs introduced by env substitution, e.g. [""] when +// ADMIN_TELEGRAM_ID is unset. The live config UI normalizes these to []. +const config = parseJsonc(content, [], { allowTrailingComma: true }); +const allowFrom = config?.tools?.elevated?.allowFrom; +if (allowFrom && typeof allowFrom === "object") { + for (const [channel, ids] of Object.entries(allowFrom)) { + if (!Array.isArray(ids)) continue; + allowFrom[channel] = ids.filter((id) => typeof id !== "string" || id.trim() !== ""); + } + content = JSON.stringify(config, null, 2) + "\n"; +} + process.stdout.write(content); diff --git a/docker-compose.yml.hbs b/docker-compose.yml.hbs index 743fca7..7a94481 100644 --- a/docker-compose.yml.hbs +++ b/docker-compose.yml.hbs @@ -59,8 +59,19 @@ services: - OPENAI_BASE_URL={{this.openai_base_url}} - OPENAI_CODEX_BASE_URL={{this.openai_codex_base_url}} # ── Telegram ── + - TELEGRAM_ENABLED={{this.telegram_enabled}} +{{#if this.telegram_enabled}} - TELEGRAM_BOT_TOKEN={{this.telegram.bot_token}} - ADMIN_TELEGRAM_ID={{this.telegram.allow_from}} +{{/if}} + # ── Matrix ── + # MATRIX_ENABLED is always emitted so openclaw.jsonc ${MATRIX_ENABLED} resolves cleanly. + # Sensitive vars (homeserver, token) are only emitted when Matrix is enabled. + - MATRIX_ENABLED={{this.matrix_enabled}} +{{#if this.matrix_enabled}} + - MATRIX_HOMESERVER={{this.matrix.homeserver}} + - MATRIX_ACCESS_TOKEN={{this.matrix.access_token}} +{{/if}} # ── Domain & UI ── - OPENCLAW_DOMAIN={{this.domain}} - OPENCLAW_DOMAIN_PATH={{this.domain_path}} diff --git a/docs/MATRIX.md b/docs/MATRIX.md new file mode 100644 index 0000000..acc0f4f --- /dev/null +++ b/docs/MATRIX.md @@ -0,0 +1,206 @@ +# Matrix Setup + +OpenClaw supports Matrix as a messaging channel via the `@openclaw/matrix` plugin. Matrix is an open, federated, E2E-encrypted messaging protocol. You can use any Matrix account on any homeserver — no phone number required. + +Full upstream config reference: https://docs.openclaw.ai/channels/matrix + +--- + +## 1. Create a Matrix Bot Account + +Create a dedicated Matrix account for your claw to use as its bot identity. + +**Option A: matrix.org (easiest)** + +1. Go to [app.element.io](https://app.element.io) and register a new account +2. Use a username that identifies it as a bot (e.g., `openclaw-yourname`) +3. Note the homeserver: `https://matrix.org` + +**Option B: Any other public homeserver** + +Any Matrix homeserver works. The account only needs to be able to join rooms and receive DMs. + +--- + +## 2. Get an Access Token + +The stack uses access-token auth. Do not use password auth. + +1. Log in to your bot account in Element (or any Matrix client) +2. Go to **Settings** → **Help & About** → scroll to **Access Token** +3. Copy the token — it looks like `syt_...` + +Alternatively, use the Matrix REST API: + +```bash +curl -XPOST 'https://matrix.org/_matrix/client/v3/login' \ + -H 'Content-Type: application/json' \ + -d '{"type":"m.login.password","user":"@yourbot:matrix.org","password":"yourpassword"}' +# → {"access_token": "syt_...", ...} +``` + +--- + +## 3. Configure + +Add to `stack.yml` under the claw that should use Matrix: + +```yaml +claws: + personal-claw: + matrix: + enabled: true + access_token: ${PERSONAL_CLAW_MATRIX_ACCESS_TOKEN} + # homeserver: "https://matrix.org" # optional — overrides the default +``` + +Add the token to `.env`: + +```env +PERSONAL_CLAW_MATRIX_ACCESS_TOKEN=syt_your_token_here +``` + +The claw name prefix (e.g. `PERSONAL_CLAW`) must match the claw key in `stack.yml` with hyphens replaced by underscores and uppercased. + +Then rebuild: + +```bash +npm run pre-deploy +``` + +--- + +## 4. Room Configuration (optional) + +By default: + +- DMs require pairing approval (`dm.policy: pairing`) +- Group rooms respond to @mentions in any joined room (`groupPolicy: open`) +- The bot auto-joins any room it's invited to (`auto_join: always`) + +To restrict to specific rooms, change `groupPolicy` to `allowlist` in `openclaw.jsonc` — see **Rooms and Mention Gating** below. + +--- + +## 5. Deploy + +After running `npm run pre-deploy`, sync and restart the claw: + +```bash +scripts/sync-deploy.sh +# Restart the specific claw (service name: -openclaw-) +sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-personal-claw' +``` + +On first boot, the OpenClaw gateway loads the bundled `@openclaw/matrix` plugin automatically when `MATRIX_ENABLED=true`. No installation step is required — the plugin ships with OpenClaw. + +--- + +## 6. Pair Your Account + +Matrix DMs default to pairing approval. After the claw starts: + +1. Send any message to your bot account from your personal Matrix client +2. On the VPS, check pending pairings: + +```bash +openclaw pairing list matrix +``` + +3. Approve your device: + +```bash +openclaw pairing approve matrix +``` + +After approval, the bot responds to your DMs normally. + +--- + +## Rooms and Mention Gating + +Rooms use `groupPolicy: open` by default — the bot responds when @mentioned in any room it has joined. To restrict to specific rooms, set `groupPolicy: "allowlist"` in the per-claw `openclaw.jsonc` and add rooms under `channels.matrix.groups`. To add a room to the allowlist: + +1. Invite the bot account to the room from your Matrix client +2. Edit `openclaw//openclaw.jsonc` and add the room under `channels.matrix.groups`: + +```jsonc +"matrix": { + // ... + "groups": { + "!roomid:matrix.org": { "enabled": true, "mention_only": false } + } +} +``` + +1. Run `npm run pre-deploy && scripts/sync-deploy.sh`, then restart the claw + +Alternatively, configure rooms live via the Control UI without redeploying. + +To require the bot to be @mentioned before it responds: set `"mention_only": true` for that room. + +--- + +## E2EE (Optional) + +Matrix E2EE is supported but requires additional setup: + +1. Edit `openclaw//openclaw.jsonc` and set `"encryption": true` in the `channels.matrix` block, then run `npm run pre-deploy && scripts/sync-deploy.sh` +2. Verify the bot's Matrix device from another Matrix client (e.g., Element → Security → Verify) +3. Ensure `~/.openclaw/matrix/` is included in your backup (crypto state must survive restarts — the backup script covers this automatically) + +> **Note:** `encryption` is not rendered from `stack.yml` — it must be set directly in the per-claw `openclaw.jsonc`. Use the Control UI or a direct file edit. + +> **Recommendation:** Start without E2EE (`encryption: false`). Add E2EE only after confirming the Matrix channel works. Rotating the access token creates a new device identity and requires re-verification in encrypted rooms. + +> **Beeper:** Beeper requires E2EE to be enabled. Configure `encryption: true` and complete device verification before using Beeper as a Matrix client. + +--- + +## Token Rotation + +If you need to rotate the Matrix access token: + +1. Generate a new token for the bot account (via Element Settings or the login API) +2. Update `.env` with the new token +3. Run `npm run pre-deploy` → `scripts/sync-deploy.sh` +4. Restart the claw: `sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-'` + +Token rotation does not affect OpenClaw device pairings, but may require Matrix-side device re-verification in encrypted rooms. + +--- + +## Troubleshooting + +### Bot not responding to DMs + +Check pending pairings: + +```bash +openclaw pairing list matrix +openclaw pairing approve matrix +``` + +### Bot not responding in rooms + +- Confirm the room ID is in `matrix.groups` in `stack.yml` +- Confirm `auto_join` is not set to `never` +- Check claw logs: `sudo docker logs -openclaw- 2>&1 | grep -i matrix` + +### Plugin not loading + +The `@openclaw/matrix` plugin is bundled with OpenClaw. If the channel is enabled but not responding, check that the plugin entry is in `openclaw.json`: + +```bash +sudo docker exec --user node -openclaw- openclaw plugins list +``` + +### E2EE verification + +If the bot is in encrypted rooms and messages are not decrypted: + +1. Open Element with your personal account +2. Go to the room → click the bot's name → **Verify** +3. Complete the emoji verification flow + +The bot's device must be verified from within each encrypted room. diff --git a/docs/PROPOSAL-MATRIX-CHANNEL.md b/docs/PROPOSAL-MATRIX-CHANNEL.md new file mode 100644 index 0000000..1d3bb30 --- /dev/null +++ b/docs/PROPOSAL-MATRIX-CHANNEL.md @@ -0,0 +1,443 @@ +# Proposal: feat/matrix-channel — Matrix Channel Integration + +**Status:** Implemented +**Branch:** `feat/matrix-channel` +**Depends on:** None +**Idempotency note:** In this context, "idempotent" means the change can be applied repeatedly without drift and can be introduced either before initial deployment or later on an existing deployment. + +--- + +## Goal + +Add Matrix as a supported messaging channel in `openclaw-stack`, using the existing OpenClaw Matrix plugin documented at: + +`https://docs.openclaw.ai/channels/matrix` + +OpenClaw supports Matrix via the `@openclaw/matrix` plugin. For this stack, the implementation work is plugin installation, channel configuration, persistence, and operator workflows. + +--- + +## What Upstream Already Supports + +Per the current OpenClaw docs and confirmed against a live installation, Matrix is: + +- **Bundled with OpenClaw core** — `~/.openclaw/extensions/` is empty on a working Matrix install; no `plugins install` step needed +- Plugin key in `plugins.entries` is `"matrix"` (unscoped), not `"@openclaw/matrix"` +- Configured under `channels.matrix` +- Compatible with direct messages, rooms, threads, media, reactions, polls, location, and native commands +- Capable of E2EE when `channels.matrix.encryption: true` and the crypto module is available +- Able to run multiple Matrix accounts via `channels.matrix.accounts` + +Important implication: this stack does not need to invent a Matrix transport layer, and does not need to manage plugin installation. It needs to make Matrix channel configuration first-class deployment concerns. + +--- + +## Recommended Path + +Implement Matrix in `openclaw-stack` using OpenClaw's native Matrix plugin and config model: + +1. Activate the bundled `@openclaw/matrix` plugin via `plugins.entries.matrix.enabled` — no install step needed. +2. Expose Matrix credentials/config through `stack.yml` and `.env`. +3. Render `channels.matrix` into each instance's `openclaw.json`. +4. Persist Matrix runtime state so access tokens, sync state, and crypto state survive restarts. +5. Document DM pairing, room allowlists, and E2EE verification as operator workflows. + +Implement Matrix through the official plugin and stack-managed configuration. + +--- + +## Proposed Stack Design + +### 1. Plugin activation + +Matrix is bundled with OpenClaw core — no install step is needed. The `~/.openclaw/extensions/` directory remains empty on a working Matrix installation. + +To activate the plugin, declare it in `plugins.entries` using the unscoped key `"matrix"`: + +```jsonc +"plugins": { + "entries": { + "matrix": { "enabled": "${MATRIX_ENABLED}" } + } +} +``` + +When `MATRIX_ENABLED=false`, the plugin entry remains in the config but the channel is inactive. When `MATRIX_ENABLED=true`, the channel starts on gateway boot — no restart of the entrypoint is needed beyond the normal container start. + +### 2. Channel configuration + +The stack should render OpenClaw's native Matrix config into `openclaw.json`, for example: + +```jsonc +{ + "channels": { + "matrix": { + "enabled": true, + "homeserver": "$MATRIX_HOMESERVER", + "accessToken": "$MATRIX_ACCESS_TOKEN", // Access token for the Matrix account this claw uses as its bot-style identity + "encryption": false, + "dm": { "policy": "pairing" }, + "groupPolicy": "allowlist" + } + } +} +``` + +Optional E2EE sets `"encryption": true`. + +### 3. Runtime persistence + +The upstream docs state that Matrix stores credentials and sync/crypto state under `~/.openclaw`, including: + +- `~/.openclaw/credentials/matrix/credentials.json` +- `~/.openclaw/matrix/accounts/...` + +That means this proposal must account for persistent storage, especially for: + +- Access-token backed sessions +- Sync state +- E2EE crypto state and device verification + +This stack already persists the relevant `~/.openclaw` paths, so Matrix can reuse them without a new volume design. + +Backup coverage should be extended to include: + +- `~/.openclaw/matrix/` + +E2EE should not be considered production-ready in this stack until backup/restore covers the Matrix crypto state under that tree. + +### 4. Access-control model + +The plugin's default Matrix DM behavior is `dm.policy = "pairing"`, which is a good operational default for this stack. + +For rooms, upstream defaults are stricter than Telegram: + +- `channels.matrix.groupPolicy = "allowlist"` by default +- Room allowlisting is configured via `channels.matrix.groups` +- Group sender allowlisting is configured via `channels.matrix.groupAllowFrom` +- Invites auto-join by default and can be restricted with `autoJoin` and `autoJoinAllowlist` + +Matrix is not just "Telegram but different transport." It has a richer room and allowlist model that should be surfaced in stack config/docs. + +### 5. Mental model: claws vs Matrix accounts + +A claw is an OpenClaw gateway instance with its own domain, config, token, workspace, and runtime state. + +A Matrix account is just one messaging identity that a claw can log in as. + +For v1, the intended model is: + +- one claw = one Matrix bot account +- one Matrix bot account can talk to multiple human Matrix users +- DMs are gated by pairing +- rooms are gated by allowlists and per-room policy + +This is not a "one Matrix account per human user" design. Multiple people can use the same claw through the same bot account, subject to the claw's pairing and room policy. + +Upstream supports multiple Matrix accounts per claw. In this stack, the normal and intended relationship is one Matrix account per claw, and that is the model used by this proposal. + +--- + +## Required Changes + +### `stack.yml.example` + +Add a top-level per-claw `matrix:` section, parallel to `telegram:`, and map it to upstream `channels.matrix`. Example: + +```yaml +defaults: + matrix: + enabled: false + homeserver: "https://matrix.org" + dm_policy: pairing + dm_allow_from: [] + group_policy: allowlist + encryption: false + groups: + # "!roomid:matrix.org": + # enabled: true + # mention_only: true + group_allow_from: [] + auto_join: always + auto_join_allowlist: [] + +claws: + main: + matrix: + enabled: true + access_token: ${MAIN_CLAW_MATRIX_ACCESS_TOKEN} # Access token for the Matrix account this claw logs in as +``` + +This matches the repo's current config style and deep-merge behavior. The render step can translate it into upstream fields such as `dm.policy`, `groupPolicy`, `groupAllowFrom`, and `autoJoin`. + +Default resolution: + +- `defaults.matrix.homeserver` should provide the normal stack-wide default +- individual claws may override it with `claws..matrix.homeserver` +- per-claw override is only needed when different claws intentionally use different homeservers + +Room identity: + +- stack config should use canonical Matrix room IDs such as `!roomid:matrix.org` +- room aliases such as `#room:matrix.org` may help operators discover rooms, but the stack should render and persist canonical room IDs to avoid alias-resolution ambiguity + +### `.env.example` + +Add per-claw access-token credentials following the stack's existing naming pattern: + +```env +MAIN_CLAW_MATRIX_ACCESS_TOKEN= +# Optional if homeserver differs per claw: +# MAIN_CLAW_MATRIX_HOMESERVER=https://matrix.org +``` + +Use access-token auth only in this stack. The rendered container env can still expose `MATRIX_HOMESERVER` and `MATRIX_ACCESS_TOKEN`, but stack inputs should follow the per-claw naming pattern already used elsewhere in this repo. Multi-account support is not part of this stack model and does not need a flat env-var scheme. + +`MAIN_CLAW_MATRIX_ACCESS_TOKEN` is only an example. The actual env var name depends on the claw name, such as `ALERTS_CLAW_MATRIX_ACCESS_TOKEN`. +This token belongs to the Matrix account the claw uses as its bot-style identity, not to the homeserver itself. + +This proposal is about the claw's interactive Matrix identity. If Matrix is later used for host/infrastructure alerts, that should use a separate Matrix sender identity, separate access token, and likely a separate room configuration rather than reusing the claw's chat identity. + +### `openclaw.jsonc` template + +Render `channels.matrix` only when enabled, and pass through supported options such as: + +- `enabled` +- `homeserver` +- `accessToken` +- `encryption` +- `dm.policy` +- `dm.allowFrom` +- `groupPolicy` +- `groupAllowFrom` +- `groups` +- `autoJoin` +- `autoJoinAllowlist` + +### Deploy/install scripts + +No plugin install step is needed in the entrypoint or elsewhere. Matrix is bundled and activated through config (`plugins.entries.matrix.enabled`). + +The entrypoint requires no Matrix-specific changes. The plugin is loaded by the OpenClaw gateway process on startup when the `plugins.entries.matrix` entry is enabled. + +### `docker-compose.yml.hbs` + +Add Matrix env vars to each claw service so `openclaw.json` can resolve them via env substitution at startup. + +Required env wiring: + +```yaml +{{#if this.matrix.enabled}} +- MATRIX_HOMESERVER={{this.matrix.homeserver}} +- MATRIX_ACCESS_TOKEN={{this.matrix.access_token}} +{{/if}} +``` + +These env vars should only be emitted when Matrix is enabled for that claw. + +Implementation note: + +- Ensure the claw-service Handlebars context exposes `this.matrix` in the same way it already exposes other per-claw settings such as `this.telegram` + +### Docs + +Add operator documentation covering: + +- Creating a Matrix bot account +- Obtaining an access token +- Understanding that the claw's Matrix access token is for interactive chat, not for a separate infra-alert sender +- DM pairing approval with `openclaw pairing list matrix` and `openclaw pairing approve matrix ` +- Inviting the bot to a room before room usage +- How `auto_join` and `auto_join_allowlist` affect room invites +- How allowed rooms are represented in stack config +- Whether mention-gating is enabled for room interactions +- Room allowlisting and mention-gating +- E2EE verification flow +- Beeper-specific note: requires E2EE enabled + +### Validation rules + +Add pre-deploy and startup validation for these cases: + +- `matrix.enabled: true` requires `matrix.homeserver` +- `matrix.enabled: true` requires `matrix.access_token` +- `npm run pre-deploy` should fail before rendering deploy artifacts if required Matrix config is missing +- `npm run pre-deploy` should fail if the resolved per-claw secret (for example `MAIN_CLAW_MATRIX_ACCESS_TOKEN`) is unset while Matrix is enabled for that claw +- `matrix.encryption: true` emits a clear warning or hard failure if crypto support is unavailable + +### Restart behavior + +Matrix install and config should follow the stack's current reload model: + +- First-time Matrix enablement requires a gateway restart because plugin loading is not hot-reloadable +- Subsequent `channels.matrix` config changes may be hot-reloadable if they are treated as ordinary channel config by OpenClaw +- Any `plugins.*` change still requires restart + +--- + +## Operational Notes + +### DM behavior + +Matrix DMs default to pairing approval, which is appropriate for this stack's security posture. + +Relevant upstream commands: + +```bash +openclaw pairing list matrix +openclaw pairing approve matrix +``` + +Initial operator flow: + +1. Deploy the claw with Matrix enabled and valid access token. +2. Send the first DM to the bot account from a Matrix client. +3. Review pending Matrix pairings with `openclaw pairing list matrix`. +4. Approve the intended user/device with `openclaw pairing approve matrix `. + +### Rooms and mention-gating + +Rooms are handled as group sessions. Upstream defaults to `groupPolicy: "allowlist"` and supports per-room config, sender allowlists, and mention gating. This should be surfaced directly in stack configuration and operator docs. + +Initial operator flow: + +1. Invite the bot account to the room. +2. Ensure the room is allowed by the claw's Matrix config. +3. If `auto_join` is restricted, ensure the room or inviter is in the appropriate allowlist. +4. If mention-gating is enabled for that room policy, address the bot explicitly when sending commands. + +### E2EE + +Matrix E2EE is supported, but it introduces operational requirements: + +- Crypto module must be available +- The bot/device must be verified from another Matrix client +- Crypto state must persist across restarts +- Rotating access tokens creates a new device store and may require re-verification +- Token rotation is an operator action followed by redeploy, not a deploy-script feature +- Token rotation should not imply OpenClaw user re-pairing, but it may require Matrix-side device re-verification in encrypted rooms + +Recommendation: support `encryption: false` first as the baseline deployment path, but design persistence correctly so `encryption: true` is a safe follow-up rather than a redesign. + +Release gate for production E2EE: + +- v1 may expose `matrix.encryption` +- production-ready E2EE requires backup/restore coverage for `~/.openclaw/matrix/` +- until that backup coverage is implemented and verified, encrypted Matrix should be treated as supported with operational caution rather than the baseline production mode + +### Multi-account + +Upstream supports multiple Matrix accounts per claw, but that is not part of this stack's intended model. + +For this stack, the normal model is one Matrix account per claw, and that is the model used in this proposal. Multi-account support is optional and remains out of scope for v1. + +--- + +## Idempotency + +Matrix integration should remain fully idempotent in the deployment sense: + +- **Before initial deployment:** enable Matrix in config, provide credentials, deploy once +- **After deployment:** add Matrix config, run the normal deploy flow, and let the stack configure the channel +- **Repeat deploys:** re-running deploy should not duplicate plugin state or require cleanup +- **Disable later:** turning Matrix off should stop channel startup without affecting unrelated OpenClaw state + +Clarification: + +- "Can be added before or after deployment" is one consequence of idempotency here +- The stronger meaning is that the same deploy action can be safely re-applied and converge on the intended state + +--- + +## Implementation Constraints + +### Plugin model + +`@openclaw/matrix` is bundled with OpenClaw and does not live in `~/.openclaw/extensions/`. The plugin key in `plugins.entries` is `"matrix"` (unscoped). No version pinning, no install path, no extensions directory management. + +If a future OpenClaw version moves Matrix to an external plugin, the entrypoint install pattern can be reintroduced at that time. + +### Runtime persistence + +Matrix runtime persistence is already satisfied by the current stack design. + +Reasoning: + +- Each claw bind-mounts `instances//.openclaw` into `/home/node/.openclaw` +- Setup scripts create and persist that directory on the host +- Matrix credentials, sync state, and crypto state live under that persisted tree + +Remaining follow-up: + +- Extend host backups to include Matrix account state under `.openclaw/matrix/` + +### Migration and drift + +The stack should define expected behavior for claws that already have manual Matrix state. + +Cases to handle: + +- Existing Matrix credentials and sync state already present under `~/.openclaw` +- Existing claw config updated from "no Matrix" to stack-managed Matrix + +Expected behavior: + +- The stack reuses existing persisted Matrix state where valid +- Deploy converges config without deleting valid runtime state +- Validation surfaces mismatches clearly rather than silently overwriting them + +Mismatch handling: + +- Persisted credentials exist and configured access token differs: treat the configured token as source of truth, reuse existing state where possible, and warn that Matrix device/session state may need re-verification +- Persisted sync/crypto state exists and homeserver changes: warn clearly and treat this as a migration-risk change that may require a fresh Matrix session or re-sync +- Persisted plugin/state exists but Matrix is now disabled in stack config: do not delete runtime state automatically; disable startup only + +Reset boundaries: + +- If the homeserver changes, existing Matrix sync/crypto state should be treated as invalid and the implementation should require a fresh Matrix session rather than silently reusing the old state +- If the configured access token changes and the persisted Matrix session cannot authenticate cleanly, the implementation should prompt for a fresh Matrix session reset rather than silently looping on bad state +- If Matrix is disabled, persisted state should be left on disk unless the operator explicitly removes it + +Reset mechanism: + +- v1 does not need a dedicated reset command in the proposal +- implementation should at minimum fail clearly and tell the operator that the persisted Matrix session/state must be reset before proceeding +- a dedicated reset command or helper script can be added later if reset handling becomes common enough to justify it + +### `stack.yml` shape + +Use a top-level per-claw `matrix:` block, parallel to `telegram:`, and render it into upstream `channels.matrix`. + +Reasoning: + +- Matches current repo config conventions +- Works cleanly with deep-merge defaults +- Keeps stack operator config concise +- Still maps cleanly onto upstream Matrix settings + +### Multi-account scope + +Initial implementation should expose only the default Matrix account. Multiple Matrix accounts per claw are possible upstream, but that is not part of this stack's intended model. + +Reasoning: + +- One Matrix bot identity per claw matches the stack's current operating model +- Multi-account support adds nested secrets, routing choices, and more operator complexity +- It is useful, but not necessary for initial Matrix support + +### E2EE scope for v1 + +Initial implementation should expose E2EE config, but unencrypted Matrix should remain the baseline supported path. + +Reasoning: + +- Upstream supports both encrypted and unencrypted Matrix +- E2EE adds device verification, recovery, and backup complexity +- The current stack persistence model is sufficient for runtime use, but backup coverage should be extended before encrypted recovery is considered fully operationally mature +- This keeps v1 practical without painting the design into a corner + +--- + +## Conclusion + +The implementation integrates Matrix into `openclaw-stack` through config and env-var wiring only. No plugin install step is needed — the plugin is bundled with OpenClaw and activated via `plugins.entries.matrix.enabled`. The stack's job is channel configuration, credential management, and persistence coverage. diff --git a/docs/TELEGRAM.md b/docs/TELEGRAM.md index 0c601ee..bfcf8c7 100644 --- a/docs/TELEGRAM.md +++ b/docs/TELEGRAM.md @@ -2,10 +2,10 @@ OpenClaw uses Telegram in two ways: -1. **OpenClaw channel** — chat with your AI agent via a Telegram bot (required) +1. **OpenClaw channel** — chat with your AI agent via a Telegram bot (optional per-claw) 2. **Host alerter** — VPS health alerts sent to Telegram (optional) -Both can use the same bot, or you can create separate bots. +Both are optional. If you don't use Telegram, set `telegram.enabled: false` in `stack.yml` (under `defaults` or per-claw) and skip this guide. Both can use the same bot, or you can create separate bots. ## 1. Create a Telegram Bot @@ -21,16 +21,35 @@ Send any message to [@userinfobot](https://t.me/userinfobot) — it replies with ## 3. Configure -Add both values to `stack.yml` under your claw config, and the bot token to `.env`: +Telegram is controlled by the `telegram.enabled` toggle in `stack.yml`. It defaults to `true` in `stack.yml.example`, so existing deployments continue working. To disable Telegram for a claw (or globally via `defaults`), set `enabled: false`: ```yaml -# stack.yml — under claws. -telegram_bot_token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz" -your_telegram_id: "123456789" +# stack.yml — defaults (applies to all claws unless overridden) +defaults: + telegram: + enabled: true # false to disable Telegram for all claws + allow_from: ${ADMIN_TELEGRAM_ID} + +# stack.yml — per-claw +claws: + personal-claw: + telegram: + bot_token: ${PERSONAL_CLAW_TELEGRAM_BOT_TOKEN} + # telegram: + # enabled: false # override default to disable for this claw only ``` -- `YOUR_TELEGRAM_ID` gates elevated mode — only this Telegram user can activate `/elevated` commands -- `OPENCLAW_TELEGRAM_BOT_TOKEN` connects the gateway to Telegram as a messaging channel +Add the bot token to `.env`: + +```env +PERSONAL_CLAW_TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz +ADMIN_TELEGRAM_ID=123456789 +``` + +- `ADMIN_TELEGRAM_ID` gates elevated mode — only this Telegram user can activate `/elevated` commands +- The per-claw bot token connects the gateway to Telegram as a messaging channel + +When `telegram.enabled: false`, the Telegram channel block is stripped from the deployed `openclaw.json` entirely — it won't appear in the Control UI. After deployment, the gateway connects to Telegram automatically. Message your bot to start chatting — the gateway may prompt you to approve the device via `openclaw devices approve` (same flow as browser pairing). diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index dc4a288..74ba8ff 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -58,12 +58,25 @@ }, "channels": { "telegram": { - "enabled": true, + "enabled": "${TELEGRAM_ENABLED}", "dmPolicy": "pairing", // Require device pairing for DMs "groupPolicy": "allowlist", // Only respond in explicitly allowed groups "streamMode": "partial" // Stream partial responses to Telegram // botToken read from TELEGRAM_BOT_TOKEN env var (set in docker-compose.yml) // WARNING: Each claw MUST use a unique bot token. Sharing tokens causes getUpdates 409 conflicts. + }, + // Matrix channel — enabled via MATRIX_ENABLED env var (controlled by matrix.enabled in stack.yml). + // Configure matrix: in stack.yml, set access token in .env, then run npm run pre-deploy. + // Full config reference: https://docs.openclaw.ai/channels/matrix + // Setup guide: docs/MATRIX.md + "matrix": { + "enabled": "${MATRIX_ENABLED}", + "homeserver": "${MATRIX_HOMESERVER}", + "accessToken": "${MATRIX_ACCESS_TOKEN}", // Access token for the Matrix bot account + "deviceName": "OPENCLAW_GATEWAY", // Device name shown in Matrix clients' device list + "encryption": false, // Set to true for E2EE (requires crypto module — see docs/MATRIX.md) + "dm": { "policy": "pairing" }, // Require device pairing for DMs + "groupPolicy": "open" // Respond in any room when @mentioned } }, "logging": { @@ -261,6 +274,7 @@ "allow": [ "telemetry", // Unified telemetry — replaces llm-logger plugin and debug-logger hook "memory-core" // File-backed memory search tools + // @openclaw/matrix is bundled with OpenClaw — no allow entry needed; declare in entries below ], "load": { "paths": ["/app/openclaw-stack/plugins"] // Load directly from openclaw-stack mount — avoids copying into OpenClaw's namespace @@ -310,6 +324,13 @@ // Loaded from /app/extensions/memory-core/ (ships with OpenClaw). "memory-core": { "enabled": true + }, + // Matrix Channel Plugin + // @openclaw/matrix is bundled with OpenClaw — no install step needed. + // Plugin key is "matrix" (unscoped). Always loaded; channel activation + // is controlled by channels.matrix.enabled, not this field. + "matrix": { + "enabled": true } } }, diff --git a/playbooks/00-fresh-deploy-setup.md b/playbooks/00-fresh-deploy-setup.md index 3809fee..8f2bddd 100644 --- a/playbooks/00-fresh-deploy-setup.md +++ b/playbooks/00-fresh-deploy-setup.md @@ -61,8 +61,8 @@ grep -A1 '^claws:' stack.yml | tail -n +2 | grep '^\s\+[a-z]' | sed 's/://;s/^\s 2. **`VPS_IP`** — Must not be `EMPTY` or contain `<`. 3. **`CF_TUNNEL_TOKEN`** or **`CF_API_TOKEN`** — At least one must show `SET`. If both missing: "Set `CLOUDFLARE_TUNNEL_TOKEN` (manual — create tunnel in CF Dashboard) or `CLOUDFLARE_API_TOKEN` (automated). See [`docs/CLOUDFLARE-TUNNEL.md`](../docs/CLOUDFLARE-TUNNEL.md)." 4. **`domain`** — The `stack.yml` domain line must not contain angle brackets (e.g., ``). `${VAR}` references are OK — verify the referenced `.env` variable (e.g., `ROOT_DOMAIN`) is set. -5. **`ADMIN_TELEGRAM_ID`** — Must be numeric. If empty: "Send a message to @userinfobot on Telegram to get your numeric user ID." -6. **Bot tokens** — Each claw name needs a matching `_TELEGRAM_BOT_TOKEN` line in `.env` (uppercased, hyphens→underscores). If missing: "Create a Telegram bot via @BotFather and paste the token. See `docs/TELEGRAM.md`." +5. **`ADMIN_TELEGRAM_ID`** — Required if `telegram.enabled` is `true` for any claw (the default). Must be numeric. If empty: "Send a message to @userinfobot on Telegram to get your numeric user ID." Skip if Telegram is disabled for all claws. +6. **Bot tokens** — Each claw with `telegram.enabled: true` (the default) needs a matching `_TELEGRAM_BOT_TOKEN` line in `.env` (uppercased, hyphens→underscores). If missing: "Create a Telegram bot via @BotFather and paste the token. See `docs/TELEGRAM.md`." Skip for claws with `telegram.enabled: false`. 7. **Claws** — The `claws` section lists claw names. Single claw = standard deploy. Multiple claws: inform user each gets its own container/domain. ### If any fields are invalid or missing diff --git a/playbooks/00-onboarding.md b/playbooks/00-onboarding.md index eaee212..8a2c490 100644 --- a/playbooks/00-onboarding.md +++ b/playbooks/00-onboarding.md @@ -118,7 +118,7 @@ Ask: > Do you want **one** OpenClaw instance or **multiple**? > > - **One** (default) — A single claw named `personal-claw`. Good for most users. -> - **Multiple** — Separate claws for different contexts (e.g., personal + work). Each gets its own container, Telegram bot, and subdomain. +> - **Multiple** — Separate claws for different contexts (e.g., personal + work). Each gets its own container, subdomain, and optionally its own Telegram bot. > > You can always add more later. @@ -147,7 +147,18 @@ For each additional claw beyond `personal-claw`: 4. Add the corresponding env var to `.env`: `_TELEGRAM_BOT_TOKEN=` -### 3.2 Telegram Bot(s) +### 3.2 Telegram Channel + +Ask: + +> Do you want to use **Telegram** to chat with your claws? +> +> - **Yes** (default) — Set up a Telegram bot for each claw +> - **No** — Disable Telegram (you can still use the browser Control UI) + +#### Yes (Telegram enabled) + +`telegram.enabled` defaults to `true` in `stack.yml.example` — no config change needed. For **each claw**, walk through bot creation: @@ -166,9 +177,7 @@ Write to `.env` as the claw's bot token variable (e.g., `PERSONAL_CLAW_TELEGRAM_ > Each claw **must** have a unique bot token — sharing tokens causes polling conflicts. -### 3.3 Admin Telegram ID - -Ask: +Then ask for Admin Telegram ID: > Now we need your Telegram user ID so OpenClaw knows who you are. > @@ -181,6 +190,18 @@ Validate: positive integer. Write to `.env` as `ADMIN_TELEGRAM_ID`. +#### No (Telegram disabled) + +Set `telegram.enabled: false` in `stack.yml` under `defaults`: + +```yaml +defaults: + telegram: + enabled: false +``` + +Skip bot token and Admin Telegram ID setup. The Telegram channel won't appear in the Control UI. + --- ## § 4. Host Alerts diff --git a/playbooks/06-backup.md b/playbooks/06-backup.md index 82319f3..cc9b302 100644 --- a/playbooks/06-backup.md +++ b/playbooks/06-backup.md @@ -129,6 +129,7 @@ cat /logs/session-prune.log | `instances//.openclaw/openclaw.json` | OpenClaw configuration (per-claw) | | `instances//.openclaw/credentials/` | API keys and tokens (per-claw) | | `instances//.openclaw/workspace/` | User workspaces and data (per-claw) | +| `instances//.openclaw/matrix/` | Matrix sync state and E2EE crypto keys (per-claw, when present) | | `docker-compose.yml` | Docker Compose deployment config | --- diff --git a/playbooks/07-verification.md b/playbooks/07-verification.md index 490563a..f59b0d8 100644 --- a/playbooks/07-verification.md +++ b/playbooks/07-verification.md @@ -419,10 +419,12 @@ openclaw cron list **Expected:** Both scripts exit 0 with no errors. `health.json` and `maintenance.json` contain valid JSON with current timestamps. Workspace copies exist. Both host cron entries exist. `openclaw cron list` shows "Daily VPS Health Check" with status `ok`. -### Telegram Delivery Test +### Telegram Delivery Test (Host Alerter) + +> **Skip** if `host_alerter` is not configured in `stack.yml` or its Telegram credentials are empty. ```bash -# Test Telegram delivery (if configured) +# Test Telegram delivery (if host alerter configured) TELEGRAM_TOKEN=$(sudo grep -oP 'ENV__HOSTALERT_TELEGRAM_BOT_TOKEN=\K.+' /stack.env) TELEGRAM_CHAT=$(sudo grep -oP 'ENV__HOSTALERT_TELEGRAM_CHAT_ID=\K.+' /stack.env) diff --git a/playbooks/08b-pair-devices.md b/playbooks/08b-pair-devices.md index 0496790..e2bbcd6 100644 --- a/playbooks/08b-pair-devices.md +++ b/playbooks/08b-pair-devices.md @@ -147,11 +147,13 @@ If the device shows as approved but the browser still can't connect, ask the use ## Telegram Pairing -If the claw's Telegram bot token is configured in `.env`, the claws are already connected to Telegram. Tell the user: +> **Skip this section** if `telegram.enabled` is `false` for all claws in `stack.yml`. + +If the claw has `telegram.enabled: true` and a bot token configured in `.env`, the claws are already connected to Telegram. Tell the user: > **Telegram:** Your bot is live. Open Telegram and send a message to your bot. If the claw prompts for device approval, run `openclaw --instance devices approve ` the same way you approved the browser. Repeat for each claw if needed. -If the bot token is empty, skip this step — Telegram was not configured. +If Telegram is disabled or the bot token is empty, skip this step. --- diff --git a/playbooks/maintenance.md b/playbooks/maintenance.md index a659270..432e0c7 100644 --- a/playbooks/maintenance.md +++ b/playbooks/maintenance.md @@ -17,7 +17,7 @@ All secrets should be rotated on a regular cadence. If a token is suspected comp | Provider API keys (Anthropic, OpenAI, etc.) | AI Gateway KV (`creds:*`) — managed via `/config` UI | Per provider policy | | `EGRESS_PROXY_AUTH_TOKEN` | Local `.env` + AI Gateway Worker secret + VPS egress proxy container | 90 days | | `EGRESS_PROXY_URL` | AI Gateway Worker secret | Only if hostname changes | -| `HOSTALERT_TELEGRAM_BOT_TOKEN` | Local `.env` (deployed via `npm run pre-deploy`) | As needed | +| `HOSTALERT_TELEGRAM_BOT_TOKEN` | Local `.env` (deployed via `npm run pre-deploy`) | As needed (only if host alerter uses Telegram) | | `SANDBOX_REGISTRY_TOKEN` | Local `.env` + VPS `sandbox-registry/htpasswd` | 90 days | | SSH keys (`~/.ssh/vps1_openclaw_ed25519`) | Local machine + VPS `authorized_keys` | Annual | @@ -327,8 +327,8 @@ Hot-reloadable config changes (agents, skills, models) take effect without resta ## Adding a New Claw -1. Add a new entry under `claws` in `stack.yml` with per-claw overrides (domain, resources, Telegram bot token, etc.) -2. Add the claw's Telegram bot token to `.env` (e.g., `NEW_CLAW_TELEGRAM_BOT_TOKEN=...`) +1. Add a new entry under `claws` in `stack.yml` with per-claw overrides (domain, resources, etc.) +2. If `telegram.enabled` is `true` (the default), add the claw's Telegram bot token to `.env` (e.g., `NEW_CLAW_TELEGRAM_BOT_TOKEN=...`) 3. Deploy (builds, syncs configs + workspaces, auto-starts the new service): ```bash scripts/deploy.sh diff --git a/stack.yml.example b/stack.yml.example index e319ed2..61c6980 100644 --- a/stack.yml.example +++ b/stack.yml.example @@ -79,7 +79,26 @@ defaults: domain_path: "" dashboard_path: /dashboard telegram: + enabled: true allow_from: ${ADMIN_TELEGRAM_ID} + # Matrix channel (optional — alternative or complement to Telegram) + # Enable per-claw by setting matrix.enabled: true and providing an access token. + # Full config reference: https://docs.openclaw.ai/channels/matrix + # Setup guide: docs/MATRIX.md + # + # Rendered into docker-compose.yml (controlled via stack.yml + .env): + # enabled, homeserver, access_token + # Requires direct openclaw.jsonc edit or Control UI (not rendered from stack.yml): + # encryption, dm_policy, group_policy, auto_join, groups (room allowlist) + matrix: + enabled: false + homeserver: "https://matrix.org" + # Not rendered — configure in per-claw openclaw.jsonc or via Control UI: + # dm_policy: pairing # pairing | open | closed + # group_policy: allowlist # allowlist | open | closed + # encryption: false # true requires E2EE crypto — see docs/MATRIX.md §E2EE + # auto_join: always # always | allowlist | never + # groups: {} # room allowlist — see docs/MATRIX.md §Rooms resources: cpus: 6 memory: 12G @@ -95,6 +114,10 @@ claws: dashboard_port: 6090 telegram: bot_token: ${PERSONAL_CLAW_TELEGRAM_BOT_TOKEN} + # matrix: + # enabled: true + # access_token: ${CLAW_NAME_MATRIX_ACCESS_TOKEN} # Access token for the Matrix bot account, set per claw + # # homeserver: "https://matrix.org" # Override stack-wide default if needed health_check_cron: true # Enable health check cron on the main claw - not needed on each claw # work-claw: