added claude cli log and test and optimize log windows through seperate tabs, update readme changelog

This commit is contained in:
duffyduck 2026-03-12 01:25:35 +01:00
parent b3a2fd7092
commit 2e4a12c812
5 changed files with 301 additions and 45 deletions

View File

@ -4,22 +4,43 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c
---
## [0.0.0.4] — 2026-03-11
## [0.0.0.4] — 2026-03-11 / 2026-03-12
### Hinzugefügt
**Diagnostic Container — Selbstcheck-UI**
- Neuer Container `aria-diagnostic` mit Web-UI auf Port 3001
- 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
- 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
- TLS Fallback für RVS-Verbindung (wie Bridge und App)
### Geändert
**Bridge → aria-core: OpenClaw Gateway Protokoll**
- Bridge nutzt jetzt das echte OpenClaw Gateway WebSocket-Protokoll (Port 18789 statt 8080)
- Vollständiger Handshake: `connect.challenge``connect` Request (mit Auth-Token) → `hello-ok`
- Nachrichten über `chat.send` Method mit `sessionKey` und `idempotencyKey`
- Nachrichten über `chat.send` Method mit `message` und `idempotencyKey`
- Antworten über `chat:final` Events (statt custom JSON)
- Streaming-Support vorbereitet (`chat:delta` Events werden empfangen)
- Fehlerbehandlung für `chat:error` Events — werden an die App weitergeleitet
- Client-ID: `gateway-client` / Mode: `backend` (OpenClaw akzeptiert nur bestimmte Werte)
**Docker-Compose Fixes**
- `OPENCLAW_GATEWAY_BIND=0.0.0.0` — Gateway bindet auf alle Interfaces (sonst nur 127.0.0.1, Bridge kann nicht zugreifen)
- `OPENCLAW_GATEWAY_TOKEN` statt `AUTH_TOKEN` — korrekter Env-Var-Name fuer OpenClaw Gateway Auth
- `ARIA_AUTH_TOKEN` an Bridge-Container durchgereicht — Bridge authentifiziert sich am Gateway
**Docker-Compose Überarbeitung**
- Bridge + Diagnostic nutzen `network_mode: "service:aria"` — teilen Netzwerk mit aria-core, kein separates Netz nötig
- `ANTHROPIC_API_KEY` + `ANTHROPIC_BASE_URL` — OpenClaw nutzt Anthropic-Provider, nicht OpenAI
- `DEFAULT_MODEL=claude-sonnet-4-6` — ohne `openai/` Prefix (Anthropic-Provider)
- `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)
### Behoben
- Handshake fehlgeschlagen `[object Object]` — Fehlermeldung wurde nicht korrekt stringifiziert
- `client.id` und `client.mode` im Connect-Request — OpenClaw akzeptiert nur vordefinierte Werte (`cli`, `gateway-client`, `webchat` etc.)
- `chat.send` nutzt `message` statt `text` als Parameter — OpenClaw Schema-Validierung
---

View File

@ -131,6 +131,10 @@ Ab da übernimmt ARIA. 😄
│ │ ↕ WebSocket zu aria-core (lokal) │ │
│ │ ↕ WebSocket zu RVS (öffentlich) │ │
│ │ Brücke: App ⟷ RVS ⟷ Bridge ⟷ ARIA │ │
│ │ │ │
│ │ [diagnostic] Selbstcheck Web-UI (Port 3001) │ │
│ │ Gateway + RVS + Proxy Status │ │
│ │ Chat-Test, Tabbed Logs │ │
│ └──────────────────┬──────────────────────────────┘ │
│ │ Volume Mount │
│ ▼ │
@ -213,13 +217,16 @@ services:
privileged: true # ARIAs Wohnung — sie hat die Schlüssel
depends_on:
- proxy
ports:
- "3001:3001" # Diagnostic Web-UI (laeuft im shared network)
environment:
- CANVAS_HOST=127.0.0.1
- OPENCLAW_GATEWAY_BIND=0.0.0.0 # Bridge muss von Docker-Netz zugreifen
- OPENCLAW_GATEWAY_TOKEN=${ARIA_AUTH_TOKEN}
- OPENAI_API_KEY=not-needed
- OPENAI_BASE_URL=http://proxy:3456/v1
- DEFAULT_MODEL=openai/claude-sonnet-4-6
- ANTHROPIC_API_KEY=not-needed
- ANTHROPIC_BASE_URL=http://proxy:3456
- DEFAULT_MODEL=claude-sonnet-4-6
- RATE_LIMIT_PER_USER=30
- DISPLAY=:0
volumes:
@ -240,6 +247,7 @@ services:
container_name: aria-bridge
depends_on:
- aria
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
volumes:
- ./aria-data/voices:/voices:ro # TTS Stimmen
- ./aria-data/config/aria.env:/config/aria.env
@ -257,8 +265,23 @@ services:
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
- RVS_TOKEN=${RVS_TOKEN:-}
restart: unless-stopped
networks:
- aria-net
# ─── Diagnostic (Selbstcheck-UI) ──────────────────────
diagnostic:
build: ./diagnostic
container_name: aria-diagnostic
depends_on:
- aria
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
environment:
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
- PROXY_URL=http://proxy:3456
- RVS_HOST=${RVS_HOST:-}
- RVS_PORT=${RVS_PORT:-443}
- RVS_TLS=${RVS_TLS:-true}
- RVS_TLS_FALLBACK=${RVS_TLS_FALLBACK:-true}
- RVS_TOKEN=${RVS_TOKEN:-}
restart: unless-stopped
networks:
aria-net:
@ -697,6 +720,30 @@ cat aria-data/brain/memory/$(date +%Y-%m-%d).md
---
## Diagnostic — Selbstcheck-UI
Web-Dashboard zur Diagnose aller ARIA-Verbindungen. Läuft auf **Port 3001** der ARIA-VM.
```
http://<ARIA-VM-IP>:3001
```
**Status-Karten:**
- **OpenClaw Gateway** — Verbindung + Handshake Status, Reconnect-Button
- **RVS (Rendezvous)** — Verbindung mit TLS-Fallback, Reconnect-Button
- **Claude Proxy** — Erreichbarkeit + Test-Prompt an Claude (prüft ob Credentials gültig sind)
**Chat-Test:**
- "Gateway senden" — Nachricht direkt an OpenClaw, Antwort wird angezeigt
- "Via RVS senden" — Nachricht über den Rendezvous-Server
**Tabbed Logs:**
Separate Tabs mit Zähler: Alle | Gateway | RVS | Proxy | Server. Autoscroll pausiert automatisch beim Hochscrollen.
> Der Diagnostic-Container teilt das Netzwerk mit aria-core (`network_mode: "service:aria"`), deshalb erreicht er den Gateway auf `localhost:18789` und den Proxy über das Docker-Netz.
---
## Repo-Struktur
```
@ -728,6 +775,12 @@ aria/ ← Gitea Repo — hier wird entwickelt
│ ├── aria_bridge.py ← Wake-Word + Whisper STT + Piper TTS
│ └── modes.py ← Betriebsmodi
├── diagnostic/ ← Selbstcheck Web-UI (Port 3001)
│ ├── Dockerfile
│ ├── server.js ← Gateway + RVS + Proxy Tests
│ ├── index.html ← Dashboard mit Tabs
│ └── package.json
├── rvs/ ← Rendezvous-Server (Rechenzentrum)
│ ├── docker-compose.yml ← eigenes Compose — separat deployen!
│ ├── server.js ← WebSocket Relay — reiner Durchleiter

View File

@ -10,20 +10,22 @@
h1 { font-size: 20px; margin-bottom: 16px; color: #0096FF; }
h2 { font-size: 14px; margin-bottom: 8px; color: #8888AA; text-transform: uppercase; letter-spacing: 1px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-bottom: 16px; }
.card { background: #12122A; border: 1px solid #1E1E2E; border-radius: 8px; padding: 12px; }
.card.full { grid-column: 1 / -1; }
.status-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.dot.connected { background: #34C759; box-shadow: 0 0 6px #34C759; }
.dot.connecting { background: #FFD60A; }
.dot.connecting, .dot.testing { background: #FFD60A; animation: pulse 1s infinite; }
.dot.disconnected { background: #FF3B30; }
.dot.error { background: #FF3B30; box-shadow: 0 0 6px #FF3B30; }
.dot.not_configured { background: #555570; }
.dot.unknown { background: #555570; }
@keyframes pulse { 50% { opacity: 0.5; } }
.status-label { font-size: 13px; }
.error-text { color: #FF6B6B; font-size: 11px; margin-top: 4px; }
.error-text { color: #FF6B6B; font-size: 11px; margin-top: 4px; word-break: break-all; }
.btn { background: #0096FF; color: #fff; border: none; border-radius: 6px; padding: 8px 16px;
font-family: inherit; font-size: 13px; cursor: pointer; margin: 4px 4px 4px 0; }
@ -32,12 +34,23 @@
.btn.secondary { background: #1E1E2E; border: 1px solid #333; }
.btn.secondary:hover { background: #2A2A3E; }
.log-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.log-header h2 { margin-bottom: 0; }
/* Tabs */
.tab-bar { display: flex; gap: 2px; margin-bottom: 0; }
.tab-btn { background: #1E1E2E; color: #8888AA; border: 1px solid #1E1E2E; border-bottom: none;
border-radius: 6px 6px 0 0; padding: 6px 14px; font-family: inherit; font-size: 12px;
cursor: pointer; position: relative; top: 1px; }
.tab-btn.active { background: #080810; color: #E0E0F0; border-color: #1E1E2E; }
.tab-btn .tab-count { background: #333; color: #888; border-radius: 8px; padding: 1px 6px;
font-size: 10px; margin-left: 4px; }
.log-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 4px; }
.log-header h2 { margin-bottom: 0; font-size: 12px; }
.pause-hint { font-size: 11px; color: #FFD60A; display: none; }
.pause-hint.visible { display: inline; }
.log-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
height: 300px; overflow-y: auto; padding: 8px; font-size: 11px; line-height: 1.6; position: relative; }
.log-panel { background: #080810; border: 1px solid #1E1E2E; border-radius: 0 0 6px 6px; }
.log-box { height: 280px; overflow-y: auto; padding: 8px; font-size: 11px; line-height: 1.6; }
.log-box.hidden { display: none; }
.log-entry { white-space: pre-wrap; word-break: break-all; }
.log-entry.error { color: #FF6B6B; }
.log-entry.warn { color: #FFD60A; }
@ -81,6 +94,16 @@
<div class="error-text" id="rvs-error"></div>
<button class="btn secondary" onclick="send({action:'reconnect_rvs'})">Reconnect</button>
</div>
<div class="card">
<h2>Claude Proxy</h2>
<div class="status-row">
<div class="dot" id="proxy-dot"></div>
<span class="status-label" id="proxy-status">Nicht getestet</span>
</div>
<div class="error-text" id="proxy-error"></div>
<button class="btn secondary" id="btn-proxy-test" onclick="testProxyBtn()">Proxy testen</button>
</div>
</div>
<!-- Chat Test -->
@ -96,47 +119,108 @@
</div>
</div>
<!-- Verbindungslog -->
<div class="card" style="margin-top:12px">
<div class="log-header">
<h2>Verbindungslog</h2>
<span>
<span class="pause-hint" id="pause-hint">Autoscroll pausiert</span>
<button class="btn secondary" id="btn-scroll" onclick="resumeScroll()" style="display:none;padding:4px 10px;font-size:11px">Nach unten</button>
</span>
<!-- Logs mit Tabs -->
<div class="card" style="margin-top:12px; padding: 8px 0 0 0;">
<div style="padding: 0 12px;">
<div class="tab-bar">
<button class="tab-btn active" data-tab="all" onclick="switchTab('all')">Alle <span class="tab-count" id="count-all">0</span></button>
<button class="tab-btn" data-tab="gateway" onclick="switchTab('gateway')">Gateway <span class="tab-count" id="count-gateway">0</span></button>
<button class="tab-btn" data-tab="rvs" onclick="switchTab('rvs')">RVS <span class="tab-count" id="count-rvs">0</span></button>
<button class="tab-btn" data-tab="proxy" onclick="switchTab('proxy')">Proxy <span class="tab-count" id="count-proxy">0</span></button>
<button class="tab-btn" data-tab="server" onclick="switchTab('server')">Server <span class="tab-count" id="count-server">0</span></button>
</div>
</div>
<div class="log-panel">
<div class="log-header">
<span>
<span class="pause-hint" id="pause-hint">Autoscroll pausiert</span>
<button class="btn secondary" id="btn-scroll" onclick="resumeScroll()" style="display:none;padding:4px 10px;font-size:11px">Nach unten</button>
</span>
</div>
<div class="log-box" id="log-all"></div>
<div class="log-box hidden" id="log-gateway"></div>
<div class="log-box hidden" id="log-rvs"></div>
<div class="log-box hidden" id="log-proxy"></div>
<div class="log-box hidden" id="log-server"></div>
</div>
<div class="log-box" id="log-box"></div>
</div>
<script>
const logBox = document.getElementById('log-box');
const chatBox = document.getElementById('chat-box');
const pauseHint = document.getElementById('pause-hint');
const btnScroll = document.getElementById('btn-scroll');
let ws;
let logAutoScroll = true;
let activeTab = 'all';
const autoScroll = { all: true, gateway: true, rvs: true, proxy: true, server: true };
const logCounts = { all: 0, gateway: 0, rvs: 0, proxy: 0, server: 0 };
// Autoscroll pausiert wenn User hochscrollt
logBox.addEventListener('scroll', () => {
const atBottom = logBox.scrollHeight - logBox.scrollTop - logBox.clientHeight < 30;
logAutoScroll = atBottom;
pauseHint.classList.toggle('visible', !atBottom);
btnScroll.style.display = atBottom ? 'none' : 'inline';
const logBoxes = {
all: document.getElementById('log-all'),
gateway: document.getElementById('log-gateway'),
rvs: document.getElementById('log-rvs'),
proxy: document.getElementById('log-proxy'),
server: document.getElementById('log-server'),
};
// Scroll-Pause pro aktivem Tab
Object.entries(logBoxes).forEach(([tab, box]) => {
box.addEventListener('scroll', () => {
const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 30;
autoScroll[tab] = atBottom;
if (tab === activeTab) {
pauseHint.classList.toggle('visible', !atBottom);
btnScroll.style.display = atBottom ? 'none' : 'inline';
}
});
});
function resumeScroll() {
logAutoScroll = true;
logBox.scrollTop = logBox.scrollHeight;
autoScroll[activeTab] = true;
const box = logBoxes[activeTab];
box.scrollTop = box.scrollHeight;
pauseHint.classList.remove('visible');
btnScroll.style.display = 'none';
}
function switchTab(tab) {
activeTab = tab;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
Object.entries(logBoxes).forEach(([t, box]) => box.classList.toggle('hidden', t !== tab));
// Pause-Hinweis aktualisieren
const box = logBoxes[tab];
const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 30;
pauseHint.classList.toggle('visible', !atBottom);
btnScroll.style.display = atBottom ? 'none' : 'inline';
if (autoScroll[tab]) box.scrollTop = box.scrollHeight;
}
function updateCount(source) {
logCounts.all++;
document.getElementById('count-all').textContent = logCounts.all;
// source → tab mapping
const tab = mapSourceToTab(source);
if (tab && logCounts[tab] !== undefined) {
logCounts[tab]++;
document.getElementById(`count-${tab}`).textContent = logCounts[tab];
}
}
function mapSourceToTab(source) {
if (source === 'gateway') return 'gateway';
if (source === 'rvs') return 'rvs';
if (source === 'proxy') return 'proxy';
if (source === 'server' || source === 'browser') return 'server';
return null;
}
const STATUS_LABELS = {
connected: 'Verbunden',
connecting: 'Verbinde...',
testing: 'Teste...',
disconnected: 'Getrennt',
error: 'Fehler',
not_configured: 'Nicht konfiguriert',
unknown: 'Nicht getestet',
};
function connectWS() {
@ -165,10 +249,7 @@
addChat('received', msg.text, 'chat:final');
return;
}
if (msg.type === 'chat_delta') {
// Deltas optional anzeigen
return;
}
if (msg.type === 'chat_delta') { return; }
if (msg.type === 'chat_error') {
addChat('error', msg.error, 'chat:error');
return;
@ -178,10 +259,15 @@
addChat('received', p.text || '?', `via RVS (${p.sender || '?'})`);
return;
}
if (msg.type === 'response') {
// chat.send Ack — nur loggen
if (msg.type === 'proxy_result') {
if (msg.ok) {
addChat('received', msg.reply, 'Claude Proxy direkt');
} else {
addChat('error', msg.error, 'Claude Proxy Fehler');
}
return;
}
if (msg.type === 'response') { return; }
};
}
@ -205,6 +291,10 @@
send({ action: 'test_rvs', text });
}
function testProxyBtn() {
send({ action: 'test_proxy', text: 'Antworte mit genau einem Wort: Ping' });
}
function updateState(state) {
// Gateway
const gw = state.gateway || {};
@ -219,6 +309,13 @@
document.getElementById('rvs-status').textContent = STATUS_LABELS[rvs.status] || rvs.status;
document.getElementById('rvs-error').textContent = rvs.lastError || '';
// Proxy
const proxy = state.proxy || {};
document.getElementById('proxy-dot').className = `dot ${proxy.status || 'unknown'}`;
document.getElementById('proxy-status').textContent = STATUS_LABELS[proxy.status] || proxy.status;
document.getElementById('proxy-error').textContent = proxy.lastError || '';
document.getElementById('btn-proxy-test').disabled = proxy.status === 'testing';
// Buttons
document.getElementById('btn-gw').disabled = gw.status !== 'connected';
document.getElementById('btn-rvs').disabled = rvs.status !== 'connected';
@ -226,11 +323,23 @@
function addLog(level, source, message, ts) {
const time = ts ? new Date(ts).toLocaleTimeString('de-DE') : new Date().toLocaleTimeString('de-DE');
const line = `${time} [${source}] ${message}`;
// In "Alle" und in den passenden Tab schreiben
appendToLog('all', level, line);
const tab = mapSourceToTab(source);
if (tab) appendToLog(tab, level, line);
updateCount(source);
}
function appendToLog(tab, level, line) {
const box = logBoxes[tab];
const el = document.createElement('div');
el.className = `log-entry ${level}`;
el.textContent = `${time} [${source}] ${message}`;
logBox.appendChild(el);
if (logAutoScroll) logBox.scrollTop = logBox.scrollHeight;
el.textContent = line;
box.appendChild(el);
if (autoScroll[tab]) box.scrollTop = box.scrollHeight;
}
function addChat(type, text, meta) {

View File

@ -27,11 +27,13 @@ const RVS_PORT = process.env.RVS_PORT || "443";
const RVS_TLS = process.env.RVS_TLS || "true";
const RVS_TLS_FALLBACK = process.env.RVS_TLS_FALLBACK || "true";
const RVS_TOKEN = process.env.RVS_TOKEN || "";
const PROXY_URL = process.env.PROXY_URL || "http://proxy:3456";
// ── State ───────────────────────────────────────────────
const state = {
gateway: { status: "disconnected", lastError: null, handshakeOk: false },
rvs: { status: "disconnected", lastError: null },
proxy: { status: "unknown", lastError: null },
};
const logs = [];
let gatewayWs = null;
@ -340,6 +342,73 @@ function sendToRVS(text) {
return true;
}
// ── Claude Proxy Test ────────────────────────────────────
async function testProxy(prompt) {
state.proxy.status = "testing";
state.proxy.lastError = null;
broadcastState();
log("info", "proxy", `Teste Proxy: ${PROXY_URL}`);
try {
// Schritt 1: Erreichbarkeit pruefen
const healthUrl = `${PROXY_URL}/v1/models`;
log("info", "proxy", `Rufe ab: ${healthUrl}`);
const modelsRes = await fetch(healthUrl, {
headers: { "Authorization": "Bearer not-needed" },
signal: AbortSignal.timeout(10000),
});
if (!modelsRes.ok) {
throw new Error(`Models-Endpoint: HTTP ${modelsRes.status} ${modelsRes.statusText}`);
}
const modelsData = await modelsRes.json();
const modelCount = modelsData.data?.length || 0;
log("info", "proxy", `Proxy erreichbar — ${modelCount} Model(s) verfuegbar`);
// Schritt 2: Chat Completion testen (kurzer Prompt)
const testPrompt = prompt || "Antworte mit genau einem Wort: Ping";
log("info", "proxy", `Sende Test-Prompt: "${testPrompt}"`);
const chatRes = await fetch(`${PROXY_URL}/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer not-needed",
},
body: JSON.stringify({
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: testPrompt }],
max_tokens: 200,
}),
signal: AbortSignal.timeout(30000),
});
if (!chatRes.ok) {
const errBody = await chatRes.text().catch(() => "");
throw new Error(`Chat-Completion: HTTP ${chatRes.status}${errBody.slice(0, 300)}`);
}
const chatData = await chatRes.json();
const reply = chatData.choices?.[0]?.message?.content || "(leer)";
log("info", "proxy", `Antwort: "${reply.slice(0, 200)}"`);
state.proxy.status = "connected";
state.proxy.lastError = null;
broadcastState();
broadcast({ type: "proxy_result", ok: true, reply });
} catch (err) {
log("error", "proxy", `Fehler: ${err.message}`);
state.proxy.status = "error";
state.proxy.lastError = err.message;
broadcastState();
broadcast({ type: "proxy_result", ok: false, error: err.message });
}
}
// ── Hilfsfunktionen ─────────────────────────────────────
function waitForMessage(ws, timeoutMs) {
@ -391,6 +460,8 @@ wss.on("connection", (ws) => {
connectGateway();
} else if (msg.action === "reconnect_rvs") {
connectRVS();
} else if (msg.action === "test_proxy") {
testProxy(msg.text);
}
} catch {}
});
@ -407,6 +478,7 @@ server.listen(HTTP_PORT, "0.0.0.0", () => {
log("info", "server", `Gateway: ${GATEWAY_URL}`);
log("info", "server", `Token: ${GATEWAY_TOKEN ? GATEWAY_TOKEN.slice(0, 8) + "..." : "(keiner)"}`);
log("info", "server", `RVS: ${RVS_HOST ? `${RVS_HOST}:${RVS_PORT}` : "(nicht konfiguriert)"}`);
log("info", "server", `Proxy: ${PROXY_URL}`);
// Verbindungen aufbauen
connectGateway();

View File

@ -76,6 +76,7 @@ services:
network_mode: "service:aria" # Teilt Netzwerk mit aria-core → localhost:18789
environment:
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
- PROXY_URL=http://proxy:3456
- RVS_HOST=${RVS_HOST:-}
- RVS_PORT=${RVS_PORT:-443}
- RVS_TLS=${RVS_TLS:-true}