fix: all service pages use live domain; cell_name/domain propagate to DNS; /api/status reads stored identity
Changes: - ConfigContext.jsx: React context that loads /api/config once; exposes domain, cell_name, refresh() — wraps entire app in App.jsx - Email/Calendar/Files pages: replace hardcoded 'mail.cell', 'calendar.cell', 'files.cell', 'webdav.cell' with domain from ConfigContext; hostname updates immediately after Settings save (refreshConfig() called on save) - /api/status: cell_name and domain now read from stored _identity in config_manager, not hardcoded 'personal-internet-cell' / 'cell.local' - network_manager.apply_cell_name(old, new): updates hostname A-record in primary zone file and reloads CoreDNS; called from PUT /api/config when cell_name changes - Old identity captured before save so apply_cell_name gets the correct old value - Settings EmailForm: smtp/imap ports are read-only with note (docker-compose.yml level) - Settings FilesForm: port is read-only with note (Caddy proxies on 80 externally) - Settings CalendarForm: port labeled "Internal port; clients use 80 via Caddy" Tests added: - test_apply_cell_name_renames_host_record - test_apply_cell_name_noop_when_same Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -335,6 +335,51 @@ class TestNetworkManagerApply(unittest.TestCase):
|
||||
"Corefile must not keep old 'cell' zone block")
|
||||
|
||||
|
||||
class TestNetworkManagerApplyCellName(unittest.TestCase):
|
||||
"""apply_cell_name updates the DNS zone hostname record."""
|
||||
|
||||
def setUp(self):
|
||||
self.test_dir = tempfile.mkdtemp()
|
||||
self.data_dir = os.path.join(self.test_dir, 'data')
|
||||
self.config_dir = os.path.join(self.test_dir, 'config')
|
||||
os.makedirs(os.path.join(self.data_dir, 'dns'), exist_ok=True)
|
||||
os.makedirs(os.path.join(self.config_dir, 'dhcp'), exist_ok=True)
|
||||
os.makedirs(os.path.join(self.config_dir, 'ntp'), exist_ok=True)
|
||||
with open(os.path.join(self.config_dir, 'dhcp', 'dnsmasq.conf'), 'w') as f:
|
||||
f.write('domain=cell\n')
|
||||
with open(os.path.join(self.config_dir, 'ntp', 'chrony.conf'), 'w') as f:
|
||||
f.write('server pool.ntp.org iburst\n')
|
||||
# Create a zone file with old cell name
|
||||
with open(os.path.join(self.data_dir, 'dns', 'cell.zone'), 'w') as f:
|
||||
f.write('$ORIGIN cell.\n$TTL 300\n'
|
||||
'@ IN SOA ns1.cell. admin.cell. 2024010101 3600 900 604800 300\n'
|
||||
'ns1 IN A 172.20.0.3\n'
|
||||
'mycell IN A 172.20.0.2\n'
|
||||
'@ IN A 172.20.0.2\n')
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / 'api'))
|
||||
from network_manager import NetworkManager
|
||||
self.nm = NetworkManager(self.data_dir, self.config_dir)
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.test_dir)
|
||||
|
||||
@patch('subprocess.run')
|
||||
def test_apply_cell_name_renames_host_record(self, mock_run):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
result = self.nm.apply_cell_name('mycell', 'newcell')
|
||||
zone = open(os.path.join(self.data_dir, 'dns', 'cell.zone')).read()
|
||||
self.assertIn('newcell IN A 172.20.0.2', zone)
|
||||
self.assertNotIn('mycell IN A', zone)
|
||||
self.assertIn('cell-dns', ' '.join(result['restarted']))
|
||||
|
||||
@patch('subprocess.run')
|
||||
def test_apply_cell_name_noop_when_same(self, mock_run):
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
result = self.nm.apply_cell_name('mycell', 'mycell')
|
||||
self.assertEqual(result['restarted'], [])
|
||||
mock_run.assert_not_called()
|
||||
|
||||
|
||||
class TestEmailManagerApply(unittest.TestCase):
|
||||
"""Test email_manager.apply_config writes mailserver.env correctly."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user