- Rename app title to SUB-ALIGATOR (header, page titles, exports, README) - Add dark amber-terminal theme with a header toggle, persisted in localStorage - Add pool deletion (soft delete): allocations are kept in history and block the address space again when the same or an overlapping pool is re-added (re-enable on exact CIDR match, re-attach contained allocations) - Reject new pools that overlap an enabled pool - Move Home link into the page header, keep other links in the footer - Load app.js globally via base template, add theme.js (CSP-safe, no inline JS) |
||
|---|---|---|
| migrations | ||
| scripts | ||
| src | ||
| static | ||
| templates | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| compose.yaml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
SUB-ALIGATOR
A simple internal web application for tracking and automatically allocating IPv4 subnets.
Purpose
This application is intended solely for internal tracking and automatic allocation of IPv4 subnets. It is not a general IPAM, monitoring tool, device inventory, VLAN manager, or network topology viewer.
It is designed to run only inside a LAN/VPN. The application does not include authentication and must be placed behind a firewall/reverse proxy in a trusted network.
Features
- Manage one or more IP pools manually through the web UI.
- First-run setup wizard: create the first pool before allocating subnets.
- Automatic subnet allocation using a first-fit algorithm for prefixes /20 through /30.
- Atomic allocation writes using SQLite
BEGIN IMMEDIATEtransactions. - Retro TUI/terminal-style web UI, server-side rendered with Askama.
- Dynamic preview of the next free subnet via HTMX.
- Allocation history with filtering, search, and pagination.
- Release subnets (status
released) with a mandatory note, directly from the history table or detail page. - Released subnets free up address space and can be reused by the first-fit algorithm.
- Delete pools without losing their allocations: the subnets stay in history and keep blocking the address range if the same or an overlapping pool is added again.
- Light and dark (amber terminal) theme, switchable in the page header and remembered per browser.
- Audit log of all operations.
- Markdown, CSV, and JSON exports.
- SQLite migrations run automatically on startup.
- Backup and restore scripts.
Architecture
- Rust edition 2024
- Axum + Tokio
- SQLx with SQLite
- Askama for HTML templates
- HTMX from a local static file
- Custom CSS without external frameworks
- CIDR/IP calculations using
ipnet
Quick start
cd sub-aligator
cp .env.example .env
docker compose up -d --build
Open a browser at:
http://SERVER_IP:8080
On first launch you will be asked to create the first pool. The default port can be changed in .env via the APP_PORT variable.
Persistent data
Docker Compose creates local directories:
./data– SQLite database./exports– generated exports (Markdown, CSV, JSON)./backups– database backups
These directories are bind-mounted into the container, so data persists even if the container is removed.
Backup and restore
Backup
./scripts/backup.sh
The script creates a consistent database backup using sqlite3 .backup with a UTC timestamp in the filename. Backups are stored in ./backups.
Restore
./scripts/restore.sh ./backups/subnet-allocator.db.20240930T120000Z.backup
Warning: restore overwrites the current database. Stop the container and back up the current state before restoring.
Automating backups
On the host you can use a systemd timer or cron. Example cron entry:
0 2 * * * /path/to/sub-aligator/scripts/backup.sh >> /var/log/subnet-allocator-backup.log 2>&1
Exports
After every successful allocation or release, read-only exports are regenerated:
/app/exports/allocations.md/app/exports/allocations.csv/app/exports/allocations.json
They are also available for download via endpoints:
GET /exports/allocations.mdGET /exports/allocations.csvGET /exports/allocations.json
The Markdown export contains a separate section for each pool with a table of active and reserved allocations. CSV and JSON include all allocations, including released ones.
Security notes
- The application has no authentication. Use it only behind a VPN/LAN/firewall.
- CSRF tokens are required for POST forms.
- Security headers are set:
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Content-Security-Policy. - The application has no default Internet exposure; port binding is handled by Docker Compose.
/healthzreturns only{"status":"ok"}.
Adding authentication later via Caddy + Authentik
The application is prepared for authentication through a trusted reverse proxy. The function get_actor_from_request() in src/audit.rs currently returns local, but can be updated to read a header such as X-Authentik-Username.
Example Caddy configuration with Authentik:
subnet.example.com {
reverse_proxy /outpost.goauthentik.io/* http://authentik:9000
forward_auth http://authentik:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email
}
reverse_proxy subnet-allocator:8080
}
After updating get_actor_from_request(), the audit log will record the real user.
First-fit allocation example
- First request
/30receives10.91.0.0/30. - Next request
/24receives10.91.1.0/24(avoids the occupied/30). - Another request
/30fills the gap and receives10.91.0.4/30.
The algorithm always respects CIDR alignment and finds the first free space large enough.
Endpoints
GET /– main pageGET /healthz– healthcheckGET /setup– first-run pool setupGET /pools– pool managementPOST /pools/new– create a new poolGET /partials/pools/{slug}/preview?prefix_length=24– HTMX previewPOST /allocations– create an allocationGET /allocations/{id}– allocation detailPOST /allocations/{id}/release– release an allocationGET /exports/allocations.{md,csv,json}– exports
Troubleshooting
Container does not start
docker compose logs -f subnet-allocator
Database is locked
If the process terminates unexpectedly, a -wal or -shm file may remain. Stop the container and check ./data.
Exports are not generated
Check the permissions of the ./exports directory and the APP_EXPORTS_DIR variable.
Licence
MIT. See the LICENSE file.