From 544637c0731476a1ac28375648133e8e29701409 Mon Sep 17 00:00:00 2001 From: alvis Date: Tue, 7 Jul 2026 08:58:30 +0000 Subject: [PATCH] Move adolf config to agap_git root (kb#65) Relocate the OpenClaw gateway config from openai/adolf/ to adolf/ at the repo root, since it's shared config rather than part of the openai/ compose project's own tree. Update the docker-compose.yml bind-mount path (./adolf/openclaw.json -> ../adolf/openclaw.json) and comments, plus README.md references, to match. Verified: adolf container recreated healthy with the new bind-mount source resolving to /home/alvis/agap_git/adolf/openclaw.json, and a fresh openclaw.json.last-good snapshot confirms the config was accepted. --- adolf/README.md | 111 ++++++++++++++++++++++++++++ adolf/openclaw.json | 148 ++++++++++++++++++++++++++++++++++++++ openai/docker-compose.yml | 30 ++++++-- 3 files changed, 283 insertions(+), 6 deletions(-) create mode 100644 adolf/README.md create mode 100644 adolf/openclaw.json diff --git a/adolf/README.md b/adolf/README.md new file mode 100644 index 0000000..4a98bd2 --- /dev/null +++ b/adolf/README.md @@ -0,0 +1,111 @@ +# Adolf — OpenClaw gateway deployment + +Adolf is the self-hosted OpenClaw fork that runs as the `adolf` container +(Matrix-first personal assistant). This directory holds its **version-controlled +gateway configuration**. + +- Source tree (the OpenClaw fork being built): `/home/alvis/adolf` +- Compose service `adolf` lives in: `openai/docker-compose.yml` +- This config directory lives at the repo root (`agap_git/adolf/`), not + nested inside `openai/`, since it is shared config rather than part of + the `openai` compose project's own tree. +- Model backend: `adolf-llm` container (Kimi-CLI wrapper) on `:8010` + +## Config source of truth + +The gateway config is **`openclaw.json` in this directory**. It is bind-mounted +**read-only** over the `adolf-state` volume: + +```yaml +volumes: + - adolf-state:/home/node/.openclaw # runtime state only + - ../adolf/openclaw.json:/home/node/.openclaw/openclaw.json:ro # tracked config +``` + +Previously this file was a hand-edited copy inside the `adolf-state` Docker +volume (edited via `docker cp` into the running container). It is now tracked +in git and seeded into the container by the mount, so **git is the single +source of truth**. + +- The file is **JSONC** (comments + unquoted keys allowed). +- **No secrets live here.** Every credential is a `${VAR}` reference resolved + from the container's environment, which is sourced from `openai/.env` + (gitignored, never committed): `OPENCLAW_GATEWAY_TOKEN`, `ADOLF_KEY`, + `MATRIX_*`, `MARKETPLACE_MCP_TOKEN`. +- The gateway reads this file and writes its own `openclaw.json.last-good` + and `openclaw.json.rejected.*` snapshots into the volume dir (writable). It + does **not** rewrite this file, so the read-only mount is safe. + +### Changing the config + +1. Edit `agap_git/adolf/openclaw.json`. +2. Restart the container: + ```bash + env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \ + docker compose -f /home/alvis/agap_git/openai/docker-compose.yml up -d adolf + ``` +3. Verify it came up healthy and the config was accepted: + ```bash + env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \ + docker ps --filter name=adolf --format '{{.Names}} {{.Status}}' + env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \ + docker logs --tail 40 adolf + ``` + A fresh `openclaw.json.rejected.*` file in `/home/node/.openclaw` means the + edit failed validation and the previous `.last-good` is still in use. + +Because the mount is read-only, editing config through the gateway UI/API is +intentionally disabled — all changes go through git. + +## Granting a Matrix user access to Adolf + +Adolf only responds to Matrix users on an **allow-list**. This is the +`channels.matrix.dm` block in `openclaw.json`: + +```jsonc +channels: { + matrix: { + enabled: true, + encryption: true, + dm: { + policy: "allowlist", + allowFrom: [ + "@admin:mtx.alogins.net", + "@elizaveta:mtx.alogins.net", + ], + }, + groupPolicy: "disabled", // no group-room handling yet + autoJoin: "always", + }, +} +``` + +- **`dm.policy: "allowlist"`** — only users whose full Matrix ID appears in + `allowFrom` can DM the bot. Everyone else is ignored. + (`policy: "pairing"` is the alternative: the owner must approve each unknown + sender interactively. `allowlist` is stricter and declarative.) +- **`dm.allowFrom`** — the list of authorized Matrix user IDs. +- **`groupPolicy: "disabled"`** — Adolf does not act in group rooms; DM only. + +### To grant a new user access + +1. Get the user's **full Matrix ID**, e.g. `@ivan:mtx.alogins.net`. +2. Add it to `allowFrom` in `agap_git/adolf/openclaw.json`: + ```jsonc + allowFrom: [ + "@admin:mtx.alogins.net", + "@elizaveta:mtx.alogins.net", + "@ivan:mtx.alogins.net", + ], + ``` +3. Restart adolf (see "Changing the config" above). +4. The user can now start a DM with Adolf's Matrix account + (`MATRIX_USER_ID` in `openai/.env`). `autoJoin: "always"` means Adolf + auto-accepts the DM invite; conversation is end-to-end encrypted + (`encryption: true`). + +To **revoke** access, remove the ID from `allowFrom` and restart. + +> Matrix accounts themselves are created on the Synapse homeserver +> (`mtx.alogins.net`) — see the AgapHost wiki **Matrix** page. The allow-list +> here only controls which existing Matrix users Adolf will talk to. diff --git a/adolf/openclaw.json b/adolf/openclaw.json new file mode 100644 index 0000000..7723154 --- /dev/null +++ b/adolf/openclaw.json @@ -0,0 +1,148 @@ +{ + // Adolf P6 — OpenClaw gateway config for the "adolf" container. + // Lives in the adolf-state VOLUME (mounted at /home/node/.openclaw), not + // in the openai/ git repo. Secrets referenced below (${VAR}) are resolved + // from this container's process env, itself sourced from openai/.env + // (gitignored) via docker-compose.yml — never inlined here. + + gateway: { + mode: "local", + auth: { + // Compose already binds "lan" (0.0.0.0) and publishes 18789/18790 to + // the host, so this is a non-loopback bind and auth is mandatory. + // Shared-secret token auth also gives the openclaw-tools bridge + // (which calls POST /tools/invoke with the same token) full + // trusted-operator scope, which is what lets gateway.tools.allow + // below actually unlock cron/nodes for it. + mode: "token", + token: "${OPENCLAW_GATEWAY_TOKEN}", + }, + tools: { + // cron and nodes are owner-only and hard-denied on the HTTP + // /tools/invoke surface by default. The openclaw-tools MCP bridge + // (P5) calls that surface for cron_create/cron_list/nodes_invoke, so + // without this allow-list those tools 404 even with a valid token. + allow: ["cron", "nodes"], + }, + }, + + // Model provider: adolf-llm (P2/P4), the Kimi-CLI OpenAI-compatible + // wrapper on :8010. Its HTTP server (openai/adolf-llm/server.js) performs + // NO api-key/Authorization validation at all -- ADOLF_KEY's value is + // functionally irrelevant to adolf-llm itself. It's still wired through + // env (not hardcoded) because OpenClaw's custom-provider schema requires + // a non-empty apiKey field and ${VAR} substitution fails closed on an + // empty/missing var. + models: { + mode: "merge", + providers: { + "adolf-llm": { + baseUrl: "http://adolf-llm:8010/v1", + apiKey: "${ADOLF_KEY}", + api: "openai-completions", + // Margin above the server.js SSE heartbeat cadence (empty-content + // keepalive delta every ~25s once idle) so the idle watchdog never + // fires on long thinking/tool/MCP phases even if a heartbeat tick + // is delayed (kb #71). + timeoutSeconds: 300, + models: [ + { id: "adolf", name: "Adolf", input: ["text", "image"] }, + ], + }, + }, + }, + + agents: { + defaults: { + model: "adolf-llm/adolf", + }, + }, + + ui: { + assistant: { name: "Adolf" }, + }, + + // Matrix channel (P6). Deliberately no accessToken/userId/password here: + // MATRIX_HOMESERVER / MATRIX_USER_ID / MATRIX_PASSWORD / MATRIX_DEVICE_NAME + // are config-key-backed env vars OpenClaw reads automatically when the + // matching config key is unset, so real creds never touch this file or + // git. Password auth (not the raw access token) mints Adolf its own fresh + // Matrix device instead of reusing the existing matrixbot (Python/nio) + // device -- see the P6 report for why that separation matters. + // + // Conservative defaults on purpose: dm "pairing" (owner must approve + // unknown senders) and groupPolicy "disabled" (no room handling yet). + // Revisit once the legacy matrixbot adapter is retired. + channels: { + matrix: { + enabled: true, + encryption: true, + dm: { policy: "allowlist", allowFrom: ["@admin:mtx.alogins.net", "@elizaveta:mtx.alogins.net"] }, + groupPolicy: "disabled", + autoJoin: "always", + // mtx.alogins.net is deliberately mapped to the host-gateway (private) + // IP via extra_hosts in docker-compose.yml to dodge a hairpin-NAT dead + // end on the public route -- not an actual SSRF exposure, so opt out + // of the private-network block for this trusted, self-owned homeserver. + network: { dangerouslyAllowPrivateNetwork: true }, + }, + }, + + // MCP registry (P6) -- same servers as openai/shared-mcp.json, + // expressed in OpenClaw's own mcp.servers schema. `type: "http"` is + // OpenClaw's documented CLI-native alias for transport: "streamable-http". + mcp: { + servers: { + cognee: { + type: "http", + url: "http://cognee-mcp:8000/mcp", + }, + "openclaw-tools": { + type: "http", + url: "http://openclaw-tools:8020/mcp", + }, + // kanboard-mcp-adolf (kb task #58) -- standalone kanboard-mcp image + // (source /home/alvis/kanboard/mcp), second instance on :3104, + // authenticated as the Kanboard "adolf" user via its own personal API + // access token (KANBOARD_AUTH_USER=adolf in that instance's + // .env.adolf) -- a genuinely distinct credential from the "claude" + // instance on :3103 (app-wide jsonrpc token). Not part of the openai + // compose network, so reached via host.docker.internal (already + // extra_hosts-mapped for this container) rather than a service name. + kanboard: { + type: "http", + url: "http://host.docker.internal:3104/mcp", + }, + // marketplace-mcp (kb task #61) -- the SAME shared marketplace-mcp + // instance Claude Code and OpenWebUI use (single service, port 3101, + // network_mode: host on the Agap host), not a second copy. It can + // place real orders on live marketplace accounts, so it's gated by a + // shared bearer token (MARKETPLACE_MCP_TOKEN in Vaultwarden / this + // container's env, injected via openai/.env -> docker-compose.yml). + // Reached via host.docker.internal, same reasoning as kanboard above. + marketplace: { + type: "http", + url: "http://host.docker.internal:3101/mcp", + headers: { + Authorization: "Bearer ${MARKETPLACE_MCP_TOKEN}", + }, + }, + }, + }, + + // Cognee memory plugin (P8) — installed external plugin under + // .openclaw/extensions/cognee-memory. Activation entry is required for the + // gateway to load it at startup (discovery alone is not enough). + plugins: { + entries: { + "cognee-memory": { + enabled: true, + // External (non-bundled) plugins must opt in to conversation + prompt-injection + // hook access explicitly. before_prompt_build => allowPromptInjection; + // agent_end => allowConversationAccess. + hooks: { allowConversationAccess: true, allowPromptInjection: true }, + config: {}, + }, + }, + }, +} diff --git a/openai/docker-compose.yml b/openai/docker-compose.yml index 16c51ed..4109adb 100644 --- a/openai/docker-compose.yml +++ b/openai/docker-compose.yml @@ -121,12 +121,17 @@ services: - "host.docker.internal:host-gateway" restart: unless-stopped - # Adolf — OpenClaw fork (Matrix-first personal assistant). Runtime config - # (Matrix channel, SOUL.md, model provider -> adolf-llm:8010, MCP registry, - # gateway.tools.allow for cron/nodes) is wired in P6 and lives in the - # adolf-state VOLUME (openclaw.json + SOUL.md), not in this repo. Matrix - # creds and ADOLF_KEY come from openai/.env (gitignored, never committed). - # Source: /home/alvis/adolf. + # Adolf — OpenClaw fork (Matrix-first personal assistant). The OpenClaw + # gateway config (Matrix channel + allow-list, model provider -> + # adolf-llm:8010, MCP registry, gateway.tools.allow for cron/nodes) is + # version-controlled at agap_git/adolf/openclaw.json (repo root, alongside + # this openai/ project, not nested inside it) and bind-mounted read-only + # over the adolf-state volume (see volumes below), so git is the single + # source of truth — not a hand-edited volume file. The volume still + # holds runtime state only (Matrix crypto/devices, credentials, sessions, + # workspace/SOUL.md, logs). Matrix creds and ADOLF_KEY come from + # openai/.env (gitignored, never committed). Source tree: /home/alvis/adolf. + # To change config: edit ../adolf/openclaw.json + restart adolf. adolf: build: context: ../../adolf @@ -149,9 +154,22 @@ services: - MATRIX_USER_ID=${MATRIX_USER_ID:-} - MATRIX_PASSWORD=${MATRIX_PASSWORD:-} - MATRIX_DEVICE_NAME=${MATRIX_DEVICE_NAME:-Adolf OpenClaw Gateway} + # marketplace-mcp bearer token (kb task #61) -- referenced by + # openclaw.json's mcp.servers.marketplace.headers.Authorization via + # ${MARKETPLACE_MCP_TOKEN} substitution; never inlined into that file. + - MARKETPLACE_MCP_TOKEN=${MARKETPLACE_MCP_TOKEN:-} - TZ=Europe/Riga volumes: + # Runtime state only (Matrix crypto/devices, credentials, sessions, + # workspace, logs). The gateway config file itself is overlaid below. - adolf-state:/home/node/.openclaw + # Version-controlled OpenClaw gateway config, mounted read-only on top + # of the state volume so it is the single source of truth. The gateway + # reads this JSONC file and snapshots its own .last-good/.rejected + # copies into the volume dir (writable) — it never rewrites this file, + # so read-only is safe. Edit the tracked file + restart to change config; + # runtime/UI edits are intentionally disabled by the ro mount. + - ../adolf/openclaw.json:/home/node/.openclaw/openclaw.json:ro extra_hosts: - "host.docker.internal:host-gateway" # mtx.alogins.net's public A record can't hairpin-NAT back through the