Adds live cert status, one-click ACME renewal, and custom cert upload directly to the Vault page so users never need to touch Caddy config. Backend: - CaddyManager.get_cert_status() now returns domain, domain_mode, and cert_type so the UI can render the right controls without a separate identity fetch - CaddyManager.renew_cert() reloads Caddy and invalidates the status cache; the frontend polls until the cert turns valid - CaddyManager.upload_custom_cert() validates PEM, writes cert+key to the shared config/caddy/certs/ volume, updates identity (cert_type=custom), and regenerates the Caddyfile so Caddy references the new paths - LAN-mode Caddyfile switches from /etc/caddy/internal/ to the shared certs dir automatically when cert_type=custom is set - ddns_api default no longer includes /api/v1 — the plugin appends it; legacy /api/v1 suffix is stripped at write time to keep the Caddyfile clean - POST /api/caddy/cert-renew and POST /api/caddy/custom-cert routes added Frontend: - TLSPanel component at the top of Vault.jsx shows status badge (valid/expiring-soon/expired/pending/internal) with domain and expiry - Renew button visible only for ACME modes; spins during the API call then polls GET /api/caddy/cert-status every 10 s until valid - Upload Custom Cert opens a modal with PEM text areas; works for all modes - caddyAPI.renewCert() and uploadCustomCert() added to api.js Tests: 22 new tests across 5 classes covering enriched status, renew_cert guards, upload_custom_cert validation/writes/persistence, custom-cert Caddyfile path selection, and ddns_api suffix stripping. All 2093 existing tests continue to pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+37
-1
@@ -897,7 +897,7 @@ def connectivity_get_peer_exits():
|
||||
|
||||
@app.route('/api/caddy/cert-status', methods=['GET'])
|
||||
def caddy_cert_status():
|
||||
"""Return TLS certificate status (expiry, days remaining, status).
|
||||
"""Return TLS certificate status (expiry, days remaining, domain, mode).
|
||||
|
||||
Refreshes from Caddy if the cached value is older than 5 minutes.
|
||||
For LAN mode returns {'status': 'internal'}; for ACME modes returns
|
||||
@@ -910,6 +910,42 @@ def caddy_cert_status():
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/caddy/cert-renew', methods=['POST'])
|
||||
def caddy_cert_renew():
|
||||
"""Trigger ACME certificate renewal by reloading Caddy.
|
||||
|
||||
Returns immediately with status='pending'; poll GET /api/caddy/cert-status
|
||||
to track progress (Caddy typically acquires the cert within 30-60 s).
|
||||
"""
|
||||
try:
|
||||
result = caddy_manager.renew_cert()
|
||||
return jsonify(result), (200 if result.get('ok') else 400)
|
||||
except Exception as e:
|
||||
logger.error(f"caddy_cert_renew: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/caddy/custom-cert', methods=['POST'])
|
||||
def caddy_upload_custom_cert():
|
||||
"""Install a custom TLS certificate (PEM format).
|
||||
|
||||
Body: { "cert_pem": "<PEM>", "key_pem": "<PEM>" }
|
||||
Validates the cert/key pair, writes to the shared certs directory,
|
||||
and reloads Caddy with the updated Caddyfile.
|
||||
"""
|
||||
try:
|
||||
data = request.get_json(silent=True) or {}
|
||||
cert_pem = (data.get('cert_pem') or '').strip()
|
||||
key_pem = (data.get('key_pem') or '').strip()
|
||||
if not cert_pem or not key_pem:
|
||||
return jsonify({'ok': False, 'error': 'cert_pem and key_pem are required'}), 400
|
||||
result = caddy_manager.upload_custom_cert(cert_pem, key_pem)
|
||||
return jsonify(result), (200 if result.get('ok') else 422)
|
||||
except Exception as e:
|
||||
logger.error(f"caddy_upload_custom_cert: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/egress/status', methods=['GET'])
|
||||
def egress_status():
|
||||
"""Return egress status for all installed services that have an egress config."""
|
||||
|
||||
Reference in New Issue
Block a user