Stop managing OpenClaw config; keep only add-on-specific options; enable terminal by default (v0.5.4)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
name: OpenClaw Assistant
|
name: OpenClaw Assistant
|
||||||
version: "0.5.3"
|
version: "0.5.4"
|
||||||
slug: openclaw_assistant
|
slug: openclaw_assistant
|
||||||
description: Run OpenClaw Assistant (OpenClaw-compatible) as a Home Assistant add-on.
|
description: Run OpenClaw Assistant (OpenClaw-compatible) as a Home Assistant add-on.
|
||||||
url: https://github.com/techartdev/OpenClawHomeAssistant
|
url: https://github.com/techartdev/OpenClawHomeAssistant
|
||||||
@@ -21,31 +21,17 @@ panel_icon: mdi:robot
|
|||||||
map:
|
map:
|
||||||
- addon_config:rw
|
- addon_config:rw
|
||||||
options:
|
options:
|
||||||
telegram_bot_token: ""
|
|
||||||
timezone: "Europe/Sofia"
|
timezone: "Europe/Sofia"
|
||||||
# Optional: lock DMs to specific Telegram user ids (comma-separated).
|
|
||||||
# If set, add-on will use dmPolicy=allowlist and allowFrom=<ids>.
|
|
||||||
telegram_allow_from: ""
|
|
||||||
|
|
||||||
# Model selection (string like "openai-codex/gpt-5.2" or "ollama/gpt-oss:20b")
|
# Enable web terminal inside Home Assistant (Ingress) via ttyd
|
||||||
model_primary: "openai-codex/gpt-5.2"
|
enable_terminal: true
|
||||||
|
|
||||||
# Gateway UI exposure (LAN)
|
|
||||||
# - bind: loopback (default) or lan
|
|
||||||
# - port: default 18789
|
|
||||||
# - token: set to a fixed token so Control UI can connect; required if bind=lan
|
|
||||||
gateway_bind: "loopback"
|
|
||||||
gateway_port: 18789
|
|
||||||
gateway_token: ""
|
|
||||||
|
|
||||||
# Public base URL for opening the Gateway Web UI in a new tab (not embedded).
|
# Public base URL for opening the Gateway Web UI in a new tab (not embedded).
|
||||||
# Recommended: NO trailing slash.
|
# Recommended: NO trailing slash.
|
||||||
# Example: "https://example.duckdns.org:12345" or "http://192.168.1.10:18789"
|
# Example: "https://example.duckdns.org:12345" or "http://192.168.1.10:18789"
|
||||||
gateway_public_url: ""
|
gateway_public_url: ""
|
||||||
|
|
||||||
# Run `openclaw doctor --fix` on startup (useful after upgrades; slows restarts)
|
# Optional: Home Assistant long-lived token (for local HA API scripts/tools)
|
||||||
run_doctor_on_start: false
|
|
||||||
|
|
||||||
homeassistant_token: ""
|
homeassistant_token: ""
|
||||||
|
|
||||||
# Optional: Router SSH defaults (leave empty if you don't need router automation)
|
# Optional: Router SSH defaults (leave empty if you don't need router automation)
|
||||||
@@ -55,35 +41,21 @@ options:
|
|||||||
router_ssh_user: ""
|
router_ssh_user: ""
|
||||||
router_ssh_key_path: "/data/keys/router_ssh"
|
router_ssh_key_path: "/data/keys/router_ssh"
|
||||||
|
|
||||||
# Brave Search API key (optional; enables Brave-backed web search tool)
|
|
||||||
brave_api_key: ""
|
|
||||||
|
|
||||||
# Enable web terminal inside Home Assistant (Ingress) via ttyd
|
|
||||||
enable_terminal: false
|
|
||||||
|
|
||||||
# Cleanup stale session lock files left after crashes/restarts
|
# Cleanup stale session lock files left after crashes/restarts
|
||||||
clean_session_locks_on_start: true
|
clean_session_locks_on_start: true
|
||||||
clean_session_locks_on_exit: true
|
clean_session_locks_on_exit: true
|
||||||
|
|
||||||
|
|
||||||
schema:
|
schema:
|
||||||
telegram_bot_token: str
|
|
||||||
timezone: str
|
timezone: str
|
||||||
telegram_allow_from: str?
|
enable_terminal: bool?
|
||||||
model_primary: str
|
|
||||||
gateway_bind: list(loopback|lan)
|
|
||||||
gateway_port: int
|
|
||||||
gateway_token: str?
|
|
||||||
gateway_public_url: str?
|
gateway_public_url: str?
|
||||||
run_doctor_on_start: bool
|
|
||||||
homeassistant_token: str?
|
homeassistant_token: str?
|
||||||
|
|
||||||
router_ssh_host: str
|
router_ssh_host: str
|
||||||
router_ssh_user: str
|
router_ssh_user: str
|
||||||
router_ssh_key_path: str
|
router_ssh_key_path: str
|
||||||
|
|
||||||
brave_api_key: str?
|
|
||||||
enable_terminal: bool?
|
|
||||||
clean_session_locks_on_start: bool?
|
clean_session_locks_on_start: bool?
|
||||||
clean_session_locks_on_exit: bool?
|
clean_session_locks_on_exit: bool?
|
||||||
|
|
||||||
|
|||||||
@@ -71,13 +71,32 @@ http {
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 style="margin:0 0 8px 0">OpenClaw Assistant</h2>
|
<h2 style="margin:0 0 8px 0">OpenClaw Assistant</h2>
|
||||||
<div class="row" style="margin-bottom:6px">
|
<div class="row" style="margin-bottom:6px">
|
||||||
<a class="btn" href="__GATEWAY_PUBLIC_URL____GW_PUBLIC_URL_PATH__?token=__GATEWAY_TOKEN__" target="_blank" rel="noopener noreferrer">Open Gateway Web UI</a>
|
<a class="btn" id="gwbtn" href="__GATEWAY_PUBLIC_URL____GW_PUBLIC_URL_PATH__?token=__GATEWAY_TOKEN__" target="_blank" rel="noopener noreferrer">Open Gateway Web UI</a>
|
||||||
<a class="btn secondary" href="./terminal/" target="_self">Open Terminal (full page)</a>
|
<a class="btn secondary" href="./terminal/" target="_self">Open Terminal (full page)</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted">
|
<div class="muted" id="gwhint">
|
||||||
Tip: The gateway UI is intentionally opened outside of Ingress to avoid websocket/proxy issues.
|
Tip: The gateway UI is intentionally opened outside of Ingress to avoid websocket/proxy issues.
|
||||||
Configure <code>gateway_public_url</code> in the add-on options.
|
Configure <code>gateway_public_url</code> in the add-on options, then complete onboarding in the terminal.
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var publicUrl = '__GATEWAY_PUBLIC_URL__';
|
||||||
|
var token = '__GATEWAY_TOKEN__';
|
||||||
|
var btn = document.getElementById('gwbtn');
|
||||||
|
var hint = document.getElementById('gwhint');
|
||||||
|
if (!publicUrl || publicUrl === '__GATEWAY_PUBLIC_URL__') {
|
||||||
|
btn.style.display = 'none';
|
||||||
|
hint.innerHTML = 'Configure <code>gateway_public_url</code> in the add-on options to enable the external Gateway UI link.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!token || token === '__GATEWAY_TOKEN__') {
|
||||||
|
// Token not known yet (likely before onboarding). Don't show a broken link.
|
||||||
|
btn.style.display = 'none';
|
||||||
|
hint.innerHTML = 'Gateway token not found yet. Open the terminal below and run <code>openclaw onboard</code> (or <code>openclaw configure</code>) to finish setup.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<div class="term">
|
<div class="term">
|
||||||
<iframe src="./terminal/" title="Terminal"></iframe>
|
<iframe src="./terminal/" title="Terminal"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+20
-123
@@ -10,20 +10,13 @@ if [ ! -f "$OPTIONS_FILE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Read add-on options (ALL optional; onboarding can fill the rest)
|
# Read add-on options (only add-on-specific knobs; OpenClaw is configured via onboarding)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
BOT_TOKEN=$(jq -r '.telegram_bot_token // empty' "$OPTIONS_FILE")
|
|
||||||
TZNAME=$(jq -r '.timezone // "Europe/Sofia"' "$OPTIONS_FILE")
|
TZNAME=$(jq -r '.timezone // "Europe/Sofia"' "$OPTIONS_FILE")
|
||||||
ALLOW_FROM_RAW=$(jq -r '.telegram_allow_from // empty' "$OPTIONS_FILE")
|
|
||||||
MODEL_PRIMARY=$(jq -r '.model_primary // empty' "$OPTIONS_FILE")
|
|
||||||
GW_BIND=$(jq -r '.gateway_bind // empty' "$OPTIONS_FILE")
|
|
||||||
GW_PORT=$(jq -r '.gateway_port // empty' "$OPTIONS_FILE")
|
|
||||||
GW_TOKEN=$(jq -r '.gateway_token // empty' "$OPTIONS_FILE")
|
|
||||||
GW_PUBLIC_URL=$(jq -r '.gateway_public_url // empty' "$OPTIONS_FILE")
|
GW_PUBLIC_URL=$(jq -r '.gateway_public_url // empty' "$OPTIONS_FILE")
|
||||||
HA_TOKEN=$(jq -r '.homeassistant_token // empty' "$OPTIONS_FILE")
|
HA_TOKEN=$(jq -r '.homeassistant_token // empty' "$OPTIONS_FILE")
|
||||||
BRAVE_KEY=$(jq -r '.brave_api_key // empty' "$OPTIONS_FILE")
|
ENABLE_TERMINAL=$(jq -r '.enable_terminal // true' "$OPTIONS_FILE")
|
||||||
ENABLE_TERMINAL=$(jq -r '.enable_terminal // false' "$OPTIONS_FILE")
|
|
||||||
|
|
||||||
# Generic router SSH settings
|
# Generic router SSH settings
|
||||||
ROUTER_HOST=$(jq -r '.router_ssh_host // empty' "$OPTIONS_FILE")
|
ROUTER_HOST=$(jq -r '.router_ssh_host // empty' "$OPTIONS_FILE")
|
||||||
@@ -108,110 +101,12 @@ if [ -n "$HA_TOKEN" ]; then
|
|||||||
printf '%s' "$HA_TOKEN" > /config/secrets/homeassistant.token
|
printf '%s' "$HA_TOKEN" > /config/secrets/homeassistant.token
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$BRAVE_KEY" ]; then
|
|
||||||
export BRAVE_API_KEY="$BRAVE_KEY"
|
|
||||||
umask 077
|
|
||||||
printf '%s' "$BRAVE_KEY" > /config/secrets/brave_api_key
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Non-invasive OpenClaw config management
|
# OpenClaw config is managed by OpenClaw itself (onboarding / configure).
|
||||||
# - If config is missing: create it.
|
# This add-on intentionally does NOT create/patch /config/.openclaw/openclaw.json.
|
||||||
# - If config exists: ONLY patch the fields whose add-on options are set.
|
|
||||||
# - If config is not parseable as strict JSON (e.g. JSON5): do NOT touch it.
|
|
||||||
# (This keeps onboarding-managed config intact.)
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
OPENCLAW_CONFIG_PATH="/config/.openclaw/openclaw.json"
|
|
||||||
export OPENCLAW_CONFIG_PATH
|
|
||||||
|
|
||||||
python3 - <<'PY'
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
cfg_path = Path(os.environ.get('OPENCLAW_CONFIG_PATH', '/config/.openclaw/openclaw.json'))
|
|
||||||
|
|
||||||
def set_path(d, keys, value):
|
|
||||||
cur = d
|
|
||||||
for k in keys[:-1]:
|
|
||||||
if k not in cur or not isinstance(cur[k], dict):
|
|
||||||
cur[k] = {}
|
|
||||||
cur = cur[k]
|
|
||||||
cur[keys[-1]] = value
|
|
||||||
|
|
||||||
# Load existing config if possible
|
|
||||||
cfg = {}
|
|
||||||
if cfg_path.exists():
|
|
||||||
try:
|
|
||||||
cfg = json.loads(cfg_path.read_text(encoding='utf-8'))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"INFO: {cfg_path} exists but is not strict JSON; leaving it untouched ({e}).")
|
|
||||||
raise SystemExit(0)
|
|
||||||
|
|
||||||
# Patch only if env var is set (non-empty)
|
|
||||||
|
|
||||||
def env(name):
|
|
||||||
v = os.environ.get(name, '')
|
|
||||||
return v if v != '' else None
|
|
||||||
|
|
||||||
# Ensure gateway.mode is set so `openclaw gateway run` can start.
|
|
||||||
# Safe default for the add-on.
|
|
||||||
set_path(cfg, ['gateway', 'mode'], 'local')
|
|
||||||
|
|
||||||
# gateway bind/port/token
|
|
||||||
bind_ = env('GW_BIND')
|
|
||||||
if bind_:
|
|
||||||
set_path(cfg, ['gateway', 'bind'], bind_)
|
|
||||||
|
|
||||||
port_ = env('GW_PORT')
|
|
||||||
if port_:
|
|
||||||
try:
|
|
||||||
set_path(cfg, ['gateway', 'port'], int(port_))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
token_ = env('GW_TOKEN')
|
|
||||||
if token_:
|
|
||||||
set_path(cfg, ['gateway', 'auth', 'mode'], 'token')
|
|
||||||
set_path(cfg, ['gateway', 'auth', 'token'], token_)
|
|
||||||
|
|
||||||
# Agent defaults: workspace + primary model
|
|
||||||
# We always ensure workspace points to the add-on workspace (safe and expected).
|
|
||||||
set_path(cfg, ['agents', 'defaults', 'workspace'], '/config/clawd')
|
|
||||||
|
|
||||||
model_primary = env('MODEL_PRIMARY')
|
|
||||||
if model_primary:
|
|
||||||
set_path(cfg, ['agents', 'defaults', 'model', 'primary'], model_primary)
|
|
||||||
# Ensure models entry exists (minimal)
|
|
||||||
models = cfg.get('agents', {}).get('defaults', {}).get('models', {})
|
|
||||||
if not isinstance(models, dict):
|
|
||||||
models = {}
|
|
||||||
models.setdefault(model_primary, {})
|
|
||||||
set_path(cfg, ['agents', 'defaults', 'models'], models)
|
|
||||||
|
|
||||||
# Telegram channel only if token provided in options
|
|
||||||
bot_token = env('BOT_TOKEN')
|
|
||||||
if bot_token:
|
|
||||||
set_path(cfg, ['channels', 'telegram', 'enabled'], True)
|
|
||||||
set_path(cfg, ['channels', 'telegram', 'botToken'], bot_token)
|
|
||||||
|
|
||||||
allow_from_raw = env('ALLOW_FROM_RAW')
|
|
||||||
if allow_from_raw:
|
|
||||||
ids = [x.strip() for x in allow_from_raw.split(',') if x.strip()]
|
|
||||||
if ids:
|
|
||||||
set_path(cfg, ['channels', 'telegram', 'dmPolicy'], 'allowlist')
|
|
||||||
set_path(cfg, ['channels', 'telegram', 'allowFrom'], ids)
|
|
||||||
else:
|
|
||||||
# If not set, do NOT override dmPolicy/allowFrom; let onboarding decide.
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Write (pretty JSON; JSON is valid JSON5 too)
|
|
||||||
cfg_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
cfg_path.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n", encoding='utf-8')
|
|
||||||
print(f"INFO: OpenClaw config written/patched at {cfg_path}")
|
|
||||||
PY
|
|
||||||
|
|
||||||
# Convenience info for later (router SSH access path & HA token file)
|
# Convenience info for later (router SSH access path & HA token file)
|
||||||
cat > /config/CONNECTION_NOTES.txt <<EOF
|
cat > /config/CONNECTION_NOTES.txt <<EOF
|
||||||
Home Assistant token (if set): /config/secrets/homeassistant.token
|
Home Assistant token (if set): /config/secrets/homeassistant.token
|
||||||
@@ -221,15 +116,6 @@ Router SSH (generic):
|
|||||||
key=${ROUTER_KEY}
|
key=${ROUTER_KEY}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
RUN_DOCTOR=$(jq -r '.run_doctor_on_start // false' "$OPTIONS_FILE")
|
|
||||||
|
|
||||||
# Run doctor ONLY if explicitly enabled; otherwise don't touch user-managed config/state.
|
|
||||||
if [ "$RUN_DOCTOR" = "true" ]; then
|
|
||||||
echo "Running assistant doctor (auto-fix) ..."
|
|
||||||
(timeout 60s openclaw doctor --fix --yes) || true
|
|
||||||
else
|
|
||||||
echo "Skipping doctor on startup (run_doctor_on_start=false)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Graceful shutdown handling (PID 1 trap) to reduce stale locks
|
# Graceful shutdown handling (PID 1 trap) to reduce stale locks
|
||||||
@@ -285,17 +171,28 @@ fi
|
|||||||
# Start ingress reverse proxy (nginx). This provides the add-on UI inside HA.
|
# Start ingress reverse proxy (nginx). This provides the add-on UI inside HA.
|
||||||
# Token is injected server-side; never put it in the browser URL.
|
# Token is injected server-side; never put it in the browser URL.
|
||||||
|
|
||||||
# Render nginx config from template with the gateway token.
|
# Render nginx config from template.
|
||||||
# NOTE: This intentionally exposes the token in the browser URL via a redirect.
|
# The gateway token is NOT managed by the add-on; OpenClaw will generate/store it.
|
||||||
# This matches OpenClaw Control UI's current expectations.
|
# If we don't know it yet, the landing page will show a hint instead of a broken link.
|
||||||
GW_TOKEN="$GW_TOKEN" GW_PUBLIC_URL="$GW_PUBLIC_URL" python3 - <<'PY'
|
GW_PUBLIC_URL="$GW_PUBLIC_URL" python3 - <<'PY'
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
tpl = Path('/etc/nginx/nginx.conf.tpl').read_text()
|
tpl = Path('/etc/nginx/nginx.conf.tpl').read_text()
|
||||||
token = os.environ.get('GW_TOKEN','')
|
|
||||||
public_url = os.environ.get('GW_PUBLIC_URL','')
|
public_url = os.environ.get('GW_PUBLIC_URL','')
|
||||||
|
|
||||||
|
# Best-effort: read token from OpenClaw config if it exists and is strict JSON.
|
||||||
|
# (If it's JSON5 or missing, we leave token empty and the landing page will guide the user.)
|
||||||
|
token = ''
|
||||||
|
try:
|
||||||
|
cfg_path = Path('/config/.openclaw/openclaw.json')
|
||||||
|
if cfg_path.exists():
|
||||||
|
import json
|
||||||
|
cfg = json.loads(cfg_path.read_text(encoding='utf-8'))
|
||||||
|
token = (((cfg.get('gateway') or {}).get('auth') or {}).get('token')) or ''
|
||||||
|
except Exception:
|
||||||
|
token = ''
|
||||||
|
|
||||||
conf = tpl.replace('__GATEWAY_TOKEN__', token)
|
conf = tpl.replace('__GATEWAY_TOKEN__', token)
|
||||||
conf = conf.replace('__GATEWAY_PUBLIC_URL__', public_url)
|
conf = conf.replace('__GATEWAY_PUBLIC_URL__', public_url)
|
||||||
conf = conf.replace('__GW_PUBLIC_URL_PATH__', '' if public_url.endswith('/') else '/')
|
conf = conf.replace('__GW_PUBLIC_URL_PATH__', '' if public_url.endswith('/') else '/')
|
||||||
|
|||||||
Reference in New Issue
Block a user