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:
+61
-5
@@ -9,7 +9,7 @@ docker-compose.yml uses ${VAR:-default} substitution to read from it.
|
||||
|
||||
import ipaddress
|
||||
import os
|
||||
from typing import Dict
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Fixed host-number offsets within the subnet (e.g. 172.20.0.0/16 → 172.20.0.<offset>)
|
||||
CONTAINER_OFFSETS: Dict[str, int] = {
|
||||
@@ -48,6 +48,57 @@ ENV_VAR_NAMES: Dict[str, str] = {
|
||||
'filegator': 'FILEGATOR_IP',
|
||||
}
|
||||
|
||||
# Default host-port bindings for each service
|
||||
PORT_DEFAULTS: Dict[str, int] = {
|
||||
'dns_port': 53,
|
||||
'dhcp_port': 67,
|
||||
'ntp_port': 123,
|
||||
'mail_smtp_port': 25,
|
||||
'mail_submission_port': 587,
|
||||
'mail_imap_port': 993,
|
||||
'radicale_port': 5232,
|
||||
'webdav_port': 8080,
|
||||
'wg_port': 51820,
|
||||
'api_port': 3000,
|
||||
'webui_port': 8081,
|
||||
'rainloop_port': 8888,
|
||||
'filegator_port': 8082,
|
||||
}
|
||||
|
||||
# Mapping from port key → docker-compose env var name
|
||||
PORT_ENV_VAR_NAMES: Dict[str, str] = {
|
||||
'dns_port': 'DNS_PORT',
|
||||
'dhcp_port': 'DHCP_PORT',
|
||||
'ntp_port': 'NTP_PORT',
|
||||
'mail_smtp_port': 'MAIL_SMTP_PORT',
|
||||
'mail_submission_port': 'MAIL_SUBMISSION_PORT',
|
||||
'mail_imap_port': 'MAIL_IMAP_PORT',
|
||||
'radicale_port': 'RADICALE_PORT',
|
||||
'webdav_port': 'WEBDAV_PORT',
|
||||
'wg_port': 'WG_PORT',
|
||||
'api_port': 'API_PORT',
|
||||
'webui_port': 'WEBUI_PORT',
|
||||
'rainloop_port': 'RAINLOOP_PORT',
|
||||
'filegator_port': 'FILEGATOR_PORT',
|
||||
}
|
||||
|
||||
# Mapping from port key → docker-compose service name(s) that must restart on port change
|
||||
PORT_TO_CONTAINERS: Dict[str, List[str]] = {
|
||||
'dns_port': ['dns'],
|
||||
'dhcp_port': ['dhcp'],
|
||||
'ntp_port': ['ntp'],
|
||||
'mail_smtp_port': ['mail'],
|
||||
'mail_submission_port': ['mail'],
|
||||
'mail_imap_port': ['mail'],
|
||||
'radicale_port': ['radicale'],
|
||||
'webdav_port': ['webdav'],
|
||||
'wg_port': ['wireguard'],
|
||||
'api_port': ['api'],
|
||||
'webui_port': ['webui'],
|
||||
'rainloop_port': ['rainloop'],
|
||||
'filegator_port': ['filegator'],
|
||||
}
|
||||
|
||||
|
||||
def get_service_ips(ip_range: str) -> Dict[str, str]:
|
||||
"""
|
||||
@@ -78,22 +129,27 @@ def get_virtual_ips(ip_range: str) -> Dict[str, str]:
|
||||
}
|
||||
|
||||
|
||||
def write_env_file(ip_range: str, path: str) -> bool:
|
||||
def write_env_file(ip_range: str, path: str, ports: Optional[Dict[str, int]] = None) -> bool:
|
||||
"""
|
||||
Write (or overwrite) the docker-compose .env file with IPs derived from ip_range.
|
||||
Write (or overwrite) the docker-compose .env file with IPs and ports.
|
||||
|
||||
docker-compose reads this file automatically at startup to substitute
|
||||
${VAR:-default} placeholders in docker-compose.yml. Call this at setup
|
||||
time and whenever ip_range changes so containers get the right IPs on
|
||||
the next `docker-compose up -d`.
|
||||
time and whenever ip_range or port values change.
|
||||
|
||||
ports: override specific port defaults (keys from PORT_DEFAULTS).
|
||||
Returns True on success, False if the path is not writable.
|
||||
"""
|
||||
try:
|
||||
ips = get_service_ips(ip_range)
|
||||
merged_ports = dict(PORT_DEFAULTS)
|
||||
if ports:
|
||||
merged_ports.update(ports)
|
||||
lines = [f'CELL_NETWORK={ip_range}\n']
|
||||
for svc, var in ENV_VAR_NAMES.items():
|
||||
lines.append(f'{var}={ips[svc]}\n')
|
||||
for key, var in PORT_ENV_VAR_NAMES.items():
|
||||
lines.append(f'{var}={merged_ports[key]}\n')
|
||||
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
||||
with open(path, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
Reference in New Issue
Block a user