Compare commits

..
5 Commits
Author SHA1 Message Date
duffyduck c9988e7244 release: bump version to 0.2.5.9 2026-10-25 13:13:29 +01:00
duffyduckandClaude Opus 5.5 a00199c121 feat(update): App laedt Updates per HTTP mit Fortschrittsanzeige statt als 65-MB-WebSocket-Block
Bisher kam die APK als EINE WebSocket-Nachricht (~65 MB Base64) mit starrem
60-s-Timeout — ueber den externen Proxy (Heim-Upload → Hetzner → Handy) zu
knapp, Abbruch ohne Fortschritt.

RVS: GET/HEAD /update/latest.apk — nur mit gueltigem Raum-Token im Header
X-RVS-Token (sonst 403), Content-Length, Range/206 zum Fortsetzen. Version
im Dateinamen wird ohne angehaengten Punkt erkannt.

App: Download per HTTP (react-native-fs) mit Fortschritt in % und MB,
Abbrechen-Knopf, Abbruch nur bei 30 s ohne Daten statt fester Gesamtzeit,
3 Versuche, Groessen-Check gegen Content-Length. Alter RVS (404): Fallback
auf den WebSocket-Weg mit 5 min Timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:10:07 +01:00
duffyduck baf9e93c2a release: bump version to 0.2.5.8 2026-10-25 12:55:10 +01:00
duffyduckandClaude Opus 5.5 db2055ff3b fix: alte App-Versionen in der Raum-Liste erkennen + HF-Token aus zwei weiteren Log-Zeilen
RVS: die ARIA-App wird auch ohne client_hello erkannt, sobald sie eine
Nachricht schickt, die nur sie sendet (app_log mit platform android/ios,
location_update, stt_lease_request, update_download, vnc_open).

Bridge: "Voice-Config geladen" und "Voice-Config gespeichert" schrieben
den huggingfaceToken noch im Klartext ins Log — jetzt ***.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:52:36 +01:00
duffyduckandClaude Opus 5.5 5a59ea3930 fix(xtts): Flotte zeigt die echte Karte statt "GPU void"
Das NVIDIA-Container-Toolkit ersetzt NVIDIA_VISIBLE_DEVICES im Prozess durch
"void" — whisper/f5tts/voxtral meldeten das als GPU. Die Compose gibt die
Karte jetzt zusaetzlich als ARIA_GPU mit (nur Anzeige).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:51:41 +01:00
12 changed files with 281 additions and 44 deletions
+3 -1
View File
@@ -855,7 +855,9 @@ Satelliten verbinden dann nicht mehr). ACME-State persistent in `./data/caddy/`
- WebSocket Relay (alle Message-Types: chat, audio, file, config, xtts, update, etc.)
- OAuth-Callback HTTP: `GET /oauth/callback/{service}?code=...` → broadcastet als
`oauth_callback`-WS-Message + zeigt dem Browser eine "OAuth erfolgreich"-Seite
- Auto-Update: APK-Verteilung an Apps ueber WebSocket
- Auto-Update: App fragt per WebSocket nach der Version, laedt die APK per HTTP
(`/update/latest.apk`, nur mit gueltigem Raum-Token im Header `X-RVS-Token`,
mit Fortschrittsanzeige + Fortsetzen per Range). Alte Apps: Fallback ueber WebSocket
- Heartbeat + tote Verbindungen aufraeumen
**Auto-Update APK bereitstellen:**
+2
View File
@@ -16,6 +16,7 @@ import WorkspaceScreen from './src/workspace/WorkspaceScreen';
import SettingsScreen from './src/screens/SettingsScreen';
import ViewModeToggle from './src/components/ViewModeToggle';
import SecurityLock from './src/components/SecurityLock';
import UpdateProgress from './src/components/UpdateProgress';
import rvs from './src/services/rvs';
import { initLogger, installGlobalCrashReporter } from './src/services/logger';
import { acquireBackgroundAudio } from './src/services/backgroundAudio';
@@ -193,6 +194,7 @@ const App: React.FC = () => {
/>
</Tab.Navigator>
</NavigationContainer>
<UpdateProgress />
</GestureHandlerRootView>
);
};
+2 -2
View File
@@ -79,8 +79,8 @@ android {
applicationId "com.ariacockpit"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 20507
versionName "0.2.5.7"
versionCode 20509
versionName "0.2.5.9"
// Fallback fuer Libraries mit Product Flavors
missingDimensionStrategy 'react-native-camera', 'general'
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "aria-cockpit",
"version": "0.2.5.7",
"version": "0.2.5.9",
"private": true,
"scripts": {
"android": "react-native run-android",
+57
View File
@@ -0,0 +1,57 @@
/**
* Fortschrittsanzeige beim App-Update: Balken mit Prozent und MB, Knopf
* zum Abbrechen. Erscheint nur, waehrend ein Update laedt/installiert.
*/
import React, { useEffect, useState } from 'react';
import { Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import updateService, { UpdateProgress as Progress } from '../services/updater';
const UpdateProgress: React.FC = () => {
const [p, setP] = useState<Progress | null>(null);
useEffect(() => updateService.onProgress(setP), []);
if (!p || !p.active) return null;
const known = p.fraction >= 0;
const pct = known ? Math.round(p.fraction * 100) : 0;
return (
<Modal visible transparent animationType="fade" onRequestClose={() => {}}>
<View style={styles.overlay}>
<View style={styles.box}>
<Text style={styles.title}>Update auf Version {p.version}</Text>
<Text style={styles.status}>{p.status}</Text>
<View style={styles.track}>
<View style={[styles.bar, { width: known ? `${pct}%` : '100%' }, !known && styles.barUnknown]} />
</View>
<Text style={styles.numbers}>
{known ? `${pct} % · ` : ''}
{p.receivedMB > 0 ? `${p.receivedMB.toFixed(1)} von ${p.totalMB.toFixed(1)} MB` : `${p.totalMB.toFixed(1)} MB`}
</Text>
{p.status !== 'Installiere …' && (
<TouchableOpacity style={styles.btn} onPress={() => updateService.cancelDownload()}>
<Text style={styles.btnText}>Abbrechen</Text>
</TouchableOpacity>
)}
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)', justifyContent: 'center', padding: 24 },
box: { backgroundColor: '#1A1A2E', borderRadius: 14, padding: 20 },
title: { color: '#FFFFFF', fontSize: 17, fontWeight: '700' },
status: { color: '#C8C8D8', fontSize: 14, marginTop: 6 },
track: { height: 10, borderRadius: 5, backgroundColor: '#2A2A3E', marginTop: 14, overflow: 'hidden' },
bar: { height: 10, borderRadius: 5, backgroundColor: '#0096FF' },
barUnknown: { opacity: 0.35 },
numbers: { color: '#9090A8', fontSize: 13, marginTop: 8 },
btn: { alignSelf: 'flex-end', marginTop: 14, paddingVertical: 8, paddingHorizontal: 16,
borderRadius: 8, backgroundColor: '#2A2A3E' },
btnText: { color: '#FFFFFF', fontWeight: '600' },
});
export default UpdateProgress;
+139 -34
View File
@@ -17,6 +17,26 @@ const APP_VERSION = packageJson.version || '0.0.0.0';
type UpdateCallback = (info: UpdateInfo) => void;
/** Fortschritt fuer die Anzeige (UpdateProgress-Komponente) */
export interface UpdateProgress {
active: boolean;
version: string;
/** 0..1, -1 = unbekannt */
fraction: number;
receivedMB: number;
totalMB: number;
/** z.B. "Lade herunter …", "Neuer Versuch (2/3) …", "Installiere …" */
status: string;
}
type ProgressCallback = (p: UpdateProgress) => void;
const HTTP_ATTEMPTS = 3;
// Abbruch nur, wenn der Download HAENGT (so lange kommt kein Byte) — nicht
// nach einer festen Gesamtzeit wie frueher (60 s fuer 65 MB war zu knapp).
const READ_STALL_MS = 30_000;
const WS_TIMEOUT_MS = 5 * 60_000;
export interface UpdateInfo {
version: string;
downloadUrl: string;
@@ -27,6 +47,10 @@ class UpdateService {
private listeners: UpdateCallback[] = [];
private checking = false;
private downloading = false;
private progressListeners: ProgressCallback[] = [];
private progress: UpdateProgress = { active: false, version: '', fraction: -1, receivedMB: 0, totalMB: 0, status: '' };
private jobId: number | null = null;
private cancelled = false;
constructor() {
// Beim Start alte APK-Reste aus dem Cache wegraeumen — wenn diese App
@@ -150,46 +174,125 @@ class UpdateService {
);
}
/** APK ueber WebSocket herunterladen und installieren */
onProgress(callback: ProgressCallback): () => void {
this.progressListeners.push(callback);
callback(this.progress);
return () => {
this.progressListeners = this.progressListeners.filter(cb => cb !== callback);
};
}
private setProgress(p: Partial<UpdateProgress>): void {
this.progress = { ...this.progress, ...p };
this.progressListeners.forEach(cb => cb(this.progress));
}
/** Laufenden Download abbrechen (Knopf in der Fortschrittsanzeige) */
cancelDownload(): void {
this.cancelled = true;
if (this.jobId !== null) {
try { RNFS.stopDownload(this.jobId); } catch (_) {}
}
}
/**
* APK per HTTP vom RVS laden (/update/latest.apk, Token im Header).
* Gibt false zurueck, wenn der RVS den Weg noch nicht kennt (alter RVS) —
* dann nimmt downloadAndInstall den alten WebSocket-Weg.
*/
private async downloadViaHttp(info: UpdateInfo, destPath: string): Promise<boolean> {
const cfg = rvs.getConfig();
if (!cfg) return false;
const url = `${cfg.useTLS ? 'https' : 'http'}://${cfg.host}:${cfg.port}/update/latest.apk`;
let lastErr = '';
for (let attempt = 1; attempt <= HTTP_ATTEMPTS; attempt++) {
if (this.cancelled) throw new Error('Abgebrochen');
this.setProgress({ status: attempt === 1 ? 'Lade herunter …' : `Neuer Versuch (${attempt}/${HTTP_ATTEMPTS}) …`,
fraction: -1, receivedMB: 0 });
let expected = 0;
try {
const job = RNFS.downloadFile({
fromUrl: url,
toFile: destPath,
headers: { 'X-RVS-Token': cfg.token },
connectionTimeout: 15_000,
readTimeout: READ_STALL_MS,
progressInterval: 300,
begin: (r) => { expected = r.contentLength > 0 ? r.contentLength : 0; },
progress: (r) => {
const total = r.contentLength > 0 ? r.contentLength : info.size;
this.setProgress({
fraction: total > 0 ? Math.min(1, r.bytesWritten / total) : -1,
receivedMB: r.bytesWritten / 1048576,
totalMB: total / 1048576,
});
},
});
this.jobId = job.jobId;
const res = await job.promise;
this.jobId = null;
if (res.statusCode === 403 || res.statusCode === 404) {
// Alter RVS ohne HTTP-Download (404) bzw. Token unbekannt (403)
console.warn(`[Update] HTTP-Download nicht verfuegbar (${res.statusCode}) — nehme WebSocket`);
return false;
}
if (res.statusCode !== 200) throw new Error(`HTTP ${res.statusCode}`);
const st = await RNFS.stat(destPath);
if (expected && Number(st.size) !== expected) {
throw new Error(`Datei unvollstaendig (${st.size} von ${expected} Bytes)`);
}
return true;
} catch (err: any) {
this.jobId = null;
if (this.cancelled) throw new Error('Abgebrochen');
lastErr = err?.message || String(err);
console.warn(`[Update] HTTP-Download Versuch ${attempt} fehlgeschlagen: ${lastErr}`);
await new Promise<void>(r => setTimeout(r, 2000 * attempt));
}
}
throw new Error(`Download fehlgeschlagen: ${lastErr}`);
}
/** Alter Weg: APK als ein grosser Block ueber die WebSocket-Verbindung */
private async downloadViaWebSocket(info: UpdateInfo, destPath: string): Promise<void> {
this.setProgress({ status: 'Lade herunter (ohne Fortschritt) …', fraction: -1 });
rvs.send('update_download' as any, {});
const base64 = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => { unsub(); reject(new Error('Download-Timeout (5 min)')); }, WS_TIMEOUT_MS);
const unsub = rvs.onMessage((msg: RVSMessage) => {
if ((msg.type as string) === 'update_data') {
clearTimeout(timeout);
unsub();
if (msg.payload.error) reject(new Error(msg.payload.error as string));
else resolve(msg.payload.base64 as string);
}
});
});
if (this.cancelled) throw new Error('Abgebrochen');
await RNFS.writeFile(destPath, base64, 'base64');
}
/** APK herunterladen (HTTP mit Fortschritt, sonst WebSocket) und installieren */
async downloadAndInstall(info: UpdateInfo): Promise<void> {
if (this.downloading) return;
this.downloading = true;
this.cancelled = false;
this.setProgress({ active: true, version: info.version, fraction: -1, receivedMB: 0,
totalMB: info.size / 1048576, status: 'Starte …' });
const fileName = `ARIA-v${info.version}.apk`;
const destPath = `${RNFS.CachesDirectoryPath}/${fileName}`;
try {
console.log(`[Update] Fordere APK v${info.version} an...`);
Alert.alert('Download gestartet', `Version ${info.version} wird ueber RVS heruntergeladen...`);
// APK ueber WebSocket anfordern
rvs.send('update_download' as any, {});
// Auf update_data warten (einmalig)
const apkData = await new Promise<{base64: string, fileName: string}>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Download-Timeout (60s)')), 60000);
const unsub = rvs.onMessage((msg: RVSMessage) => {
if ((msg.type as string) === 'update_data') {
clearTimeout(timeout);
unsub();
if (msg.payload.error) {
reject(new Error(msg.payload.error as string));
} else {
resolve({
base64: msg.payload.base64 as string,
fileName: msg.payload.fileName as string || `ARIA-${info.version}.apk`,
});
}
}
});
});
// Vor dem Schreiben alte APKs im Cache wegraeumen — falls mehrere
// Updates in einer Session gezogen werden
console.log(`[Update] Lade APK v${info.version} …`);
// Vor dem Download alte APKs im Cache wegraeumen
await this.cleanupOldApks();
// Base64 als APK-Datei speichern
const destPath = `${RNFS.CachesDirectoryPath}/${apkData.fileName}`;
await RNFS.writeFile(destPath, apkData.base64, 'base64');
const viaHttp = await this.downloadViaHttp(info, destPath);
if (!viaHttp) await this.downloadViaWebSocket(info, destPath);
const fileSize = await RNFS.stat(destPath);
console.log(`[Update] APK gespeichert: ${destPath} (${(Number(fileSize.size) / 1024 / 1024).toFixed(1)}MB)`);
console.log(`[Update] APK gespeichert: ${destPath} (${(Number(fileSize.size) / 1024 / 1024).toFixed(1)}MB, ${viaHttp ? 'HTTP' : 'WebSocket'})`);
this.setProgress({ fraction: 1, status: 'Installiere …' });
// APK installieren via natives ApkInstaller Module (FileProvider + Intent)
if (Platform.OS === 'android') {
@@ -199,15 +302,17 @@ class UpdateService {
} catch (installErr: any) {
Alert.alert(
'APK heruntergeladen',
`Version ${info.version} gespeichert.\n\nBitte manuell installieren:\nDateimanager → ${apkData.fileName} antippen.\n\n(${installErr.message})`,
`Version ${info.version} gespeichert.\n\nBitte manuell installieren:\nDateimanager → ${fileName} antippen.\n\n(${installErr.message})`,
);
}
}
} catch (err: any) {
console.error(`[Update] Fehler: ${err.message}`);
Alert.alert('Update fehlgeschlagen', err.message);
try { await RNFS.unlink(destPath); } catch (_) {}
if (!this.cancelled) Alert.alert('Update fehlgeschlagen', err.message);
} finally {
this.downloading = false;
this.setProgress({ active: false });
}
}
+2 -2
View File
@@ -720,7 +720,7 @@ class ARIABridge:
logger.info("Voice-Config geladen: tts=%s voice=%s f5tts=%s flux=%s",
self.tts_enabled, self.xtts_voice or "default",
self._f5tts_config or "defaults",
self._flux_config or "defaults")
_redact(self._flux_config) or "defaults")
except Exception as e:
logger.warning("Voice-Config laden fehlgeschlagen: %s", e)
# Whisper-Modell: Config hat Vorrang, dann env/Default (medium)
@@ -2734,7 +2734,7 @@ class ARIABridge:
config_data.update(getattr(self, "_debug_log_config", {}))
with open("/shared/config/voice_config.json", "w") as f:
json.dump(config_data, f, indent=2)
logger.info("[rvs] Voice-Config gespeichert: %s", config_data)
logger.info("[rvs] Voice-Config gespeichert: %s", _redact(config_data))
except Exception as e:
logger.warning("[rvs] Config speichern fehlgeschlagen: %s", e)
return
+57 -1
View File
@@ -403,6 +403,51 @@ function handleHttpRequest(req, res) {
return;
}
// App-Update als normaler HTTP-Download (statt eines riesigen WS-Blocks):
// Content-Length fuer die Fortschrittsanzeige, Range zum Fortsetzen.
// Nur fuer Geraete mit gueltigem Raum-Token (Header X-RVS-Token).
if ((req.method === "GET" || req.method === "HEAD") && pathname === "/update/latest.apk") {
const tok = String(req.headers["x-rvs-token"] || "");
if (!tok || !rooms.has(tok)) {
res.writeHead(403, { "Content-Type": "text/plain" });
res.end("Forbidden\n");
return;
}
const apk = getLatestAPK();
if (!apk) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Keine APK verfuegbar\n");
return;
}
const size = fs.statSync(apk.path).size;
const headers = {
"Content-Type": "application/vnd.android.package-archive",
"Content-Disposition": `attachment; filename="ARIA-v${apk.version}.apk"`,
"Accept-Ranges": "bytes",
"Cache-Control": "no-store",
"X-APK-Version": apk.version,
};
let start = 0, end = size - 1, status = 200;
const m = /^bytes=(\d*)-(\d*)$/.exec(String(req.headers.range || ""));
if (m && (m[1] || m[2])) {
if (m[1]) { start = parseInt(m[1], 10); if (m[2]) end = Math.min(parseInt(m[2], 10), size - 1); }
else { start = Math.max(0, size - parseInt(m[2], 10)); }
if (start > end || start >= size) {
res.writeHead(416, { "Content-Range": `bytes */${size}` });
res.end();
return;
}
status = 206;
headers["Content-Range"] = `bytes ${start}-${end}/${size}`;
}
headers["Content-Length"] = end - start + 1;
res.writeHead(status, headers);
if (req.method === "HEAD") { res.end(); return; }
log(`APK-Download (HTTP) v${apk.version}${status === 206 ? ` ab Byte ${start}` : ""}`);
fs.createReadStream(apk.path, { start, end }).pipe(res);
return;
}
// Health-Endpoint
if (req.method === "GET" && pathname === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
@@ -490,6 +535,17 @@ function learnIdentity(ws, msg) {
if (!id.kind) set("App", "", "", p.version);
else if (p.version && !id.version) id.version = String(p.version).slice(0, 40);
break;
// Nachrichten, die nur die ARIA-App schickt — erkennt auch alte APKs
// ohne client_hello (sobald sie irgendetwas davon senden)
case "app_log":
if (!id.kind && /^(android|ios)$/.test(String(p.platform || ""))) set("App", "", `${p.platform}`, "");
break;
case "location_update":
case "stt_lease_request":
case "update_download":
case "vnc_open":
if (!id.kind) set("App", "", "", "");
break;
case "llm_cluster_request":
case "llm_cluster_models_request":
if (!id.kind) {
@@ -732,7 +788,7 @@ function getLatestAPK() {
.filter(f => f.endsWith(".apk"))
.map(f => {
// ARIA-v0.0.2.3.apk oder ARIA-Cockpit-release.apk
const match = f.match(/(\d+\.\d+\.\d+[\.\d]*)/);
const match = f.match(/(\d+(?:\.\d+){2,})/);
return { file: f, path: path.join(UPDATES_DIR, f), version: match ? match[1] : null };
})
.filter(f => f.version)
+3
View File
@@ -43,6 +43,7 @@ services:
environment:
# GPU-Wahl: NVIDIA_VISIBLE_DEVICES (mehrere via "0,1"). Default GPU 0.
- NVIDIA_VISIBLE_DEVICES=${F5TTS_GPU:-0}
- ARIA_GPU=${F5TTS_GPU:-0} # nur Anzeige (Toolkit macht aus NVIDIA_VISIBLE_DEVICES "void")
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
- NODE_NAME=${NODE_NAME:-node} # erscheint in Diagnostic + Logs
# Bootstrap-only — alle anderen F5-TTS-Settings (Modell, cfg_strength,
@@ -70,6 +71,7 @@ services:
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=${WHISPER_GPU:-1}
- ARIA_GPU=${WHISPER_GPU:-1} # nur Anzeige (Toolkit macht aus NVIDIA_VISIBLE_DEVICES "void")
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
- NODE_NAME=${NODE_NAME:-node}
- RVS_HOST=${RVS_HOST}
@@ -105,6 +107,7 @@ services:
- ./voice-id:/voice-id # Speaker-Fingerprint (wie whisper)
environment:
- NVIDIA_VISIBLE_DEVICES=${VOXTRAL_GPU:-1} # STT-3B ~9 GB → 12-GB-Karte
- ARIA_GPU=${VOXTRAL_GPU:-1} # nur Anzeige (Toolkit macht aus NVIDIA_VISIBLE_DEVICES "void")
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
- NODE_NAME=${NODE_NAME:-node}
- RVS_HOST=${RVS_HOST}
+5 -1
View File
@@ -78,7 +78,11 @@ F5TTS_DEVICE = os.getenv("F5TTS_DEVICE", "cuda") # nur Bootstrap
# ── Compute-Fleet: Worker-Identitaet & Registrierung ──────────────
# Meldet sich bei der aria-bridge (worker_hello) + periodischer worker_ping.
NODE_NAME = os.getenv("NODE_NAME", "node").strip() or "node"
GPU_IDS = os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip()
# Welche Karte(n) dieser Dienst nutzt — fuer die Anzeige. ARIA_GPU setzt die
# Compose; NVIDIA_VISIBLE_DEVICES ersetzt das Container-Toolkit im Prozess
# durch "void", taugt also nicht zur Anzeige.
GPU_IDS = (os.getenv("ARIA_GPU", "").strip()
or os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip().replace("void", ""))
WORKER_SERVICE = "f5tts"
INSTANCE_ID = f"{WORKER_SERVICE}@{NODE_NAME}"
WORKER_PING_INTERVAL_S = int(os.getenv("WORKER_PING_INTERVAL_S", "10"))
+5 -1
View File
@@ -78,7 +78,11 @@ VOXTRAL_DEVICE = os.getenv("VOXTRAL_DEVICE", "cuda")
# Registry per periodischem worker_ping frisch. INSTANCE_ID adressiert diesen
# Worker bei Redundanz (targetInstance-Routing, Stage 3).
NODE_NAME = os.getenv("NODE_NAME", "node").strip() or "node"
GPU_IDS = os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip()
# Welche Karte(n) dieser Dienst nutzt — fuer die Anzeige. ARIA_GPU setzt die
# Compose; NVIDIA_VISIBLE_DEVICES ersetzt das Container-Toolkit im Prozess
# durch "void", taugt also nicht zur Anzeige.
GPU_IDS = (os.getenv("ARIA_GPU", "").strip()
or os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip().replace("void", ""))
WORKER_SERVICE = "voxtral"
INSTANCE_ID = f"{WORKER_SERVICE}@{NODE_NAME}"
WORKER_PING_INTERVAL_S = int(os.getenv("WORKER_PING_INTERVAL_S", "10"))
+5 -1
View File
@@ -77,7 +77,11 @@ WHISPER_LANGUAGE = os.getenv("WHISPER_LANGUAGE", "de")
# ── Compute-Fleet: Worker-Identitaet & Registrierung ──────────────
# Meldet sich bei der aria-bridge (worker_hello) + periodischer worker_ping.
NODE_NAME = os.getenv("NODE_NAME", "node").strip() or "node"
GPU_IDS = os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip()
# Welche Karte(n) dieser Dienst nutzt — fuer die Anzeige. ARIA_GPU setzt die
# Compose; NVIDIA_VISIBLE_DEVICES ersetzt das Container-Toolkit im Prozess
# durch "void", taugt also nicht zur Anzeige.
GPU_IDS = (os.getenv("ARIA_GPU", "").strip()
or os.getenv("NVIDIA_VISIBLE_DEVICES", "").strip().replace("void", ""))
WORKER_SERVICE = "whisper"
INSTANCE_ID = f"{WORKER_SERVICE}@{NODE_NAME}"
WORKER_PING_INTERVAL_S = int(os.getenv("WORKER_PING_INTERVAL_S", "10"))