From 5e2b31385f521f0d9d4db67c1d805ee37bdf26f3 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 12 Mar 2026 02:13:11 +0100 Subject: [PATCH] added claude login for credentials creation if credentials not exist --- CHANGELOG.md | 4 +- diagnostic/index.html | 49 +++++++++++++++++++++ diagnostic/server.js | 99 +++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 2 +- 4 files changed, 152 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb28509..0c37dda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c - Status-Karten: OpenClaw Gateway, RVS, Claude Proxy — jeweils mit Dot-Indicator - Claude Proxy Test: Prüft Erreichbarkeit (`/v1/models`) und sendet Test-Prompt an Claude — zeigt verfügbare Modelle als Tags + `DEFAULT_MODEL` Hinweis für docker-compose.yml - Auth-Check: "Auth prüfen" Button zeigt Inhalt von `/root/.config/claude/` im Proxy-Container (Dateien + `.credentials.json`) — per Docker Exec API -- Docker Exec API: Generische `dockerExec()` Funktion für Befehle in laufenden Containern (via Docker Socket) +- Claude Login via UI: "Login starten" Button führt `claude login` im Proxy-Container aus, streamt Output live (URL + Code), klickbarer Link zum Autorisieren +- Docker Exec API: Generische `dockerExec()` + Streaming-Variante `dockerExecStreaming()` für Befehle in laufenden Containern (via Docker Socket) - Chat-Test: Nachrichten direkt über Gateway oder via RVS senden - Tabbed Logs: Separate Tabs für Alle, Gateway, RVS, Proxy, Server — mit Zähler pro Tab - Autoscroll-Pause: Automatisch wenn hochgescrollt, "Nach unten" Button zum Fortsetzen @@ -37,6 +38,7 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c - `OPENCLAW_GATEWAY_TOKEN` statt `AUTH_TOKEN` — korrekter Env-Var-Name - `ARIA_AUTH_TOKEN` an Bridge und Diagnostic durchgereicht - Port 3001 auf aria-Service gemappt (für Diagnostic Web-UI) +- Proxy Claude-Config Volume `:ro` → `:rw` — Login via Diagnostic-UI braucht Schreibzugriff ### Behoben diff --git a/diagnostic/index.html b/diagnostic/index.html index e2036fe..1bad9e0 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -108,8 +108,20 @@
+ + @@ -291,6 +303,34 @@ el.textContent = msg.error ? `Fehler: ${msg.error}` : msg.info; return; } + if (msg.type === 'login_output') { + const el = document.getElementById('login-output'); + el.textContent += msg.text + '\n'; + el.scrollTop = el.scrollHeight; + return; + } + if (msg.type === 'login_url') { + const el = document.getElementById('login-output'); + el.textContent += msg.raw + '\n'; + el.scrollTop = el.scrollHeight; + document.getElementById('login-url-box').style.display = 'block'; + const link = document.getElementById('login-url-link'); + link.href = msg.url; + link.textContent = msg.url; + return; + } + if (msg.type === 'login_status') { + if (msg.status === 'done') { + document.getElementById('login-done').style.display = 'block'; + document.getElementById('btn-proxy-login').disabled = false; + document.getElementById('btn-proxy-login').textContent = 'Erneut einloggen'; + } else if (msg.status === 'error') { + const el = document.getElementById('login-output'); + el.textContent += 'FEHLER: ' + msg.error + '\n'; + document.getElementById('btn-proxy-login').disabled = false; + } + return; + } if (msg.type === 'docker_logs') { showDockerLogs(msg); return; @@ -327,6 +367,15 @@ send({ action: 'check_proxy_auth' }); } + function startProxyLogin() { + document.getElementById('proxy-login-box').style.display = 'block'; + document.getElementById('login-output').textContent = 'Starte Login...\n'; + document.getElementById('login-url-box').style.display = 'none'; + document.getElementById('login-done').style.display = 'none'; + document.getElementById('btn-proxy-login').disabled = true; + send({ action: 'proxy_login' }); + } + function loadDockerLogs() { if (!DOCKER_TABS.includes(activeTab)) return; send({ action: 'docker_logs', tab: activeTab, tail: 200 }); diff --git a/diagnostic/server.js b/diagnostic/server.js index d227579..fe06b7f 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -425,6 +425,103 @@ async function testProxy(prompt) { } } +// ── Claude Login im Proxy-Container ───────────────────── + +let loginProcess = null; + +function dockerExecStreaming(containerName, cmd, onData, onEnd) { + const createBody = JSON.stringify({ + AttachStdout: true, AttachStderr: true, AttachStdin: false, Tty: true, + Cmd: Array.isArray(cmd) ? cmd : ["sh", "-c", cmd], + }); + + const createReq = http.request({ + socketPath: "/var/run/docker.sock", + path: `/containers/${containerName}/exec`, + method: "POST", + headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(createBody) }, + }, (res) => { + let data = ""; + res.on("data", (c) => data += c); + res.on("end", () => { + if (res.statusCode !== 201) { + onEnd(new Error(`Exec create: HTTP ${res.statusCode}`)); + return; + } + const execId = JSON.parse(data).Id; + + const startBody = JSON.stringify({ Detach: false, Tty: true }); + const startReq = http.request({ + socketPath: "/var/run/docker.sock", + path: `/exec/${execId}/start`, + method: "POST", + headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(startBody) }, + }, (sRes) => { + // Tty=true: kein Multiplexing, direkt Text + sRes.on("data", (chunk) => { + const text = chunk.toString("utf-8"); + if (text.trim()) onData(text); + }); + sRes.on("end", () => onEnd(null)); + sRes.on("error", (err) => onEnd(err)); + }); + startReq.on("error", (err) => onEnd(err)); + startReq.write(startBody); + startReq.end(); + }); + }); + createReq.on("error", (err) => onEnd(err)); + createReq.write(createBody); + createReq.end(); +} + +function startProxyLogin() { + if (loginProcess) { + log("warn", "proxy", "Login laeuft bereits"); + return; + } + + loginProcess = true; + log("info", "proxy", "Starte Claude Login im Proxy-Container..."); + broadcast({ type: "login_status", status: "starting" }); + + // Volume ist :ro — wir muessen es rw remounten oder direkt im Container schreiben + // Da das Volume ro ist, schreiben wir die Credentials in einen temp-Pfad und kopieren sie danach + // Einfacher: claude login schreibt nach /root/.config/claude/ — das ist das gemountete Volume + // Problem: Volume ist :ro! Login kann nicht schreiben. + // Loesung: Login OHNE Volume ausfuehren, dann Credentials rauskopieren + // Alternative: Wir machen es anders — Login direkt, Output streamen + + // claude login: --no-open verhindert Browser-Oeffnung (Container hat keinen) + // Fallback auf verschiedene CLI-Versionen + dockerExecStreaming("aria-proxy", "claude login 2>&1 || echo 'Login-Befehl fehlgeschlagen. claude --version:' && claude --version 2>&1", + (chunk) => { + // Jeder Chunk wird live an die UI gesendet + log("info", "proxy", `[login] ${chunk.trim()}`); + + // URLs erkennen und hervorheben + const urlMatch = chunk.match(/https?:\/\/[^\s\]]+/); + if (urlMatch) { + broadcast({ type: "login_url", url: urlMatch[0], raw: chunk.trim() }); + } else { + broadcast({ type: "login_output", text: chunk.trim() }); + } + }, + (err) => { + loginProcess = null; + if (err) { + log("error", "proxy", `Login fehlgeschlagen: ${err.message}`); + broadcast({ type: "login_status", status: "error", error: err.message }); + } else { + log("info", "proxy", "Login-Prozess beendet"); + broadcast({ type: "login_status", status: "done" }); + // Auth nochmal pruefen + setTimeout(() => checkProxyAuth(), 1000); + } + } + ); +} + async function checkProxyAuth() { try { log("info", "proxy", "Pruefe Auth-Dateien im Proxy-Container..."); @@ -610,6 +707,8 @@ wss.on("connection", (ws) => { testProxy(msg.text); } else if (msg.action === "check_proxy_auth") { checkProxyAuth(); + } else if (msg.action === "proxy_login") { + startProxyLogin(); } else if (msg.action === "docker_logs") { handleDockerLogs(ws, msg.tab, msg.tail); } diff --git a/docker-compose.yml b/docker-compose.yml index 21c048b..d19b644 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: sed -i 's/if (model\.includes/if ((model||\"claude-sonnet-4\").includes/g' $$DIST/adapter/cli-to-openai.js && claude-max-api" volumes: - - ~/.config/claude:/root/.config/claude:ro # Claude CLI Auth + - ~/.config/claude:/root/.config/claude # Claude CLI Auth (rw fuer Login via Diagnostic) environment: - HOST=0.0.0.0 restart: unless-stopped