feat: Settings changes now apply to real service config files and restart containers

Each service manager now has apply_config() that writes to the actual config:
- network: dhcp_range → dnsmasq.conf (reload cell-dhcp), ntp_servers → chrony.conf
  (restart cell-ntp), domain → dnsmasq.conf domain= line
- email: domain → mailserver.env OVERRIDE_HOSTNAME + POSTMASTER_ADDRESS,
  restart cell-mail
- wireguard: port/address/private_key → wg0.conf ListenPort/Address/PrivateKey,
  restart cell-wireguard
- calendar: port → radicale config hosts=, restart cell-radicale

PUT /api/config now calls apply_config() after persisting JSON, and returns
{restarted: [...], warnings: [...]} so Settings UI can show which containers
were restarted. _restart_container() helper added to BaseServiceManager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 04:27:22 -04:00
parent ae73246878
commit 87ff50c378
7 changed files with 237 additions and 12 deletions
+45
View File
@@ -207,6 +207,51 @@ class EmailManager(BaseServiceManager):
except Exception as e:
logger.error(f"Error saving domain config: {e}")
def apply_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""Write config to mailserver.env and restart cell-mail."""
restarted = []
warnings = []
env_file = os.path.join(self.config_dir, 'mail', 'mailserver.env')
try:
# Read existing env file
env_lines = []
if os.path.exists(env_file):
with open(env_file) as f:
env_lines = f.readlines()
def _set_env(lines, key, value):
found = False
result = []
for l in lines:
if l.startswith(f'{key}='):
result.append(f'{key}={value}\n')
found = True
else:
result.append(l)
if not found:
result.append(f'{key}={value}\n')
return result
changed = False
if 'domain' in config and config['domain']:
domain = config['domain']
env_lines = _set_env(env_lines, 'OVERRIDE_HOSTNAME', f'mail.{domain}')
env_lines = _set_env(env_lines, 'POSTMASTER_ADDRESS', f'admin@{domain}')
# Also persist to domain_config_file
self._save_domain_config({'domain': domain})
changed = True
if changed:
with open(env_file, 'w') as f:
f.writelines(env_lines)
self._restart_container('cell-mail')
restarted.append('cell-mail')
except Exception as e:
warnings.append(f"mailserver.env update failed: {e}")
logger.error(f"apply_config error: {e}")
return {'restarted': restarted, 'warnings': warnings}
def get_email_status(self) -> Dict[str, Any]:
"""Get detailed email service status including postfix/dovecot state."""
try: