remote-postgres / start.sh
imkrish's picture
Switch default tunnel to bore (no token needed)
8dec6cd verified
Raw History Blame Contribute Delete
5.41 kB
#!/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"