# Stupid Simple Network Inventory **Aplicación web autoalojada para inventario de red y visualización de topología lógica** [English](README.md) · [Français](README.fr.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)
--- ![Vista de Topología](img/tpology.png) --- ## ✨ 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 ```bash # 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` / `` | Inicio de sesión normal | | `INITIAL_ADMIN_PASSWORD` no definido | `admin` / `admin` | Cambio de contraseña obligatorio antes de acceder a la aplicación | --- ## 🔄 Actualización Los datos se almacenan en `./db_data/` (bind-mount), que nunca es modificado al reconstruir los contenedores. La actualización es segura: ```bash git pull docker compose up --build -d ``` Las migraciones de base de datos se ejecutan automáticamente al iniciar — no se requieren pasos manuales. --- ## ⚙️ 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/`. | ```bash 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. ```bash # .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. ```bash # 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: ```bash docker compose up -d ``` ### Rotación de clave Para rotar el secreto JWT (invalida todas las sesiones activas): ```bash # 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 ```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: ```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 # 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: ```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 detecta el archivo automáticamente — no se requiere reinicio. Para uso solo local, enlazar a loopback para evitar exposición accidental en la LAN: ```yaml # 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. --- ## 🏷️ Detección de logos de marcas La detección se realiza automáticamente sobre el **nombre** y la **descripción** del equipo (coincidencia de palabras clave, sin distinción de mayúsculas). No se requiere configuración manual — basta con incluir una palabra clave reconocible en el nombre o la descripción. Los logos se muestran en dos lugares: - **Vista de topología** — pequeños SVGs en línea junto al nombre del equipo en cada chip - **Lista de equipos** — badge(s) de color en la card del equipo Pueden aparecer varios logos simultáneamente si coinciden varias palabras clave. | Categoría | Marca | Palabras clave | |-----------|-------|----------------| | Virtualización | Proxmox | `proxmox`, `pve` | | Virtualización | Docker | `docker` | | NAS | Synology | `synology`, `dsm` | | NAS | TrueNAS | `truenas`, `freenas` | | Red | Ubiquiti / UniFi | `ubiquiti`, `unifi`, `usg`, `udm` | | Red | MikroTik | `mikrotik`, `routeros` | | Red | Cisco | `cisco` | | Red | TP-Link | `tp-link`, `tplink` | | Red | ASUS | `asus` | | Red | Netgear | `netgear` | | Red | pfSense | `pfsense` | | Red | OPNsense | `opnsense` | | Red | OpenWrt | `openwrt` | | Web / proxy | Apache | `apache`, `apache2`, `httpd` | | Web / proxy | Traefik | `traefik` | | Base de datos | MariaDB | `mariadb` | | Orquestación | Kubernetes | `kubernetes`, `k8s`, `kubectl`, `k3s` | | OS | Debian | `debian` | | OS | Ubuntu | `ubuntu` | | Automatización | Ansible | `ansible` | | Servidores | Dell | `dell`, `idrac`, `poweredge` | | Servidores | HP | `proliant`, `ilo`, `hewlett` | | SBC / DIY | Raspberry Pi | `raspberry`, `rpi`, `raspi` | | SBC / DIY | Arduino | `arduino` | | Escritorio | KDE / Plasma | `kde`, `plasma` | | Herramientas | 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` | | Medios / domótica | Jellyfin | `jellyfin` | | Medios / domótica | Home Assistant | `homeassistant`, `home assistant`, `hassio` | | Medios / domótica | Philips Hue | `philips hue`, `hue bridge` | | Medios / domótica | Xiaomi | `xiaomi`, `mi home`, `yeelight` | --- ## 🧑‍💻 Desarrollo ### Tests del backend ```bash cd backend pip install -r requirements.txt -r requirements-test.txt pytest tests/ -v ``` ### Dev local (sin Docker) ```bash # 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`](docs/architecture.md) para el flujo de solicitudes detallado, la configuración Docker y el modelo de autenticación. --- ## 🤖 Desarrollado con asistencia de IA Esta aplicación fue desarrollada íntegramente con la ayuda de asistentes de código IA: - **[Claude Code](https://claude.ai/code)** (Anthropic) — utilizado a lo largo del proyecto para el diseño de la arquitectura, la API backend (FastAPI, SQLAlchemy, autenticación, descubrimiento ICMP), los componentes frontend (Vue 3, layout CSS de topología, i18n, modo oscuro), la configuración Docker & Nginx, y la documentación. - **[Codex](https://openai.com/codex)** (OpenAI) — también utilizado durante el desarrollo para la generación de código y sugerencias en todo el proyecto. --- ## 📄 Licencia Este proyecto se distribuye bajo la [Licencia Pública General GNU v3.0](LICENSE). Eres libre de usar, modificar y distribuir este software bajo los términos de la GPL v3. Cualquier trabajo derivado debe distribuirse bajo la misma licencia.