Config openclaw via node (merge seguro): re-correr no corrompe JSON, auto-reparacion desde .last-good y canal Telegram (allowlist)

This commit is contained in:
renato97
2026-08-03 22:33:53 -03:00
parent 344e49d4cc
commit bea9779823
+127 -81
View File
@@ -1,21 +1,25 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================= # =============================================================================
# OpenClaw All-in-One — instalación en 1 click # OpenClaw All-in-One — instalación en 1 click (v2, idempotente)
# ============================================= # =============================================================================
# Instala y pre-configura: # Instala y pre-configura:
# • openclaw (CLI + Gateway daemon, systemd usuario) # • openclaw (CLI + Gateway daemon, systemd usuario)
# • free-ide-proxy (endpoint OpenAI-compatible local en 127.0.0.1:6446) # • free-ide-proxy (endpoint OpenAI-compatible local en 127.0.0.1:6446)
# Con el modelo por defecto deepseek-v4-flash-free apuntando a TU proxy. # • Canal Telegram (opcional, si pasás TELEGRAM_BOT_TOKEN + TELEGRAM_USER_ID)
# Con tu modelo apuntando a TU proxy. Reescribir la config es SEGURO:
# usa node (JSON.parse/stringify), hace backup, y se auto-repara si el
# openclaw.json previo está corrupto (cae al .last-good).
# #
# Uso (Linux/macOS): # Uso (Linux/macOS):
# curl -fsSL https://gitea.cbcren.online/renato97/openclaw-allinone/raw/branch/main/install.sh | bash # curl -fsSL https://gitea.cbcren.online/renato97/openclaw-allinone/raw/branch/main/install.sh | bash
# #
# Variables de entorno opcionales (override): # Con Telegram:
# MODEL modelo a usar (default: deepseek-v4-flash-free) # curl -fsSL https://gitea.cbcren.online/renato97/openclaw-allinone/raw/branch/main/install.sh | \
# PROXY_API_KEY clave del proxy (default: embebida, local-only) # TELEGRAM_BOT_TOKEN=123:abc TELEGRAM_USER_ID=123456789 bash
# GATEWAY_PORT puerto del gateway de openclaw (default: 18789) #
# GITEA_URL base de tu Gitea (default: https://gitea.cbcren.online/) # Variables (override):
# GITEA_USER usuario de tu Gitea (default: renato97) # MODEL, PROXY_API_KEY, GATEWAY_PORT, PROXY_PORT, GITEA_URL, GITEA_USER,
# PROXY_REPO, PROXY_VERSION, TELEGRAM_BOT_TOKEN, TELEGRAM_USER_ID
# ============================================================================= # =============================================================================
set -euo pipefail set -euo pipefail
@@ -33,10 +37,10 @@ GITEA_USER="${GITEA_USER:-renato97}"
PROXY_REPO="${PROXY_REPO:-free-ide-proxy}" PROXY_REPO="${PROXY_REPO:-free-ide-proxy}"
PROXY_VERSION="${PROXY_VERSION:-v1.0.0}" PROXY_VERSION="${PROXY_VERSION:-v1.0.0}"
OPENCLAW_VER="${OPENCLAW_VER:-2026.7.1-2}" OPENCLAW_VER="${OPENCLAW_VER:-2026.7.1-2}"
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_USER_ID="${TELEGRAM_USER_ID:-}"
# Normalizar GITEA_URL (sin barra final)
GITEA_URL="${GITEA_URL%/}" GITEA_URL="${GITEA_URL%/}"
export PATH="$HOME/.local/bin:$HOME/.openclaw/bin:/usr/local/bin:$PATH" export PATH="$HOME/.local/bin:$HOME/.openclaw/bin:/usr/local/bin:$PATH"
# ---------------- Detectar OS / arch ---------------- # ---------------- Detectar OS / arch ----------------
@@ -51,7 +55,7 @@ case "$(uname -m)" in
aarch64|arm64) ARCH=arm64 ;; aarch64|arm64) ARCH=arm64 ;;
*) ARCH=source ;; *) ARCH=source ;;
esac esac
log "OS=$OS arch=$(uname -m) modelo=$MODEL gateway=:$GATEWAY_PORT proxy=:$PROXY_PORT" log "OS=$OS arch=$(uname -m) modelo=$MODEL gateway=:$GATEWAY_PORT proxy=:$PROXY_PORT telegram=$([ -n "$TELEGRAM_BOT_TOKEN" ] && echo 'si' || echo 'no')"
# ---------------- 1) openclaw ---------------- # ---------------- 1) openclaw ----------------
if command -v openclaw >/dev/null 2>&1; then if command -v openclaw >/dev/null 2>&1; then
@@ -64,6 +68,7 @@ else
curl -fsSL https://openclaw.ai/install.sh | bash curl -fsSL https://openclaw.ai/install.sh | bash
fi fi
command -v openclaw >/dev/null 2>&1 || die "openclaw no quedó disponible en PATH" command -v openclaw >/dev/null 2>&1 || die "openclaw no quedó disponible en PATH"
command -v node >/dev/null 2>&1 || die "node no está disponible"
OPENCLAW_BIN="$(command -v openclaw)" OPENCLAW_BIN="$(command -v openclaw)"
# ---------------- 2) free-ide-proxy ---------------- # ---------------- 2) free-ide-proxy ----------------
@@ -83,14 +88,13 @@ if [ "$PROXY_OK" -eq 0 ]; then
chmod +x "$PROXY_BIN" chmod +x "$PROXY_BIN"
else else
warn "No hay binario precompilado para $OS/$ARCH — compilando desde fuente (requiere Go 1.22+)..." warn "No hay binario precompilado para $OS/$ARCH — compilando desde fuente (requiere Go 1.22+)..."
command -v go >/dev/null 2>&1 || die "Go no está instalado. Instala Go 1.22+ o usa Linux/amd64 con el binario precompilado." command -v go >/dev/null 2>&1 || die "Go no está instalado. Instala Go 1.22+ o usa Linux/amd64."
TMP="$(mktemp -d)" TMP="$(mktemp -d)"
git clone --depth 1 "${GITEA_URL}/${GITEA_USER}/${PROXY_REPO}.git" "$TMP/proxy" git clone --depth 1 "${GITEA_URL}/${GITEA_USER}/${PROXY_REPO}.git" "$TMP/proxy"
(cd "$TMP/proxy" && go build -o "$PROXY_BIN" .) (cd "$TMP/proxy" && go build -o "$PROXY_BIN" .)
rm -rf "$TMP" rm -rf "$TMP"
fi fi
# systemd unit a nivel usuario (no requiere root)
mkdir -p "$HOME/.config/systemd/user" mkdir -p "$HOME/.config/systemd/user"
cat > "$HOME/.config/systemd/user/free-ide-proxy.service" <<UNIT cat > "$HOME/.config/systemd/user/free-ide-proxy.service" <<UNIT
[Unit] [Unit]
@@ -112,71 +116,113 @@ UNIT
export XDG_RUNTIME_DIR="/run/user/$(id -u)" export XDG_RUNTIME_DIR="/run/user/$(id -u)"
export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus" export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"
mkdir -p "$XDG_RUNTIME_DIR" 2>/dev/null || true mkdir -p "$XDG_RUNTIME_DIR" 2>/dev/null || true
log "Habilitando linger para arrancar los servicios de usuario al boot..." log "Habilitando linger (servicios de usuario al boot)..."
(sudo loginctl enable-linger "$USER" 2>/dev/null || loginctl enable-linger "$USER" 2>/dev/null) || warn "no se pudo enable-linger (los servicios de usuario no arrancarán solos al boot)" (sudo loginctl enable-linger "$USER" 2>/dev/null || loginctl enable-linger "$USER" 2>/dev/null) || warn "no se pudo enable-linger"
systemctl --user daemon-reload systemctl --user daemon-reload
systemctl --user enable --now free-ide-proxy.service systemctl --user enable --now free-ide-proxy.service
sleep 2 sleep 2
curl -fsS "http://127.0.0.1:${PROXY_PORT}/health" >/dev/null 2>&1 || die "free-ide-proxy no quedó arriba en :${PROXY_PORT}" curl -fsS "http://127.0.0.1:${PROXY_PORT}/health" >/dev/null 2>&1 || die "free-ide-proxy no quedó arriba en :${PROXY_PORT}"
log "free-ide-proxy activo en http://127.0.0.1:${PROXY_PORT} (${MODEL} y más)" log "free-ide-proxy activo en http://127.0.0.1:${PROXY_PORT}"
fi fi
# ---------------- 3) Config de openclaw (pre-cargada) ---------------- # ---------------- 3) Config de openclaw (merge SEGURO via node) ----------------
# Reutiliza el token del gateway si ya existía una config (idempotencia). # Si otro servicio (ej. un bot viejo) usa el mismo bot de Telegram, lo detiene
GATEWAY_TOKEN="" # para evitar conflicto de long-polling.
if [ -f "$HOME/.openclaw/openclaw.json" ]; then if [ -n "$TELEGRAM_BOT_TOKEN" ] && systemctl list-unit-files --type=service 2>/dev/null | grep -q 'vps-bot.service'; then
GATEWAY_TOKEN="$(grep -o '"token"[[:space:]]*:[[:space:]]*"[^"]*"' "$HOME/.openclaw/openclaw.json" | head -1 | sed -E 's/.*:"([^"]*)"/\1/')" log "Deteniendo vps-bot.service (usaría el mismo bot de Telegram)..."
cp "$HOME/.openclaw/openclaw.json" "$HOME/.openclaw/openclaw.json.bak.$(date +%s)" sudo systemctl stop vps-bot.service 2>/dev/null || true
log "Backup de config existente guardado en ~/.openclaw/openclaw.json.bak.*" sudo systemctl disable vps-bot.service 2>/dev/null || true
fi fi
GATEWAY_TOKEN="${GATEWAY_TOKEN:-$(openssl rand -hex 24 2>/dev/null || cat /dev/urandom | tr -dc 'a-f0-9' | head -c 48)}"
mkdir -p "$HOME/.openclaw/workspace" mkdir -p "$HOME/.openclaw/workspace"
cat > "$HOME/.openclaw/openclaw.json" <<JSON export OPENCLAW_MODEL="$MODEL"
{ export OPENCLAW_PROXY_KEY="$PROXY_API_KEY"
"agents": { export OPENCLAW_PROXY_URL="http://127.0.0.1:${PROXY_PORT}/v1"
"defaults": { export OPENCLAW_GATEWAY_PORT="$GATEWAY_PORT"
"workspace": "$HOME/.openclaw/workspace", export OPENCLAW_TG_TOKEN="$TELEGRAM_BOT_TOKEN"
"skipBootstrap": true, export OPENCLAW_TG_USER="$TELEGRAM_USER_ID"
"model": { "primary": "free-ide/$MODEL" },
"models": { "free-ide/$MODEL": {} } node - "$HOME/.openclaw" <<'NODE'
} const fs = require('fs');
}, const path = require('path');
"gateway": { const crypto = require('crypto');
"mode": "local", const dir = process.argv[2];
"auth": { "mode": "token", "token": "$GATEWAY_TOKEN" }, const cfgPath = path.join(dir, 'openclaw.json');
"port": $GATEWAY_PORT, const lastGood = path.join(dir, 'openclaw.json.last-good');
"bind": "loopback",
"tailscale": { "mode": "off", "resetOnExit": false } const readSafe = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } };
},
"session": { "dmScope": "per-channel-peer" }, let cfg = readSafe(cfgPath);
"tools": { "profile": "coding" }, let base = cfg ? 'existing' : 'none';
"models": { if (!cfg) {
"mode": "merge", if (fs.existsSync(cfgPath)) fs.copyFileSync(cfgPath, cfgPath + '.corrupt.' + Date.now());
"providers": { cfg = readSafe(lastGood) || {};
"free-ide": { base = readSafe(lastGood) ? 'last-good' : 'fresh';
"baseUrl": "http://127.0.0.1:$PROXY_PORT/v1",
"api": "openai-completions",
"apiKey": "$PROXY_API_KEY",
"models": [
{
"id": "$MODEL",
"name": "$MODEL (Custom Provider)",
"contextWindow": 128000,
"maxTokens": 4096,
"input": ["text"],
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
"reasoning": false
}
]
}
}
},
"hooks": { "internal": { "entries": { "session-memory": { "enabled": true } } } }
} }
JSON if (fs.existsSync(cfgPath)) fs.copyFileSync(cfgPath, cfgPath + '.bak.' + Date.now());
const model = process.env.OPENCLAW_MODEL || 'deepseek-v4-flash-free';
const tgToken = process.env.OPENCLAW_TG_TOKEN || '';
const tgUser = process.env.OPENCLAW_TG_USER || '';
cfg.agents = cfg.agents || {};
cfg.agents.defaults = cfg.agents.defaults || {};
cfg.agents.defaults.workspace = path.join(dir, 'workspace');
cfg.agents.defaults.skipBootstrap = true;
cfg.agents.defaults.model = { primary: 'free-ide/' + model };
cfg.agents.defaults.models = { ['free-ide/' + model]: {} };
cfg.gateway = cfg.gateway || {};
cfg.gateway.mode = 'local';
cfg.gateway.auth = cfg.gateway.auth || {};
cfg.gateway.auth.mode = 'token';
if (!cfg.gateway.auth.token) cfg.gateway.auth.token = crypto.randomBytes(24).toString('hex');
cfg.gateway.port = parseInt(process.env.OPENCLAW_GATEWAY_PORT || '18789', 10);
cfg.gateway.bind = 'loopback';
cfg.gateway.tailscale = { mode: 'off', resetOnExit: false };
cfg.session = cfg.session || { dmScope: 'per-channel-peer' };
cfg.tools = cfg.tools || { profile: 'coding' };
cfg.models = cfg.models || {};
cfg.models.mode = 'merge';
cfg.models.providers = cfg.models.providers || {};
cfg.models.providers['free-ide'] = {
baseUrl: process.env.OPENCLAW_PROXY_URL,
api: 'openai-completions',
apiKey: process.env.OPENCLAW_PROXY_KEY,
models: [{
id: model,
name: model + ' (Custom Provider)',
contextWindow: 128000,
maxTokens: 4096,
input: ['text'],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
reasoning: false
}]
};
if (tgToken) {
cfg.channels = cfg.channels || {};
cfg.channels.telegram = {
enabled: true,
botToken: tgToken,
dmPolicy: 'allowlist',
allowFrom: tgUser ? [tgUser] : []
};
cfg.commands = cfg.commands || {};
cfg.commands.ownerAllowFrom = [...new Set([...(cfg.commands.ownerAllowFrom || []), ...(tgUser ? ['telegram:' + tgUser] : [])])];
}
const tmp = cfgPath + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2), { mode: 0o600 });
fs.chmodSync(tmp, 0o600);
fs.renameSync(tmp, cfgPath);
fs.writeFileSync(lastGood, JSON.stringify(cfg, null, 2));
console.log('config OK (base=' + base + ') modelo=free-ide/' + model + ' telegram=' + (tgToken ? 'si' : 'no'));
NODE
chmod 600 "$HOME/.openclaw/openclaw.json" chmod 600 "$HOME/.openclaw/openclaw.json"
log "Config escrita en ~/.openclaw/openclaw.json (modelo: free-ide/$MODEL)" log "Config escrita (con backup en ~/.openclaw/openclaw.json.bak.*)"
# ---------------- 4) Gateway daemon ---------------- # ---------------- 4) Gateway daemon ----------------
export XDG_RUNTIME_DIR="/run/user/$(id -u)" export XDG_RUNTIME_DIR="/run/user/$(id -u)"
@@ -188,7 +234,7 @@ log "Instalando daemon del gateway (systemd usuario)..."
systemctl --user daemon-reload 2>/dev/null || true systemctl --user daemon-reload 2>/dev/null || true
systemctl --user enable openclaw-gateway.service >/dev/null 2>&1 || true systemctl --user enable openclaw-gateway.service >/dev/null 2>&1 || true
if systemctl --user is-active --quiet openclaw-gateway.service 2>/dev/null; then if systemctl --user is-active --quiet openclaw-gateway.service 2>/dev/null; then
log "Reiniciando gateway para tomar la config nueva..." log "Reiniciando gateway para tomar la config..."
systemctl --user restart openclaw-gateway.service >/dev/null 2>&1 || true systemctl --user restart openclaw-gateway.service >/dev/null 2>&1 || true
else else
systemctl --user start openclaw-gateway.service >/dev/null 2>&1 || { systemctl --user start openclaw-gateway.service >/dev/null 2>&1 || {
@@ -197,19 +243,18 @@ else
systemctl --user start openclaw-gateway.service >/dev/null 2>&1 || true systemctl --user start openclaw-gateway.service >/dev/null 2>&1 || true
} }
fi fi
sleep 4 sleep 5
# ---------------- 5) Verificación ---------------- # ---------------- 5) Verificación ----------------
log "Verificando proxy..." log "Verificando proxy..."
curl -fsS "http://127.0.0.1:${PROXY_PORT}/health" && echo curl -fsS "http://127.0.0.1:${PROXY_PORT}/health" && echo
log "Verificando gateway ($GATEWAY_PORT)..." log "Estado del gateway..."
(ss -tln 2>/dev/null || netstat -tln 2>/dev/null) | grep -q ":$GATEWAY_PORT " && echo "gateway escuchando en :$GATEWAY_PORT" || warn "no se detectó el puerto $GATEWAY_PORT (revisa: systemctl --user status openclaw-gateway)" systemctl --user is-active openclaw-gateway.service 2>&1
(ss -tln 2>/dev/null || netstat -tln 2>/dev/null) | grep -q ":$GATEWAY_PORT " && echo "gateway escuchando en :$GATEWAY_PORT" || warn "no se detectó :$GATEWAY_PORT (systemctl --user status openclaw-gateway)"
if timeout 60 "$OPENCLAW_BIN" agent -m "Responde SOLO con: OK" --agent main >/dev/null 2>&1; then if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
log "Inferencia verificada: el agente respondió OK con $MODEL." log "Canal Telegram configurado. Estado:"
else timeout 30 "$OPENCLAW_BIN" channels status 2>&1 | grep -iE 'telegram|channel|connected|error' | head -15 || true
warn "La verificación de inferencia no respondió en 60s. El gateway puede estar calentando; probá luego:"
warn " openclaw agent -m 'hola' --agent main"
fi fi
echo echo
@@ -218,9 +263,10 @@ echo " ✅ OpenClaw listo"
echo " Modelo : $MODEL" echo " Modelo : $MODEL"
echo " Endpoint : http://127.0.0.1:${PROXY_PORT}/v1" echo " Endpoint : http://127.0.0.1:${PROXY_PORT}/v1"
echo " Gateway : ws://127.0.0.1:${GATEWAY_PORT}" echo " Gateway : ws://127.0.0.1:${GATEWAY_PORT}"
echo " Servicios : free-ide-proxy + openclaw-gateway (systemd usuario)" if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
echo " Telegram : bot conectado (DM, dmPolicy=allowlist, owner=$TELEGRAM_USER_ID)"
fi
echo "----------------------------------------------------------------------------" echo "----------------------------------------------------------------------------"
echo " Chat: openclaw chat" echo " Chat: openclaw chat | Status: openclaw gateway status"
echo " Status: openclaw gateway status" echo " Panel: openclaw dashboard | Canales: openclaw channels status"
echo " Panel: openclaw dashboard"
echo "==============================================================================" echo "=============================================================================="