File size: 5,409 Bytes
8003382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8dec6cd
 
 
 
8003382
8dec6cd
8003382
 
8dec6cd
 
 
 
 
 
 
 
 
 
8003382
8dec6cd
 
 
 
 
 
 
 
 
 
 
 
 
8003382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env bash
set -euo pipefail

# Postgres client/server binaries live in /usr/lib/postgresql/<ver>/bin on Debian
export PATH="$(ls -d /usr/lib/postgresql/*/bin 2>/dev/null | head -n1):$PATH"

: "${POSTGRES_USER:=demo}"
: "${POSTGRES_DB:=demo}"
: "${PGPORT:=5432}"
: "${APP_PORT:=7860}"
export PGHOST=/tmp   # local clients connect over the unix socket in /tmp (trust auth)

# ---- pick data + backup locations ----
# HF persistent storage (if enabled) mounts a writable /data owned by uid 1000.
# When present we keep BOTH the live cluster AND backups there so they survive restarts.
if [ -d /data ] && [ -w /data ]; then
  echo "[start] Persistent /data detected β€” using it for the cluster and backups."
  export PGDATA="${PGDATA:-/data/pgdata}"
  export BACKUP_DIR="${BACKUP_DIR:-/data/backups}"
else
  echo "[start] No persistent /data β€” cluster is EPHEMERAL; relying on backup/restore."
  export PGDATA="${PGDATA:-/home/appuser/data/pgdata}"
  export BACKUP_DIR="${BACKUP_DIR:-/home/appuser/backups}"
fi
mkdir -p "$BACKUP_DIR"

# Generate a strong password if the user didn't supply one as a Space secret
if [ -z "${POSTGRES_PASSWORD:-}" ]; then
  POSTGRES_PASSWORD="$(python3 -c 'import secrets;print(secrets.token_urlsafe(18))')"
  echo "[start] No POSTGRES_PASSWORD secret set β€” generated one for this session."
fi
export POSTGRES_PASSWORD POSTGRES_USER POSTGRES_DB PGPORT BACKUP_DIR

FRESH_INIT=0

# ---- initialize the cluster (only when the data dir is empty) ----
if [ ! -s "$PGDATA/PG_VERSION" ]; then
  FRESH_INIT=1
  echo "[start] Initializing PostgreSQL cluster at $PGDATA"
  mkdir -p "$PGDATA"
  chmod 700 "$PGDATA"
  PWFILE="$(mktemp)"
  printf '%s' "$POSTGRES_PASSWORD" > "$PWFILE"
  initdb -D "$PGDATA" \
         --auth-host=scram-sha-256 \
         --auth-local=trust \
         -U "$POSTGRES_USER" \
         --pwfile="$PWFILE" >/dev/null
  rm -f "$PWFILE"

  {
    echo "listen_addresses = '*'"
    echo "port = $PGPORT"
    echo "unix_socket_directories = '/tmp'"
  } >> "$PGDATA/postgresql.conf"

  {
    echo "host all all 0.0.0.0/0 scram-sha-256"
    echo "host all all ::/0      scram-sha-256"
  } >> "$PGDATA/pg_hba.conf"
fi

# ---- start postgres ----
echo "[start] Starting PostgreSQL on port $PGPORT"
pg_ctl -D "$PGDATA" -o "-p $PGPORT" -w -l "$PGDATA/server.log" start

# ensure the target database exists
if ! psql -p "$PGPORT" -U "$POSTGRES_USER" -d postgres -tAc \
       "SELECT 1 FROM pg_database WHERE datname='$POSTGRES_DB'" | grep -q 1; then
  echo "[start] Creating database '$POSTGRES_DB'"
  createdb -p "$PGPORT" -U "$POSTGRES_USER" "$POSTGRES_DB"
fi

# ---- restore the latest backup onto a freshly-initialized cluster ----
# On a fresh boot (ephemeral disk, or first run) pull back the most recent dump so the
# data "survives" restarts. Skipped when the cluster already had data (persistent disk).
if [ "$FRESH_INIT" = "1" ]; then
  echo "[start] Fresh cluster β€” checking for a backup to restore"
  python /app/backup.py restore || echo "[start] no restore performed"
fi

# ---- expose Postgres publicly via a TCP tunnel ----
# Default: bore (no signup). If NGROK_AUTHTOKEN is set, use ngrok instead.
# Whichever runs writes the public endpoint to /tmp/tunnel.json for the web app.
rm -f /tmp/tunnel.json
if [ -n "${NGROK_AUTHTOKEN:-}" ]; then
  echo "[start] Tunnel: ngrok (NGROK_AUTHTOKEN present)"
  ngrok config add-authtoken "$NGROK_AUTHTOKEN" >/dev/null 2>&1 || true
  ngrok tcp "$PGPORT" --log=stdout > "$HOME/ngrok.log" 2>&1 &
  ( set +e
    for _ in $(seq 1 30); do
      pub="$(curl -fsS localhost:4040/api/tunnels 2>/dev/null | grep -oE 'tcp://[^"]+' | head -1)"
      if [ -n "$pub" ]; then
        hp="${pub#tcp://}"
        printf '{"host":"%s","port":"%s","provider":"ngrok"}' "${hp%%:*}" "${hp##*:}" > /tmp/tunnel.json
        echo "[start] ngrok tunnel up at ${hp}"; break
      fi
      sleep 1
    done ) &
else
  BORE_HOST="${BORE_HOST:-bore.pub}"
  echo "[start] Tunnel: bore -> ${BORE_HOST} (no token needed)"
  bore local "$PGPORT" --to "$BORE_HOST" > "$HOME/bore.log" 2>&1 &
  ( set +e
    for _ in $(seq 1 30); do
      port="$(grep -aoE 'listening at [^[:space:]]+:[0-9]+' "$HOME/bore.log" 2>/dev/null \
              | grep -oE '[0-9]+$' | tail -1)"
      if [ -n "$port" ]; then
        printf '{"host":"%s","port":"%s","provider":"bore"}' "$BORE_HOST" "$port" > /tmp/tunnel.json
        echo "[start] bore tunnel up at ${BORE_HOST}:${port}"; break
      fi
      sleep 1
    done ) &
fi

# ---- keep the Space awake (free Spaces sleep after 48h with NO http traffic) ----
# Postgres/ngrok traffic does NOT count β€” only requests to this web app do. So we hit
# the PUBLIC url (goes through HF's router => resets the idle timer). SPACE_HOST is
# injected by HF, e.g. user-space.hf.space. Each hit is logged to the Space logs.
if [ -n "${SPACE_HOST:-}" ]; then
  echo "[start] Self keep-alive enabled β€” pinging https://${SPACE_HOST}/keepalive every 20m"
  ( while true; do
      sleep 1200
      ts="$(date '+%Y-%m-%d %H:%M:%S')"
      code="$(curl -fsS -o /dev/null -w '%{http_code}' \
              "https://${SPACE_HOST}/keepalive?src=self" 2>/dev/null || echo 000)"
      echo "[keepalive] self ${ts} -> ${code}"
    done ) &
fi

# ---- start the web UI (foreground; keeps the container alive) ----
echo "[start] Starting web UI on port $APP_PORT"
cd /app
exec uvicorn main:app --host 0.0.0.0 --port "$APP_PORT"