# Stupid Simple Network Inventory **Self-hosted network inventory and logical topology visualisation** [FranΓ§ais](README.fr.md) Β· [EspaΓ±ol](README.es.md) ![Python](https://img.shields.io/badge/Python-3.11-3776AB?logo=python&logoColor=white) ![FastAPI](https://img.shields.io/badge/FastAPI-009688?logo=fastapi&logoColor=white) ![Vue.js](https://img.shields.io/badge/Vue.js-3-4FC08D?logo=vue.js&logoColor=white) ![SQLite](https://img.shields.io/badge/SQLite-003B57?logo=sqlite&logoColor=white) ![Nginx](https://img.shields.io/badge/Nginx-009639?logo=nginx&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=white) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)
--- ![Topology view](img/tpology.png) --- ## ✨ 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 ```bash # 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` / `` | 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: ```bash 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/`. | ```bash 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. #### Option A β€” Auto-generated secret (recommended for single-node) 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. ```bash # .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. ```bash # 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: ```bash docker compose up -d ``` ### Key rotation To rotate the JWT secret (invalidates all active sessions): ```bash # 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 ```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: ```yaml # 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: ```yaml # /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: ```yaml # 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` | | Network | Ubiquiti / UniFi | `ubiquiti`, `unifi`, `usg`, `udm` | | Network | MikroTik | `mikrotik`, `routeros` | | Network | Cisco | `cisco` | | Network | TP-Link | `tp-link`, `tplink` | | 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` | | 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` | | Database | MariaDB | `mariadb` | | 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`, `rpi`, `raspi` | | SBC / DIY | Arduino | `arduino` | | Desktop | KDE / Plasma | `kde`, `plasma` | | Tools | Excalidraw | `excalidraw` | | Self-hosted | Nextcloud | `nextcloud` | | Self-hosted | Paperless-NGX | `paperless`, `paperless-ngx` | | Self-hosted | Uptime Kuma | `uptime-kuma`, `uptime kuma` | | Self-hosted | MkDocs | `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` | | 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` | | Media / home automation | Philips Hue | `philips hue`, `hue bridge` | | Media / home automation | Xiaomi | `xiaomi`, `mi home`, `yeelight` | --- ## πŸ§‘β€πŸ’» Development ### Backend tests ```bash cd backend pip install -r requirements.txt -r requirements-test.txt pytest tests/ -v ``` ### Local dev (without Docker) ```bash # 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`](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](https://claude.ai/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](https://openai.com/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](LICENSE). 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.