Stupid Simple Network Inventory

Python FastAPI Vue.js SQLite Nginx Docker

Self-hosted web application for manual network inventory and logical network topology visualisation.

Features

  • Manual inventory — add and manage devices (18 types) with IPs, VLANs, descriptions and optional web links
  • Topology view — card-based layout per network (LAN / VLAN 802.1Q), with WAN and gateway sections
  • ICMP ping sweep — check reachability of all known hosts in one click
  • Auto-discovery — ping sweep + PTR DNS lookup on a subnet to import new hosts
  • Authentication — JWT-based login with forced password change on first use
  • Dark mode — light / dark theme toggle
  • i18n — French, English, Spanish

Stack

Layer Technology
Backend FastAPI + SQLAlchemy + SQLite (Python 3.11)
Frontend Vue 3 + Vite, served by Nginx
Auth JWT HS256, 24-hour expiry
Runtime Docker Compose

Quick start

# 1. Clone and enter the project
git clone https://git.raspot.in/olivier/stupid-simple-network-inventory.git
cd stupid-simple-network-inventory

# 2. Create the data directory owned by the current user
mkdir -p db_data

# 3. Configure environment (required for correct bind-mount ownership)
cp .env.example .env
# Edit .env:
#   DOCKER_UID / DOCKER_GID → output of: id -u && id -g
#   INITIAL_ADMIN_PASSWORD  → set to avoid the default admin/admin bootstrap

# 4. Build and start
docker compose --env-file .env up --build -d

# 5. Open http://localhost:8080 in your browser

First login

Case Credentials Behaviour
INITIAL_ADMIN_PASSWORD set admin / <your password> Normal login
INITIAL_ADMIN_PASSWORD unset admin / admin Forced password change before accessing the app

Configuration

All configuration is via environment variables. See .env.example for the full list with descriptions.

Variable Default Description
SECRET_KEY auto-generated JWT signing key. Set explicitly in production.
INITIAL_ADMIN_PASSWORD (empty) Bootstrap admin password. If unset, admin/admin is used with forced change.
ALLOWED_ORIGINS * CORS allowed origins (comma-separated). Set to your domain in production.
DOCKER_UID / DOCKER_GID 1000 UID/GID for the backend process. Must match the host user owning ./db_data/.

Using .env with Docker Compose

cp .env.example .env
# Edit .env — at minimum set DOCKER_UID, DOCKER_GID, INITIAL_ADMIN_PASSWORD
docker compose --env-file .env up --build -d

Security

Secret management

Two options depending on your security requirements.

Leave SECRET_KEY unset (or empty) in .env. On first start the backend generates a random 64-character hex key, writes it to db_data/secret_key.txt with permissions 0600, and reuses it on every subsequent restart. The secret never appears in an environment variable, a compose file, or a log.

# .env — leave the line empty or remove it
SECRET_KEY=

The only requirement is that db_data/ is backed up (it already contains the database).

Option B — Docker Compose file secret

Stores the secret in a file on the host, outside version control, and mounts it into the container. The value never appears in an environment variable.

# Generate and store the secret outside the project directory
mkdir -p ~/.secrets
python3 -c "import secrets; print(secrets.token_hex(32))" > ~/.secrets/topologie_secret_key
chmod 600 ~/.secrets/topologie_secret_key

Then uncomment the secrets: blocks in docker-compose.yml (see comments in that file) and remove SECRET_KEY from .env. Docker Compose merges the override automatically:

docker compose up -d

Key rotation

To rotate the JWT secret (invalidates all active sessions):

# Option A — environment variable (recommended)
# Set a new SECRET_KEY in your deployment config and restart

# Option B — file rotation
docker compose stop backend
rm db_data/secret_key.txt
docker compose start backend
# All users will need to log in again

HTTPS

This application does not terminate TLS. For production use, place it behind a reverse proxy that handles HTTPS:

# Example nginx reverse-proxy (external, on the host or a dedicated container)
server {
    listen 443 ssl;
    server_name inventory.example.com;

    ssl_certificate     /etc/letsencrypt/live/inventory.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/inventory.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

For local-only use, bind to loopback to prevent accidental LAN exposure:

# docker-compose.override.yml
services:
  frontend:
    ports:
      - "127.0.0.1:8080:8080"

Container hardening

The containers run with reduced privileges:

Measure Backend Frontend
Non-root user DOCKER_UID:DOCKER_GID (host user) nginx (UID 101)
cap_drop: ALL
cap_add: NET_RAW ✓ (ping)
no-new-privileges — ¹
Healthcheck

¹ Omitted on the backend: ping uses file capabilities (cap_net_raw=ep); no-new-privileges suppresses the file effective bit and would prevent the subprocess from acquiring CAP_NET_RAW in its effective set even though the parent holds it in its permitted set.


Data persistence

All data is stored in ./db_data/:

File Description
topology.db SQLite database
secret_key.txt Auto-generated JWT secret (0600 permissions)

Backup: cp -r db_data/ db_data.bak/

Restore: stop the stack, replace db_data/, restart.


Development

Backend tests

cd backend
pip install -r requirements.txt -r requirements-test.txt
pytest tests/ -v

Local dev (without Docker)

# Backend
cd backend
pip install -r requirements.txt
uvicorn main:app --reload

# Frontend (separate terminal)
cd frontend
npm install
npm run dev   # Vite dev server on :5173, proxies /api/ to :8000

Architecture

See docs/architecture.md for the detailed request flow, Docker setup, and authentication model.

S
Description
Self-hosted web application for manual network inventory and logical network topology visualisation.
Readme GPL-3.0 700 KiB
Languages
Vue 53.8%
Python 29.2%
JavaScript 16.5%
Dockerfile 0.3%
HTML 0.2%