chore: ensure we can use the same collabora instance for multiple cloud instances

Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
This commit is contained in:
Bert-Jan Fikse 2026-03-06 17:00:33 +01:00
parent d3d7bb9ba5
commit 6be4a50f8f
Signed by: bert-jan
GPG key ID: C1E0AB516AC16D1A
7 changed files with 405 additions and 12 deletions

View file

@ -9,6 +9,7 @@ docker_volume_base_dir: /srv/data
# Collabora-specific configuration
collabora_service_name: collabora
collabora_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ collabora_service_name }}"
collabora_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ collabora_service_name }}"
# Service configuration
collabora_domain: "office.local.test"
@ -20,9 +21,15 @@ collabora_extra_hosts: []
collabora_traefik_network: "proxy"
collabora_use_ssl: true
# Allowed WOPI host domains (Nextcloud, OpenCloud, etc.)
# These domains are allowed to open documents via Collabora.
# SSL verification for WOPI callbacks (set to false for self-signed certs)
collabora_ssl_verification: true
# Allowed WOPI host domains (Nextcloud, OpenCloud WOPI server, etc.)
# These domains are allowed to send WOPI requests to Collabora.
# Each entry is used as a regex pattern (dots are auto-escaped).
collabora_allowed_domains:
- "nextcloud.local.test"
- "opencloud.local.test"
# Domains allowed to embed Collabora in an iframe (Nextcloud, OpenCloud, etc.)
collabora_frame_ancestors:
- "nextcloud.local.test"

View file

@ -8,6 +8,19 @@
state: directory
mode: '0755'
- name: Create collabora volume directory
file:
path: "{{ collabora_docker_volume_dir }}"
state: directory
mode: '0755'
- name: Create coolwsd configuration
template:
src: coolwsd.xml.j2
dest: "{{ collabora_docker_volume_dir }}/coolwsd.xml"
mode: '0644'
notify: restart collabora
- name: Create docker-compose file for collabora
template:
src: docker-compose.yml.j2

View file

@ -0,0 +1,340 @@
<!-- Managed by Ansible - do not edit manually -->
<!-- Based on Collabora CODE default coolwsd.xml with alias_groups and frame_ancestors customized -->
<config>
<accessibility desc="Accessibility settings">
<enable type="bool" default="false">false</enable>
</accessibility>
<allowed_languages desc="List of supported languages on this instance." default="de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru">de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru</allowed_languages>
<languagetool desc="Remote API settings for spell and grammar checking">
<enabled type="bool" default="false">false</enabled>
<base_url type="string" default=""></base_url>
<user_name type="string" default=""></user_name>
<api_key type="string" default=""></api_key>
<ssl_verification type="string" default="true">true</ssl_verification>
<rest_protocol type="string" default=""></rest_protocol>
</languagetool>
<deepl desc="DeepL API settings">
<enabled type="bool" default="false">false</enabled>
<api_url type="string" default=""></api_url>
<auth_key type="string" default=""></auth_key>
</deepl>
<sys_template_path type="path" relative="true" default="systemplate"></sys_template_path>
<child_root_path type="path" relative="true" default="jails"></child_root_path>
<mount_jail_tree type="bool" default="true">true</mount_jail_tree>
<server_name type="string" default=""></server_name>
<file_server_root_path type="path" relative="true" default="browser/../"></file_server_root_path>
<hexify_embedded_urls type="bool" default="false">false</hexify_embedded_urls>
<experimental_features type="bool" default="true">true</experimental_features>
<memproportion type="double" default="80.0"></memproportion>
<num_prespawn_children type="uint" default="4">4</num_prespawn_children>
<fetch_update_check type="uint" default="10">10</fetch_update_check>
<allow_update_popup type="bool" default="true">true</allow_update_popup>
<per_document>
<max_concurrency type="uint" default="4">4</max_concurrency>
<batch_priority type="uint" default="5">5</batch_priority>
<bgsave_priority type="uint" default="5">5</bgsave_priority>
<bgsave_timeout_secs type="uint" default="120">120</bgsave_timeout_secs>
<redlining_as_comments type="bool" default="false">false</redlining_as_comments>
<pdf_resolution_dpi type="uint" default="96">96</pdf_resolution_dpi>
<idle_timeout_secs type="uint" default="3600">3600</idle_timeout_secs>
<idlesave_duration_secs type="uint" default="30">30</idlesave_duration_secs>
<autosave_duration_secs type="uint" default="300">300</autosave_duration_secs>
<background_autosave type="bool" default="true">true</background_autosave>
<background_manualsave type="bool" default="true">true</background_manualsave>
<always_save_on_exit type="bool" default="false">false</always_save_on_exit>
<limit_virt_mem_mb type="uint">0</limit_virt_mem_mb>
<limit_stack_mem_kb type="uint">8000</limit_stack_mem_kb>
<limit_file_size_mb type="uint">0</limit_file_size_mb>
<limit_num_open_files type="uint">0</limit_num_open_files>
<limit_load_secs type="uint" default="100">100</limit_load_secs>
<limit_store_failures type="uint" default="5">5</limit_store_failures>
<limit_convert_secs type="uint" default="100">100</limit_convert_secs>
<min_time_between_saves_ms type="uint" default="500">500</min_time_between_saves_ms>
<min_time_between_uploads_ms type="uint" default="5000">5000</min_time_between_uploads_ms>
<cleanup enable="true">
<cleanup_interval_ms type="uint" default="10000">10000</cleanup_interval_ms>
<bad_behavior_period_secs type="uint" default="60">60</bad_behavior_period_secs>
<idle_time_secs type="uint" default="300">300</idle_time_secs>
<limit_dirty_mem_mb type="uint" default="3072">3072</limit_dirty_mem_mb>
<limit_cpu_per type="uint" default="85">85</limit_cpu_per>
<lost_kit_grace_period_secs default="120">120</lost_kit_grace_period_secs>
</cleanup>
</per_document>
<per_view>
<out_of_focus_timeout_secs type="uint" default="300">300</out_of_focus_timeout_secs>
<idle_timeout_secs type="uint" default="900">900</idle_timeout_secs>
<custom_os_info type="string" default=""></custom_os_info>
<min_saved_message_timeout_secs type="uint" default="6">6</min_saved_message_timeout_secs>
</per_view>
<ver_suffix type="string" default=""></ver_suffix>
<logging>
<color type="bool">true</color>
<level type="string" default="warning">warning</level>
<level_startup type="string" default="trace">trace</level_startup>
<disabled_areas type="string" default="Socket,WebSocket,Admin,Pixel">Socket,WebSocket,Admin,Pixel</disabled_areas>
<most_verbose_level_settable_from_client type="string" default="notice">notice</most_verbose_level_settable_from_client>
<least_verbose_level_settable_from_client type="string" default="fatal">fatal</least_verbose_level_settable_from_client>
<protocol type="bool">false</protocol>
<lokit_sal_log type="string" default="-INFO-WARN">-INFO-WARN</lokit_sal_log>
<file enable="false">
<property name="path">/var/log/coolwsd.log</property>
<property name="rotation">never</property>
<property name="archive">timestamp</property>
<property name="compress">true</property>
<property name="purgeAge">10 days</property>
<property name="purgeCount">10</property>
<property name="rotateOnOpen">true</property>
<property name="flush">false</property>
</file>
<anonymize>
<anonymize_user_data type="bool" default="false">false</anonymize_user_data>
<anonymization_salt type="uint" default="82589933">82589933</anonymization_salt>
</anonymize>
<docstats type="bool" default="false">false</docstats>
<userstats type="bool" default="false">false</userstats>
<disable_server_audit type="bool" default="false">false</disable_server_audit>
</logging>
<canvas_slideshow_enabled type="bool" default="true">true</canvas_slideshow_enabled>
<logging_ui_cmd>
<merge type="bool" default="true">true</merge>
<merge_display_end_time type="bool" default="false">true</merge_display_end_time>
<file enable="false">
<property name="path">/var/log/coolwsd-ui-cmd.log</property>
<property name="purgeCount">10</property>
<property name="rotateOnOpen">true</property>
<property name="flush">false</property>
</file>
</logging_ui_cmd>
<trace_event enable="false">
<path type="string" default="/var/log/coolwsd.trace.json">/var/log/coolwsd.trace.json</path>
</trace_event>
<browser_logging default="false">false</browser_logging>
<trace enable="false">
<path compress="true" snapshot="false"></path>
<filter>
<message></message>
</filter>
<outgoing>
<record default="false">false</record>
</outgoing>
</trace>
<net>
<proto type="string" default="all">all</proto>
<listen type="string" default="any">any</listen>
<service_root type="path" default=""></service_root>
<post_allow allow="true">
<host>192\.168\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:192\.168\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>127\.0\.0\.1</host>
<host>::ffff:127\.0\.0\.1</host>
<host>::1</host>
<host>172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}</host>
</post_allow>
<lok_allow>
<host>192\.168\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:192\.168\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>127\.0\.0\.1</host>
<host>::ffff:127\.0\.0\.1</host>
<host>::1</host>
<host>172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>::ffff:10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}</host>
<host>localhost</host>
</lok_allow>
<content_security_policy></content_security_policy>
<frame_ancestors>{{ collabora_frame_ancestors | map('regex_replace', '^(.*)$', 'https://\\1') | join(' ') }}</frame_ancestors>
<connection_timeout_secs type="int" default="30">30</connection_timeout_secs>
<proxy_prefix type="bool" default="false">false</proxy_prefix>
</net>
<ssl>
<enable type="bool" default="true">true</enable>
<termination type="bool" default="false">false</termination>
<cert_file_path type="path" relative="false">/etc/coolwsd/cert.pem</cert_file_path>
<key_file_path type="path" relative="false">/etc/coolwsd/key.pem</key_file_path>
<ca_file_path type="path" relative="false">/etc/coolwsd/ca-chain.cert.pem</ca_file_path>
<ssl_verification type="string" default="false">false</ssl_verification>
<cipher_list default="ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"></cipher_list>
<hpkp enable="false" report_only="false">
<max_age enable="true" type="uint" default="1000">1000</max_age>
<report_uri enable="false" type="string"></report_uri>
<pins><pin></pin></pins>
</hpkp>
<sts>
<enabled type="bool" default="false">false</enabled>
<max_age type="int" default="31536000">31536000</max_age>
</sts>
</ssl>
<security>
<seccomp type="bool" default="true">true</seccomp>
<capabilities type="bool" default="true">true</capabilities>
<jwt_expiry_secs type="int" default="1800">1800</jwt_expiry_secs>
<enable_macros_execution type="bool" default="false">false</enable_macros_execution>
<macro_security_level type="int" default="1">1</macro_security_level>
<enable_websocket_urp type="bool" default="false">false</enable_websocket_urp>
<enable_metrics_unauthenticated type="bool" default="false">false</enable_metrics_unauthenticated>
<server_signature type="bool" default="false">false</server_signature>
</security>
<certificates>
<database_path type="string" default=""></database_path>
</certificates>
<watermark>
<opacity type="double" default="0.2">0.2</opacity>
<text type="string"></text>
</watermark>
<user_interface>
<mode type="string" default="default">default</mode>
<use_integration_theme type="bool" default="true">true</use_integration_theme>
<statusbar_save_indicator type="bool" default="true">true</statusbar_save_indicator>
</user_interface>
<storage>
<filesystem allow="false" />
<wopi allow="true">
<max_file_size type="uint">0</max_file_size>
<locking>
<refresh type="int" default="900">900</refresh>
</locking>
<alias_groups mode="groups">
{% for domain in collabora_allowed_domains %}
<group>
<host allow="true">https://{{ domain }}:443</host>
</group>
{% endfor %}
</alias_groups>
<is_legacy_server type="bool" default="false">false</is_legacy_server>
</wopi>
<ssl>
<as_scheme type="bool" default="true">true</as_scheme>
<enable type="bool"></enable>
<cert_file_path type="path" relative="false"></cert_file_path>
<key_file_path type="path" relative="false"></key_file_path>
<ca_file_path type="path" relative="false"></ca_file_path>
<cipher_list></cipher_list>
</ssl>
</storage>
<admin_console>
<enable type="bool" default="true">true</enable>
<enable_pam type="bool" default="false">false</enable_pam>
<username></username>
<password></password>
<logging>
<admin_login type="bool" default="true">true</admin_login>
<metrics_fetch type="bool" default="true">true</metrics_fetch>
<monitor_connect type="bool" default="true">true</monitor_connect>
<admin_action type="bool" default="true">true</admin_action>
</logging>
</admin_console>
<monitors></monitors>
<quarantine_files default="false" enable="false">
<limit_dir_size_mb default="250" type="uint">250</limit_dir_size_mb>
<max_versions_to_maintain default="5" type="uint">5</max_versions_to_maintain>
<path type="path" relative="false"></path>
<expiry_min type="int" default="3000">3000</expiry_min>
</quarantine_files>
<cache_files>
<path type="path" relative="false"></path>
<expiry_min type="int" default="3000">1000</expiry_min>
</cache_files>
<extra_export_formats>
<impress_swf type="bool" default="false">false</impress_swf>
<impress_bmp type="bool" default="false">false</impress_bmp>
<impress_gif type="bool" default="false">false</impress_gif>
<impress_png type="bool" default="false">false</impress_png>
<impress_svg type="bool" default="false">false</impress_svg>
<impress_tiff type="bool" default="false">false</impress_tiff>
</extra_export_formats>
<serverside_config>
<idle_timeout_secs type="uint" default="3600">3600</idle_timeout_secs>
</serverside_config>
<remote_config>
<remote_url type="string" default=""></remote_url>
</remote_config>
<stop_on_config_change type="bool" default="false">false</stop_on_config_change>
<remote_font_config>
<url type="string" default=""></url>
</remote_font_config>
<fonts_missing>
<handling type="string" default="log">log</handling>
</fonts_missing>
<indirection_endpoint>
<url type="string" default=""></url>
<migration_timeout_secs type="uint" default="180">180</migration_timeout_secs>
<geolocation_setup>
<enable type="bool" default="false">false</enable>
<timezone type="string"></timezone>
<allowed_websocket_origins></allowed_websocket_origins>
</geolocation_setup>
<server_name type="string" default=""></server_name>
</indirection_endpoint>
<home_mode>
<enable type="bool" default="false">false</enable>
</home_mode>
<zotero>
<enable type="bool" default="true">true</enable>
</zotero>
<help_url type="string" default="https://help.collaboraoffice.com/help.html?">https://help.collaboraoffice.com/help.html?</help_url>
<overwrite_mode>
<enable type="bool" default="false">false</enable>
</overwrite_mode>
<wasm>
<enable type="bool" default="false">false</enable>
<force type="bool" default="false">false</force>
</wasm>
<document_signing>
<enable type="bool" default="true">true</enable>
</document_signing>
</config>

View file

@ -4,11 +4,9 @@ services:
container_name: {{ collabora_service_name }}
restart: unless-stopped
environment:
domain: {{ collabora_allowed_domains | map('replace', '.', '\\.') | map('regex_replace', '^(.*)$', '^\\1$$') | join('|') }}
extra_params: >-
--o:ssl.enable=false
--o:ssl.termination=true
--o:net.frame_ancestors={{ collabora_allowed_domains | map('regex_replace', '^(.*)$', 'https://\\1') | join(' ') }}
extra_params: "--o:ssl.enable=false --o:ssl.termination=true --o:ssl.ssl_verification={{ collabora_ssl_verification | string | lower }}"
volumes:
- {{ collabora_docker_volume_dir }}/coolwsd.xml:/etc/coolwsd/coolwsd.xml:ro
cap_add:
- MKNOD
networks:

View file

@ -15,3 +15,8 @@
community.docker.docker_container_exec:
container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1"
command: php /var/www/html/occ config:app:set richdocuments wopi_allowlist --value=''
- name: Activate richdocuments configuration (fetch discovery from Collabora)
community.docker.docker_container_exec:
container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1"
command: php /var/www/html/occ richdocuments:activate-config

View file

@ -41,5 +41,10 @@ opencloud_s3_access_key: ""
opencloud_s3_secret_key: ""
opencloud_s3_bucket: "opencloud"
# Collabora integration (set opencloud_collabora_domain to enable)
opencloud_collabora_domain: ""
opencloud_wopi_domain: ""
opencloud_collabora_insecure: true
# CSP configuration (extra URLs to allow in connect-src)
opencloud_csp_extra_connect_src: []

View file

@ -7,8 +7,8 @@ services:
- /bin/sh
command: ["-c", "opencloud init || true; opencloud server"]
volumes:
- {{ opencloud_docker_volume_dir }}/config:/etc/ocis
- {{ opencloud_docker_volume_dir }}/data:/var/lib/ocis
- {{ opencloud_docker_volume_dir }}/config:/etc/opencloud
- {{ opencloud_docker_volume_dir }}/data:/var/lib/opencloud
environment:
{% if opencloud_use_ssl %}
OC_URL: "https://{{ opencloud_domain }}"
@ -19,7 +19,7 @@ services:
OC_LOG_LEVEL: "{{ opencloud_log_level }}"
PROXY_TLS: "false"
{% if opencloud_csp_extra_connect_src | length > 0 %}
PROXY_CSP_CONFIG_FILE_OVERRIDE_LOCATION: "/etc/ocis/csp-override.yaml"
PROXY_CSP_CONFIG_FILE_OVERRIDE_LOCATION: "/etc/opencloud/csp-override.yaml"
{% endif %}
IDM_ADMIN_PASSWORD: "{{ opencloud_admin_password }}"
{% if opencloud_oidc_issuer %}
@ -43,6 +43,19 @@ services:
STORAGE_USERS_DECOMPOSEDS3_ACCESS_KEY: "{{ opencloud_s3_access_key }}"
STORAGE_USERS_DECOMPOSEDS3_SECRET_KEY: "{{ opencloud_s3_secret_key }}"
STORAGE_USERS_DECOMPOSEDS3_BUCKET: "{{ opencloud_s3_bucket }}"
{% endif %}
{% if opencloud_collabora_domain %}
OC_ADD_RUN_SERVICES: "collaboration"
COLLABORA_DOMAIN: "{{ opencloud_collabora_domain }}"
COLLABORATION_APP_NAME: "CollaboraOnline"
COLLABORATION_APP_PRODUCT: "Collabora"
COLLABORATION_APP_ADDR: "https://{{ opencloud_collabora_domain }}"
COLLABORATION_APP_INSECURE: "{{ opencloud_collabora_insecure | string | lower }}"
COLLABORATION_APP_PROOF_DISABLE: "{{ opencloud_collabora_insecure | string | lower }}"
COLLABORATION_CS3API_DATAGATEWAY_INSECURE: "{{ opencloud_collabora_insecure | string | lower }}"
COLLABORATION_HTTP_ADDR: "0.0.0.0:9300"
COLLABORATION_WOPI_SRC: "https://{{ opencloud_wopi_domain }}"
FRONTEND_APP_HANDLER_SECURE_VIEW_APP_ADDR: "eu.opencloud.api.collaboration"
{% endif %}
networks:
- {{ opencloud_traefik_network }}
@ -63,6 +76,18 @@ services:
- traefik.http.routers.{{ opencloud_service_name }}.entrypoints=web
{% endif %}
- traefik.http.services.{{ opencloud_service_name }}.loadbalancer.server.port={{ opencloud_port }}
{% if opencloud_collabora_domain %}
- traefik.http.routers.{{ opencloud_service_name }}.service={{ opencloud_service_name }}
- traefik.http.routers.{{ opencloud_service_name }}-wopi.rule=Host(`{{ opencloud_wopi_domain }}`)
- traefik.http.routers.{{ opencloud_service_name }}-wopi.service={{ opencloud_service_name }}-wopi
- traefik.http.services.{{ opencloud_service_name }}-wopi.loadbalancer.server.port=9300
{% if opencloud_use_ssl %}
- traefik.http.routers.{{ opencloud_service_name }}-wopi.entrypoints=websecure
- traefik.http.routers.{{ opencloud_service_name }}-wopi.tls=true
{% else %}
- traefik.http.routers.{{ opencloud_service_name }}-wopi.entrypoints=web
{% endif %}
{% endif %}
networks:
{{ opencloud_traefik_network }}: