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:
+14
-2
@@ -356,9 +356,10 @@ def get_cell_status():
|
||||
current_time = time.time()
|
||||
uptime_seconds = int(current_time - API_START_TIME)
|
||||
|
||||
identity = config_manager.configs.get('_identity', {})
|
||||
return jsonify({
|
||||
"cell_name": "personal-internet-cell",
|
||||
"domain": "cell.local",
|
||||
"cell_name": identity.get('cell_name', os.environ.get('CELL_NAME', 'mycell')),
|
||||
"domain": identity.get('domain', os.environ.get('CELL_DOMAIN', 'cell')),
|
||||
"uptime": uptime_seconds,
|
||||
"peers_count": len(peers),
|
||||
"services": services_status,
|
||||
@@ -397,6 +398,8 @@ def update_config():
|
||||
# Handle identity fields (cell_name, domain, ip_range, wireguard_port)
|
||||
identity_keys = {'cell_name', 'domain', 'ip_range', 'wireguard_port'}
|
||||
identity_updates = {k: v for k, v in data.items() if k in identity_keys}
|
||||
# Capture old identity BEFORE saving, for apply_cell_name comparison
|
||||
old_identity = dict(config_manager.configs.get('_identity', {}))
|
||||
if identity_updates:
|
||||
stored = config_manager.configs.get('_identity', {})
|
||||
stored.update(identity_updates)
|
||||
@@ -439,6 +442,15 @@ def update_config():
|
||||
all_restarted.extend(net_result.get('restarted', []))
|
||||
all_warnings.extend(net_result.get('warnings', []))
|
||||
|
||||
# Apply cell name change to DNS hostname record
|
||||
if identity_updates.get('cell_name'):
|
||||
old_name = old_identity.get('cell_name', os.environ.get('CELL_NAME', 'mycell'))
|
||||
new_name = identity_updates['cell_name']
|
||||
if old_name != new_name:
|
||||
cn_result = network_manager.apply_cell_name(old_name, new_name)
|
||||
all_restarted.extend(cn_result.get('restarted', []))
|
||||
all_warnings.extend(cn_result.get('warnings', []))
|
||||
|
||||
logger.info(f"Updated config, restarted: {all_restarted}")
|
||||
return jsonify({
|
||||
"message": "Configuration updated and applied",
|
||||
|
||||
@@ -422,6 +422,36 @@ class NetworkManager(BaseServiceManager):
|
||||
warnings.append(f"CoreDNS reload failed: {e}")
|
||||
|
||||
return {'restarted': restarted, 'warnings': warnings}
|
||||
|
||||
def apply_cell_name(self, old_name: str, new_name: str) -> Dict[str, Any]:
|
||||
"""Update the cell hostname record in the primary DNS zone file."""
|
||||
restarted = []
|
||||
warnings = []
|
||||
if not old_name or not new_name or old_name == new_name:
|
||||
return {'restarted': restarted, 'warnings': warnings}
|
||||
try:
|
||||
dns_data = os.path.join(self.data_dir, 'dns')
|
||||
if os.path.isdir(dns_data):
|
||||
for fname in os.listdir(dns_data):
|
||||
if fname.endswith('.zone') and 'local' not in fname:
|
||||
zone_file = os.path.join(dns_data, fname)
|
||||
with open(zone_file) as f:
|
||||
content = f.read()
|
||||
# Replace hostname record: old_name IN A ...
|
||||
import re
|
||||
content = re.sub(
|
||||
rf'^{re.escape(old_name)}(\s+IN\s+A\s+)',
|
||||
f'{new_name}\\1',
|
||||
content, flags=re.MULTILINE
|
||||
)
|
||||
with open(zone_file, 'w') as f:
|
||||
f.write(content)
|
||||
break
|
||||
self._reload_dns_service()
|
||||
restarted.append('cell-dns (reloaded)')
|
||||
except Exception as e:
|
||||
warnings.append(f"cell_name DNS update failed: {e}")
|
||||
return {'restarted': restarted, 'warnings': warnings}
|
||||
|
||||
def test_dns_resolution(self, domain: str) -> Dict:
|
||||
"""Test DNS resolution for a domain using Python socket."""
|
||||
|
||||
Reference in New Issue
Block a user