#!/bin/sh # ══════════════════════════════════════════════════════════════ # MicProtocol HF Space entrypoint — starts the full stack in one # container, then runs nginx in the foreground on APP_PORT. # ══════════════════════════════════════════════════════════════ set -e APP_PORT="${APP_PORT:-7860}" API_PORT="${API_PORT:-4000}" WEB_PORT="${WEB_PORT:-3000}" ADMIN_PORT="${ADMIN_PORT:-3006}" MIC_REALTIME_PORT="${MIC_REALTIME_PORT:-4001}" POSTGRES_PORT="${POSTGRES_PORT:-5432}" REDIS_PORT="${REDIS_PORT:-6379}" MEILISEARCH_PORT="${MEILISEARCH_PORT:-7700}" # ─── Required secrets with stable fallbacks ──────────────────── : "${POSTGRES_USER:=micprotocol}" : "${POSTGRES_PASSWORD:=micprotocol_secret}" : "${POSTGRES_DB:=micprotocol}" : "${REDIS_PASSWORD:=redis_secret}" : "${MEILISEARCH_API_KEY:=masterKey}" : "${COOKIE_SECRET:=$(openssl rand -hex 32)}" : "${BETTER_AUTH_SECRET:=$(openssl rand -base64 32)}" : "${MIC_REALTIME_INTERNAL_TOKEN:=$(openssl rand -hex 32)}" : "${MIC_REALTIME_SHARED_SECRET:=$(openssl rand -hex 32)}" export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB export REDIS_PASSWORD REDIS_HOST=localhost REDIS_PORT=$REDIS_PORT export MEILISEARCH_API_KEY MEILISEARCH_HOST=http://localhost:$MEILISEARCH_PORT export COOKIE_SECRET BETTER_AUTH_SECRET export MIC_REALTIME_INTERNAL_TOKEN MIC_REALTIME_SHARED_SECRET # Derived URLs export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public" export REDIS_URL="redis://:${REDIS_PASSWORD}@localhost:${REDIS_PORT}" export API_URL="http://localhost:${API_PORT}" export WEB_URL="http://localhost:${WEB_PORT}" export BETTER_AUTH_URL="http://localhost:${API_PORT}" export MIC_REALTIME_URL="http://localhost:${MIC_REALTIME_PORT}" export MIC_REALTIME_PUBLIC_URL="http://localhost:${MIC_REALTIME_PORT}" export INTERNAL_API_TOKEN="${MIC_REALTIME_INTERNAL_TOKEN}" export NODE_ENV="${NODE_ENV:-production}" export DEV_AUTH_BYPASS_ENABLED="${DEV_AUTH_BYPASS_ENABLED:-true}" # Public URLs the browser sees (overridable via HF space variables) : "${NEXT_PUBLIC_API_URL:=http://localhost:${APP_PORT}/api}" : "${NEXT_PUBLIC_MIC_REALTIME_URL:=ws://localhost:${APP_PORT}/ws}" : "${NEXT_PUBLIC_ROOT_DOMAIN:=localhost}" export NEXT_PUBLIC_API_URL NEXT_PUBLIC_MIC_REALTIME_URL NEXT_PUBLIC_ROOT_DOMAIN log() { echo "[entrypoint] $*"; } wait_for_port() { local host="$1" port="$2" name="$3" log "Waiting for $name at $host:$port ..." for i in $(seq 1 60); do if nc -z "$host" "$port" 2>/dev/null; then log "$name is ready." return 0 fi sleep 0.5 done log "WARNING: $name at $host:$port did not become ready." } # ─── 1. PostgreSQL ───────────────────────────────────────────── log "Initialising PostgreSQL ..." # Locate the postgres binary directory (Debian installs versioned dirs) PG_BIN_DIR=$(ls -d /usr/lib/postgresql/*/bin 2>/dev/null | head -1) if [ -z "$PG_BIN_DIR" ]; then log "ERROR: postgres binaries not found" exit 1 fi PG_INITDB="$PG_BIN_DIR/initdb" PG_CTL="$PG_BIN_DIR/pg_ctl" PG_POSTGRES="$PG_BIN_DIR/postgres" mkdir -p /var/lib/postgresql/data /var/run/postgresql /var/log chown -R postgres:postgres /var/lib/postgresql/data /var/run/postgresql chmod 700 /var/lib/postgresql/data # Initialize data directory if empty if [ -z "$(ls -A /var/lib/postgresql/data 2>/dev/null)" ]; then su postgres -c "$PG_INITDB -D /var/lib/postgresql/data -U ${POSTGRES_USER} --auth=trust" >/var/log/initdb.log 2>&1 || true fi # Start postgres directly in the background (more reliable than pg_ctl in containers) rm -f /tmp/.s.PGSQL.* /var/run/postgresql/.s.PGSQL.* su postgres -c "$PG_POSTGRES -D /var/lib/postgresql/data -c listen_addresses=localhost -c port=${POSTGRES_PORT} -c unix_socket_directories=/var/run/postgresql" >/var/log/postgresql.log 2>&1 & PG_PID=$! # Wait for postgres to accept connections for i in $(seq 1 30); do if su postgres -c "$PG_CTL -D /var/lib/postgresql/data status" >/dev/null 2>&1; then break fi if nc -z localhost "$POSTGRES_PORT" 2>/dev/null; then break fi sleep 1 done wait_for_port localhost "$POSTGRES_PORT" postgres # Configure password + database (trust auth means any password works, but set it anyway) su postgres -c "psql -tAc \"SELECT 1 FROM pg_database WHERE datname='${POSTGRES_DB}'\"" 2>/dev/null | grep -q 1 \ || su postgres -c "createdb ${POSTGRES_DB}" 2>/dev/null || true su postgres -c "psql -c \"ALTER USER ${POSTGRES_USER} WITH PASSWORD '${POSTGRES_PASSWORD}';\"" 2>/dev/null || true # ─── 2. Redis ────────────────────────────────────────────────── log "Starting Redis ..." redis-server --daemonize yes --port "$REDIS_PORT" --requirepass "$REDIS_PASSWORD" >/dev/null 2>&1 || true wait_for_port localhost "$REDIS_PORT" redis # ─── 3. Meilisearch ──────────────────────────────────────────── log "Starting Meilisearch ..." mkdir -p /app/meili_data MEILISEARCH_MASTER_KEY="$MEILISEARCH_API_KEY" nohup meilisearch \ --http-addr "localhost:${MEILISEARCH_PORT}" \ --env production \ --data-path /app/meili_data \ --no-analytics >/var/log/meilisearch.log 2>&1 & wait_for_port localhost "$MEILISEARCH_PORT" meilisearch # ─── 4. Database schema (push, idempotent) ───────────────────── log "Pushing Prisma schema ..." cd /app/apps/api npx prisma db push --accept-data-loss >/var/log/prisma-push.log 2>&1 || log "prisma db push completed with warnings (see /var/log/prisma-push.log)" cd /app # ─── 5. mic-realtime (Erlang) ────────────────────────────────── log "Starting mic-realtime ..." export MIC_REALTIME_PORT MIC_REALTIME_HOST=0.0.0.0 \ MICPROTOCOL_API_URL="http://localhost:${API_PORT}" \ REDIS_HOST=localhost REDIS_PORT="$REDIS_PORT" REDIS_PASSWORD="$REDIS_PASSWORD" nohup /opt/mic-realtime/_build/default/rel/mic_realtime/bin/mic_realtime foreground \ >/var/log/mic-realtime.log 2>&1 & wait_for_port localhost "$MIC_REALTIME_PORT" mic-realtime # ─── 6. API (serves HTTP + BullMQ workers) ───────────────────── log "Starting MicProtocol API ..." nohup node /app/apps/api/dist/main.js >/var/log/api.log 2>&1 & wait_for_port localhost "$API_PORT" api # ─── 7. Web (Next.js standalone) ─────────────────────────────── log "Starting MicProtocol Web ..." export HOSTNAME=0.0.0.0 PORT=3000 ( cd /app/apps/web && node server.js ) 2>&1 & wait_for_port localhost "$WEB_PORT" web # ─── 8. Admin (Next.js standalone) ───────────────────────────── log "Starting MicProtocol Admin ..." export HOSTNAME=0.0.0.0 PORT=3006 ( cd /app/apps/admin && node server.js ) 2>&1 & wait_for_port localhost "$ADMIN_PORT" admin # ─── 9. Nginx (foreground, entrypoint) ───────────────────────── log "All services up. Starting nginx on port ${APP_PORT} ..." exec nginx -g 'daemon off;'