Spaces:
Paused
Paused
File size: 8,548 Bytes
5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 0497dac 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 5ec8557 36f3df7 f8a7f2f 36f3df7 5ec8557 407d645 5ec8557 36f3df7 5ec8557 1e6332d 6d2d3c8 f8a7f2f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | #!/bin/bash
set -e
BOOT_START=$(date +%s)
echo "[entrypoint] mem0 Self-Hosted on HuggingFace Spaces"
echo "==============================================================================="
# ββ Configuration βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Affine-style: PGDATA lives DIRECTLY on the HF persistent volume (/data).
# No restore-rsync, no backup loop β both hammered the FUSE mount until it
# died ("Transport endpoint is not connected"). FUSE quirks handled below:
# empty dirs are dropped and .git/ is injected into tracked dirs, so dirs
# Postgres scans as object stores must be purged at every boot.
MEM0_WORK="/opt/data" # Fast local FS: history, sessions, logs
PGDATA="/data/pgdata" # Persistent HF bucket (FUSE)
MEM0_UID=1000
MEM0_GID=1000
# ββ Singleton guard ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Hanya satu instance yang menjalankan steps. Instance duplikat (dari HF rapid-
# restart) cukup sleep infinity β tidak exit (exit = HF restart lagi).
# MEM0_ENTRYPOINT_OWNER di-export sebelum exec gosu sehingga instance setelah
# privilege-drop tahu dia adalah kelanjutan sah, bukan duplikat.
ENTRYPOINT_PID_FILE="/tmp/mem0-entrypoint.pid"
ENTRYPOINT_LOG="/tmp/mem0-entrypoint.log"
# Always clear stale PID file at container startup (new container = new PID namespace)
rm -f "$ENTRYPOINT_PID_FILE" "$ENTRYPOINT_LOG" 2>/dev/null || true
if [ -z "${MEM0_ENTRYPOINT_OWNER:-}" ]; then
# Instance baru tanpa token β cek apakah ada instance lain yang sudah running
if [ -f "$ENTRYPOINT_PID_FILE" ]; then
OLD_PID=$(cat "$ENTRYPOINT_PID_FILE" 2>/dev/null || echo "")
if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
echo "[entrypoint] Instance $OLD_PID already running β standing by silently."
sleep infinity
exit 0
else
echo "[entrypoint] Stale PID $OLD_PID (dead) β taking over..."
rm -f "$ENTRYPOINT_PID_FILE" "$ENTRYPOINT_LOG" 2>/dev/null || true
fi
fi
echo $$ > "$ENTRYPOINT_PID_FILE"
export MEM0_ENTRYPOINT_OWNER=$$
# Buat log world-writable sekarang (sebagai root) agar mem0 bisa write setelah gosu
touch "$ENTRYPOINT_LOG" && chmod 666 "$ENTRYPOINT_LOG"
fi
exec > >(tee -a "$ENTRYPOINT_LOG") 2>&1
# ββ Stage 1: Prepare persistent storage (as root) βββββββββββββββββββββββββββ
if [ "$(id -u)" = "0" ]; then
echo ""
echo "=== 1. Preparing persistent storage ==="
mkdir -p "$MEM0_WORK"/{history,sessions,logs}
mkdir -p "$PGDATA"
chown ${MEM0_UID}:${MEM0_GID} "$MEM0_WORK"/* 2>/dev/null || true
chown -R ${MEM0_UID}:${MEM0_GID} "$PGDATA"
# Init if empty
FRESH_INIT=0
if [ ! -f "$PGDATA/PG_VERSION" ]; then
FRESH_INIT=1
echo "[entrypoint] Initializing PostgreSQL at $PGDATA"
chmod 700 "$PGDATA"
gosu mem0 /usr/lib/postgresql/15/bin/initdb -D "$PGDATA" --auth=trust --encoding=UTF8 --locale=C.UTF-8
# Configure PostgreSQL for local connections
cat >> "$PGDATA/postgresql.conf" <<EOF
listen_addresses = 'localhost'
port = 5432
max_connections = 100
shared_buffers = 128MB
effective_cache_size = 512MB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 4MB
min_wal_size = 1GB
max_wal_size = 4GB
EOF
# Allow local connections without password (incl. replication for future use)
cat > "$PGDATA/pg_hba.conf" <<EOF
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
local replication all trust
host replication all 127.0.0.1/32 trust
host replication all ::1/128 trust
EOF
chown -R ${MEM0_UID}:${MEM0_GID} "$PGDATA"
echo "[TIMER] PostgreSQL init complete"
else
echo "PostgreSQL data exists at $PGDATA"
fi
# FUSE repair, every boot (mirrors affine bootstrap.sh):
# - HF bucket drops empty dirs and injects .git/ into tracked dirs.
# - Postgres scans these dirs as object stores; any stray entry β PANIC.
# They must exist and be COMPLETELY EMPTY (no .gitkeep).
for dir in pg_replslot pg_twophase pg_dynshmem pg_notify; do
mkdir -p "$PGDATA/$dir"
find "$PGDATA/$dir" -mindepth 1 -delete 2>/dev/null || true
done
# These tolerate a .gitkeep marker (Postgres ignores unknown files here)
for dir in pg_commit_ts pg_serial pg_stat pg_stat_tmp \
pg_logical/snapshots pg_logical/mappings; do
mkdir -p "$PGDATA/$dir"
touch "$PGDATA/$dir/.gitkeep"
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"
# Defensive re-assert: only UID 1000 persists on the FUSE idmapped mount.
# Scoped chown on dirs only β recursive chown over FUSE has killed the mount.
chown ${MEM0_UID}:${MEM0_GID} "$PGDATA" "$PGDATA"/*/ 2>/dev/null || true
chmod 700 "$PGDATA"
# Fix socket dir so mem0 can create the unix socket lock file
mkdir -p /var/run/postgresql
chown ${MEM0_UID}:${MEM0_GID} /var/run/postgresql
export FRESH_INIT
fi
# ββ Stage 2: First-boot DB setup (fresh cluster only) ββββββββββββββββββββββ
if [ "$(id -u)" = "0" ] && [ "${FRESH_INIT:-0}" = "1" ]; then
echo ""
echo "=== 2. First-boot database setup ==="
mkdir -p /var/log/postgresql
chown -R ${MEM0_UID}:${MEM0_GID} /var/log/postgresql
# FUSE repair before first pg_ctl start (same logic as supervisord wrapper)
for dir in pg_replslot pg_twophase pg_dynshmem pg_notify; do
mkdir -p "$PGDATA/$dir"
find "$PGDATA/$dir" -mindepth 1 -delete 2>/dev/null || true
done
for dir in pg_commit_ts pg_serial pg_stat pg_stat_tmp pg_logical/snapshots pg_logical/mappings; do
mkdir -p "$PGDATA/$dir"
touch "$PGDATA/$dir/.gitkeep"
find "$PGDATA/$dir" -mindepth 1 -name .git -type d -exec rm -rf {} + 2>/dev/null || true
done
mkdir -p /tmp/pg_tblspc
rm -rf "$PGDATA/pg_tblspc"
ln -sfn /tmp/pg_tblspc "$PGDATA/pg_tblspc"
# Start postgres temporarily to run init-db + migrations, then stop
echo "Running database initialization script..."
PGLOG="/tmp/pg-init.log"
touch "$PGLOG" && chown ${MEM0_UID}:${MEM0_GID} "$PGLOG"
set +e
gosu mem0 /usr/lib/postgresql/15/bin/pg_ctl -D "$PGDATA" -l "$PGLOG" -w start
PG_START_RC=$?
set -e
echo "[pg_ctl start exit: $PG_START_RC]"
echo "=== pg startup log ==="
cat "$PGLOG"
echo "=== end pg log ==="
if [ "$PG_START_RC" -ne 0 ]; then
echo "[ERROR] PostgreSQL failed to start; aborting"
exit 1
fi
gosu mem0 /init-db.sh
# Run app-DB migrations (users/settings/etc. in mem0_app) BEFORE first boot.
# Fresh volumes have no tables; without this, uvicorn crashes at import on
# any DB access and supervisord puts mem0-api into FATAL.
echo "=== Running database migrations ==="
gosu mem0 sh -c 'cd /opt/mem0/api && python -m alembic upgrade head' \
|| echo "[WARN] alembic failed (non-fatal; API will retry tables lazily)"
gosu mem0 /usr/lib/postgresql/15/bin/pg_ctl -D "$PGDATA" -w stop
fi
echo ""
echo "=== 3. Starting services via supervisord ==="
# Stream program logs to container stdout so tracebacks are visible in HF logs.
# (supervisord itself cannot reopen /dev/fd/1 -> EACCES on every dispatcher)
touch /var/log/supervisor/mem0-api.log /var/log/supervisor/mem0-api.err.log \
/var/log/supervisor/mem0-dashboard.log /var/log/supervisor/mem0-dashboard.err.log \
/var/log/supervisor/postgresql.log /var/log/supervisor/postgresql.err.log \
/var/log/supervisor/nginx.log /var/log/supervisor/nginx.err.log
chmod 666 /var/log/supervisor/*.log
tail -q -F /var/log/supervisor/*.log &
# supervisord runs as root (user=root in supervisord.conf) so it can recreate
# FUSE-volatile dirs before each postgres spawn via the pg wrapper command.
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf |