diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b444ef213..de01fcdd37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -344,6 +344,23 @@ jobs: echo "no fail-fast error for PUID with a read-only rootfs"; docker logs frigate-ro-puid; exit 1 fi docker rm -f frigate-ro-puid + - name: Assert EXTRA_GROUPS with read-only fails fast with clear error + run: | + docker run -d --name frigate-ro-groups --shm-size 256m \ + --read-only --tmpfs /tmp:rw,size=1g --tmpfs /run:exec,mode=0755 \ + -e EXTRA_GROUPS=44 \ + -v /tmp/frigate-config-ro:/config \ + -v /tmp/frigate-media-ro:/media/frigate \ + ${{ steps.setup.outputs.image-name }}-amd64 + found=0 + for i in $(seq 1 12); do + if docker logs frigate-ro-groups 2>&1 | grep -q "EXTRA_GROUPS needs a writable /etc"; then found=1; break; fi + sleep 5 + done + if [ "$found" -ne 1 ]; then + echo "no fail-fast error for EXTRA_GROUPS with a read-only rootfs"; docker logs frigate-ro-groups; exit 1 + fi + docker rm -f frigate-ro-groups - name: Teardown if: always() run: docker rm -f frigate || true diff --git a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/init-usermod/run b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/init-usermod/run index ab93a6c9de..489aa6c664 100755 --- a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/init-usermod/run +++ b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/init-usermod/run @@ -79,6 +79,15 @@ fi # EXTRA_GROUPS: numeric host GIDs granting device access (e.g. host render/video) if [[ -n "${EXTRA_GROUPS:-}" ]]; then + # groupadd and usermod -aG both write /etc/group. Checked up front so a + # read-only rootfs reports the real problem instead of dying mid-loop. + if [[ ! -w /etc/group ]]; then + echo "[ERROR] EXTRA_GROUPS needs a writable /etc and is not compatible with read_only: true." >&2 + echo "[ERROR] Use docker's group_add: with the same GIDs instead; it needs no writes inside the container." >&2 + echo "[ERROR] See https://docs.frigate.video/configuration/non_root for the compatibility matrix." >&2 + exit 1 + fi + for gid in ${EXTRA_GROUPS//,/ }; do if ! [[ "$gid" =~ ^[0-9]+$ ]] || [[ "$gid" -eq 0 ]]; then echo "[ERROR] EXTRA_GROUPS must be nonzero numeric GIDs, got '${gid}'" >&2 diff --git a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/prepare/run b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/prepare/run index 343b2e1082..533133ed38 100755 --- a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/prepare/run +++ b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/prepare/run @@ -189,7 +189,14 @@ fi # Must stay after the sweep, which reads an absent /media/frigate as an # unmounted volume rather than a swept one if [[ "$(id -u)" -eq 0 && ! -d /media/frigate ]]; then - mkdir -p /media/frigate + # The image does not ship this directory, so on a read-only rootfs it can + # only come from a mount. Report that rather than failing under errexit. + if ! mkdir -p /media/frigate 2>/dev/null; then + echo "[ERROR] /media/frigate does not exist and could not be created, which is what happens with read_only: true and no recordings volume." >&2 + echo "[ERROR] Mount a volume at /media/frigate." >&2 + echo "[ERROR] See https://docs.frigate.video/configuration/non_root for the compatibility matrix." >&2 + exit 1 + fi if [[ "${FRIGATE_RUN_AS_ROOT:-false}" != "true" ]]; then chown "${PUID:-1000}:${PGID:-1000}" /media/frigate fi diff --git a/docs/docs/configuration/non_root.md b/docs/docs/configuration/non_root.md index a45b66680d..b1053a7746 100644 --- a/docs/docs/configuration/non_root.md +++ b/docs/docs/configuration/non_root.md @@ -21,7 +21,7 @@ Most upgrades need nothing. Frigate aligns your volume ownership on the first bo | Root (escape hatch) | `FRIGATE_RUN_AS_ROOT=true` | Never touched | Not supported | | Granular root | `FRIGATE_ROOT_SERVICES=frigate` | Aligned at boot; recordings and exports also at create | Not supported | -`PUID`/`PGID` remapping runs `usermod` at startup, which writes to `/etc/passwd`, so it can't work with a read-only root filesystem. That combination stops at startup with a message pointing here. Docker's `user:` mode has no such startup work, which is why it's the one mode that supports `read_only: true`; see [Hardened deployment](#hardened-deployment). +`PUID`/`PGID` remapping runs `usermod` at startup, which writes to `/etc/passwd`, so it can't work with a read-only root filesystem. That combination stops at startup with a message pointing here. `EXTRA_GROUPS` writes to `/etc/group` and stops the same way; use Docker's `group_add:` instead, which needs no writes inside the container. Docker's `user:` mode has no such startup work, which is why it's the one mode that supports `read_only: true`; see [Hardened deployment](#hardened-deployment). `FRIGATE_RUN_AS_ROOT` is matched against the exact lowercase string `true`. `True`, `TRUE`, and `1` are all ignored. `FRIGATE_DEVICE_ACLS` works the same way: only the lowercase string `false` turns off the automatic device grants.