diff --git a/deploy/webmail.sh b/deploy/webmail.sh index c78463c..40dfb54 100755 --- a/deploy/webmail.sh +++ b/deploy/webmail.sh @@ -326,10 +326,15 @@ cmd_up() { ylw "SEARCH_BACKEND=meili — starting the Meilisearch container too" profile=(--profile meili) } - # Best-effort only: Docker creates any missing bind-mount path itself, and - # the `init` service fixes ownership. This line must not be fatal, because - # DATA_ROOT is typically root-owned while you may be running as yourself. - mkdir -p "${DATA_ROOT}"/{postgres,blobs,redis,backups,meili,state} 2>/dev/null || true + # DATA_ROOT must exist before compose runs: Synology's Docker daemon will + # not create a missing bind-mount source, it just fails the container. The + # `init` service creates the subdirectories from there. + mkdir -p "${DATA_ROOT}" 2>/dev/null || true + if [ ! -d "${DATA_ROOT}" ]; then + die "cannot create ${DATA_ROOT} (running as $(id -un)). Create it first: + sudo mkdir -p ${DATA_ROOT} +then re-run this command." + fi detect_compose # shellcheck disable=SC2086 compose "${profile[@]}" up -d "${build[@]}" $COMPOSE_WAIT diff --git a/docker-compose.yml b/docker-compose.yml index e367058..57f2c64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -96,17 +96,22 @@ services: - -c - | set -e - mkdir -p /mnt/blobs /mnt/backups /mnt/redis /mnt/meili - chown -R 10001:10001 /mnt/blobs # backend runs as uid 10001 - chown -R 999:999 /mnt/backups # pg_dump runs as postgres (999) - chown -R 999:999 /mnt/redis # redis runs as uid 999 + mkdir -p /mnt/postgres /mnt/blobs /mnt/backups /mnt/redis /mnt/meili /mnt/state + # Only recurse when ownership is actually wrong: `chown -R` over a large + # blob store on every start would be minutes of pointless disk churn. + [ "$$(stat -c %u /mnt/blobs)" = "10001" ] || chown -R 10001:10001 /mnt/blobs + [ "$$(stat -c %u /mnt/backups)" = "999" ] || chown -R 999:999 /mnt/backups + [ "$$(stat -c %u /mnt/redis)" = "999" ] || chown -R 999:999 /mnt/redis chmod 700 /mnt/backups echo "[init] data directories ready" volumes: - - ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/blobs:/mnt/blobs - - ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/backups:/mnt/backups - - ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/redis:/mnt/redis - - ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/meili:/mnt/meili + # ONE mount, of DATA_ROOT itself. Synology's Docker daemon refuses to + # create a missing bind-mount source ("Bind mount failed: ... doesn't + # exists") instead of creating it the way Docker does elsewhere, so every + # subdirectory mounted below must exist before its container is created. + # Mounting the parent here means only DATA_ROOT has to pre-exist, and this + # service creates the rest — which is why everything now depends on it. + - ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}:/mnt restart: "no" logging: *logging @@ -135,6 +140,8 @@ services: # Full page writes stay ON: NAS filesystems (btrfs/ext4) are not 8k-atomic. volumes: - ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/postgres:/var/lib/postgresql/data + depends_on: + init: { condition: service_completed_successfully } healthcheck: # -h 127.0.0.1 forces a TCP check. Without it pg_isready uses the Unix # socket, which the temporary server that the postgres image runs during