diff --git a/api/network_manager.py b/api/network_manager.py index be9e2b4..8cc0904 100644 --- a/api/network_manager.py +++ b/api/network_manager.py @@ -182,6 +182,18 @@ class NetworkManager(BaseServiceManager): if not ok: logger.warning('update_split_horizon_zone: zone file write failed for %s', effective_domain) + # If the internal zone name happens to be a parent of the effective DDNS + # domain (e.g. primary_domain='pic.ngo', effective_domain='pic2.pic.ngo'), + # bootstrap service records like 'api', 'calendar' etc. would pollute the + # zone display and shadow the public domain. Remove them. + _stale = {'api', 'webui', 'calendar', 'files', 'mail', 'webmail', 'webdav'} + if effective_domain.endswith('.' + primary_domain): + existing = self._load_dns_records(primary_domain) + cleaned = [r for r in existing if r.get('name', '') not in _stale] + if len(cleaned) < len(existing): + self.update_dns_zone(primary_domain, cleaned) + logger.info('Removed stale service records from zone %s', primary_domain) + corefile = os.path.join(self.config_dir, 'dns', 'Corefile') peers_data = peers or [] ok_cf = _fm.generate_corefile( diff --git a/tests/test_network_manager.py b/tests/test_network_manager.py index 5a66be3..804cffb 100644 --- a/tests/test_network_manager.py +++ b/tests/test_network_manager.py @@ -458,6 +458,43 @@ class TestUpdateSplitHorizonZone(unittest.TestCase): calls = [str(c) for c in mock_run.call_args_list] self.assertTrue(any('SIGUSR1' in c for c in calls)) + @patch('subprocess.run') + def test_removes_stale_service_records_when_primary_is_parent(self, _mock): + """Stale LAN service names (api, calendar…) are removed from a parent zone.""" + # Bootstrap a pic.ngo zone with service records (wrong internal zone name) + stale_records = [ + {'name': 'pic2', 'type': 'A', 'value': '10.0.0.1'}, + {'name': 'api', 'type': 'A', 'value': '10.0.0.1'}, + {'name': 'calendar','type': 'A', 'value': '10.0.0.1'}, + {'name': 'files', 'type': 'A', 'value': '10.0.0.1'}, + ] + self.nm.update_dns_zone('pic.ngo', stale_records) + + # update_split_horizon_zone should strip api/calendar/files from pic.ngo + self.nm.update_split_horizon_zone( + 'pic2.pic.ngo', '172.20.0.2', primary_domain='pic.ngo' + ) + content = open(os.path.join(self.data_dir, 'dns', 'pic.ngo.zone')).read() + self.assertNotIn('calendar', content) + self.assertNotIn('\napi ', content) + self.assertNotIn('\nfiles ', content) + # Non-stale record (pic2 is the cell_name, not in _stale set) survives + # but api/calendar/files are gone + self.assertIn('172.20.0.2', open( + os.path.join(self.data_dir, 'dns', 'pic2.pic.ngo.zone')).read()) + + @patch('subprocess.run') + def test_no_stale_cleanup_when_primary_not_parent(self, _mock): + """When primary_domain is unrelated, no zone file is touched.""" + stale_records = [{'name': 'calendar', 'type': 'A', 'value': '10.0.0.1'}] + self.nm.update_dns_zone('cell', stale_records) + self.nm.update_split_horizon_zone( + 'pic2.pic.ngo', '172.20.0.2', primary_domain='cell' + ) + # cell zone is untouched + content = open(os.path.join(self.data_dir, 'dns', 'cell.zone')).read() + self.assertIn('calendar', content) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/webui/src/pages/Calendar.jsx b/webui/src/pages/Calendar.jsx index fd1a63d..05e7299 100644 --- a/webui/src/pages/Calendar.jsx +++ b/webui/src/pages/Calendar.jsx @@ -31,8 +31,10 @@ function InfoRow({ label, value }) { } function Calendar() { - const { domain = 'cell', service_ips = {}, service_configs = {} } = useConfig(); - const cellHost = `calendar.${domain}`; + const { domain = 'cell', effective_domain, domain_mode = 'lan', service_ips = {}, service_configs = {} } = useConfig(); + const svcDomain = (domain_mode !== 'lan' && effective_domain) ? effective_domain : domain; + const proto = domain_mode === 'lan' ? 'http' : 'https'; + const cellHost = `calendar.${svcDomain}`; const calendarIp = service_ips.vip_calendar || '172.20.0.21'; const dnsIp = service_ips.dns || '172.20.0.3'; const calendarPort = service_configs.calendar?.port ?? 5232; @@ -85,10 +87,10 @@ function Calendar() { Use these settings in your calendar / contacts app (iOS, Android, Thunderbird, etc.)
Android (DAVx⁵ app)
Thunderbird
macOS (Finder)
-Go → Connect to Server → http://{webdavHost}
+Go → Connect to Server → {proto}://{webdavHost}
Windows
-Map Network Drive → \\{webdavHost}\DavWWWRoot or use http://{webdavHost} in "Connect to a Web Site"
+Map Network Drive → \\{webdavHost}\DavWWWRoot or use {proto}://{webdavHost} in "Connect to a Web Site"
iOS (Files app)
-Files → ... → Connect to Server → http://{webdavHost}
+Files → ... → Connect to Server → {proto}://{webdavHost}
Android
-Use Solid Explorer or FX File Explorer → Add cloud → WebDAV → http://{webdavHost}
+Use Solid Explorer or FX File Explorer → Add cloud → WebDAV → {proto}://{webdavHost}