Bootstrap deployment docs and Gitea setup scripts
This commit is contained in:
14
README.md
Normal file
14
README.md
Normal file
@@ -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.
|
||||
42
how-to/gitea-openclaw-bootstrap.md
Normal file
42
how-to/gitea-openclaw-bootstrap.md
Normal file
@@ -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
|
||||
98
how-to/mattermost-openclaw.md
Normal file
98
how-to/mattermost-openclaw.md
Normal file
@@ -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 <BOT_TOKEN>' https://mm.an2.io/api/v4/users/me
|
||||
- curl -sS -H 'Authorization: Bearer <BOT_TOKEN>' https://mm.an2.io/api/v4/users/<BOT_USER_ID>/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.
|
||||
46
how-to/traefik-openclaw.md
Normal file
46
how-to/traefik-openclaw.md
Normal file
@@ -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
|
||||
136
scripts/bootstrap-gitea-openclaw.sh
Executable file
136
scripts/bootstrap-gitea-openclaw.sh
Executable file
@@ -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 <<EOF2
|
||||
Usage: $0 [options]
|
||||
|
||||
Options:
|
||||
--username <name>
|
||||
--email <email>
|
||||
--fullname <name>
|
||||
--http-url <url>
|
||||
--ssh-host <host>
|
||||
--ssh-port <port>
|
||||
--ssh-key-path <path>
|
||||
--ssh-host-alias <alias>
|
||||
--container-name <name>
|
||||
--token-name <name>
|
||||
--token-scopes <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 - <<PY
|
||||
import json
|
||||
print(json.dumps(open('${SSH_KEY_PATH}.pub').read().strip()))
|
||||
PY
|
||||
)
|
||||
|
||||
curl -fsS -X POST "${HTTP_URL%/}/api/v1/user/keys" \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "{\"title\":\"${USERNAME}@$(hostname)\",\"key\":${PUB_JSON}}" >/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" <<EOF2
|
||||
Host ${SSH_HOST_ALIAS}
|
||||
HostName ${SSH_HOST}
|
||||
Port ${SSH_PORT}
|
||||
User git
|
||||
IdentityFile ${SSH_KEY_PATH}
|
||||
IdentitiesOnly yes
|
||||
EOF2
|
||||
fi
|
||||
|
||||
ssh -o StrictHostKeyChecking=accept-new -T "${SSH_HOST_ALIAS}" || true
|
||||
Reference in New Issue
Block a user