Fix fail2ban-master path resolution for Docker container

In the Docker image, the app source is copied to /app/app/ (not
backend/app/), so parents[2] resolved to '/' instead of /app.
This left the fail2ban package absent from sys.path, causing every
pickle.loads() call on socket responses to raise:

  ModuleNotFoundError: No module named 'fail2ban'

Replace the hardcoded parents[2] with a walk-up search that iterates
over all ancestors until it finds a fail2ban-master/ sibling directory.
Works correctly in both local dev and Docker without environment-specific
path magic.
This commit is contained in:
2026-03-01 20:48:59 +01:00
parent f06fea699f
commit 19bb94ee47

View File

@@ -39,10 +39,33 @@ from app.utils.fail2ban_client import Fail2BanConnectionError, Fail2BanProtocolE
# ---------------------------------------------------------------------------
# Ensure the bundled fail2ban package is importable from fail2ban-master/
#
# The directory layout differs between local dev and the Docker image:
# Local: <repo-root>/backend/app/main.py → fail2ban-master at parents[2]
# Docker: /app/app/main.py → fail2ban-master at parents[1]
# Walk up from this file until we find a "fail2ban-master" sibling directory
# so the path resolution is environment-agnostic.
# ---------------------------------------------------------------------------
_FAIL2BAN_MASTER: Path = Path(__file__).resolve().parents[2] / "fail2ban-master"
if str(_FAIL2BAN_MASTER) not in sys.path:
sys.path.insert(0, str(_FAIL2BAN_MASTER))
def _find_fail2ban_master() -> Path | None:
"""Return the first ``fail2ban-master`` directory found while walking up.
Returns:
Absolute :class:`~pathlib.Path` to the ``fail2ban-master`` directory,
or ``None`` if no such directory exists among the ancestors.
"""
here = Path(__file__).resolve()
for ancestor in here.parents:
candidate = ancestor / "fail2ban-master"
if candidate.is_dir():
return candidate
return None
_fail2ban_master: Path | None = _find_fail2ban_master()
if _fail2ban_master is not None and str(_fail2ban_master) not in sys.path:
sys.path.insert(0, str(_fail2ban_master))
log: structlog.stdlib.BoundLogger = structlog.get_logger()