Replace WireGuard prototype with native host service and scoped access
This commit is contained in:
1 parent
37b5394df1
commit
11f16ef4a3
16 files changed
+667
-136
No files matched your search
+13
-4
@@ -14,7 +14,7 @@ import urllib.request
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
LABEL = 'de.casaderoll.athena-deck.standalone'
|
||||
FILES = ['separator_sources.json','deploy/separator-web/Dockerfile','deploy/separator-web/app.py','deploy/separator-web/index.html','separator.py','separator_runtime.py','separator_worker.py','separator-ui.js','deploy/ladypoly/Dockerfile','deploy/ladypoly/bridge.py','music.py','music-ui.js','audio_cpp_runtime.py','audio-cpp-ui.js','ltx_original_runtime.py','ltx_original_entry.py','video_original.py','ltx-original-ui.js','deploy/ltx-original-requirements.lock','backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','deploy/swarm-ui/Dockerfile','deploy/swarm-ui/patch-source.py','deploy/swarm-ui/proxy.py','deploy/swarm-ui/entrypoint.py','deploy/Dockerfile.app-update','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','prompt_enhancer.py','prompt_enhancer_worker.py','image_upload.py','audio_policy.py','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','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','deploy/import_dashboard_history.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 = ['network/native.py','deploy/setup_network_helper.py','separator_sources.json','deploy/separator-web/Dockerfile','deploy/separator-web/app.py','deploy/separator-web/index.html','separator.py','separator_runtime.py','separator_worker.py','separator-ui.js','deploy/ladypoly/Dockerfile','deploy/ladypoly/bridge.py','music.py','music-ui.js','audio_cpp_runtime.py','audio-cpp-ui.js','ltx_original_runtime.py','ltx_original_entry.py','video_original.py','ltx-original-ui.js','deploy/ltx-original-requirements.lock','backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','deploy/swarm-ui/Dockerfile','deploy/swarm-ui/patch-source.py','deploy/swarm-ui/proxy.py','deploy/swarm-ui/entrypoint.py','deploy/Dockerfile.app-update','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','prompt_enhancer.py','prompt_enhancer_worker.py','image_upload.py','audio_policy.py','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','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','deploy/import_dashboard_history.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):
|
||||
@@ -107,6 +107,8 @@ def launch(config):
|
||||
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.
|
||||
args += ['-e','DECK_REQUIRE_SETUP=0']
|
||||
if config.get('network_helper'):
|
||||
args+=['--mount','type=bind,src=/run/athena-deck-network,dst=/run/athena-deck-network,readonly','-e','DECK_NETWORK_MODE=native','-e','DECK_PROXY_TOKEN_FILE=/run/athena-deck-network/proxy-token']
|
||||
if config.get('docker_helper'):
|
||||
args += ['--mount','type=bind,src=/run/athena-deck-docker,dst=/run/athena-deck-docker,readonly','-e','DECK_DOCKER_HELPER_SOCKET=/run/athena-deck-docker/control.sock']
|
||||
if config['gpu_telemetry']:
|
||||
@@ -233,10 +235,12 @@ def parse_api_ports(value):
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description='Athena Deck auf Debian 12/13 getrennt installieren. Kein WireGuard, keine Host-Paket- oder Treiberänderungen.')
|
||||
parser=argparse.ArgumentParser(description='Athena Deck auf Debian 12/13 getrennt installieren. WireGuard optional als nativer Hostdienst; keine Treiberänderungen.')
|
||||
actions=parser.add_mutually_exclusive_group(required=True)
|
||||
for action in ('check','install','setup','start','stop','status','update','rollback','docker-helper'):
|
||||
for action in ('check','install','setup','start','stop','status','update','rollback','docker-helper','network-helper'):
|
||||
actions.add_argument('--'+action,action='store_true')
|
||||
parser.add_argument('--lan-address',help='Host-LAN-Adresse für den nativen WireGuard-Dienst.')
|
||||
parser.add_argument('--network-ports',help='Explizit freizugebende Ports; Standard GUI und API-Portbereich.')
|
||||
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)
|
||||
@@ -257,7 +261,12 @@ def main():
|
||||
else:
|
||||
config=load_manifest(args.directory)
|
||||
item=owned(config['name'],args.directory)
|
||||
if args.docker_helper:
|
||||
if args.network_helper:
|
||||
if not args.lan_address:raise RuntimeError('--lan-address erforderlich.')
|
||||
ports=args.network_ports or ','.join(map(str,[config['port'],*config.get('api_ports',[])]))
|
||||
run(sys.executable,str(ROOT/'deploy/setup_network_helper.py'),'--client-uid','65534','--client-gid','65534','--lan-address',args.lan_address,'--gui-port',str(config['port']),'--ports',ports)
|
||||
config['network_helper']=True;write_manifest(args.directory,config);update(args.directory,force=True,reuse_runtime=True)
|
||||
elif args.docker_helper:
|
||||
run(sys.executable,str(ROOT/'deploy/setup_docker_helper.py'),'--client-uid','65534','--client-gid','65534','--deck-state',str(args.directory/'state'))
|
||||
config['docker_helper']=True;write_manifest(args.directory,config);update(args.directory,force=True)
|
||||
elif args.status:
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install the native Deck host network service, initially disconnected.
|
||||
Does not stop existing gateways, import secrets or change default routes.
|
||||
"""
|
||||
import argparse
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import socket
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
MARKER='# Athena Deck native network service'
|
||||
def main():
|
||||
p=argparse.ArgumentParser();p.add_argument('--client-uid',type=int,required=True);p.add_argument('--client-gid',type=int,required=True);p.add_argument('--lan-address',required=True);p.add_argument('--gui-port',type=int,default=8108);p.add_argument('--install-tools',action='store_true');p.add_argument('--ports',default='8108,8120,8121,8122,8123,8124');a=p.parse_args()
|
||||
if os.geteuid()!=0 or not Path('/run/systemd/system').is_dir():raise SystemExit('Root auf Debian mit systemd erforderlich.')
|
||||
if a.client_uid<1 or a.client_gid<1:raise SystemExit('Unprivilegierte Deck-UID/GID erforderlich.')
|
||||
address=ipaddress.ip_address(a.lan_address)
|
||||
if address.version!=4 or address.is_loopback or address.is_unspecified:raise SystemExit('Konkrete Host-LAN-Adresse erforderlich.')
|
||||
ports=sorted(set(int(v) for v in a.ports.split(',')))
|
||||
if a.gui_port not in ports or any(not 1024<=v<=65535 for v in ports) or len(ports)>16:raise SystemExit('Maximal 16 feste, nicht privilegierte Ports einschließlich GUI-Port.')
|
||||
if a.install_tools and (not shutil.which('wg') or not shutil.which('ip')):
|
||||
subprocess.run(['apt-get','update'],check=True)
|
||||
subprocess.run(['apt-get','install','-y','--no-install-recommends','wireguard-tools','iproute2'],check=True)
|
||||
if not shutil.which('wg') or not shutil.which('ip'):raise SystemExit('Zuerst Debian-Pakete wireguard-tools und iproute2 installieren. Kein Kernel- oder Treiberupdate erforderlich.')
|
||||
base=Path('/opt/athena-deck-network');state=Path('/var/lib/athena-deck-network');unit=Path('/etc/systemd/system/athena-deck-network.service')
|
||||
if any(p.is_symlink() for p in (base,state,unit)):raise SystemExit('Symlink-Ziel nicht erlaubt.')
|
||||
if unit.exists() and not unit.read_text().startswith(MARKER):raise SystemExit('Dienstname belegt.')
|
||||
if base.exists() and not (base/'managed-by-deck').exists():raise SystemExit('Installationsverzeichnis belegt.')
|
||||
base.mkdir(mode=0o755,exist_ok=True);(base/'managed-by-deck').touch();(base/'network').mkdir(exist_ok=True)
|
||||
source=Path(__file__).resolve().parent.parent/'network'
|
||||
for name in ('__init__.py','native.py','config.py','policy.py'):shutil.copyfile(source/name,base/'network'/name)
|
||||
state.mkdir(mode=0o700,exist_ok=True);state.chmod(0o700)
|
||||
policy=state/'policy.json';interfaces=json.loads(subprocess.check_output(['ip','-j','address','show'],text=True))
|
||||
interface=next((r['ifname'] for r in interfaces if any(v.get('local')==str(address) for v in r.get('addr_info',[]))),None)
|
||||
if not interface:raise SystemExit('LAN-Adresse ist nicht auf diesem Host vorhanden.')
|
||||
value={'lan_address':str(address),'lan_interface':interface,'gui_port':a.gui_port,'ports':ports}
|
||||
if policy.exists() and json.loads(policy.read_text())!=value:raise SystemExit('Bestehende Dienst-Portdefinition nicht automatisch ändern.')
|
||||
policy.write_text(json.dumps(value));policy.chmod(0o600)
|
||||
unit.write_text(MARKER+f'''
|
||||
[Unit]
|
||||
Description=Athena Deck native WireGuard and restricted access service
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory={base}
|
||||
ExecStart=/usr/bin/python3 -m network.native --client-uid {a.client_uid} --client-gid {a.client_gid}
|
||||
Restart=on-failure
|
||||
RuntimeDirectory=athena-deck-network
|
||||
RuntimeDirectoryMode=0755
|
||||
RuntimeDirectoryPreserve=yes
|
||||
StateDirectory=athena-deck-network
|
||||
StateDirectoryMode=0700
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_CHOWN
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
ReadWritePaths=/var/lib/athena-deck-network /run/athena-deck-network
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
''');unit.chmod(0o644)
|
||||
subprocess.run(['systemctl','daemon-reload'],check=True)
|
||||
subprocess.run(['systemctl','enable','athena-deck-network.service'],check=True,capture_output=True)
|
||||
subprocess.run(['systemctl','restart','athena-deck-network.service'],check=True)
|
||||
for _ in range(30):
|
||||
try:
|
||||
with socket.socket(socket.AF_UNIX) as sock:
|
||||
sock.settimeout(2);sock.connect('/run/athena-deck-network/control.sock');sock.sendall(b'{"action":"status"}\n')
|
||||
with sock.makefile('rb') as f:status=json.loads(f.readline(65536))
|
||||
if status.get('installed'):break
|
||||
except (OSError,ValueError):pass
|
||||
time.sleep(.2)
|
||||
else:raise SystemExit('Nativer Dienst nicht bereit; systemctl status athena-deck-network.service prüfen.')
|
||||
print('Nativer Netzwerkdienst installiert. Bestehender Gateway und dessen Konfiguration unverändert.')
|
||||
if __name__=='__main__':main()
|
||||
Reference in new issue
Block a user