Update OpenClaw to 2026.2.24 and enhance gateway configuration options

- Added `gateway_remote_url` and `gateway_additional_allowed_origins` options for improved remote gateway support.
- Updated startup guidance for `gateway_auth_mode=trusted-proxy`.
- Adjusted token retrieval instructions in the landing page.
- Updated translations for new configuration options.
- Fixed various issues related to gateway settings and TLS certificate generation.
This commit is contained in:
techartdev
2026-02-25 16:24:21 +02:00
parent 90607c867d
commit a82df8c851
15 changed files with 284 additions and 62 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
fi
PAYLOAD=$(jq -n \
--arg title "🚀 New release published: $TITLE" \
--arg title "🚀 New App/Add-on release published: $TITLE" \
--arg repo "$REPO" \
--arg tag "$RELEASE_TAG" \
--arg url "$RELEASE_URL" \
+20
View File
@@ -2,6 +2,26 @@
All notable changes to the OpenClaw Assistant Home Assistant Add-on will be documented in this file.
## [0.5.54] - 2026-02-25
### Changed
- Added startup guidance when `gateway_auth_mode=trusted-proxy` is enabled to clarify why direct local CLI gateway calls can show `trusted_proxy_user_missing`/unauthorized.
- Bump OpenClaw to 2026.2.24.
### Added
- New add-on option `gateway_additional_allowed_origins` for extra Control UI origins in `lan_https` mode.
- **Custom SANs in TLS certificate** (`lan_https` mode): hostnames and IPs from `gateway_additional_allowed_origins` and `gateway_public_url` are now included in the server certificate's Subject Alternative Name. The certificate auto-regenerates when SANs change.
### Fixed
- **Gateway token on landing page**: read token directly from `openclaw.json` instead of via `openclaw config get` which redacts secrets since OpenClaw v2026.2.22+ (fixes "Open Gateway Web UI" button sending `openclaw_redacted` as the token).
- **Token retrieval instructions**: all "get your token" references in the landing page and DOCS now use `jq -r '.gateway.auth.token' /config/.openclaw/openclaw.json` with a note explaining why the old `openclaw config get` command no longer works.
- `lan_https` startup no longer overwrites `gateway.controlUi.allowedOrigins` with defaults only.
- Control UI origins are now merged as: built-in defaults + existing config values + `gateway_additional_allowed_origins` (deduplicated).
- In `lan_reverse_proxy` and other non-`lan_https` setups, Control UI origins now also include the origin derived from `gateway_public_url`.
- `gateway.controlUi.allowedOrigins` configuration is now consistently applied via merge logic (defaults + existing values + user extras), reducing manual `openclaw.json` edits after upgrades.
- Add-on no longer exits/restarts when OpenClaw runtime process is restarted during onboarding or config changes.
- `run.sh` now supervises the OpenClaw runtime (`openclaw gateway run` / `openclaw node run`) and auto-restarts it while keeping nginx + terminal alive.
## [0.5.53] - 2026-02-24
- Bump OpenClaw to 2026.2.23.
+1 -1
View File
@@ -103,7 +103,7 @@ USER root
# Install OpenClaw globally
RUN npm config set fund false && npm config set audit false \
&& npm install -g openclaw@2026.2.23
&& npm install -g openclaw@2026.2.24
# Shell aliases and color options for interactive use
RUN tee -a /etc/bash.bashrc <<'EOF'
+14 -1
View File
@@ -1,5 +1,5 @@
name: OpenClaw Assistant
version: "0.5.53"
version: "0.5.54"
slug: openclaw_assistant
description: Run OpenClaw Assistant (OpenClaw-compatible) as a Home Assistant add-on.
url: https://github.com/techartdev/OpenClawHomeAssistant
@@ -62,6 +62,10 @@ options:
# Default is local.
gateway_mode: local
# Remote gateway URL (used when gateway_mode=remote)
# Example: "ws://192.168.1.20:18789" or "wss://gateway.example.com:443"
gateway_remote_url: ""
# Gateway network bind mode:
# - loopback: bind to 127.0.0.1 only (local access only, most secure)
# - lan: bind to all interfaces (accessible from local network)
@@ -89,6 +93,13 @@ options:
# Example: "127.0.0.1,192.168.88.0/24"
gateway_trusted_proxies: ""
# Additional allowed origins for gateway.controlUi.allowedOrigins.
# Merged with built-in defaults and existing configured origins.
# In lan_https mode, hostnames/IPs from these origins are also added to the
# TLS certificate SAN so the cert is valid for custom domains.
# Example: "https://ha.example.com:8443,capacitor://localhost"
gateway_additional_allowed_origins: ""
# Enable OpenAI-compatible Chat Completions API endpoint
# When enabled, OpenClaw can be used as a conversation agent in HA Assist pipeline
# via Extended OpenAI Conversation (HACS) or any OpenAI-compatible client
@@ -133,11 +144,13 @@ schema:
clean_session_locks_on_start: bool?
clean_session_locks_on_exit: bool?
gateway_mode: list(local|remote)?
gateway_remote_url: str?
gateway_bind_mode: list(loopback|lan|tailnet)?
gateway_port: int(1,65535)?
access_mode: list(custom|local_only|lan_https|lan_reverse_proxy|tailnet_https)?
gateway_auth_mode: list(token|trusted-proxy)?
gateway_trusted_proxies: str?
gateway_additional_allowed_origins: str?
enable_openai_api: bool?
force_ipv4_dns: bool?
gateway_env_vars:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 62 KiB

+3 -2
View File
@@ -109,8 +109,9 @@
Set <code>gateway_public_url</code> in add-on options if the button URL is wrong.
</div>
<div class="muted" style="margin-top:6px">
If the Gateway UI says <b>Unauthorized</b>, get your token in the terminal:
<code>openclaw config get gateway.auth.token</code>
If the Gateway UI says <b>Unauthorized</b>, get your token from the terminal:<br>
<code>jq -r '.gateway.auth.token' /config/.openclaw/openclaw.json</code><br>
<small style="color:#6b7280">(Since OpenClaw v2026.2.22+, <code>openclaw config get</code> redacts secrets — read the file directly instead.)</small>
</div>
</details>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 62 KiB

+43 -21
View File
@@ -57,12 +57,13 @@ def set_gateway_setting(key, value):
return write_config(cfg)
def apply_gateway_settings(mode: str, bind_mode: str, port: int, enable_openai_api: bool, auth_mode: str, trusted_proxies_csv: str):
def apply_gateway_settings(mode: str, remote_url: str, bind_mode: str, port: int, enable_openai_api: bool, auth_mode: str, trusted_proxies_csv: str):
"""
Apply gateway settings to OpenClaw config.
Args:
mode: "local" or "remote"
remote_url: remote Gateway websocket URL (used when mode=remote)
bind_mode: "loopback", "lan", or "tailnet"
port: Port number to listen on (must be 1-65535)
enable_openai_api: Enable OpenAI-compatible Chat Completions endpoint
@@ -98,6 +99,11 @@ def apply_gateway_settings(mode: str, bind_mode: str, port: int, enable_openai_a
gateway = cfg["gateway"]
# gateway.remote settings
if "remote" not in gateway or not isinstance(gateway.get("remote"), dict):
gateway["remote"] = {}
remote_cfg = gateway["remote"]
# auth should be nested inside gateway
if "auth" not in gateway:
gateway["auth"] = {}
@@ -120,6 +126,7 @@ def apply_gateway_settings(mode: str, bind_mode: str, port: int, enable_openai_a
trusted_proxy_cfg_default = {"userHeader": "x-forwarded-user"}
current_mode = gateway.get("mode", "")
current_remote_url = remote_cfg.get("url", "")
current_bind = gateway.get("bind", "")
current_port = gateway.get("port", 18789)
current_openai_api = chat_completions.get("enabled", False)
@@ -133,6 +140,10 @@ def apply_gateway_settings(mode: str, bind_mode: str, port: int, enable_openai_a
gateway["mode"] = mode
changes.append(f"mode: {current_mode} -> {mode}")
if current_remote_url != remote_url:
remote_cfg["url"] = remote_url
changes.append(f"remote.url: {current_remote_url} -> {remote_url}")
if current_bind != bind_mode:
gateway["bind"] = bind_mode
changes.append(f"bind: {current_bind} -> {bind_mode}")
@@ -166,11 +177,11 @@ def apply_gateway_settings(mode: str, bind_mode: str, port: int, enable_openai_a
print("ERROR: Failed to write config")
return False
else:
print(f"INFO: Gateway settings already correct (mode={mode}, bind={bind_mode}, port={port}, chatCompletions={enable_openai_api}, authMode={auth_mode}, trustedProxies={trusted_proxies})")
print(f"INFO: Gateway settings already correct (mode={mode}, remoteUrl={remote_url}, bind={bind_mode}, port={port}, chatCompletions={enable_openai_api}, authMode={auth_mode}, trustedProxies={trusted_proxies})")
return True
def set_control_ui_origins(origins_csv: str):
def set_control_ui_origins(origins_csv: str, additional_origins_csv: str = ""):
"""
Configure gateway.controlUi for the built-in HTTPS proxy.
@@ -186,8 +197,8 @@ def set_control_ui_origins(origins_csv: str):
been written by earlier add-on versions.
Args:
origins_csv: Comma-separated list of allowed origins.
Pass an empty string to clear / leave unset.
origins_csv: Comma-separated list of default origins provided by the add-on.
additional_origins_csv: Comma-separated list of user-provided extra origins.
"""
cfg = read_config()
if cfg is None:
@@ -201,14 +212,23 @@ def set_control_ui_origins(origins_csv: str):
gateway["controlUi"] = {}
control_ui = gateway["controlUi"]
origins = [o.strip() for o in origins_csv.split(",") if o.strip()]
default_origins = [o.strip() for o in origins_csv.split(",") if o.strip()]
additional_origins = [o.strip() for o in (additional_origins_csv or "").split(",") if o.strip()]
changes = []
# --- allowedOrigins ---
current_origins = control_ui.get("allowedOrigins", [])
if current_origins != origins:
control_ui["allowedOrigins"] = origins
changes.append(f"allowedOrigins: {current_origins} -> {origins}")
if not isinstance(current_origins, list):
current_origins = []
merged_origins = []
for origin in [*default_origins, *current_origins, *additional_origins]:
if isinstance(origin, str) and origin and origin not in merged_origins:
merged_origins.append(origin)
if current_origins != merged_origins:
control_ui["allowedOrigins"] = merged_origins
changes.append(f"allowedOrigins: {current_origins} -> {merged_origins}")
# --- dangerouslyDisableDeviceAuth ---
# Skips the interactive pairing handshake (error 1008: pairing required).
@@ -224,7 +244,7 @@ def set_control_ui_origins(origins_csv: str):
changes.append(f"removed invalid key: {stale_key}")
if not changes:
print(f"INFO: controlUi already correct: origins={origins}, deviceAuth=disabled")
print(f"INFO: controlUi already correct: origins={merged_origins}, deviceAuth=disabled")
return True
if write_config(cfg):
@@ -243,16 +263,17 @@ def main():
cmd = sys.argv[1]
if cmd == "apply-gateway-settings":
if len(sys.argv) != 8:
print("Usage: oc_config_helper.py apply-gateway-settings <local|remote> <loopback|lan|tailnet> <port> <enable_openai_api:true|false> <auth_mode:token|trusted-proxy> <trusted_proxies_csv>")
if len(sys.argv) != 9:
print("Usage: oc_config_helper.py apply-gateway-settings <local|remote> <remote_url> <loopback|lan|tailnet> <port> <enable_openai_api:true|false> <auth_mode:token|trusted-proxy> <trusted_proxies_csv>")
sys.exit(1)
mode = sys.argv[2]
bind_mode = sys.argv[3]
port = int(sys.argv[4])
enable_openai_api = sys.argv[5].lower() == "true"
auth_mode = sys.argv[6]
trusted_proxies_csv = sys.argv[7]
success = apply_gateway_settings(mode, bind_mode, port, enable_openai_api, auth_mode, trusted_proxies_csv)
remote_url = sys.argv[3]
bind_mode = sys.argv[4]
port = int(sys.argv[5])
enable_openai_api = sys.argv[6].lower() == "true"
auth_mode = sys.argv[7]
trusted_proxies_csv = sys.argv[8]
success = apply_gateway_settings(mode, remote_url, bind_mode, port, enable_openai_api, auth_mode, trusted_proxies_csv)
sys.exit(0 if success else 1)
elif cmd == "get":
@@ -266,11 +287,12 @@ def main():
sys.exit(0)
elif cmd == "set-control-ui-origins":
if len(sys.argv) != 3:
print("Usage: oc_config_helper.py set-control-ui-origins <origins_csv>")
if len(sys.argv) not in (3, 4):
print("Usage: oc_config_helper.py set-control-ui-origins <origins_csv> [additional_origins_csv]")
sys.exit(1)
origins_csv = sys.argv[2]
success = set_control_ui_origins(origins_csv)
additional_origins_csv = sys.argv[3] if len(sys.argv) == 4 else ""
success = set_control_ui_origins(origins_csv, additional_origins_csv)
sys.exit(0 if success else 1)
elif cmd == "set":
+149 -31
View File
@@ -47,11 +47,13 @@ CLEAN_LOCKS_ON_EXIT=$(jq -r '.clean_session_locks_on_exit // true' "$OPTIONS_FIL
# Gateway configuration
GATEWAY_MODE=$(jq -r '.gateway_mode // "local"' "$OPTIONS_FILE")
GATEWAY_REMOTE_URL=$(jq -r '.gateway_remote_url // empty' "$OPTIONS_FILE")
GATEWAY_BIND_MODE=$(jq -r '.gateway_bind_mode // "loopback"' "$OPTIONS_FILE")
GATEWAY_PORT=$(jq -r '.gateway_port // 18789' "$OPTIONS_FILE")
ENABLE_OPENAI_API=$(jq -r '.enable_openai_api // false' "$OPTIONS_FILE")
GATEWAY_AUTH_MODE=$(jq -r '.gateway_auth_mode // "token"' "$OPTIONS_FILE")
GATEWAY_TRUSTED_PROXIES=$(jq -r '.gateway_trusted_proxies // empty' "$OPTIONS_FILE")
GATEWAY_ADDITIONAL_ALLOWED_ORIGINS=$(jq -r '.gateway_additional_allowed_origins // empty' "$OPTIONS_FILE")
FORCE_IPV4_DNS=$(jq -r '.force_ipv4_dns // true' "$OPTIONS_FILE")
ACCESS_MODE=$(jq -r '.access_mode // "custom"' "$OPTIONS_FILE")
NGINX_LOG_LEVEL=$(jq -r '.nginx_log_level // "minimal"' "$OPTIONS_FILE")
@@ -452,8 +454,10 @@ EOF
GW_PID=""
NGINX_PID=""
TTYD_PID=""
SHUTTING_DOWN="false"
shutdown() {
SHUTTING_DOWN="true"
echo "Shutdown requested; stopping services..."
if [ -n "${NGINX_PID}" ] && kill -0 "${NGINX_PID}" >/dev/null 2>&1; then
@@ -534,7 +538,7 @@ if [ -f "$OPENCLAW_CONFIG_PATH" ]; then
if [ -f "$HELPER_PATH" ]; then
# In lan_https mode the gateway uses an internal port; nginx owns the external one.
EFFECTIVE_GW_PORT="$GATEWAY_INTERNAL_PORT"
if ! python3 "$HELPER_PATH" apply-gateway-settings "$GATEWAY_MODE" "$GATEWAY_BIND_MODE" "$EFFECTIVE_GW_PORT" "$ENABLE_OPENAI_API" "$GATEWAY_AUTH_MODE" "$GATEWAY_TRUSTED_PROXIES"; then
if ! python3 "$HELPER_PATH" apply-gateway-settings "$GATEWAY_MODE" "$GATEWAY_REMOTE_URL" "$GATEWAY_BIND_MODE" "$EFFECTIVE_GW_PORT" "$ENABLE_OPENAI_API" "$GATEWAY_AUTH_MODE" "$GATEWAY_TRUSTED_PROXIES"; then
rc=$?
echo "ERROR: Failed to apply gateway settings via oc_config_helper.py (exit code ${rc})."
echo "ERROR: Gateway configuration may be incorrect; aborting startup."
@@ -549,11 +553,18 @@ else
echo "INFO: Run 'openclaw onboard' first, then restart the add-on"
fi
if [ "$GATEWAY_AUTH_MODE" = "trusted-proxy" ]; then
echo "NOTICE: gateway_auth_mode=trusted-proxy is enabled."
echo "NOTICE: Direct local CLI calls to the gateway may return unauthorized (trusted_proxy_user_missing) unless identity headers are injected by your reverse proxy."
echo "NOTICE: For local terminal CLI workflows, temporarily switch to token auth or use commands that don't require direct gateway WS auth."
fi
# ------------------------------------------------------------------------------
# TLS certificate generation for built-in HTTPS proxy (lan_https mode)
# Generates a local CA + server cert so phones/tablets get proper HTTPS.
# The CA cert can be installed once on a device for trusted access.
# ------------------------------------------------------------------------------
LAN_IP=""
if [ "$ENABLE_HTTPS_PROXY" = "true" ]; then
CERT_DIR="/config/certs"
mkdir -p "$CERT_DIR"
@@ -573,16 +584,46 @@ if [ "$ENABLE_HTTPS_PROXY" = "true" ]; then
echo "INFO: Local CA created at $CERT_DIR/ca.crt"
fi
# --- Server cert (regenerated when LAN IP changes) ---
if [ ! -f "$CERT_DIR/gateway.crt" ] || [ ! -f "$CERT_DIR/gateway.key" ] || [ "$LAN_IP" != "$STORED_IP" ]; then
# --- Extra SANs from gateway_additional_allowed_origins + gateway_public_url ---
EXTRA_SANS=""
EXTRA_SAN_SOURCES="${GATEWAY_ADDITIONAL_ALLOWED_ORIGINS},${GW_PUBLIC_URL}"
if [ "$EXTRA_SAN_SOURCES" != "," ]; then
EXTRA_SANS="$(python3 - "$EXTRA_SAN_SOURCES" "${LAN_IP:-}" <<'PY'
import sys, re
from urllib.parse import urlparse
raw = sys.argv[1] if len(sys.argv) > 1 else ""
lan_ip = sys.argv[2] if len(sys.argv) > 2 else ""
entries = [e.strip() for e in raw.split(",") if e.strip()]
sans = []
seen = {"127.0.0.1", "localhost", "homeassistant", "homeassistant.local"}
if lan_ip:
seen.add(lan_ip)
for entry in entries:
if "://" not in entry:
entry = "https://" + entry
host = urlparse(entry).hostname or ""
if host and host not in seen:
seen.add(host)
if re.match(r"^\d{1,3}(\.\d{1,3}){3}$", host):
sans.append(f"IP:{host}")
else:
sans.append(f"DNS:{host}")
print(",".join(sans), end="")
PY
)"
fi
STORED_EXTRA_SANS=$(cat "$CERT_DIR/.cert_extra_sans" 2>/dev/null || echo "")
# --- Server cert (regenerated when LAN IP or SANs change) ---
if [ ! -f "$CERT_DIR/gateway.crt" ] || [ ! -f "$CERT_DIR/gateway.key" ] || [ "$LAN_IP" != "$STORED_IP" ] || [ "$EXTRA_SANS" != "$STORED_EXTRA_SANS" ]; then
echo "INFO: Generating server TLS certificate for IP: ${LAN_IP:-unknown}..."
openssl genrsa -out "$CERT_DIR/gateway.key" 2048 2>/dev/null
openssl req -new -key "$CERT_DIR/gateway.key" -out "$CERT_DIR/gateway.csr" \
-subj "/CN=OpenClaw Gateway" 2>/dev/null
# SAN extension — include LAN IP, loopback, and common mDNS names
# SAN extension — include LAN IP, loopback, common mDNS names + user extras
cat > "$CERT_DIR/_san.ext" <<SANEOF
subjectAltName=IP:${LAN_IP:-127.0.0.1},IP:127.0.0.1,DNS:localhost,DNS:homeassistant,DNS:homeassistant.local
subjectAltName=IP:${LAN_IP:-127.0.0.1},IP:127.0.0.1,DNS:localhost,DNS:homeassistant,DNS:homeassistant.local${EXTRA_SANS:+,${EXTRA_SANS}}
SANEOF
openssl x509 -req -in "$CERT_DIR/gateway.csr" \
@@ -593,7 +634,8 @@ SANEOF
rm -f "$CERT_DIR/gateway.csr" "$CERT_DIR/_san.ext" "$CERT_DIR/ca.srl"
chmod 600 "$CERT_DIR/gateway.key"
printf '%s' "$LAN_IP" > "$CERT_DIR/.cert_ip"
echo "INFO: Server TLS certificate generated (SAN includes IP:${LAN_IP:-127.0.0.1})"
printf '%s' "$EXTRA_SANS" > "$CERT_DIR/.cert_extra_sans"
echo "INFO: Server TLS certificate generated (SAN: IP:${LAN_IP:-127.0.0.1}${EXTRA_SANS:+,${EXTRA_SANS}})"
else
echo "INFO: Reusing existing TLS certificate (IP: $STORED_IP)"
fi
@@ -603,32 +645,44 @@ SANEOF
cp "$CERT_DIR/ca.crt" /etc/nginx/html/openclaw-ca.crt 2>/dev/null || true
echo "INFO: CA certificate available for download at /cert/ca.crt on the HTTPS port"
fi
# ------------------------------------------------------------------
# Configure gateway.controlUi for the HTTPS proxy:
#
# 1. allowedOrigins — the browser's HTTPS origin must be listed,
# otherwise v2026.2.21+ rejects with 1008 "origin not allowed".
#
# 2. dangerouslyDisableDeviceAuth — skips the interactive device
# pairing ceremony (1008 "pairing required"). In a self-hosted
# add-on the user already controls the token, so per-device
# approval adds friction without real security benefit.
#
# NOTE: v2026.2.22+ emits a startup security warning when this
# flag is active. The warning is expected and harmless for this
# use case — run `openclaw security audit` for details.
#
# Also cleans up any stale keys (e.g. pairingMode) from older
# add-on versions that would cause "Unrecognized key" errors.
# Configure gateway.controlUi.allowedOrigins:
# - In lan_https: include HTTPS proxy defaults (LAN IP + common hostnames)
# - In all modes: also include origin from gateway_public_url when present
# - Helper merges with existing origins + user extras and deduplicates
# ------------------------------------------------------------------
if [ -n "$LAN_IP" ] && [ -f "$HELPER_PATH" ] && [ -f "$OPENCLAW_CONFIG_PATH" ]; then
if [ -f "$HELPER_PATH" ] && [ -f "$OPENCLAW_CONFIG_PATH" ]; then
ALLOWED_ORIGINS=""
if [ "$ENABLE_HTTPS_PROXY" = "true" ] && [ -n "$LAN_IP" ]; then
ALLOWED_ORIGINS="https://${LAN_IP}:${GATEWAY_PORT}"
# Also permit common mDNS/hostname variants so the cert SAN names work too
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},https://homeassistant.local:${GATEWAY_PORT}"
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},https://homeassistant:${GATEWAY_PORT}"
python3 "$HELPER_PATH" set-control-ui-origins "$ALLOWED_ORIGINS" || \
echo "WARN: Could not set controlUi settings — gateway may reject the Control UI"
fi
if [ -n "$GW_PUBLIC_URL" ]; then
GW_PUBLIC_ORIGIN="$(python3 - "$GW_PUBLIC_URL" <<'PY'
import sys
from urllib.parse import urlparse
u = (sys.argv[1] or '').strip()
p = urlparse(u)
if p.scheme in ('http', 'https') and p.netloc:
print(f"{p.scheme}://{p.netloc}", end='')
PY
)"
if [ -n "$GW_PUBLIC_ORIGIN" ]; then
if [ -n "$ALLOWED_ORIGINS" ]; then
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},${GW_PUBLIC_ORIGIN}"
else
ALLOWED_ORIGINS="$GW_PUBLIC_ORIGIN"
fi
fi
fi
python3 "$HELPER_PATH" set-control-ui-origins "$ALLOWED_ORIGINS" "$GATEWAY_ADDITIONAL_ALLOWED_ORIGINS" || \
echo "WARN: Could not set controlUi settings — gateway may reject the Control UI"
fi
# ------------------------------------------------------------------------------
@@ -645,9 +699,53 @@ if [ -f /usr/local/lib/openclaw-proxy-shim.cjs ]; then
export OPENCLAW_GLOBAL_NODE_MODULES
fi
echo "Starting OpenClaw Assistant gateway (openclaw)..."
start_openclaw_runtime() {
echo "Starting OpenClaw Assistant runtime (openclaw)..."
if [ "$GATEWAY_MODE" = "remote" ]; then
# Remote mode: do NOT start a local gateway service.
# Start a node/client host that connects to the configured remote gateway URL.
REMOTE_URL="$(timeout 2s openclaw config get gateway.remote.url 2>/dev/null | tr -d '\n' || true)"
if [ -z "$REMOTE_URL" ]; then
echo "ERROR: gateway_mode=remote but gateway.remote.url is empty in openclaw config"
echo "ERROR: Configure gateway.remote.url first, then restart the add-on"
return 1
fi
NODE_HOST=""
NODE_PORT=""
NODE_TLS_FLAG=""
if ! eval "$(python3 - "$REMOTE_URL" <<'PY'
import sys
from urllib.parse import urlparse
url = (sys.argv[1] or '').strip()
p = urlparse(url)
if p.scheme not in ('ws', 'wss') or not p.hostname:
print('echo "ERROR: Invalid gateway.remote.url (expected ws:// or wss://): %s"' % url.replace('"', '\\"'))
print('exit 1')
raise SystemExit(0)
port = p.port or (443 if p.scheme == 'wss' else 80)
print(f'NODE_HOST={p.hostname}')
print(f'NODE_PORT={port}')
print(f'NODE_TLS_FLAG={"--tls" if p.scheme == "wss" else ""}')
PY
)"; then
echo "ERROR: Failed to parse gateway.remote.url: $REMOTE_URL"
return 1
fi
echo "INFO: gateway_mode=remote detected; starting node host to $NODE_HOST:$NODE_PORT ${NODE_TLS_FLAG}"
# shellcheck disable=SC2086
openclaw node run --host "$NODE_HOST" --port "$NODE_PORT" $NODE_TLS_FLAG &
else
openclaw gateway run &
fi
GW_PID=$!
return 0
}
if ! start_openclaw_runtime; then
exit 1
fi
# Start web terminal (optional)
TTYD_PID_FILE="/var/run/openclaw-ttyd.pid"
@@ -717,8 +815,12 @@ fi
# Render nginx config from template.
# The gateway token is NOT managed by the add-on; OpenClaw will generate/store it.
# Best-effort: query it via CLI (works even if openclaw.json is JSON5). If unknown, we hide the button.
GW_TOKEN="$(timeout 2s openclaw config get gateway.auth.token 2>/dev/null | tr -d '\n' || true)"
# Read directly from config file — the CLI redacts secrets since v2026.2.22+.
GW_TOKEN="$(python3 -c "
import json, os
p = os.environ.get('OPENCLAW_CONFIG_PATH', '/config/.openclaw/openclaw.json')
print(json.load(open(p)).get('gateway',{}).get('auth',{}).get('token',''), end='')
" 2>/dev/null || true)"
# Collect disk usage for landing page status card
DISK_TOTAL="" DISK_USED="" DISK_AVAIL="" DISK_PCT=""
@@ -755,5 +857,21 @@ else
echo "WARN: nginx failed to start (PID $NGINX_PID exited); ingress UI may be unavailable"
fi
# Wait for gateway; if it exits, shut down others.
wait "${GW_PID}"
# Keep add-on alive even if gateway/node runtime restarts itself (e.g. during onboarding).
# If runtime exits unexpectedly, restart it while nginx/ttyd stay up.
while true; do
GW_EXIT_CODE=0
wait "${GW_PID}" || GW_EXIT_CODE=$?
if [ "$SHUTTING_DOWN" = "true" ]; then
break
fi
echo "WARN: OpenClaw runtime exited with code ${GW_EXIT_CODE}. Restarting in 2s..."
sleep 2
if ! start_openclaw_runtime; then
echo "ERROR: Failed to restart OpenClaw runtime; retrying in 5s..."
sleep 5
fi
done
+8
View File
@@ -50,6 +50,14 @@ configuration:
local: "Локален (препоръчително)"
remote: "Отдалечен"
gateway_remote_url:
name: URL на отдалечен Gateway
description: WebSocket URL на отдалечения gateway, използван когато Gateway Mode е remote (пример - ws://192.168.1.20:18789 или wss://gateway.example.com:443)
gateway_additional_allowed_origins:
name: Допълнителни разрешени origins
description: Допълнителни origins за Control UI, разделени със запетая; сливат се в gateway.controlUi.allowedOrigins при lan_https (пример - https://ha.example.com:8443,capacitor://localhost)
gateway_bind_mode:
name: Режим на свързване на Gateway
description: Режим на мрежово свързване - loopback (само 127.0.0.1, най-сигурен), lan (всички интерфейси) или tailnet (само Tailscale интерфейс). Пренебрегва се от предварителните режими на достъп.
+8
View File
@@ -50,6 +50,14 @@ configuration:
local: "Lokal (empfohlen)"
remote: "Remote"
gateway_remote_url:
name: Remote Gateway-URL
description: Remote-Gateway-WebSocket-URL, verwendet wenn Gateway-Modus auf Remote steht (Beispiel - ws://192.168.1.20:18789 oder wss://gateway.example.com:443)
gateway_additional_allowed_origins:
name: Zusätzliche erlaubte Origins
description: Kommagetrennte zusätzliche Control-UI-Origins, die bei lan_https in gateway.controlUi.allowedOrigins zusammengeführt werden (Beispiel - https://ha.example.com:8443,capacitor://localhost)
gateway_bind_mode:
name: Gateway-Bindungsmodus
description: Netzwerk-Bindungsmodus - loopback (nur 127.0.0.1, am sichersten), lan (alle Schnittstellen) oder tailnet (nur Tailscale-Schnittstelle). Wird durch Zugriffsmodus-Voreinstellungen überschrieben.
+8
View File
@@ -50,6 +50,14 @@ configuration:
local: "Local (recommended)"
remote: "Remote"
gateway_remote_url:
name: Remote Gateway URL
description: Remote gateway WebSocket URL used when Gateway Mode is Remote (example - ws://192.168.1.20:18789 or wss://gateway.example.com:443)
gateway_additional_allowed_origins:
name: Additional Allowed Origins
description: Comma-separated extra Control UI origins to merge into gateway.controlUi.allowedOrigins in lan_https mode (example - https://ha.example.com:8443,capacitor://localhost)
gateway_bind_mode:
name: Gateway Bind Mode
description: Network bind mode - loopback (127.0.0.1 only, most secure), lan (all interfaces), or tailnet (Tailscale interface only). Overridden by access_mode presets.
+8
View File
@@ -50,6 +50,14 @@ configuration:
local: "Local (recomendado)"
remote: "Remoto"
gateway_remote_url:
name: URL del Gateway Remoto
description: URL WebSocket del gateway remoto utilizada cuando Gateway Mode es Remote (ejemplo - ws://192.168.1.20:18789 o wss://gateway.example.com:443)
gateway_additional_allowed_origins:
name: Orígenes permitidos adicionales
description: Orígenes extra de Control UI separados por comas que se combinan en gateway.controlUi.allowedOrigins en modo lan_https (ejemplo - https://ha.example.com:8443,capacitor://localhost)
gateway_bind_mode:
name: Modo de enlace del Gateway
description: "Modo de enlace de red: loopback (solo 127.0.0.1, más seguro), lan (todas las interfaces) o tailnet (solo interfaz Tailscale). Se anula con las preselecciones del modo de acceso."
+8
View File
@@ -43,6 +43,14 @@ configuration:
name: Wyczyść blokady sesji przy wyjściu
description: Usuń pliki blokad sesji gdy dodatek zatrzymuje się poprawnie
gateway_remote_url:
name: URL zdalnego Gateway
description: Adres WebSocket zdalnego gateway używany gdy Gateway Mode = Remote (przykład - ws://192.168.1.20:18789 lub wss://gateway.example.com:443)
gateway_additional_allowed_origins:
name: Dodatkowe dozwolone origins
description: Dodatkowe origins Control UI rozdzielone przecinkami; łączone z gateway.controlUi.allowedOrigins w trybie lan_https (przykład - https://ha.example.com:8443,capacitor://localhost)
gateway_bind_mode:
name: Tryb bindowania Gateway
description: Tryb bindowania sieci - loopback (tylko 127.0.0.1, najbezpieczniejszy), lan (wszystkie interfejsy) lub tailnet (tylko interfejs Tailscale). Nadpisywane przez ustawienia trybu dostępu.
@@ -50,6 +50,14 @@ configuration:
local: "Local (recomendado)"
remote: "Remoto"
gateway_remote_url:
name: URL do Gateway Remoto
description: URL WebSocket do gateway remoto usada quando Gateway Mode está em Remote (exemplo - ws://192.168.1.20:18789 ou wss://gateway.example.com:443)
gateway_additional_allowed_origins:
name: Origens permitidas adicionais
description: Origens extras da Control UI separadas por vírgula, mescladas em gateway.controlUi.allowedOrigins no modo lan_https (exemplo - https://ha.example.com:8443,capacitor://localhost)
gateway_bind_mode:
name: Modo de Vinculação do Gateway
description: Modo de vinculação de rede - loopback (somente 127.0.0.1, mais seguro), lan (todas as interfaces) ou tailnet (somente interface Tailscale). Substituído pelas predefinições do modo de acesso.