Files
olivier 6ae7fac766 feat: add Free, Bouygues Telecom and SFR brand icons
None of the three are in simple-icons — custom wifi icons with
official brand colors (Free #CD1126, Bouygues #0099CC, SFR #E2001A).
Keywords avoid the bare word "free" to prevent false positives on
freenas/freebsd/etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 13:43:55 +02:00

14 KiB

Stupid Simple Network Inventory

Self-hosted network inventory and logical topology visualisation

Français · Español

Python FastAPI Vue.js SQLite Nginx Docker License: GPL v3


Topology view


Features

  • 🗂️ Manual inventory — add and manage devices (21 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
  • 🏷️ Brand logos — automatic detection and display of vendor logos (Proxmox, Cisco, Synology, Docker, 30+ more)
  • 🔐 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

🔄 Updating

Data is stored in ./db_data/ (bind-mount), which is never touched by container rebuilds. Updating is safe:

git pull
docker compose up --build -d

Database migrations run automatically on startup — no manual steps required.


⚙️ 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.
BIND_ADDRESS 0.0.0.0 IP address to listen on. Set to the interface facing the reverse proxy.
DOCKER_UID / DOCKER_GID 1000 UID/GID for the backend process. Must match the host user owning ./db_data/.
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.

Nginx

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;
    }
}

Traefik — Docker labels

Add these labels to the frontend service and connect it to the network shared with Traefik:

# docker-compose.override.yml
services:
  frontend:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.inventory.rule=Host(`inventory.example.com`)"
      - "traefik.http.routers.inventory.entrypoints=websecure"
      - "traefik.http.routers.inventory.tls.certresolver=letsencrypt"
      - "traefik.http.services.inventory.loadbalancer.server.port=8080"
    networks:
      - internal
      - traefik_public  # network shared with your Traefik instance

networks:
  traefik_public:
    external: true

Traefik — Dynamic configuration (file provider)

If Traefik is not running in Docker (or you prefer the file provider), drop a file in your dynamic config directory:

# /etc/traefik/dynamic/inventory.yml
http:
  routers:
    inventory:
      rule: "Host(`inventory.example.com`)"
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
      service: inventory-svc

  services:
    inventory-svc:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8080"

Traefik picks up the file automatically — no restart required.

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

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.


🏷️ Brand logo detection

Logo detection runs automatically against the device name and description fields (case-insensitive keyword matching). No manual configuration needed — just include a recognisable keyword in the name or description.

Logos are displayed in two places:

  • Topology view — small inline SVGs next to the device name on each chip
  • Device list — coloured badge(s) in the device card

Multiple logos can appear simultaneously if several keywords match.

Category Brand Trigger keywords
Virtualisation Proxmox proxmox, pve
Virtualisation Docker docker
NAS Synology synology, dsm
NAS TrueNAS truenas, freenas
French ISP Orange orange, sosh, livebox
French ISP OVH ovh, ovhcloud, kimsufi, soyoustart
French ISP Free freebox, free mobile, free telecom, iliad
French ISP Bouygues Telecom bouygues, bbox
French ISP SFR sfr, red by sfr, sfr box
Network Ubiquiti / UniFi ubiquiti, unifi, usg, udm
Network MikroTik mikrotik, routeros
Network Cisco cisco
Network TP-Link tp-link, tplink, tp link
Network ASUS asus
Network Netgear netgear
Network pfSense pfsense
Network OPNsense opnsense
Network OpenWrt openwrt
Web / proxy Apache apache, apache2, httpd
Web / proxy Traefik traefik
Web / proxy Apache Guacamole guacamole
Bastion Bastion / jump host bastion, jumphost, jump host, jump server, teleport, bastillion
Auth / SSO Authelia authelia
Auth / SSO Keycloak keycloak
Auth / SSO Authentik authentik
Auth / SSO Okta okta
Auth / SSO Auth0 auth0
Password vault Vaultwarden vaultwarden
Password vault Bitwarden bitwarden
Password vault 1Password 1password, onepassword
Password vault KeePassXC keepass, keepassxc
Password vault HashiCorp Vault hashicorp vault, hashicorp
Archiving Archive server archive, archiver, archivage, archivar, archivebox
Mail Mail server mail, smtp, imap, postfix, dovecot, mailcow, mailu, roundcube
Database MariaDB mariadb, maria db
Orchestration Kubernetes kubernetes, k8s, kubectl, k3s
OS Debian debian
OS Ubuntu ubuntu
Automation Ansible ansible
Servers Dell dell, idrac, poweredge
Servers HP proliant, ilo, hewlett
SBC / DIY Raspberry Pi raspberry, raspberrypi, rpi, raspi
SBC / DIY Arduino arduino
Desktop KDE / Plasma kde, plasma, kde desktop
Tools Excalidraw excalidraw
Self-hosted Nextcloud nextcloud
Self-hosted Paperless-NGX paperless, paperless-ng, paperless-ngx
Self-hosted Uptime Kuma uptime-kuma, uptimekuma, uptime kuma
Self-hosted MkDocs mkdocs, material for mkdocs
CMS / Blog WordPress wordpress
CMS / Blog Ghost ghost
CMS / Blog Grav grav
CMS / Blog Jekyll jekyll
CMS / Blog Hugo hugo
CMS / Blog Hexo hexo
CMS / Blog Drupal drupal
CMS / Blog Joomla joomla
CMS / Blog TYPO3 typo3
CMS / Blog OctoberCMS octobercms, october cms
CMS / Blog Textpattern textpattern
Analytics Matomo matomo
Analytics Plausible plausible
Smart TV Samsung samsung, tizen, samsung tv
Smart TV LG lg, webos, lg tv
Smart TV Sony sony, bravia
Smart TV Panasonic panasonic
Smart TV Sharp sharp
Smart TV Toshiba toshiba
Smart TV Vestel vestel
TV Box Chromecast / Google TV chromecast, google tv
TV Box Android TV android tv, androidtv
TV Box Apple TV apple tv, appletv
TV Box Amazon Fire TV fire tv, firetv, amazon fire
TV Box Roku roku
TV Box Kodi kodi
Media / torrent Radarr radarr
Media / torrent Sonarr sonarr
Media / torrent Transmission transmission
Media / home automation Jellyfin jellyfin
Media / home automation Home Assistant homeassistant, home assistant, hassio, hass
Media / home automation Philips Hue philips hue, hue bridge, hue hub
Media / home automation Xiaomi xiaomi, mi home, yeelight

🧑‍💻 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.


🤖 Built with AI assistance

This application was developed entirely with the help of AI coding assistants:

  • Claude Code (Anthropic) — used throughout the project for architecture design, backend API (FastAPI, SQLAlchemy, authentication, ICMP discovery), frontend components (Vue 3, CSS topology layout, i18n, dark mode), Docker & Nginx configuration, and documentation.
  • Codex (OpenAI) — also used during development for code generation and suggestions across the entire codebase.

📄 License

This project is licensed under the GNU General Public License v3.0.

You are free to use, modify and distribute this software under the terms of the GPL v3. Any derivative work must be distributed under the same license.