#!/usr/bin/env bash # ChatCsv&Pdf-Backend — HF Space entrypoint. # Loads the single `ENV_FILE` secret (verbatim copy of `.env`) into the # environment, then starts uvicorn. Same pattern as the reference # huggingface-service start.sh, simplified for this app. set -euo pipefail log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } # Track pre-existing process env (HF Space Secrets injected by the platform # take precedence over ENV_FILE entries with the same name). declare -A _ENV_FROM_PROCESS for _k in $(compgen -e); do _ENV_FROM_PROCESS["$_k"]=1; done # Parse a multi-line KEY=VALUE document (ENV_FILE secret value) and export # each entry. Handles CRLF, blank lines, '#' comments, 'export ' prefixes, # surrounding quotes, and values containing '=' (split on first '=' only). # Never logs values — names only. load_env_document() { local content="$1" line key val local _ok=0 _skip=0 _fail=0 if [[ "$content" != *$'\n'* && "$content" == *'\n'* ]]; then log "ENV_FILE: no real newlines found — expanding literal \\n sequences" content="${content//\\n/$'\n'}" fi while IFS= read -r line || [ -n "$line" ]; do line="${line%$'\r'}" key="${line#"${line%%[![:space:]]*}"}" [ -n "$key" ] || continue case "$key" in '#'*) continue ;; esac if [[ "$key" == 'export '* ]]; then key="${key#export }" key="${key#"${key%%[![:space:]]*}"}" fi case "$key" in *=*) val="${key#*=}"; key="${key%%=*}" ;; *) log "ENV FAIL: malformed line, no '=' found: ${line:0:40}"; _fail=$((_fail+1)); continue ;; esac key="${key%"${key##*[![:space:]]}"}" if ! [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then log "ENV FAIL: invalid variable name '${key:0:40}'"; _fail=$((_fail+1)); continue fi val="${val#"${val%%[![:space:]]*}"}" val="${val%"${val##*[![:space:]]}"}" case "$val" in \"*\") val="${val#\"}"; val="${val%\"}" ;; \'*\') val="${val#\'}"; val="${val%\'}" ;; esac if [ -n "${_ENV_FROM_PROCESS[$key]:-}" ]; then log "ENV SKIP: $key (already set in process environment — keeping existing value)" _skip=$((_skip+1)) elif export "$key=$val" 2>/dev/null; then log "ENV OK: $key"; _ok=$((_ok+1)) else log "ENV FAIL: $key (could not export)"; _fail=$((_fail+1)) fi done <<< "$content" log "ENV_FILE load summary: $_ok loaded, $_skip skipped, $_fail failed, $((_ok+_skip+_fail)) total" } if [ -n "${ENV_FILE:-}" ]; then log "ENV_FILE secret detected (${#ENV_FILE} chars) — loading embedded environment" load_env_document "$ENV_FILE" else log "ENV_FILE secret not set — falling back to .env files (local dev) / platform env (HF Secrets)" [ -f ".env" ] && log ".env file present — pydantic-settings will load it" || log "no .env file — using process environment" fi _crit_missing="" for _v in APPLICATION_MAIN_BACKEND_AUTH_TOKEN REDIS_URL PINECONE_API_KEYS; do [ -n "${!_v:-}" ] || _crit_missing="$_crit_missing $_v" done if [ -n "$_crit_missing" ]; then log "WARNING: keys not set:$_crit_missing (app still boots; /chat degrades without Pinecone/Redis)" else log "Critical keys: all present" fi unset _crit_missing _v log "=== Starting up ===" log "Python: $(python --version 2>&1)" log "Working dir: $(pwd)" mkdir -p "${STORAGE_DIR:-/app/storage}" /app/storage log "Storage directories: OK" python -c "import fastapi, uvicorn, httpx, pydantic" 2>/dev/null \ && log "Core dependencies: OK" \ || { log "ERROR: missing core dependencies (fastapi/uvicorn/httpx/pydantic)"; exit 1; } HOST="${HOST:-0.0.0.0}" PORT="${PORT:-7860}" log "Self-ping: SELF_PING_ENABLED=${SELF_PING_ENABLED:-true} SELF_PING_URL=${SELF_PING_URL:-'(derived from SPACE_ID)'} interval=${SELF_PING_INTERVAL:-600}s" _cron_base="${FRONTEND_BASE_URL:-'(missing)'}" _cron_secret_state="missing"; [ -n "${CRON_SECRET:-}" ] && _cron_secret_state="set" _cron_v1_state="missing"; [ -n "${EXTERNAL_API_CRON_TOKEN:-}" ] && _cron_v1_state="set" log "Frontend credit crons: CRON_ENABLED=${CRON_ENABLED:-true} CRON_TIMEZONE=${CRON_TIMEZONE:-'(system)'} FRONTEND_BASE_URL=${_cron_base} /api/cron(${_cron_secret_state}) /api/v1/cron(${_cron_v1_state}) daily=00:00" unset _cron_base _cron_secret_state _cron_v1_state log "Starting uvicorn: host=$HOST port=$PORT workers=1 app=app.main:app" exec python -m uvicorn app.main:app --host "$HOST" --port "$PORT" --workers 1