Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.1 KiB
Stupid Simple Network Inventory
Aplicación web autoalojada para inventario de red y visualización de topología lógica
✨ Características
- 🗂️ Inventario manual — añade y gestiona dispositivos (18 tipos) con IPs, VLANs, descripciones y enlaces web opcionales
- 🗺️ Vista de topología — disposición en tarjetas por red (LAN / VLAN 802.1Q), con secciones WAN y puerta de enlace
- 📡 Ping ICMP — comprueba la accesibilidad de todos los hosts conocidos con un clic
- 🔍 Descubrimiento automático — ping sweep + consulta PTR DNS en una subred para importar nuevos hosts
- 🏷️ Logos de fabricantes — detección y visualización automáticas de logos de editores/fabricantes (Proxmox, Cisco, Synology, Docker, y más de 30 más)
- 🔐 Autenticación — inicio de sesión JWT con cambio de contraseña obligatorio en el primer uso
- 🌙 Modo oscuro — alternancia entre tema claro y oscuro
- 🌍 i18n — francés, inglés, español
🛠️ Stack
| Capa | Tecnología |
|---|---|
| Backend | FastAPI + SQLAlchemy + SQLite (Python 3.11) |
| Frontend | Vue 3 + Vite, servido por Nginx |
| Auth | JWT HS256, expiración 24h |
| Runtime | Docker Compose |
🚀 Inicio rápido
# 1. Clonar y entrar en el proyecto
git clone https://git.raspot.in/olivier/stupid-simple-network-inventory.git
cd stupid-simple-network-inventory
# 2. Crear el directorio de datos propiedad del usuario actual
mkdir -p db_data
# 3. Configurar el entorno (necesario para la propiedad correcta del bind-mount)
cp .env.example .env
# Editar .env:
# DOCKER_UID / DOCKER_GID → resultado de: id -u && id -g
# INITIAL_ADMIN_PASSWORD → definir para evitar el bootstrap admin/admin por defecto
# 4. Construir e iniciar
docker compose --env-file .env up --build -d
# 5. Abrir http://localhost:8080 en el navegador
Primer inicio de sesión
| Caso | Credenciales | Comportamiento |
|---|---|---|
INITIAL_ADMIN_PASSWORD definido |
admin / <tu contraseña> |
Inicio de sesión normal |
INITIAL_ADMIN_PASSWORD no definido |
admin / admin |
Cambio de contraseña obligatorio antes de acceder a la aplicación |
⚙️ Configuración
Toda la configuración se realiza mediante variables de entorno. Ver .env.example para la lista completa con descripciones.
| Variable | Por defecto | Descripción |
|---|---|---|
SECRET_KEY |
auto-generada | Clave de firma JWT. Definir explícitamente en producción. |
INITIAL_ADMIN_PASSWORD |
(vacío) | Contraseña de bootstrap del admin. Si no se define, se usa admin/admin con cambio forzado. |
ALLOWED_ORIGINS |
* |
Orígenes CORS permitidos (separados por comas). Definir en tu dominio en producción. |
BIND_ADDRESS |
0.0.0.0 |
Dirección IP de escucha. Definir en la interfaz frente al reverse proxy. |
DOCKER_UID / DOCKER_GID |
1000 |
UID/GID para el proceso backend. Debe coincidir con el usuario propietario de ./db_data/. |
cp .env.example .env
# Editar .env — definir como mínimo DOCKER_UID, DOCKER_GID, INITIAL_ADMIN_PASSWORD
docker compose --env-file .env up --build -d
🔒 Seguridad
Gestión de secretos
Dos opciones según sus requisitos de seguridad.
Opción A — Secreto auto-generado (recomendado para nodo único)
Dejar SECRET_KEY sin definir (o vacío) en .env. En el primer arranque, el backend genera una clave hexadecimal aleatoria de 64 caracteres, la escribe en db_data/secret_key.txt con permisos 0600 y la reutiliza en cada reinicio posterior. El secreto nunca aparece en una variable de entorno, un archivo compose o un log.
# .env — dejar la línea vacía o eliminarla
SECRET_KEY=
El único requisito es que db_data/ esté respaldado (ya contiene la base de datos).
Opción B — Secreto mediante archivo Docker Compose
Almacena el secreto en un archivo en el host, fuera del control de versiones, y lo monta en el contenedor. El valor nunca aparece en una variable de entorno.
# Generar y almacenar el secreto fuera del directorio del proyecto
mkdir -p ~/.secrets
python3 -c "import secrets; print(secrets.token_hex(32))" > ~/.secrets/topologie_secret_key
chmod 600 ~/.secrets/topologie_secret_key
Luego descomentar los bloques secrets: en docker-compose.yml (ver los comentarios en ese archivo) y eliminar SECRET_KEY de .env. Docker Compose fusiona la anulación automáticamente:
docker compose up -d
Rotación de clave
Para rotar el secreto JWT (invalida todas las sesiones activas):
# Opción A — variable de entorno (recomendado)
# Definir un nuevo SECRET_KEY en la configuración de despliegue y reiniciar
# Opción B — rotación por archivo
docker compose stop backend
rm db_data/secret_key.txt
docker compose start backend
# Todos los usuarios deberán iniciar sesión de nuevo
HTTPS
Esta aplicación no termina TLS. Para uso en producción, colócala detrás de un reverse proxy que gestione 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 — Labels Docker
Añadir estas etiquetas al servicio frontend y conectarlo a la red compartida con 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 # red compartida con tu instancia de Traefik
networks:
traefik_public:
external: true
Traefik — Configuración dinámica (file provider)
Si Traefik no corre en Docker (o prefieres el file provider), deposita un archivo en tu directorio de configuración dinámica:
# /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 detecta el archivo automáticamente — no se requiere reinicio.
Para uso solo local, enlazar a loopback para evitar exposición accidental en la LAN:
# docker-compose.override.yml
services:
frontend:
ports:
- "127.0.0.1:8080:8080"
Endurecimiento de contenedores
| Medida | Backend | Frontend |
|---|---|---|
| Usuario no-root | DOCKER_UID:DOCKER_GID (usuario host) |
nginx (UID 101) |
cap_drop: ALL |
✓ | ✓ |
cap_add: NET_RAW |
✓ (ping) | — |
no-new-privileges |
— ¹ | ✓ |
| Healthcheck | ✓ | ✓ |
¹ Omitido en el backend: el ping usa file capabilities (cap_net_raw=ep); no-new-privileges suprimiría el bit efectivo del archivo e impediría al subproceso adquirir CAP_NET_RAW en su conjunto efectivo aunque el padre lo tenga en su conjunto permitido.
💾 Persistencia de datos
Todos los datos se almacenan en ./db_data/:
| Archivo | Descripción |
|---|---|
topology.db |
Base de datos SQLite |
secret_key.txt |
Secreto JWT auto-generado (permisos 0600) |
Copia de seguridad: cp -r db_data/ db_data.bak/
Restauración: detener la stack, reemplazar db_data/, reiniciar.
🧑💻 Desarrollo
Tests del backend
cd backend
pip install -r requirements.txt -r requirements-test.txt
pytest tests/ -v
Dev local (sin Docker)
# Backend
cd backend
pip install -r requirements.txt
uvicorn main:app --reload
# Frontend (terminal separada)
cd frontend
npm install
npm run dev # Servidor dev Vite en :5173, proxifica /api/ hacia :8000
🏗️ Arquitectura
Ver docs/architecture.md para el flujo de solicitudes detallado, la configuración Docker y el modelo de autenticación.
