diff --git a/outline/README.org b/outline/README.org index 9f2a0c5..ee958fe 100644 --- a/outline/README.org +++ b/outline/README.org @@ -64,6 +64,22 @@ there is no drift, and blocks the deployment (=exit 1=) if the realignment fails, so the problem is visible at deploy time instead of as a cryptic crash-loop. +* Datastore ownership alignment + +The =init= hook aligns the ownership of the service datastore with +the user the Outline container runs as. Since 1.10.0 the image runs +as the unprivileged =nodejs= user (older images, up to 1.6.1, ran as +=root=), while the datastore is provisioned by =root=. Without +realignment the application cannot write its =uploads=, =public= and +=avatars= buckets and every attachment upload fails with "Permission +denied writing to ... Check the host machine file system +permissions". This was seen on 2026-09-11 on elabore.coop after the +1.6.1 to 1.10.0 upgrade, on every existing datastore. + +The hook reads the user from the image's =Config.User=, so it stays +version-agnostic: images running as =root= are left untouched, and +re-running the hook on an already aligned datastore is a no-op. + * Building a new image We use the official image with an added patch due to 2 bugs: diff --git a/outline/hooks/init b/outline/hooks/init index cd8413b..d00df11 100755 --- a/outline/hooks/init +++ b/outline/hooks/init @@ -71,4 +71,42 @@ $SERVICE_NAME: #DEBUG: \"http\" " +## The datastore is bind-mounted into the container. Outline runs as +## an unprivileged user (image Config.User: root up to 1.6.1, "nodejs" +## since 1.10.0) and must write its uploads, public and avatars +## buckets. Provisioned by root, the datastore is not writable by +## that user and every upload fails with "Permission denied writing +## to ... Check the host machine file system permissions". Align the +## datastore ownership with the image user; skip images running as +## root. See README.org, "Datastore ownership alignment". +app_user= +if [ -n "$DOCKER_BASE_IMAGE" ]; then + app_user=$(docker image inspect "$DOCKER_BASE_IMAGE" \ + --format '{{.Config.User}}') || exit 1 +fi + +case "$app_user" in + ""|0|0:0|root) + ## image runs as root: nothing to align + ;; + *:*) + uid="${app_user%%:*}" + gid="${app_user#*:}" + ;; + *) + uid_gid=($(docker_get_uid_gid "$SERVICE_NAME" "$app_user" "$app_user")) || exit 1 + uid="${uid_gid[0]}" + gid="${uid_gid[1]}" + ;; +esac + +if [ -n "${uid:-}" ]; then + mkdir -p "$SERVICE_DATASTORE" + chown -R "$uid:$gid" "$SERVICE_DATASTORE" || { + err "Failed to align datastore ownership on '$uid:$gid'." + exit 1 + } + info "Datastore ownership aligned on '$uid:$gid'." +fi +