From 1607a2e86f173633914103b6a273357ad78307f1 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Sun, 7 Jun 2026 15:58:27 -0400 Subject: [PATCH] fix: peer access to /api/services/active and unconditional Caddy startup regen - Add _PEER_READABLE_PATHS allowlist in enforce_auth so peer-role sessions can read /api/services/active; fixes My Services showing 'not installed' for cell members when services are installed - Move Caddy regeneration before the early-return in reapply_on_startup so the Caddyfile is always rebuilt from current identity on startup, even when no store services are installed; fixes ERR_SSL_PROTOCOL_ERROR after a cell rename (Caddyfile retained old wildcard domain) Co-Authored-By: Claude Sonnet 4.6 --- api/app.py | 9 +++++++++ api/service_store_manager.py | 24 +++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/app.py b/api/app.py index 9aeb05f..c57945d 100644 --- a/api/app.py +++ b/api/app.py @@ -187,6 +187,13 @@ def enforce_setup(): return jsonify({'error': 'Setup required', 'redirect': '/setup'}), 428 +# Read-only endpoints accessible to peer-role sessions (not just admin). +# Add paths here when peers need to read shared cell state. +_PEER_READABLE_PATHS = frozenset({ + '/api/services/active', +}) + + @app.before_request def enforce_auth(): """Enforce session-based authentication and role-based access control. @@ -238,6 +245,8 @@ def enforce_auth(): if path.startswith('/api/peer/'): if role != 'peer': return jsonify({'error': 'Forbidden'}), 403 + elif path in _PEER_READABLE_PATHS: + pass # both admin and peer may read these endpoints else: if role != 'admin': return jsonify({'error': 'Forbidden'}), 403 diff --git a/api/service_store_manager.py b/api/service_store_manager.py index 55bf1df..48707c3 100644 --- a/api/service_store_manager.py +++ b/api/service_store_manager.py @@ -407,6 +407,19 @@ class ServiceStoreManager(BaseServiceManager): from firewall_manager import apply_service_rules installed = self.config_manager.get_installed_services() + + # Always regenerate the Caddyfile so a cell rename or fresh install + # produces the correct domain even when no store services are installed. + try: + caddy_routes = [ + r.get('caddy_route') + for r in (installed or {}).values() + if r.get('caddy_route') + ] + self.caddy_manager.regenerate_with_installed(caddy_routes) + except Exception as e: + logger.warning(f'reapply_on_startup: caddy regenerate failed: {e}') + if not installed: return @@ -419,17 +432,6 @@ class ServiceStoreManager(BaseServiceManager): except Exception as e: logger.warning(f'reapply_on_startup: apply_service_rules({svc_id}) failed: {e}') - # Regenerate Caddyfile - try: - caddy_routes = [ - r.get('caddy_route') - for r in installed.values() - if r.get('caddy_route') - ] - self.caddy_manager.regenerate_with_installed(caddy_routes) - except Exception as e: - logger.warning(f'reapply_on_startup: caddy regenerate failed: {e}') - # Bring up per-service compose stacks if self.service_composer is not None: try: