feat(mcp): add auto-configuration for Home Assistant and enhance documentation
This commit is contained in:
+100
-3
@@ -58,6 +58,7 @@ CONTROLUI_DISABLE_DEVICE_AUTH=$(jq -r '.controlui_disable_device_auth // true' "
|
||||
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")
|
||||
AUTO_CONFIGURE_MCP=$(jq -r '.auto_configure_mcp // false' "$OPTIONS_FILE")
|
||||
GW_ENV_VARS_TYPE=$(jq -r 'if .gateway_env_vars == null then "null" else (.gateway_env_vars | type) end' "$OPTIONS_FILE")
|
||||
GW_ENV_VARS_RAW=$(jq -r '.gateway_env_vars // empty' "$OPTIONS_FILE")
|
||||
GW_ENV_VARS_JSON=$(jq -c '.gateway_env_vars // []' "$OPTIONS_FILE")
|
||||
@@ -453,6 +454,7 @@ EOF
|
||||
# Graceful shutdown handling (PID 1 trap) to reduce stale locks
|
||||
# ------------------------------------------------------------------------------
|
||||
GW_PID=""
|
||||
GW_RELAY_PID=""
|
||||
NGINX_PID=""
|
||||
TTYD_PID=""
|
||||
SHUTTING_DOWN="false"
|
||||
@@ -476,6 +478,11 @@ shutdown() {
|
||||
wait "${GW_PID}" || true
|
||||
fi
|
||||
|
||||
if [ -n "${GW_RELAY_PID}" ] && kill -0 "${GW_RELAY_PID}" >/dev/null 2>&1; then
|
||||
kill -TERM "${GW_RELAY_PID}" >/dev/null 2>&1 || true
|
||||
wait "${GW_RELAY_PID}" || true
|
||||
fi
|
||||
|
||||
if [ "$CLEAN_LOCKS_ON_EXIT" = "true" ]; then
|
||||
cleanup_session_locks || true
|
||||
fi
|
||||
@@ -700,15 +707,61 @@ if [ -f /usr/local/lib/openclaw-proxy-shim.cjs ]; then
|
||||
export OPENCLAW_GLOBAL_NODE_MODULES
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Auto-configure MCP (Model Context Protocol) for Home Assistant
|
||||
# Registers HA as an MCP server so OpenClaw can control HA entities/services.
|
||||
# Requires: homeassistant_token set in add-on options + mcporter CLI available.
|
||||
# Runs once; re-runs when the token changes.
|
||||
# Auto-detects HA API URL: supervisor proxy if available, else localhost:8123.
|
||||
# ------------------------------------------------------------------------------
|
||||
if [ "$AUTO_CONFIGURE_MCP" = "true" ] && [ -n "$HA_TOKEN" ]; then
|
||||
if command -v mcporter >/dev/null 2>&1; then
|
||||
# Detect HA API URL: prefer supervisor proxy (works in all add-on network modes),
|
||||
# fall back to localhost:8123 (works with host_network: true).
|
||||
if [ -n "${SUPERVISOR_TOKEN:-}" ]; then
|
||||
MCP_HA_URL="http://supervisor/core/api/mcp"
|
||||
else
|
||||
MCP_HA_URL="http://localhost:8123/api/mcp"
|
||||
fi
|
||||
MCP_FLAG="/config/.openclaw/.mcp_ha_configured"
|
||||
MCP_TOKEN_HASH=$(printf '%s' "$HA_TOKEN" | sha256sum | cut -d' ' -f1)
|
||||
|
||||
if [ -f "$MCP_FLAG" ] && [ "$(cat "$MCP_FLAG" 2>/dev/null)" = "$MCP_TOKEN_HASH" ]; then
|
||||
echo "INFO: MCP Home Assistant server already configured (token unchanged)"
|
||||
else
|
||||
echo "INFO: Configuring MCP for Home Assistant at $MCP_HA_URL ..."
|
||||
# Remove stale entry if present (token may have changed)
|
||||
mcporter config remove HA 2>/dev/null || true
|
||||
|
||||
if mcporter config add HA "$MCP_HA_URL" \
|
||||
--header "Authorization=Bearer $HA_TOKEN" \
|
||||
--scope home 2>&1; then
|
||||
printf '%s' "$MCP_TOKEN_HASH" > "$MCP_FLAG"
|
||||
echo "INFO: MCP server 'HA' registered — OpenClaw can now control Home Assistant"
|
||||
else
|
||||
echo "WARN: MCP auto-configuration failed. Configure manually in the terminal:"
|
||||
echo "WARN: mcporter config add HA \"$MCP_HA_URL\" --header \"Authorization=Bearer YOUR_TOKEN\" --scope home"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "INFO: mcporter not available; skipping MCP auto-configuration (run 'openclaw onboard' first)"
|
||||
fi
|
||||
elif [ "$AUTO_CONFIGURE_MCP" = "true" ] && [ -z "$HA_TOKEN" ]; then
|
||||
echo "INFO: MCP auto-configure enabled but homeassistant_token not set — skipping"
|
||||
echo "INFO: To auto-configure, set homeassistant_token in add-on Configuration, then restart"
|
||||
fi
|
||||
|
||||
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)"
|
||||
# Use $GATEWAY_REMOTE_URL directly from add-on options — do NOT read back via
|
||||
# 'openclaw config get' which can time out at startup or return redacted values.
|
||||
REMOTE_URL="$GATEWAY_REMOTE_URL"
|
||||
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"
|
||||
echo "ERROR: gateway_mode=remote but gateway_remote_url is not set in add-on options"
|
||||
echo "ERROR: Set gateway_remote_url in add-on Configuration (e.g. ws://192.168.1.10:18789), then restart"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -748,6 +801,37 @@ if ! start_openclaw_runtime; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Loopback relay for tailnet bind mode (issue #90) ---
|
||||
# When gateway.bind=tailnet the gateway only listens on the Tailscale IP.
|
||||
# The local CLI always tries ws://127.0.0.1:PORT and fails with
|
||||
# "Gateway not running" even though the gateway is healthy.
|
||||
# A lightweight Node.js relay (loopback-only) forwards those connections
|
||||
# to the Tailscale IP so terminal CLI commands work normally.
|
||||
# Token auth is still enforced end-to-end by the gateway.
|
||||
if [ "$GATEWAY_BIND_MODE" = "tailnet" ]; then
|
||||
TAILSCALE_IP=$(ip -4 addr show tailscale0 2>/dev/null \
|
||||
| awk '/inet /{gsub(/\/.*/,"",$2); print $2; exit}' || true)
|
||||
if [[ "${TAILSCALE_IP:-}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "INFO: Starting loopback relay for tailnet gateway (127.0.0.1:${GATEWAY_PORT} -> ${TAILSCALE_IP}:${GATEWAY_PORT})"
|
||||
node -e "
|
||||
const net = require('net');
|
||||
const TARGET_HOST = '${TAILSCALE_IP}';
|
||||
const TARGET_PORT = ${GATEWAY_PORT};
|
||||
const server = net.createServer(function(c) {
|
||||
const t = net.createConnection(TARGET_PORT, TARGET_HOST);
|
||||
c.pipe(t); t.pipe(c);
|
||||
c.on('error', function() { t.destroy(); });
|
||||
t.on('error', function() { c.destroy(); });
|
||||
});
|
||||
server.listen(TARGET_PORT, '127.0.0.1');" &
|
||||
GW_RELAY_PID=$!
|
||||
echo "INFO: Loopback relay started (PID ${GW_RELAY_PID})"
|
||||
else
|
||||
echo "WARN: tailnet bind mode active but Tailscale IP not found on tailscale0 interface."
|
||||
echo "WARN: Terminal CLI may show gateway as unreachable. Ensure Tailscale is running and restart."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start web terminal (optional)
|
||||
TTYD_PID_FILE="/var/run/openclaw-ttyd.pid"
|
||||
|
||||
@@ -868,6 +952,19 @@ while true; do
|
||||
break
|
||||
fi
|
||||
|
||||
# Detect agent/user-initiated self-restart (e.g. 'openclaw gateway restart').
|
||||
# When the gateway restarts itself, the old PID exits but a new process immediately
|
||||
# binds the same port. Without this check the supervisor would spawn a second
|
||||
# instance, which fails with "already listening" and loops forever.
|
||||
# Give the new process a moment to start, then re-track it instead of spawning a duplicate.
|
||||
sleep 1
|
||||
RESTARTED_PID=$(pgrep -f "openclaw.*(gateway|node).*run" 2>/dev/null | head -1 || true)
|
||||
if [ -n "$RESTARTED_PID" ] && [ "$RESTARTED_PID" != "$GW_PID" ]; then
|
||||
echo "INFO: OpenClaw runtime restarted itself (new PID $RESTARTED_PID); re-tracking."
|
||||
GW_PID="$RESTARTED_PID"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "WARN: OpenClaw runtime exited with code ${GW_EXIT_CODE}. Restarting in 2s..."
|
||||
sleep 2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user