Automate local OpenClaw Traefik setup and add docs
This commit is contained in:
20
README.md
Normal file
20
README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# openclaw-deploy-scripts
|
||||||
|
|
||||||
|
Deployment and backup scripts for services on this host.
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- Docker and local deployment helpers under `/opt/deploy`
|
||||||
|
- Shared Traefik helpers in `common.sh`
|
||||||
|
- Local OpenClaw deployment now supports automatic Traefik wiring
|
||||||
|
- Companion how-to docs live under `how-to/`
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- `how-to/traefik-openclaw.md`
|
||||||
|
- `how-to/mattermost-openclaw.md`
|
||||||
|
- `how-to/gitea-openclaw-bootstrap.md`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
These scripts are evolving toward unattended rebuilds.
|
||||||
296
deploy-local-openclaw.sh
Executable file
296
deploy-local-openclaw.sh
Executable file
@@ -0,0 +1,296 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# OpenClaw Local (Bare-Metal) Deployment Script
|
||||||
|
# ==============================================================================
|
||||||
|
# Installs OpenClaw directly on the host via npm.
|
||||||
|
# Source: https://docs.openclaw.ai/install
|
||||||
|
#
|
||||||
|
# Usage: deploy-local-openclaw.sh
|
||||||
|
# deploy-local-openclaw.sh --domain oc.an2.io
|
||||||
|
# deploy-local-openclaw.sh --remove [--purge] [--yes]
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/common.sh"
|
||||||
|
|
||||||
|
# --- Parse arguments ---
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
# --- Service config ---
|
||||||
|
SERVICE_NAME="openclaw"
|
||||||
|
BASE_DIR="/opt/${SERVICE_NAME}"
|
||||||
|
UNIT_NAME="${SERVICE_NAME}"
|
||||||
|
|
||||||
|
# --- Handle --remove ---
|
||||||
|
if [[ "$ARG_REMOVE" == "1" ]]; then
|
||||||
|
require_root
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}══════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${YELLOW} Removing ${SERVICE_NAME} (local)${NC}"
|
||||||
|
echo -e "${YELLOW}══════════════════════════════════════════════${NC}"
|
||||||
|
if [[ "$ARG_PURGE" == "1" ]]; then
|
||||||
|
echo -e " Mode: ${RED}Purge (delete all data)${NC}"
|
||||||
|
else
|
||||||
|
echo -e " Mode: Safe (keep data)"
|
||||||
|
fi
|
||||||
|
echo -e " Data: ${BASE_DIR}"
|
||||||
|
echo -e " Systemd: ${UNIT_NAME}.service"
|
||||||
|
echo -e "${YELLOW}══════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$ARG_PURGE" == "1" ]] && [[ -d "$BASE_DIR" ]]; then
|
||||||
|
backup_script="${SCRIPT_DIR}/backup-${SERVICE_NAME}.sh"
|
||||||
|
[[ -f "$backup_script" ]] && warn "Back up first? ${backup_script}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
unit_file="/etc/systemd/system/${UNIT_NAME}.service"
|
||||||
|
if [[ -f "$unit_file" ]]; then
|
||||||
|
systemctl stop "${UNIT_NAME}.service" > /dev/null 2>&1 || true
|
||||||
|
systemctl disable "${UNIT_NAME}.service" > /dev/null 2>&1 || true
|
||||||
|
rm -f "$unit_file"
|
||||||
|
systemctl daemon-reload
|
||||||
|
ok "Removed systemd unit: ${UNIT_NAME}.service"
|
||||||
|
fi
|
||||||
|
|
||||||
|
remove_traefik_dynamic_config "$SERVICE_NAME"
|
||||||
|
|
||||||
|
if [[ "$ARG_PURGE" == "1" ]]; then
|
||||||
|
echo ""
|
||||||
|
warn "This will permanently delete: $BASE_DIR"
|
||||||
|
if [[ "$ARG_YES" != "1" ]]; then
|
||||||
|
read -rp "Are you sure? [y/N] " confirm
|
||||||
|
if [[ "${confirm,,}" != "y" ]]; then
|
||||||
|
info "Purge cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -rf "$BASE_DIR"
|
||||||
|
rm -rf "$HOME/.openclaw" 2>/dev/null
|
||||||
|
ok "Purged: $BASE_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${GREEN} ${SERVICE_NAME} removed${NC}"
|
||||||
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
||||||
|
if [[ "$ARG_PURGE" != "1" ]]; then
|
||||||
|
echo -e " Data preserved at ${BASE_DIR}"
|
||||||
|
echo -e " To purge: $0 --remove --purge"
|
||||||
|
fi
|
||||||
|
echo -e " To redeploy: $0"
|
||||||
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Domain resolution ---
|
||||||
|
if [[ -n "$ARG_DOMAIN" ]]; then
|
||||||
|
DOMAIN="$ARG_DOMAIN"
|
||||||
|
elif [[ -f "${BASE_DIR}/.env" ]] && grep -q '^DOMAIN=' "${BASE_DIR}/.env"; then
|
||||||
|
DOMAIN="$(grep '^DOMAIN=' "${BASE_DIR}/.env" | cut -d= -f2)"
|
||||||
|
else
|
||||||
|
DOMAIN="oc.an2.io"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Detect current state for banner ---
|
||||||
|
install_mode="Fresh install"
|
||||||
|
if [[ -f "${BASE_DIR}/.env" ]]; then
|
||||||
|
install_mode="Re-run (preserving data)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
service_status="Not running"
|
||||||
|
if systemctl is-active --quiet "${UNIT_NAME}.service" 2>/dev/null; then
|
||||||
|
service_status="Running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Print deployment plan ---
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${CYAN} Deploying OpenClaw (Local)${NC}"
|
||||||
|
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo -e " Domain: ${DOMAIN}"
|
||||||
|
echo -e " Data: ${BASE_DIR}/data/"
|
||||||
|
echo -e " Service: ${service_status}"
|
||||||
|
echo -e " Mode: ${install_mode}"
|
||||||
|
echo -e "${CYAN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Check if already deployed and running ---
|
||||||
|
if [[ -f "${BASE_DIR}/.env" ]] && \
|
||||||
|
systemctl is-active --quiet "${UNIT_NAME}.service" 2>/dev/null; then
|
||||||
|
echo -e "${GREEN} OpenClaw is already running — nothing to do.${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " Health: curl -fsS http://127.0.0.1:18789/healthz"
|
||||||
|
echo -e " Logs: journalctl -u ${UNIT_NAME}.service -f"
|
||||||
|
echo -e " Backup: ${SCRIPT_DIR}/backup-openclaw.sh"
|
||||||
|
echo -e " Remove: $0 --remove [--purge]"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Infrastructure ---
|
||||||
|
require_root
|
||||||
|
detect_os
|
||||||
|
|
||||||
|
# --- Install Node.js 22+ ---
|
||||||
|
install_nodejs_22() {
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
local node_major
|
||||||
|
node_major="$(node --version | sed 's/^v//' | cut -d. -f1)"
|
||||||
|
if (( node_major >= 22 )); then
|
||||||
|
ok "Node.js $(node --version) already installed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "Node.js $(node --version) is too old (need 22+). Upgrading..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Installing Node.js 22..."
|
||||||
|
case "$DISTRO_FAMILY" in
|
||||||
|
debian)
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - > /dev/null 2>&1
|
||||||
|
apt-get install -y -qq nodejs > /dev/null 2>&1
|
||||||
|
;;
|
||||||
|
fedora)
|
||||||
|
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - > /dev/null 2>&1
|
||||||
|
dnf install -y -q nodejs > /dev/null 2>&1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
ok "Node.js $(node --version) installed."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install base prerequisites
|
||||||
|
case "$DISTRO_FAMILY" in
|
||||||
|
debian)
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq curl openssl jq git ca-certificates > /dev/null 2>&1
|
||||||
|
;;
|
||||||
|
fedora)
|
||||||
|
dnf install -y -q curl openssl jq git ca-certificates > /dev/null 2>&1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
install_nodejs_22
|
||||||
|
|
||||||
|
# --- Backup restore check ---
|
||||||
|
if check_and_restore_backup "$SERVICE_NAME"; then
|
||||||
|
saved_domain="$DOMAIN"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "${BASE_DIR}/.env"
|
||||||
|
DOMAIN="$saved_domain"
|
||||||
|
ok "Backup restored."
|
||||||
|
else
|
||||||
|
# --- Fresh install ---
|
||||||
|
if [[ -f "${BASE_DIR}/.env" ]]; then
|
||||||
|
saved_domain="$DOMAIN"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "${BASE_DIR}/.env"
|
||||||
|
DOMAIN="$saved_domain"
|
||||||
|
info "Existing installation found. Updating..."
|
||||||
|
npm update -g openclaw 2>/dev/null || true
|
||||||
|
else
|
||||||
|
info "Installing OpenClaw via npm..."
|
||||||
|
npm install -g openclaw@latest
|
||||||
|
|
||||||
|
mkdir -p "${BASE_DIR}/data"
|
||||||
|
|
||||||
|
# Redirect ~/.openclaw to our data dir
|
||||||
|
rm -rf "$HOME/.openclaw" 2>/dev/null
|
||||||
|
ln -sfn "${BASE_DIR}/data" "$HOME/.openclaw"
|
||||||
|
|
||||||
|
# --- Generate .env ---
|
||||||
|
cat > "${BASE_DIR}/.env" <<DOTENV
|
||||||
|
# Generated by deploy-local-openclaw.sh on $(date -Iseconds)
|
||||||
|
DOMAIN=${DOMAIN}
|
||||||
|
OPENCLAW_CONFIG_DIR=${BASE_DIR}/data
|
||||||
|
DOTENV
|
||||||
|
chmod 600 "${BASE_DIR}/.env"
|
||||||
|
ok ".env written."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Install systemd unit ---
|
||||||
|
cat > "/etc/systemd/system/${UNIT_NAME}.service" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=OpenClaw Gateway
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Environment=OPENCLAW_CONFIG_DIR=${BASE_DIR}/data
|
||||||
|
ExecStart=$(command -v openclaw || echo /usr/bin/openclaw) gateway start
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
TimeoutStartSec=60
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable "${UNIT_NAME}.service" > /dev/null 2>&1
|
||||||
|
systemctl restart "${UNIT_NAME}.service"
|
||||||
|
ok "Systemd unit installed and started: ${UNIT_NAME}.service"
|
||||||
|
|
||||||
|
# --- Wait for health ---
|
||||||
|
info "Waiting for OpenClaw to become healthy (up to 60s)..."
|
||||||
|
elapsed=0
|
||||||
|
while (( elapsed < 60 )); do
|
||||||
|
if curl -fsS http://127.0.0.1:18789/ > /dev/null 2>&1; then
|
||||||
|
ok "OpenClaw is responding."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
(( elapsed += 5 ))
|
||||||
|
done
|
||||||
|
if (( elapsed >= 60 )); then
|
||||||
|
warn "OpenClaw did not respond within 60s. Check: journalctl -u ${UNIT_NAME}.service"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Configure gateway for Traefik/browser access ---
|
||||||
|
OPENCLAW_CONFIG_JSON="${BASE_DIR}/data/openclaw.json"
|
||||||
|
if [[ -f "$OPENCLAW_CONFIG_JSON" ]]; then
|
||||||
|
python3 - <<PY
|
||||||
|
import json
|
||||||
|
path = "${OPENCLAW_CONFIG_JSON}"
|
||||||
|
with open(path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
gw = data.setdefault('gateway', {})
|
||||||
|
gw['bind'] = 'lan'
|
||||||
|
cu = gw.setdefault('controlUi', {})
|
||||||
|
origins = cu.setdefault('allowedOrigins', [])
|
||||||
|
want = 'https://${DOMAIN}'
|
||||||
|
if want not in origins:
|
||||||
|
origins.append(want)
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
f.write('\n')
|
||||||
|
print('updated', path)
|
||||||
|
PY
|
||||||
|
systemctl restart "${UNIT_NAME}.service"
|
||||||
|
ok "Updated OpenClaw gateway bind/origins for Traefik access."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Ensure Traefik and dynamic config ---
|
||||||
|
ensure_docker_network
|
||||||
|
ensure_traefik
|
||||||
|
write_traefik_dynamic_config "$SERVICE_NAME" "$DOMAIN" "http://host.docker.internal:18789"
|
||||||
|
ok "Traefik route configured for https://${DOMAIN}"
|
||||||
|
|
||||||
|
# --- Summary ---
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${GREEN} OpenClaw deployed successfully (local)${NC}"
|
||||||
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo -e " Local URL: http://localhost:18789"
|
||||||
|
echo -e " Public URL: https://${DOMAIN}"
|
||||||
|
echo -e " Data: ${BASE_DIR}/data/"
|
||||||
|
echo -e " Systemd: systemctl status ${UNIT_NAME}"
|
||||||
|
echo -e " Logs: journalctl -u ${UNIT_NAME}.service -f"
|
||||||
|
echo -e " Traefik: ${TRAEFIK_DYNAMIC_DIR}/${SERVICE_NAME}.yml"
|
||||||
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
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
|
||||||
Reference in New Issue
Block a user