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

15 KiB
Raw Permalink Blame History

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 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

sudo mkdir -p <INSTALL_DIR>
cd <INSTALL_DIR>

JWT-Secret generieren und in .env ablegen (wird von Docker Compose automatisch eingelesen):

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.

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:

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):

sudo docker compose config

5. Container starten

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:

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

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:

<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:

sudo apachectl configtest
sudo systemctl reload apache2

Erreichbarkeit von außen prüfen:

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:
    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:
    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:

sudo systemctl restart docker
cd <INSTALL_DIR> && sudo docker compose up -d

Verifikation nach einem vollständigen Server-Reboot

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):

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:

sudo -u www-data <PHP_BINARY> occ app:enable eurooffice

Kontrolle per SSH:

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:

cd <NC_DOCROOT>
sudo -u www-data <PHP_BINARY> occ config:list eurooffice

DocumentServer-URL setzen:

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):

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:

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:

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:
    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.