A stale or empty-token Caddyfile on disk caused Caddy to reject the /load request, so the Renew button appeared to do nothing. Now renew_cert() calls regenerate_with_installed([]) first, which writes a fresh Caddyfile from current identity/config before reloading Caddy. This ensures a broken on-disk file never blocks ACME renewal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-5
@@ -649,11 +649,12 @@ class CaddyManager(BaseServiceManager):
|
||||
# ── Active cert management ────────────────────────────────────────────
|
||||
|
||||
def renew_cert(self) -> Dict[str, Any]:
|
||||
"""Trigger ACME cert renewal by reloading Caddy.
|
||||
"""Regenerate the Caddyfile, reload Caddy, and trigger ACME cert renewal.
|
||||
|
||||
Returns immediately with status='pending' so the caller can poll
|
||||
GET /api/caddy/cert-status to track progress. Not applicable to
|
||||
LAN mode — callers should use upload_custom_cert() instead.
|
||||
Regenerates first so a stale or broken on-disk Caddyfile never blocks
|
||||
the reload. Returns immediately with status='pending'; the caller
|
||||
polls GET /api/caddy/cert-status to track progress. Not applicable
|
||||
to LAN mode — callers should use upload_custom_cert() instead.
|
||||
"""
|
||||
ident = (self.config_manager.get_identity() if self.config_manager else {}) or {}
|
||||
domain_mode = ident.get('domain_mode', 'lan')
|
||||
@@ -665,7 +666,17 @@ class CaddyManager(BaseServiceManager):
|
||||
'Upload a custom certificate instead.',
|
||||
}
|
||||
|
||||
if not self.reload_caddy():
|
||||
# Regenerate → write → reload in one shot so the Caddyfile is always fresh.
|
||||
if self.config_manager:
|
||||
try:
|
||||
ok = self.regenerate_with_installed([])
|
||||
except Exception as exc:
|
||||
logger.error('renew_cert: regenerate_with_installed failed: %s', exc)
|
||||
ok = False
|
||||
else:
|
||||
ok = self.reload_caddy()
|
||||
|
||||
if not ok:
|
||||
return {'ok': False, 'error': 'Caddy reload failed — check Caddy logs.'}
|
||||
|
||||
# Invalidate the cached status so the next poll triggers a fresh SSL check.
|
||||
|
||||
@@ -493,17 +493,17 @@ class TestRenewCert(unittest.TestCase):
|
||||
self.assertFalse(result['ok'])
|
||||
self.assertIn('LAN', result['error'])
|
||||
|
||||
def test_acme_mode_calls_reload(self):
|
||||
def test_acme_mode_calls_regenerate(self):
|
||||
mgr = _mgr(identity={'domain_mode': 'pic_ngo'})
|
||||
with patch.object(mgr, 'reload_caddy', return_value=True) as mock_reload:
|
||||
with patch.object(mgr, 'regenerate_with_installed', return_value=True) as mock_regen:
|
||||
result = mgr.renew_cert()
|
||||
mock_reload.assert_called_once()
|
||||
mock_regen.assert_called_once_with([])
|
||||
self.assertTrue(result['ok'])
|
||||
self.assertEqual(result['status'], 'pending')
|
||||
|
||||
def test_reload_failure_propagated(self):
|
||||
mgr = _mgr(identity={'domain_mode': 'cloudflare'})
|
||||
with patch.object(mgr, 'reload_caddy', return_value=False):
|
||||
with patch.object(mgr, 'regenerate_with_installed', return_value=False):
|
||||
result = mgr.renew_cert()
|
||||
self.assertFalse(result['ok'])
|
||||
self.assertIn('reload failed', result['error'])
|
||||
@@ -512,7 +512,7 @@ class TestRenewCert(unittest.TestCase):
|
||||
import time
|
||||
mgr = _mgr(identity={'domain_mode': 'pic_ngo'})
|
||||
mgr._cert_refreshed_at = time.monotonic()
|
||||
with patch.object(mgr, 'reload_caddy', return_value=True):
|
||||
with patch.object(mgr, 'regenerate_with_installed', return_value=True):
|
||||
mgr.renew_cert()
|
||||
self.assertIsNone(mgr._cert_refreshed_at)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user