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 ────────────────────────────────────────────
|
# ── Active cert management ────────────────────────────────────────────
|
||||||
|
|
||||||
def renew_cert(self) -> Dict[str, Any]:
|
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
|
Regenerates first so a stale or broken on-disk Caddyfile never blocks
|
||||||
GET /api/caddy/cert-status to track progress. Not applicable to
|
the reload. Returns immediately with status='pending'; the caller
|
||||||
LAN mode — callers should use upload_custom_cert() instead.
|
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 {}
|
ident = (self.config_manager.get_identity() if self.config_manager else {}) or {}
|
||||||
domain_mode = ident.get('domain_mode', 'lan')
|
domain_mode = ident.get('domain_mode', 'lan')
|
||||||
@@ -665,7 +666,17 @@ class CaddyManager(BaseServiceManager):
|
|||||||
'Upload a custom certificate instead.',
|
'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.'}
|
return {'ok': False, 'error': 'Caddy reload failed — check Caddy logs.'}
|
||||||
|
|
||||||
# Invalidate the cached status so the next poll triggers a fresh SSL check.
|
# 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.assertFalse(result['ok'])
|
||||||
self.assertIn('LAN', result['error'])
|
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'})
|
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()
|
result = mgr.renew_cert()
|
||||||
mock_reload.assert_called_once()
|
mock_regen.assert_called_once_with([])
|
||||||
self.assertTrue(result['ok'])
|
self.assertTrue(result['ok'])
|
||||||
self.assertEqual(result['status'], 'pending')
|
self.assertEqual(result['status'], 'pending')
|
||||||
|
|
||||||
def test_reload_failure_propagated(self):
|
def test_reload_failure_propagated(self):
|
||||||
mgr = _mgr(identity={'domain_mode': 'cloudflare'})
|
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()
|
result = mgr.renew_cert()
|
||||||
self.assertFalse(result['ok'])
|
self.assertFalse(result['ok'])
|
||||||
self.assertIn('reload failed', result['error'])
|
self.assertIn('reload failed', result['error'])
|
||||||
@@ -512,7 +512,7 @@ class TestRenewCert(unittest.TestCase):
|
|||||||
import time
|
import time
|
||||||
mgr = _mgr(identity={'domain_mode': 'pic_ngo'})
|
mgr = _mgr(identity={'domain_mode': 'pic_ngo'})
|
||||||
mgr._cert_refreshed_at = time.monotonic()
|
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()
|
mgr.renew_cert()
|
||||||
self.assertIsNone(mgr._cert_refreshed_at)
|
self.assertIsNone(mgr._cert_refreshed_at)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user