From ecf5eaaaf792959e8a2912951cd08a6391007fbe Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sat, 11 Apr 2026 00:04:26 +0200 Subject: [PATCH] Bootstrap deployment docs and Gitea setup scripts --- README.md | 14 +++ how-to/gitea-openclaw-bootstrap.md | 42 +++++++++ how-to/mattermost-openclaw.md | 98 ++++++++++++++++++++ how-to/traefik-openclaw.md | 46 ++++++++++ scripts/bootstrap-gitea-openclaw.sh | 136 ++++++++++++++++++++++++++++ 5 files changed, 336 insertions(+) create mode 100644 README.md create mode 100644 how-to/gitea-openclaw-bootstrap.md create mode 100644 how-to/mattermost-openclaw.md create mode 100644 how-to/traefik-openclaw.md create mode 100755 scripts/bootstrap-gitea-openclaw.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..0dbf728 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# openclaw-deploy + +Deployment notes and bootstrap scripts for this OpenClaw host. + +## Contents + +- `how-to/traefik-openclaw.md` - OpenClaw behind Traefik +- `how-to/mattermost-openclaw.md` - Mattermost integration notes and fixes +- `how-to/gitea-openclaw-bootstrap.md` - Gitea bootstrap notes for the OpenClaw service user +- `scripts/bootstrap-gitea-openclaw.sh` - unattended-ish Gitea user/key bootstrap helper + +## Goal + +Make the local setup reproducible with minimal manual steps, then gradually automate the remaining pieces. diff --git a/how-to/gitea-openclaw-bootstrap.md b/how-to/gitea-openclaw-bootstrap.md new file mode 100644 index 0000000..5d7f482 --- /dev/null +++ b/how-to/gitea-openclaw-bootstrap.md @@ -0,0 +1,42 @@ +# OpenClaw Gitea bootstrap +======================= + +Created user +- username: openclaw +- fullname: OpenClaw +- email: openclaw@git.an2.io + +SSH key +- private key: /home/user/.ssh/id_gitea_openclaw +- public key: /home/user/.ssh/id_gitea_openclaw.pub +- Gitea key title: openclaw@xeon + +SSH config +- host alias: gitea +- hostname: git.an2.io +- port: 222 +- user: git +- identity: /home/user/.ssh/id_gitea_openclaw + +Verified +- ssh -T gitea +- result: authenticated successfully as openclaw + +Access token +- created for user: openclaw +- token name: openclaw-bootstrap +- scopes: all +- token value was generated during bootstrap and should be stored securely if you want API repo management later + +Recommended usage +- clone/pull/push via SSH +- repo create/delete via Gitea API token + +Example remote forms +- ssh://git@git.an2.io:222/openclaw/repo.git +- gitea:openclaw/repo.git (via SSH config alias if your git tooling uses it explicitly) + +Suggested next steps +1. Store the generated Gitea token somewhere safe for future API use +2. Create a first test repo under openclaw +3. Verify clone, commit, and push with the SSH identity diff --git a/how-to/mattermost-openclaw.md b/how-to/mattermost-openclaw.md new file mode 100644 index 0000000..8f7b4b3 --- /dev/null +++ b/how-to/mattermost-openclaw.md @@ -0,0 +1,98 @@ +# OpenClaw + Mattermost notes +=========================== + +Current working state +- OpenClaw gateway is reachable on port 18789 +- Mattermost channel connects as @claw +- Native slash commands are working +- Slash commands were registered successfully for 1 team +- Inline buttons are enabled in channel config +- Button callback handling is working + +What was wrong +1. A manual Mattermost plugin install was present even though Mattermost is bundled in current OpenClaw. +2. The bot account was not a member of any Mattermost team. +3. Because the bot had zero teams, OpenClaw had nowhere to register native slash commands. + +Observed failure mode +- Logs showed: + - mattermost: registered slash command callback at /api/channels/mattermost/command + - mattermost: connected as @claw + - mattermost: native slash commands enabled but no commands could be registered; keeping slash callbacks inactive + +Actual root cause +- Mattermost API check showed the bot user belonged to zero teams. +- Native slash command registration is team-scoped, so no team membership means no commands can be created. + +What was cleaned up +- Moved manual override plugin out of the active extensions path: + - /home/user/.openclaw/extensions/mattermost + - -> /home/user/.openclaw/extensions/mattermost.disabled-20260410-230312 +- Removed stale plugin install metadata from /home/user/.openclaw/openclaw.json: + - plugins.installs.mattermost +- Removed stale disabled bundled plugin config warning: + - plugins.entries.huggingface + +Mattermost config in use +- baseUrl: https://mm.an2.io +- commands.native: true +- commands.nativeSkills: true +- callbackPath: /api/channels/mattermost/command +- callbackUrl: http://159.69.76.190:18789/api/channels/mattermost/command +- interactions.allowedSourceIps: ["172.20.0.3", "172.20.0.0/16", "127.0.0.1", "::1"] + +What fixed it +- Added the @claw bot to a Mattermost team +- After reconnect/restart, OpenClaw successfully registered slash commands + +Successful log indicators +- mattermost: connected as @claw +- mattermost: registered command /oc_status +- mattermost: registered command /oc_model +- mattermost: registered command /oc_models +- mattermost: slash commands activated for account default (20 commands) +- mattermost: slash commands registered (20 commands across 1 teams, callback=http://159.69.76.190:18789/api/channels/mattermost/command) + +Important reminder +- The bot must belong to at least one team for native slash command registration to work. +- The bot should also be added to the channels where you want it to interact. +- If native slash commands fail again, first check team membership before debugging callback URLs. + +Useful checks +- curl -sS -H 'Authorization: Bearer ' https://mm.an2.io/api/v4/users/me +- curl -sS -H 'Authorization: Bearer ' https://mm.an2.io/api/v4/users//teams +- grep -Ei 'mattermost|slash|callback' /tmp/openclaw-gateway-run.log + +If slash commands break again +1. Confirm the bot is still in a team +2. Confirm callbackUrl is reachable from the Mattermost server +3. Check for old leftover manually installed overrides in ~/.openclaw/extensions/ +4. Restart gateway and re-read Mattermost startup logs + +Button callback note +- Native slash commands now work. +- Button callback test initially failed with: mattermost interaction: rejected callback source remote=172.20.0.3 +- Updated channels.mattermost.interactions.allowedSourceIps to include: + - 172.20.0.3 + - 172.20.0.0/16 + - 127.0.0.1 + - ::1 +- After the allowlist update, button callbacks worked end-to-end. + +Elevated exec from Mattermost +- Added tools.elevated.enabled = true +- Added tools.elevated.allowFrom.mattermost = ["63rzn4hbijnrmjbomxxugntg9h"] +- This allows elevated exec only from the specified Mattermost source/account instead of opening it broadly. +- Gateway restart is required for this change to take full effect. + +Provider routing note for elevated exec +- Elevated exec checks for this session path were evaluated under provider=webchat, not provider=mattermost. +- Because of that, tools.elevated.allowFrom needed both: + - mattermost: ["63rzn4hbijnrmjbomxxugntg9h"] + - webchat: ["63rzn4hbijnrmjbomxxugntg9h"] +- Gateway restart is required after changing these allowFrom rules. + +Elevated exec verification +- After adding both provider allowlists and restarting the gateway, elevated exec from the Mattermost session path worked. +- Confirmed in-session test: sudo -n true -> SUDO_OK +- This verified that elevated exec was allowed for the effective provider path used by that session. diff --git a/how-to/traefik-openclaw.md b/how-to/traefik-openclaw.md new file mode 100644 index 0000000..16e7d64 --- /dev/null +++ b/how-to/traefik-openclaw.md @@ -0,0 +1,46 @@ +# OpenClaw behind Traefik +======================= + +What I changed +- Changed OpenClaw gateway bind in /home/user/.openclaw/openclaw.json from loopback to lan +- Added Traefik dynamic config at /opt/traefik/dynamic/openclaw.yml +- Target upstream is http://host.docker.internal:18789 +- Added gateway.controlUi.allowedOrigins entry for https://oc.an2.io + +Why +- Traefik runs in Docker. +- The Traefik container already has host.docker.internal mapped to the Docker host gateway. +- OpenClaw was only listening on 127.0.0.1:18789, which the container could not reach. +- Binding OpenClaw to lan makes it listen on the host network so Traefik can reach it through the host gateway. +- The Control UI loaded through Traefik uses browser origin https://oc.an2.io, so that exact origin must be listed in gateway.controlUi.allowedOrigins. + +Traefik config summary +- Router name: openclaw +- Entry point: websecure +- TLS cert resolver: letsencrypt +- Host rule: oc.an2.io +- Service upstream: http://host.docker.internal:18789 +- Dynamic file: /opt/traefik/dynamic/openclaw.yml + +OpenClaw config summary +- File: /home/user/.openclaw/openclaw.json +- gateway.bind: lan +- gateway.controlUi.allowedOrigins includes: + - http://localhost:18789 + - http://127.0.0.1:18789 + - https://oc.an2.io + +Notes +- https://localhost:18789 is not the correct public URL because OpenClaw on port 18789 speaks plain HTTP. +- TLS is terminated by Traefik on port 443 and then proxied to http://host.docker.internal:18789. +- If the browser shows "origin not allowed", check gateway.controlUi.allowedOrigins first. + +Useful checks +- ss -ltnp | grep 18789 +- curl http://localhost:18789/ +- curl -k https://oc.an2.io/ +- sudo docker logs --tail 100 traefik + +If needed +- Restart OpenClaw gateway after config changes +- Traefik dynamic config should hot-reload automatically, no Traefik restart is usually needed diff --git a/scripts/bootstrap-gitea-openclaw.sh b/scripts/bootstrap-gitea-openclaw.sh new file mode 100755 index 0000000..cf61990 --- /dev/null +++ b/scripts/bootstrap-gitea-openclaw.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Bootstrap a dedicated Gitea user for OpenClaw on a local Dockerized Gitea host. +# +# What it does: +# - ensures an SSH keypair exists locally +# - creates the Gitea user via `gitea admin user create` inside the container +# - generates an API token for that user +# - uploads the SSH public key via the Gitea API +# - writes/updates an SSH config entry for easy git access +# +# Requirements: +# - local Docker access +# - running container named `gitea` +# - curl, python3, ssh-keygen +# - host reachability to the Gitea HTTP URL and SSH port +# +# Example: +# ./scripts/bootstrap-gitea-openclaw.sh \ +# --username openclaw \ +# --email openclaw@git.an2.io \ +# --fullname OpenClaw \ +# --http-url https://git.an2.io \ +# --ssh-host git.an2.io \ +# --ssh-port 222 + +USERNAME="openclaw" +EMAIL="openclaw@git.an2.io" +FULLNAME="OpenClaw" +HTTP_URL="https://git.an2.io" +SSH_HOST="git.an2.io" +SSH_PORT="222" +SSH_KEY_PATH="${HOME}/.ssh/id_gitea_openclaw" +SSH_HOST_ALIAS="gitea" +CONTAINER_NAME="gitea" +ACCESS_TOKEN_NAME="openclaw-bootstrap" +ACCESS_TOKEN_SCOPES="all" + +usage() { + cat < + --email + --fullname + --http-url + --ssh-host + --ssh-port + --ssh-key-path + --ssh-host-alias + --container-name + --token-name + --token-scopes +EOF2 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --username) USERNAME="$2"; shift 2 ;; + --email) EMAIL="$2"; shift 2 ;; + --fullname) FULLNAME="$2"; shift 2 ;; + --http-url) HTTP_URL="$2"; shift 2 ;; + --ssh-host) SSH_HOST="$2"; shift 2 ;; + --ssh-port) SSH_PORT="$2"; shift 2 ;; + --ssh-key-path) SSH_KEY_PATH="$2"; shift 2 ;; + --ssh-host-alias) SSH_HOST_ALIAS="$2"; shift 2 ;; + --container-name) CONTAINER_NAME="$2"; shift 2 ;; + --token-name) ACCESS_TOKEN_NAME="$2"; shift 2 ;; + --token-scopes) ACCESS_TOKEN_SCOPES="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; + esac +done + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { echo "Missing command: $1" >&2; exit 1; } +} + +need_cmd docker +need_cmd curl +need_cmd python3 +need_cmd ssh-keygen + +mkdir -p "$(dirname "$SSH_KEY_PATH")" +if [[ ! -f "$SSH_KEY_PATH" ]]; then + ssh-keygen -t ed25519 -f "$SSH_KEY_PATH" -C "${USERNAME}@${SSH_HOST}" -N '' +fi + +if sudo docker exec --user git "$CONTAINER_NAME" gitea admin user list | awk 'NR>1 {print $2}' | grep -qx "$USERNAME"; then + echo "User $USERNAME already exists" +else + sudo docker exec --user git "$CONTAINER_NAME" gitea admin user create \ + --username "$USERNAME" \ + --fullname "$FULLNAME" \ + --email "$EMAIL" \ + --random-password \ + --must-change-password=false +fi + +TOKEN=$(sudo docker exec --user git "$CONTAINER_NAME" gitea admin user generate-access-token \ + --username "$USERNAME" \ + --token-name "$ACCESS_TOKEN_NAME" \ + --raw \ + --scopes "$ACCESS_TOKEN_SCOPES") + +echo "Generated token for $USERNAME (store this securely):" +echo "$TOKEN" + +PUB_JSON=$(python3 - </tmp/gitea-key-result.json || true + +mkdir -p "${HOME}/.ssh" +touch "${HOME}/.ssh/config" +chmod 600 "${HOME}/.ssh/config" +if ! grep -q "^Host ${SSH_HOST_ALIAS}$" "${HOME}/.ssh/config"; then + cat >> "${HOME}/.ssh/config" <