Homelab-Dokumentation und Konfigurationsvorlagen
Dokumentation aus dem Wiki uebernommen und um die Dienste auf dem Server
erweitert. Alle Anleitungen sind so geschrieben, dass sich der jeweilige
Dienst allein daraus neu aufsetzen laesst.
docs/ Dienste im Heimnetz (aus dem Wiki, anonymisiert)
services/ Dienste auf dem Server mit Konfigurationsvorlagen
- matrix-synapse: Homeserver, Postgres, Well-Known-Delegation
- coturn: TURN-Relay fuer Anrufe
- element-web: Web-Client
- wikijs, gitea, nginx
Durchgehend anonymisiert: echte Domain durch example.com ersetzt, IP-Adressen
und E-Mail-Adressen durch Platzhalter. Konfigurationsdateien liegen nur als
.example mit Platzhaltern statt echter Secrets vor.
Die Anleitungen halten die Stolpersteine fest, die beim Aufbau tatsaechlich
aufgetreten sind, unter anderem:
- Synapse verlangt LC_COLLATE=C, sonst startet es nicht
- server_name ist nachtraeglich nicht aenderbar -> Delegation noetig
- register_new_matrix_user liest conf.d nicht, Secret muss per -k kommen
- coturn braucht external-ip, sonst kommen keine Medien durch
- Gruppenmitgliedschaft fuer den Zertifikatszugriff wirkt erst beim Neustart
- nginx vererbt add_header nicht in location-Bloecke mit eigenen Direktiven
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Niemals echte Zugangsdaten oder Schluessel einchecken
|
||||
*.key
|
||||
*.pem
|
||||
*.env
|
||||
secrets*
|
||||
!*.example
|
||||
@@ -0,0 +1,64 @@
|
||||
# Homelab
|
||||
|
||||
Dokumentation und Konfiguration meines Homelabs. Das Ziel ist, jeden Dienst
|
||||
allein anhand dieser Unterlagen neu aufsetzen zu können.
|
||||
|
||||
## Aufbau
|
||||
|
||||
```
|
||||
homelab/
|
||||
├── docs/ Beschreibungen und Anleitungen (aus dem Wiki übernommen)
|
||||
│ ├── uebersicht.md
|
||||
│ ├── dienste/ Was der jeweilige Dienst macht
|
||||
│ └── anleitungen/ Schritt für Schritt
|
||||
└── services/ Dienste auf dem Server, mit Konfigurationsvorlagen
|
||||
├── matrix-synapse/
|
||||
├── coturn/
|
||||
├── element-web/
|
||||
├── wikijs/
|
||||
├── gitea/
|
||||
└── nginx/
|
||||
```
|
||||
|
||||
Der Unterschied: `docs/` beschreibt die Dienste im Heimnetz (laufen unter
|
||||
Docker/Portainer), `services/` dokumentiert die Dienste auf dem Server, jeweils
|
||||
mit den zugehörigen Konfigurationsdateien.
|
||||
|
||||
## Dienste auf dem Server
|
||||
|
||||
| Dienst | Verzeichnis | Zweck |
|
||||
|---|---|---|
|
||||
| Matrix (Synapse) | [`services/matrix-synapse/`](services/matrix-synapse/) | Chat-Homeserver |
|
||||
| coturn | [`services/coturn/`](services/coturn/) | TURN-Relay für Anrufe |
|
||||
| Element Web | [`services/element-web/`](services/element-web/) | Web-Client für Matrix |
|
||||
| Wiki.js | [`services/wikijs/`](services/wikijs/) | Wiki |
|
||||
| Gitea | [`services/gitea/`](services/gitea/) | Git-Hosting |
|
||||
| nginx | [`services/nginx/`](services/nginx/) | Reverse Proxy, TLS |
|
||||
|
||||
## Dienste im Heimnetz
|
||||
|
||||
Siehe [`docs/uebersicht.md`](docs/uebersicht.md) – Homepage, AdGuard,
|
||||
Jellyfin/ARR, Paperless, Vaultwarden, Netdata.
|
||||
|
||||
## Konventionen
|
||||
|
||||
**Alles ist anonymisiert.** Die echte Domain steht nirgends im Repo. Vor dem
|
||||
Nachbauen zu ersetzen sind:
|
||||
|
||||
| Platzhalter | Bedeutung |
|
||||
|---|---|
|
||||
| `example.com` | eigene Domain |
|
||||
| `matrix.example.com` | Subdomain des Matrix-Servers |
|
||||
| `element.example.com` | Subdomain des Web-Clients |
|
||||
| `turn.example.com` | Subdomain des TURN-Servers |
|
||||
| `git.example.com` | Subdomain von Gitea |
|
||||
| `<IP-ADRESSE>` | konkrete IP-Adresse |
|
||||
| `HIER-…` | ein einzusetzendes Geheimnis |
|
||||
|
||||
**Keine Zugangsdaten im Repo.** Dateien mit echten Werten heißen im Original
|
||||
z. B. `homelab.yaml`; hier liegt nur `homelab.yaml.example` mit Platzhaltern.
|
||||
Secrets werden auf dem Server erzeugt und dort gehalten, nicht hier.
|
||||
|
||||
**Reihenfolge beim Nachbauen.** Erst nginx und Zertifikate, dann die
|
||||
Anwendungen. Beim Matrix-Teil zuerst Synapse, dann coturn, dann Element – die
|
||||
späteren Schritte brauchen jeweils Werte aus den früheren.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Dokumentation
|
||||
|
||||
Beschreibungen der Dienste im Heimnetz. Sie laufen als Docker-Stacks über
|
||||
Portainer, jeder Dienst als eigener Stack.
|
||||
|
||||
## Übersicht
|
||||
|
||||
- [Überblick über das Homelab](uebersicht.md)
|
||||
|
||||
## Dienste
|
||||
|
||||
| Dienst | Beschreibung | Anleitung |
|
||||
|---|---|---|
|
||||
| [Homepage](dienste/homepage.md) | Dashboard über alle Dienste | [Anleitung](anleitungen/homepage.md) |
|
||||
| [AdGuard](dienste/adguard.md) | Werbe- und Trackerfilter auf DNS-Ebene | [Anleitung](anleitungen/adguard.md) |
|
||||
| [Jellyfin & ARR](dienste/jellyfin-arr.md) | Medienbibliothek und Automatisierung | [Anleitung](anleitungen/jellyfin-arr.md) |
|
||||
| [Paperless](dienste/paperless.md) | Digitale Dokumentenablage | [Anleitung](anleitungen/paperless.md) |
|
||||
| [Vaultwarden](dienste/vaultwarden.md) | Passwortmanager | [Anleitung](anleitungen/vaultwarden.md) |
|
||||
| [Netdata](dienste/netdata.md) | Systemüberwachung | [Anleitung](anleitungen/netdata.md) |
|
||||
|
||||
## Sonstiges
|
||||
|
||||
- [Gitea: Projekt hochladen](anleitungen/gitea-projekt-hochladen.md)
|
||||
@@ -0,0 +1,11 @@
|
||||
# Anleitungen
|
||||
|
||||
Schritt-für-Schritt-Anleitungen zu den Diensten im Heimnetz.
|
||||
|
||||
- [Homepage](homepage.md)
|
||||
- [AdGuard](adguard.md)
|
||||
- [Jellyfin & ARR-Stack](jellyfin-arr.md)
|
||||
- [Paperless](paperless.md)
|
||||
- [Vaultwarden](vaultwarden.md)
|
||||
- [Netdata](netdata.md)
|
||||
- [Gitea: Projekt hochladen](gitea-projekt-hochladen.md)
|
||||
@@ -0,0 +1,32 @@
|
||||
## AdGuard - Anleitung
|
||||
|
||||
**AdGuard** blockt Werbung und Tracker zentral über DNS. So richtest du ihn ein und nutzt ihn im Alltag.
|
||||
|
||||
### Aufsetzen unter Portainer
|
||||
|
||||
1. Lege in Portainer einen **Stack** mit dem Namen `adguard` an.
|
||||
2. Füge den AdGuard-Container hinzu. Wichtig ist, dass er einen **DNS-Port** (Standard 53) und ein **Weboberflächen-Port** bereitstellt.
|
||||
3. Konfiguriere die **Adressen**, auf denen AdGuard laufen soll. Für das gesamte Netz sollte AdGuard auf der Netzwerkadresse erreichbar sein.
|
||||
4. Starte den Stack und öffne die Weboberfläche, um das Admin-Passwort beim ersten Start zu setzen.
|
||||
|
||||
### Einrichtung der Filter
|
||||
|
||||
1. Öffne die Weboberfläche von AdGuard.
|
||||
2. Aktiviere eine oder mehrere **Filterlisten** für Werbung und Tracker. Es gibt öffentliche Listen, die ständig gepflegt werden.
|
||||
3. Prüfe anschließend in der Statistik-Ansicht, wie viele Anfragen tatsächlich blockiert werden.
|
||||
|
||||
### Geräte anbinden
|
||||
|
||||
Damit der Schutz greift, müssen die Geräte AdGuard als DNS-Server verwenden:
|
||||
|
||||
- Im **Router**: Trage die Adresse von AdGuard als DNS-Server ein. Dann ist das komplette Netz abgedeckt.
|
||||
- **Pro Gerät**: In den Netzwerkeinstellungen des Geräts die DNS-Adresse manuell setzen.
|
||||
|
||||
### Tägliche Nutzung
|
||||
|
||||
- Die **Statistik** zeigt blockierte Anfragen und die meistgenutzten Domains.
|
||||
- In den **Protokollen** sieht man, welche Anfragen blockiert wurden und kann Ausnahmen hinzufügen.
|
||||
|
||||
### Stolperfalle
|
||||
|
||||
Wenn eine Website nicht mehr richtig lädt, liegt es oft an der DNS-Umleitung. Leere/blockierte Antworten kann man in der Regel per **Ausnahme-Liste (Whitelist)** leicht beheben.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Gitea: Projekt hochladen & als „GitHub-Seite“ nutzen
|
||||
|
||||
In Gitea (https://git.example.com) lässt sich ein Projekt genauso verwalten wie
|
||||
auf GitHub: Du legst ein Repository an, pusht lokale Änderungen und nutzt die
|
||||
README als Projekt-Startseite.
|
||||
|
||||
---
|
||||
|
||||
## 1. Repository in Gitea anlegen
|
||||
|
||||
1. In Gitea einloggen (https://git.example.com).
|
||||
2. Rechts oben auf das **+** bzw. **Neues Repository** klicken.
|
||||
3. Werte einstellen:
|
||||
- **Name:** z. B. `ftptui`
|
||||
- **Sichtbarkeit:** öffentlich/private (nach Bedarf)
|
||||
- **README initialisieren:** aktiv (optional)
|
||||
4. **Repository erstellen** klicken.
|
||||
|
||||
Das Repo ist jetzt unter `https://git.example.com/<benutzer>/<name>` erreichbar.
|
||||
|
||||
---
|
||||
|
||||
## 2. Lokal einrichten & hochladen (Push)
|
||||
|
||||
### Variante A: Neues Projekt, bereits existierendes Repo klonen
|
||||
|
||||
```bash
|
||||
git clone https://git.example.com/<benutzer>/<name>.git
|
||||
cd <name>
|
||||
# ... Dateien hinzufügen ...
|
||||
git add -A
|
||||
git commit -m "Erster Commit"
|
||||
git push
|
||||
```
|
||||
|
||||
### Variante B: Bestehendes lokales Projekt anbinden
|
||||
|
||||
```bash
|
||||
cd /pfad/zum/projekt
|
||||
git init
|
||||
git add -A
|
||||
git commit -m "Erster Commit"
|
||||
git branch -M main
|
||||
git remote add origin https://git.example.com/<benutzer>/<name>.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
> Aus der `shell`-Umgebung von `example.com` ist das Repo auch per **SSH**
|
||||
> erreichbar: `<E-MAIL>:<benutzer>/<name>.git`
|
||||
> (vorher unter **Einstellungen → SSH-Schlüssel** einen öffentlichen Schlüssel hinterlegen).
|
||||
|
||||
---
|
||||
|
||||
## 3. README als Projekt-Startseite
|
||||
|
||||
Die `README.md` im Stammverzeichnis des Repos wird auf der Projektseite in Gitea
|
||||
automatisch gerendert – genau wie die Projektseite bei GitHub. Diese Repo-Seite
|
||||
folgt dabei **Markdown**.
|
||||
|
||||
### README minimal aufbauen
|
||||
|
||||
```markdown
|
||||
# Projektname
|
||||
|
||||
> Kurzbeschreibung in einem Satz.
|
||||
|
||||
## Funktionsumfang
|
||||
- ...
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
pip install .
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
```bash
|
||||
projektbefehl
|
||||
```
|
||||
|
||||
## Lizenz
|
||||
MIT
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## Homepage - Anleitung
|
||||
|
||||
Die **Homepage** ist das Dashboard, über das ich alle Dienste im Homelab erreiche. Hier die Kurzanleitung zum Einrichten und Verwenden.
|
||||
|
||||
### Aufsetzen unter Portainer
|
||||
|
||||
1. Lege in Portainer einen neuen **Stack** an und nenne ihn etwa `homepage`.
|
||||
2. Füge die Container-Definition für ein Dashboard wie Homepage hinzu. Es reicht ein einzelner Dienst, der die Startseite bereitstellt.
|
||||
3. Vergib einen **Namen** und einen **Port**, über den die Seite erreichbar ist.
|
||||
4. Trage die **Adressen der einzelnen Dienste** als Kacheln ein - so werden alle Dienste auf der Startseite sichtbar.
|
||||
5. Starte den Stack und prüfe, ob die Seite unter der eingegebenen Adresse erreichbar ist.
|
||||
|
||||
### Tägliche Nutzung
|
||||
|
||||
- **Dienst aufrufen:** Ein Klick auf die Kachel öffnet den jeweiligen Dienst.
|
||||
- **Status erkennen:** Viele Dashboards zeigen an, ob ein Dienst gerade verfügbar ist oder nicht.
|
||||
|
||||
### Tipps zur Wartung
|
||||
|
||||
- Nach dem Hinzufügen eines neuen Dienstes im Homelab die Kachel in der Konfiguration ergänzen.
|
||||
- Die Konfigurationsdatei des Dashboards lässt sich in Portainer direkt im Stack ansehen und anpassen.
|
||||
@@ -0,0 +1,38 @@
|
||||
## Jellyfin & ARR-Stack - Anleitung
|
||||
|
||||
Hier geht es um Jellyfin als Media-Server und die Automatisierung mit den "ARR"-Diensten. So baust du den Stack auf und pflegst ihn.
|
||||
|
||||
### Aufsetzen unter Portainer
|
||||
|
||||
1. Lege in Portainer einen **Stack** an, zum Beispiel `media`.
|
||||
2. Füge die Dienste hinzu, die du brauchst. Als Grundgerüst sind das:
|
||||
- **Jellyfin** als Media-Server
|
||||
- **Sonarr** für Serien, **Radarr** für Filme
|
||||
- Dazu ein paar Helfer, die Downloads koordinieren und die Dateien sortieren
|
||||
3. Wichtig: Richte **gemeinsame Ordner** ein (z.B. für Filme, Serien, Downloads), damit alle Dienste auf dieselben Dateien zugreifen. Sonst funktioniert die Automatisierung nicht.
|
||||
4. Starte den Stack und richte jeden Dienst einmalig über seine Weboberfläche ein (Benutzer, Qualität, Zielordner).
|
||||
|
||||
### Die Verbindung der Dienste
|
||||
|
||||
Damit das Zusammenspiel läuft, müssen die Dienste miteinander sprechen:
|
||||
|
||||
1. Öffne die Weboberfläche von **Sonarr/Radarr**.
|
||||
2. Trage dort die **Download-Client-Daten** und die **Zielordner** ein.
|
||||
3. Richte die **Indexer** (Quellen für Filme und Serien) ein.
|
||||
4. Verbinde Sonarr/Radarr mit deiner **Medienbibliothek** von Jellyfin, damit neue Inhalte automatisch auftauchen.
|
||||
|
||||
### Inhalte verwalten
|
||||
|
||||
- **Serie hinzufügen:** In Sonarr die Serie suchen und auf "Hinzufügen" klicken. Neue Folgen werden automatisch übernommen.
|
||||
- **Film hinzufügen:** In Radarr dasselbe für Filme.
|
||||
- **Bibliothek prüfen:** In Jellyfin erscheinen neue Inhalte nach kurzer Zeit automatisch im richtigen Bereich.
|
||||
|
||||
### Wartung
|
||||
|
||||
- **Speicherplatz im Auge behalten** - eine umfangreiche Bibliothek braucht schnell viel Platz.
|
||||
- **Qualität prüfen:** Die ARR-Dienste können ältere, minderwertige Einträge automatisch durch bessere Versionen ersetzen, sobald sie verfügbar sind.
|
||||
- **Backups:** Ein Backup der Konfiguration der einzelnen Dienste ist sinnvoll, damit du bei einem Neuaufbau nicht alles von vorne einrichtest.
|
||||
|
||||
### Typisches Problem und Lösung
|
||||
|
||||
*Ein neuer Eintrag taucht nicht in Jellyfin auf.* Meist liegt es an den **Zielordnern** (Pfad-Mapping). Prüfe, ob alle Dienste auf dieselben Ordner zugreifen und die Pfade in Sonarr/Radarr zu den Pfaden in Jellyfin passen.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Netdata - Anleitung
|
||||
|
||||
**Netdata** überwacht deine Systeme in Echtzeit. So richtest du es ein und liest die wichtigsten Kennzahlen.
|
||||
|
||||
### Aufsetzen unter Portainer
|
||||
|
||||
1. Lege in Portainer einen **Stack** mit dem Namen `netdata` an.
|
||||
2. Füge den Netdata-Container hinzu. Damit er die Systeme sehen kann, muss er Zugriff auf die **Host-System**-Werte bekommen (Prozesse, Netzwerk, Festplatten).
|
||||
3. Vergib einen **Port** für die Weboberfläche.
|
||||
4. Starte den Stack und öffne die Webansicht.
|
||||
|
||||
### Die Ansicht verstehen
|
||||
|
||||
- **Dashboard:** Netdata zeigt eine lange, scrollbare Reihe von Diagrammen für jede Kennzahl.
|
||||
- **Ein Klick auf ein Diagramm** zoomt hinein und zeigt die Details.
|
||||
- **Warnungen:** Netdata kann Schwellwerte definieren und eine Meldung zeigen, wenn zum Beispiel die CPU-Last oder der Speicher extrem hoch ist.
|
||||
|
||||
### Wichtige Kennzahlen
|
||||
|
||||
- **CPU und RAM:** Sieht man schnell, ob ein Dienst das System auslastet.
|
||||
- **Netzwerk:** Datenverkehr der einzelnen Interfaces.
|
||||
- **Festplatten:** Auslastung und Zugriffsraten.
|
||||
- **Applikationen/Container:** Ob ein bestimmter Dienst reagiert und wie viel er verbraucht.
|
||||
|
||||
### Als Monitoring für den Alltag
|
||||
|
||||
- Nutze die **Alarm-Regeln**, um per Benachrichtigung informiert zu werden, statt ständig selbst zu schauen.
|
||||
- Wenn du Veränderungen am Homelab machst, siehst du direkt im Dashboard, ob etwas spürbar ansteigt (z.B. mehr Speicher oder CPU).
|
||||
|
||||
### Hinweis
|
||||
|
||||
Netdata zeigt die Zustände **live** an - es geht hier um den Moment und die letzten Stunden. Für langfristige Verläufe über Wochen oder Monate brauchst du zusätzlich ein eigenes Speichersystem, falls dir das irgendwann wichtig ist.
|
||||
@@ -0,0 +1,36 @@
|
||||
## Paperless - Anleitung
|
||||
|
||||
Mit **Paperless** organisierst du Dokumente digital, inklusive automatischer Texterkennung. So richtest du es ein und nutzt es im Alltag.
|
||||
|
||||
### Aufsetzen unter Portainer
|
||||
|
||||
1. Lege in Portainer einen **Stack** mit dem Namen `paperless` an.
|
||||
2. Füge den Paperless-Container hinzu. Er benötigt einen **Ordner für eingehende Dokumente** (Consume) und einen für die archivierten Dateien, damit die Ablage sauber getrennt ist.
|
||||
3. Wichtig: Richte eine **Datenbank** ein (z.B. PostgreSQL), in der Paperless die Metadaten speichert. Die eigentlichen Dokumente liegen in den Datei-Ordnern.
|
||||
4. Starte den Stack und öffne die Weboberfläche, um dich mit dem Admin-Account anzumelden.
|
||||
|
||||
### Dokumente einwerfen
|
||||
|
||||
1. Lege ein Dokument (PDF, Scan) in den **Eingangsordner** (Consume-Ordner).
|
||||
2. Paperless erkennt den Text automatisch (OCR) und sortiert das Dokument in die Ablage ein.
|
||||
3. Nach kurzer Zeit ist das Dokument in der Oberfläche verfügbar und durchsuchbar.
|
||||
|
||||
### Dokumente organisieren
|
||||
|
||||
- **Tags vergeben:** Vergib Tags wie "Rechnung", "Vertrag" oder "Kontoauszug", damit du klar filtern kannst.
|
||||
- **Korrespondenten:** Ordne Dokumente einem Absender zu (z.B. deinem Stromanbieter).
|
||||
- **Suchen:** Die Volltextsuche findet Dokumente auch nach Textstellen im Inneren.
|
||||
|
||||
### Berechtigungen und Zugriff
|
||||
|
||||
Bei Paperless lassen sich **Benutzer und Gruppen** einrichten. So können mehrere Personen zugreifen, ohne dass alle Dokumente offen sind:
|
||||
|
||||
- Lege Benutzer an und weise ihnen die passenden Rechte zu.
|
||||
- Dokumente können mit Sicht- und Bearbeitungsrechten versehen werden.
|
||||
|
||||
Für "nur zur Sicht" kannst du Benutzer so anlegen, dass sie Dokumente nur **lesen**, aber nicht verändern oder löschen können.
|
||||
|
||||
### Wartung
|
||||
|
||||
- Achte darauf, dass regelmäßig **Backups** der Datenbank und der Dokumenten-Ordner gemacht werden - Dokumente sind schließlich wertvoll.
|
||||
- Leere den Eingangsordner nach der Verarbeitung, damit nichts doppelt oder gar nicht verarbeitet wird.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Vaultwarden - Anleitung
|
||||
|
||||
**Vaultwarden** ist dein eigener Passwortmanager auf deiner Hardware. So richtest du ihn ein und nutzt ihn sicher.
|
||||
|
||||
### Aufsetzen unter Portainer
|
||||
|
||||
1. Lege in Portainer einen **Stack** mit dem Namen `vaultwarden` an.
|
||||
2. Füge den Vaultwarden-Container hinzu. Er braucht ein **Datenverzeichnis**, in dem die verschlüsselte Datenbank und die Schlüssel abgelegt werden.
|
||||
3. Vergib einen **Zugang über HTTPS**, damit die Verbindung verschlüsselt ist (z.B. über einen Reverse Proxy wie Nginx). Ein Passwortmanager sollte niemals unverschlüsselt über das Netz erreichbar sein.
|
||||
4. Starte den Stack und öffne die Weboberfläche.
|
||||
|
||||
### Erste Schritte
|
||||
|
||||
1. **Registrierung öffnen:** Beim ersten Aufruf kannst du einen Administrator-Account anlegen. Dieser Account ist ausschließlich für die Verwaltung gedacht.
|
||||
2. **Admin-Bereich schon schützen:** In der Konfiguration kann ein separates Admin-Token gesetzt werden. Aktiviere das, damit nicht jeder die Verwaltung öffnen kann.
|
||||
3. **Registrierung nach Einrichtung deaktivieren:** Sobald alle Benutzer angelegt sind, die Registrierung in der Konfiguration ausschalten, damit keine fremden Accounts entstehen können.
|
||||
|
||||
### Tägliche Nutzung
|
||||
|
||||
- **Passwörter speichern:** Lege Einträge für Logins, Notizen oder Karten an.
|
||||
- **Passwortgenerator:** Direkt in Vaultwarden starke, einzigartige Passwörter erzeugen lassen.
|
||||
- **Browser und Handy:** Mit der passenden App oder Browser-Erweiterung greifst du von überall auf deinen Tresor zu.
|
||||
|
||||
### Sicherheitsempfehlungen
|
||||
|
||||
- **Starkes Hauptpasswort:** Es schützt den gesamten Tresor - wähle es mit Bedacht.
|
||||
- **Backups:** Der Datenordner enthält deinen gesamten Tresor. Sichere ihn regelmäßig, damit du bei einem Ausfall nichts verlierst.
|
||||
- **2-Faktor-Authentifizierung** (falls gewünscht) für den Bereich nutzen, in dem sensible Daten liegen.
|
||||
|
||||
### Stolperfalle
|
||||
|
||||
Wenn sich die App nicht verbinden kann, liegt es fast immer an der **erzwungenen HTTPS-Verbindung** oder an einem falsch konfigurierten Reverse Proxy. Prüfe hier zuerst, bevor du woanders suchst.
|
||||
@@ -0,0 +1,20 @@
|
||||
## AdGuard - Werbeblocker auf DNS-Ebene
|
||||
|
||||
**AdGuard** kümmert sich um ein ziemlich nerviges Thema: Werbung und Tracker. Statt auf jedem einzelnen Gerät eine App zu installieren, blockt AdGuard die Werbung zentral - direkt auf der DNS-Ebene im gesamten Heimnetzwerk.
|
||||
|
||||
### Wie funktioniert das?
|
||||
|
||||
Das Prinzip ist simpel und elegant: Jedes Gerät im Netz fragt beim Surfen einen DNS-Server, welche IP-Adresse hinter einer Domain steckt. AdGuard beantwortet diese Anfragen selbst und gibt für bekannte Werbe- und Tracker-Adressen einfach *nichts* zurück beziehungsweise eine leere Antwort. Das Werbe-Panel wird so instinktiv gar nicht erst geladen.
|
||||
|
||||
Dadurch ist der Schutz für **alle Geräte im Netz** aktiv - egal ob Handy, Laptop, Tablet oder Smart-TV. Auf den einzelnen Geräten muss nichts installiert werden.
|
||||
|
||||
### Vorteile für mich
|
||||
|
||||
- **Weniger Werbung** - viele Webseiten und Apps werden deutlich ruhiger
|
||||
- **Mehr Privatsphäre** - Tracker, die mein Verhalten verfolgen, werden blockiert
|
||||
- **Schutz für alle Geräte** - zentrale Lösung statt Einzelinstallationen
|
||||
- **Schnelleres Surfen** - viele unnötige Werbeinhalte werden gar nicht erst geladen
|
||||
|
||||
### Datenschutz
|
||||
|
||||
Dabei bleibt der Datenfluss übersichtlich: Alles läuft innerhalb meines eigenen Netzes. Ich habe die volle Kontrolle darüber, welche Domains blockiert werden und welche nicht.
|
||||
@@ -0,0 +1,17 @@
|
||||
## Homepage - Das Dashboard
|
||||
|
||||
Die **Homepage** ist meine persönliche Startseite beziehungsweise das Dashboard im Homelab. Sie sammelt alle wichtigen Dienste an einem Ort, sodass ich nicht jedes Mal die IP-Adresse oder den Port eines Dienstes eingeben muss.
|
||||
|
||||
Es ist im Grunde eine ziemlich aufgeräumte Link-Sammlung mit einer schönen Oberfläche. Man klickt sich nicht durch komplizierte Menüs, sondern sieht direkt alle Kacheln der Dienste, die in meinem Netz stecken.
|
||||
|
||||
### Was ich daran mag
|
||||
|
||||
- **Alles an einem Ort** - die wichtigen Dienste sind mit einem Klick erreichbar
|
||||
- **Sauber und schnell** - die Seite ist leichtgewichtig, keine unnötige Schwere
|
||||
- **Flexibel** - neue Dienste lassen sich einfach als Kachel ergänzen
|
||||
|
||||
### Technisch gesehen
|
||||
|
||||
Die Homepage läuft als Container über einen eigenen Stack in Portainer. Sie spricht die anderen Dienste direkt an und zeigt je nach Konfiguration auch den Status an, ob ein Dienst gerade verfügbar ist.
|
||||
|
||||
Für mich ist die Homepage der natürliche Einstiegspunkt: Rufe ich im Browser die Seite auf, habe ich mein ganzes Homelab auf einen Blick.
|
||||
@@ -0,0 +1,23 @@
|
||||
## Jellyfin & der ARR-Stack - Eigene Medienbibliothek
|
||||
|
||||
Hier geht es um mein Herzstück für Filme und Serien. Ich betreibe **Jellyfin** als Media-Server und darüber einiges an Automatisierung, das man im Selbst-Hosting-Sprech den **ARR-Stack** nennt.
|
||||
|
||||
### Jellyfin - Der Media-Server
|
||||
|
||||
**Jellyfin** ist meine eigene Streaming-Plattform. Dort verwalte ich meine Film- und Seriensammlung, und ich kann auf alles von unterwegs oder im Wohnzimmer zugreifen. Das Tolle an Jellyfin: Es ist komplett kostenlos und Open Source - ich bin nicht von einem Anbieter abhängig, der mir irgendwann plötzlich Limitierungen aufdrückt.
|
||||
|
||||
Wichtig ist mir, dass alles **mir gehört**. Ich entscheide, was in der Bibliothek liegt, und niemand kann sie mir wegnehmen.
|
||||
|
||||
### Der ARR-Stack - Die Automatisierung dahinter
|
||||
|
||||
Der Name "ARR" kommt von den einzelnen Diensten, die alle auf "-arr" enden. Das sind die Helfer im Hintergrund:
|
||||
|
||||
- **Sonarr** - kümmert sich um **Serien**: verfolgt, was ich schauen will, und lädt neue Folgen automatisch, sobald sie verfügbar sind
|
||||
- **Radarr** - macht dasselbe für **Filme**
|
||||
- Dazu kommen weitere Helfer-Dienste, die das Ganze koordinieren, die Qualität kontrollieren und alles sauber benennen und einsortieren
|
||||
|
||||
Zusammenspiel in der Praxis: Ich sage dem Stack einmal, welche Serie mich interessiert - und der Rest passiert automatisch. Neue Folgen werden gefunden, heruntergeladen und landen sortiert in der Bibliothek, wo Jellyfin sie mir im schicken Layout präsentiert.
|
||||
|
||||
### Technisches Setup
|
||||
|
||||
Die ganze Sache läuft als **ein zusammenhängender Stack** in Portainer. Die einzelnen Dienste arbeiten über gemeinsame Ordner zusammen, damit nichts doppelt gelagert wird und Jellyfin immer auf die fertigen Dateien zugreifen kann.
|
||||
@@ -0,0 +1,26 @@
|
||||
## Netdata - Laufende Überwachung
|
||||
|
||||
**Netdata** ist mein digitales Frühwarnsystem. Es überwacht die Systeme in meinem Homelab rund um die Uhr und zeigt mir in Echtzeit, was gerade auf meinen Servern passiert.
|
||||
|
||||
### Was Netdata beobachtet
|
||||
|
||||
Die Überwachung geht ziemlich ins Detail. Netdata schaut sich unter anderem an:
|
||||
|
||||
- **Auslastung** - wie stark arbeiten die Prozessoren, wie viel Speicher ist belegt
|
||||
- **Temperatur und Gesundheit** - ob die Hardware an ihre Grenzen stößt
|
||||
- **Netzwerk** - wie viel Datenverkehr läuft, ob alles flüssig funktioniert
|
||||
- **Einzelne Dienste** - ob die Container und Anwendungen sauber laufen
|
||||
|
||||
### Warum ich das brauche
|
||||
|
||||
Ich möchte nicht erst dann von einem Problem erfahren, wenn etwas schon nicht mehr funktioniert. Netdata gibt mir frühzeitig ein Gefühl dafür, ob etwas aus dem Ruder läuft:
|
||||
|
||||
- **Probleme früh erkennen** - bevor sie zu Ausfällen werden
|
||||
- **Anschauliche Diagramme** - auf einen Blick sehen, was gerade los ist
|
||||
- **Ein zentraler Blick** - die wichtigen Kennzahlen meiner Systeme an einer Stelle
|
||||
|
||||
### Echtzeit statt Vermutungen
|
||||
|
||||
Besonders gefällt mir, dass Netdata in **Echtzeit** arbeitet. Ich muss nicht auf Berichte warten oder irgendwas aktualisieren - ich öffne die Ansicht und sehe den aktuellen Zustand live. Wenn ich merke, dass ein Dienst nicht mehr reagiert oder ein System ungewöhnlich stark beansprucht wird, springe ich sofort ein.
|
||||
|
||||
Damit ist Netdata der stille Wächter meines Homelabs - er schaut einfach zu und macht mich aufmerksam, wenn etwas Aufmerksamkeit verdient.
|
||||
@@ -0,0 +1,23 @@
|
||||
## Paperless - Digitale Dokumentenablage
|
||||
|
||||
**Paperless** ist meine digitale Lösung gegen den Papierkram-Berg. Es ist ein System zur Verwaltung von Dokumenten, mit dem ich Briefe, Rechnungen, Verträge und Belege einfach digital organisiere.
|
||||
|
||||
### Das Grundprinzip
|
||||
|
||||
Das Herzstück von Paperless ist die **automatische Texterkennung** (OCR). Ich kann ein eingescanntes Dokument oder ein PDF hineinwerfen, und Paperless liest den Text automatisch aus. Dadurch lassen sich die Dokumente später stichwortartig durchsuchen - ich muss nicht mehr krampfhaft nach "wie hieß doch gleich diese Rechnung" suchen.
|
||||
|
||||
### So organisiere ich meine Dokumente
|
||||
|
||||
- **Verschlagwortung** - Dokumente bekommen Tags und Kategorien, damit ich sie schnell wiederfinde
|
||||
- **Suchen statt wühlen** - Jede Suche findet das Dokument in Sekundenschnelle, auch wenn ich nur einen Bruchteil des Inhalts kenne
|
||||
- **Saubere Struktur** - Die Dokumente liegen übersichtlich sortiert und nicht mehr in irgendwelchen Ordnern verstreut
|
||||
|
||||
### Warum mir das wichtig ist
|
||||
|
||||
Ich hab genug von Papierstapeln, in denen ich mühsam etwas suchen muss. Mit Paperless habe ich alles im Griff:
|
||||
|
||||
- **Alle Belege an einem Ort** - nichts geht mehr verloren
|
||||
- **Schnell auffindbar** - durch die Texterkennung ist jedes Dokument auffindbar
|
||||
- **Sicher abgelegt** - die Dokumente liegen auf meinen eigenen Systemen
|
||||
|
||||
Paperless läuft als eigener Stack unter Portainer und ist damit ein Baustein meines Homelabs, der mir viel Chaos im Alltag erspart.
|
||||
@@ -0,0 +1,21 @@
|
||||
## Vaultwarden - Der eigene Passwortmanager
|
||||
|
||||
**Vaultwarden** ist mein privater Passwortmanager. Er ist eine kompatible, ressourcenschonende Alternative zu einigen der bekannten kommerziellen Passwortmanager-Dienste - nur eben komplett bei mir zu Hause auf meiner eigenen Hardware.
|
||||
|
||||
### Warum ein eigener Passwortmanager?
|
||||
|
||||
Ganz ehrlich: Wer sich Passwörter merken oder die gleichen überall verwenden will, ist irgendwann am Ende der Fahnenstange. Ein Passwortmanager löst das Problem, indem er für jede Seite ein eigenes, starkes Passwort generiert und verwaltet.
|
||||
|
||||
Ich muss mir nur noch **ein einziges Hauptpasswort** merken - den Rest übernimmt Vaultwarden.
|
||||
|
||||
### Der große Vorteil hier
|
||||
|
||||
Der entscheidende Punkt: **Die Daten bleiben bei mir.** Mein Passwort-Tresor liegt auf meiner eigenen Hardware und nicht bei einem externen Anbieter. Ich habe die volle Kontrolle darüber, wo meine Passwörter gespeichert sind.
|
||||
|
||||
- **Starke Passwörter** - automatisch generiert, für jeden Dienst ein eigenes
|
||||
- **Zentral verwaltet** - alle Zugangsdaten an einem sicheren Ort
|
||||
- **Selbst kontrolliert** - die Daten liegen in meiner Infrastruktur
|
||||
|
||||
### Sicherheit
|
||||
|
||||
Sicherheit endet natürlich nicht beim Speicherort. Der Tresor ist verschlüsselt und durch das Hauptpasswort geschützt. Weil alles auf meinen eigenen Systemen läuft, entscheide ich selbst über Backups und Schutzkonzepte - und ich weiß genau, was mit meinen Daten passiert.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Willkommen im Homelab
|
||||
|
||||
Dieses Wiki dokumentiert mein kleines Homelab. Hier laufen ein paar Dienste, die mir das tägliche Leben erleichtern - vom Passwortmanager bis zur eigenen Medienbibliothek.
|
||||
|
||||
Alles läuft über **Portainer**, das ist meine Docker-Verwaltungszentrale. Jeder Dienst läuft als eigener *Stack*, damit nichts durcheinanderkommt und einzelne Teile einfach neu aufgebaut oder ausgetauscht werden können.
|
||||
|
||||
## Was hier so läuft
|
||||
|
||||
| Dienst | Was er macht |
|
||||
|---|---|
|
||||
| [Homepage](homepage.md) | Das Dashboard - alle anderen Dienste auf einen Blick |
|
||||
| [AdGuard](adguard.md) | Blockt Werbung und Tracker auf DNS-Ebene |
|
||||
| [Jellyfin & ARR-Stack](jellyfin-arr.md) | Eigene Film- und Seriensammlung plus Automatisierung |
|
||||
| [Paperless](paperless.md) | Digitale Dokumentenablage - nie wieder Papierkram |
|
||||
| [Vaultwarden](vaultwarden.md) | Der eigene Passwortmanager |
|
||||
| [Netdata](netdata.md) | Laufende Überwachung der Systeme |
|
||||
|
||||
## Ein paar Gedanken zum Aufbau
|
||||
|
||||
Ich verfolge mit dem Homelab eine einfache Idee: Die eigenen Dienste sollen laufen, ohne dass ich mich ständig um sie kümmern muss. Deshalb ist alles so aufgebaut, dass es robust und übersichtlich bleibt:
|
||||
|
||||
- **Ein zentraler Zugang** über Homepage - ich muss mir keine IPs und Ports merken
|
||||
- **AdGuard auf DNS-Ebene** - der ganze Haushalt ist automatisch geschützt
|
||||
- **Monitoring mit Netdata** - ich sehe sofort, wenn etwas klemmt
|
||||
|
||||
Zu jedem Dienst gibt es eine eigene Seite mit mehr Details. Schau dich einfach um!
|
||||
|
||||
|
||||
|
||||
### Anleitungen
|
||||
|
||||
Zu jedem Dienst gibt es auch eine Schritt-für-Schritt-Anleitung: [Zu den Anleitungen](../anleitungen/README.md)
|
||||
@@ -0,0 +1,240 @@
|
||||
# TURN-Server (coturn)
|
||||
|
||||
TURN-Relay für Sprach- und Videoanrufe in Matrix/Element.
|
||||
|
||||
| Punkt | Wert |
|
||||
|---|---|
|
||||
| Software | `coturn` |
|
||||
| Öffentlich | `turn.example.com` |
|
||||
| Ports | 3478 TCP/UDP (STUN/TURN), 5349 TCP/UDP (TURNS), 49152–65535 UDP (Relay) |
|
||||
| Authentifizierung | Zeitbegrenzt über gemeinsames Secret |
|
||||
|
||||
## Wozu überhaupt?
|
||||
|
||||
WebRTC verbindet zwei Teilnehmer möglichst direkt. Sitzen beide hinter NAT
|
||||
oder einer restriktiven Firewall, klappt das nicht – der Anruf klingelt, aber
|
||||
es kommt kein Ton an. Der TURN-Server leitet den Medienstrom dann weiter.
|
||||
|
||||
Er ist damit ein **Relay für fremden Datenverkehr**. Ohne Absicherung ist das
|
||||
ein offener Proxy, mit dem sich das interne Netz des Servers erreichen lässt.
|
||||
Der Abschnitt „Absicherung“ unten ist deshalb nicht optional.
|
||||
|
||||
## Kein festes Passwort
|
||||
|
||||
coturn und Synapse teilen sich ein Secret (`static-auth-secret` bzw.
|
||||
`turn_shared_secret`). Fragt ein Client Zugangsdaten an, erzeugt Synapse daraus
|
||||
per HMAC ein kurzlebiges Paar:
|
||||
|
||||
- Benutzername: `<ablaufzeitpunkt>:<benutzer-id>`
|
||||
- Passwort: HMAC-SHA1 über den Benutzernamen mit dem Secret, Base64-kodiert
|
||||
|
||||
coturn rechnet dasselbe nach. Es gibt also **keine angelegten Benutzer** – beide
|
||||
Seiten müssen nur exakt dasselbe Secret kennen.
|
||||
|
||||
## 1. Installation
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y coturn
|
||||
|
||||
# Das Debian-Paket startet ohne diesen Schalter nicht.
|
||||
sudo sed -i 's/^#\?TURNSERVER_ENABLED=.*/TURNSERVER_ENABLED=1/' /etc/default/coturn
|
||||
```
|
||||
|
||||
## 2. Secret erzeugen
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
Derselbe Wert muss in `/etc/turnserver.conf` (`static-auth-secret`) **und** in
|
||||
der Synapse-Konfiguration (`turn_shared_secret`) stehen.
|
||||
|
||||
## 3. Konfiguration
|
||||
|
||||
`turnserver.conf.example` nach `/etc/turnserver.conf` kopieren und anpassen:
|
||||
|
||||
```bash
|
||||
sudo cp turnserver.conf.example /etc/turnserver.conf
|
||||
sudo chown root:turnserver /etc/turnserver.conf
|
||||
sudo chmod 640 /etc/turnserver.conf
|
||||
```
|
||||
|
||||
Zwingend zu ersetzen:
|
||||
|
||||
- `static-auth-secret` – der oben erzeugte Wert
|
||||
- `external-ip` – die **öffentliche** IP des Servers
|
||||
- `realm` / `server-name` – die eigene Domain
|
||||
|
||||
### external-ip nicht vergessen
|
||||
|
||||
Ohne `external-ip` kündigt coturn hinter NAT seine private Adresse als
|
||||
Relay-Kandidat an. Der Anruf baut sich dann scheinbar auf, aber es fließen keine
|
||||
Medien. Zu ermitteln mit:
|
||||
|
||||
```bash
|
||||
curl -s -4 ifconfig.me
|
||||
```
|
||||
|
||||
## 4. Zertifikat lesbar machen
|
||||
|
||||
`turns:` auf Port 5349 braucht Zertifikat und Key. coturn läuft als Benutzer
|
||||
`turnserver` und kommt an Let's-Encrypt-Material standardmäßig nicht heran.
|
||||
|
||||
Statt die Rechte global aufzuweichen, eine eigene Gruppe verwenden:
|
||||
|
||||
```bash
|
||||
sudo groupadd -f ssl-cert
|
||||
sudo usermod -aG ssl-cert turnserver
|
||||
|
||||
sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
|
||||
sudo chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive
|
||||
sudo find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \;
|
||||
```
|
||||
|
||||
> Gruppenmitgliedschaften greifen erst beim **Start eines Prozesses**. Läuft
|
||||
> coturn schon, ist danach ein `systemctl restart coturn` nötig – ein `reload`
|
||||
> reicht nicht.
|
||||
|
||||
Prüfen, ob es wirklich klappt:
|
||||
|
||||
```bash
|
||||
sudo -u turnserver head -c 30 /etc/letsencrypt/live/matrix.example.com/privkey.pem
|
||||
```
|
||||
|
||||
### Erneuerung berücksichtigen
|
||||
|
||||
coturn liest das Zertifikat nur beim Start. Nach einer Erneuerung würde es mit
|
||||
dem alten weiterarbeiten, bis es irgendwann abläuft. Deshalb ein Deploy-Hook
|
||||
nach `/etc/letsencrypt/renewal-hooks/deploy/10-reload-services.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -e
|
||||
chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||||
chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||||
find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \; || true
|
||||
systemctl reload nginx || true
|
||||
systemctl restart coturn || true
|
||||
```
|
||||
|
||||
Ausführbar machen: `sudo chmod +x …/10-reload-services.sh`
|
||||
|
||||
## 5. Absicherung
|
||||
|
||||
Die `denied-peer-ip`-Zeilen in der Beispielkonfiguration sperren alle privaten
|
||||
und reservierten Netze. **Ohne sie kann jeder, der Zugangsdaten bekommt, über
|
||||
den TURN-Server auf `localhost` und das interne Netz zugreifen** – inklusive
|
||||
Diensten, die nur lokal lauschen.
|
||||
|
||||
Weitere gesetzte Schalter:
|
||||
|
||||
| Schalter | Wirkung |
|
||||
|---|---|
|
||||
| `no-tcp-relay` | Kein TCP-Relay (RFC 6062). WebRTC braucht es nicht. |
|
||||
| `no-multicast-peers` | Kein Relay an Multicast-Adressen |
|
||||
| `no-cli` | Telnet-Verwaltungsschnittstelle aus |
|
||||
| `no-tlsv1`, `no-tlsv1_1`, `no-sslv3` | Veraltete Protokolle aus |
|
||||
| `user-quota`, `total-quota` | Begrenzt den Missbrauch |
|
||||
|
||||
## 6. Start und Prüfung
|
||||
|
||||
```bash
|
||||
sudo systemctl enable --now coturn
|
||||
sudo systemctl status coturn
|
||||
```
|
||||
|
||||
Lauschen sollte er auf beiden Ports:
|
||||
|
||||
```bash
|
||||
ss -tuln | grep -E '3478|5349'
|
||||
```
|
||||
|
||||
Im Log darf **nicht** stehen „cannot start TLS and DTLS listeners“. Das heißt,
|
||||
dass coturn das Zertifikat nicht lesen kann:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u coturn | grep -iE 'certificate|private key|TLS'
|
||||
```
|
||||
|
||||
### Relay tatsächlich testen
|
||||
|
||||
Ein Blick auf offene Ports genügt nicht – erst eine echte Allocation beweist,
|
||||
dass Authentifizierung und Relay funktionieren. Zugangsdaten selbst berechnen:
|
||||
|
||||
```bash
|
||||
python3 - 'HIER-DAS-TURN-SECRET' <<'PY'
|
||||
import hashlib, hmac, base64, time, sys
|
||||
secret = sys.argv[1]
|
||||
user = f"{int(time.time()) + 3600}:testuser"
|
||||
pw = base64.b64encode(
|
||||
hmac.new(secret.encode(), user.encode(), hashlib.sha1).digest()).decode()
|
||||
print(user); print(pw)
|
||||
PY
|
||||
```
|
||||
|
||||
Damit eine Allocation anfordern:
|
||||
|
||||
```bash
|
||||
turnutils_uclient -u '<benutzername>' -w '<passwort>' -y -n 2 turn.example.com -p 3478
|
||||
```
|
||||
|
||||
Erfolg sieht so aus – gleich viele gesendete und empfangene Nachrichten, keine
|
||||
verlorenen Pakete:
|
||||
|
||||
```
|
||||
start_mclient: tot_send_msgs=8, tot_recv_msgs=8
|
||||
Total lost packets 0 (0.000000%)
|
||||
```
|
||||
|
||||
Gegenprobe mit falschem Passwort muss `Cannot complete Allocation` liefern.
|
||||
|
||||
> `turnutils_uclient -T …` schlägt mit `error 442` fehl. Das ist **kein**
|
||||
> Fehler: `-T` fordert ein TCP-Relay an, das per `no-tcp-relay` gesperrt ist.
|
||||
|
||||
TLS-Listener separat prüfen:
|
||||
|
||||
```bash
|
||||
openssl s_client -connect turn.example.com:5349 -servername turn.example.com </dev/null
|
||||
```
|
||||
|
||||
Der Domainname muss im Zertifikat stehen:
|
||||
|
||||
```bash
|
||||
openssl s_client -connect turn.example.com:5349 </dev/null 2>/dev/null \
|
||||
| openssl x509 -noout -ext subjectAltName
|
||||
```
|
||||
|
||||
## 7. Verbindung zu Synapse
|
||||
|
||||
In `/etc/matrix-synapse/conf.d/homelab.yaml` stehen `turn_uris` und
|
||||
`turn_shared_secret`. Nach dem Neustart lässt sich prüfen, was ein Client
|
||||
wirklich ausgeliefert bekommt:
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: Bearer <access-token>" \
|
||||
https://matrix.example.com/_matrix/client/v3/voip/turnServer
|
||||
```
|
||||
|
||||
Die zurückgegebenen Zugangsdaten sollten mit `turnutils_uclient` funktionieren –
|
||||
das ist der eigentliche Ende-zu-Ende-Nachweis.
|
||||
|
||||
## Firewall
|
||||
|
||||
Freizugeben sind:
|
||||
|
||||
- 3478 TCP **und** UDP
|
||||
- 5349 TCP **und** UDP
|
||||
- 49152–65535 UDP (Relay-Bereich)
|
||||
|
||||
Der Relay-Bereich wird oft vergessen. Ohne ihn kommt die Allocation zustande,
|
||||
aber es fließen keine Medien.
|
||||
|
||||
## Stolpersteine
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Port 5349 fehlt, Log meldet „certificate file is not set properly“ | `turnserver` darf den Key nicht lesen, oder Dienst lief schon vor der Gruppenänderung |
|
||||
| Anruf klingelt, kein Ton/Bild | `external-ip` fehlt oder Relay-Ports gesperrt |
|
||||
| `error 442` im Test | `-T` verwendet; TCP-Relay ist absichtlich aus |
|
||||
| Authentifizierung schlägt fehl | Secret in coturn und Synapse verschieden |
|
||||
| Nach Zertifikatserneuerung bricht TURNS ab | Deploy-Hook fehlt |
|
||||
@@ -0,0 +1,62 @@
|
||||
# ===========================================================================
|
||||
# coturn fuer Matrix/Element-Anrufe (turn.example.com)
|
||||
# ===========================================================================
|
||||
|
||||
listening-port=3478
|
||||
tls-listening-port=5349
|
||||
|
||||
# Auf allen Adressen lauschen, aber die oeffentliche IP in Kandidaten melden.
|
||||
# Ohne external-ip kuendigt der Server hinter NAT die private IP an und
|
||||
# Verbindungen scheitern.
|
||||
external-ip=HIER-DIE-OEFFENTLICHE-IP
|
||||
|
||||
realm=example.com
|
||||
server-name=turn.example.com
|
||||
|
||||
# Zeitbegrenzte Anmeldedaten. Synapse leitet daraus mit demselben Secret
|
||||
# kurzlebige Zugangsdaten fuer die Clients ab -- es gibt keine festen Nutzer.
|
||||
use-auth-secret
|
||||
static-auth-secret=HIER-DAS-TURN-SECRET
|
||||
|
||||
# TLS-Material (SAN turn.example.com liegt im Matrix-Zertifikat)
|
||||
cert=/etc/letsencrypt/live/matrix.example.com/fullchain.pem
|
||||
pkey=/etc/letsencrypt/live/matrix.example.com/privkey.pem
|
||||
|
||||
# Veraltete/schwache Verfahren abschalten
|
||||
no-tlsv1
|
||||
no-tlsv1_1
|
||||
no-sslv3
|
||||
fingerprint
|
||||
|
||||
# Relay-Portbereich
|
||||
min-port=49152
|
||||
max-port=65535
|
||||
|
||||
# --------------------------- Absicherung ----------------------------------
|
||||
# Ohne diese Sperren laesst sich der TURN-Server als Sprungbrett in das
|
||||
# eigene interne Netz und auf localhost missbrauchen.
|
||||
no-multicast-peers
|
||||
no-cli
|
||||
no-tcp-relay
|
||||
|
||||
denied-peer-ip=0.0.0.0-0.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=100.64.0.0-100.127.255.255
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=169.254.0.0-169.254.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.0.0.0-192.0.0.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
denied-peer-ip=198.18.0.0-198.19.255.255
|
||||
denied-peer-ip=240.0.0.0-255.255.255.255
|
||||
denied-peer-ip=::1
|
||||
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
||||
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
||||
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||
|
||||
# Missbrauch begrenzen
|
||||
user-quota=12
|
||||
total-quota=1200
|
||||
|
||||
syslog
|
||||
@@ -0,0 +1,141 @@
|
||||
# Element Web
|
||||
|
||||
Web-Client für den eigenen Matrix-Server, ausgeliefert als statische Dateien
|
||||
über nginx.
|
||||
|
||||
| Punkt | Wert |
|
||||
|---|---|
|
||||
| Software | `element-web` (Release-Tarball) |
|
||||
| Öffentlich | `https://element.example.com` |
|
||||
| Verzeichnis | `/var/www/element` |
|
||||
|
||||
## Warum eine eigene Domain?
|
||||
|
||||
Element **darf nicht** unter derselben Domain laufen wie die Client-API des
|
||||
Homeservers. Beide teilen sich sonst denselben Origin, und eine Schwachstelle im
|
||||
Client könnte direkt auf die API zugreifen. Deshalb:
|
||||
|
||||
- `matrix.example.com` → nur Synapse
|
||||
- `element.example.com` → nur der Web-Client
|
||||
|
||||
## 1. Herunterladen und Signatur prüfen
|
||||
|
||||
```bash
|
||||
VER=$(curl -s https://api.github.com/repos/element-hq/element-web/releases/latest \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['tag_name'])")
|
||||
echo "$VER"
|
||||
|
||||
curl -fLO "https://github.com/element-hq/element-web/releases/download/${VER}/element-${VER}.tar.gz"
|
||||
curl -fLO "https://github.com/element-hq/element-web/releases/download/${VER}/element-${VER}.tar.gz.asc"
|
||||
```
|
||||
|
||||
Die Signatur zu prüfen ist hier keine Formsache: das Paket läuft später im
|
||||
Browser mit vollem Zugriff auf die Sitzung.
|
||||
|
||||
```bash
|
||||
curl -fsSL -o element-release-key.gpg https://packages.element.io/element-release-key.gpg
|
||||
gpg --import element-release-key.gpg
|
||||
gpg --verify element-${VER}.tar.gz.asc element-${VER}.tar.gz
|
||||
```
|
||||
|
||||
Erwartet wird `Good signature from "Element Releases <releases@riot.im>"` mit dem
|
||||
Fingerprint `712B FBEE 92DC A452 52DB 17D7 C7BE 97EF A179 B100`.
|
||||
|
||||
Die Meldung „This key is not certified with a trusted signature“ ist dabei
|
||||
normal – sie besagt nur, dass der Schlüssel lokal nicht als vertrauenswürdig
|
||||
markiert wurde. Entscheidend ist, dass der Fingerprint stimmt.
|
||||
|
||||
## 2. Ausrollen
|
||||
|
||||
```bash
|
||||
sudo tar xzf element-${VER}.tar.gz -C /var/www
|
||||
sudo rm -rf /var/www/element
|
||||
sudo mv /var/www/element-${VER} /var/www/element
|
||||
sudo chown -R www-data:www-data /var/www/element
|
||||
```
|
||||
|
||||
## 3. Konfiguration
|
||||
|
||||
`config.json.example` nach `/var/www/element/config.json` kopieren und anpassen.
|
||||
Wichtig ist der Unterschied der beiden Felder:
|
||||
|
||||
- `base_url` → wo die API tatsächlich liegt (`https://matrix.example.com`)
|
||||
- `server_name` → was dem Benutzer angezeigt wird (`example.com`)
|
||||
|
||||
```json
|
||||
{
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.example.com",
|
||||
"server_name": "example.com"
|
||||
}
|
||||
},
|
||||
"disable_guests": true
|
||||
}
|
||||
```
|
||||
|
||||
## 4. nginx
|
||||
|
||||
`nginx/element.example.com.conf` und `nginx/element-security.conf` kopieren:
|
||||
|
||||
```bash
|
||||
sudo cp nginx/element-security.conf /etc/nginx/snippets/
|
||||
sudo cp nginx/element.example.com.conf /etc/nginx/sites-available/
|
||||
sudo ln -s /etc/nginx/sites-available/element.example.com.conf /etc/nginx/sites-enabled/
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
### Zwei Fallstricke in der nginx-Konfiguration
|
||||
|
||||
**Caching.** `index.html`, `config.json` und `version` dürfen nicht dauerhaft
|
||||
gecacht werden. Sonst zeigt der Browser nach einem Update weiter die alte
|
||||
Version oder alte Serverdaten. Die Dateien unter `/bundles/` tragen dagegen
|
||||
einen Hash im Namen und dürfen unbegrenzt gecacht werden.
|
||||
|
||||
**Header-Vererbung.** nginx vererbt `add_header` **nicht**, sobald ein
|
||||
`location`-Block eigene `add_header`-Direktiven enthält. Da `location /` per
|
||||
`try_files` auf `/index.html` verweist und dieser Block ein eigenes
|
||||
`Cache-Control` setzt, würden die Sicherheitsheader dort stillschweigend
|
||||
verschwinden. Deshalb liegen sie in einem Snippet, das in **jedem** betroffenen
|
||||
Block eingebunden wird.
|
||||
|
||||
Kontrolle:
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -D - https://element.example.com/ | grep -iE '^x-|^strict|^referrer'
|
||||
```
|
||||
|
||||
Es müssen `x-frame-options`, `x-content-type-options`, `referrer-policy` und
|
||||
`strict-transport-security` auftauchen.
|
||||
|
||||
## 5. Prüfung
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}\n" https://element.example.com/
|
||||
curl -s https://element.example.com/version
|
||||
curl -s https://element.example.com/config.json
|
||||
```
|
||||
|
||||
Danach im Browser öffnen und mit einem Konto anmelden. Der Anmeldedialog sollte
|
||||
`example.com` als Server anzeigen, nicht `matrix.example.com`.
|
||||
|
||||
## Aktualisieren
|
||||
|
||||
Element ist rein statisch – ein Update ist ein Austausch des Verzeichnisses.
|
||||
Die `config.json` liegt im Verzeichnis und muss dabei gesichert werden:
|
||||
|
||||
```bash
|
||||
sudo cp /var/www/element/config.json /tmp/element-config.json
|
||||
# ... neue Version wie oben entpacken ...
|
||||
sudo cp /tmp/element-config.json /var/www/element/config.json
|
||||
sudo chown -R www-data:www-data /var/www/element
|
||||
```
|
||||
|
||||
## Stolpersteine
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Nach Update alte Version im Browser | `index.html`/`version` wurden gecacht |
|
||||
| Sicherheitsheader fehlen | `add_header`-Vererbung durch eigenen `location`-Block gekippt |
|
||||
| Anmeldung zeigt falschen Servernamen | `server_name` in `config.json` falsch |
|
||||
| Konfiguration nach Update weg | `config.json` liegt im ausgetauschten Verzeichnis |
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"default_server_config": {
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.example.com",
|
||||
"server_name": "example.com"
|
||||
}
|
||||
},
|
||||
"brand": "Element",
|
||||
"default_country_code": "DE",
|
||||
"disable_custom_urls": false,
|
||||
"disable_guests": true,
|
||||
"disable_3pid_login": true,
|
||||
"show_labs_settings": true,
|
||||
"room_directory": {
|
||||
"servers": ["example.com"]
|
||||
},
|
||||
"setting_defaults": {
|
||||
"use_system_theme": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Sicherheitsheader fuer Element. Muss in JEDEN location-Block eingebunden
|
||||
# werden, der selbst add_header verwendet: nginx vererbt add_header nicht,
|
||||
# sobald ein Block eigene Direktiven setzt.
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy no-referrer always;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
@@ -0,0 +1,51 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name element.example.com;
|
||||
location /.well-known/acme-challenge/ { root /var/www/html; }
|
||||
location / { return 301 https://$host$request_uri; }
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name element.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
root /var/www/element;
|
||||
index index.html;
|
||||
|
||||
# Element laedt Raum-Inhalte in Iframes/Worker; diese Header sind die
|
||||
# von Element empfohlene Mindestabsicherung.
|
||||
include /etc/nginx/snippets/element-security.conf;
|
||||
|
||||
# config.json und index.html duerfen nicht dauerhaft gecacht werden,
|
||||
# sonst zeigen Clients nach einem Update alte Serverdaten.
|
||||
location = /config.json {
|
||||
include /etc/nginx/snippets/element-security.conf;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
}
|
||||
location = /index.html {
|
||||
include /etc/nginx/snippets/element-security.conf;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
}
|
||||
location = /version {
|
||||
include /etc/nginx/snippets/element-security.conf;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
}
|
||||
|
||||
# Gehashte Bundle-Dateien sind unveraenderlich.
|
||||
location /bundles/ {
|
||||
include /etc/nginx/snippets/element-security.conf;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
# Gitea
|
||||
|
||||
Selbstgehostetes Git-Hosting.
|
||||
|
||||
| Punkt | Wert |
|
||||
|---|---|
|
||||
| Software | Gitea (Binary nach `/usr/local/bin/gitea`) |
|
||||
| Öffentlich | `https://git.example.com` |
|
||||
| Interner Port | `127.0.0.1:3001` |
|
||||
| Datenbank | SQLite (`/var/lib/gitea/data/gitea.db`) |
|
||||
| Dienstbenutzer | `git` |
|
||||
|
||||
## 1. Benutzer und Verzeichnisse
|
||||
|
||||
```bash
|
||||
sudo adduser --system --shell /bin/bash --gecos 'Gitea' \
|
||||
--group --disabled-password --home /home/git git
|
||||
|
||||
sudo mkdir -p /var/lib/gitea/{custom,data,log} /etc/gitea
|
||||
sudo chown -R git:git /var/lib/gitea
|
||||
sudo chmod 750 /var/lib/gitea
|
||||
|
||||
# Waehrend der Ersteinrichtung muss Gitea in /etc/gitea schreiben duerfen.
|
||||
sudo chown root:git /etc/gitea
|
||||
sudo chmod 770 /etc/gitea
|
||||
```
|
||||
|
||||
## 2. Binary installieren
|
||||
|
||||
```bash
|
||||
VER=$(curl -s https://api.github.com/repos/go-gitea/gitea/releases/latest \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['tag_name'].lstrip('v'))")
|
||||
curl -fLo /tmp/gitea "https://dl.gitea.com/gitea/${VER}/gitea-${VER}-linux-amd64"
|
||||
sudo install -m 755 /tmp/gitea /usr/local/bin/gitea
|
||||
gitea --version
|
||||
```
|
||||
|
||||
## 3. systemd-Unit
|
||||
|
||||
Nach `/etc/systemd/system/gitea.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Gitea (Git with a cup of tea)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=git
|
||||
Group=git
|
||||
WorkingDirectory=/var/lib/gitea
|
||||
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now gitea
|
||||
```
|
||||
|
||||
## 4. Ersteinrichtung
|
||||
|
||||
Gitea zunächst über nginx erreichbar machen (siehe `services/nginx/`), dann
|
||||
`https://git.example.com` im Browser öffnen und den Installationsdialog
|
||||
ausfüllen. Danach die Rechte auf `/etc/gitea` wieder einschränken:
|
||||
|
||||
```bash
|
||||
sudo chmod 750 /etc/gitea
|
||||
sudo chmod 640 /etc/gitea/app.ini
|
||||
```
|
||||
|
||||
Das ist wichtig: `app.ini` enthält den `SECRET_KEY` und den
|
||||
`INTERNAL_TOKEN`. Bleibt die Datei für alle lesbar, kann jeder lokale Benutzer
|
||||
Sitzungen fälschen.
|
||||
|
||||
## 5. Wichtige Werte in `app.ini`
|
||||
|
||||
```ini
|
||||
[server]
|
||||
HTTP_PORT = 3001
|
||||
ROOT_URL = https://git.example.com/
|
||||
DOMAIN = git.example.com
|
||||
SSH_PORT = 22
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /var/lib/gitea/data/gitea.db
|
||||
```
|
||||
|
||||
`ROOT_URL` muss stimmen, sonst erzeugt Gitea Klon-URLs und Weiterleitungen mit
|
||||
falschem Hostnamen.
|
||||
|
||||
Nach der Ersteinrichtung sollte die offene Registrierung geschlossen werden:
|
||||
|
||||
```ini
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
```
|
||||
|
||||
## 6. Sicherung
|
||||
|
||||
```bash
|
||||
sudo systemctl stop gitea
|
||||
sudo tar czf gitea-$(date +%F).tar.gz /var/lib/gitea /etc/gitea/app.ini
|
||||
sudo systemctl start gitea
|
||||
```
|
||||
|
||||
Die SQLite-Datenbank sollte nicht im laufenden Betrieb kopiert werden.
|
||||
Alternativ Giteas eigener Dump:
|
||||
|
||||
```bash
|
||||
sudo -u git gitea dump --config /etc/gitea/app.ini
|
||||
```
|
||||
|
||||
## Über die API arbeiten
|
||||
|
||||
Repository anlegen, ohne den Browser zu benutzen:
|
||||
|
||||
```bash
|
||||
curl -u '<benutzer>:<passwort-oder-token>' \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST -d '{"name":"projekt","private":true,"default_branch":"main"}' \
|
||||
https://git.example.com/api/v1/user/repos
|
||||
```
|
||||
|
||||
Issue anlegen:
|
||||
|
||||
```bash
|
||||
curl -u '<benutzer>:<token>' -H "Content-Type: application/json" \
|
||||
-X POST -d '{"title":"Titel","body":"Beschreibung"}' \
|
||||
https://git.example.com/api/v1/repos/<benutzer>/<repo>/issues
|
||||
```
|
||||
|
||||
> Besser ein Zugriffstoken (Einstellungen → Anwendungen) statt des Passworts
|
||||
> verwenden und den Geltungsbereich einschränken.
|
||||
|
||||
## Stolpersteine
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Falsche Klon-URLs | `ROOT_URL`/`DOMAIN` stimmen nicht |
|
||||
| Ersteinrichtung schlägt fehl | `/etc/gitea` für den Benutzer `git` nicht schreibbar |
|
||||
| `app.ini` weltlesbar | Rechte nach der Einrichtung nicht zurückgesetzt |
|
||||
| Beschädigte Sicherung | SQLite im laufenden Betrieb kopiert |
|
||||
@@ -0,0 +1,270 @@
|
||||
# Matrix-Homeserver (Synapse)
|
||||
|
||||
Eigener Matrix-Server mit PostgreSQL, nginx als TLS-Terminierung und
|
||||
Well-Known-Delegation, damit die Benutzer-IDs kurz bleiben.
|
||||
|
||||
| Punkt | Wert |
|
||||
|---|---|
|
||||
| Software | Synapse (Paket `matrix-synapse-py3` von packages.matrix.org) |
|
||||
| Datenbank | PostgreSQL |
|
||||
| Interner Listener | `127.0.0.1:8008` (kein TLS, das macht nginx) |
|
||||
| Öffentlich | `https://matrix.example.com` |
|
||||
| Benutzer-IDs | `@name:example.com` |
|
||||
|
||||
## Warum zwei Domains?
|
||||
|
||||
Das ist der Punkt, an dem die meisten Anleitungen unnötig kompliziert werden.
|
||||
|
||||
Der `server_name` ist der **hintere Teil jeder Benutzer-ID**. Setzt man ihn auf
|
||||
`matrix.example.com`, heißen alle Benutzer `@name:matrix.example.com` – unschön
|
||||
und nicht mehr änderbar, denn der `server_name` lässt sich nachträglich **nicht**
|
||||
umstellen, ohne alle Konten und Räume zu verlieren.
|
||||
|
||||
Deshalb:
|
||||
|
||||
- `server_name: example.com` → IDs lauten `@name:example.com`
|
||||
- Synapse läuft aber auf `matrix.example.com`
|
||||
- Zwei kleine JSON-Dokumente unter `https://example.com/.well-known/matrix/…`
|
||||
verweisen andere Server und Clients dorthin
|
||||
|
||||
Die Apex-Domain muss dafür **nur diese zwei Dateien** ausliefern, sonst nichts.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Debian/Ubuntu mit root-Rechten
|
||||
- PostgreSQL
|
||||
- nginx
|
||||
- certbot
|
||||
- DNS-A-Records auf den Server: `example.com`, `matrix.example.com`
|
||||
|
||||
## 1. Paketquelle und Installation
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings
|
||||
sudo curl -fsSL -o /etc/apt/keyrings/matrix-org-archive-keyring.gpg \
|
||||
https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
|
||||
|
||||
# <suite> ist der Codename der Distribution, z. B. noble, trixie, resolute
|
||||
echo "deb [signed-by=/etc/apt/keyrings/matrix-org-archive-keyring.gpg] \
|
||||
https://packages.matrix.org/debian/ <suite> main" \
|
||||
| sudo tee /etc/apt/sources.list.d/matrix-org.list
|
||||
|
||||
sudo apt-get update
|
||||
```
|
||||
|
||||
Ob es für die eigene Distribution ein Paket gibt, lässt sich vorher prüfen:
|
||||
|
||||
```bash
|
||||
curl -s https://packages.matrix.org/debian/dists/<suite>/main/binary-amd64/Packages \
|
||||
| grep -E "^Package:|^Version:"
|
||||
```
|
||||
|
||||
Installation. Der `server_name` wird per debconf vorbelegt, damit die
|
||||
Installation nicht interaktiv nachfragt:
|
||||
|
||||
```bash
|
||||
echo "matrix-synapse-py3 matrix-synapse/server-name string example.com" \
|
||||
| sudo debconf-set-selections
|
||||
echo "matrix-synapse-py3 matrix-synapse/report-stats boolean false" \
|
||||
| sudo debconf-set-selections
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y matrix-synapse-py3
|
||||
```
|
||||
|
||||
> **Wichtig:** Der hier gesetzte `server_name` ist endgültig. Er landet in
|
||||
> `/etc/matrix-synapse/conf.d/server_name.yaml`.
|
||||
|
||||
## 2. Datenbank
|
||||
|
||||
Synapse besteht auf `C`-Collation. Eine mit den üblichen `de_DE.UTF-8`-Vorgaben
|
||||
angelegte Datenbank wird beim Start **abgelehnt**:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql <<'SQL'
|
||||
CREATE ROLE synapse_user LOGIN PASSWORD 'HIER-EIN-STARKES-PASSWORT';
|
||||
CREATE DATABASE synapse
|
||||
ENCODING 'UTF8'
|
||||
LC_COLLATE='C'
|
||||
LC_CTYPE='C'
|
||||
template=template0
|
||||
OWNER synapse_user;
|
||||
SQL
|
||||
```
|
||||
|
||||
Kontrolle – unter `Collate`/`Ctype` muss `C` stehen:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql -c "\l synapse"
|
||||
```
|
||||
|
||||
## 3. Konfiguration
|
||||
|
||||
Eigene Einstellungen gehören nach `/etc/matrix-synapse/conf.d/`. Die
|
||||
`homeserver.yaml` wird vom Paket verwaltet und bei Upgrades überschrieben.
|
||||
|
||||
Der von der Installation angelegte SQLite-Block in `homeserver.yaml` muss
|
||||
entfernt oder auskommentiert werden, sonst kollidiert er mit der conf.d-Datei:
|
||||
|
||||
```bash
|
||||
sudo sed -i '/^database:/,/database: \/var\/lib\/matrix-synapse\/homeserver.db/d' \
|
||||
/etc/matrix-synapse/homeserver.yaml
|
||||
```
|
||||
|
||||
Dann die Vorlagen aus diesem Verzeichnis kopieren und die Platzhalter füllen:
|
||||
|
||||
```bash
|
||||
sudo cp conf.d/database.yaml.example /etc/matrix-synapse/conf.d/database.yaml
|
||||
sudo cp conf.d/homelab.yaml.example /etc/matrix-synapse/conf.d/homelab.yaml
|
||||
|
||||
sudo chown root:matrix-synapse /etc/matrix-synapse/conf.d/*.yaml
|
||||
sudo chmod 640 /etc/matrix-synapse/conf.d/*.yaml
|
||||
```
|
||||
|
||||
### Secrets explizit setzen
|
||||
|
||||
Fehlen `macaroon_secret_key` und `form_secret`, leitet Synapse sie aus dem
|
||||
Signing-Key ab und warnt beim Start. Wird der Signing-Key später ersetzt, sind
|
||||
schlagartig **alle Access-Tokens ungültig**. Deshalb einmalig festlegen:
|
||||
|
||||
```bash
|
||||
sudo tee /etc/matrix-synapse/conf.d/secrets.yaml >/dev/null <<EOF
|
||||
macaroon_secret_key: "$(openssl rand -base64 48 | tr -d '\n')"
|
||||
form_secret: "$(openssl rand -base64 48 | tr -d '\n')"
|
||||
EOF
|
||||
sudo chown root:matrix-synapse /etc/matrix-synapse/conf.d/secrets.yaml
|
||||
sudo chmod 640 /etc/matrix-synapse/conf.d/secrets.yaml
|
||||
```
|
||||
|
||||
Registrierungs-Secret separat ablegen, damit es nicht in einer YAML steht:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32 | sudo tee /etc/matrix-synapse/registration_shared_secret >/dev/null
|
||||
sudo chown root:matrix-synapse /etc/matrix-synapse/registration_shared_secret
|
||||
sudo chmod 640 /etc/matrix-synapse/registration_shared_secret
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart matrix-synapse
|
||||
sudo systemctl status matrix-synapse
|
||||
curl -s http://127.0.0.1:8008/_matrix/client/versions
|
||||
```
|
||||
|
||||
## 4. nginx
|
||||
|
||||
`nginx/matrix.conf` nach `/etc/nginx/sites-available/` kopieren, verlinken und
|
||||
ein Zertifikat holen:
|
||||
|
||||
```bash
|
||||
sudo certbot certonly --webroot -w /var/www/html -d matrix.example.com
|
||||
sudo ln -s /etc/nginx/sites-available/matrix.example.com \
|
||||
/etc/nginx/sites-enabled/
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
Zwei Details in der Konfiguration sind nicht optional:
|
||||
|
||||
- `client_max_body_size` muss mindestens so groß sein wie `max_upload_size` in
|
||||
Synapse, sonst brechen Uploads mit einem nginx-413 ab, bevor Synapse sie sieht.
|
||||
- `proxy_read_timeout` muss großzügig sein. Clients halten `/sync` als
|
||||
Long-Poll offen; beim nginx-Standard von 60 Sekunden reißt die Verbindung
|
||||
ständig ab.
|
||||
|
||||
## 5. Well-Known-Delegation
|
||||
|
||||
Im vHost der **Apex-Domain** (`example.com`):
|
||||
|
||||
```nginx
|
||||
location = /.well-known/matrix/server {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
return 200 '{"m.server": "matrix.example.com:443"}';
|
||||
}
|
||||
|
||||
location = /.well-known/matrix/client {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
return 200 '{"m.homeserver": {"base_url": "https://matrix.example.com"}}';
|
||||
}
|
||||
```
|
||||
|
||||
Der `Access-Control-Allow-Origin`-Header ist beim Client-Dokument
|
||||
zwingend – ohne ihn verweigern Browser-Clients die Abfrage.
|
||||
|
||||
Prüfen:
|
||||
|
||||
```bash
|
||||
curl -s https://example.com/.well-known/matrix/server
|
||||
curl -s https://example.com/.well-known/matrix/client
|
||||
```
|
||||
|
||||
## 6. Admin-Benutzer anlegen
|
||||
|
||||
Die Registrierung ist bewusst geschlossen (`enable_registration: false`), sonst
|
||||
wird der Server binnen Tagen als Spam-Relay missbraucht. Benutzer werden per
|
||||
Shared Secret angelegt.
|
||||
|
||||
`register_new_matrix_user` liest **nur** die mit `-c` übergebene Datei. Liegt
|
||||
das Secret in `conf.d/`, findet es der Befehl nicht – deshalb direkt übergeben:
|
||||
|
||||
```bash
|
||||
SECRET=$(sudo cat /etc/matrix-synapse/registration_shared_secret)
|
||||
sudo register_new_matrix_user -k "$SECRET" \
|
||||
-u admin -p 'PASSWORT' -a http://127.0.0.1:8008
|
||||
```
|
||||
|
||||
`-a` macht den Benutzer zum Server-Admin. Kontrolle:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql -d synapse -c "SELECT name, admin FROM users;"
|
||||
```
|
||||
|
||||
## 7. Funktionsprüfung
|
||||
|
||||
```bash
|
||||
# Login über die öffentliche URL
|
||||
curl -s -X POST https://matrix.example.com/_matrix/client/v3/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"type":"m.login.password",
|
||||
"identifier":{"type":"m.id.user","user":"admin"},
|
||||
"password":"PASSWORT"}'
|
||||
|
||||
# Föderation von außen prüfen (offizieller Tester)
|
||||
curl -s "https://federationtester.matrix.org/api/report?server_name=example.com" \
|
||||
| python3 -c "import json,sys; print('FederationOK:', json.load(sys.stdin)['FederationOK'])"
|
||||
```
|
||||
|
||||
`FederationOK: True` ist das Ziel. Schlägt es fehl, liegt es fast immer an der
|
||||
Well-Known-Datei oder an einer unvollständigen Zertifikatskette.
|
||||
|
||||
## Betrieb
|
||||
|
||||
```bash
|
||||
sudo systemctl status matrix-synapse
|
||||
sudo journalctl -u matrix-synapse -f
|
||||
```
|
||||
|
||||
Datenbank sichern:
|
||||
|
||||
```bash
|
||||
sudo -u postgres pg_dump -Fc synapse > synapse-$(date +%F).dump
|
||||
```
|
||||
|
||||
Mitzusichern sind außerdem:
|
||||
|
||||
- `/etc/matrix-synapse/homeserver.signing.key` – **ohne diesen Schlüssel ist der
|
||||
Server für andere Server eine andere Identität.** Nicht ersetzbar.
|
||||
- `/etc/matrix-synapse/conf.d/` – enthält die Secrets
|
||||
- `/var/lib/matrix-synapse/media` – hochgeladene Dateien
|
||||
|
||||
## Stolpersteine
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Start bricht mit Collation-Fehler ab | Datenbank ohne `LC_COLLATE='C'` angelegt |
|
||||
| „Config is missing macaroon_secret_key“ | `secrets.yaml` fehlt |
|
||||
| Uploads scheitern mit 413 | `client_max_body_size` < `max_upload_size` |
|
||||
| Clients verlieren dauernd die Verbindung | `proxy_read_timeout` zu klein |
|
||||
| Föderation schlägt fehl | Well-Known falsch oder Zertifikatskette unvollständig |
|
||||
| IDs lauten `@name:matrix.example.com` | `server_name` falsch gesetzt – nicht mehr korrigierbar |
|
||||
@@ -0,0 +1,12 @@
|
||||
# -> /etc/matrix-synapse/conf.d/database.yaml (root:matrix-synapse, 0640)
|
||||
database:
|
||||
name: psycopg2
|
||||
txn_limit: 10000
|
||||
args:
|
||||
user: synapse_user
|
||||
password: HIER-DAS-DATENBANK-PASSWORT
|
||||
dbname: synapse
|
||||
host: 127.0.0.1
|
||||
port: 5432
|
||||
cp_min: 5
|
||||
cp_max: 10
|
||||
@@ -0,0 +1,51 @@
|
||||
# -> /etc/matrix-synapse/conf.d/homelab.yaml (root:matrix-synapse, 0640)
|
||||
#
|
||||
# Liegt bewusst in conf.d: homeserver.yaml wird vom Paket verwaltet und bei
|
||||
# Upgrades ueberschrieben.
|
||||
|
||||
# Synapse lauscht nur lokal; TLS und Port 443 uebernimmt nginx davor.
|
||||
listeners:
|
||||
- port: 8008
|
||||
tls: false
|
||||
type: http
|
||||
x_forwarded: true
|
||||
bind_addresses: ['127.0.0.1']
|
||||
resources:
|
||||
- names: [client, federation]
|
||||
compress: false
|
||||
|
||||
public_baseurl: https://matrix.example.com/
|
||||
|
||||
# Offene Registrierung aus: sonst wird der Server als Spam-Relay missbraucht.
|
||||
enable_registration: false
|
||||
registration_shared_secret_path: /etc/matrix-synapse/registration_shared_secret
|
||||
|
||||
max_upload_size: 100M
|
||||
url_preview_enabled: true
|
||||
# Ohne diese Sperrliste laesst sich der Link-Vorschau-Dienst benutzen, um das
|
||||
# interne Netz des Servers zu scannen.
|
||||
url_preview_ip_range_blacklist:
|
||||
- '127.0.0.0/8'
|
||||
- '10.0.0.0/8'
|
||||
- '172.16.0.0/12'
|
||||
- '192.168.0.0/16'
|
||||
- '100.64.0.0/10'
|
||||
- '169.254.0.0/16'
|
||||
- '::1/128'
|
||||
- 'fe80::/64'
|
||||
- 'fc00::/7'
|
||||
|
||||
# --------------------------- VoIP / TURN ----------------------------------
|
||||
turn_uris:
|
||||
- "turn:turn.example.com:3478?transport=udp"
|
||||
- "turn:turn.example.com:3478?transport=tcp"
|
||||
- "turns:turn.example.com:5349?transport=udp"
|
||||
- "turns:turn.example.com:5349?transport=tcp"
|
||||
# Muss exakt dem static-auth-secret in der turnserver.conf entsprechen.
|
||||
turn_shared_secret: "HIER-DAS-TURN-SECRET"
|
||||
turn_user_lifetime: 86400000
|
||||
turn_allow_guests: false
|
||||
|
||||
trusted_key_servers:
|
||||
- server_name: "matrix.org"
|
||||
suppress_key_server_warning: true
|
||||
@@ -0,0 +1,7 @@
|
||||
# -> /etc/matrix-synapse/conf.d/secrets.yaml (root:matrix-synapse, 0640)
|
||||
#
|
||||
# Mit "openssl rand -base64 48" erzeugen. Fehlen die Werte, leitet Synapse sie
|
||||
# aus dem Signing-Key ab; ein spaeterer Wechsel des Signing-Keys wuerde dann
|
||||
# alle Access-Tokens ungueltig machen.
|
||||
macaroon_secret_key: "HIER-EIN-ZUFALLSWERT"
|
||||
form_secret: "HIER-EIN-ZUFALLSWERT"
|
||||
@@ -0,0 +1,41 @@
|
||||
# Synapse-Client- und Federation-API. Synapse selbst lauscht nur auf
|
||||
# 127.0.0.1:8008; TLS und Port 443 macht nginx.
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name matrix.example.com;
|
||||
location /.well-known/acme-challenge/ { root /var/www/html; }
|
||||
location / { return 301 https://$host$request_uri; }
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name matrix.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Muss mindestens so gross sein wie max_upload_size in Synapse.
|
||||
client_max_body_size 100M;
|
||||
|
||||
location ~ ^(/_matrix|/_synapse/client) {
|
||||
proxy_pass http://127.0.0.1:8008;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_http_version 1.1;
|
||||
# Long-Polling (/sync) darf nicht nach 60s abgeschnitten werden.
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 200 'Matrix homeserver. Web client: https://element.example.com\n';
|
||||
default_type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
# nginx und TLS
|
||||
|
||||
nginx ist der einzige Dienst, der von außen erreichbar ist. Alle Anwendungen
|
||||
lauschen nur auf `127.0.0.1` und werden von hier weitergereicht.
|
||||
|
||||
## Aufteilung
|
||||
|
||||
| Domain | Ziel |
|
||||
|---|---|
|
||||
| `example.com` | statische Seite + Matrix-Well-Known |
|
||||
| `matrix.example.com` | Synapse auf `127.0.0.1:8008` |
|
||||
| `element.example.com` | statische Dateien `/var/www/element` |
|
||||
| `wiki.example.com` | Wiki.js auf `127.0.0.1:3000` |
|
||||
| `git.example.com` | Gitea auf `127.0.0.1:3001` |
|
||||
| `turn.example.com` | **kein** vHost – coturn nutzt das Zertifikat direkt |
|
||||
|
||||
`turn.example.com` läuft bewusst nicht über nginx. coturn spricht kein HTTP; es
|
||||
braucht nur ein gültiges Zertifikat für Port 5349. Der Name muss trotzdem in
|
||||
einem Zertifikat stehen, deshalb wird er beim Ausstellen mit angegeben.
|
||||
|
||||
## Zertifikate
|
||||
|
||||
Ein Zertifikat für mehrere Namen:
|
||||
|
||||
```bash
|
||||
sudo certbot certonly --webroot -w /var/www/html \
|
||||
-d matrix.example.com -d element.example.com -d turn.example.com
|
||||
```
|
||||
|
||||
Für die Erstausstellung muss Port 80 die ACME-Anfrage beantworten können:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name matrix.example.com element.example.com turn.example.com;
|
||||
root /var/www/html;
|
||||
location /.well-known/acme-challenge/ { allow all; }
|
||||
}
|
||||
```
|
||||
|
||||
Enthaltene Namen prüfen:
|
||||
|
||||
```bash
|
||||
sudo openssl x509 -in /etc/letsencrypt/live/matrix.example.com/fullchain.pem \
|
||||
-noout -ext subjectAltName
|
||||
```
|
||||
|
||||
## Erneuerung
|
||||
|
||||
certbot erneuert automatisch. Dienste, die das Material **selbst** lesen (hier
|
||||
coturn), merken das nicht – sie halten es seit dem Start im Speicher. Deshalb
|
||||
ein Deploy-Hook nach
|
||||
`/etc/letsencrypt/renewal-hooks/deploy/10-reload-services.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -e
|
||||
chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||||
chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||||
find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \; || true
|
||||
systemctl reload nginx || true
|
||||
systemctl restart coturn || true
|
||||
```
|
||||
|
||||
Trockenlauf:
|
||||
|
||||
```bash
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
## Zertifikate für Nicht-HTTP-Dienste freigeben
|
||||
|
||||
Standardmäßig darf nur `root` den privaten Schlüssel lesen. Statt die Rechte
|
||||
global zu öffnen, eine Gruppe verwenden und nur die betroffenen Dienstbenutzer
|
||||
aufnehmen:
|
||||
|
||||
```bash
|
||||
sudo groupadd -f ssl-cert
|
||||
sudo usermod -aG ssl-cert turnserver
|
||||
|
||||
sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
|
||||
sudo chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive
|
||||
sudo find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \;
|
||||
```
|
||||
|
||||
Gruppenmitgliedschaften wirken erst beim Start eines Prozesses – danach ist ein
|
||||
`restart` nötig, kein `reload`.
|
||||
|
||||
## Zwei wiederkehrende Fallstricke
|
||||
|
||||
**`add_header` wird nicht vererbt.** Sobald ein `location`-Block eine eigene
|
||||
`add_header`-Direktive enthält, verliert er *alle* geerbten. Sicherheitsheader
|
||||
gehören deshalb in ein Snippet, das in jeden betroffenen Block eingebunden wird:
|
||||
|
||||
```nginx
|
||||
include /etc/nginx/snippets/element-security.conf;
|
||||
```
|
||||
|
||||
**Zeitüberschreitungen bei Long-Polling.** Matrix-Clients halten `/sync` offen.
|
||||
Mit dem Standardwert von 60 Sekunden bricht die Verbindung ständig ab:
|
||||
|
||||
```nginx
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_buffering off;
|
||||
```
|
||||
|
||||
## Prüfen
|
||||
|
||||
```bash
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/
|
||||
curl -s -o /dev/null -D - https://element.example.com/ | grep -iE '^x-|^strict'
|
||||
```
|
||||
|
||||
## Firewall
|
||||
|
||||
Freizugeben: 80 und 443 TCP, dazu für coturn 3478 und 5349 (je TCP und UDP)
|
||||
sowie 49152–65535 UDP.
|
||||
|
||||
```bash
|
||||
sudo ufw allow 80,443/tcp
|
||||
sudo ufw allow 3478,5349/tcp
|
||||
sudo ufw allow 3478,5349/udp
|
||||
sudo ufw allow 49152:65535/udp
|
||||
```
|
||||
@@ -0,0 +1,131 @@
|
||||
# Wiki.js
|
||||
|
||||
Wiki mit PostgreSQL-Backend, betrieben als systemd-Dienst hinter nginx.
|
||||
|
||||
| Punkt | Wert |
|
||||
|---|---|
|
||||
| Software | Wiki.js 2.x (Node.js) |
|
||||
| Öffentlich | `https://wiki.example.com` |
|
||||
| Interner Port | `127.0.0.1:3000` |
|
||||
| Datenbank | PostgreSQL (`wiki`) |
|
||||
| Verzeichnis | `/opt/wikijs` |
|
||||
|
||||
## 1. Voraussetzungen
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y nodejs npm postgresql
|
||||
node --version # Wiki.js 2.x braucht Node 18 oder neuer
|
||||
```
|
||||
|
||||
## 2. Datenbank
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql <<'SQL'
|
||||
CREATE ROLE wikijs LOGIN PASSWORD 'HIER-EIN-STARKES-PASSWORT';
|
||||
CREATE DATABASE wiki OWNER wikijs;
|
||||
SQL
|
||||
```
|
||||
|
||||
## 3. Installation
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/wikijs && cd /opt/wikijs
|
||||
sudo curl -fLO https://github.com/Requarks/wiki/releases/latest/download/wiki-js.tar.gz
|
||||
sudo tar xzf wiki-js.tar.gz
|
||||
sudo rm wiki-js.tar.gz
|
||||
sudo cp config.sample.yml config.yml
|
||||
```
|
||||
|
||||
`config.yml` anpassen:
|
||||
|
||||
```yaml
|
||||
port: 3000
|
||||
bindIP: 127.0.0.1 # nur lokal; TLS macht nginx
|
||||
|
||||
db:
|
||||
type: postgres
|
||||
host: localhost
|
||||
port: 5432
|
||||
user: wikijs
|
||||
pass: HIER-DAS-DATENBANK-PASSWORT
|
||||
db: wiki
|
||||
```
|
||||
|
||||
`bindIP: 127.0.0.1` ist wichtig – sonst ist Wiki.js unter Port 3000 direkt aus
|
||||
dem Internet erreichbar, am Reverse Proxy und dessen TLS vorbei.
|
||||
|
||||
## 4. systemd-Unit
|
||||
|
||||
Nach `/etc/systemd/system/wikijs.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Wiki.js
|
||||
After=network.target postgresql.service
|
||||
Wants=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=wikijs
|
||||
Group=wikijs
|
||||
WorkingDirectory=/opt/wikijs
|
||||
ExecStart=/usr/bin/node server
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
Environment=NODE_ENV=production
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=full
|
||||
ReadWritePaths=/opt/wikijs
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now wikijs
|
||||
sudo journalctl -u wikijs -f
|
||||
```
|
||||
|
||||
## 5. Ersteinrichtung
|
||||
|
||||
`https://wiki.example.com` öffnen und Administrationskonto anlegen. Danach unter
|
||||
**Administration → Groups → Guests** prüfen, welche Rechte nicht angemeldete
|
||||
Besucher haben – standardmäßig dürfen sie lesen.
|
||||
|
||||
## 6. Sicherung
|
||||
|
||||
Der gesamte Inhalt liegt in PostgreSQL:
|
||||
|
||||
```bash
|
||||
sudo -u postgres pg_dump -Fc wiki > wiki-$(date +%F).dump
|
||||
```
|
||||
|
||||
Zusätzlich `config.yml` sichern. Hochgeladene Dateien liegen je nach
|
||||
Einstellung in der Datenbank oder unter `/opt/wikijs/data`.
|
||||
|
||||
## Inhalte exportieren
|
||||
|
||||
Für eine Übernahme in ein Git-Repository lassen sich die Seiten direkt aus der
|
||||
Datenbank holen:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql -d wiki -t -A -F'|' \
|
||||
-c "SELECT id, path, title FROM pages ORDER BY id;"
|
||||
|
||||
sudo -u postgres psql -d wiki -t -A \
|
||||
-c "SELECT content FROM pages WHERE id=<id>;" > seite.md
|
||||
```
|
||||
|
||||
Wiki.js kann Inhalte auch fortlaufend nach Git spiegeln
|
||||
(**Administration → Storage → Git**). Das ist der bessere Weg, wenn die
|
||||
Dokumentation dauerhaft versioniert werden soll.
|
||||
|
||||
## Stolpersteine
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Wiki direkt über Port 3000 erreichbar | `bindIP` nicht auf `127.0.0.1` gesetzt |
|
||||
| Start bricht ab | Datenbank nicht erreichbar oder falsches Passwort |
|
||||
| Nach Update fehlen Inhalte | `config.yml` beim Austausch überschrieben |
|
||||
| Wiki öffentlich lesbar | Rechte der Gruppe „Guests“ nicht eingeschränkt |
|
||||
Reference in New Issue
Block a user