feat(host-agent,satellite): RVS_TLS_FALLBACK wie in den Compute-Bridges

Host-Agent und Satellit hatten keinen TLS-Fallback (nur die vier Compute-Bridges).
Jetzt konsistent: bei TLS-Fehlschlag einmal auf ws:// zurueckfallen, danach wieder
mit RVS_TLS starten (kein Sticky-Fallback, wie bei den Bridges). uri_host/proto
werden pro Versuch aus use_tls berechnet, damit der RVS_SNI-Pfad nur bei wss gilt.

Hilft nur wo der RVS plaintext erreichbar ist; gegen Caddy-TLS bleibt wss. RVS_TLS_
FALLBACK in beiden .env.example dokumentiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 17:58:19 +02:00
co-authored by Claude Opus 4.8
parent df5cd3be99
commit 6cb29a28ce
4 changed files with 49 additions and 16 deletions
+24 -8
View File
@@ -98,6 +98,9 @@ def _default_id() -> str:
RVS_HOST = os.environ.get("RVS_HOST", "")
RVS_PORT = int(os.environ.get("RVS_PORT", "443") or "443")
RVS_TLS = _env_bool("RVS_TLS", True)
# Bei TLS-Fehlschlag einmal auf ws:// zurueckfallen (wie die Compute-Bridges).
# Hilft nur, wenn der RVS plaintext erreichbar ist; gegen Caddy-TLS bleibt wss.
RVS_TLS_FALLBACK = _env_bool("RVS_TLS_FALLBACK", True)
RVS_TOKEN = os.environ.get("RVS_TOKEN", "")
# TLS-Hostname (SNI + Zertifikatspruefung), falls RVS_HOST eine IP ist — z.B. der
# Agent laeuft im selben Netz wie der RVS und verbindet direkt auf die interne IP,
@@ -417,22 +420,26 @@ class HostAgent:
logger.error("RVS_HOST und RVS_TOKEN sind Pflicht (siehe .env.example).")
return
backoff = 1
# RVS_SNI gesetzt (Verbindung auf eine interne IP): die URI nutzt den
# HOSTNAMEN, damit Host-Header + SNI + Cert-Pruefung stimmen (sonst weist
# Caddy die Verbindung ab -> HTTP 200 statt WS-Upgrade). Der TCP-Connect
# wird per host=/port= auf die IP in RVS_HOST umgebogen.
proto = "wss" if RVS_TLS else "ws"
uri_host = RVS_SNI if (RVS_TLS and RVS_SNI) else RVS_HOST
url = f"{proto}://{uri_host}:{RVS_PORT}?token={RVS_TOKEN}"
# use_tls kann bei Fehlschlag einmal auf ws:// fallen (RVS_TLS_FALLBACK),
# danach wieder zurueck auf RVS_TLS (kein Sticky-Fallback).
use_tls = RVS_TLS
tls_fallback_tried = False
connect_kwargs = {"max_size": 16 * 1024 * 1024,
"ping_interval": 20, "ping_timeout": 20}
while True:
proto = "wss" if use_tls else "ws"
# Bei TLS + RVS_SNI: URI nutzt den HOSTNAMEN (Host-Header/SNI/Cert),
# getaddrinfo mappt ihn auf die IP in RVS_HOST. Bei ws:// direkt die IP.
uri_host = RVS_SNI if (use_tls and RVS_SNI) else RVS_HOST
url = f"{proto}://{uri_host}:{RVS_PORT}?token={RVS_TOKEN}"
fallback = False
try:
logger.info("Verbinde mit RVS %s://%s:%s%s …", proto, uri_host, RVS_PORT,
f" (TCP {RVS_HOST})" if (RVS_TLS and RVS_SNI) else "")
f" (TCP {RVS_HOST})" if (use_tls and RVS_SNI) else "")
async with websockets.connect(url, **connect_kwargs) as ws:
self.ws = ws
backoff = 1
tls_fallback_tried = False
await self._hello(log=True)
hb = asyncio.create_task(self._heartbeat())
try:
@@ -442,10 +449,19 @@ class HostAgent:
hb.cancel()
except Exception as exc:
logger.warning("RVS-Verbindung verloren: %s", exc)
if use_tls and RVS_TLS_FALLBACK and not tls_fallback_tried:
logger.info("TLS fehlgeschlagen — Fallback auf ws://")
use_tls = False
tls_fallback_tried = True
fallback = True
finally:
self.ws = None
if fallback:
continue # sofort erneut mit ws://
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 30)
use_tls = RVS_TLS # kein Sticky-Fallback
tls_fallback_tried = False
def main() -> None: