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,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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user