Restore Video workspace and route native video API through shared endpoint

This commit is contained in:
Mikei386
2026-09-29 16:58:36 +02:00
parent badc7701fa
commit 67cf57a5b7
15 changed files with 166 additions and 20 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
WORKDIR /app
COPY deploy/image-requirements.lock /app/deploy/image-requirements.lock
COPY deploy/tts-requirements.lock /app/deploy/tts-requirements.lock
COPY api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py profiles.py capacity.py server.py runtime.py video.py catalog.py hub_auth.py auth.py collect_hardware.py /app/
COPY api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py /app/
COPY video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css login.html login.js access-ui.js network-ui.js /app/
COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
+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')
+1 -1
View File
@@ -14,7 +14,7 @@ import urllib.request
ROOT = Path(__file__).resolve().parent.parent
LABEL = 'de.casaderoll.athena-deck.standalone'
FILES = ['deploy/tts-requirements.lock','stt.py','stt-ui.js','api_compat.py','execution_setup.py','tts_runtime.py','tts_test.py','tts_worker.py','tts-ui.js','auto_test.py','auto-test-ui.js','chat_test.py','chat-test-ui.js','endpoint.py','inference.py','endpoint-ui.js','docker_support.py','docker-ui.js','deploy/docker_helper.py','deploy/setup_docker_helper.py','image_encoder_node.py','image_runtime.py','image_test.py','image-test-ui.js','profiles.py','profiles-ui.js','capacity.py','runtime.py','runtime-ui.js','video.py','video-ui.js','catalog.py','hub_auth.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','index.html','app.js','studio.js','style.css','login.html','login.js','access-ui.js','network-ui.js','network/__init__.py','network/client.py','network/config.py','network/rpc.py','deploy/Dockerfile','deploy/image-requirements.lock']
FILES = ['deploy/tts-requirements.lock','stt.py','stt-ui.js','api_compat.py','execution_setup.py','tts_runtime.py','tts_test.py','tts_worker.py','tts-ui.js','auto_test.py','auto-test-ui.js','chat_test.py','chat-test-ui.js','endpoint.py','inference.py','endpoint-ui.js','docker_support.py','docker-ui.js','deploy/docker_helper.py','deploy/setup_docker_helper.py','image_encoder_node.py','image_runtime.py','image_test.py','image-test-ui.js','profiles.py','profiles-ui.js','capacity.py','runtime.py','runtime-ui.js','video.py','video_proxy.py','video-ui.js','catalog.py','hub_auth.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','index.html','app.js','studio.js','style.css','login.html','login.js','access-ui.js','network-ui.js','network/__init__.py','network/client.py','network/config.py','network/rpc.py','deploy/Dockerfile','deploy/image-requirements.lock']
def run(*args, check=True, interactive=False):