206 lines
9.2 KiB
Bash
206 lines
9.2 KiB
Bash
#!/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 ==="
|