Initialize clean CSR inventory repository

This commit is contained in:
DenisToxic
2026-09-29 08:43:05 +02:00
commit 059f386c60
107 changed files with 9639 additions and 0 deletions

29
docs/datenbankschema.md Normal file
View File

@@ -0,0 +1,29 @@
# Geräteschema
`inventory_device` enthält die gemeinsamen Felder: Inventarnummer, Gerätetyp,
Modell, Status, Arbeitsschritt, Datum, Regal, Bearbeiter, Empfänger, Ort,
Ticket, Preis und Notizen. Jeder Gerätedatensatz besitzt genau eine passende
Spezifikation in einer 1:1-Tabelle. Die Anwendung legt diese beim Erstellen
an und entfernt die alte Spezifikation bei einem Typwechsel.
| Gerätetyp | Tabelle | Typische Felder |
| --- | --- | --- |
| Laptop | `inventory_laptopspecification` | CPU, HWID, RAM, SSD, HDD, Bildschirmgröße, Akku, WLAN, LAN, Anschlüsse |
| Desktop | `inventory_desktopspecification` | CPU, HWID, RAM, SSD, HDD, WLAN, LAN, Anschlüsse |
| AiO | `inventory_allinonespecification` | CPU, HWID, RAM, SSD, HDD, Bildschirmgröße, WLAN, LAN, Anschlüsse |
| Bildschirm/Monitor | `inventory_monitorspecification` | Bildschirmgröße, Auflösung, Anschlüsse |
| Drucker/Printer | `inventory_printerspecification` | WLAN, LAN, Anschlüsse, Druckverfahren, Duplex, Farbdruck |
| Frei definierter Computertyp | `inventory_computerspecification` | Allgemeine Computerhardware als Fallback |
Die Werte für `Anschlüsse` sind Freitext, etwa `2× HDMI, DisplayPort,
USB-C`; damit sind auch unbekannte Anschlussarten erfassbar. Gerätetypen
werden derzeit über ihre Bezeichnung zugeordnet. Weitere frei definierte
Typen verwenden die allgemeine Computertabelle, bis ein eigenes Schema
ergänzt wird.
Inspektionsbefunde und Reparaturarbeiten stehen unabhängig davon in
`inventory_inspectionrecord` und `inventory_repairrecord`. Aufgaben liegen
in `inventory_devicetask`, dokumentierte Arbeitsschritte in
`inventory_workflowevent`. Migration 0012 verschiebt vorhandene Laptop-,
Desktop- und AiO-Werte aus der allgemeinen Computertabelle. Bestehende
Inventarnummern, Aufgaben und Benutzer bleiben erhalten.

11
docs/homelab-root-ca.crt Normal file
View File

@@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBojCCAUmgAwIBAgIQUS4ubv+VPmHfsOZnHA55kDAKBggqhkjOPQQDAjAwMS4w
LAYDVQQDEyVDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSAyMDI2IEVDQyBSb290MB4X
DTI2MDkyMzE3MjEyMVoXDTM2MDgwMTE3MjEyMVowMDEuMCwGA1UEAxMlQ2FkZHkg
TG9jYWwgQXV0aG9yaXR5IC0gMjAyNiBFQ0MgUm9vdDBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABNfvDgBuLlx110lhzZllEhvq9OJ9y/BxFUJnR8RK5idzm1R9H/U/
PlszTxrP5QkxaJgBgjecOJS+19ziBEYdBXyjRTBDMA4GA1UdDwEB/wQEAwIBBjAS
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSUYvaXe7IkfVqEf49jgl5npdiw
BTAKBggqhkjOPQQDAgNHADBEAiBjTG79jTLkoYy5yhXPPbegqDlpt0+sWrp71ZZc
f/hkXgIgLG0JFVISRSCzm+bM5DM/iAwX4nyNrGKoVGuRaNtlD6w=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,113 @@
# SQLite → PostgreSQL mit Docker
Die Anwendung verwendet standardmäßig PostgreSQL. Es gibt keinen automatischen
Rückfall auf SQLite. Die folgenden Befehle sind vorbereitet; sie wurden auf dem
aktuellen Windows-Rechner ohne Docker nicht ausgeführt.
## Vorbereiten
Docker mit Compose muss verfügbar sein. Die Befehle im Projektverzeichnis ausführen.
Die bisherige Anwendung während Export, Import und Prüfung stoppen, damit keine
Änderungen nach dem Export verloren gehen. `db.sqlite3` und das Verzeichnis `media`
behalten und zusätzlich extern sichern. Niemals `docker compose down -v` ausführen,
wenn Volumes noch benötigte Daten enthalten.
`.env.example` nach `.env` kopieren und mindestens `SECRET_KEY`,
`POSTGRES_PASSWORD`, Domain, erlaubte Hosts und CSRF-Origin ersetzen.
`POSTGRES_HOST=db` beibehalten. Keine Zugangsdaten in Git ablegen.
```sh
docker compose config --quiet
docker compose --profile tools build web migration
docker compose stop web traefik
docker compose up -d --wait db
```
Die Zieldatenbank muss leer sein. Vor dem Import den Webdienst noch **nicht**
starten: Sein Startskript legt sonst bereits Seed-Daten an. Das Importkommando
verweigert einen Import, sobald Benutzer, Gruppen oder Inventardaten vorhanden sind.
Die zwei von Migration 0003 angelegten Standard-Gerätetypen werden als leere
Ausgangsbasis erkannt und innerhalb der Importtransaktion durch die Exportdaten
ersetzt. Ein vorhandenes PostgreSQL-System niemals zum Zweck des Imports leeren; stattdessen
eine neue Datenbank bzw. ein separates Compose-Projekt verwenden.
## Exportieren, Schema anlegen, importieren
```sh
docker compose run --rm migration scripts/export_sqlite.py /legacy/db.sqlite3 /transfer/sqlite-export.json
docker compose run --rm migration manage.py migrate --noinput
docker compose run --rm migration manage.py import_sqlite_export /transfer/sqlite-export.json
```
Der Export liest SQLite über eine schreibgeschützte Verbindung, erstellt einen
konsistenten temporären Snapshot und aktualisiert nur dessen Schema. Die originale
SQLite-Datei bleibt unverändert. Existiert die Exportdatei bereits, wird sie nicht
überschrieben; für einen weiteren Export einen neuen Dateinamen verwenden.
Der Export enthält Benutzer und Passwort-Hashes, Rollen, API-Schlüssel-Hashes,
Geräte, Archivstatus, Zusatzfelder, Einstellungen, Nummernfolge und Historie.
Content Types und Standardberechtigungen werden durch Migrationen angelegt und
über natürliche Schlüssel zugeordnet. Sitzungen werden nicht übernommen; Benutzer
müssen sich erneut anmelden. Der Import läuft transaktional und setzt die
PostgreSQL-ID-Sequenzen durch Djangos `loaddata` zurück. Die Gerätenummernfolge
wird mindestens auf die höchste vorhandene Gerätenummer gesetzt.
`backups/sqlite-export.json` enthält vertrauliche Daten und ist kein öffentliches
Download-Artefakt. Der Export setzt auf Linux Dateirechte `0600`. Der offline
gestartete Migrationscontainer läuft für den Zugriff auf das Backup-Verzeichnis
als root; der Webcontainer läuft weiterhin als unprivilegierter `app`-Benutzer.
## Uploads übernehmen
Der Datenbankexport enthält Dateiverweise, nicht die Dateien selbst. Bei einem
**neuen, leeren** `media_data`-Volume die bisherigen Uploads kopieren:
```sh
docker compose run --rm --entrypoint sh migration -c 'cp -a /legacy/media/. /app/media/ && chown -R app:app /app/media'
```
Ein bereits verwendetes Medienvolume zuerst sichern und seine Inhalte separat
abgleichen; der Kopierbefehl darf nicht ungeprüft über vorhandene Dateien laufen.
## Prüfen und starten
```sh
docker compose run --rm migration manage.py check
docker compose run --rm migration manage.py makemigrations --check --dry-run
docker compose run --rm -e DEBUG=true -e SECURE_SSL_REDIRECT=false migration manage.py test --noinput
docker compose up -d --build web traefik
docker compose ps
```
Die Tests verwenden eine separate PostgreSQL-Testdatenbank. Danach Anmeldung,
Geräteanzahl einschließlich Archiv, höchste Gerätenummer, Rollen, Historie und
Upload-Verweise mit dem bisherigen System vergleichen. Ein PostgreSQL-Backup
anlegen und testweise in eine separate leere Datenbank wiederherstellen:
```sh
sh scripts/backup.sh
```
Erst nach erfolgreicher Prüfung das alte System endgültig stilllegen. Für einen
Rückwechsel den neuen Webdienst stoppen und die alte Anwendung mit ihrer
unveränderten SQLite-Datei starten. Neue PostgreSQL-Änderungen werden nicht
automatisch zurück nach SQLite übertragen.
## Lokaler Docker-Betrieb ohne HTTPS-Domain
Nach dem Import alternativ nur Datenbank und Webdienst starten:
```sh
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build db web
```
Die Anwendung ist dann unter `http://localhost:8000` erreichbar. Der Port ist nur
an localhost gebunden. Diese Entwicklungsüberschreibung nicht auf dem VPS nutzen.
## Bearbeitungsversionen
Geräte besitzen nun eine ganzzahlige `revision`. Browser-Formulare senden
`expected_revision`; API-PATCH-Anfragen können ebenfalls dieses Feld verwenden.
Bestehende API-Clients mit `expected_updated_at` bleiben kompatibel. Veraltete
Versionen werden abgewiesen. Zeitstempel steigen auch bei geringer Auflösung der
Systemuhr strikt an. Migration `0006` setzt bestehende Geräte auf Revision 1.

160
docs/serverbetrieb.md Normal file
View File

@@ -0,0 +1,160 @@
# Serverbetrieb auf 192.168.188.45
Stand: 26. September 2026. Die Anwendung wurde auf dem Debian-Server `homelab`
installiert und gegen PostgreSQL geprüft.
## Zugriff
- Anwendung: **https://192.168.188.45/**
- Anmeldung: https://192.168.188.45/accounts/login/
- Der bisherige Port `http://192.168.188.45:8001/` leitet auf HTTPS weiter.
- Die Benutzerkonten und Passwort-Hashes aus der lokalen Projektdatenbank wurden übernommen. Bestehende Anmeldedaten bleiben gültig.
- Installationsverzeichnis: `/home/denis/csr-inventory`
Die Anwendung läuft mit deaktiviertem Debugmodus, sicheren Cookies und einer
separaten PostgreSQL-Datenbank. Nur Traefik veröffentlicht Webports; PostgreSQL und
Gunicorn sind nicht direkt über veröffentlichte Docker-Ports erreichbar.
## HTTPS im lokalen Netzwerk
Traefik verwendet für die private IP-Adresse eine lokale Zertifizierungsstelle.
Das öffentliche Stammzertifikat liegt in [homelab-root-ca.crt](homelab-root-ca.crt)
und auf dem Server unter `/home/denis/csr-inventory-root-ca.crt`.
Damit Windows-Browser dem Zertifikat vertrauen, kann es für den aktuellen Benutzer
importiert werden. Dieser Schritt wurde auf dem Arbeitsplatz **nicht** automatisch
ausgeführt. Im lokalen Projektverzeichnis:
```powershell
Import-Certificate -FilePath .\docs\homelab-root-ca.crt -CertStoreLocation Cert:\CurrentUser\Root
```
SHA-256-Fingerabdruck des Stammzertifikats:
```text
7B:15:0B:52:62:81:68:3D:93:AA:CE:96:63:FD:23:9E:37:E2:AE:F3:6C:F3:E7:AB:E4:91:A6:F7:F9:8F:ED:05
```
Die Serverdatei `docker-compose.override.yml` entspricht `docker-compose.lan.yml`
und veröffentlicht zusätzlich den Weiterleitungsport 8001. Traefik verwendet
den File-Provider ohne Docker-Socket und ohne veröffentlichtes Dashboard.
Statische Dateien liefert Django über WhiteNoise aus.
Die bisherige Stamm-CA bleibt erhalten. Ihr privater Schlüssel liegt ausschließlich
auf dem Host unter `tls/ca/root.key`. Traefik erhält nur die Serverzertifikate.
Der Timer `csr-inventory-tls.timer` prüft täglich um 01:30 Uhr die Gültigkeit und
erneuert 90-Tage-Zertifikate bei weniger als 30 Tagen Restlaufzeit. Traefik lädt
neue Zertifikate automatisch. Einrichtung und Wartung: [Traefik](traefik.md).
## Verwaltung
Der SSH-Benutzer `denis` hat keine direkten Docker- oder sudo-Rechte. Zur Verwaltung
wie bei der Installation eine Root-Sitzung öffnen:
```sh
ssh denis@192.168.188.45
su -
cd /home/denis/csr-inventory
docker compose ps
docker compose logs --tail 100 web traefik
```
Nach Änderungen am Anwendungscode neu bauen und starten:
```sh
sh scripts/backup.sh
docker compose up -d --build --wait web traefik
```
Die individuellen Anwendungs- und Datenbankgeheimnisse stehen ausschließlich in
der geschützten `.env` auf dem Server. Bei Aktualisierungen diese Datei, die
Serverüberschreibung und die persistenten Volumes beibehalten.
## Datenbankzugriff mit DBeaver
PostgreSQL wird nur auf `127.0.0.1:15432` des Servers veröffentlicht.
Für DBeaver einen PostgreSQL-Anschluss mit **Host** `127.0.0.1`, **Port**
`15432`, **Datenbank** `csr_inventory` und **Benutzer** `csr_inventory`
anlegen. Im Reiter **SSH** einen Tunnel zu `192.168.188.45:22` mit dem
SSH-Benutzer `denis` einrichten. Die Datenbank bleibt über die LAN-Adresse
auf Port 15432 unerreichbar.
Das Datenbankpasswort ist `POSTGRES_PASSWORD` in der geschützten `.env`.
Als Root auf dem Server kann es mit
`sed -n 's/^POSTGRES_PASSWORD=//p' /home/denis/csr-inventory/.env`
für die Eingabe in DBeaver angezeigt werden. Das SSH-Passwort ist getrennt
vom Datenbankpasswort. In DBeaver die Verbindung über die Host-Felder
konfigurieren, nicht über eine fest eingetragene JDBC-URL.
## Datensicherung
- PostgreSQL-Sicherungen: `/home/denis/csr-inventory/backups/`
- Automatische Sicherung: täglich um **02:00 Uhr Europe/Berlin**.
- Timer: `csr-inventory-backup.timer`
- Dienst: `csr-inventory-backup.service`
- Der tägliche Job sichert die Datenbank. Upload-Dateien liegen im persistenten Media-Volume. Die TLS-Zertifizierungsstelle und Serverzertifikate liegen im Projektverzeichnis `tls/`. Diese Daten und die geschützte `.env` müssen separat gesichert werden.
```sh
systemctl list-timers csr-inventory-backup.timer
journalctl -u csr-inventory-backup.service --no-pager
sh scripts/backup.sh
```
Eine erste PostgreSQL-Sicherung wurde erfolgreich in eine separate temporäre
Datenbank zurückgespielt und die Geräteanzahl verglichen. Die Prüfdatenbank wurde
anschließend entfernt.
## Ablösung der bisherigen Inventarsoftware
Die Container `computerspende-inventar-web-1` und
`computerspende-inventar-db-1` sowie deren Compose-Netzwerk wurden entfernt.
Die anderen Anwendungen auf dem Server laufen weiter.
Vor der Entfernung wurden Datenbank, Anwendungskonfiguration und Uploads unter
`/root/csr-retired-inventory-20260923/` gesichert. Die alten Docker-Volumes und das
bisherige Quellverzeichnis wurden für eine mögliche Wiederherstellung erhalten.
Die Daten dieser anderen Anwendung wurden nicht mit der neuen Datenbank vermischt.
## Durchgeführte Prüfungen
- Alle **53 Tests mit PostgreSQL erfolgreich**, einschließlich gleichzeitiger Bearbeitungen.
- Django-Produktionsprüfung ohne Beanstandungen.
- 109 exportierte Datensätze übernommen, darunter zwei Geräte und die vorhandenen Benutzerkonten.
- HTTPS-Zertifikatsprüfung, Healthcheck, Loginseite und statische Dateien vom Server und vom Arbeitsplatz geprüft.
- API-Zugriff ohne Schlüssel wird mit HTTP 401 abgewiesen.
- Weiterleitung vom bisherigen Port 8001 geprüft.
- Datenbank-Backup und Wiederherstellung in eine separate Prüfdatenbank erfolgreich.
## Datenbank- und UI-Update vom 26. September 2026
Vor Migration 0009/0010 wurde eine neue PostgreSQL-Sicherung erstellt.
61 Tests bestanden mit PostgreSQL 17. Die drei vorhandenen Geräte blieben
erhalten und besitzen jetzt jeweils einen Datensatz in
`inventory_computerspecification`. Die alten Hardware-Spalten in
`inventory_device` wurden entfernt. Beide Migrationen sind angewendet;
der Web-Container ist gesund. Der neue Ablauf unter `/ablauf/` antwortet
für angemeldete Benutzer erfolgreich. Healthcheck, Login, CSS und API-Schutz
wurden nach dem Neustart geprüft.
## Arbeitsschritt-Formulare vom 26. September 2026
Vor Migration 0011 wurde erneut eine PostgreSQL-Sicherung erstellt.
Die Migration ergänzt getrennte Tabellen für Inspektionsbefunde und
Reparaturarbeiten. Im Ablauf-Board können Bearbeiter während der
Inspektion Mängel und technische Daten, während der Reparatur die Arbeiten
und während der Lagerung den Empfänger eintragen. 63 Tests bestanden auf
PostgreSQL 17. Nach dem Neustart waren alle drei vorhandenen Geräte
erhalten; das Board antwortete angemeldet mit HTTP 200 und der Web-Container
war gesund. Healthcheck, Loginseite und CSS antworteten ebenfalls mit 200.
## Gerätetyp-Schema vom 28. September 2026
Vor Migration 0012 wurde eine PostgreSQL-Sicherung erstellt. 64 Tests
bestanden mit PostgreSQL 17. Die Migration verschob die vier vorhandenen
Geräte in drei Laptop-Spezifikationen und eine Desktop-Spezifikation;
die allgemeine Computertabelle enthält danach keine dieser Geräte mehr.
Die Tabellen für AiO, Monitor und Drucker sind vorhanden. Geräteliste,
Ablauf, Übersicht, Detailseite, Healthcheck und Loginseite wurden nach dem
Neustart erfolgreich geprüft. Das Ablauf-Board antwortete angemeldet in
etwa 0,3 Sekunden. Das Schema ist unter [Datenbankschema](datenbankschema.md)
beschrieben.

58
docs/traefik.md Normal file
View File

@@ -0,0 +1,58 @@
# Traefik-Betrieb
Traefik v3.7.13 ersetzt Caddy. Die Routerdefinition liegt unter
`traefik/dynamic/routes.yml`; ein Docker-Socket ist nicht erforderlich.
Nur HTTP/HTTPS werden veröffentlicht, das Dashboard ist deaktiviert.
WhiteNoise liefert statische Dateien aus, Uploads bleiben geschützt.
## Öffentliche Domain
In `.env` `APP_HOST` ohne Protokoll oder Port und `ACME_EMAIL` setzen.
DNS muss auf den Server zeigen; TCP 80 und 443 müssen erreichbar sein.
`docker compose up -d --build --wait` aktiviert HTTPS über Let's Encrypt.
ACME-Zustand liegt im Volume `traefik_acme` und muss gesichert werden.
## Privates LAN
Auf Linux mit Python 3 und OpenSSL, im Projektverzeichnis als Administrator:
```sh
# APP_HOST in .env auf die LAN-IP setzen.
cp docker-compose.lan.yml docker-compose.override.yml
python3 scripts/renew_local_tls.py --hostname 192.168.188.45
docker compose up -d --build --wait
install -m 644 deploy/csr-inventory-tls.service deploy/csr-inventory-tls.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now csr-inventory-tls.timer
systemctl start csr-inventory-tls.service
```
Die mitgelieferten Units setzen `/home/denis/csr-inventory` voraus; bei anderen
Installationspfaden beide Pfade im Dienst anpassen. Der LAN-Override deaktiviert
ACME und ergänzt Port 8001 als HTTPS-Weiterleitung.
Ohne vorhandene CA erzeugt das Skript eine neue. Zur Übernahme einer bestehenden
CA vor dem ersten Lauf deren `root.crt` und `root.key` nach `tls/ca/` kopieren.
Der CA-Schlüssel bleibt auf dem Host und darf nicht verteilt werden. Nur das
öffentliche Stammzertifikat wird in den Vertrauensspeicher der Clients importiert.
Auf dem Server 192.168.188.45 wird die bisherige Stamm-CA weiterverwendet.
Der Timer prüft täglich um 01:30 Europe/Berlin. Zertifikate gelten 90 Tage und
werden bei höchstens 30 Tagen Restlaufzeit erneuert. Ein Standardzertifikat
ermöglicht auch den IP-Zugriff ohne SNI. Die Dateikonfiguration wird atomar
ersetzt; Traefik lädt sie ohne Neustart. Frühere Zertifikate bleiben erhalten.
```sh
systemctl list-timers csr-inventory-tls.timer
journalctl -u csr-inventory-tls.service --no-pager
docker compose logs --tail 100 traefik
# Erneuerung bei Bedarf erzwingen:
python3 scripts/renew_local_tls.py --hostname 192.168.188.45 --force
```
`tls/`, `.env`, Uploads und Datenbank separat geschützt sichern. Der tägliche
Datenbankjob umfasst keine TLS-Schlüssel. `tls/` und die erzeugte `tls.yml`
sind vom Git-Repository und Docker-Build ausgeschlossen.
Referenzen: [File-Provider](https://doc.traefik.io/traefik/providers/file/),
[TLS-Dateikonfiguration](https://doc.traefik.io/traefik/reference/routing-configuration/other-providers/file/).