- New endpoints: GET /api/v1/pools, GET /api/v1/pools/{slug}/next,
GET/POST /api/v1/allocations, GET /api/v1/allocations/{id},
POST /api/v1/allocations/{id}/release
- Bearer token auth; only SHA-256 hashes stored, plaintext shown once
- Token management GUI at /tokens (create, revoke, delete) linked in footer
- API actions audited with actor api:<token name>
- Fix latent transaction leak: replace manual BEGIN IMMEDIATE/COMMIT with
begin_with(), which rolls back on drop; previously a failed allocation
(e.g. disallowed prefix or full pool) returned a connection with an open
transaction to the pool, causing 'database is locked' on later writes
- README: API documentation with curl examples
|
||
|---|---|---|
| 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.
- JSON REST API (
/api/v1/) with Bearer token authentication; tokens are managed in the web UI. - 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.
REST API
The application provides a JSON REST API under /api/v1/ for integrations. All API requests are authenticated with a Bearer token:
Authorization: Bearer sa_...
Managing API tokens
Tokens are managed in the web UI on the API tokens page (link in the footer):
- Create – enter a descriptive name (e.g.
monitoring-integration). The plaintext token is shown only once right after creation; only its SHA-256 hash is stored in the database. - Revoke – disables the token immediately. The record is kept for audit purposes.
- Delete – permanently removes a revoked token.
Every API action is recorded in the audit log with the actor api:<token name>.
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/pools |
List enabled pools with allocation counts |
GET |
/api/v1/pools/{slug}/next?prefix_length=30 |
Preview the next free subnet (informational only) |
GET |
/api/v1/allocations |
List allocations; filters: status, pool, q, limit (max 500), offset |
GET |
/api/v1/allocations/{id} |
Allocation detail including address range |
POST |
/api/v1/allocations |
Allocate the next free subnet |
POST |
/api/v1/allocations/{id}/release |
Release an allocation |
The allocation is always recalculated inside a write transaction – the preview from /next may differ from the actually allocated subnet under concurrent use.
Examples
TOKEN="sa_your_token_here"
BASE="http://SERVER_IP:8080"
# List pools
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/v1/pools
# Preview the next free /30 in pool "vpn"
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/pools/vpn/next?prefix_length=30"
# Allocate the next free /30 (note is mandatory, min. 3 characters)
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"pool_slug":"vpn","prefix_length":30,"note":"customer X edge","ticket":"INC-123"}' \
$BASE/api/v1/allocations
# List active allocations in pool "vpn"
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/allocations?status=active&pool=vpn"
# Release allocation 42 (release note is mandatory)
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"release_note":"no longer needed"}' \
$BASE/api/v1/allocations/42/release
Errors are returned as {"error":"message"} with an appropriate status code (400 invalid request, 401 missing/invalid token, 404 not found, 409 pool full or allocation already released).
Security notes
- The web UI has no authentication. Use it only behind a VPN/LAN/firewall.
- The REST API requires a Bearer token; only token hashes (SHA-256) are stored.
- CSRF tokens are required for POST forms in the web UI.
- 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 poolPOST /pools/{id}/delete– delete a pool (allocations are kept)GET /tokens– API token managementPOST /tokens/new– create an API tokenPOST /tokens/{id}/revoke– revoke an API tokenPOST /tokens/{id}/delete– delete a revoked API tokenGET /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}– exportsGET|POST /api/v1/...– JSON REST API (see above)
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.