# MoBaz Hugging Face Space — observability edition. # # A single container fans one public port (7860) out to four processes via # nginx, so the Space serves BOTH the live app/API and a Grafana metrics UI: # # / -> 302 -> /grafana/ (Grafana is the landing UI) # /grafana/ -> Grafana (dashboards, reverse-proxied) # /api/* /data/* -> server.py (UNCHANGED paths — the iOS/Android apps # /metrics /status -> server.py depend on these, so no app rebuild) # /app/ (+ rest) -> server.py (the web PWA) # # Grafana lives under /grafana/ (not literally "/") on purpose: Grafana has its # own /api/* endpoints that would collide with MoBaz's /api/*. The root simply # redirects in. Prometheus scrapes server.py's /metrics; Grafana reads Prometheus. # # NOTE: HF Space storage is ephemeral and the Space sleeps when idle, so the # Prometheus TSDB resets on rebuild/restart — dashboards show history since the # last boot, not forever. The built-in /status page is a no-dependency fallback. FROM python:3.11-slim ARG PROM_VERSION=2.54.1 # Pin Grafana so a rebuild can't silently pull a newer release that crash-loops # (an unpinned `grafana` once drifted onto a build whose container never passed # HF's health check). Bump deliberately; supervisord expects the unified # `grafana server` binary at /usr/share/grafana/bin/grafana (Grafana 11+). ARG GF_VERSION=13.0.2 # Base tools + nginx + supervisor, then Grafana (apt repo) and Prometheus (release tarball). RUN apt-get update && apt-get install -y --no-install-recommends \ curl ca-certificates wget gnupg nginx supervisor \ && mkdir -p /etc/apt/keyrings \ && wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor > /etc/apt/keyrings/grafana.gpg \ && echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" \ > /etc/apt/sources.list.d/grafana.list \ && apt-get update && apt-get install -y --no-install-recommends grafana="${GF_VERSION}" \ && wget -q "https://github.com/prometheus/prometheus/releases/download/v${PROM_VERSION}/prometheus-${PROM_VERSION}.linux-amd64.tar.gz" \ && tar xzf prometheus-${PROM_VERSION}.linux-amd64.tar.gz \ && mv prometheus-${PROM_VERSION}.linux-amd64/prometheus /usr/local/bin/ \ && mv prometheus-${PROM_VERSION}.linux-amd64/promtool /usr/local/bin/ \ && rm -rf prometheus-${PROM_VERSION}.linux-amd64* \ && apt-get purge -y wget gnupg && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # App code (server.py, modules, public/, bundled data) — copied by deploy_hf.sh. COPY . . # Observability config. COPY hf_space/nginx.conf /etc/nginx/nginx.conf COPY hf_space/prometheus.yml /etc/prometheus/prometheus.yml COPY hf_space/supervisord.conf /etc/supervisor/conf.d/mobaz.conf COPY hf_space/grafana/provisioning /etc/grafana/provisioning COPY hf_space/grafana/dashboards /etc/grafana/dashboards # Writable runtime dirs (HF storage is ephemeral; everything transient -> /tmp). RUN mkdir -p data /tmp/grafana/logs /tmp/grafana/plugins /tmp/prometheus \ /tmp/nginx /var/log/supervisor \ && chmod -R 777 data /tmp/grafana /tmp/prometheus /tmp/nginx # Grafana served from the /grafana/ sub-path; anonymous Viewer so the public can # see dashboards without logging in. Set GF_SECURITY_ADMIN_PASSWORD as an HF # Space secret to lock the admin account (defaults to a placeholder otherwise). ENV GF_SERVER_ROOT_URL="https://samdnx-mobis-mru.hf.space/grafana/" \ GF_SERVER_SERVE_FROM_SUB_PATH="true" \ GF_PATHS_DATA="/tmp/grafana" \ GF_PATHS_LOGS="/tmp/grafana/logs" \ GF_PATHS_PLUGINS="/tmp/grafana/plugins" \ GF_PATHS_PROVISIONING="/etc/grafana/provisioning" \ GF_AUTH_ANONYMOUS_ENABLED="true" \ GF_AUTH_ANONYMOUS_ORG_ROLE="Viewer" \ GF_USERS_ALLOW_SIGN_UP="false" \ GF_ANALYTICS_REPORTING_ENABLED="false" \ GF_ANALYTICS_CHECK_FOR_UPDATES="false" \ GF_SECURITY_ADMIN_PASSWORD="mobaz-change-me" \ GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="/etc/grafana/dashboards/mobaz.json" \ PORT=7860 EXPOSE 7860 CMD ["supervisord", "-c", "/etc/supervisor/conf.d/mobaz.conf"]