feat: Phase 2 — remove builtins layer, ServiceRegistry is installed-only
Unit Tests / test (push) Successful in 11m31s

Builtins (email/calendar/files) are no longer baked into the API image.
ServiceRegistry now only knows about installed store services. When nothing
is installed, Caddy and DNS get no service routes — no hardcoded fallback.

Changes:
- service_registry.py: remove _BUILTINS_DIR, _builtin_ids, _builtin_manifest,
  _load_manifest; get() and list_all() now delegate entirely to installed services
- caddy_manager.py: remove _build_core_service_routes(); remove hardcoded
  fallback pairs from _http01_service_pairs(); empty registry → api block only
- network_manager.py: _get_service_subdomains() returns [] when no registry
- api/services/builtins/: deleted (email, calendar, files manifests)
- Tests updated throughout: removed builtin-dependent assertions, added
  installed-service fixtures, updated fallback expectations to api-only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 08:53:44 -04:00
parent 18b50d08c1
commit 0bfe95320b
12 changed files with 419 additions and 766 deletions
+3 -40
View File
@@ -163,38 +163,12 @@ class CaddyManager(BaseServiceManager):
lines.append("}")
return "\n".join(lines)
@staticmethod
def _build_core_service_routes(domain: str) -> str:
"""Return 4-space-indented named-matcher + handle blocks for core services."""
return (
f" @calendar host calendar.{domain}\n"
f" handle @calendar {{\n"
f" reverse_proxy cell-radicale:5232\n"
f" }}\n"
f" @mail host mail.{domain} webmail.{domain}\n"
f" handle @mail {{\n"
f" reverse_proxy cell-rainloop:8888\n"
f" }}\n"
f" @files host files.{domain}\n"
f" handle @files {{\n"
f" reverse_proxy cell-filegator:8080\n"
f" }}\n"
f" @webdav host webdav.{domain}\n"
f" handle @webdav {{\n"
f" reverse_proxy cell-webdav:80\n"
f" }}\n"
f" @api host api.{domain}\n"
f" handle @api {{\n"
f" reverse_proxy cell-api:3000\n"
f" }}"
)
def _build_registry_service_routes(self, domain: str) -> str:
"""Build named-matcher + handle blocks from the service registry.
Falls back to the hardcoded ``_build_core_service_routes`` when no
registry is wired or the registry returns nothing, so the method is
always safe to call even in tests that don't supply a registry.
When no registry is wired or the registry returns nothing, only the
api block is emitted (api is always infrastructure, not delegated to
the registry).
"""
routes: List[Dict] = []
if self._service_registry is not None:
@@ -203,9 +177,6 @@ class CaddyManager(BaseServiceManager):
except Exception as exc:
logger.warning('_build_registry_service_routes: registry error: %s', exc)
if not routes:
return self._build_core_service_routes(domain)
# Pre-seed with reserved names so no registry entry can squat them.
seen_matchers: set = {'api', 'webui'}
@@ -403,14 +374,6 @@ class CaddyManager(BaseServiceManager):
except Exception as exc:
logger.warning('_http01_service_pairs: registry error: %s', exc)
pairs = []
if not pairs:
pairs = [
('calendar', 'cell-radicale:5232'),
('mail', 'cell-rainloop:8888'),
('webmail', 'cell-rainloop:8888'),
('files', 'cell-filegator:8080'),
('webdav', 'cell-webdav:80'),
]
pairs.append(('api', 'cell-api:3000'))
return pairs