Files
Euro-Office/Installationsanleitung_Euro-Office.md
2026-07-19 08:33:01 +02:00

412 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.