Add isolated Debian installer with credential setup and guarded updates

This commit is contained in:
Mikei386
2026-09-28 15:35:17 +02:00
parent e1a54e3485
commit 2ad844ed20
12 changed files with 618 additions and 8 deletions
+10 -5
View File
@@ -93,7 +93,7 @@ class HardwareProvider:
class Server(ThreadingHTTPServer):
daemon_threads = True
def __init__(self, port, state_dir=None):
super().__init__(('127.0.0.1', port), Handler)
super().__init__((os.environ.get('DECK_BIND_HOST','127.0.0.1'), port), Handler)
self.demo = DemoService()
self.hardware = HardwareProvider()
self.started = time.time()
@@ -103,6 +103,9 @@ class Server(ThreadingHTTPServer):
self.credentials = CredentialStore(rpc=request)
else:
self.credentials = CredentialStore(Path(state_dir or os.environ.get('DECK_STATE_DIR', ROOT/'.state'))/'auth.json')
if os.environ.get('DECK_REQUIRE_SETUP') == '1' and self.credentials.read() is None:
self.server_close()
raise RuntimeError('Serverzugang muss vor dem Start eingerichtet werden.')
self.sessions = {}
self.login_attempts = []
self.auth_lock = threading.Lock()
@@ -215,7 +218,7 @@ class Handler(BaseHTTPRequestHandler):
signed_in = self.authenticated(session_only=True)
value = dict(initialized=record is not None, authenticated=signed_in)
if signed_in:
value.update(api_token_configured=bool(record['api_token_hash']), password_changed_at=record['password_changed_at'], token_changed_at=record['token_changed_at'], account='Administrator', location='Athena' if os.environ.get('DECK_AUTH_RPC') else 'Mac')
value.update(api_token_configured=bool(record['api_token_hash']), password_changed_at=record['password_changed_at'], token_changed_at=record['token_changed_at'], account='Administrator', location=os.environ.get('DECK_LOCATION', 'Athena' if os.environ.get('DECK_AUTH_RPC') else 'Mac'))
return self.respond(value)
def change_credentials(self, kind):
@@ -239,7 +242,9 @@ class Handler(BaseHTTPRequestHandler):
def valid_host(self):
if os.environ.get('DECK_PROXY_TOKEN'):
return self.ingress() in ('lan','tunnel')
return self.headers.get('Host') in (f'127.0.0.1:{self.server.server_port}', f'localhost:{self.server.server_port}')
allowed = {f'127.0.0.1:{self.server.server_port}', f'localhost:{self.server.server_port}'}
allowed.update(filter(None,os.environ.get('DECK_ALLOWED_HOSTS','').split(',')))
return self.headers.get('Host') in allowed
def do_GET(self):
if not self.valid_host():
@@ -257,7 +262,7 @@ class Handler(BaseHTTPRequestHandler):
name, mime = routes[self.path]
return self.respond((ROOT/name).read_bytes(), mime=mime)
if self.path == '/api/v1/status':
return self.respond(dict(name='Athena Deck', version='0.4.0', state='ready', uptime_seconds=round(time.time()-self.server.started), mode='isolated', location='Athena' if os.environ.get('DECK_AUTH_RPC') else 'Mac', demo=self.server.demo.status()))
return self.respond(dict(name='Athena Deck', version='0.4.0', state='ready', uptime_seconds=round(time.time()-self.server.started), mode='isolated', location=os.environ.get('DECK_LOCATION', 'Athena' if os.environ.get('DECK_AUTH_RPC') else 'Mac'), demo=self.server.demo.status()))
if self.path == '/api/v1/hardware':
return self.respond(self.server.hardware.snapshot())
if self.path == '/api/v1/network':
@@ -277,7 +282,7 @@ class Handler(BaseHTTPRequestHandler):
if set(data) != {'password','api_token'}:
raise ValueError('Kennwort und API-Token werden benötigt.')
# Container installs always arrive provisioned. Never offer remote claim-on-first-use.
if os.environ.get('DECK_AUTH_RPC'):
if os.environ.get('DECK_AUTH_RPC') or os.environ.get('DECK_REQUIRE_SETUP') == '1':
return self.respond({'error':'Serverzugang wird bei der Installation eingerichtet.'},403)
self.server.credentials.setup(data['password'],data['api_token'])
return self.respond({'initialized':True})