Add encrypted configuration backup and planned restore

This commit is contained in:
Mikei386
2026-09-30 14:20:21 +02:00
parent 18adaf4a52
commit e7a1deaab0
22 changed files with 844 additions and 28 deletions
+1
View File
@@ -1,5 +1,6 @@
FROM python:3.13-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends wireguard-tools iproute2 && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir cryptography==50.0.1
WORKDIR /app
COPY . /app
ENV PYTHONDONTWRITEBYTECODE=1 DECK_CONTAINER=1
+20
View File
@@ -198,6 +198,26 @@ class Agent:
record = validate_record(data.get('credentials'))
save(DATA/'auth.json', record)
return {'saved':True}
if action == 'backup-export':
config = json.loads((DATA/'config.json').read_text()) if (DATA/'config.json').exists() else None
return {'config': config, 'enabled': self.address is not None, 'mode': self.policy.mode}
if action == 'backup-restore':
if self.address or self.policy.mode != 'lan' or self.policy.pending:
raise ValueError('Netzwerk-Restore nur im bestätigten LAN-Modus bei deaktiviertem Deck-Tunnel.')
value = data.get('backup')
if not isinstance(value, dict) or set(value) != {'config','enabled','mode'} or value['mode'] not in ('lan','both','tunnel') or type(value['enabled']) is not bool:
raise ValueError('Ungültiger Netzwerk-Backupstand.')
if value['config'] is not None:
parse_config(value['config']);save(DATA/'config.json', value['config'])
if value['enabled']:
self.connect();save(DATA/'enabled.json', True)
until=time.monotonic()+20
while not self.connected()[0] and time.monotonic()<until:time.sleep(.5)
if not self.connected()[0]:
self.disconnect();save(DATA/'enabled.json',False)
raise ValueError('WireGuard-Handshake fehlt; LAN-Zugang bleibt erhalten.')
if value['mode'] != 'lan':self.policy.propose(value['mode'], True)
return self.status(ingress)
if action == 'status':
return self.status(ingress)
if action == 'import':
+1 -1
View File
@@ -12,7 +12,7 @@ import sys
NAME = 'athena-deck-network'
BASE = Path('/opt/athena-deck')
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','image_runtime.py','image_encoder_node.py','docker_support.py','docker-ui.js','deploy/image-requirements.lock','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', 'studio.js', 'auth.py', 'access-ui.js', 'server.py', 'collect_hardware.py', 'index.html', 'app.js', 'style.css', 'network-ui.js', 'login.html', 'login.js', 'network/__init__.py', 'network/config.py', 'network/policy.py', 'network/rpc.py', 'network/agent.py', 'network/client.py', 'network/Dockerfile', '.dockerignore'}
FILES = {'backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','image_upload.py','prompt_enhancer.py','prompt_enhancer_worker.py','audio_policy.py','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','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','image_runtime.py','image_encoder_node.py','docker_support.py','docker-ui.js','deploy/image-requirements.lock','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', 'studio.js', 'auth.py', 'access-ui.js', 'server.py', 'collect_hardware.py', 'index.html', 'app.js', 'style.css', 'network-ui.js', 'login.html', 'login.js', 'network/__init__.py', 'network/config.py', 'network/policy.py', 'network/rpc.py', 'network/agent.py', 'network/client.py', 'network/Dockerfile', '.dockerignore'}
def run(*args, **kwargs):
return subprocess.run(args, capture_output=True, timeout=600, **kwargs)