//nbkelley /homelab

MBTA Dashboard - Setup#

What Was Established#

Office transit dashboard deployed on a self-hosted Debian VM (PLT-MBTADisplay, 192.168.168.42). Nginx serves static files from /var/www/MBTADisplay/public and proxies /api/ requests to a Node/Express caching proxy on port 3000. API keys are stored server-side and never exposed to the browser. Process managed via pm2 with a systemd service.

Architecture#

Browser (Anthias/Desktop)
    → Nginx (:80) → / → static files (/var/www/MBTADisplay/public)
                   → /api/ → Node/Express proxy (:3000)
                                → MBTA v3 API
                                → OpenWeatherMap API
                                → RSS feeds
                                → Caches responses

Nginx Configuration#

server {
    listen 80;
    server_name transit.intra.plgt.com 192.168.168.42;

    root /var/www/MBTADisplay/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location /api/ {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Node/Express Proxy#

Setup#

mkdir -p /opt/mbta-proxy
cd /opt/mbta-proxy
npm init -y
npm install express node-fetch

API Key Management#

  • API keys stored in /opt/mbta-proxy/.env
  • Loaded via process.env.MBTA_API_KEY in server.js
  • pm2 started with --env flag to load .env file
  • Critical: API key must survive server.js overwrites from GitHub syncs

pm2 Process Manager#

pm2 start server.js --name mbta-proxy
pm2 save
pm2 startup systemd

systemd Service (/etc/systemd/system/pm2-administrator.service)#

[Unit]
Description=PM2 process manager
After=network.target

[Service]
Type=forking
User=administrator
ExecStart=/usr/local/bin/pm2 resurrect
ExecReload=/usr/local/bin/pm2 reload all
ExecStop=/usr/local/bin/pm2 kill
Restart=on-failure

[Install]
WantedBy=multi-user.target

GitHub Deployment#

Repository#

  • Repo: https://github.com/bich-nguyen/MBTADisplay.git
  • Cloned to /var/www/MBTADisplay
  • Static files in public/ subdirectory
  • Server files in /opt/mbta-proxy/ (separate from web root)

Ownership#

sudo chown -R administrator:administrator /var/www/MBTADisplay

Note: www-data ownership breaks git operations from administrator user.

Deployment Flow#

  1. Develop locally, push to GitHub
  2. SSH into PLT-MBTADisplay
  3. cd /var/www/MBTADisplay && git pull
  4. If server.js changed: sudo cp /var/www/MBTADisplay/src/server.js /opt/mbta-proxy/server.js && pm2 restart mbta-proxy
  5. Verify: curl -s http://localhost:3000/api/data | head -c 200

Troubleshooting#

Common Issues#

403 Forbidden: Check that Nginx root points to public/ subdirectory, not project root.

API data not loading: Verify proxy is running (pm2 status), check Nginx location /api/ block, test with curl localhost:3000/api/data.

CSS/JS changes not reflecting: Clear browser cache, verify git pull updated files, check file ownership.

pm2 service fails after reboot: Run pm2 save after any process changes, verify systemd service enabled.

API key missing after server.js overwrite: Store API key in /opt/mbta-proxy/.env, not in server.js directly. The .env file survives git pulls.

CSS Media Query Configuration#

  • Issue: Initial attempt to target exactly 1024x758 pixels failed due to syntax errors.
  • Problem 1: Used semicolons instead of and operators: @media (width: 1024px; height: 758px;)
  • Problem 2: Placed in print styles section without specifying screen media type.
  • Fix: Corrected to @media screen and (width: 1024px) and (height: 758px) { ... }
  • Best Practice: Use ranges for better browser support:
    @media screen and (min-width: 1020px) and (max-width: 1028px)
                 and (min-height: 754px) and (max-height: 762px) {
        /* Targeted styles */
    }

Homelab Dashboard

Sources#

  • ingested/chats/089-Troubleshooting Proxmox Cloudflare Tunnel 502 Error.md
  • DeepSeek conversation: “Setting up a local web server with proxy” (chat 12)
  • Claude Code conversation: “Setting up nginx with GitHub” (chat 25)
  • Claude Code conversation: “MBTADashboard - Prompt Maker” (chat 24)
  • Active session: MBTA dashboard setup (2026-04-20)