From 4d2f7eebda128ec00e5d503b6498c3641481bbd4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 18:29:30 +0000 Subject: [PATCH 1/7] Pin QEMU to v8.1.5 to fix ppc64le build segfault The multi-arch Docker build fails on linux/ppc64le with 'qemu-ppc64le: QEMU internal SIGSEGV' while configuring the systemd package. This is a regression in newer (v9.x) QEMU shipped by tonistiigi/binfmt:latest, which docker/setup-qemu-action uses by default. Pin to the last known-good QEMU image so all target platforms continue to build. --- .github/workflows/docker.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 20b135e..074b1e1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -21,6 +21,11 @@ jobs: uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 + with: + # Pin QEMU: newer (v9.x) binfmt releases segfault emulating + # ppc64le while configuring the systemd package. v8.1.5 is the + # last known-good version for this multi-arch build. + image: tonistiigi/binfmt:qemu-v8.1.5 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub From 1e934fc74a754bd0faeed0a4c004f53f81a466ed Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 18:35:05 +0000 Subject: [PATCH 2/7] Fix Anchore scan: bump scan-action to v6 anchore/scan-action@v3 installs an old Grype (v0.74.4) that pulls a vulnerability DB schema Anchore no longer updates, so the scan fails with 'the vulnerability database was built 12 weeks ago (max allowed age is 5 days)' and writes an empty results.sarif, which then breaks the SARIF upload ('Unexpected end of JSON input'). Bump to anchore/scan-action@v6, which uses a current Grype with an up-to-date DB. v6 no longer writes a fixed results.sarif path, so reference the generated file via the step's 'sarif' output. --- .github/workflows/anchore.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 98133f4..307a157 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -31,7 +31,8 @@ jobs: push: false load: true - name: Scan image - uses: anchore/scan-action@v3 + id: scan + uses: anchore/scan-action@v6 with: image: "localbuild/testimage:latest" output-format: sarif @@ -41,4 +42,4 @@ jobs: - name: Upload Anchore Scan Report uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: results.sarif + sarif_file: ${{ steps.scan.outputs.sarif }} From 2f7ee75fb8f602aae192db30045c67289ce4b325 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 21:38:11 +0000 Subject: [PATCH 3/7] Neutralize systemd postinst during multi-arch build The real cause of the failing Docker build is systemd's postinst maintainer script misbehaving under QEMU user-mode emulation: it segfaults on ppc64le (with newer QEMU) and deadlocks on arm64 (hung ~3h at 'Setting up systemd (260.1-1)' with the pinned QEMU). Pinning QEMU only moved the failure between architectures. systemd is pulled in only as a transitive dependency and is never run in this container (the entrypoint launches netatalk directly), so stub the helpers its postinst invokes (systemctl, systemd-sysusers, systemd-tmpfiles, systemd-hwdb, systemd-machine-id-setup) via dpkg-divert for the duration of the install, add a permissive policy-rc.d, then restore the real binaries afterwards. --- Dockerfile | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5f2a3d0..490903f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,19 @@ ENV PERSISTENT_RUNTIME_DEPS \ ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update \ +# systemd is only a transitive dependency and is never run in this container +# (the entrypoint launches netatalk directly). Its postinst maintainer script +# hangs or segfaults under QEMU user-mode emulation during the multi-arch +# build, so stub the helpers it invokes (via dpkg-divert) for the duration of +# the install, then restore the real binaries afterwards. +RUN set -eux \ + && printf '#!/bin/sh\nexit 0\n' > /usr/sbin/policy-rc.d \ + && chmod +x /usr/sbin/policy-rc.d \ + && for b in systemctl systemd-sysusers systemd-tmpfiles systemd-hwdb systemd-machine-id-setup; do \ + dpkg-divert --local --rename --add "/usr/bin/$b"; \ + ln -sf /bin/true "/usr/bin/$b"; \ + done \ + && apt-get update \ && apt-get install \ --no-install-recommends \ --fix-missing \ @@ -24,6 +36,11 @@ RUN apt-get update \ netatalk \ \ && apt-get --assume-yes upgrade \ + && for b in systemctl systemd-sysusers systemd-tmpfiles systemd-hwdb systemd-machine-id-setup; do \ + rm -f "/usr/bin/$b"; \ + dpkg-divert --local --rename --remove "/usr/bin/$b"; \ + done \ + && rm -f /usr/sbin/policy-rc.d \ && apt-get --quiet --yes autoclean \ && apt-get --quiet --yes autoremove \ && apt-get --quiet --yes clean \ From 61975b63514cb12e41f9ed7a0aba553654835895 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 21:43:27 +0000 Subject: [PATCH 4/7] Revert "Neutralize systemd postinst during multi-arch build" This reverts commit 2f7ee75fb8f602aae192db30045c67289ce4b325. --- Dockerfile | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/Dockerfile b/Dockerfile index 490903f..5f2a3d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,19 +12,7 @@ ENV PERSISTENT_RUNTIME_DEPS \ ENV DEBIAN_FRONTEND noninteractive -# systemd is only a transitive dependency and is never run in this container -# (the entrypoint launches netatalk directly). Its postinst maintainer script -# hangs or segfaults under QEMU user-mode emulation during the multi-arch -# build, so stub the helpers it invokes (via dpkg-divert) for the duration of -# the install, then restore the real binaries afterwards. -RUN set -eux \ - && printf '#!/bin/sh\nexit 0\n' > /usr/sbin/policy-rc.d \ - && chmod +x /usr/sbin/policy-rc.d \ - && for b in systemctl systemd-sysusers systemd-tmpfiles systemd-hwdb systemd-machine-id-setup; do \ - dpkg-divert --local --rename --add "/usr/bin/$b"; \ - ln -sf /bin/true "/usr/bin/$b"; \ - done \ - && apt-get update \ +RUN apt-get update \ && apt-get install \ --no-install-recommends \ --fix-missing \ @@ -36,11 +24,6 @@ RUN set -eux \ netatalk \ \ && apt-get --assume-yes upgrade \ - && for b in systemctl systemd-sysusers systemd-tmpfiles systemd-hwdb systemd-machine-id-setup; do \ - rm -f "/usr/bin/$b"; \ - dpkg-divert --local --rename --remove "/usr/bin/$b"; \ - done \ - && rm -f /usr/sbin/policy-rc.d \ && apt-get --quiet --yes autoclean \ && apt-get --quiet --yes autoremove \ && apt-get --quiet --yes clean \ From f30d9200aad744b0259031829c4d604833f0aca9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 21:59:57 +0000 Subject: [PATCH 5/7] Drop systemd from image by routing D-Bus session bus via dbus-x11 netatalk 4.5 hard-depends on the Spotlight stack (localsearch/tinysparql), which requires a D-Bus session bus. Apt satisfied that with dbus-user-session, pulling libpam-systemd -> systemd, whose postinst hangs/segfaults under QEMU during the multi-arch build. systemd is never run in this container. Install dbus-x11 (an alternative provider of default-dbus-session-bus) so the session-bus dependency is met without systemd. A build-time guard fails the build if real systemd is ever pulled in again. --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5f2a3d0..cf2010a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,18 +12,31 @@ ENV PERSISTENT_RUNTIME_DEPS \ ENV DEBIAN_FRONTEND noninteractive +# netatalk 4.5 hard-depends on the Spotlight stack (localsearch/tinysparql), +# which needs a D-Bus session bus. Left to apt, that pulls dbus-user-session -> +# libpam-systemd -> systemd, whose postinst hangs/segfaults under QEMU during +# the multi-arch build (and systemd is never run in this container). Installing +# dbus-x11 -- which also provides default-dbus-session-bus -- satisfies that +# dependency without dragging in systemd. The guard below fails the build if +# real systemd is ever pulled in again, so this stays honest over time. RUN apt-get update \ && apt-get install \ --no-install-recommends \ --fix-missing \ --assume-yes \ $PERSISTENT_RUNTIME_DEPS \ + dbus-x11 \ avahi-daemon \ curl \ ca-certificates \ netatalk \ \ && apt-get --assume-yes upgrade \ + && if dpkg-query -W -f='${Status}\n' systemd 2>/dev/null | grep -q '^install ok installed'; then \ + echo "ERROR: the systemd package was pulled into the image (expected dbus-x11 to prevent this):"; \ + apt-cache rdepends --installed --no-recommends --no-suggests systemd; \ + exit 1; \ + fi \ && apt-get --quiet --yes autoclean \ && apt-get --quiet --yes autoremove \ && apt-get --quiet --yes clean \ From 6f9231faa047fb7e44ab876a4e3ac83f77140fdd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 22:02:15 +0000 Subject: [PATCH 6/7] Route logind dependency via elogind to keep systemd out dbus-x11 removed the session-bus path to systemd, but the dbus system bus (dbus-system-bus-common) still pulled systemd via its default-logind|logind dependency. Install elogind to satisfy logind without systemd, and make the build-time guard print the exact dependency lines still pulling systemd. --- Dockerfile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cf2010a..619985d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ RUN apt-get update \ --assume-yes \ $PERSISTENT_RUNTIME_DEPS \ dbus-x11 \ + elogind \ avahi-daemon \ curl \ ca-certificates \ @@ -33,8 +34,14 @@ RUN apt-get update \ \ && apt-get --assume-yes upgrade \ && if dpkg-query -W -f='${Status}\n' systemd 2>/dev/null | grep -q '^install ok installed'; then \ - echo "ERROR: the systemd package was pulled into the image (expected dbus-x11 to prevent this):"; \ - apt-cache rdepends --installed --no-recommends --no-suggests systemd; \ + echo "ERROR: the systemd package is installed; something still pulls it in."; \ + echo "Installed packages that depend on systemd:"; \ + apt-cache rdepends --installed systemd; \ + echo "--- their systemd/logind dependency lines ---"; \ + for p in $(apt-cache rdepends --installed systemd | tail -n +3 | tr -d ' |'); do \ + echo "## $p:"; \ + apt-cache show "$p" 2>/dev/null | grep -iE '^(Pre-)?Depends:' | grep -iE 'systemd|logind' || true; \ + done; \ exit 1; \ fi \ && apt-get --quiet --yes autoclean \ From 35d36ddba1d61080db5f42422ffa4509a960303e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 22:38:31 +0000 Subject: [PATCH 7/7] Add s6-overlay as PID 1 to supervise dbus, avahi and netatalk Replace the single-process docker-entrypoint.sh with s6-overlay so the container has a real init: zombie reaping, signal forwarding and restart-on-crash for each service. - Install s6-overlay v3.2.1.0, mapping TARGETARCH/TARGETVARIANT to the matching release asset for all built platforms. - /etc/cont-init.d/10-setup: one-shot user/share/afp.conf init (the old entrypoint logic, minus launching daemons). - /etc/services.d/{dbus,avahi,netatalk}: supervised long-running services; avahi/netatalk wait for the system bus socket before starting, and avahi stays opt-in via AVAHI=1. - ENTRYPOINT is now /init; S6_BEHAVIOUR_IF_STAGE2_FAILS=2 aborts the boot if one-time init fails. --- Dockerfile | 36 ++++++++++++++++++++--- docker-entrypoint.sh | 50 -------------------------------- root/etc/cont-init.d/10-setup | 35 ++++++++++++++++++++++ root/etc/services.d/avahi/run | 18 ++++++++++++ root/etc/services.d/dbus/run | 3 ++ root/etc/services.d/netatalk/run | 11 +++++++ 6 files changed, 99 insertions(+), 54 deletions(-) delete mode 100755 docker-entrypoint.sh create mode 100755 root/etc/cont-init.d/10-setup create mode 100755 root/etc/services.d/avahi/run create mode 100755 root/etc/services.d/dbus/run create mode 100755 root/etc/services.d/netatalk/run diff --git a/Dockerfile b/Dockerfile index 619985d..5138c81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN apt-get update \ avahi-daemon \ curl \ ca-certificates \ + xz-utils \ netatalk \ \ && apt-get --assume-yes upgrade \ @@ -59,9 +60,36 @@ RUN apt-get update \ && ln -s /usr/lib/netatalk /etc/netatalk/uams \ && mkdir /media/share -COPY docker-entrypoint.sh /docker-entrypoint.sh +# Install s6-overlay as PID 1 so the container properly supervises its services +# (dbus, avahi, netatalk): zombie reaping, signal forwarding, restart-on-crash. +# Replaces the old single-process docker-entrypoint.sh. +ARG S6_OVERLAY_VERSION=3.2.1.0 +ARG TARGETARCH +ARG TARGETVARIANT +RUN set -eux \ + && case "${TARGETARCH}${TARGETVARIANT:+/${TARGETVARIANT}}" in \ + amd64) s6_arch=x86_64 ;; \ + arm64) s6_arch=aarch64 ;; \ + arm/v7) s6_arch=arm ;; \ + 386) s6_arch=i686 ;; \ + ppc64le) s6_arch=powerpc64le ;; \ + s390x) s6_arch=s390x ;; \ + riscv64) s6_arch=riscv64 ;; \ + *) echo "unsupported target arch: ${TARGETARCH}${TARGETVARIANT}" >&2; exit 1 ;; \ + esac \ + && base="https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}" \ + && curl -fsSL "${base}/s6-overlay-noarch.tar.xz" -o /tmp/s6-noarch.tar.xz \ + && curl -fsSL "${base}/s6-overlay-${s6_arch}.tar.xz" -o /tmp/s6-arch.tar.xz \ + && tar -C / -Jxpf /tmp/s6-noarch.tar.xz \ + && tar -C / -Jxpf /tmp/s6-arch.tar.xz \ + && rm -f /tmp/s6-noarch.tar.xz /tmp/s6-arch.tar.xz + +# s6 service definitions: cont-init.d one-shots + supervised services.d daemons. +COPY root/ / COPY afp.conf /etc/afp.conf -ENV DEBIAN_FRONTEND newt +RUN chmod -R 0755 /etc/cont-init.d /etc/services.d + +# Abort the boot if one-time init (cont-init.d) fails, instead of running half-up. +ENV S6_BEHAVIOUR_IF_STAGE2_FAILS=2 -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD [ "/usr/sbin/netatalk", "-F","/etc/afp.conf","-d"] \ No newline at end of file +ENTRYPOINT ["/init"] \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index 5531135..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -if [ ! -z "${AFP_USER}" ]; then - if [ ! -z "${AFP_UID}" ]; then - cmd="$cmd --uid ${AFP_UID}" - fi - if [ ! -z "${AFP_GID}" ]; then - cmd="$cmd --gid ${AFP_GID}" - groupadd --gid ${AFP_GID} ${AFP_USER} - fi - adduser $cmd --no-create-home --disabled-password --gecos '' "${AFP_USER}" - if [ ! -z "${AFP_PASSWORD}" ]; then - echo "${AFP_USER}:${AFP_PASSWORD}" | chpasswd - fi -fi - -if [ ! -d /media/share ]; then - mkdir /media/share - echo "use -v /my/dir/to/share:/media/share" > readme.txt -fi -chown "${AFP_USER}" /media/share - -if [ ! -d /media/timemachine ]; then - mkdir /media/timemachine - echo "use -v /my/dir/to/timemachine:/media/timemachine" > readme.txt -fi -chown "${AFP_USER}" /media/timemachine - -sed -i'' -e "s,%USER%,${AFP_USER:-},g" /etc/afp.conf - -echo ---begin-afp.conf-- -cat /etc/afp.conf -echo ---end---afp.conf-- - -mkdir -p /var/run/dbus -rm -f /var/run/dbus/pid -dbus-daemon --system - -if [ "${AVAHI}" == "1" ]; then - rm -f /var/run/avahi-daemon/pid - sed -i '/rlimit-nproc/d' /etc/avahi/avahi-daemon.conf - avahi-daemon -D -else - echo "Skipping avahi daemon, enable with env variable AVAHI=1" -fi; - -# remove any previous lockfile that wasn't cleaned up -rm -f /var/run/lock/netatalk - -exec "$@" \ No newline at end of file diff --git a/root/etc/cont-init.d/10-setup b/root/etc/cont-init.d/10-setup new file mode 100755 index 0000000..8103615 --- /dev/null +++ b/root/etc/cont-init.d/10-setup @@ -0,0 +1,35 @@ +#!/command/with-contenv bash +# One-shot initialisation, run by s6-overlay before the long-running services +# start. Mirrors what the old docker-entrypoint.sh did, minus launching the +# daemons (those are now supervised services under /etc/services.d). +set -e + +if [ -n "${AFP_USER}" ]; then + cmd="" + if [ -n "${AFP_UID}" ]; then + cmd="$cmd --uid ${AFP_UID}" + fi + if [ -n "${AFP_GID}" ]; then + cmd="$cmd --gid ${AFP_GID}" + groupadd --gid "${AFP_GID}" "${AFP_USER}" || true + fi + # shellcheck disable=SC2086 + adduser $cmd --no-create-home --disabled-password --gecos '' "${AFP_USER}" || true + if [ -n "${AFP_PASSWORD}" ]; then + echo "${AFP_USER}:${AFP_PASSWORD}" | chpasswd + fi +fi + +mkdir -p /media/share /media/timemachine +chown "${AFP_USER:-root}" /media/share /media/timemachine + +sed -i'' -e "s,%USER%,${AFP_USER:-},g" /etc/afp.conf + +echo "---begin-afp.conf--" +cat /etc/afp.conf +echo "---end---afp.conf--" + +# Clean up any stale runtime state from a previous (unclean) start. +mkdir -p /var/run/dbus +rm -f /var/run/dbus/pid /var/run/dbus/system_bus_socket +rm -f /var/run/lock/netatalk diff --git a/root/etc/services.d/avahi/run b/root/etc/services.d/avahi/run new file mode 100755 index 0000000..c0ad051 --- /dev/null +++ b/root/etc/services.d/avahi/run @@ -0,0 +1,18 @@ +#!/command/with-contenv bash +# Bonjour/mDNS advertisement for the AFP shares. Opt-in via AVAHI=1. +if [ "${AVAHI}" != "1" ]; then + echo "avahi-daemon disabled (set AVAHI=1 to enable); idling." + exec sleep infinity +fi + +# Wait for the system bus socket before starting. +for _ in $(seq 1 50); do + [ -S /var/run/dbus/system_bus_socket ] && break + sleep 0.2 +done + +# rlimit-nproc trips up avahi inside containers; drop it. +sed -i '/rlimit-nproc/d' /etc/avahi/avahi-daemon.conf +rm -f /var/run/avahi-daemon/pid + +exec avahi-daemon -f /etc/avahi/avahi-daemon.conf diff --git a/root/etc/services.d/dbus/run b/root/etc/services.d/dbus/run new file mode 100755 index 0000000..638711e --- /dev/null +++ b/root/etc/services.d/dbus/run @@ -0,0 +1,3 @@ +#!/command/with-contenv bash +# System D-Bus broker. netatalk's Spotlight support and avahi-daemon talk to it. +exec dbus-daemon --system --nofork --nopidfile diff --git a/root/etc/services.d/netatalk/run b/root/etc/services.d/netatalk/run new file mode 100755 index 0000000..cf6d94e --- /dev/null +++ b/root/etc/services.d/netatalk/run @@ -0,0 +1,11 @@ +#!/command/with-contenv bash +# The AFP file server itself. -d keeps it in the foreground so s6 can supervise. + +# Wait for the system bus socket (netatalk connects to it for Spotlight). +for _ in $(seq 1 50); do + [ -S /var/run/dbus/system_bus_socket ] && break + sleep 0.2 +done + +rm -f /var/run/lock/netatalk +exec /usr/sbin/netatalk -F /etc/afp.conf -d