mirror of
https://github.com/techartdev/OpenClawHomeAssistant.git
synced 2026-08-14 08:52:15 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0a0cc3f23 | ||
|
|
00df877305 | ||
|
|
a037d8311b | ||
|
|
beb59e8eb6 | ||
|
|
a6af4b78ac | ||
|
|
f658885c59 | ||
|
|
ceb9522648 | ||
|
|
ca2c7cab82 | ||
|
|
99be0ed6e6 | ||
|
|
ec825fa8c5 | ||
|
|
971e4bc8e0 | ||
|
|
c3a926299f | ||
|
|
883f5b8778 | ||
|
|
7608357297 | ||
|
|
9235903ef2 | ||
|
|
d5f3831131 | ||
|
|
b45e665bca | ||
|
|
0b799f5fb1 | ||
|
|
6129b62512 | ||
|
|
fe082864ec | ||
|
|
4d8fa8996a | ||
|
|
45d4066d2a | ||
|
|
28574b28ef | ||
|
|
089594a95b | ||
|
|
08bc68b9e7 | ||
|
|
3a06c4604f | ||
|
|
661085afc3 | ||
|
|
ccde8598d7 | ||
|
|
80f86ae030 | ||
|
|
082c2c74b2 | ||
|
|
d070dbe1e1 | ||
|
|
9824f7c138 | ||
|
|
71e3fbe410 | ||
|
|
c2398c84a4 | ||
|
|
3e5e0877b1 | ||
|
|
c769cb88e1 | ||
|
|
865ca08873 | ||
|
|
f0a465bf7c | ||
|
|
80ef635315 | ||
|
|
2acfc77a3f | ||
|
|
91a2ddd01f | ||
|
|
f66c574611 | ||
|
|
57a8bf3868 | ||
|
|
2b6c875b0e | ||
|
|
ff5e1eba1e | ||
|
|
9a65e66b75 | ||
|
|
9b7b701d5b | ||
|
|
505115e06f | ||
|
|
72495984ad | ||
|
|
ba8fd59571 | ||
|
|
dab02d2e22 | ||
|
|
38e104d663 | ||
|
|
991cc3bcfc | ||
|
|
1a4e23e8c3 | ||
|
|
4f56a16b91 | ||
|
|
6a08c66128 | ||
|
|
02814db080 | ||
|
|
dd6b7eca6c | ||
|
|
c6232b6f5d | ||
|
|
e6cad868ab | ||
|
|
8f91cdc23e |
@@ -43,10 +43,10 @@ When you open the add-on page in Home Assistant, nginx serves a landing page wit
|
||||
| `/config/` | Yes | All user data — survives add-on updates and rebuilds |
|
||||
| `/config/.openclaw/` | Yes | OpenClaw configuration (`openclaw.json`), skills, agent data |
|
||||
| `/config/clawd/` | Yes | Agent workspace (ClawHub-installed skills, files) |
|
||||
| `/config/.node_global/` | Yes | User-installed npm packages (skills installed via dashboard) |
|
||||
| `/config/.node_global/` | Optional | User-installed npm packages when `persist_node_global=true` |
|
||||
| `/config/secrets/` | Yes | Tokens (e.g., `homeassistant.token`) |
|
||||
| `/config/keys/` | Yes | SSH keys (e.g., router SSH key) |
|
||||
| `/config/.linuxbrew/` | Yes | Homebrew install and brew-installed CLI tools |
|
||||
| `/config/.linuxbrew/` | Optional | Homebrew install and brew-installed CLI tools when `persist_brew_tools=true` |
|
||||
| `/config/gogcli/` | Yes | gog OAuth credentials for Google APIs |
|
||||
| `/usr/lib/node_modules/openclaw/` | No | OpenClaw installation (rebuilt with each image update) |
|
||||
|
||||
@@ -113,8 +113,9 @@ Save this token — you'll need it to access the Gateway Web UI and for API inte
|
||||
|
||||
1. In the terminal, confirm the gateway is running:
|
||||
```sh
|
||||
openclaw gateway status
|
||||
oc-gateway status
|
||||
```
|
||||
> In this add-on, the gateway is supervised by `run.sh` (not systemd), so `openclaw gateway status` can show misleading service-manager warnings.
|
||||
2. Click the **Open Gateway Web UI** button on the landing page
|
||||
3. If prompted for a token, paste the one from Step 2 or go to the Overview tab, paste the token in the 'Gateway Token' field and press Connect.
|
||||
|
||||
@@ -314,7 +315,9 @@ To provide the SSH key: place the private key file in the add-on config director
|
||||
|---|---|---|---|
|
||||
| `clean_session_locks_on_start` | bool | `true` | Remove stale session lock files on startup (safe — only removes locks when gateway isn't running) |
|
||||
| `clean_session_locks_on_exit` | bool | `true` | Remove session lock files on clean shutdown |
|
||||
|
||||
| `persist_node_global` | bool | `false` | Persist user-installed npm global skills/packages in `/config/.node_global/`. Turn on only if you want those installs to survive add-on rebuilds. |
|
||||
| `persist_brew_tools` | bool | `false` | Persist Homebrew and brew-installed CLI tools in `/config/.linuxbrew/`. Turn on only if you want those installs to survive add-on rebuilds. |
|
||||
| `auto_configure_mcp` | bool | `false` | Auto-register Home Assistant as an MCP server on startup (requires `homeassistant_token`) |
|
||||
---
|
||||
|
||||
## 6. Use Case Guides
|
||||
@@ -480,6 +483,71 @@ You can now use Assist (voice or text) and OpenClaw will handle conversations, c
|
||||
|
||||
The add-on includes **Chromium** for browser-based automation tasks. OpenClaw can use it for web scraping, form filling, website testing, and other browser automation skills.
|
||||
|
||||
### 6d-mcp. MCP Integration (Home Assistant Control)
|
||||
|
||||
The **Model Context Protocol (MCP)** lets OpenClaw communicate directly with Home Assistant — reading entity states, calling services, creating automations, and more. This is the recommended way to give OpenClaw full control over your smart home.
|
||||
|
||||
#### Automatic setup (recommended)
|
||||
|
||||
1. Create a **long-lived access token** in Home Assistant:
|
||||
- Go to your HA profile page (click your user avatar at the bottom of the sidebar)
|
||||
- Scroll to **Long-Lived Access Tokens** → **Create Token**
|
||||
- Give it a name (e.g. "OpenClaw") and copy the token
|
||||
2. Paste the token into the add-on option **Home Assistant Token** (`homeassistant_token`) in **Settings → Add-ons → OpenClaw Assistant → Configuration**
|
||||
3. Set **Auto-Configure MCP for Home Assistant** (`auto_configure_mcp`) to **ON**
|
||||
4. Restart the add-on
|
||||
|
||||
The add-on bundles `mcporter` and automatically registers Home Assistant as an MCP server named `HA`. It auto-detects the HA API URL (supervisor proxy when available, otherwise `localhost:8123`). Check the logs for:
|
||||
```
|
||||
INFO: MCP server 'HA' registered — OpenClaw can now control Home Assistant
|
||||
```
|
||||
|
||||
On subsequent restarts, the configuration is skipped if the token hasn’t changed.
|
||||
|
||||
#### Manual setup
|
||||
|
||||
If you prefer to configure MCP manually (or `auto_configure_mcp` is off), run this in the add-on terminal:
|
||||
|
||||
```sh
|
||||
mcporter config add HA "http://localhost:8123/api/mcp" \
|
||||
--header "Authorization=Bearer YOUR_LONG_LIVED_TOKEN" \
|
||||
--scope home
|
||||
```
|
||||
|
||||
Replace `YOUR_LONG_LIVED_TOKEN` with your HA long-lived access token.
|
||||
|
||||
#### Verifying MCP works
|
||||
|
||||
After setup, ask OpenClaw something like:
|
||||
- _"Turn off the living room lights"_
|
||||
- _"What’s the temperature of the bedroom sensor?"_
|
||||
- _"List all entities in the kitchen"_
|
||||
|
||||
If OpenClaw can execute HA actions, MCP is working.
|
||||
|
||||
#### Refreshing HA context after upgrades
|
||||
|
||||
If OpenClaw has stale or missing Home Assistant data after an upgrade, run:
|
||||
|
||||
```sh
|
||||
mcporter call home-assistant.GetLiveContext
|
||||
```
|
||||
|
||||
This refreshes the entity/service metadata that OpenClaw uses.
|
||||
|
||||
#### Model requirements
|
||||
|
||||
MCP setup requires an AI model that understands tool/skill invocation. Budget models (e.g. Gemini 2.5 Flash) may struggle with the initial MCP discovery. For the **first-time setup**, use a capable model (e.g. Gemini 3.1 Pro, Claude Sonnet 4, GPT-4.1). After MCP is configured, you can switch back to a cheaper model for daily use.
|
||||
|
||||
#### Troubleshooting MCP
|
||||
|
||||
| Symptom | Fix |
|
||||
|---|---|
|
||||
| `mcporter: command not found` | Upgrade to add-on `0.5.84` or newer. Older builds did not bundle `mcporter`; as a temporary workaround, install it manually in the add-on terminal and restart. |
|
||||
| MCP add fails with auth error | Verify your long-lived token is valid and not expired |
|
||||
| OpenClaw doesn’t see HA entities | Run `mcporter call home-assistant.GetLiveContext` to refresh |
|
||||
| Model says “what’s MCP?” | Switch to a more capable model for the initial session (see above) |
|
||||
|
||||
To enable it, add to `/config/.openclaw/openclaw.json`:
|
||||
|
||||
```json
|
||||
@@ -583,10 +651,10 @@ You should see your account listed with the `sheets` service.
|
||||
| Built-in skills | `/config/.openclaw/skills/` | Yes |
|
||||
| Agent sessions & data | `/config/.openclaw/agents/` | Yes |
|
||||
| ClawHub workspace | `/config/clawd/` | Yes |
|
||||
| User-installed npm skills | `/config/.node_global/` | Yes |
|
||||
| User-installed npm skills | `/config/.node_global/` | Optional (`persist_node_global=true`) |
|
||||
| SSH keys | `/config/keys/` | Yes |
|
||||
| Tokens | `/config/secrets/` | Yes |
|
||||
| Homebrew & brew-installed tools | `/config/.linuxbrew/` | Yes (synced on startup) |
|
||||
| Homebrew & brew-installed tools | `/config/.linuxbrew/` | Optional (`persist_brew_tools=true`) |
|
||||
| gog OAuth credentials | `/config/gogcli/` | Yes |
|
||||
| TLS certificates (lan_https) | `/config/certs/` | Yes (CA persists; server cert regenerated if IP changes) |
|
||||
| OpenClaw binary | `/usr/lib/node_modules/openclaw/` | **No** — reinstalled from image |
|
||||
@@ -603,19 +671,21 @@ This means built-in skills survive image rebuilds, and any customizations you ma
|
||||
|
||||
### How user-installed skills work
|
||||
|
||||
When you install a skill via the OpenClaw dashboard or `npm install -g`, the add-on redirects global npm installs to `/config/.node_global/`. This directory persists across updates.
|
||||
By default, user-installed npm skills and global packages are **ephemeral** to keep Home Assistant backups small.
|
||||
|
||||
The add-on also configures `pnpm` global directory to persistent storage at `/config/.node_global/pnpm/`.
|
||||
If you enable `persist_node_global`, the add-on redirects global npm installs to `/config/.node_global/` so dashboard-installed skills survive add-on rebuilds. The add-on also configures the pnpm global directory at `/config/.node_global/pnpm/` in that mode.
|
||||
|
||||
### Homebrew-installed tools
|
||||
|
||||
Homebrew (Linuxbrew) and all brew-installed CLI tools (e.g., `gemini`, `aider`, `gh`, `bw`) are now **persisted** across add-on updates. On each startup, the add-on:
|
||||
By default, Homebrew (Linuxbrew) and brew-installed CLI tools are **ephemeral** to keep Home Assistant backups small.
|
||||
|
||||
If you enable `persist_brew_tools`, the add-on:
|
||||
|
||||
1. Syncs the image's Homebrew install to `/config/.linuxbrew/`
|
||||
2. Creates a symlink from `/home/linuxbrew/.linuxbrew/` to the persistent copy
|
||||
3. On subsequent boots, only newer files are synced (user-installed packages are preserved)
|
||||
|
||||
This means `brew install` packages survive image rebuilds.
|
||||
This means `brew install` packages survive image rebuilds only when persistence is enabled.
|
||||
|
||||
---
|
||||
|
||||
@@ -641,6 +711,7 @@ The add-on image includes these tools, available in the terminal:
|
||||
| Chromium | `chromium` | Headless browser for automation |
|
||||
| SSH | `ssh` | Remote access |
|
||||
| oc-cleanup | `oc-cleanup` | Interactive disk space monitor & cache cleanup helper |
|
||||
| oc-gateway | `oc-gateway status` / `oc-gateway restart` | Add-on-native gateway status/restart helper (`run.sh` supervised, no systemd) |
|
||||
|
||||
### oc-cleanup
|
||||
|
||||
@@ -676,7 +747,7 @@ Home Assistant checks for add-on updates automatically. When an update is availa
|
||||
**What happens during an update**:
|
||||
- The container is destroyed and recreated from the new image
|
||||
- Everything under `/config/` is preserved (config, skills, workspace, keys)
|
||||
- Homebrew and brew-installed packages are preserved (synced to `/config/.linuxbrew/`)
|
||||
- Homebrew and npm global packages are preserved only if you explicitly enable `persist_brew_tools` / `persist_node_global`
|
||||
- The OpenClaw binary is updated to the version in the new image
|
||||
|
||||
### Checking your version
|
||||
@@ -689,7 +760,20 @@ openclaw --version
|
||||
|
||||
### Backup
|
||||
|
||||
Home Assistant's built-in backup system automatically includes add-on configuration data (`/config/`). This covers all persistent data: OpenClaw config, skills, workspace, keys, and tokens.
|
||||
Home Assistant's built-in backup system automatically includes add-on configuration data (`/config/`). By default this covers the important user state: OpenClaw config, skills, workspace, keys, and tokens — without large optional toolchains.
|
||||
|
||||
### Backup-friendly defaults (v0.5.75+)
|
||||
|
||||
Starting with v0.5.75, the add-on keeps large optional toolchains out of backups by default:
|
||||
|
||||
- `persist_node_global: false` → `/config/.node_global/` is not used unless you opt in
|
||||
- `persist_brew_tools: false` → `/config/.linuxbrew/` is not used unless you opt in
|
||||
|
||||
Turn these on only if you specifically want user-installed npm global skills or brew-installed CLI tools to survive add-on rebuilds.
|
||||
|
||||
### Migration note for older installs
|
||||
|
||||
If you used an older add-on version, you may already have legacy directories such as `/config/.node_global/` or `/config/.linuxbrew/` from previous persistent behavior. Disabling persistence stops future growth, but those directories still count toward backup size until you remove or archive them manually.
|
||||
|
||||
**To create a backup**: Go to **Settings → System → Backups → Create Backup**
|
||||
|
||||
@@ -698,7 +782,8 @@ Home Assistant's built-in backup system automatically includes add-on configurat
|
||||
# Key paths to back up:
|
||||
# /config/.openclaw/ - OpenClaw config, skills, agent data
|
||||
# /config/clawd/ - ClawHub workspace
|
||||
# /config/.node_global/ - User-installed npm skills
|
||||
# /config/.node_global/ - User-installed npm skills (only if persist_node_global=true)
|
||||
# /config/.linuxbrew/ - Homebrew tools (only if persist_brew_tools=true)
|
||||
# /config/keys/ - SSH keys
|
||||
# /config/secrets/ - Tokens
|
||||
```
|
||||
@@ -708,12 +793,12 @@ Home Assistant's built-in backup system automatically includes add-on configurat
|
||||
To reset the add-on to a clean state, remove the persistent data:
|
||||
|
||||
```sh
|
||||
rm -rf /config/.openclaw /config/clawd /config/.node_global
|
||||
rm -rf /config/.openclaw /config/clawd /config/.node_global /config/.linuxbrew
|
||||
```
|
||||
|
||||
Then restart the add-on. It will re-bootstrap a fresh configuration.
|
||||
|
||||
> **Warning**: This deletes all your OpenClaw configuration, skills, and workspace data. Back up first if needed.
|
||||
> **Warning**: This deletes all your OpenClaw configuration, skills, workspace data, and any optionally persisted tool installations. Back up first if needed.
|
||||
|
||||
---
|
||||
|
||||
@@ -742,11 +827,23 @@ Go to **Settings → Add-ons → OpenClaw Assistant → Log** tab. Logs show sta
|
||||
**Symptom**: Browser shows connection refused when opening the Gateway Web UI.
|
||||
|
||||
**Checks**:
|
||||
1. Is the gateway running? In the terminal: `openclaw gateway status`
|
||||
1. Is the gateway running? In the terminal: `oc-gateway status`
|
||||
2. Is the bind mode correct? `openclaw config get gateway.bind` — must be `lan` for direct LAN access, or `loopback` if using `lan_https` mode
|
||||
3. Is the port correct? `openclaw config get gateway.port`
|
||||
4. Is the firewall blocking the port? Check your HA host firewall rules
|
||||
|
||||
### Gateway restart loop: `web_search provider is not available: brave`
|
||||
|
||||
**Symptom**: Logs repeat `Invalid config at /config/.openclaw/openclaw.json` and `tools.web.search.provider: web_search provider is not available: brave`.
|
||||
|
||||
**Cause**: The persisted OpenClaw config selects the Brave web search provider, but that provider plugin is not currently installed or enabled in the add-on runtime.
|
||||
|
||||
**Fix**: In v0.5.72+ the add-on clears that unavailable provider automatically during startup. On older versions, run this in the add-on terminal, then restart:
|
||||
|
||||
```sh
|
||||
jq 'del(.tools.web.search.provider)' /config/.openclaw/openclaw.json > /tmp/openclaw.json && mv /tmp/openclaw.json /config/.openclaw/openclaw.json
|
||||
```
|
||||
|
||||
### "disconnected (1008): control ui requires device identity" / "requires HTTPS or localhost"
|
||||
|
||||
**Symptom**: Gateway UI shows error 1008 or "requires secure context / device identity".
|
||||
@@ -796,7 +893,7 @@ Go to **Settings → Add-ons → OpenClaw Assistant → Log** tab. Logs show sta
|
||||
"allowedOrigins": ["https://YOUR_IP:18789"]
|
||||
}
|
||||
```
|
||||
Then restart the gateway: `openclaw gateway restart`
|
||||
Then restart only the gateway process: `oc-gateway restart`
|
||||
3. Alternatively, approve devices individually without disabling auth:
|
||||
```sh
|
||||
openclaw devices list # show pending pairing requests
|
||||
@@ -870,7 +967,7 @@ Built-in skills are synced to persistent storage on each startup. If skills are
|
||||
|
||||
1. Check logs for `INFO: Synced built-in skills to persistent storage` — this confirms the sync ran
|
||||
2. If you see `WARN: Built-in skills directory not found`, the OpenClaw installation may be corrupted. Try reinstalling the add-on.
|
||||
3. User-installed skills (via dashboard) are stored in `/config/.node_global/` and should survive updates
|
||||
3. User-installed skills (via dashboard) survive updates only when `persist_node_global` is enabled. With the default backup-friendly setting (`false`), they are ephemeral and may need to be reinstalled after an add-on rebuild.
|
||||
|
||||
### Homebrew errors / CPU compatibility
|
||||
|
||||
|
||||
@@ -2,7 +2,153 @@
|
||||
|
||||
All notable changes to the OpenClaw Assistant Home Assistant Add-on will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
## [0.5.87] - 2026-08-10
|
||||
|
||||
### Fixed
|
||||
- Correct the bundled OpenClaw npm package version in the Docker image build for add-on `0.5.86`, fixing failed installs that requested the nonexistent `openclaw@2026.7.1-2-2`.
|
||||
|
||||
## [0.5.85] - 2026-07-21
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to `2026.7.1-2`.
|
||||
|
||||
## [0.5.84] - 2026-07-17
|
||||
|
||||
### Changed
|
||||
- Bundle `mcporter@0.12.3` in the add-on image so `auto_configure_mcp` can register Home Assistant out of the box on fresh installs without a manual global install workaround.
|
||||
- Replace the misleading startup hint that told users to run `openclaw onboard` when `mcporter` was missing. The message now correctly points to a broken image state instead.
|
||||
|
||||
## [0.5.82] - 2026-07-15
|
||||
|
||||
### Fixed
|
||||
- Repair add-on startup automatically when the bundled OpenClaw CLI is older than the persisted `/config/.openclaw/openclaw.json` format version. On mismatch, the add-on now restores the newer runtime before launching the gateway instead of silently coming up broken after a Home Assistant OS update or add-on rebuild.
|
||||
- Regenerate malformed `lan_https` CA/server certificates with proper X.509 extensions (`basicConstraints`, `keyUsage`, `extendedKeyUsage`) so Python/OpenSSL strict verification accepts the built-in HTTPS proxy certificates.
|
||||
|
||||
## [0.5.81] - 2026-07-14
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to `2026.7.1`.
|
||||
|
||||
## [0.5.80] - 2026-06-26
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to `2026.6.10`.
|
||||
|
||||
## [0.5.78] - 2026-06-16
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw through the `2026.5.28` and `2026.6.6` upstream releases.
|
||||
|
||||
## [0.5.76] - 2026-05-29
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to `2026.5.27`.
|
||||
|
||||
## [0.5.75] - 2026-05-28
|
||||
|
||||
### Changed
|
||||
- **Backup-friendly persistence defaults**: new add-on options `persist_node_global` and `persist_brew_tools`, both defaulting to `false` so large optional toolchains are no longer persisted into Home Assistant backups unless users explicitly opt in.
|
||||
- `run.sh` now keeps npm global installs and Homebrew ephemeral by default, while preserving the old rebuild-survival behavior when the new toggles are enabled.
|
||||
|
||||
### Added
|
||||
- Migration notes and documentation for older installs that already have legacy `/config/.node_global/` or `/config/.linuxbrew/` directories contributing to backup size.
|
||||
|
||||
## [0.5.74] - 2026-05-27
|
||||
|
||||
### Fixed
|
||||
- Bundle `node-llama-cpp` inside the add-on image so the default local memory/embeddings provider works in HAOS without manual package installs.
|
||||
- Add `cmake` to the image so `node-llama-cpp` can fall back to a source build when a prebuilt binary is unavailable for the target architecture.
|
||||
|
||||
## [0.5.73] - 2026-05-26
|
||||
|
||||
### Added
|
||||
- New add-on-native `oc-gateway` helper for container-supervised runtime management:
|
||||
- `oc-gateway status` shows gateway state in the HA add-on model (`run.sh` supervisor, not systemd)
|
||||
- `oc-gateway restart` requests gateway self-restart via `SIGUSR1` without full add-on restart
|
||||
|
||||
### Changed
|
||||
- Troubleshooting and setup docs now use `oc-gateway status` / `oc-gateway restart` in add-on contexts to avoid confusing systemd-related CLI output.
|
||||
|
||||
## [0.5.72] - 2026-05-04
|
||||
|
||||
### Fixed
|
||||
- Repair startup when a persisted OpenClaw config still selects the unavailable `tools.web.search.provider=brave` provider. The add-on now clears that provider before launching the gateway so OpenClaw can start; users can reinstall/enable the Brave provider later if they want web search through Brave.
|
||||
|
||||
## [0.5.71] - 2026-05-03
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw through the 2026.4.29 and 2026.5.2 upstream releases.
|
||||
|
||||
## [0.5.70] - 2026-04-30
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to 2026.4.27.
|
||||
|
||||
## [0.5.69] - 2026-04-27
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw through the 2026.4.23 and 2026.4.24 upstream releases.
|
||||
|
||||
## [0.5.68] - 2026-04-25
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw through the 2026.4.14, 2026.4.15, 2026.4.21, and 2026.4.22 upstream releases.
|
||||
|
||||
## [0.5.67] - 2026-04-25
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw through the 2026.4.5, 2026.4.8, 2026.4.9, 2026.4.10, 2026.4.11, and 2026.4.12 upstream releases.
|
||||
|
||||
## [0.5.66] - 2026-04-04
|
||||
|
||||
### Fixed
|
||||
- **"Open Gateway Web UI" button missing token on first boot / post-onboard** (issue #102): the gateway token was read once at startup, before `openclaw onboard` had a chance to write `openclaw.json`. The landing page now re-renders automatically in the background (up to ~2 min after startup) once the token appears in `openclaw.json`, and nginx is reloaded with SIGHUP — no add-on restart required. Existing installs with a token already present are unaffected.
|
||||
|
||||
## [0.5.65] - 2026-04-04
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to 2026.4.2.
|
||||
|
||||
## [0.5.63] - 2026-03-14
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to 2026.3.13.
|
||||
|
||||
## [0.5.62] - 2026-03-10
|
||||
|
||||
### Fixed
|
||||
- **Gateway restart loop** (issue #95): `openclaw gateway run` is a thin wrapper that spawns `openclaw-gateway` as a long-running daemon then exits immediately. On self-restart (SIGUSR1 / `openclaw gateway restart`), the old daemon forks a new one and exits — the new PID is not a child of run.sh. The supervisor now uses a 3-tier daemon detection function (`find_gateway_daemon_pid`): (1) port ownership via `ss -tlnp`, (2) process title via `pgrep -f "openclaw-gateway"`, (3) `/proc/*/cmdline` scan for "openclaw" (catches the daemon immediately after fork, even before process.title or port bind — critical on Pi/eMMC where initialization takes 20-30 s). Detection retries up to 10 times with a final port-occupancy guard before any supervisor-initiated restart. Non-child PIDs are monitored with `kill -0` polling instead of `wait`. The loopback relay (tailnet mode) is stopped/restarted around gateway restarts to prevent port conflicts.
|
||||
|
||||
## [0.5.61] - 2026-03-10
|
||||
|
||||
### Fixed
|
||||
- **Gateway restart loop** (issue #95): stop the tailnet loopback relay before supervisor-initiated gateway restarts and start it again after the new daemon is launched, preventing the relay from holding the local port and trapping the add-on in an `already listening` restart loop.
|
||||
|
||||
## [0.5.60] - 2026-03-10
|
||||
|
||||
### Fixed
|
||||
- **Session lock cleanup ignored non-default agents**: `cleanup_session_locks` was hardcoded to `agents/main/sessions`, skipping stale locks for any agent with a custom `forcedAgentId`. Stale locks could block the gateway from opening sessions for those agents, causing silent fallback to `main`. Cleanup now scans all `agents/*/sessions/` directories.
|
||||
|
||||
## [0.5.59] - 2026-03-10
|
||||
|
||||
- **Remote mode URL not propagated** (issue #93): `start_openclaw_runtime` was reading `gateway.remote.url` back via `openclaw config get`, which can time out (2 s limit at startup) or return an empty/redacted result. The function now uses `$GATEWAY_REMOTE_URL` directly from the already-parsed add-on options, which is the same value the config helper writes to `openclaw.json`.
|
||||
- **Terminal CLI unreachable in tailnet mode** (issue #90): when `gateway_bind_mode=tailnet` (or `access_mode=tailnet_https`), the gateway binds only to the Tailscale IP. The local CLI always connects via `ws://127.0.0.1:PORT`, causing "Gateway not running" inside the add-on terminal. A lightweight loopback relay (Node.js) is now started automatically to forward `127.0.0.1:PORT → TAILSCALE_IP:PORT`, making all terminal CLI commands work normally. Token auth is still enforced end-to-end by the gateway.
|
||||
- **Session lock cleanup ignored non-default agents**: `cleanup_session_locks` was hardcoded to `agents/main/sessions`, skipping stale locks for any agent with a custom `forcedAgentId`. Stale locks could block the gateway from opening sessions for those agents, causing silent fallback to `main`. Cleanup now scans all `agents/*/sessions/` directories.
|
||||
|
||||
### Added
|
||||
- **MCP auto-configuration for Home Assistant**: new option `auto_configure_mcp` (default: `false`). When enabled and `homeassistant_token` is set, the add-on automatically registers Home Assistant as an MCP server (`mcporter config add HA ...`) on startup. Auto-detects the HA API URL (supervisor proxy or localhost:8123). Re-configures only when the token changes.
|
||||
- Landing page: new collapsible **MCP setup** section with automatic and manual setup instructions, post-upgrade refresh command, and model tips.
|
||||
- DOCS: new **MCP Integration** guide covering automatic/manual setup, verification, model requirements, and troubleshooting.
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to 2026.3.9.
|
||||
|
||||
## [0.5.58] - 2026-03-08
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to 2026.3.7.
|
||||
|
||||
## [0.5.57] - 2026-03-07
|
||||
|
||||
### Added
|
||||
- New add-on option `controlui_disable_device_auth` (default: `true`) to control whether `gateway.controlUi.dangerouslyDisableDeviceAuth` is enabled in `lan_https` mode.
|
||||
@@ -12,11 +158,16 @@ All notable changes to the OpenClaw Assistant Home Assistant Add-on will be docu
|
||||
- `run.sh` now forwards the add-on option to the config helper.
|
||||
- Control UI guidance text and docs were updated to explain when device-pairing bypass should be ON vs OFF.
|
||||
|
||||
### Fixed
|
||||
- Docker build stability: replaced NodeSource `setup_22.x | bash` installer with explicit keyring + apt source configuration for Node.js 22, avoiding intermittent `apt-get install nodejs` exit code 100 failures.
|
||||
|
||||
### Translations
|
||||
- Added `controlui_disable_device_auth` labels/descriptions to: `en`, `bg`, `de`, `es`, `pl`, `pt-BR`.
|
||||
|
||||
### Fixed
|
||||
- Docker build stability: replaced NodeSource `setup_22.x | bash` installer with explicit keyring + apt source configuration for Node.js 22, avoiding intermittent `apt-get install nodejs` exit code 100 failures.
|
||||
## [0.5.55] - 2026-03-04
|
||||
|
||||
### Changed
|
||||
- Bump OpenClaw to 2026.3.2.
|
||||
|
||||
## [0.5.54] - 2026-02-25
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ FROM ${BUILD_FROM}
|
||||
# Base image is Debian Bookworm (glibc). This avoids musl-related native module issues
|
||||
# that occur on Alpine (e.g. clipboard, node-llama-cpp).
|
||||
|
||||
# Install base packages (without nodejs/npm - we'll get Node 22 from NodeSource)
|
||||
# Install base packages (without nodejs/npm - we'll get Node 24 from NodeSource)
|
||||
# build-essential provides gcc/cc needed by Homebrew for OpenClaw's brew-managed dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
@@ -18,6 +18,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
xz-utils \
|
||||
file \
|
||||
python3 \
|
||||
cmake \
|
||||
nginx \
|
||||
gnupg \
|
||||
build-essential \
|
||||
@@ -30,33 +31,35 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bat \
|
||||
less \
|
||||
openssl \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Node.js 22 LTS from NodeSource (Debian Bookworm ships Node 18, but OpenClaw requires 20+)
|
||||
# Use explicit keyring + apt source instead of setup_22.x pipe script for deterministic builds.
|
||||
# Install Node.js 24 LTS from NodeSource.
|
||||
# OpenClaw supports Node >=24.15.0, and this keeps the image compatible with the
|
||||
# current mcporter releases without relying on an older pinned CLI line.
|
||||
# Use explicit keyring + apt source instead of setup_24.x pipe script for deterministic builds.
|
||||
RUN mkdir -p /etc/apt/keyrings \
|
||||
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
|
||||
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
|
||||
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||
&& chmod 644 /etc/apt/keyrings/nodesource.gpg \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \
|
||||
&& chmod 644 /etc/apt/keyrings/nodesource.gpg \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" \
|
||||
> /etc/apt/sources.list.d/nodesource.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& NODE_VERSION="$(node -v)" \
|
||||
&& echo "$NODE_VERSION" \
|
||||
&& echo "$NODE_VERSION" | grep -E '^v22\.' \
|
||||
&& rm -f /etc/apt/sources.list.d/nodesource.list /etc/apt/keyrings/nodesource.gpg \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& NODE_VERSION="$(node -v)" \
|
||||
&& echo "$NODE_VERSION" \
|
||||
&& echo "$NODE_VERSION" | grep -E '^v24\.' \
|
||||
&& rm -f /etc/apt/sources.list.d/nodesource.list /etc/apt/keyrings/nodesource.gpg \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install ttyd (web terminal) - not in Debian repos, download binary
|
||||
# Docker TARGETARCH: amd64, arm64, arm/v7 → ttyd filenames: x86_64, aarch64, armhf
|
||||
ARG TARGETARCH
|
||||
RUN ARCH=$(echo ${TARGETARCH:-$(dpkg --print-architecture)} | sed 's|arm64|aarch64|;s|arm/v7|armhf|;s|armv7|armhf|;s|amd64|x86_64|') \
|
||||
&& echo "Downloading ttyd for arch: ${ARCH}" \
|
||||
&& curl -fsSL "https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.${ARCH}" -o /usr/local/bin/ttyd \
|
||||
&& chmod +x /usr/local/bin/ttyd
|
||||
&& echo "Downloading ttyd for arch: ${ARCH}" \
|
||||
&& curl -fsSL "https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.${ARCH}" -o /usr/local/bin/ttyd \
|
||||
&& chmod +x /usr/local/bin/ttyd
|
||||
|
||||
RUN node -v && npm -v
|
||||
|
||||
@@ -68,8 +71,8 @@ RUN npm install -g pnpm && pnpm -v
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
chromium \
|
||||
chromium-driver \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Homebrew (Linuxbrew) for OpenClaw skill dependencies
|
||||
# Homebrew is optional - some skills need CLI tools like gemini, aider, etc.
|
||||
@@ -80,16 +83,16 @@ ENV HOMEBREW_NO_AUTO_UPDATE=1 \
|
||||
HOMEBREW_NO_ANALYTICS=1
|
||||
|
||||
RUN useradd -m -s /bin/bash linuxbrew \
|
||||
&& mkdir -p /home/linuxbrew/.linuxbrew \
|
||||
&& chown -R linuxbrew:linuxbrew /home/linuxbrew
|
||||
&& mkdir -p /home/linuxbrew/.linuxbrew \
|
||||
&& chown -R linuxbrew:linuxbrew /home/linuxbrew
|
||||
|
||||
USER linuxbrew
|
||||
RUN /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || \
|
||||
(echo "WARNING: Homebrew installation failed (likely unsupported CPU - requires SSSE3). Some skills may not work." && exit 0)
|
||||
RUN if [ -d /home/linuxbrew/.linuxbrew/Homebrew ]; then \
|
||||
cd /home/linuxbrew/.linuxbrew/Homebrew && \
|
||||
git config --global --add safe.directory /home/linuxbrew/.linuxbrew/Homebrew && \
|
||||
/home/linuxbrew/.linuxbrew/bin/brew update --force || true; \
|
||||
cd /home/linuxbrew/.linuxbrew/Homebrew && \
|
||||
git config --global --add safe.directory /home/linuxbrew/.linuxbrew/Homebrew && \
|
||||
/home/linuxbrew/.linuxbrew/bin/brew update --force || true; \
|
||||
fi
|
||||
USER root
|
||||
|
||||
@@ -105,16 +108,20 @@ RUN chmod +x /usr/local/bin/brew
|
||||
# Skip if brew installation failed
|
||||
USER linuxbrew
|
||||
RUN if [ -x /home/linuxbrew/.linuxbrew/bin/brew ]; then \
|
||||
/home/linuxbrew/.linuxbrew/bin/brew --version && \
|
||||
/home/linuxbrew/.linuxbrew/bin/brew install gcc || true; \
|
||||
/home/linuxbrew/.linuxbrew/bin/brew --version && \
|
||||
/home/linuxbrew/.linuxbrew/bin/brew install gcc || true; \
|
||||
else \
|
||||
echo "Skipping gcc installation - Homebrew not available"; \
|
||||
echo "Skipping gcc installation - Homebrew not available"; \
|
||||
fi
|
||||
USER root
|
||||
|
||||
# Install OpenClaw globally
|
||||
# Install OpenClaw globally.
|
||||
# Bundle node-llama-cpp so the default local memory/embeddings provider works
|
||||
# in HAOS without requiring manual npm installs in /usr/lib.
|
||||
# Bundle mcporter so Home Assistant MCP auto-configuration works out of the box.
|
||||
# The image now ships Node 24, so use the current mcporter line.
|
||||
RUN npm config set fund false && npm config set audit false \
|
||||
&& npm install -g openclaw@2026.3.2
|
||||
&& npm install -g openclaw@2026.7.1-2 node-llama-cpp@3.18.1 mcporter@0.12.3
|
||||
|
||||
# Shell aliases and color options for interactive use
|
||||
RUN tee -a /etc/bash.bashrc <<'EOF'
|
||||
@@ -138,10 +145,11 @@ COPY run.sh /run.sh
|
||||
COPY oc_config_helper.py /oc_config_helper.py
|
||||
COPY render_nginx.py /render_nginx.py
|
||||
COPY oc-cleanup.sh /usr/local/bin/oc-cleanup
|
||||
COPY oc-gateway /usr/local/bin/oc-gateway
|
||||
COPY openclaw-proxy-shim.cjs /usr/local/lib/openclaw-proxy-shim.cjs
|
||||
COPY nginx.conf.tpl /etc/nginx/nginx.conf.tpl
|
||||
COPY landing.html.tpl /etc/nginx/landing.html.tpl
|
||||
RUN chmod +x /run.sh /oc_config_helper.py /render_nginx.py /usr/local/bin/oc-cleanup \
|
||||
&& mkdir -p /run/nginx
|
||||
RUN chmod +x /run.sh /oc_config_helper.py /render_nginx.py /usr/local/bin/oc-cleanup /usr/local/bin/oc-gateway \
|
||||
&& mkdir -p /run/nginx
|
||||
|
||||
CMD [ "/run.sh" ]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: OpenClaw Assistant
|
||||
version: "0.5.57"
|
||||
version: "0.5.87"
|
||||
slug: openclaw_assistant
|
||||
description: Run OpenClaw Assistant (OpenClaw-compatible) as a Home Assistant add-on.
|
||||
url: https://github.com/techartdev/OpenClawHomeAssistant
|
||||
@@ -56,6 +56,11 @@ options:
|
||||
clean_session_locks_on_start: true
|
||||
clean_session_locks_on_exit: true
|
||||
|
||||
# Persist heavy optional tooling across add-on rebuilds.
|
||||
# Disabled by default to keep Home Assistant backups small.
|
||||
persist_node_global: false
|
||||
persist_brew_tools: false
|
||||
|
||||
# Gateway mode:
|
||||
# - local: Run gateway locally (recommended for most users)
|
||||
# - remote: Connect to a remote gateway
|
||||
@@ -133,6 +138,11 @@ options:
|
||||
# Limits: max 50 variables, max key length 255 chars, max value length 10000 chars
|
||||
gateway_env_vars: []
|
||||
|
||||
# Auto-configure MCP (Model Context Protocol) for Home Assistant.
|
||||
# When enabled and homeassistant_token is set, automatically registers HA as an
|
||||
# MCP server in OpenClaw so the AI can control Home Assistant entities/services.
|
||||
auto_configure_mcp: false
|
||||
|
||||
|
||||
schema:
|
||||
timezone: str
|
||||
@@ -148,6 +158,8 @@ schema:
|
||||
|
||||
clean_session_locks_on_start: bool?
|
||||
clean_session_locks_on_exit: bool?
|
||||
persist_node_global: bool?
|
||||
persist_brew_tools: bool?
|
||||
gateway_mode: list(local|remote)?
|
||||
gateway_remote_url: str?
|
||||
gateway_bind_mode: list(loopback|lan|tailnet)?
|
||||
@@ -163,4 +175,4 @@ schema:
|
||||
- name: "match(^[A-Z_][A-Z0-9_]*$)"
|
||||
value: str
|
||||
nginx_log_level: list(full|minimal)?
|
||||
|
||||
auto_configure_mcp: bool?
|
||||
|
||||
@@ -116,6 +116,31 @@
|
||||
</details>
|
||||
|
||||
<!-- ==================== PROXY RECIPES ==================== -->
|
||||
<details>
|
||||
<summary>MCP setup (Home Assistant control)</summary>
|
||||
<div style="margin-top:8px;font-size:13px;color:#9ca3af;line-height:1.7">
|
||||
<p><b>MCP (Model Context Protocol)</b> lets OpenClaw control Home Assistant entities, services, and automations directly.</p>
|
||||
|
||||
<b>Automatic (recommended)</b>
|
||||
<ol style="margin:4px 0;padding-left:22px;line-height:1.8">
|
||||
<li>Create a <b>Long-Lived Access Token</b> in HA: click your profile avatar → scroll to <b>Long-Lived Access Tokens</b> → <b>Create Token</b></li>
|
||||
<li>Paste it into add-on option <code>homeassistant_token</code> in <b>Settings → Add-ons → Configuration</b></li>
|
||||
<li>Set <code>auto_configure_mcp</code> to <b>ON</b> in add-on Configuration</li>
|
||||
<li>Restart the add-on — MCP is configured automatically</li>
|
||||
</ol>
|
||||
|
||||
<b>Manual (terminal)</b>
|
||||
<pre style="background:#0b1220;padding:8px;border-radius:6px;overflow-x:auto;font-size:12px">mcporter config add HA "http://localhost:8123/api/mcp" \
|
||||
--header "Authorization=Bearer YOUR_LONG_LIVED_TOKEN" \
|
||||
--scope home</pre>
|
||||
|
||||
<b>After upgrades</b> — if OpenClaw has stale HA data:
|
||||
<pre style="background:#0b1220;padding:8px;border-radius:6px;overflow-x:auto;font-size:12px">mcporter call home-assistant.GetLiveContext</pre>
|
||||
|
||||
<p><b>Tip:</b> The first MCP session needs a capable model (Gemini 3.1 Pro, Claude Sonnet 4, GPT-4.1). After setup, cheaper models work fine.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Reverse-proxy recipes (NPM / Caddy / Traefik / Tailscale)</summary>
|
||||
<div style="margin-top:8px;font-size:13px;color:#9ca3af;line-height:1.7">
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cmd="${1:-status}"
|
||||
shift || true
|
||||
|
||||
find_gateway_pid() {
|
||||
local port="${1:-18789}"
|
||||
local pid=""
|
||||
|
||||
# 1) Port owner (most accurate)
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
pid=$(ss -tlnp 2>/dev/null \
|
||||
| grep ":${port} " \
|
||||
| sed -n 's/.*pid=\([0-9]*\).*/\1/p' \
|
||||
| head -1 || true)
|
||||
if [ -n "$pid" ] && [ -r "/proc/$pid/cmdline" ]; then
|
||||
local cmdline
|
||||
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)
|
||||
if echo "$cmdline" | grep -qi "openclaw\|node"; then
|
||||
echo "$pid"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2) Process title
|
||||
pid=$(pgrep -f "openclaw-gateway" 2>/dev/null | head -1 || true)
|
||||
if [ -n "$pid" ]; then
|
||||
echo "$pid"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 3) /proc cmdline fallback
|
||||
for f in /proc/[0-9]*/cmdline; do
|
||||
[ -r "$f" ] || continue
|
||||
if tr '\0' ' ' < "$f" 2>/dev/null | grep -qi "openclaw"; then
|
||||
echo "${f#/proc/}" | cut -d/ -f1
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
get_gateway_port() {
|
||||
python3 - <<'PY'
|
||||
import json, os
|
||||
p = os.environ.get('OPENCLAW_CONFIG_PATH', '/config/.openclaw/openclaw.json')
|
||||
try:
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
cfg = json.load(f)
|
||||
print(int(cfg.get('gateway', {}).get('port', 18789)))
|
||||
except Exception:
|
||||
print(18789)
|
||||
PY
|
||||
}
|
||||
|
||||
status_cmd() {
|
||||
local port pid
|
||||
port="$(get_gateway_port)"
|
||||
pid="$(find_gateway_pid "$port" || true)"
|
||||
|
||||
echo "OpenClaw add-on gateway status"
|
||||
echo "Supervisor: run.sh (not systemd)"
|
||||
echo "Configured port: ${port}"
|
||||
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "Gateway process: running (PID ${pid})"
|
||||
if command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ":${port} "; then
|
||||
echo "Listener: active on :${port}"
|
||||
else
|
||||
echo "Listener: process found, port bind not detected yet"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Gateway process: not detected"
|
||||
echo "Tip: Check add-on logs in Home Assistant for startup errors."
|
||||
exit 1
|
||||
}
|
||||
|
||||
restart_cmd() {
|
||||
local port pid
|
||||
port="$(get_gateway_port)"
|
||||
pid="$(find_gateway_pid "$port" || true)"
|
||||
|
||||
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
|
||||
echo "No running gateway process found to restart."
|
||||
echo "Tip: If startup failed, inspect add-on logs."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Requesting gateway self-restart via SIGUSR1 (PID ${pid})..."
|
||||
kill -USR1 "$pid"
|
||||
|
||||
for _ in $(seq 1 20); do
|
||||
sleep 1
|
||||
local new_pid
|
||||
new_pid="$(find_gateway_pid "$port" || true)"
|
||||
if [ -n "$new_pid" ] && kill -0 "$new_pid" 2>/dev/null; then
|
||||
echo "Gateway active (PID ${new_pid})"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Restart signal sent; gateway may still be reinitializing."
|
||||
echo "Run 'oc-gateway status' again in a few seconds."
|
||||
exit 0
|
||||
}
|
||||
|
||||
case "$cmd" in
|
||||
status)
|
||||
status_cmd
|
||||
;;
|
||||
restart|reload)
|
||||
restart_cmd
|
||||
;;
|
||||
help|-h|--help)
|
||||
cat <<'EOF'
|
||||
Usage: oc-gateway <status|restart|reload>
|
||||
|
||||
status Show add-on-native gateway status (run.sh-supervised)
|
||||
restart Request gateway self-restart via SIGUSR1
|
||||
reload Alias of restart
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $cmd" >&2
|
||||
echo "Run: oc-gateway help" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -256,6 +256,43 @@ def set_control_ui_origins(origins_csv: str, additional_origins_csv: str = "", d
|
||||
return False
|
||||
|
||||
|
||||
def repair_known_invalid_settings():
|
||||
"""Repair known config values that prevent OpenClaw from starting."""
|
||||
cfg = read_config()
|
||||
if cfg is None:
|
||||
return True
|
||||
|
||||
tools = cfg.get("tools")
|
||||
if not isinstance(tools, dict):
|
||||
return True
|
||||
|
||||
web = tools.get("web")
|
||||
if not isinstance(web, dict):
|
||||
return True
|
||||
|
||||
search = web.get("search")
|
||||
if not isinstance(search, dict):
|
||||
return True
|
||||
|
||||
provider = search.get("provider")
|
||||
changes = []
|
||||
|
||||
if provider == "brave":
|
||||
del search["provider"]
|
||||
changes.append("removed unavailable tools.web.search.provider=brave")
|
||||
|
||||
if not changes:
|
||||
print("INFO: No known invalid OpenClaw config settings found")
|
||||
return True
|
||||
|
||||
if write_config(cfg):
|
||||
print(f"INFO: Repaired OpenClaw config: {', '.join(changes)}")
|
||||
return True
|
||||
|
||||
print("ERROR: Failed to write config")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point for use by run.sh"""
|
||||
if len(sys.argv) < 2:
|
||||
@@ -300,6 +337,13 @@ def main():
|
||||
success = set_control_ui_origins(origins_csv, additional_origins_csv, disable_device_auth)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif cmd == "repair-known-invalid-settings":
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: oc_config_helper.py repair-known-invalid-settings")
|
||||
sys.exit(1)
|
||||
success = repair_known_invalid_settings()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif cmd == "set":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: oc_config_helper.py set <key> <value>")
|
||||
|
||||
+549
-101
@@ -44,6 +44,8 @@ ROUTER_KEY=$(jq -r '.router_ssh_key_path // "/data/keys/router_ssh"' "$OPTIONS_F
|
||||
# Optional: allow disabling lock cleanup if you ever need to debug
|
||||
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")
|
||||
PERSIST_NODE_GLOBAL=$(jq -r '.persist_node_global // false' "$OPTIONS_FILE")
|
||||
PERSIST_BREW_TOOLS=$(jq -r '.persist_brew_tools // false' "$OPTIONS_FILE")
|
||||
|
||||
# Gateway configuration
|
||||
GATEWAY_MODE=$(jq -r '.gateway_mode // "local"' "$OPTIONS_FILE")
|
||||
@@ -58,6 +60,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")
|
||||
@@ -148,6 +151,15 @@ export XDG_CONFIG_HOME=/config
|
||||
|
||||
mkdir -p /config/.openclaw /config/.openclaw/identity /config/clawd /config/keys /config/secrets
|
||||
|
||||
warn_legacy_persistent_dir() {
|
||||
local path="$1"
|
||||
local label="$2"
|
||||
if [ -e "$path" ]; then
|
||||
echo "WARN: Found legacy persistent ${label} at ${path}, but persistence is disabled."
|
||||
echo "WARN: It will still inflate Home Assistant backups until you remove or archive it manually."
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Sync built-in OpenClaw skills from image to persistent storage
|
||||
# On each startup, copy new/updated built-in skills so they survive rebuilds.
|
||||
@@ -179,21 +191,33 @@ else
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Persist user-installed node skills across Docker image rebuilds
|
||||
# Redirect npm/pnpm global installs to /config/.node_global (persistent storage)
|
||||
# so that skills installed via the dashboard survive container rebuilds.
|
||||
# Optional persistence for user-installed node skills across Docker image rebuilds.
|
||||
# When enabled, redirect npm/pnpm global installs to /config/.node_global so
|
||||
# dashboard-installed skills survive image updates. Disabled by default to keep
|
||||
# Home Assistant backups smaller.
|
||||
# NOTE: This MUST come after the skills sync above (which needs the original npm root -g).
|
||||
# ------------------------------------------------------------------------------
|
||||
PERSISTENT_NODE_GLOBAL="/config/.node_global"
|
||||
mkdir -p "$PERSISTENT_NODE_GLOBAL"
|
||||
npm config set prefix "$PERSISTENT_NODE_GLOBAL" 2>/dev/null || true
|
||||
export PATH="${PERSISTENT_NODE_GLOBAL}/bin:${PATH}"
|
||||
export NODE_PATH="${PERSISTENT_NODE_GLOBAL}/lib/node_modules:${NODE_PATH:-}"
|
||||
if [ "$PERSIST_NODE_GLOBAL" = "true" ] || [ "$PERSIST_NODE_GLOBAL" = "1" ]; then
|
||||
mkdir -p "$PERSISTENT_NODE_GLOBAL"
|
||||
npm config set prefix "$PERSISTENT_NODE_GLOBAL" 2>/dev/null || true
|
||||
export PATH="${PERSISTENT_NODE_GLOBAL}/bin:${PATH}"
|
||||
export NODE_PATH="${PERSISTENT_NODE_GLOBAL}/lib/node_modules:${NODE_PATH:-}"
|
||||
|
||||
# Also configure pnpm global dir to persistent storage
|
||||
export PNPM_HOME="${PERSISTENT_NODE_GLOBAL}/pnpm"
|
||||
mkdir -p "$PNPM_HOME"
|
||||
export PATH="${PNPM_HOME}:${PATH}"
|
||||
# Also configure pnpm global dir to persistent storage
|
||||
export PNPM_HOME="${PERSISTENT_NODE_GLOBAL}/pnpm"
|
||||
mkdir -p "$PNPM_HOME"
|
||||
export PATH="${PNPM_HOME}:${PATH}"
|
||||
echo "INFO: persist_node_global=true; user-installed npm skills will survive add-on rebuilds."
|
||||
else
|
||||
npm config delete prefix 2>/dev/null || true
|
||||
export npm_config_prefix="/usr/local"
|
||||
export PNPM_HOME="/tmp/.pnpm-home"
|
||||
mkdir -p "$PNPM_HOME"
|
||||
export PATH="${PNPM_HOME}:${PATH}"
|
||||
warn_legacy_persistent_dir "$PERSISTENT_NODE_GLOBAL" "node global tool/skill data"
|
||||
echo "INFO: persist_node_global=false; npm/pnpm global installs are ephemeral and excluded from HA backups."
|
||||
fi
|
||||
|
||||
# Protect critical runtime variables from accidental override via gateway_env_vars.
|
||||
is_reserved_gateway_env_var() {
|
||||
@@ -330,42 +354,48 @@ elif [ "$GW_ENV_VARS_TYPE" != "null" ]; then
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Persist Linuxbrew/Homebrew across Docker image rebuilds
|
||||
# Homebrew installs to /home/linuxbrew/.linuxbrew/ which is ephemeral.
|
||||
# We sync it to /config/.linuxbrew and symlink back so brew-installed CLI
|
||||
# tools (gog, gh, bw, etc.) survive add-on updates.
|
||||
# Optional persistence for Linuxbrew/Homebrew across Docker image rebuilds.
|
||||
# When enabled, sync /home/linuxbrew/.linuxbrew to /config/.linuxbrew so
|
||||
# brew-installed CLI tools survive image updates. Disabled by default to keep
|
||||
# Home Assistant backups smaller.
|
||||
# ------------------------------------------------------------------------------
|
||||
IMAGE_BREW_DIR="/home/linuxbrew/.linuxbrew"
|
||||
PERSISTENT_BREW_DIR="/config/.linuxbrew"
|
||||
|
||||
if [ -d "$IMAGE_BREW_DIR" ] && [ ! -L "$IMAGE_BREW_DIR" ]; then
|
||||
# Image has a real Homebrew install — sync to persistent storage
|
||||
if [ -d "$PERSISTENT_BREW_DIR" ]; then
|
||||
# Persistent copy exists: sync new/updated files from image (upgrades),
|
||||
# but preserve user-installed packages already in persistent storage.
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --update "$IMAGE_BREW_DIR/" "$PERSISTENT_BREW_DIR/" 2>/dev/null || true
|
||||
if [ "$PERSIST_BREW_TOOLS" = "true" ] || [ "$PERSIST_BREW_TOOLS" = "1" ]; then
|
||||
if [ -d "$IMAGE_BREW_DIR" ] && [ ! -L "$IMAGE_BREW_DIR" ]; then
|
||||
# Image has a real Homebrew install — sync to persistent storage
|
||||
if [ -d "$PERSISTENT_BREW_DIR" ]; then
|
||||
# Persistent copy exists: sync new/updated files from image (upgrades),
|
||||
# but preserve user-installed packages already in persistent storage.
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --update "$IMAGE_BREW_DIR/" "$PERSISTENT_BREW_DIR/" 2>/dev/null || true
|
||||
else
|
||||
cp -ru "$IMAGE_BREW_DIR/"* "$PERSISTENT_BREW_DIR/" 2>/dev/null || true
|
||||
fi
|
||||
echo "INFO: Synced Homebrew updates to persistent storage"
|
||||
else
|
||||
cp -ru "$IMAGE_BREW_DIR/"* "$PERSISTENT_BREW_DIR/" 2>/dev/null || true
|
||||
# First time: copy entire Homebrew install to persistent storage
|
||||
cp -a "$IMAGE_BREW_DIR" "$PERSISTENT_BREW_DIR" 2>/dev/null || true
|
||||
echo "INFO: Copied Homebrew to persistent storage at $PERSISTENT_BREW_DIR"
|
||||
fi
|
||||
echo "INFO: Synced Homebrew updates to persistent storage"
|
||||
# Replace image dir with symlink to persistent copy
|
||||
rm -rf "$IMAGE_BREW_DIR"
|
||||
ln -sf "$PERSISTENT_BREW_DIR" "$IMAGE_BREW_DIR"
|
||||
elif [ -L "$IMAGE_BREW_DIR" ]; then
|
||||
echo "INFO: Homebrew already linked to persistent storage"
|
||||
elif [ -d "$PERSISTENT_BREW_DIR" ]; then
|
||||
# Image doesn't have Homebrew (failed install?) but persistent copy exists
|
||||
mkdir -p "$(dirname "$IMAGE_BREW_DIR")"
|
||||
ln -sf "$PERSISTENT_BREW_DIR" "$IMAGE_BREW_DIR"
|
||||
echo "INFO: Restored Homebrew symlink from persistent storage"
|
||||
else
|
||||
# First time: copy entire Homebrew install to persistent storage
|
||||
cp -a "$IMAGE_BREW_DIR" "$PERSISTENT_BREW_DIR" 2>/dev/null || true
|
||||
echo "INFO: Copied Homebrew to persistent storage at $PERSISTENT_BREW_DIR"
|
||||
echo "INFO: Homebrew not available (install may have failed during image build)"
|
||||
fi
|
||||
# Replace image dir with symlink to persistent copy
|
||||
rm -rf "$IMAGE_BREW_DIR"
|
||||
ln -sf "$PERSISTENT_BREW_DIR" "$IMAGE_BREW_DIR"
|
||||
elif [ -L "$IMAGE_BREW_DIR" ]; then
|
||||
echo "INFO: Homebrew already linked to persistent storage"
|
||||
elif [ -d "$PERSISTENT_BREW_DIR" ]; then
|
||||
# Image doesn't have Homebrew (failed install?) but persistent copy exists
|
||||
mkdir -p "$(dirname "$IMAGE_BREW_DIR")"
|
||||
ln -sf "$PERSISTENT_BREW_DIR" "$IMAGE_BREW_DIR"
|
||||
echo "INFO: Restored Homebrew symlink from persistent storage"
|
||||
echo "INFO: persist_brew_tools=true; brew-installed tools will survive add-on rebuilds."
|
||||
else
|
||||
echo "INFO: Homebrew not available (install may have failed during image build)"
|
||||
warn_legacy_persistent_dir "$PERSISTENT_BREW_DIR" "Homebrew data"
|
||||
echo "INFO: persist_brew_tools=false; Homebrew installs stay ephemeral and excluded from HA backups."
|
||||
fi
|
||||
|
||||
# Back-compat: some docs/scripts assume /data; point it at /config.
|
||||
@@ -373,8 +403,9 @@ if [ ! -e /data ]; then
|
||||
ln -s /config /data || true
|
||||
fi
|
||||
|
||||
# Ensure these exist so cleanup doesn't fail
|
||||
mkdir -p /config/.openclaw/agents/main/sessions || true
|
||||
# Ensure the agents base directory exists so cleanup scans work even before first run.
|
||||
# Do NOT pre-create agent-specific directories; OpenClaw creates them as needed.
|
||||
mkdir -p /config/.openclaw/agents || true
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# SINGLE-INSTANCE GUARD (prevents multiple gateway runs racing each other)
|
||||
@@ -392,30 +423,43 @@ fi
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
gateway_running() {
|
||||
pgrep -f "openclaw.*gateway.*run" >/dev/null 2>&1
|
||||
pgrep -f "openclaw-gateway" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
cleanup_session_locks() {
|
||||
local sessions_dir="/config/.openclaw/agents/main/sessions"
|
||||
local glob1="${sessions_dir}"/*.jsonl.lock
|
||||
local agents_dir="/config/.openclaw/agents"
|
||||
local total_locks=0
|
||||
local cleaned_dirs=()
|
||||
|
||||
# Scan all agent session directories, not just 'main'.
|
||||
# This is needed for users who have gateway.forcedAgentId set to a non-default agent.
|
||||
shopt -s nullglob
|
||||
local locks=( $glob1 )
|
||||
local all_locks=()
|
||||
for agent_sessions_dir in "${agents_dir}"/*/sessions; do
|
||||
local agent_locks=( "${agent_sessions_dir}"/*.jsonl.lock )
|
||||
if [ ${#agent_locks[@]} -gt 0 ]; then
|
||||
all_locks+=( "${agent_locks[@]}" )
|
||||
cleaned_dirs+=( "$agent_sessions_dir" )
|
||||
total_locks=$(( total_locks + ${#agent_locks[@]} ))
|
||||
fi
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
if [ ${#locks[@]} -eq 0 ]; then
|
||||
if [ "$total_locks" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If gateway is running, do NOT remove locks automatically (could be real).
|
||||
if gateway_running; then
|
||||
echo "INFO: Gateway appears to be running; leaving session lock files untouched."
|
||||
echo "INFO: Locks present: ${#locks[@]}"
|
||||
echo "INFO: Locks present: $total_locks"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "INFO: Removing stale session lock files (${#locks[@]}) from ${sessions_dir}"
|
||||
rm -f "${sessions_dir}"/*.jsonl.lock || true
|
||||
echo "INFO: Removing stale session lock files ($total_locks) across agents: ${cleaned_dirs[*]}"
|
||||
for agent_sessions_dir in "${cleaned_dirs[@]}"; do
|
||||
rm -f "${agent_sessions_dir}"/*.jsonl.lock || true
|
||||
done
|
||||
}
|
||||
|
||||
if [ "$CLEAN_LOCKS_ON_START" = "true" ]; then
|
||||
@@ -453,6 +497,7 @@ EOF
|
||||
# Graceful shutdown handling (PID 1 trap) to reduce stale locks
|
||||
# ------------------------------------------------------------------------------
|
||||
GW_PID=""
|
||||
GW_RELAY_PID=""
|
||||
NGINX_PID=""
|
||||
TTYD_PID=""
|
||||
SHUTTING_DOWN="false"
|
||||
@@ -473,9 +518,18 @@ shutdown() {
|
||||
|
||||
if [ -n "${GW_PID}" ] && kill -0 "${GW_PID}" >/dev/null 2>&1; then
|
||||
kill -TERM "${GW_PID}" >/dev/null 2>&1 || true
|
||||
wait "${GW_PID}" || true
|
||||
# wait reaps child PIDs; for non-child (re-tracked) PIDs it fails instantly,
|
||||
# so fall back to a timed kill -0 poll to let the gateway finish cleanly.
|
||||
if ! wait "${GW_PID}" 2>/dev/null; then
|
||||
for _i in 1 2 3 4 5; do
|
||||
kill -0 "${GW_PID}" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
stop_gw_relay
|
||||
|
||||
if [ "$CLEAN_LOCKS_ON_EXIT" = "true" ]; then
|
||||
cleanup_session_locks || true
|
||||
fi
|
||||
@@ -488,6 +542,69 @@ if ! command -v openclaw >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_openclaw_version() {
|
||||
local raw
|
||||
raw="$(openclaw --version 2>/dev/null | head -n 1 || true)"
|
||||
printf '%s\n' "$raw" | grep -oE '[0-9]{4}\.[0-9]+\.[0-9]+' | head -n 1 || true
|
||||
}
|
||||
|
||||
version_is_less_than() {
|
||||
local left="$1"
|
||||
local right="$2"
|
||||
[ -n "$left" ] && [ -n "$right" ] && [ "$left" != "$right" ] && \
|
||||
[ "$(printf '%s\n%s\n' "$left" "$right" | sort -V | head -n 1)" = "$left" ]
|
||||
}
|
||||
|
||||
repair_runtime_version_mismatch() {
|
||||
local config_path="/config/.openclaw/openclaw.json"
|
||||
local runtime_version persisted_version refreshed_version
|
||||
|
||||
if [ ! -f "$config_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
runtime_version="$(get_openclaw_version)"
|
||||
persisted_version="$(python3 - "$config_path" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
print("", end="")
|
||||
raise SystemExit(0)
|
||||
|
||||
value = data.get("meta", {}).get("lastTouchedVersion", "")
|
||||
print(value if isinstance(value, str) else "", end="")
|
||||
PY
|
||||
)"
|
||||
|
||||
if [ -z "$runtime_version" ] || [ -z "$persisted_version" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! version_is_less_than "$runtime_version" "$persisted_version"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "WARN: Persisted OpenClaw config was last written by newer version $persisted_version, but bundled runtime is $runtime_version."
|
||||
echo "WARN: Attempting one-time runtime repair so the gateway can start after add-on rebuilds or Home Assistant OS updates."
|
||||
|
||||
if npm install -g "openclaw@${persisted_version}" >/tmp/openclaw-runtime-repair.log 2>&1; then
|
||||
refreshed_version="$(get_openclaw_version)"
|
||||
echo "INFO: OpenClaw runtime repair succeeded (${runtime_version} -> ${refreshed_version:-$persisted_version})."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "ERROR: Automatic runtime repair failed; startup will continue with bundled OpenClaw $runtime_version."
|
||||
echo "ERROR: Review /tmp/openclaw-runtime-repair.log and rerun 'openclaw update' or 'npm install -g openclaw@${persisted_version}' once connectivity is available."
|
||||
return 0
|
||||
}
|
||||
|
||||
repair_runtime_version_mismatch
|
||||
|
||||
# Bootstrap minimal OpenClaw config ONLY if missing.
|
||||
# We do not overwrite or patch existing configs; onboarding owns everything else.
|
||||
OPENCLAW_CONFIG_PATH="/config/.openclaw/openclaw.json"
|
||||
@@ -537,9 +654,20 @@ fi
|
||||
|
||||
if [ -f "$OPENCLAW_CONFIG_PATH" ]; then
|
||||
if [ -f "$HELPER_PATH" ]; then
|
||||
if python3 "$HELPER_PATH" repair-known-invalid-settings; then
|
||||
:
|
||||
else
|
||||
rc=$?
|
||||
echo "ERROR: Failed to repair known invalid OpenClaw config settings via oc_config_helper.py (exit code ${rc})."
|
||||
echo "ERROR: Gateway configuration may be invalid; aborting startup."
|
||||
exit "${rc}"
|
||||
fi
|
||||
|
||||
# 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_REMOTE_URL" "$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
|
||||
:
|
||||
else
|
||||
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."
|
||||
@@ -570,6 +698,71 @@ if [ "$ENABLE_HTTPS_PROXY" = "true" ]; then
|
||||
CERT_DIR="/config/certs"
|
||||
mkdir -p "$CERT_DIR"
|
||||
|
||||
cert_ext_contains() {
|
||||
local cert_path="$1"
|
||||
local extension_name="$2"
|
||||
local expected="$3"
|
||||
openssl x509 -in "$cert_path" -noout -ext "$extension_name" 2>/dev/null | grep -Fq "$expected"
|
||||
}
|
||||
|
||||
local_ca_cert_is_valid() {
|
||||
local cert_path="$1"
|
||||
[ -f "$cert_path" ] && \
|
||||
cert_ext_contains "$cert_path" basicConstraints "CA:TRUE" && \
|
||||
cert_ext_contains "$cert_path" keyUsage "Certificate Sign, CRL Sign"
|
||||
}
|
||||
|
||||
gateway_server_cert_is_valid() {
|
||||
local cert_path="$1"
|
||||
[ -f "$cert_path" ] && \
|
||||
cert_ext_contains "$cert_path" extendedKeyUsage "TLS Web Server Authentication" && \
|
||||
cert_ext_contains "$cert_path" subjectAltName "DNS:localhost"
|
||||
}
|
||||
|
||||
generate_local_ca_cert() {
|
||||
cat > "$CERT_DIR/_ca.ext" <<'CAEOF'
|
||||
[v3_ca]
|
||||
basicConstraints=critical,CA:TRUE
|
||||
keyUsage=critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid:always,issuer
|
||||
CAEOF
|
||||
|
||||
openssl genrsa -out "$CERT_DIR/ca.key" 2048 2>/dev/null
|
||||
openssl req -new -key "$CERT_DIR/ca.key" -out "$CERT_DIR/ca.csr" \
|
||||
-subj "/CN=OpenClaw Local CA" 2>/dev/null
|
||||
openssl x509 -req -in "$CERT_DIR/ca.csr" -signkey "$CERT_DIR/ca.key" \
|
||||
-out "$CERT_DIR/ca.crt" -days 3650 \
|
||||
-extfile "$CERT_DIR/_ca.ext" -extensions v3_ca 2>/dev/null
|
||||
|
||||
rm -f "$CERT_DIR/ca.csr" "$CERT_DIR/_ca.ext"
|
||||
chmod 600 "$CERT_DIR/ca.key"
|
||||
}
|
||||
|
||||
generate_gateway_server_cert() {
|
||||
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
|
||||
|
||||
cat > "$CERT_DIR/_server.ext" <<SERVER_EOF
|
||||
[v3_server]
|
||||
basicConstraints=critical,CA:FALSE
|
||||
keyUsage=critical,digitalSignature,keyEncipherment
|
||||
extendedKeyUsage=serverAuth
|
||||
subjectAltName=IP:${LAN_IP:-127.0.0.1},IP:127.0.0.1,DNS:localhost,DNS:homeassistant,DNS:homeassistant.local${EXTRA_SANS:+,${EXTRA_SANS}}
|
||||
subjectKeyIdentifier=hash
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
SERVER_EOF
|
||||
|
||||
openssl x509 -req -in "$CERT_DIR/gateway.csr" \
|
||||
-CA "$CERT_DIR/ca.crt" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
|
||||
-out "$CERT_DIR/gateway.crt" -days 3650 \
|
||||
-extfile "$CERT_DIR/_server.ext" -extensions v3_server 2>/dev/null
|
||||
|
||||
rm -f "$CERT_DIR/gateway.csr" "$CERT_DIR/_server.ext" "$CERT_DIR/ca.srl"
|
||||
chmod 600 "$CERT_DIR/gateway.key"
|
||||
}
|
||||
|
||||
# Detect primary LAN IP
|
||||
LAN_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
STORED_IP=$(cat "$CERT_DIR/.cert_ip" 2>/dev/null || echo "")
|
||||
@@ -577,12 +770,17 @@ if [ "$ENABLE_HTTPS_PROXY" = "true" ]; then
|
||||
# --- Local CA (generated once, persists across restarts) ---
|
||||
if [ ! -f "$CERT_DIR/ca.key" ] || [ ! -f "$CERT_DIR/ca.crt" ]; then
|
||||
echo "INFO: Generating local CA certificate (one-time)..."
|
||||
openssl genrsa -out "$CERT_DIR/ca.key" 2048 2>/dev/null
|
||||
openssl req -new -x509 -key "$CERT_DIR/ca.key" -out "$CERT_DIR/ca.crt" \
|
||||
-days 3650 -nodes -subj "/CN=OpenClaw Local CA" 2>/dev/null
|
||||
chmod 600 "$CERT_DIR/ca.key"
|
||||
generate_local_ca_cert
|
||||
STORED_IP="" # force server cert regeneration
|
||||
echo "INFO: Local CA created at $CERT_DIR/ca.crt"
|
||||
elif ! local_ca_cert_is_valid "$CERT_DIR/ca.crt"; then
|
||||
echo "WARN: Existing local CA certificate is missing required X.509 CA extensions."
|
||||
echo "WARN: Regenerating CA/server certificates for OpenSSL and Python strict verification compatibility."
|
||||
echo "WARN: Devices that trusted the old CA need the new /cert/ca.crt installed again."
|
||||
rm -f "$CERT_DIR/ca.key" "$CERT_DIR/ca.crt" "$CERT_DIR/gateway.key" "$CERT_DIR/gateway.crt"
|
||||
generate_local_ca_cert
|
||||
STORED_IP=""
|
||||
echo "INFO: Local CA rotated at $CERT_DIR/ca.crt"
|
||||
fi
|
||||
|
||||
# --- Extra SANs from gateway_additional_allowed_origins + gateway_public_url ---
|
||||
@@ -616,24 +814,9 @@ PY
|
||||
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
|
||||
if [ ! -f "$CERT_DIR/gateway.crt" ] || [ ! -f "$CERT_DIR/gateway.key" ] || [ "$LAN_IP" != "$STORED_IP" ] || [ "$EXTRA_SANS" != "$STORED_EXTRA_SANS" ] || ! gateway_server_cert_is_valid "$CERT_DIR/gateway.crt"; 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, 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${EXTRA_SANS:+,${EXTRA_SANS}}
|
||||
SANEOF
|
||||
|
||||
openssl x509 -req -in "$CERT_DIR/gateway.csr" \
|
||||
-CA "$CERT_DIR/ca.crt" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
|
||||
-out "$CERT_DIR/gateway.crt" -days 3650 \
|
||||
-extfile "$CERT_DIR/_san.ext" 2>/dev/null
|
||||
|
||||
rm -f "$CERT_DIR/gateway.csr" "$CERT_DIR/_san.ext" "$CERT_DIR/ca.srl"
|
||||
chmod 600 "$CERT_DIR/gateway.key"
|
||||
generate_gateway_server_cert
|
||||
printf '%s' "$LAN_IP" > "$CERT_DIR/.cert_ip"
|
||||
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}})"
|
||||
@@ -700,15 +883,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 "WARN: mcporter is missing from the add-on image; skipping MCP auto-configuration"
|
||||
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
|
||||
|
||||
@@ -744,10 +973,104 @@ PY
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Loopback relay helpers 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.
|
||||
# These functions start/stop a lightweight Node.js TCP relay on
|
||||
# 127.0.0.1:PORT -> TAILSCALE_IP:PORT so terminal CLI commands work.
|
||||
# IMPORTANT: stop_gw_relay must be called before restarting the gateway;
|
||||
# otherwise the relay holds the loopback port and the new gateway instance
|
||||
# detects it as "already listening" and exits with code 1.
|
||||
start_gw_relay() {
|
||||
if [ "$GATEWAY_BIND_MODE" != "tailnet" ]; then
|
||||
return 0
|
||||
fi
|
||||
local ts_ip
|
||||
ts_ip=$(ip -4 addr show tailscale0 2>/dev/null \
|
||||
| awk '/inet /{gsub(/\/.*/,"",$2); print $2; exit}' || true)
|
||||
if [[ "${ts_ip:-}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "INFO: Starting loopback relay for tailnet gateway (127.0.0.1:${GATEWAY_PORT} -> ${ts_ip}:${GATEWAY_PORT})"
|
||||
node -e "
|
||||
const net = require('net');
|
||||
const TARGET_HOST = '${ts_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
|
||||
}
|
||||
|
||||
stop_gw_relay() {
|
||||
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}" 2>/dev/null || true
|
||||
GW_RELAY_PID=""
|
||||
fi
|
||||
}
|
||||
|
||||
# Find a running gateway daemon's PID using multiple detection methods.
|
||||
# Used by the supervisor loop to detect self-restarts (SIGUSR1) without
|
||||
# spawning duplicate gateway instances that collide on the port.
|
||||
#
|
||||
# Three tiers, tried in order of reliability:
|
||||
# 1. Port ownership via `ss -tlnp` — authoritative, but only works once
|
||||
# the daemon has bound the port (can take 20+ s on Pi hardware).
|
||||
# 2. Process title via `pgrep -f openclaw-gateway` — works after Node.js
|
||||
# sets process.title, which also happens late during init.
|
||||
# 3. /proc cmdline scan — catches the daemon IMMEDIATELY after fork,
|
||||
# before title or port bind, by matching "openclaw" in the cmdline.
|
||||
# Excludes known PIDs (nginx, ttyd, relay, our shell, old GW_PID).
|
||||
#
|
||||
# Returns the PID on stdout and exit 0, or exits with code 1 if nothing found.
|
||||
find_gateway_daemon_pid() {
|
||||
local pid=""
|
||||
|
||||
# Tier 1: port ownership (authoritative once port is bound)
|
||||
pid=$(ss -tlnp 2>/dev/null \
|
||||
| grep ":${GATEWAY_INTERNAL_PORT} " \
|
||||
| sed -n 's/.*pid=\([0-9]*\).*/\1/p' \
|
||||
| head -1)
|
||||
[ -n "$pid" ] && { echo "$pid"; return 0; }
|
||||
|
||||
# Tier 2: process title (after Node sets process.title)
|
||||
pid=$(pgrep -f "openclaw-gateway" 2>/dev/null | head -1)
|
||||
[ -n "$pid" ] && { echo "$pid"; return 0; }
|
||||
|
||||
# Tier 3: scan /proc for any openclaw process we don't already know about.
|
||||
# The daemon's cmdline (e.g. node /usr/.../openclaw/...) contains "openclaw"
|
||||
# from the moment it is forked, even before process.title is set.
|
||||
local known=" ${NGINX_PID:-0} ${TTYD_PID:-0} ${GW_RELAY_PID:-0} ${GW_PID:-0} $$ "
|
||||
local f cand
|
||||
for f in /proc/[0-9]*/cmdline; do
|
||||
[ -r "$f" ] || continue
|
||||
if tr '\0' ' ' < "$f" 2>/dev/null | grep -q "openclaw"; then
|
||||
cand="${f#/proc/}"
|
||||
cand="${cand%%/*}"
|
||||
case "$known" in *" $cand "*) continue ;; esac
|
||||
echo "$cand"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! start_openclaw_runtime; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
start_gw_relay
|
||||
|
||||
# Start web terminal (optional)
|
||||
TTYD_PID_FILE="/var/run/openclaw-ttyd.pid"
|
||||
|
||||
@@ -814,38 +1137,61 @@ if command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ':48099 '; th
|
||||
echo "WARN: Port 48099 still in use after cleanup; nginx may fail to start"
|
||||
fi
|
||||
|
||||
# Render nginx config from template.
|
||||
# The gateway token is NOT managed by the add-on; OpenClaw will generate/store it.
|
||||
# Read directly from config file — the CLI redacts secrets since v2026.2.22+.
|
||||
GW_TOKEN="$(python3 -c "
|
||||
# ------------------------------------------------------------------------------
|
||||
# render_landing: (re-)render the nginx config + landing page HTML.
|
||||
#
|
||||
# Called once before nginx starts (token may be empty on first boot/pre-onboard)
|
||||
# and again in the background after the gateway comes up so a freshly-generated
|
||||
# token is immediately reflected in the "Open Gateway Web UI" button.
|
||||
# nginx is sent SIGHUP to reload the updated config without restarting.
|
||||
# ------------------------------------------------------------------------------
|
||||
render_landing() {
|
||||
local label="${1:-startup}"
|
||||
# Read gateway token directly from openclaw.json (CLI redacts secrets v2026.2.22+)
|
||||
local token
|
||||
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=""
|
||||
if df -h /config >/dev/null 2>&1; then
|
||||
DISK_TOTAL=$(df -h /config | awk 'NR==2{print $2}')
|
||||
DISK_USED=$(df -h /config | awk 'NR==2{print $3}')
|
||||
DISK_AVAIL=$(df -h /config | awk 'NR==2{print $4}')
|
||||
DISK_PCT=$(df -h /config | awk 'NR==2{print $5}')
|
||||
echo "INFO: Disk usage: ${DISK_USED}/${DISK_TOTAL} (${DISK_PCT} used, ${DISK_AVAIL} free)"
|
||||
# Warn early if disk is getting full
|
||||
DISK_PCT_NUM=${DISK_PCT//%/}
|
||||
if [ "$DISK_PCT_NUM" -ge 90 ] 2>/dev/null; then
|
||||
echo "WARNING: Disk is ${DISK_PCT} full! Add-on updates may fail. Run 'oc-cleanup' in the terminal."
|
||||
elif [ "$DISK_PCT_NUM" -ge 75 ] 2>/dev/null; then
|
||||
echo "NOTICE: Disk is ${DISK_PCT} full. Consider running 'oc-cleanup' in the terminal."
|
||||
local disk_total="" disk_used="" disk_avail="" disk_pct=""
|
||||
if df -h /config >/dev/null 2>&1; then
|
||||
disk_total=$(df -h /config | awk 'NR==2{print $2}')
|
||||
disk_used=$(df -h /config | awk 'NR==2{print $3}')
|
||||
disk_avail=$(df -h /config | awk 'NR==2{print $4}')
|
||||
disk_pct=$(df -h /config | awk 'NR==2{print $5}')
|
||||
if [ "$label" = "startup" ]; then
|
||||
echo "INFO: Disk usage: ${disk_used}/${disk_total} (${disk_pct} used, ${disk_avail} free)"
|
||||
local pct_num=${disk_pct//%/}
|
||||
if [ "$pct_num" -ge 90 ] 2>/dev/null; then
|
||||
echo "WARNING: Disk is ${disk_pct} full! Add-on updates may fail. Run 'oc-cleanup' in the terminal."
|
||||
elif [ "$pct_num" -ge 75 ] 2>/dev/null; then
|
||||
echo "NOTICE: Disk is ${disk_pct} full. Consider running 'oc-cleanup' in the terminal."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
GW_PUBLIC_URL="$GW_PUBLIC_URL" GW_TOKEN="$GW_TOKEN" TERMINAL_PORT="$TERMINAL_PORT" \
|
||||
ENABLE_HTTPS_PROXY="$ENABLE_HTTPS_PROXY" HTTPS_PROXY_PORT="$GATEWAY_PORT" \
|
||||
GATEWAY_INTERNAL_PORT="$GATEWAY_INTERNAL_PORT" ACCESS_MODE="$ACCESS_MODE" \
|
||||
DISK_TOTAL="$DISK_TOTAL" DISK_USED="$DISK_USED" DISK_AVAIL="$DISK_AVAIL" DISK_PCT="$DISK_PCT" \
|
||||
NGINX_LOG_LEVEL="$NGINX_LOG_LEVEL" \
|
||||
python3 /render_nginx.py
|
||||
GW_PUBLIC_URL="$GW_PUBLIC_URL" GW_TOKEN="$token" TERMINAL_PORT="$TERMINAL_PORT" \
|
||||
ENABLE_HTTPS_PROXY="$ENABLE_HTTPS_PROXY" HTTPS_PROXY_PORT="$GATEWAY_PORT" \
|
||||
GATEWAY_INTERNAL_PORT="$GATEWAY_INTERNAL_PORT" ACCESS_MODE="$ACCESS_MODE" \
|
||||
DISK_TOTAL="$disk_total" DISK_USED="$disk_used" DISK_AVAIL="$disk_avail" DISK_PCT="$disk_pct" \
|
||||
NGINX_LOG_LEVEL="$NGINX_LOG_LEVEL" \
|
||||
python3 /render_nginx.py
|
||||
|
||||
if [ "$label" != "startup" ]; then
|
||||
# Signal nginx to reload config/landing HTML without dropping connections.
|
||||
local nginx_pid
|
||||
nginx_pid=$(cat "${NGINX_PID_FILE:-/var/run/openclaw-nginx.pid}" 2>/dev/null || true)
|
||||
if [ -n "$nginx_pid" ] && kill -0 "$nginx_pid" 2>/dev/null; then
|
||||
kill -HUP "$nginx_pid" 2>/dev/null || true
|
||||
echo "INFO: Landing page re-rendered with gateway token (nginx reloaded)."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Initial render (token may be absent if openclaw.json does not exist yet)
|
||||
render_landing startup
|
||||
|
||||
echo "Starting ingress proxy (nginx) on :48099 ..."
|
||||
nginx -g 'daemon off;' &
|
||||
@@ -858,21 +1204,123 @@ else
|
||||
echo "WARN: nginx failed to start (PID $NGINX_PID exited); ingress UI may be unavailable"
|
||||
fi
|
||||
|
||||
# If the token was not available at startup (first boot / pre-onboard), schedule
|
||||
# a background re-render so the "Open Gateway Web UI" button gets the real token
|
||||
# once openclaw onboard writes openclaw.json (typically within 30-90 s).
|
||||
(
|
||||
CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-/config/.openclaw/openclaw.json}"
|
||||
for _i in $(seq 1 24); do
|
||||
sleep 5
|
||||
token=$(python3 -c "
|
||||
import json, os
|
||||
p='$CONFIG_PATH'
|
||||
try:
|
||||
print(json.load(open(p)).get('gateway',{}).get('auth',{}).get('token',''), end='')
|
||||
except Exception:
|
||||
pass
|
||||
" 2>/dev/null || true)
|
||||
if [ -n "$token" ]; then
|
||||
render_landing post-onboard
|
||||
break
|
||||
fi
|
||||
done
|
||||
) &
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Design notes (issue #95):
|
||||
# `openclaw gateway run` is a thin wrapper that spawns `openclaw-gateway` as a
|
||||
# long-running daemon and then exits. When the gateway self-restarts (SIGUSR1 /
|
||||
# `openclaw gateway restart`), the old daemon exits and a NEW daemon is forked —
|
||||
# the new PID is NOT a child of this shell so `wait` cannot block on it.
|
||||
#
|
||||
# The new daemon can take 20-30 seconds to initialise on low-power hardware
|
||||
# (Pi / eMMC). During that time its process.title and port binding are not yet
|
||||
# visible, but the process itself exists in /proc with "openclaw" in its cmdline.
|
||||
#
|
||||
# Strategy:
|
||||
# 1. `wait` for our child (the wrapper). After it exits, use
|
||||
# `find_gateway_daemon_pid` (port → pgrep → /proc scan) with retries
|
||||
# to find the daemon. If found → re-track and poll with `kill -0`.
|
||||
# 2. When the re-tracked daemon eventually exits (crash or another restart),
|
||||
# `kill -0` fails, we check again for a live daemon to re-track.
|
||||
# 3. Before any supervisor-initiated restart, do a final port-occupancy
|
||||
# guard to prevent launching a duplicate.
|
||||
GW_IS_CHILD=true # true only when GW_PID was started by us (can use `wait`)
|
||||
|
||||
while true; do
|
||||
GW_EXIT_CODE=0
|
||||
wait "${GW_PID}" || GW_EXIT_CODE=$?
|
||||
if [ "$GW_IS_CHILD" = "true" ]; then
|
||||
# Efficient blocking wait on our child process.
|
||||
GW_EXIT_CODE=0
|
||||
wait "${GW_PID}" 2>/dev/null || GW_EXIT_CODE=$?
|
||||
else
|
||||
# GW_PID is NOT our child (re-tracked after a self-restart).
|
||||
# Poll with kill -0 until it exits.
|
||||
while kill -0 "$GW_PID" 2>/dev/null; do
|
||||
if [ "$SHUTTING_DOWN" = "true" ]; then break 2; fi
|
||||
sleep 5
|
||||
done
|
||||
GW_EXIT_CODE=0
|
||||
fi
|
||||
|
||||
if [ "$SHUTTING_DOWN" = "true" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
# --- Detect self-restart ---------------------------------------------------
|
||||
# Try up to 10 times (≈ 20 s) using all 3 tiers of find_gateway_daemon_pid.
|
||||
# Tier 3 (/proc scan) usually finds the daemon on the very first attempt
|
||||
# because the process exists immediately after fork, even before port bind
|
||||
# or process.title. The retries cover edge cases on extremely slow I/O.
|
||||
RESTARTED_PID=""
|
||||
if [ "$GATEWAY_MODE" != "remote" ]; then
|
||||
for _attempt in 1 2 3 4 5 6 7 8 9 10; do
|
||||
RESTARTED_PID=$(find_gateway_daemon_pid 2>/dev/null || true)
|
||||
[ -n "$RESTARTED_PID" ] && break
|
||||
sleep 2
|
||||
done
|
||||
else
|
||||
sleep 2
|
||||
RESTARTED_PID=$(pgrep -f "openclaw.*node.*run" 2>/dev/null | head -1 || true)
|
||||
fi
|
||||
|
||||
if [ -n "$RESTARTED_PID" ]; then
|
||||
echo "INFO: OpenClaw runtime active (PID $RESTARTED_PID); monitoring."
|
||||
GW_PID="$RESTARTED_PID"
|
||||
GW_IS_CHILD=false
|
||||
continue
|
||||
fi
|
||||
|
||||
# --- Final port guard ------------------------------------------------------
|
||||
# Even if all detection methods missed the daemon during the loop above,
|
||||
# the port may now be bound (the daemon finished initialising while we slept).
|
||||
# Never launch a duplicate if the port is occupied.
|
||||
if [ "$GATEWAY_MODE" != "remote" ] && \
|
||||
ss -tlnp 2>/dev/null | grep -q ":${GATEWAY_INTERNAL_PORT} "; then
|
||||
PORT_PID=$(ss -tlnp 2>/dev/null \
|
||||
| grep ":${GATEWAY_INTERNAL_PORT} " \
|
||||
| sed -n 's/.*pid=\([0-9]*\).*/\1/p' \
|
||||
| head -1 || true)
|
||||
echo "INFO: Gateway port ${GATEWAY_INTERNAL_PORT} occupied by PID ${PORT_PID:-unknown}; monitoring."
|
||||
GW_PID="${PORT_PID:-$GW_PID}"
|
||||
GW_IS_CHILD=false
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "WARN: OpenClaw runtime exited with code ${GW_EXIT_CODE}. Restarting in 2s..."
|
||||
sleep 2
|
||||
|
||||
# Stop the loopback relay BEFORE restarting the gateway (tailnet mode only).
|
||||
# The relay holds 127.0.0.1:GATEWAY_PORT — leaving it up causes the new gateway
|
||||
# to detect the port as occupied and exit with code 1, re-entering the loop.
|
||||
stop_gw_relay
|
||||
|
||||
if ! start_openclaw_runtime; then
|
||||
echo "ERROR: Failed to restart OpenClaw runtime; retrying in 5s..."
|
||||
sleep 5
|
||||
else
|
||||
GW_IS_CHILD=true
|
||||
start_gw_relay
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -42,6 +42,14 @@ configuration:
|
||||
clean_session_locks_on_exit:
|
||||
name: Изчистване на заключвания при изход
|
||||
description: Изчистване на заключващи файлове на сесии при нормално спиране на добавката
|
||||
|
||||
persist_node_global:
|
||||
name: Запазване на npm глобални умения
|
||||
description: Когато е ВКЛ., съхранява инсталираните от потребителя npm умения и глобални пакети в /config/.node_global, за да оцелеят при преизграждане на добавката. По подразбиране е ИЗКЛ. за по-малки HA архиви.
|
||||
|
||||
persist_brew_tools:
|
||||
name: Запазване на Homebrew инструменти
|
||||
description: Когато е ВКЛ., съхранява Homebrew и инсталираните с brew CLI инструменти в /config/.linuxbrew, за да оцелеят при преизграждане на добавката. По подразбиране е ИЗКЛ. за по-малки HA архиви.
|
||||
|
||||
gateway_mode:
|
||||
name: Режим на Gateway
|
||||
@@ -110,3 +118,6 @@ configuration:
|
||||
gateway_env_vars:
|
||||
name: Променливи на средата за Gateway
|
||||
description: Променливи на средата, предавани на OpenClaw gateway процеса при стартиране (списък от записи name/value в Home Assistant UI; макс. 50 променливи, ключове до 255 знака, стойности до 10000 знака).
|
||||
auto_configure_mcp:
|
||||
name: Автоматично конфигуриране на MCP за Home Assistant
|
||||
description: "Когато е ВКЛ. и Home Assistant Token е зададен, автоматично регистрира Home Assistant като MCP сървър при всяко стартиране. Това позволява на OpenClaw да контролира HA устройства, услуги и автоматизации чрез Model Context Protocol. Изключете само ако управлявате mcporter конфигурацията ръчно."
|
||||
|
||||
@@ -42,6 +42,14 @@ configuration:
|
||||
clean_session_locks_on_exit:
|
||||
name: Sitzungssperren beim Beenden bereinigen
|
||||
description: Sitzungssperrdateien beim ordnungsgemäßen Stoppen des Add-ons bereinigen
|
||||
|
||||
persist_node_global:
|
||||
name: npm-Globale Skills dauerhaft speichern
|
||||
description: Wenn EIN, werden benutzerinstallierte npm-Skills und globale Pakete unter /config/.node_global gespeichert und über Add-on-Neubuilds hinweg behalten. Standard ist AUS, um Home-Assistant-Backups klein zu halten.
|
||||
|
||||
persist_brew_tools:
|
||||
name: Homebrew-Tools dauerhaft speichern
|
||||
description: Wenn EIN, werden Homebrew und per brew installierte CLI-Tools unter /config/.linuxbrew gespeichert und über Add-on-Neubuilds hinweg behalten. Standard ist AUS, um Home-Assistant-Backups klein zu halten.
|
||||
|
||||
gateway_mode:
|
||||
name: Gateway-Modus
|
||||
@@ -110,3 +118,6 @@ configuration:
|
||||
gateway_env_vars:
|
||||
name: Gateway-Umgebungsvariablen
|
||||
description: Umgebungsvariablen, die beim Start an den OpenClaw-Gateway-Prozess übergeben werden (Liste mit name/value-Einträgen in der Home Assistant UI; max. 50 Variablen, Schlüssel bis 255 Zeichen, Werte bis 10.000 Zeichen).
|
||||
auto_configure_mcp:
|
||||
name: MCP für Home Assistant automatisch konfigurieren
|
||||
description: "Wenn AN und Home Assistant Token gesetzt ist, wird Home Assistant bei jedem Start automatisch als MCP-Server registriert. Das erlaubt OpenClaw, HA-Geräte, -Dienste und -Automatisierungen über das Model Context Protocol zu steuern. Nur deaktivieren, wenn Sie die mcporter-Konfiguration manuell verwalten."
|
||||
|
||||
@@ -42,6 +42,14 @@ configuration:
|
||||
clean_session_locks_on_exit:
|
||||
name: Clean Session Locks on Exit
|
||||
description: Cleanup session lock files when add-on stops gracefully
|
||||
|
||||
persist_node_global:
|
||||
name: Persist npm Global Skills
|
||||
description: When ON, store user-installed npm skills and global packages under /config/.node_global so they survive add-on rebuilds. Default OFF to keep Home Assistant backups smaller.
|
||||
|
||||
persist_brew_tools:
|
||||
name: Persist Homebrew Tools
|
||||
description: When ON, store Homebrew and brew-installed CLI tools under /config/.linuxbrew so they survive add-on rebuilds. Default OFF to keep Home Assistant backups smaller.
|
||||
|
||||
gateway_mode:
|
||||
name: Gateway Mode
|
||||
@@ -110,3 +118,6 @@ configuration:
|
||||
gateway_env_vars:
|
||||
name: Gateway Environment Variables
|
||||
description: Environment variables passed to the OpenClaw gateway process at startup (list entries with name/value in Home Assistant UI; max 50 variables, keys up to 255 characters, values up to 10,000 characters).
|
||||
auto_configure_mcp:
|
||||
name: Auto-Configure MCP for Home Assistant
|
||||
description: "When ON and Home Assistant Token is set, automatically registers Home Assistant as an MCP server on each startup. This lets OpenClaw control HA entities, services, and automations via the Model Context Protocol. Disable only if you manage mcporter configuration manually."
|
||||
|
||||
@@ -42,6 +42,14 @@ configuration:
|
||||
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
|
||||
|
||||
persist_node_global:
|
||||
name: Conservar habilidades globales de npm
|
||||
description: Cuando está activado, guarda las habilidades npm instaladas por el usuario y los paquetes globales en /config/.node_global para que sobrevivan a reconstrucciones del complemento. Por defecto está desactivado para mantener pequeñas las copias de seguridad de Home Assistant.
|
||||
|
||||
persist_brew_tools:
|
||||
name: Conservar herramientas de Homebrew
|
||||
description: Cuando está activado, guarda Homebrew y las herramientas CLI instaladas con brew en /config/.linuxbrew para que sobrevivan a reconstrucciones del complemento. Por defecto está desactivado para mantener pequeñas las copias de seguridad de Home Assistant.
|
||||
|
||||
gateway_mode:
|
||||
name: Modo del Gateway
|
||||
@@ -110,3 +118,6 @@ configuration:
|
||||
gateway_env_vars:
|
||||
name: Variables de entorno del Gateway
|
||||
description: Variables de entorno que se pasan al proceso del gateway OpenClaw al iniciarse (lista de entradas name/value en la UI de Home Assistant; maximo 50 variables, claves de hasta 255 caracteres, valores de hasta 10 000 caracteres).
|
||||
auto_configure_mcp:
|
||||
name: Configurar MCP automáticamente para Home Assistant
|
||||
description: "Cuando está ACTIVADO y el token de Home Assistant está configurado, registra automáticamente Home Assistant como servidor MCP en cada inicio. Esto permite a OpenClaw controlar entidades, servicios y automatizaciones de HA mediante el Model Context Protocol. Desactive solo si administra la configuración de mcporter manualmente."
|
||||
|
||||
@@ -42,6 +42,14 @@ configuration:
|
||||
clean_session_locks_on_exit:
|
||||
name: Wyczyść blokady sesji przy wyjściu
|
||||
description: Usuń pliki blokad sesji gdy dodatek zatrzymuje się poprawnie
|
||||
|
||||
persist_node_global:
|
||||
name: Zachowuj globalne umiejętności npm
|
||||
description: Po włączeniu zapisuje umiejętności npm instalowane przez użytkownika i pakiety globalne w /config/.node_global, aby przetrwały przebudowę dodatku. Domyślnie wyłączone, aby kopie zapasowe Home Assistant były mniejsze.
|
||||
|
||||
persist_brew_tools:
|
||||
name: Zachowuj narzędzia Homebrew
|
||||
description: Po włączeniu zapisuje Homebrew i narzędzia CLI instalowane przez brew w /config/.linuxbrew, aby przetrwały przebudowę dodatku. Domyślnie wyłączone, aby kopie zapasowe Home Assistant były mniejsze.
|
||||
|
||||
gateway_remote_url:
|
||||
name: URL zdalnego Gateway
|
||||
@@ -110,3 +118,6 @@ configuration:
|
||||
gateway_env_vars:
|
||||
name: Zmienne środowiskowe gateway
|
||||
description: Zmienne środowiskowe przekazywane do procesu gateway OpenClaw podczas uruchamiania (lista wpisow name/value w UI Home Assistant; maks. 50 zmiennych, klucze do 255 znakow, wartosci do 10000 znakow).
|
||||
auto_configure_mcp:
|
||||
name: Automatyczna konfiguracja MCP dla Home Assistant
|
||||
description: "Gdy WŁĄCZONE i token Home Assistant jest ustawiony, automatycznie rejestruje Home Assistant jako serwer MCP przy każdym starcie. Pozwala to OpenClaw kontrolować urządzenia HA, usługi i automatyzacje przez Model Context Protocol. Wyłącz tylko jeśli zarządzasz konfiguracją mcporter ręcznie."
|
||||
|
||||
@@ -42,6 +42,14 @@ configuration:
|
||||
clean_session_locks_on_exit:
|
||||
name: Limpar Bloqueios de Sessão ao Sair
|
||||
description: Limpar arquivos de bloqueio de sessão quando o add-on parar normalmente
|
||||
|
||||
persist_node_global:
|
||||
name: Persistir skills globais do npm
|
||||
description: Quando ativado, armazena skills npm instaladas pelo usuário e pacotes globais em /config/.node_global para sobreviverem a reconstruções do add-on. O padrão é desativado para manter menores os backups do Home Assistant.
|
||||
|
||||
persist_brew_tools:
|
||||
name: Persistir ferramentas do Homebrew
|
||||
description: Quando ativado, armazena o Homebrew e ferramentas CLI instaladas via brew em /config/.linuxbrew para sobreviverem a reconstruções do add-on. O padrão é desativado para manter menores os backups do Home Assistant.
|
||||
|
||||
gateway_mode:
|
||||
name: Modo do Gateway
|
||||
@@ -110,3 +118,6 @@ configuration:
|
||||
gateway_env_vars:
|
||||
name: Variáveis de Ambiente do Gateway
|
||||
description: Variaveis de ambiente passadas para o processo do gateway OpenClaw na inicializacao (lista de entradas name/value na UI do Home Assistant; maximo de 50 variaveis, chaves com ate 255 caracteres, valores com ate 10.000 caracteres).
|
||||
auto_configure_mcp:
|
||||
name: Configurar MCP automaticamente para o Home Assistant
|
||||
description: "Quando LIGADO e o token do Home Assistant estiver definido, registra automaticamente o Home Assistant como servidor MCP a cada inicialização. Isso permite que o OpenClaw controle entidades, serviços e automações do HA via Model Context Protocol. Desative apenas se você gerencia a configuração do mcporter manualmente."
|
||||
|
||||
Reference in New Issue
Block a user