From c10801ae751b45ce72788245625d8570700e9311 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 18:48:56 +0200 Subject: [PATCH] fix(collabora): pin BUILD_FROM per arch and restore the payload capabilities build.json named the multi-arch collabora/code:latest for both architectures. The builder never passes --platform -- it runs each architecture on its own native runner -- so BUILD_FROM is the only thing selecting which binaries end up in the add-on. That resolves correctly today only because the runner architecture happens to match the target. Name collabora/code:latest-amd64 and collabora/code:latest-arm64, which are published in lockstep with latest. The official image sets file capabilities on two binaries, and COPY --from does not carry extended attributes, so they arrived stripped: coolforkit-caps cap_chown,cap_fowner,cap_sys_chroot=ep coolmount cap_sys_admin=ep Without them coolwsd starts and serves the admin console, but cannot chroot a kit process, so no document ever opens. Reapply and verify them. Replace the smoke test, which is why the build is currently red: coolwsd refuses to run as root (exit 78), and --version does not exit anyway, since the official entrypoint passes it to the long-running server. Check instead that every binary resolves its libraries against the Debian base. For ssl: true, hand Collabora the certificate copies in /etc/coolwsd rather than /ssl. coolwsd runs as uid 1001 and a private key in /ssl is commonly root-only, so it could not be read; the copies were already being made and chowned, but nothing pointed at them. Co-Authored-By: Claude --- collabora/CHANGELOG.md | 5 +- collabora/Dockerfile | 73 ++++++++++++++++++---- collabora/build.json | 4 +- collabora/rootfs/etc/cont-init.d/99-run.sh | 11 +++- 4 files changed, 76 insertions(+), 17 deletions(-) diff --git a/collabora/CHANGELOG.md b/collabora/CHANGELOG.md index ced06271ec..db45763362 100644 --- a/collabora/CHANGELOG.md +++ b/collabora/CHANGELOG.md @@ -1,6 +1,9 @@ ## 26.04.2.4.1 (2026-07-26) -- Rebuild on a Debian base: upstream turned collabora/code into a distroless image with no shell, which broke the addon build entirely. collabora/code:latest stays the tracked upstream image in build.json, but is now a build stage whose payload is copied onto ghcr.io/hassio-addons/debian-base, and the addon ships its own launcher in place of the removed /start-collabora-online.sh +- Rebuild on a Debian base: upstream turned collabora/code into a distroless image with no shell, which broke the addon build entirely. collabora/code stays the tracked upstream image in build.json, but is now a build stage whose payload is copied onto ghcr.io/hassio-addons/debian-base, and the addon ships its own launcher in place of the removed /start-collabora-online.sh +- build.json names the architecture explicitly again (`collabora/code:latest-amd64` and `collabora/code:latest-arm64`). The builder never passes `--platform`, so the tag is the only thing that decides which binaries land in the addon +- Restore the file capabilities on `coolforkit-caps` and `coolmount`. The official image carries them as extended attributes, which `COPY --from` does not transfer, and without them Collabora starts but cannot open any document +- The certificates for `ssl: true` are read from the copies in /etc/coolwsd rather than from /ssl directly, which Collabora could not read as uid 1001 when the private key is root-only - Fix version numbering: releases on CollaboraOnline/online are now Helm charts only, which had renumbered the addon from 25.4.9.2 down to 1.3.0 and hid updates. The version is tracked from the collabora/code Docker Hub tags again - `server_name` is now passed to Collabora, fixing `Your browser has been unable to connect to the Collabora server` behind a reverse proxy - `domain1` was never passed to Collabora at all (the script read a `domain` option that does not exist, and recent Collabora releases dropped that variable). It is now deprecated and applied as `server_name` diff --git a/collabora/Dockerfile b/collabora/Dockerfile index 387ca84341..c13571a3df 100644 --- a/collabora/Dockerfile +++ b/collabora/Dockerfile @@ -20,6 +20,16 @@ ARG BUILD_VERSION ############################################################################### # Get Collabora Online from the official image (BUILD_FROM, see build.json) # +# build.json pins the architecture explicitly, collabora/code:latest-amd64 and +# collabora/code:latest-arm64, rather than the multi-arch collabora/code:latest. +# The builder never passes --platform: it runs the amd64 build on a native amd64 +# runner and the aarch64 build on a native arm runner, so the only thing that +# decides which Collabora binaries end up in the add-on is this tag. With the +# multi-arch tag that happens to resolve correctly, but only for as long as the +# runner architecture keeps matching the target, and a mismatch would silently +# produce an image full of foreign-architecture binaries. The per-arch tags are +# published in lockstep with latest, so nothing is lost by naming them. +# # Upstream rebuilt collabora/code as a Nix-based distroless image: /bin and # /sbin are empty, so it can no longer be the base of the add-on itself, as s6, # bashio and every RUN need a shell. It stays the tracked upstream image, and @@ -38,8 +48,12 @@ FROM ${BUILD_FROM} AS collabora ############################################################################### FROM ghcr.io/hassio-addons/debian-base:9.3.0 -# Inherited from the base, but declared explicitly: the linkage check below -# relies on pipefail to notice a failing ldd. +# Inherited from the base, declared here so it is visible to hadolint and to +# anyone adding a pipe below. Note that the linkage check does NOT pipe into +# grep: with pipefail an ldd that exits non-zero (a binary it cannot handle at +# all) would make the pipeline fail even though grep matched, and "if" would +# then read that as "no unresolved libraries" -- the one case worth catching. +# Capturing the output and matching it with case avoids the question entirely. SHELL ["/bin/bash", "-o", "pipefail", "-c"] ################## @@ -111,18 +125,55 @@ RUN \ rm -rf /etc/coolwsd/proof_key* && \ fc-cache /opt/collaboraoffice/share/fonts/truetype > /dev/null 2>&1 || true -# Fail the build rather than ship an image that cannot start. ha_autoapps.sh is -# invoked with "|| true", so a missing package would go unnoticed, and the -# binaries were built inside the upstream Nix image against a different libc. -# ldd is the right check here: it resolves every NEEDED library against this -# base without booting Collabora, which would try to build a jail. +# Restore the file capabilities. The official image carries them as extended +# attributes on two binaries: +# coolforkit-caps cap_chown,cap_fowner,cap_sys_chroot=ep +# coolmount cap_sys_admin=ep +# COPY --from does not transfer extended attributes, so both arrive stripped. +# Nothing about the build notices: coolwsd starts and serves the admin console, +# but every document fails to open because it cannot chroot a kit process. Set +# them again and check they stuck, so a builder without xattr support fails here +# instead of shipping an add-on that only looks like it works. +# +# cap_sys_admin on coolmount only takes effect if the container is given +# SYS_ADMIN, which the add-on does not request; without it Collabora copies its +# child roots instead of bind-mounting them, which is slower but works. +RUN \ + setcap "cap_chown,cap_fowner,cap_sys_chroot=ep" /usr/bin/coolforkit-caps && \ + setcap "cap_sys_admin=ep" /usr/bin/coolmount && \ + caps="$(getcap /usr/bin/coolforkit-caps /usr/bin/coolmount)" && \ + case "$caps" in \ + *cap_sys_chroot*) ;; \ + *) echo "coolforkit-caps lost its capabilities: ${caps}"; exit 1 ;; \ + esac && \ + case "$caps" in \ + *cap_sys_admin*) ;; \ + *) echo "coolmount lost its capabilities: ${caps}"; exit 1 ;; \ + esac + +# Fail the build rather than ship an image that cannot start: ha_autoapps.sh is +# invoked with "|| true", and the payload was linked against the libraries of +# the distroless image, so both are worth proving here. +# +# coolwsd itself cannot be executed as a smoke test. It refuses to run as root +# ("Do not run as root. Please run as cool user.", exit 78), and --version does +# not exit either -- the official entrypoint passes it to the long-running +# server to get the version into the log. Checking that every binary resolves +# its libraries proves the same thing and terminates. RUN \ command -v openssl > /dev/null && \ command -v su > /dev/null && \ - for binary in /usr/bin/coolwsd /usr/bin/coolforkit-ns /usr/bin/coolmount; do \ - if ldd "$binary" | grep "not found"; then \ - echo "unresolved shared libraries in $binary" && exit 1; \ - fi; \ + for binary in \ + /usr/bin/coolwsd \ + /usr/bin/coolforkit-caps \ + /usr/bin/coolforkit-ns \ + /usr/bin/coolmount \ + /opt/collaboraoffice/program/soffice.bin \ + /opt/collaboraoffice/program/libmergedlo.so; do \ + libs="$(ldd "$binary" 2>&1)"; \ + case "$libs" in \ + *"not found"*) echo "Unresolved libraries in ${binary}:"; echo "$libs"; exit 1 ;; \ + esac; \ done ################ diff --git a/collabora/build.json b/collabora/build.json index 76a11401e7..f70552aa00 100644 --- a/collabora/build.json +++ b/collabora/build.json @@ -1,6 +1,6 @@ { "build_from": { - "aarch64": "collabora/code:latest", - "amd64": "collabora/code:latest" + "aarch64": "collabora/code:latest-arm64", + "amd64": "collabora/code:latest-amd64" } } diff --git a/collabora/rootfs/etc/cont-init.d/99-run.sh b/collabora/rootfs/etc/cont-init.d/99-run.sh index 5b1522dd38..23b13dc252 100755 --- a/collabora/rootfs/etc/cont-init.d/99-run.sh +++ b/collabora/rootfs/etc/cont-init.d/99-run.sh @@ -94,16 +94,21 @@ if bashio::config.true 'ssl'; then bashio::log.error "Key file /ssl/${keyfile} not found" exit 1 fi + # Point Collabora at the copies rather than at /ssl. coolwsd runs as uid + # 1001 and /ssl is mounted read-only with whatever ownership the certificate + # tooling left behind, which for a private key is commonly root-only. These + # copies are picked up by the chown below, so they are readable regardless. cp -f "/ssl/${keyfile}" /etc/coolwsd/key.pem cp -f "/ssl/${certfile}" /etc/coolwsd/cert.pem cp -f "/ssl/${certfile}" /etc/coolwsd/ca-chain.cert.pem + chmod 600 /etc/coolwsd/key.pem extra_params="${extra_params/--o:ssl.enable=false/}" extra_params="${extra_params} \ --o:ssl.enable=true \ --o:ssl.termination=false \ - --o:ssl.cert_file_path=/ssl/${certfile} \ - --o:ssl.key_file_path=/ssl/${keyfile} \ - --o:ssl.ca_file_path=/ssl/${certfile}" + --o:ssl.cert_file_path=/etc/coolwsd/cert.pem \ + --o:ssl.key_file_path=/etc/coolwsd/key.pem \ + --o:ssl.ca_file_path=/etc/coolwsd/ca-chain.cert.pem" elif [[ "$extra_params" != *ssl.termination* ]]; then # coolwsd defaults ssl.termination to false, so with ssl disabled it builds # http:// and ws:// URLs even when the browser reached it over https through