MGNREGA Transparency Dashboard — मनरेगा पारदर्शिता डैशबोर्ड
Live: configure via sslip.io, e.g. https://app.3-109-118-59.sslip.io
Hindi‑first, offline‑ready dashboard for Uttar Pradesh MGNREGA data. Designed for low‑literacy users with voice, big touch targets, and simple comparisons.
- Latest‑month KPIs only (no confusing FY sums on home)
- Detail pages per KPI with: compare (prev month/year, state average, other district), trends, and pie breakdowns
- Instant FY and rolling‑12 tiles (precomputed with fallback calculation)
- Offline mode: Service Worker (Network‑First) + IndexedDB cache of API responses; offline banner
- Resilience: CSV seed + ingest worker with retries/backoff; graceful fallback when API is down
- Security: HTTPS (Caddy), CSP/HSTS/Referrer‑Policy/X‑Frame‑Options; rate limits on /locate and compare
- Observability: Prometheus
/metrics, health/api/healthz&/api/ready - Low‑literacy UX: Hindi by default, guided tour, big cards, simple pies, voice narration
Browser (React PWA)
└─ Caddy (TLS, security headers)
└─ Frontend (Nginx serving SPA)
└─ FastAPI (backend)
├─ Redis (cache)
└─ Postgres (DB)
▲
└─ Ingest Worker (data.gov.in, CSV fallback)
GET /api/districts— districts (UP)GET /api/districts/{code}/summary— latest month summaryGET /api/districts/{code}/current— latest raw monthGET /api/districts/{code}/compare— vs state average (latest month)GET /api/districts/{code}/compare-current?baseline=prev_month|prev_year|state_avgGET /api/districts/{code}/compare-district?other=...— vs other districtGET /api/districts/{code}/rollup?fy=YYYY-YYYY— FY aggregate (precomputed + fallback)GET /api/districts/{code}/rolling?months=12— rolling N months (precomputed + fallback)GET /api/last-updated— freshness + ingest source (api/csv)GET /metrics— Prometheus
- HTTPS via Caddy and free hostname (sslip.io)
- CSP, HSTS, Referrer‑Policy, X‑Frame‑Options, X‑Content‑Type‑Options
- Rate limits on geolocate (/locate) and compare endpoints
- No PII stored; only public program data
Prerequisites: Ubuntu 22.04 VM, Docker + compose plugin, open ports 22/80/443.
- Clone and env
sudo mkdir -p /opt/ovr && sudo chown ubuntu:ubuntu /opt/ovr
cd /opt/ovr
git clone https://github.com/ikrishanaa/Our-Voice-Our-Rights.git .
cat > .env << 'EOF'
DATA_GOV_API_KEY=YOUR_API_KEY
DATA_GOV_RESOURCE_ID=ee03643a-ee4c-48c2-ac30-9f2ff26ab722
# optional
# ALERT_WEBHOOK=
# VITE_PLAUSIBLE_DOMAIN=
EOF- Bind frontend to loopback (Caddy will expose HTTPS)
sed -i 's/"3001:80"/"127.0.0.1:3001:80"/' docker-compose.yml- Build, start, seed
docker compose build --no-cache
docker compose up -d
docker compose run --rm seed- Caddy HTTPS (sslip.io). If Elastic IP is A.B.C.D, hostname is
app.A-B-C-D.sslip.io.
sudo apt -y install debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt -y install caddy
# Replace the hostname below
sudo bash -c "cat > /etc/caddy/Caddyfile" <<'EOF'
app.A-B-C-D.sslip.io {
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://plausible.io; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://app.A-B-C-D.sslip.io http://127.0.0.1:3001 https://plausible.io; frame-ancestors 'self'; form-action 'self'; base-uri 'self';"
Permissions-Policy "geolocation=(self), microphone=()"
}
reverse_proxy 127.0.0.1:3001
}
EOF
sudo systemctl reload caddyOpen https://app.A-B-C-D.sslip.io
Note: CSP is set by Caddy. Keep CSP disabled in frontend/nginx to avoid duplicate policies.
cd /opt/ovr
git pull
# rebuild what changed
docker compose build
docker compose up -dsudo mkdir -p /opt/ovr/backups && sudo chown ubuntu:ubuntu /opt/ovr/backups
cat > /opt/ovr/bin/pg_backup.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F_%H%M)
docker exec -i mgnrega-db pg_dump -U mgnrega_user mgnrega_db | gzip > /opt/ovr/backups/pg_${TS}.sql.gz
find /opt/ovr/backups -type f -name "pg_*.sql.gz" -mtime +14 -delete
EOF
chmod +x /opt/ovr/bin/pg_backup.sh
(crontab -l 2>/dev/null; echo "15 2 * * * /opt/ovr/bin/pg_backup.sh") | crontab -Backend
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000Frontend
cd frontend
npm install
npm run devRequired
DATA_GOV_API_KEY— your data.gov.in API keyDATA_GOV_RESOURCE_ID—ee03643a-ee4c-48c2-ac30-9f2ff26ab722
Optional
ALERT_WEBHOOK— POSTs a JSON alert if ingest failsVITE_PLAUSIBLE_DOMAIN— enable privacy‑safe analytics
- Health:
/api/healthzand/api/ready - Metrics:
/metrics(Prometheus) - Logs:
docker compose logs -f backend(or any service) - Seed:
docker compose run --rm seed
- Districts dropdown empty in Incognito: ensure DB is seeded and CSP allows same‑origin
connect-src; prefer CSP only in Caddy. - Rolling shows 0: backend now falls back to raw aggregation; rebuild backend and refresh.
- HTTPS fails: ports 80/443 allowed in security group + UFW;
sudo systemctl status caddy.
Production Deployment guide on VM - EC2
This guide deploys the app to a self‑managed VM (AWS EC2 shown, works similarly for other VPS). Target stack:
- Ubuntu 22.04 LTS
- Docker + compose plugin
- Caddy as TLS reverse proxy (free hostname via sslip.io)
- Postgres + Redis in Docker
- Open inbound: 22, 80, 443 in cloud security group and UFW
- Elastic/Public IP
- Your data.gov.in credentials (API key, resource id)
- EC2 → Launch instance
- AMI: Ubuntu 22.04 LTS (x86)
- Instance type: t3.small or t3.medium
- Key pair: create/download .pem
- Security group: allow 22/80/443
- Storage: 60–80GB
- Allocate Elastic IP → associate with instance
chmod 600 ~/Downloads/ovr-prod.pem
ssh -i ~/Downloads/ovr-prod.pem ubuntu@ELASTIC_IP
# Updates
sudo apt update && sudo apt -y upgrade
# Optional swap (2G) on small instances
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile \
&& echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab && sudo swapon /swapfile
# UFW
sudo apt -y install ufw
sudo ufw allow OpenSSH && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
sudo ufw --force enablesudo apt -y install ca-certificates curl gnupg lsb-release
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release; echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git
sudo usermod -aG docker $USER
newgrp dockersudo mkdir -p /opt/ovr && sudo chown ubuntu:ubuntu /opt/ovr
cd /opt/ovr
git clone https://github.com/ikrishanaa/Our-Voice-Our-Rights.git .
# Minimal env (two required)
cat > .env << 'EOF'
DATA_GOV_API_KEY=YOUR_API_KEY
DATA_GOV_RESOURCE_ID=ee03643a-ee4c-48c2-ac30-9f2ff26ab722
# optional
# ALERT_WEBHOOK=
# VITE_PLAUSIBLE_DOMAIN=
EOFsed -i 's/"3001:80"/"127.0.0.1:3001:80"/' docker-compose.ymldocker compose build --no-cache
docker compose up -d
# one-time seed
docker compose run --rm seedCheck:
docker compose ps
curl -s http://127.0.0.1:3001/api/healthz || trueFree hostname pattern: app.A-B-C-D.sslip.io for Elastic IP A.B.C.D.
Install Caddy:
sudo apt -y install debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt -y install caddyWrite Caddyfile (replace hostname):
sudo bash -c "cat > /etc/caddy/Caddyfile" <<'EOF'
app.A-B-C-D.sslip.io {
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://plausible.io; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://app.A-B-C-D.sslip.io http://127.0.0.1:3001 https://plausible.io; frame-ancestors 'self'; form-action 'self'; base-uri 'self';"
Permissions-Policy "geolocation=(self), microphone=()"
}
reverse_proxy 127.0.0.1:3001
}
EOF
sudo systemctl reload caddyVerify:
curl -I https://app.A-B-C-D.sslip.io- Update:
cd /opt/ovr
git pull
docker compose build
docker compose up -d- Logs:
docker compose logs -f backend- Health:
curl -s https://app.A-B-C-D.sslip.io/api/ready- Metrics: https://app.A-B-C-D.sslip.io/metrics
sudo mkdir -p /opt/ovr/backups && sudo chown ubuntu:ubuntu /opt/ovr/backups
cat > /opt/ovr/bin/pg_backup.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F_%H%M)
docker exec -i mgnrega-db pg_dump -U mgnrega_user mgnrega_db | gzip > /opt/ovr/backups/pg_${TS}.sql.gz
find /opt/ovr/backups -type f -name "pg_*.sql.gz" -mtime +14 -delete
EOF
chmod +x /opt/ovr/bin/pg_backup.sh
(crontab -l 2>/dev/null; echo "15 2 * * * /opt/ovr/bin/pg_backup.sh") | crontab -- District list empty in Incognito
- Ensure DB seeded (
docker compose run --rm seed) - Avoid duplicate CSP policies; keep CSP only in Caddy; rebuild frontend if you removed CSP from nginx
- Ensure DB seeded (
- Rolling shows 0
- Rebuild backend with latest fallback (
docker compose build backend && docker compose up -d backend)
- Rebuild backend with latest fallback (
- TLS fails
- Security group + UFW allow 80/443;
sudo systemctl status caddy
- Security group + UFW allow 80/443;
- Change DB credentials, rotate regularly
- Move backups to S3 or external storage
- Add fail2ban and SSH key‑only login
- Add alerts via
ALERT_WEBHOOKfor ingest failures - Enable Prometheus scraping of
/metrics
Public‑good project to make MGNREGA data accessible for citizens. Hindi‑first design, UP scope in Phase 1.