inmitial commit

This commit is contained in:
2026-07-19 08:33:01 +02:00
commit 1064a4d8e8
2 changed files with 411 additions and 0 deletions
Binary file not shown.
+411
View File
@@ -0,0 +1,411 @@
# Installationsanleitung: Euro-Office DocumentServer mit Nextcloud-Anbindung
Diese Anleitung beschreibt die Installation des Euro-Office DocumentServers (Docker-Variante)
auf einem Debian-13-Server und die Anbindung an eine bestehende Nextcloud-Instanz über Apache2
als Reverse-Proxy.
Projekt: https://github.com/Euro-Office/DocumentServer
Dokumentation: https://euro-office.github.io/documentation/
---
## 1. Parameter dieser Installation
Alle im Dokument verwendeten Platzhalter sind hier zentral aufgeführt. Für eine abweichende
Installation genügt es, diese Tabelle anzupassen und die Werte in den Befehlen entsprechend
zu übernehmen.
| Platzhalter | Beschreibung | Beispielwert (diese Installation) |
|-----------------------------|------------------------------------------------------------|------------------------------------------------------|
| `<DS_HOSTNAME>` | Hostname des Euro-Office DocumentServers | `eo-ds.ndnaht.de` |
| `<NC_HOSTNAME>` | Hostname der bestehenden Nextcloud-Instanz | `cloud.ndnaht.de` |
| `<IPV4_ADDRESS>` | Öffentliche IPv4-Adresse des Servers | `135.181.90.132` |
| `<IPV6_ADDRESS>` | Öffentliche IPv6-Adresse des Servers | `2a01:4f9:c014:1ba8::1` |
| `<INSTALL_DIR>` | Arbeitsverzeichnis für Docker-Compose-Stack | `/opt/euro-office` |
| `<VHOST_DIR>` | Verzeichnis der Apache-vHost-Konfigurationsdateien | `/usr/local/apache2/conf/vhosts` |
| `<INTERNAL_PORT>` | Lokaler Port, auf den der Container gemappt wird | `8080` |
| `<DOCKER_BRIDGE_NAME>` | Fester Name der Docker-Netzwerk-Bridge | `br-euroffice` |
| `<CERT_DIR>` | Verzeichnis mit TLS-Zertifikat (hier: dehydrated) | `/var/lib/dehydrated/certs/<DS_HOSTNAME>` |
| `<NC_DOCROOT>` | Docroot-Verzeichnis der Nextcloud-Installation | `/var/www/cloud.ndnaht.de/htdocs` |
| `<NC_LOGFILE>` | Pfad der Nextcloud-Logdatei | `/var/www/cloud.ndnaht.de/logs/cloud.log` |
| `<PHP_BINARY>` | Pfad zum PHP-CLI-Binary | `/usr/local/php/bin/php` |
| `<JWT_SECRET>` | Gemeinsames Secret zwischen DocumentServer und Nextcloud | wird generiert (siehe Schritt 3) |
---
## 2. Voraussetzungen
- **Betriebssystem:** Debian 13 (getestet), amd64 oder arm64
- **Docker Engine** (Community Edition) inkl. **Compose-Plugin v2**, installiert über das offizielle Docker-Repository:
```
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable
```
- **Mindestens 4 GB RAM** (8 GB empfohlen für Mehrbenutzerbetrieb), 10 GB freier Plattenplatz
- **Bestehende Nextcloud-Instanz** (getestet mit Nextcloud 34), erreichbar über Apache2, mit SSH-/`occ`-Zugriff
- **DNS-Eintrag** für `<DS_HOSTNAME>`, zeigt auf `<IPV4_ADDRESS>` (bzw. `<IPV6_ADDRESS>`)
- **Gültiges TLS-Zertifikat** für `<DS_HOSTNAME>` (hier über [dehydrated](https://github.com/dehydrated-io/dehydrated) verwaltet)
- **Apache2** bereits installiert und aktiv, mit folgenden Modulen aktiviert:
`proxy`, `proxy_http`, `proxy_wstunnel`, `ssl`, `headers`, `rewrite`
- **Firewall-Hinweis:** Falls der Server eine eigene iptables/nftables-Firewall betreibt
(Eigenbau-Skript, CSF, ufw o. ä.), die *nicht* automatisch mit Docker zusammenarbeitet,
siehe Abschnitt 8 ("Firewall-Anpassung für Docker-Bridge-Netzwerke"). Ohne diesen Schritt
ist der Container u. U. nicht einmal vom Host selbst erreichbar.
---
## 3. Arbeitsverzeichnis und Secrets anlegen
```bash
sudo mkdir -p <INSTALL_DIR>
cd <INSTALL_DIR>
```
JWT-Secret generieren und in `.env` ablegen (wird von Docker Compose automatisch eingelesen):
```bash
echo "EO_JWT_SECRET=$(openssl rand -hex 32)" | sudo tee .env
sudo chmod 600 .env
```
> Diesen Wert später 1:1 in die Nextcloud-Konfiguration übernehmen (Schritt 10).
---
## 4. docker-compose.yaml
Der Container wird **nicht** direkt öffentlich exponiert, sondern nur auf `127.0.0.1`
gebunden — Apache übernimmt als Reverse-Proxy die öffentliche Erreichbarkeit inkl. TLS.
Das Docker-Netzwerk bekommt einen **festen Bridge-Namen**, damit Firewall-Regeln, die
auf den Interface-Namen verweisen, auch nach einem Neuaufbau des Netzwerks gültig bleiben.
```yaml
services:
euro-office:
image: ghcr.io/euro-office/documentserver:latest
container_name: euro-office-ds
restart: unless-stopped
environment:
- JWT_ENABLED=true
- JWT_SECRET=${EO_JWT_SECRET}
# EXAMPLE_ENABLED bewusst NICHT gesetzt (=false) -> kein offenes Beispiel-App in Produktion
ports:
- "127.0.0.1:<INTERNAL_PORT>:80"
volumes:
- eo_data:/var/lib/euro-office/documentserver
- eo_logs:/var/log/euro-office/documentserver
- eo_config:/etc/euro-office/documentserver
networks:
- euroffice
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost/healthcheck || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
volumes:
eo_data:
eo_logs:
eo_config:
networks:
euroffice:
driver: bridge
driver_opts:
com.docker.network.bridge.name: <DOCKER_BRIDGE_NAME>
```
Datei anlegen:
```bash
cd <INSTALL_DIR>
sudo tee docker-compose.yaml > /dev/null << 'EOF'
# --- Inhalt wie oben, Platzhalter durch tatsächliche Werte ersetzt ---
EOF
```
Konfiguration prüfen, bevor gestartet wird (zeigt das aufgelöste `JWT_SECRET`):
```bash
sudo docker compose config
```
---
## 5. Container starten
```bash
cd <INSTALL_DIR>
sudo docker compose up -d
sudo docker compose ps
```
Erwartet: Status `Up ... (healthy)` nach ca. 6090 Sekunden (erste Fontgenerierung dauert etwas).
Lokalen Health-Check testen:
```bash
curl -i --max-time 5 http://127.0.0.1:<INTERNAL_PORT>/healthcheck
```
Erwartet: `HTTP/1.1 200 OK`
> **Falls dieser Test hängt/fehlschlägt (Timeout oder Connection Reset), obwohl
> `docker compose ps` bereits `healthy` zeigt:** Das deutet auf ein Firewall-Problem hin —
> siehe Abschnitt 8.
---
## 6. Apache-Module prüfen/aktivieren
```bash
sudo a2enmod proxy proxy_http proxy_wstunnel ssl headers rewrite
sudo systemctl restart apache2
sudo systemctl status apache2 --no-pager
```
---
## 7. Apache-vHost anlegen
Datei `<VHOST_DIR>/<DS_HOSTNAME>.conf`:
```apache
<VirtualHost <IPV4_ADDRESS>:80 [<IPV6_ADDRESS>]:80>
ServerName <DS_HOSTNAME>
RewriteEngine on
RewriteCond %{HTTPS} !=on
RewriteRule (.*) https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
CustomLog /var/log/apache2/<DS_HOSTNAME>-access.log combined
ErrorLog /var/log/apache2/<DS_HOSTNAME>-error.log
</VirtualHost>
<VirtualHost <IPV4_ADDRESS>:443 [<IPV6_ADDRESS>]:443>
ServerName <DS_HOSTNAME>
SSLEngine on
SSLCertificateFile <CERT_DIR>/fullchain.pem
SSLCertificateKeyFile <CERT_DIR>/privkey.pem
ProxyPreserveHost On
ProxyRequests Off
# WebSocket-Verbindungen (Co-Editing) an proxy_wstunnel weiterreichen
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/(.*) ws://127.0.0.1:<INTERNAL_PORT>/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule ^/(.*) http://127.0.0.1:<INTERNAL_PORT>/$1 [P,L]
# Wichtig: DocumentServer muss wissen, dass er hinter TLS läuft,
# sonst generiert er interne Editor-/Cache-URLs mit http:// statt https://
# ("Mixed Content", Editor kann Dokumente nicht laden/herunterladen).
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Host "<DS_HOSTNAME>"
RequestHeader set X-Forwarded-Port "443"
ProxyPass / http://127.0.0.1:<INTERNAL_PORT>/ retry=0
ProxyPassReverse / http://127.0.0.1:<INTERNAL_PORT>/
# Große Uploads (Dokumente) erlauben
LimitRequestBody 0
CustomLog /var/log/apache2/<DS_HOSTNAME>-access.log combined
ErrorLog /var/log/apache2/<DS_HOSTNAME>-error.log
</VirtualHost>
```
Aktivieren und testen:
```bash
sudo apachectl configtest
sudo systemctl reload apache2
```
Erreichbarkeit von außen prüfen:
```bash
curl -i https://<DS_HOSTNAME>/healthcheck
```
Erwartet: `HTTP/1.1 200 OK`
---
## 8. Firewall-Anpassung für Docker-Bridge-Netzwerke
**Nur relevant, wenn eine eigene Firewall-Lösung eingesetzt wird, die den Datenverkehr über
Docker-Bridge-Interfaces nicht automatisch zulässt** (z. B. eigene iptables/nftables-Skripte
mit einer restriktiven `OUTPUT`/`FORWARD`-Policy).
### Symptom
- `docker compose ps` zeigt den Container als `healthy` (interner Check funktioniert)
- Ein `curl` **von außerhalb des Containers** auf `127.0.0.1:<INTERNAL_PORT>` oder die
Container-IP hängt oder liefert `Connection reset by peer` / `Operation timed out`
- `ping` auf die Container-IP funktioniert (ICMP ist oft separat erlaubt), TCP jedoch nicht
### Ursache
Docker bindet Container-Ports über einen `docker-proxy`-Prozess bzw. NAT-Regeln, die
Datenverkehr über das jeweilige Docker-Bridge-Interface (`br-...`) leiten. Eine restriktive
Firewall mit interface-spezifischen `ACCEPT`-Regeln (z. B. nur für `eth0`) und einer
abschließenden `DROP`-Regel lässt diesen Bridge-Verkehr nicht automatisch durch.
### Lösung
1. Docker-Netzwerk mit **festem Bridge-Namen** betreiben (siehe Abschnitt 4,
`com.docker.network.bridge.name: <DOCKER_BRIDGE_NAME>`) — verhindert, dass sich der
Interface-Name bei jedem Neuaufbau des Netzwerks ändert.
2. In der Firewall-Konfiguration eine **explizite Ausnahme für dieses eine Interface**
eintragen (kein Wildcard wie `br+`, das würde auch fremde/zukünftige Bridges ungefiltert
durchlassen). Je nach Firewall-Lösung z. B.:
- Eigenbau-Skript mit `unprotected_ifs`-Variable: `unprotected_ifs="<DOCKER_BRIDGE_NAME>"`
- iptables direkt: gezielte `ACCEPT`-Regeln für `-i <DOCKER_BRIDGE_NAME>` / `-o <DOCKER_BRIDGE_NAME>`
in den Chains `INPUT`, `OUTPUT` und `FORWARD`
3. Firewall neu laden und Docker-Daemon **danach** neu starten, damit Docker seine eigenen
iptables-Chains (`DOCKER`, `DOCKER-FORWARD`, `DOCKER-USER`, …) neu aufbaut:
```bash
sudo systemctl restart <firewall-service>
sudo systemctl restart docker
cd <INSTALL_DIR> && sudo docker compose up -d
```
4. **Startreihenfolge dauerhaft absichern**, damit das Problem nach einem Server-Reboot nicht
erneut auftritt — Docker soll immer *nach* der Firewall starten:
```bash
sudo mkdir -p /etc/systemd/system/docker.service.d/
sudo tee /etc/systemd/system/docker.service.d/after-firewall.conf > /dev/null << 'EOF'
[Unit]
After=<firewall-service>.service
Wants=<firewall-service>.service
EOF
sudo systemctl daemon-reload
```
### Restrisiko
Ein manueller Neustart der Firewall **während Docker bereits läuft** stört bestehende,
schon aktive Container-Verbindungen in der Regel nicht. Wird jedoch **danach** ein neues
Docker-Netzwerk angelegt (z. B. `docker compose down && up`, oder ein neuer Compose-Stack),
kann die Regel-Einfügung fehlschlagen (`Failed to Setup IP tables: ... No chain/target/match
by that name`), weil Dockers eigene Chains beim Firewall-Neuaufbau entfernt wurden. In diesem
Fall genügt es, den Docker-Daemon einmal neu zu starten und den Stack erneut zu starten:
```bash
sudo systemctl restart docker
cd <INSTALL_DIR> && sudo docker compose up -d
```
### Verifikation nach einem vollständigen Server-Reboot
```bash
sudo reboot
# nach dem Reboot, neu verbinden:
systemctl status <firewall-service> --no-pager
systemctl status docker.service --no-pager
sudo docker compose -f <INSTALL_DIR>/docker-compose.yaml ps
curl -i --max-time 5 https://<DS_HOSTNAME>/healthcheck
ip a | grep -A2 <DOCKER_BRIDGE_NAME>
sudo docker network ls
```
---
## 9. Nextcloud-Connector-App installieren
Die Euro-Office-Integration ist im offiziellen Nextcloud App Store gelistet.
**Über die Weboberfläche:**
1. Als Administrator einloggen
2. Profilbild (oben rechts) → **Apps**
3. Kategorie **„Büro & Text"** auswählen
4. **„Euro-Office"** suchen → **„Download and enable"**
**Alternativ über die Konsole (`occ`):**
```bash
cd <NC_DOCROOT>
sudo -u www-data <PHP_BINARY> occ app:install eurooffice
```
`app:install` lädt die App aus dem Nextcloud App Store herunter **und** aktiviert sie in
einem Schritt. Falls die App bereits heruntergeladen, aber nur deaktiviert ist, genügt
stattdessen:
```bash
sudo -u www-data <PHP_BINARY> occ app:enable eurooffice
```
**Kontrolle per SSH:**
```bash
sudo -u www-data <PHP_BINARY> <NC_DOCROOT>/occ app:list | grep -i eurooffice
```
Erwartet: Eintrag unter *Enabled*, z. B. `eurooffice: 11.0.1`
---
## 10. Nextcloud konfigurieren (DocumentServer-URL + JWT-Secret)
Zuerst prüfen, welche Config-Keys die App verwendet:
```bash
cd <NC_DOCROOT>
sudo -u www-data <PHP_BINARY> occ config:list eurooffice
```
DocumentServer-URL setzen:
```bash
sudo -u www-data <PHP_BINARY> occ config:app:set eurooffice DocumentServerUrl --value="https://<DS_HOSTNAME>/"
```
JWT-Secret setzen (identischer Wert wie in `<INSTALL_DIR>/.env`, Variable `EO_JWT_SECRET`):
```bash
sudo grep EO_JWT_SECRET <INSTALL_DIR>/.env
sudo -u www-data <PHP_BINARY> occ config:app:set eurooffice jwt_secret --value="<JWT_SECRET>"
```
Kontrolle:
```bash
sudo -u www-data <PHP_BINARY> occ config:list eurooffice
```
---
## 11. Funktionstest
1. In Nextcloud ein Office-Dokument (`.docx`, `.xlsx`, `.pptx`) öffnen
2. Der Euro-Office-Editor sollte sich im Browser öffnen und das Dokument anzeigen
**Falls die Meldung „Herunterladen ist fehlgeschlagen" erscheint:**
Browser-Entwicklerkonsole (F12) prüfen. Eine Meldung wie
*„Laden von gemischten aktiven Inhalten … wurde blockiert"* (Mixed Content) weist darauf hin,
dass der DocumentServer interne URLs mit `http://` statt `https://` erzeugt. Prüfen, ob die
`X-Forwarded-*`-Header im vHost (Abschnitt 7) korrekt gesetzt sind, danach:
```bash
sudo apachectl configtest
sudo systemctl reload apache2
```
Browser-Hard-Reload (`Strg+Shift+R`) und Dokument erneut öffnen.
---
## 12. Wartung / Hinweise
- **Volumes:** `eo_data`, `eo_logs`, `eo_config` (benannte Docker-Volumes) enthalten
Konfiguration, Cache und Logs. Für Backups: `docker run --rm -v <volume>:/data -v $(pwd):/backup alpine tar czf /backup/<volume>.tar.gz -C /data .`
- **Updates:** `docker compose pull && docker compose up -d`
- **Logs im Container:**
```bash
sudo docker compose exec euro-office find /var/log/euro-office -iname "*.log"
```
- **EXAMPLE_ENABLED:** bewusst nicht gesetzt/aktiviert — die Beispiel-App hat keine
Zugriffskontrolle und darf nicht öffentlich erreichbar sein.