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
+21
-92
@@ -1,101 +1,30 @@
|
||||
"""Local management via fixed SSH target or container-private Unix RPC."""
|
||||
from auth import initial_record
|
||||
import base64
|
||||
import hashlib
|
||||
"""Authenticated Deck administration through a native host Unix socket only."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import secrets
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SSH = ['ssh', '-i', str(Path.home()/'.ssh/athena_key'), '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', '-o', 'StrictHostKeyChecking=yes', 'root@192.168.1.212']
|
||||
from network.rpc import request
|
||||
|
||||
class NetworkClient:
|
||||
def __init__(self):
|
||||
self.disabled = os.environ.get('DECK_NETWORK_MODE') == 'disabled'
|
||||
self.local = os.environ.get('DECK_NETWORK_SOCKET') == '1'
|
||||
self.job = None
|
||||
self.lock = threading.Lock()
|
||||
self.cached = None
|
||||
self.cached_at = 0
|
||||
|
||||
def call(self, action, values=None, ingress='management'):
|
||||
if self.disabled:
|
||||
raise ValueError('WireGuard ist in dieser eigenständigen Installation nicht angebunden.')
|
||||
data = dict(values or {}, action=action)
|
||||
if self.local:
|
||||
from network.rpc import request
|
||||
data.update(ingress=ingress, proxy_token=os.environ.get('DECK_PROXY_TOKEN', ''))
|
||||
return request(data)
|
||||
result = subprocess.run(SSH + ['docker exec -i athena-deck-network python3 -m network.rpc'], input=json.dumps(data), text=True, capture_output=True, timeout=35)
|
||||
try:
|
||||
value = json.loads(result.stdout)
|
||||
except ValueError:
|
||||
raise ValueError('Deck-Netzwerkmodul auf Athena nicht erreichbar oder noch nicht installiert.') from None
|
||||
if result.returncode or ('error' in value and 'installed' not in value):
|
||||
raise ValueError(value.get('error', 'Netzwerkaktion fehlgeschlagen.'))
|
||||
self.cached = None
|
||||
return value
|
||||
|
||||
def status(self, ingress='management'):
|
||||
if self.disabled:
|
||||
return dict(installed=False, install_supported=False, state='disabled-module', ingress=ingress, error='Eigenständige Installation ohne WireGuard-Modul. Zugriff ist per SSH-Tunnel möglich. Vorhandene Gateways werden nicht verändert.')
|
||||
self.native=os.environ.get('DECK_NETWORK_MODE')=='native'
|
||||
self.disabled=not self.native
|
||||
self.local=False
|
||||
self.job=None;self.lock=threading.Lock();self.cached=None;self.cached_at=0
|
||||
def call(self,action,values=None,ingress='management'):
|
||||
if self.disabled:raise ValueError('Nativen WireGuard-Systemdienst zuerst auf dem Debian-Host einrichten.')
|
||||
token=Path(os.environ.get('DECK_PROXY_TOKEN_FILE','/run/athena-deck-network/proxy-token')).read_text().strip()
|
||||
result=request(dict(values or {},action=action,ingress=ingress,proxy_token=token),path='/run/athena-deck-network/control.sock')
|
||||
self.cached=None
|
||||
return result
|
||||
def status(self,ingress='management'):
|
||||
if self.disabled:return dict(installed=False,install_supported=False,state='disabled-module',ingress=ingress,backend='native-systemd',error='Nativer WireGuard-Systemdienst ist noch nicht angebunden. Einrichtung siehe Installationsanleitung.')
|
||||
with self.lock:
|
||||
if self.job and self.job['state'] == 'running':
|
||||
return dict(installed=False, state='installing', job=self.job.copy(), ingress=ingress)
|
||||
if self.cached is not None and time.monotonic()-self.cached_at < 3:
|
||||
return dict(self.cached, ingress=ingress, job=self.job)
|
||||
try:
|
||||
result = self.call('status', ingress=ingress)
|
||||
result['reachable'] = True
|
||||
except (ValueError, OSError, subprocess.SubprocessError):
|
||||
result = dict(installed=False, reachable=False, state='unavailable', error='Netzwerkmodul noch nicht installiert oder Athena nicht erreichbar.')
|
||||
self.cached = result
|
||||
self.cached_at = time.monotonic()
|
||||
return dict(result, ingress=ingress, job=self.job)
|
||||
|
||||
def install(self, password, api_token=None):
|
||||
if self.disabled:
|
||||
raise ValueError('WireGuard-Installation ist für diese eigenständige Instanz deaktiviert.')
|
||||
if self.local:
|
||||
raise ValueError('Die Server-Instanz ist bereits installiert.')
|
||||
if not isinstance(password, str) or not 16 <= len(password) <= 256:
|
||||
raise ValueError('Bitte ein eigenes Deck-Passwort mit mindestens 16 Zeichen setzen.')
|
||||
auth = initial_record(password,api_token)
|
||||
with self.lock:
|
||||
if self.job and self.job['state'] == 'running':
|
||||
raise ValueError('Installation läuft bereits.')
|
||||
self.job = dict(state='running', message='Eigener Deck-Container wird gebaut und gestartet. Das kann mehrere Minuten dauern.')
|
||||
threading.Thread(target=self._install, args=(auth,), daemon=True).start()
|
||||
return dict(job=self.job.copy())
|
||||
|
||||
def _install(self, auth):
|
||||
try:
|
||||
# Import the source allowlist without executing the installer entrypoint.
|
||||
from network.install_remote import FILES
|
||||
payload = dict(auth=auth, files={name:base64.b64encode((ROOT/name).read_bytes()).decode() for name in FILES})
|
||||
script = (ROOT/'network/install_remote.py').read_text()
|
||||
# Remote command contains only fixed trusted program text, never user input.
|
||||
import shlex
|
||||
command = 'python3 -c ' + shlex.quote(script)
|
||||
result = subprocess.run(SSH+[command], input=json.dumps(payload), text=True, capture_output=True, timeout=900)
|
||||
value = json.loads(result.stdout)
|
||||
if result.returncode or ('error' in value and 'installed' not in value):
|
||||
raise ValueError(value.get('error', 'Installation fehlgeschlagen.'))
|
||||
# Confirm helper readiness; don't report a successful installation from docker run alone.
|
||||
for _ in range(20):
|
||||
try:
|
||||
self.call('status')
|
||||
break
|
||||
except (ValueError, OSError, subprocess.SubprocessError):
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise ValueError('Container angelegt, aber Netzwerk-Helper nicht bereit. Installation prüfen.')
|
||||
self.job = dict(state='complete', message='Netzwerkmodul installiert. Jetzt eine eigene WireGuard-Konfiguration importieren.')
|
||||
except Exception as exc:
|
||||
self.job = dict(state='failed', message=str(exc) if isinstance(exc, ValueError) and not isinstance(exc, json.JSONDecodeError) else 'Installation fehlgeschlagen; SSH und Docker prüfen.')
|
||||
finally:
|
||||
self.cached = None
|
||||
if self.cached is not None and time.monotonic()-self.cached_at<3:return dict(self.cached,ingress=ingress)
|
||||
try:result=self.call('status',ingress=ingress);result['reachable']=True
|
||||
except (ValueError,OSError,json.JSONDecodeError):result=dict(installed=False,install_supported=False,reachable=False,state='unavailable',error='Nativer WireGuard-Systemdienst nicht erreichbar.')
|
||||
self.cached=result;self.cached_at=time.monotonic()
|
||||
return dict(result,ingress=ingress)
|
||||
def install(self,password=None,api_token=None):
|
||||
raise ValueError('WireGuard wird nativ mit deploy/setup_network_helper.py eingerichtet. Kein Netzwerk-Container und keine zweite Deck-Instanz.')
|
||||
Reference in new issue
Block a user