# 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) | |-----------------------------|------------------------------------------------------------|------------------------------------------------------| | `` | Hostname des Euro-Office DocumentServers | `eo-ds.ndnaht.de` | | `` | Hostname der bestehenden Nextcloud-Instanz | `cloud.ndnaht.de` | | `` | Öffentliche IPv4-Adresse des Servers | `135.181.90.132` | | `` | Öffentliche IPv6-Adresse des Servers | `2a01:4f9:c014:1ba8::1` | | `` | Arbeitsverzeichnis für Docker-Compose-Stack | `/opt/euro-office` | | `` | Verzeichnis der Apache-vHost-Konfigurationsdateien | `/usr/local/apache2/conf/vhosts` | | `` | Lokaler Port, auf den der Container gemappt wird | `8080` | | `` | Fester Name der Docker-Netzwerk-Bridge | `br-euroffice` | | `` | Verzeichnis mit TLS-Zertifikat (hier: dehydrated) | `/var/lib/dehydrated/certs/` | | `` | Docroot-Verzeichnis der Nextcloud-Installation | `/var/www/cloud.ndnaht.de/htdocs` | | `` | Pfad der Nextcloud-Logdatei | `/var/www/cloud.ndnaht.de/logs/cloud.log` | | `` | Pfad zum PHP-CLI-Binary | `/usr/local/php/bin/php` | | `` | 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 ``, zeigt auf `` (bzw. ``) - **Gültiges TLS-Zertifikat** für `` (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 cd ``` 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::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: ``` Datei anlegen: ```bash cd 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 sudo docker compose up -d sudo docker compose ps ``` Erwartet: Status `Up ... (healthy)` nach ca. 60–90 Sekunden (erste Fontgenerierung dauert etwas). Lokalen Health-Check testen: ```bash curl -i --max-time 5 http://127.0.0.1:/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 `/.conf`: ```apache :80 []:80> ServerName RewriteEngine on RewriteCond %{HTTPS} !=on RewriteRule (.*) https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L] CustomLog /var/log/apache2/-access.log combined ErrorLog /var/log/apache2/-error.log :443 []:443> ServerName SSLEngine on SSLCertificateFile /fullchain.pem SSLCertificateKeyFile /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:/$1 [P,L] RewriteCond %{HTTP:Upgrade} !=websocket [NC] RewriteRule ^/(.*) http://127.0.0.1:/$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 "" RequestHeader set X-Forwarded-Port "443" ProxyPass / http://127.0.0.1:/ retry=0 ProxyPassReverse / http://127.0.0.1:/ # Große Uploads (Dokumente) erlauben LimitRequestBody 0 CustomLog /var/log/apache2/-access.log combined ErrorLog /var/log/apache2/-error.log ``` Aktivieren und testen: ```bash sudo apachectl configtest sudo systemctl reload apache2 ``` Erreichbarkeit von außen prüfen: ```bash curl -i https:///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:` 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: `) — 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=""` - iptables direkt: gezielte `ACCEPT`-Regeln für `-i ` / `-o ` 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 sudo systemctl restart docker cd && 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=.service Wants=.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 && sudo docker compose up -d ``` ### Verifikation nach einem vollständigen Server-Reboot ```bash sudo reboot # nach dem Reboot, neu verbinden: systemctl status --no-pager systemctl status docker.service --no-pager sudo docker compose -f /docker-compose.yaml ps curl -i --max-time 5 https:///healthcheck ip a | grep -A2 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 sudo -u www-data 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 occ app:enable eurooffice ``` **Kontrolle per SSH:** ```bash sudo -u www-data /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 sudo -u www-data occ config:list eurooffice ``` DocumentServer-URL setzen: ```bash sudo -u www-data occ config:app:set eurooffice DocumentServerUrl --value="https:///" ``` JWT-Secret setzen (identischer Wert wie in `/.env`, Variable `EO_JWT_SECRET`): ```bash sudo grep EO_JWT_SECRET /.env sudo -u www-data occ config:app:set eurooffice jwt_secret --value="" ``` Kontrolle: ```bash sudo -u www-data 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 :/data -v $(pwd):/backup alpine tar czf /backup/.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.