diff --git a/openclaw_assistant/Dockerfile b/openclaw_assistant/Dockerfile index b4515ad..a7d855d 100644 --- a/openclaw_assistant/Dockerfile +++ b/openclaw_assistant/Dockerfile @@ -43,9 +43,10 @@ RUN npm config set fund false && npm config set audit false \ && npm install -g openclaw@2026.1.30 COPY run.sh /run.sh +COPY oc_config_helper.py /oc_config_helper.py COPY nginx.conf.tpl /etc/nginx/nginx.conf.tpl COPY landing.html.tpl /etc/nginx/landing.html.tpl -RUN chmod +x /run.sh \ +RUN chmod +x /run.sh /oc_config_helper.py \ && mkdir -p /run/nginx CMD [ "/run.sh" ] diff --git a/openclaw_assistant/config.yaml b/openclaw_assistant/config.yaml index 9902ae1..4b10ef9 100644 --- a/openclaw_assistant/config.yaml +++ b/openclaw_assistant/config.yaml @@ -1,5 +1,5 @@ name: OpenClaw Assistant -version: "0.5.25" +version: "0.5.26" slug: openclaw_assistant description: Run OpenClaw Assistant (OpenClaw-compatible) as a Home Assistant add-on. url: https://github.com/techartdev/OpenClawHomeAssistant @@ -47,6 +47,15 @@ options: clean_session_locks_on_start: true clean_session_locks_on_exit: true + # Gateway network bind mode: + # - loopback: bind to 127.0.0.1 only (local access only, more secure) + # - lan: bind to all interfaces (accessible from local network) + # Default is loopback for security. + gateway_bind_mode: loopback + + # Gateway port to listen on + gateway_port: 18789 + schema: timezone: str @@ -60,4 +69,6 @@ schema: clean_session_locks_on_start: bool? clean_session_locks_on_exit: bool? + gateway_bind_mode: list(loopback|lan)? + gateway_port: int(1,65535)? diff --git a/openclaw_assistant/oc_config_helper.py b/openclaw_assistant/oc_config_helper.py new file mode 100644 index 0000000..87d523d --- /dev/null +++ b/openclaw_assistant/oc_config_helper.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +""" +OpenClaw config helper for Home Assistant add-on. +Safely reads/writes openclaw.json without corrupting it. +""" + +import json +import os +import re +import sys +from pathlib import Path + +CONFIG_PATH = Path(os.environ.get("OPENCLAW_CONFIG_PATH", "/config/.openclaw/openclaw.json")) + + + +def read_config(): + """Read and parse openclaw.json.""" + if not CONFIG_PATH.exists(): + return None + try: + return json.loads(CONFIG_PATH.read_text(encoding="utf-8")) + except (json.JSONDecodeError, IOError) as e: + print(f"ERROR: Failed to read config: {e}", file=sys.stderr) + return None + + +def write_config(cfg): + """Write config back to file with nice formatting.""" + try: + CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) + CONFIG_PATH.write_text(json.dumps(cfg, indent=2) + "\n", encoding="utf-8") + return True + except IOError as e: + print(f"ERROR: Failed to write config: {e}", file=sys.stderr) + return False + + +def get_gateway_setting(key, default=None): + """Get a gateway setting from config.""" + cfg = read_config() + if cfg is None: + return default + return cfg.get("gateway", {}).get(key, default) + + +def set_gateway_setting(key, value): + """Set a gateway setting, preserving other config.""" + cfg = read_config() + if cfg is None: + cfg = {} + + if "gateway" not in cfg: + cfg["gateway"] = {} + + cfg["gateway"][key] = value + return write_config(cfg) + + +def apply_bind_mode_settings(bind_mode: str, port: int): + """ + Apply bind mode settings to OpenClaw config. + + Args: + bind_mode: "loopback" or "lan" + port: Port number to listen on (must be 1-65535) + """ + # Validate bind mode + if bind_mode not in ["loopback", "lan"]: + print(f"ERROR: Invalid bind_mode '{bind_mode}'. Must be 'loopback' or 'lan'") + return False + + # Validate port range + if port < 1 or port > 65535: + print(f"ERROR: Invalid port {port}. Must be between 1 and 65535") + return False + + cfg = read_config() + if cfg is None: + cfg = {} + + if "gateway" not in cfg: + cfg["gateway"] = {} + + gateway = cfg["gateway"] + + current_bind = gateway.get("bind", "") + current_port = gateway.get("port", 18789) + + changes = [] + + if current_bind != bind_mode: + gateway["bind"] = bind_mode + changes.append(f"bind: {current_bind} -> {bind_mode}") + + # Always update port when it differs from the desired value + if current_port != port: + gateway["port"] = port + changes.append(f"port: {current_port} -> {port}") + + if changes: + if write_config(cfg): + print(f"INFO: Updated gateway settings: {', '.join(changes)}") + return True + else: + print("ERROR: Failed to write config") + return False + else: + print(f"INFO: Gateway settings already correct (bind={bind_mode}, port={port})") + return True + + +def main(): + """CLI entry point for use by run.sh""" + if len(sys.argv) < 2: + print("Usage: oc_config_helper.py [args...]") + sys.exit(1) + + cmd = sys.argv[1] + + if cmd == "apply-bind-mode": + if len(sys.argv) != 4: + print("Usage: oc_config_helper.py apply-bind-mode ") + sys.exit(1) + bind_mode = sys.argv[2] + port = int(sys.argv[3]) + success = apply_bind_mode_settings(bind_mode, port) + sys.exit(0 if success else 1) + + elif cmd == "get": + if len(sys.argv) != 3: + print("Usage: oc_config_helper.py get ") + sys.exit(1) + key = sys.argv[2] + value = get_gateway_setting(key) + if value is not None: + print(value) + sys.exit(0) + + elif cmd == "set": + if len(sys.argv) != 4: + print("Usage: oc_config_helper.py set ") + sys.exit(1) + key = sys.argv[2] + value = sys.argv[3] + # Try to convert to int if it looks like a number + try: + value = int(value) + except ValueError: + pass + success = set_gateway_setting(key, value) + sys.exit(0 if success else 1) + + else: + print(f"Unknown command: {cmd}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/openclaw_assistant/run.sh b/openclaw_assistant/run.sh index d4b4ace..a94da81 100644 --- a/openclaw_assistant/run.sh +++ b/openclaw_assistant/run.sh @@ -27,6 +27,10 @@ ROUTER_KEY=$(jq -r '.router_ssh_key_path // "/data/keys/router_ssh"' "$OPTIONS_F CLEAN_LOCKS_ON_START=$(jq -r '.clean_session_locks_on_start // true' "$OPTIONS_FILE") CLEAN_LOCKS_ON_EXIT=$(jq -r '.clean_session_locks_on_exit // true' "$OPTIONS_FILE") +# Gateway bind mode (loopback or lan) +GATEWAY_BIND_MODE=$(jq -r '.gateway_bind_mode // "loopback"' "$OPTIONS_FILE") +GATEWAY_PORT=$(jq -r '.gateway_port // 18789' "$OPTIONS_FILE") + export TZ="$TZNAME" # Reduce risk of secrets ending up in logs @@ -170,6 +174,8 @@ cfg_path.parent.mkdir(parents=True, exist_ok=True) cfg = { "gateway": { "mode": "local", + "port": 18789, + "bind": "loopback", "auth": { "mode": "token", "token": secrets.token_urlsafe(24) @@ -182,6 +188,35 @@ print("INFO: Wrote minimal OpenClaw config (gateway.mode=local, auth.token gener PY fi +# ------------------------------------------------------------------------------ +# Apply gateway LAN mode settings safely using helper script +# This updates gateway.bind and gateway.port without touching other settings +# ------------------------------------------------------------------------------ +export OPENCLAW_CONFIG_PATH="/config/.openclaw/openclaw.json" + +# Find the helper script (copied to root in Dockerfile, or fallback to add-on dir) +HELPER_PATH="/oc_config_helper.py" +if [ ! -f "$HELPER_PATH" ] && [ -f "$(dirname "$0")/oc_config_helper.py" ]; then + HELPER_PATH="$(dirname "$0")/oc_config_helper.py" +fi + +if [ -f "$OPENCLAW_CONFIG_PATH" ]; then + if [ -f "$HELPER_PATH" ]; then + if ! python3 "$HELPER_PATH" apply-bind-mode "$GATEWAY_BIND_MODE" "$GATEWAY_PORT"; then + rc=$? + echo "ERROR: Failed to apply gateway bind mode via oc_config_helper.py (exit code ${rc})." + echo "ERROR: Gateway bind configuration may be incorrect; aborting startup." + exit "${rc}" + fi + else + echo "WARN: oc_config_helper.py not found, cannot apply gateway settings" + echo "INFO: Ensure the add-on image includes oc_config_helper.py and restart" + fi +else + echo "WARN: OpenClaw config not found at $OPENCLAW_CONFIG_PATH, cannot apply gateway settings" + echo "INFO: Run 'openclaw onboard' first, then restart the add-on" +fi + echo "Starting OpenClaw Assistant gateway (openclaw)..." openclaw gateway run & GW_PID=$! diff --git a/openclaw_assistant/translations/bg.yaml b/openclaw_assistant/translations/bg.yaml new file mode 100644 index 0000000..18579d0 --- /dev/null +++ b/openclaw_assistant/translations/bg.yaml @@ -0,0 +1,44 @@ +configuration: + timezone: + name: Часова зона + description: Часова зона за добавката (напр. Europe/Sofia, America/New_York) + + enable_terminal: + name: Активиране на уеб терминал + description: Активиране на уеб терминал бутона в Home Assistant (Ingress) чрез ttyd + + gateway_public_url: + name: Публичен URL на Gateway + description: Публичен базов URL за отваряне на Gateway уеб интерфейса в нов таб (не вграден). Пример - https://example.duckdns.org:12345 или http://192.168.1.10:18789 + + homeassistant_token: + name: Home Assistant токен + description: Опционално - дълготраен токен на Home Assistant за локални API скриптове/инструменти + + router_ssh_host: + name: SSH хост на рутер + description: Опционално - SSH име на хост или IP адрес на рутер/защитна стена + + router_ssh_user: + name: SSH потребител на рутер + description: Опционално - SSH потребителско име за рутер/защитна стена + + router_ssh_key_path: + name: Път до SSH ключ на рутер + description: Път до SSH частен ключ за достъп до рутер (по подразбиране - /data/keys/router_ssh) + + clean_session_locks_on_start: + name: Изчистване на заключвания при стартиране + description: Изчистване на остарели заключващи файлове на сесии след срив/рестарт при стартиране на добавката + + clean_session_locks_on_exit: + name: Изчистване на заключвания при изход + description: Изчистване на заключващи файлове на сесии при нормално спиране на добавката + + gateway_bind_mode: + name: Режим на свързване на Gateway + description: Режим на мрежово свързване - loopback (само 127.0.0.1, по-сигурно) или lan (всички интерфейси, достъпно от локалната мрежа) + + gateway_port: + name: Порт на Gateway + description: Номер на порт, на който OpenClaw gateway да слуша (по подразбиране - 18789) diff --git a/openclaw_assistant/translations/de.yaml b/openclaw_assistant/translations/de.yaml new file mode 100644 index 0000000..f3b4a44 --- /dev/null +++ b/openclaw_assistant/translations/de.yaml @@ -0,0 +1,44 @@ +configuration: + timezone: + name: Zeitzone + description: Zeitzone für das Add-on (z.B. Europe/Sofia, America/New_York) + + enable_terminal: + name: Web-Terminal aktivieren + description: Web-Terminal-Schaltfläche in Home Assistant (Ingress) über ttyd aktivieren + + gateway_public_url: + name: Öffentliche Gateway-URL + description: Öffentliche Basis-URL zum Öffnen der Gateway-Weboberfläche in einem neuen Tab (nicht eingebettet). Beispiel - https://example.duckdns.org:12345 oder http://192.168.1.10:18789 + + homeassistant_token: + name: Home Assistant Token + description: Optional - Langlebiger Home Assistant Token für lokale API-Skripte/Tools + + router_ssh_host: + name: Router SSH-Host + description: Optional - SSH-Hostname oder IP-Adresse des Routers/der Firewall + + router_ssh_user: + name: Router SSH-Benutzer + description: Optional - SSH-Benutzername für Router/Firewall + + router_ssh_key_path: + name: Router SSH-Schlüsselpfad + description: Pfad zum privaten SSH-Schlüssel für Router-Zugriff (Standard - /data/keys/router_ssh) + + clean_session_locks_on_start: + name: Sitzungssperren beim Start bereinigen + description: Veraltete Sitzungssperrdateien nach Abstürzen/Neustarts beim Start des Add-ons bereinigen + + clean_session_locks_on_exit: + name: Sitzungssperren beim Beenden bereinigen + description: Sitzungssperrdateien beim ordnungsgemäßen Stoppen des Add-ons bereinigen + + gateway_bind_mode: + name: Gateway-Bindungsmodus + description: Netzwerk-Bindungsmodus - loopback (nur 127.0.0.1, sicherer) oder lan (alle Schnittstellen, vom lokalen Netzwerk aus zugänglich) + + gateway_port: + name: Gateway-Port + description: Portnummer, auf der das OpenClaw-Gateway lauscht (Standard - 18789) diff --git a/openclaw_assistant/translations/en.yaml b/openclaw_assistant/translations/en.yaml new file mode 100644 index 0000000..ba3fcd4 --- /dev/null +++ b/openclaw_assistant/translations/en.yaml @@ -0,0 +1,44 @@ +configuration: + timezone: + name: Timezone + description: Timezone for the add-on (e.g., Europe/Sofia, America/New_York) + + enable_terminal: + name: Enable Web Terminal + description: Enable web terminal Button inside Home Assistant (Ingress) via ttyd + + gateway_public_url: + name: Gateway Public URL + description: Public base URL for opening the Gateway Web UI in a new tab (not embedded). Example - https://example.duckdns.org:12345 or http://192.168.1.10:18789 + + homeassistant_token: + name: Home Assistant Token + description: Optional - Home Assistant long-lived token for local HA API scripts/tools + + router_ssh_host: + name: Router SSH Host + description: Optional - Router/firewall SSH hostname or IP address + + router_ssh_user: + name: Router SSH User + description: Optional - Router/firewall SSH username + + router_ssh_key_path: + name: Router SSH Key Path + description: Path to SSH private key for router access (default - /data/keys/router_ssh) + + clean_session_locks_on_start: + name: Clean Session Locks on Start + description: Cleanup stale session lock files left after crashes/restarts when add-on starts + + clean_session_locks_on_exit: + name: Clean Session Locks on Exit + description: Cleanup session lock files when add-on stops gracefully + + gateway_bind_mode: + name: Gateway Bind Mode + description: Network bind mode - loopback (127.0.0.1 only, more secure) or lan (all interfaces, accessible from local network) + + gateway_port: + name: Gateway Port + description: Port number for the OpenClaw gateway to listen on (default - 18789) diff --git a/openclaw_assistant/translations/es.yaml b/openclaw_assistant/translations/es.yaml new file mode 100644 index 0000000..47f355b --- /dev/null +++ b/openclaw_assistant/translations/es.yaml @@ -0,0 +1,44 @@ +configuration: + timezone: + name: Zona horaria + description: Zona horaria para el complemento (ej. Europe/Sofia, America/New_York) + + enable_terminal: + name: Activar terminal web + description: Activar botón de terminal web dentro de Home Assistant (Ingress) mediante ttyd + + gateway_public_url: + name: URL pública del Gateway + description: URL base pública para abrir la interfaz web del Gateway en una nueva pestaña (no integrada). Ejemplo - https://example.duckdns.org:12345 o http://192.168.1.10:18789 + + homeassistant_token: + name: Token de Home Assistant + description: Opcional - Token de larga duración de Home Assistant para scripts/herramientas de API local + + router_ssh_host: + name: Host SSH del router + description: Opcional - Nombre de host SSH o dirección IP del router/firewall + + router_ssh_user: + name: Usuario SSH del router + description: Opcional - Nombre de usuario SSH del router/firewall + + router_ssh_key_path: + name: Ruta de clave SSH del router + description: Ruta a la clave privada SSH para acceso al router (predeterminado - /data/keys/router_ssh) + + clean_session_locks_on_start: + name: Limpiar bloqueos de sesión al iniciar + description: Limpiar archivos de bloqueo de sesión obsoletos dejados después de fallos/reinicios cuando se inicia el complemento + + clean_session_locks_on_exit: + name: Limpiar bloqueos de sesión al salir + description: Limpiar archivos de bloqueo de sesión cuando el complemento se detiene correctamente + + gateway_bind_mode: + name: Modo de enlace del Gateway + description: Modo de enlace de red - loopback (solo 127.0.0.1, más seguro) o lan (todas las interfaces, accesible desde la red local) + + gateway_port: + name: Puerto del Gateway + description: Número de puerto en el que el gateway de OpenClaw escuchará (predeterminado - 18789)