feat(service-ports): remove hardcoded ports from docker-compose, make all service ports configurable

All host port bindings in docker-compose.yml now use \${VAR:-default} substitution,
driven by the .env file generated by ip_utils.write_env_file(). Changing a port in
Settings triggers a per-container pending-restart banner so only the affected container
is restarted on Apply.

- ip_utils: add PORT_DEFAULTS, PORT_ENV_VAR_NAMES, PORT_TO_CONTAINERS; extend
  write_env_file() to accept optional ports dict and write all port env vars
- docker-compose: convert all hardcoded port bindings to \${VAR:-default} form
- app.py: add _collect_service_ports helper; detect port changes in update_config,
  write updated .env and call _set_pending_restart with specific container list;
  update _set_pending_restart to merge/accumulate pending state with containers list;
  update apply_pending_config to use --no-deps <service> for targeted restarts
- config_manager: add submission_port, webmail_port to email schema; add manager_port
  to files schema
- Settings.jsx: make all email/files ports editable, add submission_port, webmail_port,
  manager_port fields; update stale identity note
- tests: 8 new tests for PORT_DEFAULTS, PORT_ENV_VAR_NAMES, and port override in write_env_file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 11:51:10 -04:00
parent c3b2c8d8e5
commit 673fe04164
7 changed files with 283 additions and 53 deletions
+5 -2
View File
@@ -60,10 +60,12 @@ class ConfigManager:
},
'email': {
'required': ['domain', 'smtp_port', 'imap_port'],
'optional': ['users', 'ssl_cert', 'ssl_key'],
'optional': ['users', 'ssl_cert', 'ssl_key', 'submission_port', 'webmail_port'],
'types': {
'smtp_port': int,
'submission_port': int,
'imap_port': int,
'webmail_port': int,
'domain': str
}
},
@@ -77,9 +79,10 @@ class ConfigManager:
},
'files': {
'required': ['port', 'data_dir'],
'optional': ['users', 'quota'],
'optional': ['users', 'quota', 'manager_port'],
'types': {
'port': int,
'manager_port': int,
'data_dir': str,
'quota': int
}