Add explicit HTTP status code documentation to every endpoint
across 15 router files. Each endpoint now declares all possible
response codes (200/201/204/400/401/404/409/429/502/503) with
descriptions so frontend can distinguish error types.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- All backend routers moved to /api/v1/ prefix
- Frontend BASE_URL updated to /api/v1
- Setup redirect middleware updated to redirect to /api/v1/setup
- Health router path fixed: prefix=/api/v1/health, @router.get('')
- conftest.py: set server_status=online for test fixture
- Created Docs/API_VERSIONING.md with deprecation policy
- Updated Docs/Backend-Development.md with versioning section
- Updated Instructions.md curl examples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit enforces the repository boundary by eliminating direct database connection
dependencies (DbDep) from all routers. Routers now depend on service context dependencies
that combine the database connection with the related repositories.
Changes:
- Add 5 service context dependencies in dependencies.py:
* SessionServiceContext: db + session_repo
* BlocklistServiceContext: db + blocklist_repo + import_log_repo + settings_repo
* SettingsServiceContext: db + settings_repo
* BanServiceContext: db + fail2ban_db_repo
* HistoryServiceContext: db + fail2ban_db_repo + history_archive_repo
- Refactor all 9 routers (auth, bans, blocklist, config_misc, dashboard, geo,
history, jails, setup) to use service contexts instead of DbDep.
- Update Backend-Development.md with clear examples of the new pattern and
documentation of available service contexts.
Rationale:
- Enforces the repository boundary through the dependency system
- Makes database operations explicit and auditable
- Improves testability by allowing service contexts to be mocked
- Prevents accidental direct database access from routers
The deprecated DbDep remains available for backward compatibility with
services that have not yet been refactored, but routers can no longer import it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TASK-004: Replace module-level mutable runtime flags in service layer with
injected state holder, eliminating hidden global state and improving testability
and synchronization boundaries.
Changes:
- Create JailServiceState dataclass in app/utils/runtime_state.py to hold
backend capability cache and synchronization lock
- Add JailServiceState as a field in RuntimeState (with default_factory)
- Remove module-level _backend_cmd_supported and _backend_cmd_lock from
jail_service.py
- Refactor _check_backend_cmd_supported() to accept state parameter
- Inject JailServiceState into list_jails() and _fetch_jail_summary() via
parameters
- Add get_jail_service_state() dependency provider in app/dependencies.py
- Add JailServiceStateDep type alias for router injection
- Update jails router to receive and pass state to service functions
- Update all tests to use jail_service_state fixture and pass state to functions
- Remove duplicate _MAX_PAGE_SIZE constant definition
- Document mutable state management in Backend-Development.md
- Update Architecture.md to describe JailServiceState and state nesting pattern
Benefits:
- Eliminates global mutable state and associated race conditions
- Makes state visible to callers (not hidden in module scope)
- Enables test isolation (each test gets fresh state)
- Prepares codebase for multi-worker deployments (state can be extracted to
shared backend)
- Synchronization boundaries are now explicit (state.get_backend_cmd_lock())
Compliance:
- All tests pass (17 passed in TestListJails, TestGetJail, TestLockInitialization)
- No ruff linting errors
- Type-safe: JailServiceState properly typed with asyncio.Lock, bool | None
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of returning a bound method (geo_cache.lookup_batch), now inject
the GeoCache instance directly into routers and services. This provides
proper runtime isolation since T-04 made GeoCache a proper object.
Changes:
- Remove get_geo_batch_lookup() dependency provider
- Add GeoCacheDep type alias for injecting GeoCache instances
- Update all routers (bans, blocklist, dashboard, jails) to use GeoCacheDep
- Update ban_service, blocklist_service, jail_service to accept GeoCache
- Update service protocols to match new signatures
- Update docstrings to reference GeoCache methods instead of module functions
All callers now call geo_cache.lookup_batch(...) directly instead of
geo_batch_lookup(...), providing real dependency injection with proper
testing isolation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Task 1 — fix Stop/Reload Jail returning 404
Root cause: reload_jail and reload_all sent an empty config stream
(["reload", name, [], []]). In fail2ban's reload protocol the end-of-
reload phase deletes every jail still in reload_state — i.e. every jail
that received no configuration commands. An empty stream means *all*
affected jails are silently removed from the daemon's runtime, causing
everything touching those jails afterwards (including stop) to receive
UnknownJailException → HTTP 404.
Fixes:
- reload_jail: send ["start", name] in the config stream; startJail()
removes the jail from reload_state so the end phase commits instead of
deletes, and un-idles the jail.
- reload_all: fetch current jail list first, build a ["start", name]
entry for every active jail, then send reload --all with that stream.
- stop_jail: made idempotent — if the jail is already gone (not-found
error) the operation silently succeeds (200 OK) rather than returning
404, matching the user expectation that stop = ensure-stopped.
- Router: removed dead JailNotFoundError handler from stop endpoint.
391 tests pass (2 new), ruff clean, mypy clean (pre-existing
config.py error unchanged).
Task 2 — access list simulator
- Docker/simulate_accesses.sh: writes fake HTTP-scan log lines in
custom format (bangui-access: http scan from <IP> ...) to
Docker/logs/access.log so the bangui-access jail detects them.
- fail2ban/filter.d/bangui-access.conf: failregex matching the above.
- fail2ban/jail.d/bangui-access.conf: polling jail on access.log,
same settings as bangui-sim (maxretry=3, bantime=60s).
- .gitignore: whitelist new bangui-access.conf files.
- Docker/fail2ban-dev-config/README.md: added "Testing the Access
List Feature" section with step-by-step instructions and updated
Configuration Reference + Troubleshooting.