From 6bd5f02b03763c734e04c06304a4dc0cc1344553 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Mon, 8 Jun 2026 13:52:00 -0400 Subject: [PATCH] fix: surface DDNS registration failure during setup wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems on fresh install with pic_ngo mode: 1. Caddy crashed at startup because ddns.token was empty (registration hadn't completed yet), producing a bare `token` keyword in the Caddyfile that Caddy rejects with "wrong argument count". Fix: fall back to lan mode in _caddyfile_pic_ngo when the token is empty so Caddy always starts cleanly. The Caddyfile is regenerated once registration completes and the token is persisted. 2. DDNS registration failures were silently swallowed — the wizard showed "Setup complete!" with no indication that HTTPS wouldn't work. This made it look like everything was fine when the subdomain was never registered (e.g. name already taken from a previous install, or transient network error). Fix: capture the exception, classify it (name_taken vs transient), and return it as a `warnings` list in the setup response. The wizard done screen now shows amber warning cards with actionable text instead of auto-redirecting, giving the user a "Continue to login" button and a clear explanation of what went wrong. Co-Authored-By: Claude Sonnet 4.6 --- api/setup_manager.py | 21 +++++++++++++++++++-- webui/src/pages/Setup.jsx | 34 ++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/api/setup_manager.py b/api/setup_manager.py index a9a01c0..6e420b5 100644 --- a/api/setup_manager.py +++ b/api/setup_manager.py @@ -247,6 +247,7 @@ class SetupManager: self.config_manager.set_ddns_config(ddns_cfg) # ── trigger DDNS registration for pic_ngo ───────────────────────── + warnings: List[str] = [] if domain_mode == 'pic_ngo': try: from ddns_manager import DDNSManager @@ -254,13 +255,29 @@ class SetupManager: ddns_mgr.register(cell_name, '') logger.info(f'DDNS registered: {cell_name}.pic.ngo') except Exception as exc: - logger.warning(f'DDNS registration failed (will retry at next heartbeat): {exc}') + msg = str(exc) + logger.warning(f'DDNS registration failed: {msg}') + if '409' in msg or 'taken' in msg.lower(): + warnings.append( + f'The name "{cell_name}" is already registered on pic.ngo. ' + 'HTTPS will not be active until you re-register: go to ' + 'Settings → DDNS and click Re-register, or choose a different name.' + ) + else: + warnings.append( + 'DDNS registration could not be completed right now ' + f'({msg}). The cell will retry automatically. ' + 'HTTPS will activate once registration succeeds.' + ) # ── mark setup complete (must be last) ───────────────────────── self.config_manager.set_identity_field('setup_complete', True) logger.info(f"Setup completed. cell_name={cell_name!r}, domain_mode={domain_mode!r}") - return {'success': True, 'redirect': '/login'} + result: Dict[str, Any] = {'success': True, 'redirect': '/login'} + if warnings: + result['warnings'] = warnings + return result finally: try: diff --git a/webui/src/pages/Setup.jsx b/webui/src/pages/Setup.jsx index 2968b2e..f7734db 100644 --- a/webui/src/pages/Setup.jsx +++ b/webui/src/pages/Setup.jsx @@ -540,8 +540,9 @@ function Step4Review({ domainType, domainName, timezone, onBack, onSubmit, submi export default function Setup() { const navigate = useNavigate(); - const [step, setStep] = useState(1); - const [done, setDone] = useState(false); + const [step, setStep] = useState(1); + const [done, setDone] = useState(false); + const [setupWarnings, setSetupWarnings] = useState([]); const [password, setPassword] = useState(''); const [passwordConfirm, setPasswordConfirm] = useState(''); @@ -600,9 +601,13 @@ export default function Setup() { }; try { - await setupAPI.complete(payload); + const res = await setupAPI.complete(payload); + const warnings = res?.data?.warnings || []; + setSetupWarnings(warnings); setDone(true); - setTimeout(() => navigate('/login', { replace: true }), 2000); + if (warnings.length === 0) { + setTimeout(() => navigate('/login', { replace: true }), 2000); + } } catch (e) { setSubmitError( e?.response?.data?.errors?.join(' ') || @@ -617,10 +622,27 @@ export default function Setup() { if (done) { return (
-
+

Setup complete!

-

Redirecting to login...

+ {setupWarnings.length > 0 ? ( +
+ {setupWarnings.map((w, i) => ( +
+ +

{w}

+
+ ))} + +
+ ) : ( +

Redirecting to login...

+ )}
);