Add owned OpenAI endpoint and coordinated native model switching
This commit is contained in:
+25
-7
@@ -14,7 +14,7 @@ import urllib.request
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
LABEL = 'de.casaderoll.athena-deck.standalone'
|
||||
FILES = ['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','catalog.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','demo.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 = ['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','catalog.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):
|
||||
@@ -98,6 +98,9 @@ def launch(config):
|
||||
'-p','127.0.0.1:'+str(config['port'])+':8108',
|
||||
'--health-cmd', 'python3 -c "import urllib.request,json; assert json.load(urllib.request.urlopen(\'http://127.0.0.1:8108/api/v1/auth/status\',timeout=3))[\'initialized\']"',
|
||||
'--health-interval','30s','--health-timeout','5s','--health-retries','3']
|
||||
if config.get('api_ports'):
|
||||
args+=['-e','DECK_API_BIND=0.0.0.0','-e','DECK_API_PORTS='+','.join(map(str,config['api_ports']))]
|
||||
for port in config['api_ports']:args+=['-p',f'127.0.0.1:{port}:{port}']
|
||||
if config.get('development_setup'):
|
||||
args[args.index('--health-cmd')+1] = 'python3 -c "import urllib.request; urllib.request.urlopen(\'http://127.0.0.1:8108/api/v1/auth/status\',timeout=3)"'
|
||||
# Explicit isolated development bootstrap, reachable only through host loopback/SSH.
|
||||
@@ -151,6 +154,9 @@ def install(args):
|
||||
if base.exists():
|
||||
raise RuntimeError('Installationsverzeichnis existiert bereits. Es wird nicht überschrieben.')
|
||||
free_port(args.port)
|
||||
api_ports=parse_api_ports(args.api_ports or '8120-8124')
|
||||
if args.port in api_ports:raise RuntimeError('Verwaltungsport und API-Ports müssen verschieden sein.')
|
||||
for port in api_ports:free_port(port)
|
||||
parent=base.parent
|
||||
while not parent.exists():parent=parent.parent
|
||||
if shutil.disk_usage(parent).free < 25*1024**3:
|
||||
@@ -162,7 +168,7 @@ def install(args):
|
||||
for sub in ('state','models','backups','bootstrap'):
|
||||
(base/sub).mkdir(mode=0o700)
|
||||
os.chown(base/'state',65534,65534)
|
||||
config=dict(owner=LABEL,base=str(base),name=args.name,port=args.port,image=image,previous_image=None,gpu_telemetry=args.gpu_telemetry,image_runtime=True,docker_helper=True)
|
||||
config=dict(owner=LABEL,base=str(base),name=args.name,port=args.port,api_ports=api_ports,image=image,previous_image=None,gpu_telemetry=args.gpu_telemetry,image_runtime=True,docker_helper=True)
|
||||
run(sys.executable,str(ROOT/'deploy/setup_docker_helper.py'),'--client-uid','65534','--client-gid','65534')
|
||||
write_manifest(base,config)
|
||||
provision(config)
|
||||
@@ -177,23 +183,26 @@ def install(args):
|
||||
print('Generierter API-Token: geschützte Datei '+str(base/'bootstrap/api-token.txt')+'. In Passwortmanager übernehmen und Datei danach entfernen.')
|
||||
|
||||
|
||||
def update(base, rollback=False, force=False):
|
||||
def update(base, rollback=False, force=False, api_ports=None):
|
||||
config=load_manifest(base)
|
||||
previous=owned(config['name'],base)
|
||||
if not previous:
|
||||
raise RuntimeError('Kein installierter Deck-Container vorhanden.')
|
||||
selected_ports=config.get('api_ports',[]) if api_ports is None else parse_api_ports(api_ports)
|
||||
if config['port'] in selected_ports:raise RuntimeError('Verwaltungsport und API-Ports müssen verschieden sein.')
|
||||
for port in set(selected_ports)-set(config.get('api_ports',[])):free_port(port)
|
||||
image=config.get('previous_image') if rollback else build()
|
||||
if not image:raise RuntimeError('Kein vorheriger Build gespeichert.')
|
||||
if image==config['image'] and not force:
|
||||
if image==config['image'] and selected_ports==config.get('api_ports',[]) and not force:
|
||||
print('Dieser Quellstand ist bereits installiert.');return
|
||||
backup_name=config['name']+'-previous'
|
||||
if inspect(backup_name):raise RuntimeError('Rückfall-Containername belegt. Keine Änderung ausgeführt.')
|
||||
stamp=str(time.time_ns())
|
||||
shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('models','runtime','runtime-verification','image-tests','image-verification','image-verification-*','image-runtime'))
|
||||
shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('models','runtime','runtime-verification','image-tests','image-verification','image-verification-*','image-runtime','router-verification-*'))
|
||||
was_running=previous['State']['Running']
|
||||
if was_running:run('docker','stop','--time','15',config['name'])
|
||||
run('docker','rename',config['name'],backup_name)
|
||||
new=dict(config,image=image,previous_image=config['image'])
|
||||
new=dict(config,image=image,previous_image=config['image'],api_ports=selected_ports)
|
||||
try:
|
||||
launch(new);ready(new);write_manifest(base,new)
|
||||
except Exception:
|
||||
@@ -206,6 +215,14 @@ def update(base, rollback=False, force=False):
|
||||
print('Nur Athena Deck wurde aktualisiert. Vorheriges Image bleibt für --rollback erhalten.')
|
||||
|
||||
|
||||
def parse_api_ports(value):
|
||||
import re
|
||||
if not isinstance(value,str) or not re.fullmatch(r'[0-9]{4,5}(?:-[0-9]{4,5})?',value):raise RuntimeError('API-Port oder kleiner Portbereich erwartet, z. B. 8120-8124.')
|
||||
parts=[int(x) for x in value.split('-')];start=parts[0];end=parts[-1]
|
||||
if not 1024<=start<=end<=65535 or end-start>=16:raise RuntimeError('Maximal 16 nicht privilegierte API-Ports verwenden.')
|
||||
return list(range(start,end+1))
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description='Athena Deck auf Debian 12/13 getrennt installieren. Kein WireGuard, keine Host-Paket- oder Treiberänderungen.')
|
||||
actions=parser.add_mutually_exclusive_group(required=True)
|
||||
@@ -214,6 +231,7 @@ def main():
|
||||
parser.add_argument('--directory',type=Path,default=Path('/opt/athena-deck-standalone'))
|
||||
parser.add_argument('--name',default='athena-deck-standalone')
|
||||
parser.add_argument('--port',type=int,default=8110)
|
||||
parser.add_argument('--api-ports',help='Separater API-Port oder Bereich, z. B. 8120-8124. Installationsstandard: 8120-8124; Updates behalten den bisherigen Bereich.')
|
||||
parser.add_argument('--gpu-telemetry',action='store_true',help='Vorhandenes NVIDIA Container Toolkit für Messwerte und Fit-Prüfung nutzen; installiert keine Treiber.')
|
||||
args=parser.parse_args()
|
||||
import re
|
||||
@@ -248,7 +266,7 @@ def main():
|
||||
if item:run('docker','start',config['name'])
|
||||
else:launch(config)
|
||||
ready(config);print('Deck bereit.')
|
||||
elif args.update or args.rollback:update(args.directory,args.rollback)
|
||||
elif args.update or args.rollback:update(args.directory,args.rollback,api_ports=args.api_ports)
|
||||
|
||||
if __name__=='__main__':
|
||||
try:main()
|
||||
|
||||
Reference in New Issue
Block a user