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:
@@ -195,16 +195,18 @@ function EmailForm({ data, onChange }) {
|
||||
<Field label="Mail Domain">
|
||||
<TextInput value={data.domain} onChange={(v) => onChange({ ...data, domain: v })} placeholder="mail.example.com" />
|
||||
</Field>
|
||||
<Field label="SMTP Port" hint="Fixed by docker-compose.yml">
|
||||
<TextInput value={data.smtp_port ?? 587} readOnly />
|
||||
<Field label="SMTP Port" hint="MTA-to-MTA (default 25)">
|
||||
<NumberInput value={data.smtp_port ?? 25} onChange={(v) => onChange({ ...data, smtp_port: v })} min={1} max={65535} />
|
||||
</Field>
|
||||
<Field label="IMAP Port" hint="Fixed by docker-compose.yml">
|
||||
<TextInput value={data.imap_port ?? 993} readOnly />
|
||||
<Field label="Submission Port" hint="Client mail send (default 587)">
|
||||
<NumberInput value={data.submission_port ?? 587} onChange={(v) => onChange({ ...data, submission_port: v })} min={1} max={65535} />
|
||||
</Field>
|
||||
<Field label="IMAP Port" hint="Client mail fetch (default 993)">
|
||||
<NumberInput value={data.imap_port ?? 993} onChange={(v) => onChange({ ...data, imap_port: v })} min={1} max={65535} />
|
||||
</Field>
|
||||
<Field label="Webmail Port" hint="Rainloop webmail UI (default 8888)">
|
||||
<NumberInput value={data.webmail_port ?? 8888} onChange={(v) => onChange({ ...data, webmail_port: v })} min={1} max={65535} />
|
||||
</Field>
|
||||
<p className="text-xs text-gray-400">
|
||||
Ports 587 (SMTP) and 993 (IMAP) are set by docker-compose port bindings and cannot be changed at runtime.
|
||||
Only <strong>domain</strong> is applied on Save.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -225,8 +227,11 @@ function CalendarForm({ data, onChange }) {
|
||||
function FilesForm({ data, onChange }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<Field label="Internal Port" hint="Fixed by docker-compose.yml">
|
||||
<TextInput value={data.port ?? 80} readOnly />
|
||||
<Field label="WebDAV Port" hint="Host port for WebDAV (default 8080)">
|
||||
<NumberInput value={data.port ?? 8080} onChange={(v) => onChange({ ...data, port: v })} min={1} max={65535} />
|
||||
</Field>
|
||||
<Field label="File Manager Port" hint="Filegator host port (default 8082)">
|
||||
<NumberInput value={data.manager_port ?? 8082} onChange={(v) => onChange({ ...data, manager_port: v })} min={1} max={65535} />
|
||||
</Field>
|
||||
<Field label="Data Directory">
|
||||
<TextInput value={data.data_dir} onChange={(v) => onChange({ ...data, data_dir: v })} placeholder="/app/data/webdav" />
|
||||
@@ -234,9 +239,6 @@ function FilesForm({ data, onChange }) {
|
||||
<Field label="Default Quota (MB)">
|
||||
<NumberInput value={data.quota} onChange={(v) => onChange({ ...data, quota: v })} min={0} />
|
||||
</Field>
|
||||
<p className="text-xs text-gray-400">
|
||||
Clients always connect on port 80 via Caddy reverse proxy, regardless of internal port.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -271,9 +273,9 @@ function VaultForm({ data, onChange }) {
|
||||
const SERVICE_DEFS = [
|
||||
{ key: 'network', label: 'Network (DNS/DHCP/NTP)', icon: Network, Form: NetworkForm, defaults: { dns_port: 53, dhcp_range: '', ntp_servers: [] } },
|
||||
{ key: 'wireguard', label: 'WireGuard VPN', icon: Shield, Form: WireguardForm, defaults: { port: 51820, address: '', private_key: '' } },
|
||||
{ key: 'email', label: 'Email (SMTP/IMAP)', icon: Mail, Form: EmailForm, defaults: { domain: '', smtp_port: 587, imap_port: 993 } },
|
||||
{ key: 'email', label: 'Email (SMTP/IMAP)', icon: Mail, Form: EmailForm, defaults: { domain: '', smtp_port: 25, submission_port: 587, imap_port: 993, webmail_port: 8888 } },
|
||||
{ key: 'calendar', label: 'Calendar (CalDAV)', icon: Calendar, Form: CalendarForm, defaults: { port: 5232, data_dir: '' } },
|
||||
{ key: 'files', label: 'Files (WebDAV)', icon: HardDrive, Form: FilesForm, defaults: { port: 80, data_dir: '', quota: 1024 } },
|
||||
{ key: 'files', label: 'Files (WebDAV)', icon: HardDrive, Form: FilesForm, defaults: { port: 8080, manager_port: 8082, data_dir: '', quota: 1024 } },
|
||||
{ key: 'routing', label: 'Routing & Firewall', icon: GitBranch, Form: RoutingForm, defaults: { nat_enabled: true, firewall_enabled: true } },
|
||||
{ key: 'vault', label: 'Vault & Trust', icon: Lock, Form: VaultForm, defaults: { ca_configured: false, fernet_configured: false } },
|
||||
];
|
||||
@@ -499,8 +501,8 @@ function Settings() {
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mt-2">
|
||||
Note: IP Range and WireGuard Port are also set via environment variables in docker-compose.yml.
|
||||
Changes here are stored in config and take effect on next container start.
|
||||
IP Range and port changes update the .env file and mark affected containers for restart.
|
||||
Use the banner above to apply when ready.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user