From 88d4505d2fb539a58c33785121e53513a2acfee5 Mon Sep 17 00:00:00 2001 From: Tim Bendt Date: Mon, 23 Feb 2026 11:06:40 -0500 Subject: [PATCH] gateway configs --- AGENTS.md | 18 ++++++++++ docker-compose.yml | 12 +++---- docker/Dockerfile | 11 +++--- docker/bin/start-gateway.sh | 69 +++++++++++++++++++++++++++++++++++++ docker/config/openclaw.json | 61 ++++++++++++++++++++++++++++---- stack.yml | 15 +++----- 6 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 docker/bin/start-gateway.sh diff --git a/AGENTS.md b/AGENTS.md index fef0594..00ae900 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -418,6 +418,24 @@ docker exec CONTAINER_NAME sh -c 'echo {} > /home/node/.openclaw/devices/pending - Localhost (`127.0.0.1`) auto-approves. - Config edits trigger Gateway reload/restart automatically; container restart is usually unnecessary. +### Keep Web UI Working Across Rebuilds + +To avoid repeated reconnect/pairing friction after redeploys: + +1. Keep a stable Tailscale hostname so the browser origin does not change: + +```bash +TAILSCALE_HOSTNAME=openclaw-gateway +``` + +2. Keep the same `OPENCLAW_GATEWAY_TOKEN` between deployments. +3. Persist and reuse the same `openclaw-config` volume (contains `devices/paired.json`). +4. If UI shows `token_missing`, open a tokenized URL and re-save settings: + +```bash +docker exec CONTAINER_NAME node dist/index.js dashboard --no-open +``` + ### Permission Denied Errors **Problem**: `EACCES: permission denied, open '/home/node/.openclaw/workspace/...'` diff --git a/docker-compose.yml b/docker-compose.yml index 3d98d23..40bf29d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,8 +32,13 @@ services: TERM: xterm-256color OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN} MOONSHOT_API_KEY: ${MOONSHOT_API_KEY} + OPENAI_API_KEY: ${OPENAI_API_KEY} + OPENCLAW_GATEWAY_BIND: ${OPENCLAW_GATEWAY_BIND:-lan} + OPENCLAW_TAILSCALE_MODE: ${OPENCLAW_TAILSCALE_MODE:-off} + OPENCLAW_ENABLE_TAILSCALE: ${OPENCLAW_ENABLE_TAILSCALE:-0} GOG_ACCOUNT: ${GOG_ACCOUNT:-} TAILSCALE_AUTH_KEY: ${TAILSCALE_AUTH_KEY:-} + TAILSCALE_HOSTNAME: ${TAILSCALE_HOSTNAME:-openclaw-gateway} volumes: - openclaw-config:/home/node/.openclaw - openclaw-workspace:/home/node/.openclaw/workspace @@ -44,12 +49,7 @@ services: init: true networks: - dokploy-network - command: - [ - "/bin/sh", - "-c", - "/home/node/.local/bin/tailscale-start.sh && node dist/index.js gateway --port 18789", - ] + command: ["/usr/local/bin/start-gateway.sh"] # healthcheck: # test: ["CMD", "healthcheck.sh"] # interval: 30s diff --git a/docker/Dockerfile b/docker/Dockerfile index 6d3673e..e1285a6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,9 +43,9 @@ RUN echo '#!/bin/sh' > /home/node/.local/bin/tailscale-start.sh && \ echo 'mkdir -p /var/run/tailscale /home/node/.local/share/tailscale /home/node/.local/share/tailscale/files' >> /home/node/.local/bin/tailscale-start.sh && \ echo 'tailscaled --socket=/tmp/tailscale.sock --tun=userspace-networking &' >> /home/node/.local/bin/tailscale-start.sh && \ echo 'sleep 3' >> /home/node/.local/bin/tailscale-start.sh && \ - echo 'tailscale --socket=/tmp/tailscale.sock up --authkey=$TAILSCALE_AUTH_KEY' >> /home/node/.local/bin/tailscale-start.sh && \ + echo 'if [ -n "$TAILSCALE_AUTH_KEY" ]; then tailscale --socket=/tmp/tailscale.sock up --authkey="$TAILSCALE_AUTH_KEY" --hostname="${TAILSCALE_HOSTNAME:-openclaw-gateway}" || true; fi' >> /home/node/.local/bin/tailscale-start.sh && \ echo 'sleep 2' >> /home/node/.local/bin/tailscale-start.sh && \ - echo 'tailscale --socket=/tmp/tailscale.sock serve --bg 18789' >> /home/node/.local/bin/tailscale-start.sh && \ + echo 'tailscale --socket=/tmp/tailscale.sock serve --bg 18789 || true' >> /home/node/.local/bin/tailscale-start.sh && \ chmod +x /home/node/.local/bin/tailscale-start.sh # Copy custom tools into the image @@ -54,7 +54,7 @@ COPY bin/* /usr/local/bin/ RUN chmod +x /usr/local/bin/* # Create directories in the persistent volume location -RUN mkdir -p /home/node/.openclaw/ssh /home/node/.openclaw/gog \ +RUN mkdir -p /home/node/.openclaw/ssh /home/node/.openclaw/gog /opt/openclaw/defaults \ && chown -R node:node /home/node/.openclaw # Link gog config and ssh to standard locations @@ -62,8 +62,9 @@ RUN mkdir -p /home/node/.config /home/node/.ssh \ && ln -sf /home/node/.openclaw/gog /home/node/.config/gog \ && ln -sf /home/node/.openclaw/ssh /home/node/.ssh -# Copy config into the image -COPY config/openclaw.json /home/node/.openclaw/openclaw.json +# Copy default config into the image +COPY config/openclaw.json /opt/openclaw/defaults/openclaw.json +RUN chown -R node:node /opt/openclaw/defaults # Switch back to node user USER node diff --git a/docker/bin/start-gateway.sh b/docker/bin/start-gateway.sh new file mode 100644 index 0000000..5297500 --- /dev/null +++ b/docker/bin/start-gateway.sh @@ -0,0 +1,69 @@ +#!/bin/sh +set -eu + +CONFIG_DIR="${HOME:-/home/node}/.openclaw" +CONFIG_FILE="${CONFIG_DIR}/openclaw.json" +DEFAULT_CONFIG="/opt/openclaw/defaults/openclaw.json" +BIND="${OPENCLAW_GATEWAY_BIND:-lan}" +TAILSCALE_MODE="${OPENCLAW_TAILSCALE_MODE:-off}" +PORT="${OPENCLAW_GATEWAY_PORT:-18789}" + +mkdir -p "${CONFIG_DIR}" + +if [ ! -f "${CONFIG_FILE}" ] && [ -f "${DEFAULT_CONFIG}" ]; then + cp "${DEFAULT_CONFIG}" "${CONFIG_FILE}" +fi + +if [ ! -f "${CONFIG_FILE}" ]; then + printf '{}\n' > "${CONFIG_FILE}" +fi + +tmp_file="$(mktemp)" +jq \ + --arg bind "${BIND}" \ + --arg tailscale_mode "${TAILSCALE_MODE}" \ + --arg token "${OPENCLAW_GATEWAY_TOKEN:-}" \ + ' + .env.OPENCLAW_GATEWAY_TOKEN = "${OPENCLAW_GATEWAY_TOKEN}" | + .env.OPENAI_API_KEY = "${OPENAI_API_KEY}" | + .gateway.bind = $bind | + .gateway.tailscale.mode = $tailscale_mode | + .gateway.auth.mode = "token" | + .gateway.auth.token = (if $token == "" then (.gateway.auth.token // "${OPENCLAW_GATEWAY_TOKEN}") else $token end) | + .gateway.controlUi.allowInsecureAuth = true | + .models.providers.openai = { + baseUrl: "https://api.openai.com/v1", + apiKey: "${OPENAI_API_KEY}", + auth: "api-key", + api: "openai-completions", + models: [ + { + id: "gpt-4.1-mini", + name: "GPT-4.1 mini", + reasoning: false, + input: ["text"], + cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0}, + contextWindow: 1047576, + maxTokens: 32768 + }, + { + id: "gpt-image-1", + name: "GPT Image 1", + reasoning: false, + input: ["text", "image"], + cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0}, + contextWindow: 128000, + maxTokens: 4096 + } + ] + } | + .agents.defaults.model.fallbacks = ((.agents.defaults.model.fallbacks // []) + ["openai/gpt-4.1-mini"] | unique) | + .agents.defaults.models["openai/gpt-4.1-mini"] = {alias: "OpenAI GPT-4.1 mini"} + ' "${CONFIG_FILE}" > "${tmp_file}" +mv "${tmp_file}" "${CONFIG_FILE}" + +if [ "${OPENCLAW_ENABLE_TAILSCALE:-0}" = "1" ] && [ -x /home/node/.local/bin/tailscale-start.sh ]; then + /home/node/.local/bin/tailscale-start.sh || true +fi + +exec node dist/index.js gateway --bind "${BIND}" --port "${PORT}" diff --git a/docker/config/openclaw.json b/docker/config/openclaw.json index 19ec855..a73f054 100644 --- a/docker/config/openclaw.json +++ b/docker/config/openclaw.json @@ -5,7 +5,8 @@ }, "env": { "MOONSHOT_API_KEY": "${MOONSHOT_API_KEY}", - "OPENCLAW_GATEWAY_TOKEN": "${OPENCLAW_GATEWAY_TOKEN}" + "OPENCLAW_GATEWAY_TOKEN": "${OPENCLAW_GATEWAY_TOKEN}", + "OPENAI_API_KEY": "${OPENAI_API_KEY}" }, "wizard": { "lastRunAt": "2026-02-19T04:49:36.701Z", @@ -23,11 +24,47 @@ }, "models": { "providers": { + "openai": { + "baseUrl": "https://api.openai.com/v1", + "apiKey": "${OPENAI_API_KEY}", + "auth": "api-key", + "api": "openai-completions", + "models": [ + { + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "reasoning": false, + "input": ["text"], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 1047576, + "maxTokens": 32768 + }, + { + "id": "gpt-image-1", + "name": "GPT Image 1", + "reasoning": false, + "input": ["text", "image"], + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "contextWindow": 128000, + "maxTokens": 4096 + } + ] + }, "moonshot": { "baseUrl": "https://api.moonshot.ai/v1", "apiKey": "${MOONSHOT_API_KEY}", "auth": "api-key", - "api": "openai-responses", + "api": "openai-completions", "models": [ { "id": "kimi-k2.5", @@ -51,13 +88,16 @@ "defaults": { "model": { "primary": "kimi-coding/k2p5", - "fallbacks": ["moonshot/kimi-k2.5"] + "fallbacks": ["moonshot/kimi-k2.5", "openai/gpt-4.1-mini"] }, "models": { - "moonshot/kimi-k2.5": { + "kimi-coding/k2p5": { "alias": "Kimi K2.5" }, - "kimi-coding/k2p5": { + "openai/gpt-4.1-mini": { + "alias": "OpenAI GPT-4.1 mini" + }, + "moonshot/kimi-k2.5": { "alias": "Kimi K2.5" } }, @@ -97,9 +137,16 @@ } }, "gateway": { - "bind": "loopback", + "bind": "lan", + "auth": { + "mode": "token", + "token": "${OPENCLAW_GATEWAY_TOKEN}" + }, + "controlUi": { + "allowInsecureAuth": true + }, "tailscale": { - "mode": "serve", + "mode": "off", "resetOnExit": false } }, diff --git a/stack.yml b/stack.yml index 2ab8fe2..90defca 100644 --- a/stack.yml +++ b/stack.yml @@ -8,6 +8,10 @@ services: TERM: xterm-256color OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN} MOONSHOT_API_KEY: ${MOONSHOT_API_KEY} + OPENAI_API_KEY: ${OPENAI_API_KEY} + OPENCLAW_GATEWAY_BIND: ${OPENCLAW_GATEWAY_BIND:-lan} + OPENCLAW_TAILSCALE_MODE: ${OPENCLAW_TAILSCALE_MODE:-off} + OPENCLAW_ENABLE_TAILSCALE: ${OPENCLAW_ENABLE_TAILSCALE:-0} GOG_ACCOUNT: ${GOG_ACCOUNT:-} volumes: - openclaw-config:/home/node/.openclaw @@ -29,16 +33,7 @@ services: - node.hostname == tpi-n1 networks: - dokploy-network - command: - [ - "node", - "dist/index.js", - "gateway", - "--bind", - "${OPENCLAW_GATEWAY_BIND:-lan}", - "--port", - "18789", - ] + command: ["/usr/local/bin/start-gateway.sh"] volumes: openclaw-config: