feat: Phase 6 — require_active_service decorator + wizard install wiring

Email/calendar/files routes now return 404 when the service is not
installed, using a require_active_service decorator that checks
ServiceRegistry. Status endpoints are exempt so health checks always work.

SetupManager.complete_setup() now accepts a service_store_manager and
installs any wizard-selected services in a background daemon thread after
setup completes. Failures are logged but do not fail the wizard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 16:58:57 -04:00
parent a69ca1e402
commit 44d7e96f29
12 changed files with 556 additions and 5 deletions
+18 -1
View File
@@ -10,6 +10,7 @@ import fcntl
import logging
import os
import re
import threading
from typing import Any, Dict, List
logger = logging.getLogger(__name__)
@@ -94,9 +95,10 @@ def _build_ddns_config(domain_mode: str, cloudflare_api_token: str = '',
class SetupManager:
"""Manages the first-run setup wizard state and completion."""
def __init__(self, config_manager, auth_manager):
def __init__(self, config_manager, auth_manager, service_store_manager=None):
self.config_manager = config_manager
self.auth_manager = auth_manager
self.service_store_manager = service_store_manager
# ── state helpers ─────────────────────────────────────────────────────
@@ -263,6 +265,21 @@ class SetupManager:
# ── mark setup complete (must be last) ─────────────────────────
self.config_manager.set_identity_field('setup_complete', True)
# Trigger service installs in background — non-blocking, failures are logged
if services_enabled and self.service_store_manager is not None:
def _install_services():
for svc_id in services_enabled:
try:
result = self.service_store_manager.install(svc_id)
if result.get('ok'):
logger.info('Wizard: installed service %r', svc_id)
else:
logger.warning('Wizard: install %r failed: %s',
svc_id, result.get('error') or result.get('errors'))
except Exception as exc:
logger.warning('Wizard: install %r raised: %s', svc_id, exc)
threading.Thread(target=_install_services, daemon=True).start()
logger.info(f"Setup completed. cell_name={cell_name!r}, domain_mode={domain_mode!r}")
return {'success': True, 'redirect': '/login'}