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.)

- - - - + + + + @@ -118,7 +120,7 @@ function Calendar() {

Android (DAVx⁵ app)

  1. Install DAVx⁵ from Play Store / F-Droid
  2. -
  3. Login with URL: http://{cellHost}/
  4. +
  5. Login with URL: {proto}://{cellHost}/
  6. Select calendars & address books to sync
@@ -126,7 +128,7 @@ function Calendar() {

Thunderbird

  1. Calendar → New Calendar → On the Network
  2. -
  3. Format: CalDAV, Location: http://{cellHost}/
  4. +
  5. Format: CalDAV, Location: {proto}://{cellHost}/
diff --git a/webui/src/pages/Dashboard.jsx b/webui/src/pages/Dashboard.jsx index f626106..9ef95cb 100644 --- a/webui/src/pages/Dashboard.jsx +++ b/webui/src/pages/Dashboard.jsx @@ -21,12 +21,14 @@ import { useConfig } from '../contexts/ConfigContext'; function Dashboard({ isOnline }) { const navigate = useNavigate(); - const { domain = 'cell', cell_name = 'mycell' } = useConfig(); + const { domain = 'cell', cell_name = 'mycell', effective_domain, domain_mode = 'lan' } = useConfig(); + const svcDomain = (domain_mode !== 'lan' && effective_domain) ? effective_domain : domain; + const proto = domain_mode === 'lan' ? 'http' : 'https'; const SERVICES = [ - { name: 'Cell Home', url: `http://${cell_name}.${domain}`, desc: 'Main UI — no login needed' }, - { name: 'Calendar', url: `http://calendar.${domain}`, desc: 'Use your configured account credentials' }, - { name: 'Files', url: `http://files.${domain}`, desc: 'Use your configured account credentials' }, - { name: 'Webmail', url: `http://mail.${domain}`, desc: 'Use your configured account credentials' }, + { name: 'Cell Home', url: domain_mode === 'lan' ? `http://${cell_name}.${domain}` : `https://${svcDomain}`, desc: 'Main UI — no login needed' }, + { name: 'Calendar', url: `${proto}://calendar.${svcDomain}`, desc: 'Use your configured account credentials' }, + { name: 'Files', url: `${proto}://files.${svcDomain}`, desc: 'Use your configured account credentials' }, + { name: 'Webmail', url: `${proto}://mail.${svcDomain}`, desc: 'Use your configured account credentials' }, ]; const [cellStatus, setCellStatus] = useState(null); const [servicesStatus, setServicesStatus] = useState(null); diff --git a/webui/src/pages/Email.jsx b/webui/src/pages/Email.jsx index 97b978b..4fad071 100644 --- a/webui/src/pages/Email.jsx +++ b/webui/src/pages/Email.jsx @@ -31,8 +31,10 @@ function InfoRow({ label, value }) { } function Email() { - const { domain = 'cell', service_ips = {}, service_configs = {} } = useConfig(); - const cellHost = `mail.${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 = `mail.${svcDomain}`; const emailCfg = service_configs.email || {}; const mailIp = service_ips.vip_mail || '172.20.0.23'; const dnsIp = service_ips.dns || '172.20.0.3'; @@ -113,8 +115,8 @@ function Email() {

Webmail

- - + +
diff --git a/webui/src/pages/Files.jsx b/webui/src/pages/Files.jsx index aaeea44..bb1999f 100644 --- a/webui/src/pages/Files.jsx +++ b/webui/src/pages/Files.jsx @@ -31,9 +31,11 @@ function InfoRow({ label, value }) { } function Files() { - const { domain = 'cell', service_ips = {}, service_configs = {} } = useConfig(); - const filesHost = `files.${domain}`; - const webdavHost = `webdav.${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 filesHost = `files.${svcDomain}`; + const webdavHost = `webdav.${svcDomain}`; const filesIp = service_ips.vip_files || '172.20.0.22'; const webdavIp = service_ips.vip_webdav || '172.20.0.24'; const filesCfg = service_configs.files || {}; @@ -85,7 +87,7 @@ function Files() {

Web file manager

- +
@@ -101,7 +103,7 @@ function Files() {

WebDAV (mount as drive)

- + @@ -120,19 +122,19 @@ function Files() {

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}