Replace Deck video generation with selected external service lifecycle

This commit is contained in:
Mikei386
2026-09-29 14:24:52 +02:00
parent 71e8345606
commit badc7701fa
23 changed files with 219 additions and 636 deletions
+47 -1
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env python3
"""Root-owned local helper. Only inventory and fresh Docker install; no generic RPC."""
import argparse
import re
import urllib.request
import urllib.error
import json
import os
from pathlib import Path
@@ -97,8 +100,51 @@ class Manager:
self._checked(['/usr/bin/docker','compose','version','--short'])
self._phase('Docker bereit. Keine Anwendungen oder Modelle installiert.','complete')
except Exception as exc:self._phase(str(exc) if isinstance(exc,ValueError) else 'Docker-Installation fehlgeschlagen. Hostzustand vor erneutem Versuch prüfen.','failed')
def video_configs(self):
path=self.state/'video-services.json'
if not path.exists():return []
if path.is_symlink() or path.stat().st_uid!=0 or path.stat().st_mode&0o022:raise ValueError('Videodienste müssen durch root eingerichtet werden.')
configs=json.loads(path.read_text())
if not isinstance(configs,list) or len(configs)>20:raise ValueError('Ungültige Dienstliste.')
ids=set()
for config in configs:
if not re.fullmatch('[a-z0-9-]{1,40}',config.get('id','')) or config['id'] in ids or not re.fullmatch('[a-f0-9]{64}',config.get('container_id','')):raise ValueError('Ungültige Dienstzuordnung.')
ids.add(config['id'])
return configs
def video_status(self,config):
result=run(['/usr/bin/docker','inspect','--format','{{.State.Running}}',config['container_id']])
if result.returncode:return dict(id=config['id'],name=config['name'],running=None,ready=False,api_url=config['api_url'],message='Zugeordneter Container fehlt.')
running=result.stdout.strip()=='true';ready=False
if running:
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self,*args,**kwargs):return None
try:
headers={}
if config.get('token_file'):headers['Authorization']='Bearer '+Path(config['token_file']).read_text().strip()
request=urllib.request.Request(config['health_url'],headers=headers)
with urllib.request.build_opener(NoRedirect).open(request,timeout=2) as response:ready=response.status==200
except (OSError,ValueError,urllib.error.URLError):pass
return dict(id=config['id'],name=config['name'],running=running,ready=ready,api_url=config['api_url'],message=config.get('message','Original-API des Dienstes.'))
def video_inventory(self):return {'services':[self.video_status(config) for config in self.video_configs()]}
def video_action(self,ident,start):
with self.lock:
configs=self.video_configs();config=next((c for c in configs if c['id']==ident),None)
if config is None:raise ValueError('Videodienst nicht freigegeben.')
status=self.video_status(config)
if start and status['running']:return status
if start:
if any(self.video_status(c)['running'] is not False for c in configs if c['id']!=ident):raise ValueError('Ein anderer Videodienst ist noch aktiv oder nicht prüfbar.')
probe=run(['/usr/bin/nvidia-smi','--query-compute-apps=pid','--format=csv,noheader,nounits'])
if probe.returncode or probe.stdout.strip():raise ValueError('GPU durch einen anderen Dienst belegt. Deck beendet keine fremden Modelle. Alten Router zuerst freigeben.')
result=run(['/usr/bin/docker','start',config['container_id']] if start else ['/usr/bin/docker','stop','--time','30',config['container_id']],timeout=45)
if result.returncode:raise ValueError('Videodienst konnte nicht gestartet oder beendet werden.')
return self.video_status(config)
def dispatch(self,data):
if not isinstance(data,dict) or set(data)!={'action'}:raise ValueError('Ungültige Helferanfrage.')
if not isinstance(data,dict):raise ValueError('Ungültige Helferanfrage.')
if set(data)=={'action','service'} and data['action'] in ('video-start','video-stop'):
return self.video_action(data['service'],data['action']=='video-start')
if set(data)!={'action'}:raise ValueError('Ungültige Helferanfrage.')
if data['action']=='video-status':return self.video_inventory()
if data['action']=='status':return self.status()
if data['action']=='install':return self.install()
raise ValueError('Aktion nicht erlaubt.')