43 KiB
BanGUI — Architecture
This document describes the system architecture of BanGUI, a web application for monitoring, managing, and configuring fail2ban. It defines every major component, module, and data flow so that any developer can understand how the pieces fit together before writing code.
1. High-Level Overview
BanGUI is a two-tier web application with a clear separation between frontend and backend, connected through a RESTful JSON API.
┌──────────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Frontend (React + Fluent UI) │ │
│ │ TypeScript · Vite · Single-Page Application │ │
│ └──────────────────────────┬─────────────────────────────────┘ │
└─────────────────────────────┼────────────────────────────────────┘
│ HTTP / JSON (REST API)
┌─────────────────────────────┼────────────────────────────────────┐
│ Server │
│ ┌──────────────────────────┴─────────────────────────────────┐ │
│ │ Backend (FastAPI) │ │
│ │ Python 3.12+ · Async · Pydantic v2 · structlog │ │
│ └─────┬──────────────┬──────────────┬────────────────────────┘ │
│ │ │ │ │
│ ┌─────┴─────┐ ┌─────┴─────┐ ┌────┴─────┐ │
│ │ SQLite │ │ fail2ban │ │ External │ │
│ │ (App DB) │ │ (Socket) │ │ APIs │ │
│ └───────────┘ └───────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────────┘
Component Summary
| Component | Technology | Purpose |
|---|---|---|
| Frontend | TypeScript, React, Fluent UI v9, Vite | User interface — displays data, captures user input, communicates with the backend API |
| Backend | Python 3.12+, FastAPI, Pydantic v2, aiosqlite | Business logic, data persistence, fail2ban communication, scheduling |
| Application Database | SQLite (via aiosqlite) | Stores BanGUI's own data: configuration, session state, blocklist sources, import logs |
| fail2ban | Unix domain socket | The monitored service — BanGUI reads status, issues commands, and reads the fail2ban database |
| External APIs | HTTP (via aiohttp) | IP geolocation, ASN/RIR lookups, blocklist downloads |
2. Backend Architecture
The backend follows a layered architecture with strict separation of concerns. Dependencies flow inward: routers depend on services, services depend on repositories — never the reverse.
┌─────────────────────────────────┐
│ FastAPI Application │
│ (main.py) │
└──────────┬──────────────────────-┘
│
┌────────────────┼────────────────┐
│ │ │
┌─────┴──────┐ ┌─────┴──────┐ ┌──────┴──────┐
│ Routers │ │ Tasks │ │ Config │
│ (HTTP) │ │ (Scheduled)│ │ (Settings) │
└─────┬──────┘ └─────┬──────┘ └─────────────┘
│ │
┌─────┴───────────────┴──────┐
│ Services │
│ (Business Logic) │
└─────┬──────────────┬───────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│Repositories│ │ External │
│ (Database) │ │ Clients │
└─────┬──────┘ └─────┬──────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ SQLite │ │fail2ban / │
│ │ │HTTP APIs │
└────────────┘ └────────────┘
2.1 Project Structure
backend/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app factory, lifespan, exception handlers
│ ├── config.py # Pydantic settings (env vars, .env loading)
│ ├── dependencies.py # FastAPI Depends() providers (DB, services, auth)
│ ├── models/ # Pydantic schemas
│ │ ├── auth.py # Login request/response, session models
│ │ ├── ban.py # Ban request/response/domain models
│ │ ├── jail.py # Jail request/response/domain models
│ │ ├── config.py # Configuration view/edit models
│ │ ├── blocklist.py # Blocklist source/import models
│ │ ├── history.py # Ban history models
│ │ ├── server.py # Server status, health check models
│ │ └── setup.py # Setup wizard models
│ ├── routers/ # FastAPI routers (HTTP layer only)
│ │ ├── auth.py # POST /api/auth/login, POST /api/auth/logout
│ │ ├── setup.py # POST /api/setup (first-run configuration)
│ │ ├── dashboard.py # GET /api/dashboard/status, GET /api/dashboard/bans
│ │ ├── jails.py # CRUD + controls for jails
│ │ ├── bans.py # Ban/unban actions, currently banned list
│ │ ├── config.py # View/edit fail2ban configuration
│ │ ├── history.py # Historical ban queries
│ │ ├── blocklist.py # Blocklist source management, manual import trigger
│ │ ├── geo.py # IP geolocation and lookup
│ │ └── server.py # Server settings (log level, DB purge, etc.)
│ ├── services/ # Business logic (one service per domain)
│ │ ├── auth_service.py # Password verification, session creation/validation
│ │ ├── setup_service.py # First-run setup logic, configuration persistence
│ │ ├── jail_service.py # Jail listing, start/stop/reload, status aggregation
│ │ ├── ban_service.py # Ban/unban execution, currently-banned queries
│ │ ├── config_service.py # Read/write fail2ban config, regex validation
│ │ ├── history_service.py # Historical ban queries, per-IP timeline
│ │ ├── blocklist_service.py # Download, validate, apply blocklists
│ │ ├── geo_service.py # IP-to-country resolution, ASN/RIR lookup
│ │ ├── server_service.py # Server settings, log management, DB purge
│ │ └── health_service.py # fail2ban connectivity checks, version detection
│ ├── repositories/ # Data access layer (raw queries only)
│ │ ├── settings_repo.py # App configuration CRUD in SQLite
│ │ ├── session_repo.py # Session storage and lookup
│ │ ├── blocklist_repo.py # Blocklist sources and import log persistence
│ │ └── import_log_repo.py # Import run history records
│ ├── tasks/ # APScheduler background jobs
│ │ ├── blocklist_import.py# Scheduled blocklist download and application
│ │ └── health_check.py # Periodic fail2ban connectivity probe
│ └── utils/ # Helpers, constants, shared types
│ ├── fail2ban_client.py # Async wrapper around the fail2ban socket protocol
│ ├── ip_utils.py # IP/CIDR validation and normalisation
│ ├── time_utils.py # Timezone-aware datetime helpers
│ └── constants.py # Shared constants (default paths, limits, etc.)
├── tests/
│ ├── conftest.py # Shared fixtures (test app, client, mock DB)
│ ├── test_routers/ # One test file per router
│ ├── test_services/ # One test file per service
│ └── test_repositories/ # One test file per repository
├── pyproject.toml
└── .env.example
2.2 Module Purposes
Routers (app/routers/)
The HTTP interface layer. Each router maps URL paths to handler functions. Routers parse and validate incoming requests using Pydantic models, delegate all logic to services, and return typed responses. They contain zero business logic.
| Router | Prefix | Purpose |
|---|---|---|
auth.py |
/api/auth |
Login (password check), logout, session validation |
setup.py |
/api/setup |
First-run wizard — save initial configuration |
dashboard.py |
/api/dashboard |
Server status bar data, recent bans for the dashboard |
jails.py |
/api/jails |
List jails, jail detail, start/stop/reload/idle controls |
bans.py |
/api/bans |
Ban an IP, unban an IP, unban all, list currently banned IPs |
config.py |
/api/config |
Read and write fail2ban jail/filter/server configuration |
history.py |
/api/history |
Query historical bans, per-IP timeline |
blocklist.py |
/api/blocklists |
CRUD blocklist sources, trigger import, view import logs |
geo.py |
/api/geo |
IP geolocation lookup, ASN and RIR data |
server.py |
/api/server |
Log level, log target, DB path, purge age, flush logs |
Services (app/services/)
The business logic layer. Services orchestrate operations, enforce rules, and coordinate between repositories, the fail2ban client, and external APIs. Each service covers a single domain.
| Service | Purpose |
|---|---|
auth_service.py |
Hashes and verifies the master password, creates and validates session tokens, enforces session expiry |
setup_service.py |
Validates setup input, persists initial configuration, ensures setup runs only once |
jail_service.py |
Retrieves jail list and details from fail2ban, aggregates metrics (banned count, failure count), sends start/stop/reload/idle commands |
ban_service.py |
Executes ban and unban commands via the fail2ban socket, queries the currently banned IP list, validates IPs before banning |
config_service.py |
Reads active jail and filter configuration from fail2ban, writes configuration changes, validates regex patterns, triggers reload |
history_service.py |
Queries the fail2ban database for historical ban records, builds per-IP timelines, computes ban counts and repeat-offender flags |
blocklist_service.py |
Downloads blocklists via aiohttp, validates IPs/CIDRs, applies bans through fail2ban or iptables, logs import results |
geo_service.py |
Resolves IP addresses to country, ASN, and RIR using external APIs or a local database, caches results |
server_service.py |
Reads and writes fail2ban server-level settings (log level, log target, syslog socket, DB location, purge age) |
health_service.py |
Probes fail2ban socket connectivity, retrieves server version and global stats, reports online/offline status |
Repositories (app/repositories/)
The data access layer. Repositories execute raw SQL queries against the application SQLite database. They return plain data or domain models — they never raise HTTP exceptions or contain business logic.
| Repository | Purpose |
|---|---|
settings_repo.py |
CRUD operations for application settings (master password hash, DB path, fail2ban socket path, preferences) |
session_repo.py |
Store, retrieve, and delete session records for authentication |
blocklist_repo.py |
Persist blocklist source definitions (name, URL, enabled/disabled) |
import_log_repo.py |
Record import run results (timestamp, source, IPs imported, errors) for the import log view |
Models (app/models/)
Pydantic schemas that define data shapes and validation. Models are split into three categories per domain:
- Request models — validate incoming API data (e.g.,
BanRequest,LoginRequest) - Response models — shape outgoing API data (e.g.,
JailResponse,BanListResponse) - Domain models — internal representations used between services and repositories (e.g.,
Ban,Jail)
Tasks (app/tasks/)
APScheduler background jobs that run on a schedule without user interaction.
| Task | Purpose |
|---|---|
blocklist_import.py |
Downloads all enabled blocklist sources, validates entries, applies bans, records results in the import log |
health_check.py |
Periodically pings the fail2ban socket and updates the cached server status so the frontend always has fresh data |
Utils (app/utils/)
Pure helper modules with no framework dependencies.
| Module | Purpose |
|---|---|
fail2ban_client.py |
Async client that communicates with fail2ban via its Unix domain socket — sends commands and parses responses using the fail2ban protocol. Modelled after ./fail2ban-master/fail2ban/client/csocket.py and ./fail2ban-master/fail2ban/client/fail2banclient.py. |
ip_utils.py |
Validates IPv4/IPv6 addresses and CIDR ranges using the ipaddress stdlib module, normalises formats |
time_utils.py |
Timezone-aware datetime construction, formatting helpers, time-range calculations |
constants.py |
Shared constants: default socket path, default database path, time-range presets, limits |
Configuration (app/config.py)
A single Pydantic settings model that loads all configuration from environment variables (prefixed BANGUI_) and an optional .env file. Validated at startup — the application refuses to start if required values are missing.
Dependencies (app/dependencies.py)
FastAPI Depends() providers that inject shared resources into route handlers: the database connection, service instances, the authenticated session, and the fail2ban client. This is the wiring layer that connects routers to services without tight coupling.
Application Entry Point (app/main.py)
The FastAPI app factory. Responsibilities:
- Creates the
FastAPIinstance with metadata (title, version, docs URL) - Registers the lifespan context manager (startup: open DB, create aiohttp session, start scheduler; shutdown: close all)
- Mounts all routers
- Registers global exception handlers that map domain exceptions to HTTP status codes
- Applies the setup-redirect middleware (redirects all requests to
/api/setupwhen no configuration exists)
3. Frontend Architecture
The frontend is a React single-page application built with TypeScript, Vite, and Fluent UI v9. It communicates exclusively with the backend REST API — it never accesses fail2ban, the database, or external services directly.
┌──────────────────────────────────────────────────────────────┐
│ React Application │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ Pages │───▶│ Components │───▶│ Fluent UI v9 │ │
│ └────┬─────┘ └────────────┘ └──────────────────┘ │
│ │ │
│ ┌────┴─────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ Hooks │───▶│ API Layer │───▶│ Backend (REST) │ │
│ └──────────┘ └────────────┘ └──────────────────┘ │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │Providers │ │ Types │ │ Theme │ │
│ │(Context) │ │(Interfaces)│ │(Tokens, Styles) │ │
│ └──────────┘ └────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────┘
3.1 Project Structure
frontend/
├── public/
├── src/
│ ├── api/ # API client and per-domain request functions
│ │ ├── client.ts # Central fetch wrapper (typed GET/POST/PUT/DELETE)
│ │ ├── endpoints.ts # API path constants
│ │ ├── auth.ts # Login, logout, session check
│ │ ├── dashboard.ts # Dashboard status and ban list
│ │ ├── jails.ts # Jail CRUD and controls
│ │ ├── bans.ts # Ban/unban actions, banned list
│ │ ├── config.ts # Configuration read/write
│ │ ├── history.ts # Ban history queries
│ │ ├── blocklist.ts # Blocklist source management
│ │ ├── geo.ts # IP lookup / geolocation
│ │ └── server.ts # Server settings
│ ├── assets/ # Static images, fonts, icons
│ ├── components/ # Reusable UI components
│ │ ├── BanTable.tsx # Data table for ban entries
│ │ ├── JailCard.tsx # Summary card for a jail
│ │ ├── StatusBar.tsx # Server status indicator strip
│ │ ├── TimeRangeSelector.tsx # Quick preset picker (24h, 7d, 30d, 365d)
│ │ ├── IpInput.tsx # IP address input with validation
│ │ ├── RegexTester.tsx # Side-by-side regex match preview
│ │ ├── WorldMap.tsx # Country-outline map with ban counts
│ │ ├── ImportLogTable.tsx # Blocklist import run history
│ │ ├── ConfirmDialog.tsx # Reusable confirmation modal
│ │ └── ... # (additional shared components)
│ ├── hooks/ # Custom React hooks (stateful logic + API calls)
│ │ ├── useAuth.ts # Login state, login/logout actions
│ │ ├── useBans.ts # Fetch ban list for a time range
│ │ ├── useJails.ts # Fetch jail list and details
│ │ ├── useConfig.ts # Fetch and update configuration
│ │ ├── useHistory.ts # Fetch historical ban data
│ │ ├── useBlocklists.ts # Fetch and manage blocklist sources
│ │ ├── useServerStatus.ts # Poll server health / status
│ │ └── useGeo.ts # IP lookup hook
│ ├── layouts/ # Page-level layout wrappers
│ │ └── AppLayout.tsx # Sidebar navigation + header + content area
│ ├── pages/ # Route-level page components (one per route)
│ │ ├── SetupPage.tsx # First-run wizard
│ │ ├── LoginPage.tsx # Password prompt
│ │ ├── DashboardPage.tsx # Ban overview, status bar, access list
│ │ ├── WorldMapPage.tsx # Geographical ban map + access table
│ │ ├── JailsPage.tsx # Jail list, detail, controls, ban/unban
│ │ ├── ConfigPage.tsx # Configuration viewer/editor
│ │ ├── HistoryPage.tsx # Ban history browser
│ │ └── BlocklistPage.tsx # Blocklist source management + import log
│ ├── providers/ # React context providers
│ │ ├── AuthProvider.tsx # Authentication state and guards
│ │ └── ThemeProvider.tsx # Light/dark theme switching
│ ├── theme/ # Fluent UI theme definitions
│ │ ├── customTheme.ts # Brand colour ramp, light and dark themes
│ │ └── tokens.ts # Spacing, sizing, and z-index constants
│ ├── types/ # Shared TypeScript interfaces
│ │ ├── auth.ts # LoginRequest, SessionInfo
│ │ ├── ban.ts # Ban, BanListResponse, BanRequest
│ │ ├── jail.ts # Jail, JailDetail, JailListResponse
│ │ ├── config.ts # ConfigSection, ConfigUpdateRequest
│ │ ├── history.ts # HistoryEntry, IpTimeline
│ │ ├── blocklist.ts # BlocklistSource, ImportLogEntry
│ │ ├── geo.ts # GeoInfo, AsnInfo
│ │ ├── server.ts # ServerStatus, ServerSettings
│ │ └── api.ts # ApiError, PaginatedResponse
│ ├── utils/ # Pure helper functions
│ │ ├── formatDate.ts # Date/time formatting with timezone support
│ │ ├── formatIp.ts # IP display formatting
│ │ └── constants.ts # Frontend constants (time presets, etc.)
│ ├── App.tsx # Root: FluentProvider + BrowserRouter + routes
│ ├── main.tsx # Vite entry point
│ └── vite-env.d.ts # Vite type shims
├── tsconfig.json
├── vite.config.ts
└── package.json
3.2 Module Purposes
Pages (src/pages/)
Top-level route components. Each page composes layout, components, and hooks to create a full screen. Pages contain no business logic — they orchestrate what is displayed and delegate data fetching to hooks.
| Page | Route | Purpose |
|---|---|---|
SetupPage |
/setup |
First-run wizard: set master password, database path, fail2ban connection, preferences |
LoginPage |
/login |
Single-field password prompt; redirects to requested page after success |
DashboardPage |
/ |
Server status bar, ban list table, access list tab, time-range selector |
WorldMapPage |
/map |
World map with per-country ban counts, companion access table, country filter |
JailsPage |
/jails |
Jail overview list, jail detail panel, controls (start/stop/reload), ban/unban forms, IP lookup, whitelist management |
ConfigPage |
/config |
View and edit jail parameters, filter regex, server settings, regex tester, add log observation |
HistoryPage |
/history |
Browse all past bans, filter by jail/IP/time, per-IP timeline drill-down |
BlocklistPage |
/blocklists |
Manage blocklist sources, schedule configuration, import log, manual import trigger |
Components (src/components/)
Reusable UI building blocks. Components receive data via props, emit changes via callbacks, and never call the API directly. Built exclusively with Fluent UI v9 components.
| Component | Purpose |
|---|---|
StatusBar |
Displays fail2ban server status (online/offline, version, jail count, total bans) |
BanTable |
Sortable data table for ban entries with columns for time, IP, jail, country, etc. |
JailCard |
Summary card showing jail name, status badge, key metrics |
TimeRangeSelector |
Quick-preset picker for filtering data (24h, 7d, 30d, 365d) |
IpInput |
IP address text field with inline validation |
WorldMap |
SVG/Canvas country-outline map with count overlays and click-to-filter |
RegexTester |
Side-by-side sample log + regex input with live match highlighting |
ImportLogTable |
Table displaying blocklist import history |
ConfirmDialog |
Reusable Fluent UI Dialog for destructive action confirmations |
Hooks (src/hooks/)
Encapsulate all stateful logic, side effects, and API calls. Components and pages consume hooks to stay declarative.
| Hook | Purpose |
|---|---|
useAuth |
Manages login state, provides login(), logout(), and isAuthenticated |
useBans |
Fetches ban list for a given time range, returns { bans, loading, error } |
useJails |
Fetches jail list and individual jail detail |
useConfig |
Reads and writes fail2ban configuration |
useHistory |
Queries historical ban data with filters |
useBlocklists |
Manages blocklist sources and import triggers |
useServerStatus |
Polls the server status endpoint at an interval |
useGeo |
Performs IP geolocation lookups on demand |
API Layer (src/api/)
A thin typed wrapper around fetch. All HTTP communication is centralised here — components and hooks never construct HTTP requests directly.
| Module | Purpose |
|---|---|
client.ts |
Central get<T>, post<T>, put<T>, del<T> functions with error handling and credentials |
endpoints.ts |
All API path constants in one place — no hard-coded URLs anywhere else |
auth.ts |
login(), logout(), checkSession() |
dashboard.ts |
fetchStatus(), fetchRecentBans() |
jails.ts |
fetchJails(), fetchJailDetail(), startJail(), stopJail(), reloadJail() |
bans.ts |
banIp(), unbanIp(), unbanAll(), fetchBannedIps() |
config.ts |
fetchConfig(), updateConfig(), testRegex() |
history.ts |
fetchHistory(), fetchIpTimeline() |
blocklist.ts |
fetchSources(), addSource(), removeSource(), triggerImport(), fetchImportLog() |
geo.ts |
lookupIp() |
server.ts |
fetchServerSettings(), updateServerSettings() |
Types (src/types/)
Shared TypeScript interfaces and type aliases. Purely declarative — no runtime code. Grouped by domain. Any type used by two or more files lives here.
Providers (src/providers/)
React context providers for application-wide concerns.
| Provider | Purpose |
|---|---|
AuthProvider |
Holds authentication state, wraps protected routes, redirects unauthenticated users to /login |
ThemeProvider |
Manages light/dark theme selection, supplies the active Fluent UI theme to FluentProvider |
Theme (src/theme/)
Fluent UI custom theme definitions and design token constants. No component logic — only colours, spacing, and sizing values.
Utils (src/utils/)
Pure helper functions with no React or framework dependency. Date formatting, IP display formatting, shared constants.
4. Data Flow
4.1 Request Lifecycle
Every user action follows this flow through the system:
User Action (click, form submit)
│
▼
Page / Component
│ calls hook
▼
Hook (useXxx)
│ calls API function
▼
API Layer (src/api/)
│ HTTP request
▼
FastAPI Router (app/routers/)
│ validates input (Pydantic)
│ calls Depends() for auth + services
▼
Service (app/services/)
│ enforces business rules
│ calls repository or fail2ban client
▼
Repository (app/repositories/) or fail2ban Client (app/utils/)
│ executes SQL query │ sends socket command
▼ ▼
SQLite Database fail2ban Server
│ │
└──────────── response bubbles back up ─────┘
4.2 Authentication Flow
┌─────────┐ POST /api/auth/login ┌─────────────┐
│ Login │ ─────────────────────────────▶│ auth router │
│ Page │ { password: "***" } │ │
└─────────┘ └──────┬───────┘
│
┌──────┴───────┐
│ auth_service │
│ - verify hash │
│ - create token│
└──────┬───────┘
│
┌──────┴───────┐
│ session_repo │
│ - store token │
└──────┬───────┘
│
Set-Cookie: session=<token> │
◀─────────────────────────────────────────────────┘
- The master password is hashed and stored during setup.
- On login, the submitted password is verified against the stored hash.
- A session token is created, stored in the database, and returned as an HTTP-only cookie.
- Every subsequent request is authenticated via the session cookie using a FastAPI dependency.
- The
AuthProvideron the frontend guards all routes except/setupand/login.
4.3 fail2ban Communication
BanGUI communicates with fail2ban through its Unix domain socket using the fail2ban client-server protocol.
┌────────────────────┐ ┌──────────────────┐
│ ban_service.py │ │ fail2ban server │
│ jail_service.py │──socket──│ │
│ config_service.py │ │ /var/run/fail2ban│
│ health_service.py │ │ /fail2ban.sock │
└────────────────────┘ └──────────────────┘
The fail2ban_client.py utility module wraps this communication:
- Opens an async connection to the Unix socket
- Serialises commands using the fail2ban protocol (pickle-based, see
./fail2ban-master/fail2ban/client/csocket.py) - Parses responses into typed Python objects
- Handles connection errors gracefully (timeout, socket not found, permission denied)
Reference source: The vendored fail2ban source at
./fail2ban-masteris included in the repository as an authoritative protocol reference. When implementing or debugging socket communication, consult:
File What it documents ./fail2ban-master/fail2ban/client/csocket.pyCSocketclass — low-level Unix socket connection, pickle serialisation,CSPROTO.ENDframing./fail2ban-master/fail2ban/client/fail2banclient.pyFail2banClient— command dispatch, argument handling, response beautification./fail2ban-master/fail2ban/client/beautifier.pyResponse parser — converts raw server replies into human-readable / structured output ./fail2ban-master/fail2ban/protocol.pyCSPROTOconstants and the full list of supported commands with descriptions./fail2ban-master/fail2ban/client/configreader.pyConfig file parsing used by fail2ban — reference for understanding jail/filter structure
Key commands used:
| Command | Purpose |
|---|---|
status |
Get global server status (number of jails, fail2ban version) |
status <jail> |
Get jail detail (banned IPs, failure count, filter info) |
set <jail> banip <ip> |
Ban an IP in a specific jail |
set <jail> unbanip <ip> |
Unban an IP from a specific jail |
set <jail> idle on/off |
Toggle jail idle mode |
start/stop <jail> |
Start or stop a jail |
reload <jail> |
Reload a single jail configuration |
reload |
Reload all jails |
get <jail> ... |
Read jail settings (findtime, bantime, maxretry, filter, actions, etc.) |
set <jail> ... |
Write jail settings |
set loglevel <level> |
Change server log level |
set logtarget <target> |
Change server log target |
set dbpurgeage <seconds> |
Set database purge age |
flushlogs |
Flush and re-open log files |
4.4 fail2ban Database Access
In addition to the live socket, BanGUI reads the fail2ban SQLite database directly for historical data that the socket protocol does not expose (ban history, past log matches). This is read-only access.
history_service.py ──read-only──▶ fail2ban.db (SQLite)
The fail2ban database contains:
banstable — historical ban records (IP, jail, timestamp, ban data)jailstable — jail definitionslogstable — matched log lines per ban
BanGUI queries these tables to power the Ban History page and the per-IP timeline view.
4.5 External API Communication
geo_service.py ──aiohttp──▶ IP Geolocation API (country, ASN, RIR)
blocklist_service.py ──aiohttp──▶ Blocklist URLs (plain-text IP lists)
All external HTTP calls go through a shared aiohttp.ClientSession created during startup and closed during shutdown. External data is validated before use (IP format, response structure).
5. Database Design
BanGUI maintains its own SQLite database (separate from the fail2ban database) to store application state.
5.1 Application Database Tables
| Table | Purpose |
|---|---|
settings |
Key-value store for application configuration (master password hash, fail2ban socket path, database path, timezone, session duration) |
sessions |
Active session tokens with expiry timestamps |
blocklist_sources |
Registered blocklist URLs (id, name, url, enabled, created_at, updated_at) |
import_logs |
Record of every blocklist import run (id, source_id, timestamp, ips_imported, ips_skipped, errors, status) |
5.2 Database Boundaries
| Database | Owner | BanGUI Access |
|---|---|---|
BanGUI application DB (bangui.db) |
BanGUI | Read + Write |
fail2ban DB (fail2ban.db) |
fail2ban | Read-only (for history queries) |
6. Authentication & Session Management
- Single-user model — one master password, no usernames.
- Password is hashed with a strong algorithm (e.g., bcrypt or argon2) and stored in the application database during setup.
- Sessions are token-based, stored server-side in the
sessionstable, and delivered to the browser as HTTP-only secure cookies. - Session expiry is configurable (set during setup, stored in
settings). - The frontend
AuthProviderchecks session validity on mount and redirects to/loginif invalid. - The backend
dependencies.pyprovides anauthenticateddependency that validates the session cookie on every protected endpoint.
7. Scheduling
APScheduler 4.x (async mode) manages recurring background tasks.
┌──────────────────────┐
│ APScheduler │
│ (async, in-process) │
├──────────────────────┤
│ blocklist_import │ ── runs on configured schedule (default: daily 03:00)
│ health_check │ ── runs every 30 seconds
└──────────────────────┘
- The scheduler is started during the FastAPI lifespan startup and stopped during shutdown.
- Job schedules are persisted in the application database so they survive restarts.
- Users can modify the blocklist import schedule through the web interface.
- A manual "Run Now" button triggers the blocklist import job outside the schedule.
8. API Design
8.1 Conventions
- All endpoints are grouped under
/api/prefix. - JSON request and response bodies, validated by Pydantic models.
- Authentication via session cookie on all endpoints except
/api/setupand/api/auth/login. - Setup-redirect middleware: while no configuration exists, all endpoints return
303 See Other→/api/setup. - Standard HTTP status codes:
200success,201created,204no content,400bad request,401unauthorized,404not found,422validation error,500server error. - Error responses follow a consistent shape:
{ "detail": "Human-readable message" }.
8.2 Endpoint Groups
| Group | Endpoints | Description |
|---|---|---|
| Auth | POST /login, POST /logout |
Session management |
| Setup | POST /setup |
First-run configuration |
| Dashboard | GET /status, GET /bans |
Overview data for the main page |
| Jails | GET /, GET /:name, POST /:name/start, POST /:name/stop, POST /:name/reload, POST /reload-all |
Jail listing and controls |
| Bans | POST /ban, POST /unban, POST /unban-all, GET /banned |
Ban management |
| Config | GET /, PUT /, POST /test-regex |
Configuration viewing and editing |
| History | GET /, GET /ip/:ip |
Historical ban browsing |
| Blocklists | GET /sources, POST /sources, DELETE /sources/:id, POST /import, GET /import-log |
Blocklist management |
| Geo | GET /lookup/:ip |
IP geolocation and enrichment |
| Server | GET /settings, PUT /settings, POST /flush-logs |
Server-level settings |
9. Deployment Architecture
┌──────────────────────────────────────────────────┐
│ Host Machine │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Reverse Proxy (nginx / caddy) │ │
│ │ - TLS termination │ │
│ │ - /api/* → backend (uvicorn) │ │
│ │ - /* → frontend (static files) │ │
│ └──────────────┬───────────────┬──────────────┘ │
│ │ │ │
│ ┌──────────────┴───┐ ┌───────┴──────────────┐ │
│ │ Backend │ │ Frontend │ │
│ │ uvicorn + FastAPI │ │ Static build (Vite) │ │
│ │ (port 8000) │ │ (served by proxy) │ │
│ └────────┬──────────┘ └──────────────────────┘ │
│ │ │
│ ┌────────┴──────────────────────────────────┐ │
│ │ fail2ban (systemd service) │ │
│ │ Socket: /var/run/fail2ban/fail2ban.sock │ │
│ │ Database: /var/lib/fail2ban/fail2ban.db │ │
│ └───────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
- The backend runs as an ASGI server (uvicorn) behind a reverse proxy.
- The frontend is built to static files by Vite and served directly by the reverse proxy.
- The backend process needs read access to the fail2ban socket and the fail2ban database.
- Both the application database and the fail2ban database reside on the same host.
10. Design Principles
These principles govern all architectural decisions in BanGUI.
| Principle | Application |
|---|---|
| Separation of Concerns | Frontend and backend are independent. Backend layers (router → service → repository) never mix responsibilities. |
| Single Responsibility | Each module, service, and component has one well-defined job. |
| Dependency Inversion | Services depend on abstractions (protocols), not concrete implementations. FastAPI Depends() wires everything. |
| Async Everything | All I/O is non-blocking. No synchronous database, HTTP, or socket calls anywhere in the backend. |
| Validate at the Boundary | Pydantic models validate all data entering the backend. TypeScript types enforce structure on the frontend. |
| Fail Fast | Configuration is validated at startup. Invalid input is rejected immediately with clear errors. |
| Composition over Inheritance | Small, focused objects are composed together rather than building deep class hierarchies. |
| DRY | Shared logic lives in utils, hooks, or base services — never duplicated across modules. |
| KISS | The simplest correct solution wins. No premature abstractions or over-engineering. |
| YAGNI | Only build what is needed now. Extend when a real requirement appears. |