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
+38 -26
View File
@@ -500,35 +500,47 @@ class TestDNSZoneRecords:
f"got {rec['value']}"
)
def test_calendar_resolves_to_wg_server_ip(self):
records = self._records()
rec = next((r for r in records if r['name'] == 'calendar'), None)
assert rec and rec['value'] == self._WG_SERVER_IP, \
f"calendar.dev should resolve to WG server IP; got {rec}"
def test_service_records_absent_without_registry(self):
"""Without a registry, service subdomain records are not generated.
def test_files_resolves_to_wg_server_ip(self):
Phase 2: service DNS records only exist when a service is installed
and the registry reports it. The hardcoded fallback is gone.
"""
records = self._records()
rec = next((r for r in records if r['name'] == 'files'), None)
assert rec and rec['value'] == self._WG_SERVER_IP, \
f"files.dev should resolve to WG server IP; got {rec}"
names = {r['name'] for r in records}
assert 'calendar' not in names, \
'calendar DNS record must not appear without a registry'
assert 'files' not in names, \
'files DNS record must not appear without a registry'
assert 'mail' not in names, \
'mail DNS record must not appear without a registry'
assert 'webmail' not in names, \
'webmail DNS record must not appear without a registry'
assert 'webdav' not in names, \
'webdav DNS record must not appear without a registry'
def test_mail_resolves_to_wg_server_ip(self):
records = self._records()
rec = next((r for r in records if r['name'] == 'mail'), None)
assert rec and rec['value'] == self._WG_SERVER_IP, \
f"mail.dev should resolve to WG server IP; got {rec}"
def test_webmail_resolves_to_wg_server_ip(self):
records = self._records()
rec = next((r for r in records if r['name'] == 'webmail'), None)
assert rec and rec['value'] == self._WG_SERVER_IP, \
f"webmail.dev should resolve to WG server IP; got {rec}"
def test_webdav_resolves_to_wg_server_ip(self):
records = self._records()
rec = next((r for r in records if r['name'] == 'webdav'), None)
assert rec and rec['value'] == self._WG_SERVER_IP, \
f"webdav.dev should resolve to WG server IP; got {rec}"
def test_service_records_present_with_registry(self):
"""With a registry that provides calendar/mail/files, all resolve to WG IP."""
from unittest.mock import MagicMock
import network_manager as nm
registry = MagicMock()
registry.get_caddy_routes.return_value = [
{'service_id': 'calendar', 'subdomain': 'calendar',
'backend': 'cell-radicale:5232', 'extra_subdomains': [], 'extra_backends': {}},
{'service_id': 'email', 'subdomain': 'mail',
'backend': 'cell-rainloop:8888', 'extra_subdomains': ['webmail'], 'extra_backends': {}},
{'service_id': 'files', 'subdomain': 'files',
'backend': 'cell-filegator:8080', 'extra_subdomains': ['webdav'], 'extra_backends': {}},
]
mgr = nm.NetworkManager.__new__(nm.NetworkManager)
mgr._service_registry = registry
records = mgr._build_dns_records('pic0', '172.20.0.0/16')
names = {r['name'] for r in records}
for expected in ('calendar', 'mail', 'webmail', 'files', 'webdav'):
assert expected in names, f'{expected} should be in DNS records with registry'
for rec in records:
assert rec['value'] == self._WG_SERVER_IP, \
f"Record {rec['name']} should point to WG server IP"
def test_cell_name_resolves_to_wg_server_ip(self):
records = self._records(cell_name='mypic')