diff --git a/DOCS.md b/DOCS.md index f73a834..599f105 100644 --- a/DOCS.md +++ b/DOCS.md @@ -29,7 +29,7 @@ The add-on container runs three services: | Service | Port | Purpose | |---|---|---| | **OpenClaw Gateway** | 18789 (configurable) | The AI agent server — handles skills, chat, automations | -| **nginx** (Ingress proxy) | 8099 (fixed) | Serves the landing page inside Home Assistant | +| **nginx** (Ingress proxy) | 48099 (fixed) | Serves the landing page inside Home Assistant | | **ttyd** (Web terminal) | 7681 (configurable) | Provides a browser-based terminal for setup and management | When you open the add-on page in Home Assistant, nginx serves a landing page with: @@ -46,6 +46,7 @@ When you open the add-on page in Home Assistant, nginx serves a landing page wit | `/config/.node_global/` | Yes | User-installed npm packages (skills installed via dashboard) | | `/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 | | `/usr/lib/node_modules/openclaw/` | No | OpenClaw installation (rebuilt with each image update) | > **Important**: Everything under `/config/` persists across add-on updates. The container filesystem (`/usr/`, `/opt/`, etc.) is rebuilt each time the image changes. @@ -381,7 +382,7 @@ The connection details are also saved to `/config/CONNECTION_NOTES.txt` for refe | User-installed npm skills | `/config/.node_global/` | Yes | | SSH keys | `/config/keys/` | Yes | | Tokens | `/config/secrets/` | Yes | -| Homebrew-installed tools | `/home/linuxbrew/` | **No** — container filesystem | +| Homebrew & brew-installed tools | `/config/.linuxbrew/` | Yes (synced on startup) | | OpenClaw binary | `/usr/lib/node_modules/openclaw/` | **No** — reinstalled from image | ### How built-in skills work @@ -402,7 +403,13 @@ The add-on also configures `pnpm` global directory to persistent storage at `/co ### Homebrew-installed tools -Some skills depend on CLI tools installed via Homebrew (e.g., `gemini`, `aider`). These are installed into `/home/linuxbrew/` which is **not persistent** — they are lost on image rebuild. Skills will typically reinstall their dependencies automatically when needed. +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: + +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. --- @@ -443,7 +450,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 packages are lost (reinstalled by skills on demand) +- Homebrew and brew-installed packages are preserved (synced to `/config/.linuxbrew/`) - The OpenClaw binary is updated to the version in the new image ### Checking your version @@ -490,9 +497,9 @@ Then restart the add-on. It will re-bootstrap a fresh configuration. Go to **Settings → Add-ons → OpenClaw Assistant → Log** tab. Logs show startup messages, errors, and service status. -### Port 8099 conflict (add-on page won't load) +### Port 48099 conflict (add-on page won't load) -**Symptom**: `bind() to 0.0.0.0:8099 failed (98: Address already in use)` in logs. +**Symptom**: `bind() to 0.0.0.0:48099 failed (98: Address already in use)` in logs. **Cause**: A stale nginx process from a previous run is still holding the port. This can happen after a crash or unclean restart. diff --git a/openclaw_assistant/config.yaml b/openclaw_assistant/config.yaml index 729faaf..fa47c87 100644 --- a/openclaw_assistant/config.yaml +++ b/openclaw_assistant/config.yaml @@ -1,5 +1,5 @@ name: OpenClaw Assistant -version: "0.5.41" +version: "0.5.42" slug: openclaw_assistant description: Run OpenClaw Assistant (OpenClaw-compatible) as a Home Assistant add-on. url: https://github.com/techartdev/OpenClawHomeAssistant @@ -16,7 +16,7 @@ host_network: true # Home Assistant Ingress (UI inside the add-on page) ingress: true -ingress_port: 8099 +ingress_port: 48099 panel_title: OpenClaw Assistant panel_icon: mdi:robot diff --git a/openclaw_assistant/nginx.conf.tpl b/openclaw_assistant/nginx.conf.tpl index d344673..36ac288 100644 --- a/openclaw_assistant/nginx.conf.tpl +++ b/openclaw_assistant/nginx.conf.tpl @@ -19,7 +19,7 @@ http { # Ingress note: keep redirects relative so we stay under HA Ingress. server { - listen 8099; + listen 48099; # Web terminal (ttyd) # ttyd base-path is configured as /terminal (no trailing slash). diff --git a/openclaw_assistant/run.sh b/openclaw_assistant/run.sh index 0d151df..4dfe766 100644 --- a/openclaw_assistant/run.sh +++ b/openclaw_assistant/run.sh @@ -114,6 +114,45 @@ export PNPM_HOME="${PERSISTENT_NODE_GLOBAL}/pnpm" mkdir -p "$PNPM_HOME" export PATH="${PNPM_HOME}:${PATH}" +# ------------------------------------------------------------------------------ +# 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. +# ------------------------------------------------------------------------------ +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 + else + cp -ru "$IMAGE_BREW_DIR/"* "$PERSISTENT_BREW_DIR/" 2>/dev/null || true + fi + echo "INFO: Synced Homebrew updates to 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" + 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" +else + echo "INFO: Homebrew not available (install may have failed during image build)" +fi + # Back-compat: some docs/scripts assume /data; point it at /config. if [ ! -e /data ]; then ln -s /config /data || true @@ -341,14 +380,14 @@ if [ -f "$NGINX_PID_FILE" ]; then fi rm -f "$NGINX_PID_FILE" fi -# Also kill any orphaned nginx workers that might hold port 8099 +# Also kill any orphaned nginx workers that might hold port 48099 if command -v pkill >/dev/null 2>&1; then pkill -f "nginx.*-c /etc/nginx/nginx.conf" 2>/dev/null || true sleep 1 fi -# Verify port 8099 is actually free before proceeding -if command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ':8099 '; then - echo "WARN: Port 8099 still in use after cleanup; nginx may fail to start" +# Verify port 48099 is actually free before proceeding +if command -v ss >/dev/null 2>&1 && ss -tlnp 2>/dev/null | grep -q ':48099 '; then + echo "WARN: Port 48099 still in use after cleanup; nginx may fail to start" fi # Render nginx config from template. @@ -390,7 +429,7 @@ except Exception: pass PY -echo "Starting ingress proxy (nginx) on :8099 ..." +echo "Starting ingress proxy (nginx) on :48099 ..." nginx -g 'daemon off;' & NGINX_PID=$! sleep 1