Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker. Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
# Stupid Simple Network Inventory
|
||||
|
||||
Self-hosted web application for manual network inventory and logical network topology visualisation.
|
||||
|
||||
## 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 <repo> && cd topologie
|
||||
|
||||
# 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
|
||||
# Set DOCKER_UID / DOCKER_GID to match your host user:
|
||||
# id -u && id -g
|
||||
# Set INITIAL_ADMIN_PASSWORD to avoid the admin/admin bootstrap.
|
||||
|
||||
# 4. Build and start
|
||||
docker compose --env-file .env up --build -d
|
||||
|
||||
# 5. Open the app
|
||||
open http://localhost:8080
|
||||
```
|
||||
|
||||
### 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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. |
|
||||
| `DOCKER_UID` / `DOCKER_GID` | `1000` | UID/GID for the backend process. Must match the host user owning `./db_data/`. |
|
||||
|
||||
### Using .env with Docker Compose
|
||||
|
||||
```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
|
||||
# Example nginx reverse-proxy (external, on the host or a dedicated container)
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
The containers run with reduced privileges:
|
||||
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user