Restore Video workspace and route native video API through shared endpoint

This commit is contained in:
Mikei386 committed 2026-09-29 16:58:36 +02:00
1 parent badc7701fa
commit 67cf57a5b7
15 files changed
+166 -20

No files matched your search

+46 -1
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
"""Root-owned local helper. Only inventory and fresh Docker install; no generic RPC."""
import argparse
import http.client
from urllib.parse import urlsplit
import re
import urllib.request
import urllib.error
@@ -149,6 +151,46 @@ class Manager:
if data['action']=='install':return self.install()
raise ValueError('Aktion nicht erlaubt.')
def video_http(manager,data,source,target):
"""Fixed registered upstream only; no model translation or secret returned to Deck."""
sent=False;connection=None
try:
if set(data)!={'action','service','method','path','length','headers'}:raise ValueError('Ungültige Proxy-Anfrage.')
method=data['method'];path=data['path'];length=data['length'];headers=data['headers']
if method not in ('GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS') or not isinstance(path,str) or not path.startswith('/') or path.startswith('//') or any(ord(c)<32 for c in path):raise ValueError('Ungültige HTTP-Anfrage.')
if type(length) is not int or not 0<=length<=256*1024*1024 or not isinstance(headers,dict):raise ValueError('Ungültige Anfragegröße.')
allowed={'content-type','accept','range','if-range','if-none-match','if-modified-since'}
if any(not isinstance(k,str) or k.lower() not in allowed or not isinstance(v,str) or '\r' in v or '\n' in v for k,v in headers.items()):raise ValueError('Ungültige Header.')
config=next((c for c in manager.video_configs() if c['id']==data['service']),None)
if config is None:raise ValueError('Videodienst nicht freigegeben.')
if manager.video_status(config)['running'] is not True:raise ValueError('Videodienst nicht gestartet.')
url=urlsplit(config['api_url'])
if url.scheme not in ('http','https') or not url.hostname or url.username or url.password:raise ValueError('Ungültiges Backend-Ziel.')
connection=(http.client.HTTPSConnection if url.scheme=='https' else http.client.HTTPConnection)(url.hostname,url.port,timeout=3600)
connection.putrequest(method,path,skip_accept_encoding=True)
for key,value in headers.items():connection.putheader(key,value)
if config.get('token_file'):connection.putheader('Authorization','Bearer '+Path(config['token_file']).read_text().strip())
connection.putheader('Content-Length',str(length));connection.putheader('Connection','close');connection.endheaders()
remaining=length
while remaining:
chunk=source.read(min(1024*1024,remaining))
if not chunk:raise ValueError('Unvollständige Anfrage.')
connection.send(chunk);remaining-=len(chunk)
response=connection.getresponse();sent=True
target.write(f'HTTP/1.1 {response.status} {response.reason}\r\n'.encode())
hop={'connection','keep-alive','proxy-authenticate','proxy-authorization','te','trailer','transfer-encoding','upgrade'}
for key,value in response.getheaders():
if key.lower() not in hop:target.write(f'{key}: {value}\r\n'.encode('latin-1'))
target.write(b'Connection: close\r\n\r\n');target.flush()
if method!='HEAD':
while chunk:=response.read(1024*1024):target.write(chunk);target.flush()
except Exception:
if not sent:
body=b'{"error":"Video-API nicht erreichbar oder Anfrage nicht erlaubt."}'
target.write(b'HTTP/1.1 502 Bad Gateway\r\nContent-Type: application/json\r\nContent-Length: '+str(len(body)).encode()+b'\r\nConnection: close\r\n\r\n'+body)
finally:
if connection:connection.close()
class Handler(socketserver.StreamRequestHandler):
def handle(self):
self.connection.settimeout(20)
@@ -157,7 +199,10 @@ class Handler(socketserver.StreamRequestHandler):
if uid not in (0,self.server.client_uid):raise ValueError('Client nicht erlaubt.')
raw=self.rfile.readline(4097)
if len(raw)>4096:raise ValueError('Anfrage zu groß.')
result=self.server.manager.dispatch(json.loads(raw))
data=json.loads(raw)
if isinstance(data,dict) and data.get('action')=='video-http':
self.connection.settimeout(3600);video_http(self.server.manager,data,self.rfile,self.wfile);return
result=self.server.manager.dispatch(data)
except Exception as exc:result={'error':str(exc) if isinstance(exc,ValueError) else 'Docker-Systemhelfer nicht verfügbar.'}
self.wfile.write(json.dumps(result).encode()+b'\n')