6ae7fac766
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>
416 lines
14 KiB
Markdown
416 lines
14 KiB
Markdown
<div align="center">
|
|
|
|
<img src="frontend/public/favicon.svg" width="96" height="96" alt="" />
|
|
|
|
# Stupid Simple Network Inventory
|
|
|
|
**Self-hosted network inventory and logical topology visualisation**
|
|
|
|
[Français](README.fr.md) · [Español](README.es.md)
|
|
|
|

|
|

|
|

|
|

|
|

|
|

|
|
[](LICENSE)
|
|
|
|
</div>
|
|
|
|
---
|
|
|
|

|
|
|
|
---
|
|
|
|
## ✨ 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` / `<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:
|
|
|
|
```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` |
|
|
| 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
|
|
|
|
```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.
|