A desktop application for OPNsense lease operations and MSD webfilter operations, including static and dynamic DHCP workflows, firewall log streaming, and webfilter log/address-list management.
- This project is licensed under PolyForm Noncommercial License 1.0.0.
- Commercial use, commercial modification, and use in commercial applications are not permitted without a separate commercial license from the author.
- See
LICENSEfor the full terms.
- Base URL-driven configuration (
loginanddashboardURLs are derived automatically, port:81enforced) - Static Gruen lease workflow: CSV import/validation, diffing, add/update/delete review for the
lan(Gruen) interface - Static WLANBYOD lease workflow: identical workflow for the
opt4(WLANBYOD) interface — separate panel, separate CSV state - Dynamic lease workflow: load dynamic leases; move eligible leases to static and update conflicting static entries
- Supported source interfaces for move/update:
Gruen→lan,WLANBYOD→opt4
- Supported source interfaces for move/update:
- Conflict helpers with suggested free IP/hostname and field-level update selection (IP/MAC)
- Lease views:
Static Gruen,Static WLANBYOD,Dynamic,Review, andRun Log - Collapsible review sections (Add, Conflicts, Deletes) per interface panel
- Static lease export fetches both Gruen (
export_static.csv) and WLANBYOD (export_static_wlanbyod.csv) in a single API call - Firewall stream view with live event ingestion and source health state
- Webfilter log view (MSD/Schulfilter Plus on port
80/1920) with resilient polling and dynamic UI actions - Webfilter address-list management for whitelist and blacklist entries:
- Fetch lists and entry totals
- Add, edit, and delete entries
- Import and export list entries
- Source polling policy with per-source stale thresholds, retry backoff, and poll jitter helpers
- Settings persistence, run history, and in-app updater support
- Install Rust and Tauri prerequisites (Windows):
- Install dependencies:
- npm install
- Create a Python environment and install deps:
- cd backend
- python -m venv .venv
- .venv\Scripts\activate
- pip install -r requirements.txt
Only requests is required for backend dependencies. Playwright and selector configuration are no longer needed.
The backend authenticates against OPNsense, captures session cookies, and calls
the /api/tfk/dhcp/* endpoints directly.
The repository now includes a containerized server API for server-first analysis endpoints:
POST /api/auth/loginPOST /api/auth/refreshGET /api/history/eventsGET /api/history/trendsGET /api/status/freshnessPOST /api/proxy/executeGET /api/proxy/operations/{requestId}GET /health
server/Dockerfile- source Dockerfile used by GitHub Actions to publish the server imagedocker-compose.yml- local runtime stackserver/requirements.txt- Python dependencies for the server container
Create a .env.server file in the repository root:
TFK_SERVER_PORT=8080
TFK_SERVER_JWT_SECRET=replace-with-a-long-random-secret
TFK_SERVER_BOOTSTRAP_USERNAME=admin
TFK_SERVER_BOOTSTRAP_PASSWORD=replace-with-strong-password
TFK_SERVER_BOOTSTRAP_ROLE=admin
TFK_SERVER_CORS_ORIGINS=*
TFK_SERVER_COLLECTOR_ENABLED=1
TFK_SERVER_COLLECTOR_SAMPLE_MODE=0
TFK_SERVER_COLLECTOR_INTERVAL_SECONDS=20
TFK_SOURCE_OPNSENSE_BASE_URL=https://your-opnsense-host:81
TFK_SOURCE_API_USERNAME=replace-with-opnsense-api-username
TFK_SOURCE_API_KEY=replace-with-opnsense-api-key
TFK_SOURCE_API_SECRET=replace-with-opnsense-api-secret
TFK_SOURCE_OPNSENSE_USERNAME=replace-with-opnsense-username
TFK_SOURCE_OPNSENSE_PASSWORD=replace-with-opnsense-password
TFK_SOURCE_COOKIE_HEADER=
TFK_SOURCE_MSD_COOKIE=
TFK_SOURCE_VERIFY_TLS=0
TFK_SOURCE_CONNECT_TIMEOUT_SECONDS=10
TFK_SOURCE_READ_TIMEOUT_SECONDS=30
TFK_SOURCE_FIREWALL_MAX_EVENTS=25
TFK_SOURCE_DHCP_PATH=/api/tfk/dhcp/dynamic_leases
TFK_SOURCE_FIREWALL_STREAM_PATH=/api/diagnostics/firewall/streamLog
TFK_MSD_USERNAME=replace-with-msd-username
TFK_MSD_PASSWORD=replace-with-msd-password
TFK_PROXY_HMAC_SHARED_SECRET=replace-with-long-random-secret
TFK_PROXY_REQUEST_AUDIENCE=tfk-manager-server
TFK_PROXY_MAX_SKEW_SECONDS=120
TFK_PROXY_NONCE_TTL_SECONDS=300
TFK_SERVER_ALLOWED_PROXY_IPS=127.0.0.1/32Notes:
TFK_SERVER_BOOTSTRAP_USERNAMEandTFK_SERVER_BOOTSTRAP_PASSWORDare used to create the initial login user if it does not exist yet.- Supported roles from current authz policy are
adminandanalyst. - If the bootstrap user already exists, startup will keep the existing account.
TFK_SERVER_COLLECTOR_ENABLED=1runs a background collector loop in the API container.TFK_SERVER_COLLECTOR_SAMPLE_MODE=0enables real source ingestion using theTFK_SOURCE_*andTFK_MSD_*credentials.- DHCP and firewall collectors now follow the same GUI-session flow as the desktop app: use
TFK_SOURCE_OPNSENSE_USERNAME+TFK_SOURCE_OPNSENSE_PASSWORD(or import an existing browser cookie viaTFK_SOURCE_COOKIE_HEADER) to access/api/tfk/dhcp/*and firewall stream endpoints. TFK_SOURCE_DHCP_PATHdefaults to/api/tfk/dhcp/dynamic_leasesand automatically falls back to/api/tfk/dynamicleases/get.- Set
TFK_SERVER_COLLECTOR_SAMPLE_MODE=1when you want demo data without source credentials. TFK_PROXY_HMAC_SHARED_SECRETmust match the desktop delegated-mode secret (orVITE_TFK_PROXY_HMAC_SHARED_SECRET) for proxy request signing.TFK_PROXY_REQUEST_AUDIENCEmust match the desktop audience configured in Settings.
docker compose --env-file .env.server pull
docker compose --env-file .env.server up -d- Base URL:
http://localhost:8080 - Health endpoint:
curl http://localhost:8080/healthGet tokens:
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"replace-with-strong-password"}'Use the returned accessToken as bearer token:
curl "http://localhost:8080/api/history/events?startAt=2026-03-21T00:00:00Z&endAt=2026-03-21T23:59:59Z&limit=100" \
-H "Authorization: Bearer <accessToken>"docker compose downData persists in Docker volume tfk_server_data (SQLite DB at /data/server.db inside the container).
For delegated operations over remote networks, place NGINX in front of the server and keep the API container private.
- Baseline hardened TLS profile and high-security mTLS profile:
docs/server/reverse-proxy-hardening.md
- Includes rate limiting, forwarded-header handling, request-size limits, method restrictions, and SSE-safe proxy timeout/buffering guidance.
Example delegated-mode call path:
- Desktop logs in to server and receives JWT.
- Desktop signs
POST /api/proxy/executewith HMAC (X-Proxy-Signature). - Server validates nonce/timestamp/audience/signature and role/scope.
- Server executes allow-listed operation and writes audit record.
| Mode | Description |
|---|---|
export |
Fetch /api/tfk/dhcp/static_leases once; write export_static.csv (Gruen/lan) and export_static_wlanbyod.csv (WLANBYOD/opt4) |
dynamic |
Fetch dynamic leases from /api/tfk/dhcp/leases; write export_dynamic.csv |
move_dynamic |
Move a dynamic lease to static — interface mapped via TFK_IFACE (Gruen→lan, WLANBYOD→opt4) |
update_dynamic_conflict |
Update an existing static lease from dynamic values — same interface mapping as above |
| UI name | OPNsense API if value |
|---|---|
| Gruen | lan |
| WLANBYOD | opt4 |
- npm run tauri dev
Running npm run build automatically copies backend/ into src-tauri/resources/backend/ via scripts/copy-backend.mjs before compiling — no manual sync needed for production or CI builds.
- Frontend/unit tests (Vitest):
npm run test
- Dynamic lease backend slice tests:
python -m unittest -v backend/src/test_dynamic_leases_slice.py
- Frontend:
src/App.tsx(single-file React app) with helper modules for parsing/protocol/polling logic. - Tauri bridge:
src-tauri/src/lib.rscommands invoke Python backend modes and return typed JSON payloads. - Backend:
backend/src/opnsense_dhcp_ui.py,backend/src/fetch_firewall_log_stream.py, andbackend/src/fetch_webfilter.py.
- Passwords are stored in OS keyring via Tauri commands; avoid committing local capture artifacts or endpoint dumps.
- Keep OPNsense (
:81) and MSD webfilter (:80/:1920) flows separated in implementation and diagnostics.
Compiled binaries are available on the GitHub releases page:
- The app checks GitHub releases on startup and prompts to install when a new version is available.
- Update packages are signed; configure signing secrets for the release workflow:
TAURI_SIGNING_PRIVATE_KEYTAURI_SIGNING_PRIVATE_KEY_PASSWORD
- The updater expects
latest.jsonin the GitHub release assets.
- CSV format expects semicolon separators.
- Backend script path is resolved automatically from the app; keep the
backendfolder next to the app. - Configure
Python pathin Settings if you use a non-default Python install. loginUrlanddashboardUrlare read-only in the UI and derived from the Base URL.
- If you need commercial use rights, request a separate commercial license from the project owner.