diff --git a/backend/app/routers/health.py b/backend/app/routers/health.py index 9e11879..2fdbe03 100644 --- a/backend/app/routers/health.py +++ b/backend/app/routers/health.py @@ -84,7 +84,7 @@ async def health_check( components.append( ComponentHealth(name="scheduler", healthy=False, message="Not initialised"), ) - except Exception: # pragma: no cover - defensive + except AttributeError: # pragma: no cover - defensive scheduler_state = "unknown" components.append( ComponentHealth(name="scheduler", healthy=False, message="Not accessible"), @@ -100,10 +100,11 @@ async def health_check( components.append( ComponentHealth(name="cache", healthy=False, message="Not initialised"), ) - except Exception: # pragma: no cover - defensive + except AttributeError: # pragma: no cover - defensive cache_state = "uninitialised" - - # --- fail2ban --- + components.append( + ComponentHealth(name="cache", healthy=False, message="Not accessible"), + ) fail2ban_online: bool = server_status.online if not fail2ban_online: components.append( diff --git a/backend/app/services/ban_service.py b/backend/app/services/ban_service.py index 796a292..629b96a 100644 --- a/backend/app/services/ban_service.py +++ b/backend/app/services/ban_service.py @@ -481,6 +481,7 @@ async def list_bans( log.warning("ban_service_geo_lookup_failed", ip=ip) except Exception as exc: log.error("ban_service_geo_lookup_unexpected_error", ip=ip, error=type(exc).__name__) + raise # Bubble programming errors to global handler items.append( DomainDashboardBanItem( @@ -648,7 +649,7 @@ async def bans_by_country( return ip, None except Exception as exc: log.error("ban_service_geo_lookup_unexpected_error", ip=ip, error=type(exc).__name__) - return ip, None + raise # Bubble programming errors to global handler results = await asyncio.gather(*(_safe_lookup(ip) for ip in unique_ips)) geo_map = {ip: geo for ip, geo in results if geo is not None} diff --git a/backend/app/services/jail_service.py b/backend/app/services/jail_service.py index bf0f5d3..432de08 100644 --- a/backend/app/services/jail_service.py +++ b/backend/app/services/jail_service.py @@ -159,12 +159,13 @@ async def _check_backend_cmd_supported( if state.backend_cmd_supported is not None: return state.backend_cmd_supported - # Probe: send the command and catch any exception. + # Probe: send the command and catch only fail2ban protocol errors. + # Programming errors (TypeError, AttributeError) bubble up to global handler. try: ok(await client.send(["get", jail_name, "backend"])) state.backend_cmd_supported = True log.debug("backend_cmd_supported_detected") - except Exception: + except ValueError: state.backend_cmd_supported = False log.debug("backend_cmd_unsupported_detected") diff --git a/backend/app/services/server_service.py b/backend/app/services/server_service.py index 0237e98..acd031f 100644 --- a/backend/app/services/server_service.py +++ b/backend/app/services/server_service.py @@ -14,7 +14,7 @@ from typing import cast import structlog -from app.exceptions import ServerOperationError +from app.exceptions import Fail2BanConnectionError, Fail2BanProtocolError, ServerOperationError from app.models.server_domain import DomainServerSettings, DomainServerSettingsResult from app.models.server import ServerSettingsUpdate from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT @@ -79,7 +79,7 @@ async def _safe_get( try: response = await client.send(command) return ok(cast("Fail2BanResponse", response)) - except Exception: + except (Fail2BanConnectionError, Fail2BanProtocolError, ValueError): return default diff --git a/backend/app/utils/config_writer.py b/backend/app/utils/config_writer.py index 112f4e6..1e72a30 100644 --- a/backend/app/utils/config_writer.py +++ b/backend/app/utils/config_writer.py @@ -150,7 +150,7 @@ def _write_parser_atomic( with os.fdopen(fd, "w", encoding="utf-8") as f: f.write(content) os.replace(tmp_path_str, str(path)) - except Exception: + except OSError: with contextlib.suppress(OSError): os.unlink(tmp_path_str) raise