# Betrieb

Dieses Dokument richtet sich an die Person, die die Anlage installiert,
aktualisiert und im Fehlerfall wieder zum Laufen bringt. Es beschreibt, was auf
dem Host tatsaechlich passiert -- nicht, wie die Oberflaeche bedient wird.

---

## 1. Was wo liegt

| Zweck | Pfad |
|---|---|
| Anwendung (Code, venv, statische Dateien) | `/srv/<name>/` |
| Instanzdaten (Datenbank, Schluessel, Lizenz) | `/srv/<name>/data/` |
| Protokolle | `/var/log/<name>/` |
| Umgebungsdatei | `/etc/tesm/<name>.env` |
| Zertifikate (selbstsigniert) | `/etc/tesm/certs/<name>/` |
| nginx-Site | `/etc/nginx/sites-available/<name>` |
| systemd-Unit | `/etc/systemd/system/<name>.service` |
| Privilegierter Helfer | `/usr/local/lib/tesm/tesm-helper` |
| Verwaltungswerkzeug | `/usr/local/bin/<name>-admin` |

`<name>` ist `tesm` bzw. `tesm-license` -- oder, bei einer zusaetzlichen
Instanz, `tesm-<instanz>`. Dieser Name (der **SITE_KEY**) benennt alles, was
ausserhalb von `/srv/<name>` liegt. Deshalb koennen zwei Installationen
derselben Anwendung nebeneinander laufen, ohne sich gegenseitig die
nginx-Site, die netplan-Datei oder das Sitzungscookie zu ueberschreiben.

Alles, was ein Mensch verlieren wuerde, liegt unter `data/`. Das Verzeichnis
wird von `install.sh` nie angefasst.

---

## 2. Installation

```bash
sudo bash deploy/install.sh --app tesm
sudo bash deploy/install.sh --app tesm-license
```

Wichtige Optionen:

| Option | Wirkung |
|---|---|
| `--https` | Port 443 mit TLS, Port 80 leitet dorthin um. Ohne `--domain` entsteht ein selbstsigniertes Zertifikat. |
| `--domain <name>` | Oeffentlicher Name. Setzt `server_name`; die Site ist dann **nicht** mehr die Vorgabe-Site. |
| `--acme-email <a>` | Erst zusammen mit `--domain` wird ein Let's-Encrypt-Zertifikat angefordert (und HSTS gesetzt). Ohne die Adresse bleibt es selbstsigniert -- interne Namen wie `lizenz.firma.local` kann keine CA bestaetigen. |
| `--instance <name>` | Zusaetzliche Instanz neben der Hauptinstallation. |
| `--port` / `--http-port` / `--https-port` | Abweichende Ports (nur fuer Testinstanzen sinnvoll). |
| `--skip-packages` | Keine Systempakete installieren. |

Danach:

```bash
sudo -u tesm tesm-admin create-admin
```

Beim Lizenzserver zusaetzlich, **in dieser Reihenfolge**:

```bash
sudo -u tesm-license tesm-license-admin init-key      # Signaturschluessel -- sofort sichern!
sudo -u tesm-license tesm-license-admin bootstrap     # eigene Master-Lizenz
sudo -u tesm-license tesm-license-admin create-admin
```

> **Der Signaturschluessel ist nicht wiederherstellbar.** Geht
> `data/master_signing_key.json` verloren, ist keine ausgestellte Lizenz mehr
> pruefbar. Sichern Sie ihn getrennt vom Server.

### Zwei Anwendungen auf einem Host

Beide wollen Port 80 und 443. nginx unterscheidet sie am Namen:

```bash
sudo bash deploy/install.sh --app tesm --https
sudo bash deploy/install.sh --app tesm-license --https --domain lizenz.firma.local
```

Die Installation **ohne** Domain wird zur Vorgabe-Site (`default_server`) und
beantwortet alles, was zu keinem Namen passt. Zwei namenlose Sites auf
demselben Port weist nginx ab -- richtig so, und beim Uebernehmen sofort
sichtbar.

---

## 3. Aktualisierung

```bash
sudo bash deploy/install.sh --app tesm
```

Derselbe Befehl. Das Skript erkennt an der vorhandenen Datenbank, dass es eine
Aktualisierung ist, und tut dann zusaetzlich:

1. **Vollstaendige Sicherung** nach `/srv/<name>-backup-<zeitstempel>` --
   unabhaengig davon, ob sich das Schema geaendert hat.
2. Dateiabgleich; `data/` bleibt unangetastet.
3. Schema-Migrationen beim Start (versioniert, mit Pruefsumme).
4. **Gesundheitspruefung.** Antwortet die Anwendung nicht, wird der alte Stand
   automatisch zurueckgeholt; die fehlgeschlagene Version bleibt unter
   `/srv/<name>-failed-<zeitstempel>` zur Analyse liegen.

Eine bereits eingerichtete HTTPS-Konfiguration bleibt erhalten, auch ohne
`--https`.

---

## 4. HTTPS

Der Zielzustand ist immer: **Port 443 mit TLS, Port 80 leitet um, ausser dem
ACME-Pfad.**

Drei Wege dorthin:

**a) Bei der Installation** -- siehe oben, `--https`.

**b) Nachtraeglich ueber die Kommandozeile:**

```bash
sudo -u tesm tesm-admin web-setup --https --self-signed --apply
sudo -u tesm tesm-admin web-setup --https --domain tesm.firma.de \
     --acme --acme-email it@firma.de --hsts --apply
```

`--print-config` zeigt die Datei, ohne etwas zu schreiben.

**c) In der Oberflaeche** unter *Einstellungen -> Webserver und TLS*.

Danach muss `COOKIE_SECURE` in `/etc/tesm/<name>.env` auf `1` stehen (alle drei
Wege erledigen das selbst) und der Dienst neu gestartet werden.

### Die Site-Datei wird immer neu erzeugt

Sie ist **kein** Ort fuer Handarbeit. Jede Uebernahme schreibt sie vollstaendig
aus der Vorlage neu. Was frueher von Hand hineingeschrieben wurde -- Domain,
Ports, Zertifikatspfade, HSTS, Upload-Grenze -- ist heute Einstellung in der
Anwendung.

Der Grund steht im Vorgaengerprojekt: dort wurde die Datei nur angelegt, wenn
sie fehlte. Ein einmal falscher `alias`-Pfad ueberlebte dadurch jahrelang jedes
Update, und die Oberflaeche kam die ganze Zeit ohne Design.

### Selbstsigniert oder Let's Encrypt?

* **Oeffentlich erreichbar, echter DNS-Name:** Let's Encrypt. Nur dann ist HSTS
  sinnvoll.
* **Geschlossenes Netz:** selbstsigniert. Browser zeigen eine Warnung, die
  Verbindung ist trotzdem verschluesselt und das Sitzungscookie darf `Secure`
  tragen. **HSTS hier nicht einschalten** -- der Browser merkt sich die Vorgabe
  monatelang und die Warnung laesst sich dann nicht mehr wegklicken.

Die Erneuerung von Let's Encrypt laeuft ueber `certbot certonly --webroot`
(bewusst nicht ueber das nginx-Plugin, das die erzeugte Site umschreiben
wuerde). Der ACME-Pfad bleibt auch bei aktiver Umleitung erreichbar.

---

## 5. Dienste

```bash
systemctl status tesm.service          # Weboberflaeche (gunicorn)
systemctl status tesm-monitor.service  # Ueberwachungsschleife (nur TESM)
systemctl status tesm-license.service
journalctl -u tesm -n 100 -f
```

Die Units laufen unprivilegiert (`User=tesm`) mit
`NoNewPrivileges`, `ProtectSystem=strict`, `PrivateTmp` und einer engen
`RestrictAddressFamilies`-Liste. Schreibrechte gibt es nur auf `/srv/<name>/data`
und `/var/log/<name>`.

---

## 6. Root-Rechte

Die Anwendung hat keine. Alles, was Root braucht, geht durch **einen** Pfad:

```
/usr/local/lib/tesm/tesm-helper <verb> [argumente]
```

Der Helfer gehoert `root`, ist per sudoers ausschliesslich fuer genau diesen
Pfad mit `NOPASSWD` freigegeben und prueft jedes Argument gegen eine
Positivliste. Er reicht nie etwas an eine Shell weiter (kein `eval`, kein
`sh -c`).

Verben ansehen:

```bash
sudo /usr/local/lib/tesm/tesm-helper
grep -n ')$' /usr/local/lib/tesm/tesm-helper
```

Regel fuer Aenderungen: **Jedes neue Verb braucht eine Argumentpruefung.**
`tests/test_repo_hygiene.py` erzwingt, dass jedes der Anwendung bekannte Verb
im Helfer vorkommt und dass jede Pfadpruefung `..` abweist.

---

## 7. Sicherung und Wiederherstellung

In der Oberflaeche unter *Sicherung* -- verschluesselte Ausfuhr (Argon2id +
AES-256-GCM) mit einem selbst gewaehlten Kennwort. Der Import zeigt zuerst eine
Vorschau, bevor irgendetwas geschrieben wird.

Auf dem Host reicht:

```bash
sudo systemctl stop tesm
sudo tar -czf /root/tesm-data-$(date +%F).tar.gz -C /srv/tesm data
sudo systemctl start tesm
```

Unverzichtbar:

* `data/app.db` -- alle Daten
* `data/secret.key` -- ohne sie sind alle Sitzungen ungueltig
* `data/data.keys` -- ohne sie sind **alle gespeicherten Geheimnisse verloren**
* `data/license.json` und `data/license_key.json`
* Lizenzserver zusaetzlich: `data/master_signing_key.json`

---

## 8. Wenn etwas nicht geht

**Die Anwendung antwortet nicht.**
```bash
journalctl -u tesm -n 200 --no-pager
curl -s http://127.0.0.1:5000/gesundheit
```
Antwortet gunicorn, aber der Browser nicht, liegt es an nginx.

**nginx laesst sich nicht neu laden.**
```bash
sudo nginx -t
```
Die Uebernahme aus der Anwendung nimmt eine abgelehnte Konfiguration selbst
zurueck. Eine von Hand bearbeitete Datei tut das nicht -- in dem Fall hilft
`install.sh`, das eine unbrauchbare Site durch die Vorlage ersetzt.

**Anmeldung schlaegt ohne Fehlermeldung fehl.**
Fast immer `COOKIE_SECURE=1` ohne HTTPS. Der Browser bekommt dann ein Cookie,
das er ueber HTTP nie zuruecksendet.
```bash
grep COOKIE_SECURE /etc/tesm/tesm.env
```

**"Die Anfrage kam von einer fremden Herkunft (Origin/Referer)."**
Der Reverse Proxy reicht den Host falsch durch. In der Site-Datei muss
`proxy_set_header Host $http_host;` stehen -- **nicht** `$host`, das verwirft
den Port.

**Statische Dateien fehlen, die Oberflaeche ist unformatiert.**
```bash
grep alias /etc/nginx/sites-available/tesm
ls /srv/tesm/static /srv/tesm/static-core
```
Beide `alias`-Pfade muessen in das Installationsverzeichnis zeigen.

**Zwei Instanzen stoeren sich.**
`SITE_KEY` in beiden `/etc/tesm/*.env` vergleichen -- er muss sich
unterscheiden. Sonst teilen sich beide nginx-Site und Sitzungscookie.

**Die Wartung meldet "Der SSH-Host-Schluessel ist unbekannt".**
Kein Fehler, sondern die Absicht: ein Hintergrundauftrag darf keinen fremden
Schluessel akzeptieren. Die Freigabe erfolgt von Hand, in zwei Schritten, auf
der Detailseite des Geraets (*Clients -> Geraet -> SSH-Host-Schluessel*):
erst **Auslesen**, dann den Fingerprint mit einer unabhaengigen Quelle
vergleichen, dann **Freigeben**. Die Wartungsuebersicht zeigt in einer eigenen
Spalte, fuer welche Geraete das noch offen ist. Fuer Switche liegt dieselbe
Freigabe auf der Switch-Detailseite.

Der Schluessel wird nach **IP und Port** gespeichert, nicht nach Name -- TESM
verbindet sich immer ueber die hinterlegte IP-Adresse. Aendert sich die IP
eines Geraets, ist der Schluessel erneut freizugeben.

**Die Pruefkette des Aenderungsprotokolls ist gebrochen.**
Unter *Diagnose* sichtbar. Das Protokoll ist verkettet gehasht; ein Bruch heisst,
dass jemand direkt in die Datenbank geschrieben hat. Der Eintrag, ab dem es
nicht mehr stimmt, wird genannt.

---

## 9. Zurueck auf eine aeltere Version

```bash
sudo systemctl stop tesm
sudo mv /srv/tesm /srv/tesm-kaputt
sudo cp -a /srv/tesm-backup-<zeitstempel> /srv/tesm
sudo systemctl start tesm
```

Schema-Migrationen laufen nur vorwaerts. Ein Ruecksprung ueber eine
Schema-Aenderung hinweg braucht deshalb auch die Datenbank aus der Sicherung --
genau das liegt im Backup-Verzeichnis.
