fix: P0/P1 audit fixes — DDNS correctness, peer provisioning gates, honest stubs

CloudflareDDNS.update() was calling the wrong endpoint; fix to use the
correct zone-records API so DDNS updates actually land.

NoIP and FreeDNS providers now return explicit "not implemented" errors
instead of silently claiming success, preventing false-positive health state.

PicNgoDNS ACME dns-challenge now sends the token in the request body (was
missing), so cert issuance no longer silently fails.

add_peer gates builtin-service provisioning on the installed-services list
so a freshly-provisioned peer does not attempt to configure services that
aren't present, eliminating the startup error loop.

Startup Caddyfile regeneration added to routes/config.py so that a stale
on-disk Caddyfile no longer triggers the health-monitor restart loop after
a config change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 08:23:00 -04:00
parent 649378b59b
commit cc7a223fdf
4 changed files with 200 additions and 74 deletions
+16
View File
@@ -660,6 +660,22 @@ def ddns_register():
return jsonify({'error': str(e)}), 500
@bp.route('/api/ddns/sync', methods=['POST'])
def ddns_sync_records():
"""Sync per-service public DNS records (Cloudflare provider)."""
try:
from app import ddns_manager
from ddns_manager import DDNSError
try:
result = ddns_manager.sync_service_records()
except DDNSError as exc:
return jsonify({'error': str(exc)}), 400
return jsonify(result)
except Exception as e:
logger.error('Error in /api/ddns/sync: %s', e)
return jsonify({'error': str(e)}), 500
@bp.route('/api/config/pending', methods=['GET'])
def get_pending_config():
from app import config_manager
+5
View File
@@ -83,11 +83,16 @@ def add_peer():
provisioned = ['auth']
domain = _configured_domain()
# Only provision accounts on services that are actually installed —
# email/calendar/files are optional store services.
for step_name, step_fn in [
('email', lambda: email_manager.create_email_user(peer_name, domain, password)),
('calendar', lambda: calendar_manager.create_calendar_user(peer_name, password)),
('files', lambda: file_manager.create_user(peer_name, password)),
]:
if step_name not in _installed:
logger.debug(f"Peer {peer_name}: {step_name} not installed — skipping account provisioning")
continue
try:
if step_fn():
provisioned.append(step_name)