#!/bin/sh set -e echo "=== OpenClaw Dependency & Runtime Bootstrap ===" export PATH="/home/node/.openclaw/bin:/home/node/.openclaw/.local/bin:$PATH" SCRIPTS_DIR="/home/node/.openclaw/scripts" mkdir -p /home/node/.openclaw/bin "$SCRIPTS_DIR" /home/node/.openclaw/.config touch /home/node/.openclaw/.config/keep # 1. Sanitize legacy config keys in openclaw.json node -e ' const fs = require("fs"); for (const file of ["/home/node/.openclaw/openclaw.json", "/home/openclaw/.openclaw/openclaw.json"]) { if (fs.existsSync(file)) { try { const cfg = JSON.parse(fs.readFileSync(file, "utf8")); if (cfg.browser) { delete cfg.browser.remoteCdpTimeoutMs; if (cfg.browser.profiles) { if (cfg.browser.profiles.default) delete cfg.browser.profiles.default.color; if (cfg.browser.profiles.chrome) delete cfg.browser.profiles.chrome; } } if (cfg.gateway && cfg.gateway.controlUi) { delete cfg.gateway.controlUi.dangerouslyDisableDeviceAuth; } if (!cfg.agents) cfg.agents = {}; cfg.agents.ownership = "explicit"; if (cfg.agents.entries && cfg.agents.entries.main) { delete cfg.agents.entries.main.default; } fs.writeFileSync(file, JSON.stringify(cfg, null, 2)); console.log("Sanitized config file:", file); } catch (e) { console.error("Failed to sanitize", file, e.message); } } } ' || true # 2. Run pre-flight session recovery echo "Running pre-flight self-healing..." python3 "$SCRIPTS_DIR/recover_sessions.py" || true # 3. Doctor fix echo "Running openclaw doctor --fix to migrate runtime config on PVC..." openclaw doctor --fix || true # 4. Garmin OAuth tokens bootstrap / preserve if [ ! -s /home/node/.openclaw/garmin_tokens/garmin_tokens.json ] && [ -n "$GARMIN_TOKENS_BASE64" ]; then echo "Bootstrapping initial Garmin OAuth tokens from secret..." mkdir -p /home/node/.openclaw/garmin_tokens echo "$GARMIN_TOKENS_BASE64" | base64 -d > /home/node/.openclaw/garmin_tokens/garmin_tokens.json chmod 700 /home/node/.openclaw/garmin_tokens chmod 600 /home/node/.openclaw/garmin_tokens/garmin_tokens.json elif [ -s /home/node/.openclaw/garmin_tokens/garmin_tokens.json ]; then echo "Preserving live Garmin OAuth tokens on PVC..." chmod 700 /home/node/.openclaw/garmin_tokens chmod 600 /home/node/.openclaw/garmin_tokens/garmin_tokens.json fi # 5. Cleanup legacy binaries and superseded local skills rm -rf /home/node/.openclaw/bin/whisper* /home/node/.openclaw/bin/.whisper* /home/node/.openclaw/scripts/whisper* /home/node/.openclaw/models/whisper || true rm -rf /home/node/.openclaw/skills/memobird /home/node/.openclaw/skills/nerd-report /home/node/.openclaw/skills/rvv-regensburg /home/node/.openclaw/skills/skill-creator || true rm -rf /home/node/.openclaw/workspace/skills /home/node/.openclaw/workspace-koch/skills /home/node/.openclaw/workspace-recherche/skills || true # 6. Ensure tools.allow and models.providers.openrouter.apiKey unset openclaw config unset tools.allow || true openclaw config unset models.providers.openrouter.apiKey || true openclaw config unset agents.entries.mila_tracker.tools.profile || true openclaw config unset agents.entries.nutrition_tracker.tools.profile || true # 7. Decouple hardlinked bootstrap files for dir in /home/node/.openclaw/workspace-*; do if [ -d "$dir" ]; then for file in USER.md MEMORY.md; do if [ -f "/home/node/.openclaw/workspace/$file" ]; then cp --remove-destination "/home/node/.openclaw/workspace/$file" "$dir/$file" chown node:node "$dir/$file" 2>/dev/null || true chmod 664 "$dir/$file" 2>/dev/null || true fi done fi done # 8. Install CLI binaries if [ ! -f "/home/node/.openclaw/bin/openhue.real" ]; then echo "Installing openhue CLI binary..." curl -sL https://github.com/openhue/openhue-cli/releases/download/0.24/openhue_Linux_x86_64.tar.gz | tar -xz -C /home/node/.openclaw/bin openhue mv /home/node/.openclaw/bin/openhue /home/node/.openclaw/bin/openhue.real printf '#!/bin/sh\nexport HOME=/home/node/.openclaw\nexec /home/node/.openclaw/bin/openhue.real "$@"\n' > /home/node/.openclaw/bin/openhue chmod +x /home/node/.openclaw/bin/openhue /home/node/.openclaw/bin/openhue.real fi if [ ! -f "/home/node/.openclaw/bin/summarize" ]; then echo "Installing summarize CLI via npm..." npm install -g --prefix /home/node/.openclaw @steipete/summarize || true fi if [ ! -f "/home/node/.openclaw/bin/mcp-server-github" ]; then echo "Installing @modelcontextprotocol/server-github via npm..." npm install -g --prefix /home/node/.openclaw @modelcontextprotocol/server-github || true fi if [ ! -f "/home/node/.openclaw/bin/kubectl" ]; then echo "Installing kubectl CLI binary..." curl -fsSL https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl -o /home/node/.openclaw/bin/kubectl chmod +x /home/node/.openclaw/bin/kubectl fi if [ ! -f "/home/node/.openclaw/bin/jq" ]; then echo "Installing jq CLI binary..." curl -fsSL https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 -o /home/node/.openclaw/bin/jq chmod +x /home/node/.openclaw/bin/jq fi if [ ! -f "/home/node/.openclaw/bin/file" ]; then echo "Setting up file CLI helper..." cp "$SCRIPTS_DIR/file_cli.py" /home/node/.openclaw/bin/file chmod +x /home/node/.openclaw/bin/file fi if [ -x "/home/node/.openclaw/.local/bin/python3" ]; then /home/node/.openclaw/bin/uv pip install --break-system-packages --python /home/node/.openclaw/.local/bin/python3 pillow google-api-python-client pypdf || true fi # 9. Clone or pull infrapuzzle repository to PVC mkdir -p /home/node/.openclaw/repos if [ ! -d "/home/node/.openclaw/repos/infrapuzzle/.git" ]; then echo "Cloning infrapuzzle repository to PVC..." git clone --depth 1 "https://${GIT_SYNC_USERNAME}:${GIT_SYNC_PASSWORD}@git.moritzgraf.de/moritz/infrapuzzle.git" /home/node/.openclaw/repos/infrapuzzle || true else echo "Pulling latest infrapuzzle commits on PVC..." git -C /home/node/.openclaw/repos/infrapuzzle pull origin master || git -C /home/node/.openclaw/repos/infrapuzzle pull origin main || true fi # 10. Seed and maintain persistent mcp-venv if [ ! -d "/home/node/.openclaw/mcp-venv" ]; then echo "Seeding persistent mcp-venv..." uv venv --python 3.12 /home/node/.openclaw/mcp-venv uv pip install --python /home/node/.openclaw/mcp-venv \ prometheus-mcp-server \ ha-mcp \ chuk-mcp-open-meteo \ gitea-mcp \ git+https://github.com/Taxuspt/garmin_mcp \ "mcp>=2.0" \ filetype \ google-api-python-client \ google-auth \ bring-api \ requests \ beautifulsoup4 \ youtube-transcript-api fi if [ -d "/home/node/.openclaw/mcp-venv" ]; then python3 -c 'import os; p="/home/node/.openclaw/mcp-venv/lib/python3.12/site-packages/mcp/server/fastmcp.py"; open(p, "w").write("import os\nfrom fastmcp import FastMCP as _FastMCP, Context\n\nclass FastMCP(_FastMCP):\n def __init__(self, name=\"MCP Server\", host=None, port=None, **kwargs):\n if host is not None: os.environ[\"FASTMCP_HOST\"] = str(host)\n if port is not None: os.environ[\"FASTMCP_PORT\"] = str(port)\n super().__init__(name=name, **kwargs)\n")' || true python3 "$SCRIPTS_DIR/patch_garmin_mcp.py" || true fi # 11. Extensions & Plugins if [ ! -d "/home/node/.openclaw/extensions/perplexity" ]; then echo "Installing perplexity plugin..." openclaw plugins install clawhub:@openclaw/perplexity-plugin --accept-capabilities || true fi echo "Enabling memory-wiki plugin..." openclaw plugins enable memory-wiki || true echo "Patching perplexity web search runtime for chat completions compatibility..." node "$SCRIPTS_DIR/patch_perplexity.mjs" || true # 12. Ensure OpenRouter auth profile in state DB echo "Ensuring OpenRouter auth profile in state DB..." node --input-type=module -e ' import { DatabaseSync } from "node:sqlite"; const key = process.env.OPENROUTER_API_KEY; if (key) { try { const dbPath = "/home/node/.openclaw/state/openclaw.sqlite"; const db = new DatabaseSync(dbPath); db.prepare("CREATE TABLE IF NOT EXISTS config_machine_state (state_key TEXT PRIMARY KEY, value_json TEXT NOT NULL, updated_at_ms INTEGER NOT NULL)").run(); const row = db.prepare("SELECT value_json FROM config_machine_state WHERE state_key = \x27authProfiles.store\x27").get(); let store = row?.value_json ? JSON.parse(row.value_json) : { version: 1, profiles: {} }; if (!store.profiles) store.profiles = {}; store.profiles["openrouter:default"] = { type: "api_key", provider: "openrouter", key: key }; db.prepare("INSERT INTO config_machine_state (state_key, value_json, updated_at_ms) VALUES (\x27authProfiles.store\x27, ?, ?) ON CONFLICT(state_key) DO UPDATE SET value_json = excluded.value_json, updated_at_ms = excluded.updated_at_ms") .run(JSON.stringify(store), Date.now()); console.log("Synchronized openrouter auth profile in state DB."); } catch (e) { console.error("Could not sync auth profile:", e.message); } } ' || true # 13. Pre-warm SQLite page cache echo "Pre-warming SQLite database files into OS page cache for instant cold start..." cat /home/node/.openclaw/state/openclaw.sqlite /home/node/.openclaw/agents/*/agent/openclaw-agent.sqlite > /dev/null 2>&1 || true echo "=== Bootstrap Complete ==="