#!/bin/bash # postgres-fuse-wrap: recreate FUSE-volatile dirs then exec postgres as mem0. # Run by supervisord as root on every (re)start so dirs exist even after the # ~80s crash-recovery fsync window during which FUSE drops empty dirs. set -e PGDATA=/data/pgdata # Dirs that must exist AND be completely empty (Postgres repopulates them). for dir in pg_replslot pg_twophase pg_dynshmem pg_notify \ pg_snapshots pg_logical/snapshots pg_logical/mappings; do mkdir -p "$PGDATA/$dir" find "$PGDATA/$dir" -mindepth 1 -delete 2>/dev/null || true done # Dirs that must exist; contents are written by Postgres — only recreate if missing. for dir in pg_subtrans pg_xact pg_multixact/members pg_multixact/offsets; do mkdir -p "$PGDATA/$dir" done # Dirs that tolerate marker files (Postgres ignores unknown files here) for dir in pg_commit_ts pg_serial pg_stat pg_stat_tmp; do mkdir -p "$PGDATA/$dir" find "$PGDATA/$dir" -mindepth 1 -name '.git' -type d -exec rm -rf {} + 2>/dev/null || true done # pg_tblspc must be a symlink to tmpfs — FUSE drops real empty dirs mkdir -p /tmp/pg_tblspc rm -rf "$PGDATA/pg_tblspc" ln -sfn /tmp/pg_tblspc "$PGDATA/pg_tblspc" # Fix ownership: this wrapper runs as root but postgres runs as mem0. # Only chown the dirs we just created — no recursive chown over FUSE (kills mount). chown mem0:mem0 \ "$PGDATA/pg_subtrans" \ "$PGDATA/pg_xact" \ "$PGDATA/pg_multixact" \ "$PGDATA/pg_multixact/members" \ "$PGDATA/pg_multixact/offsets" \ "$PGDATA/pg_snapshots" \ "$PGDATA/pg_logical" \ "$PGDATA/pg_logical/snapshots" \ "$PGDATA/pg_logical/mappings" \ "$PGDATA/pg_commit_ts" \ "$PGDATA/pg_serial" \ "$PGDATA/pg_stat" \ "$PGDATA/pg_stat_tmp" \ "$PGDATA/pg_replslot" \ "$PGDATA/pg_twophase" \ "$PGDATA/pg_dynshmem" \ "$PGDATA/pg_notify" \ 2>/dev/null || true chown -h mem0:mem0 "$PGDATA/pg_tblspc" # Background loop: FUSE can drop empty dirs at any time during runtime, not # just at startup. Re-assert critical dirs every 5s while postgres is alive. fuse_keepalive() { while true; do sleep 5 for dir in pg_replslot pg_twophase pg_dynshmem pg_notify \ pg_snapshots pg_logical/snapshots pg_logical/mappings; do if [ ! -d "$PGDATA/$dir" ]; then mkdir -p "$PGDATA/$dir" 2>/dev/null && \ chown mem0:mem0 "$PGDATA/$dir" 2>/dev/null || true fi done if [ ! -L "$PGDATA/pg_tblspc" ]; then rm -rf "$PGDATA/pg_tblspc" ln -sfn /tmp/pg_tblspc "$PGDATA/pg_tblspc" 2>/dev/null chown -h mem0:mem0 "$PGDATA/pg_tblspc" 2>/dev/null || true fi done } fuse_keepalive & KEEPALIVE_PID=$! # Drop to mem0 and exec postgres. # Note: exec replaces this shell, so trap cleanup of keepalive on exit. trap "kill $KEEPALIVE_PID 2>/dev/null" EXIT exec gosu mem0 /usr/lib/postgresql/15/bin/postgres \ -D "$PGDATA" \ -c config_file="$PGDATA/postgresql.conf"