<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Dashboard on homelab</title>
    <link>https://homelab.nbkelley.com/tags/dashboard/</link>
    <description>Recent content in Dashboard on homelab</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Fri, 01 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://homelab.nbkelley.com/tags/dashboard/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Homelab Dashboard</title>
      <link>https://homelab.nbkelley.com/docs/services/homelab-dashboard/</link>
      <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>
      <guid>https://homelab.nbkelley.com/docs/services/homelab-dashboard/</guid>
      <description>&lt;h1 id=&#34;homelab-dashboard&#34;&gt;Homelab Dashboard&lt;a class=&#34;anchor&#34; href=&#34;#homelab-dashboard&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;what-was-established&#34;&gt;What Was Established&lt;a class=&#34;anchor&#34; href=&#34;#what-was-established&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;A Node.js + Express dashboard is deployed at &lt;code&gt;https://status.nbkelley.com&lt;/code&gt;, serving homelab monitoring data. All API calls are server-side — no internal IPs, credentials, or raw API responses reach the browser.&lt;/p&gt;&#xA;&lt;h2 id=&#34;deployment&#34;&gt;Deployment&lt;a class=&#34;anchor&#34; href=&#34;#deployment&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Detail&lt;/th&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Public URL&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://status.nbkelley.com&#34;&gt;https://status.nbkelley.com&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Host&lt;/td&gt;&#xA;          &lt;td&gt;proxy VM (192.168.1.222)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Port&lt;/td&gt;&#xA;          &lt;td&gt;3002&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Runtime&lt;/td&gt;&#xA;          &lt;td&gt;Node.js + Express, Docker container&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;compose.yaml location&lt;/td&gt;&#xA;          &lt;td&gt;/home/iluvatar/compose.yaml on proxy VM&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;App directory&lt;/td&gt;&#xA;          &lt;td&gt;/opt/homelab-dashboard/ on proxy VM&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Routing&lt;/td&gt;&#xA;          &lt;td&gt;Cloudflare Tunnel → 127.0.0.1:3002&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;file-structure&#34;&gt;File Structure&lt;a class=&#34;anchor&#34; href=&#34;#file-structure&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/opt/homelab-dashboard/&#xA;  server.js         ← Express app, all API logic&#xA;  package.json&#xA;  package-lock.json&#xA;  dockerfile&#xA;  start.sh&#xA;  node_modules/&#xA;  public/&#xA;    index.html&#xA;    styles.css&#xA;    app.js          ← frontend render engine&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;composeyaml-entry&#34;&gt;compose.yaml Entry&lt;a class=&#34;anchor&#34; href=&#34;#composeyaml-entry&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;homelab-dashboard&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;build&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/opt/homelab-dashboard&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;container_name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;homelab-dashboard&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;restart&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;unless-stopped&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;network_mode&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;host&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;environment&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ae81ff&#34;&gt;PORT=3002&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;network_mode: host&lt;/code&gt; means the app binds directly to host port 3002. The &lt;code&gt;ports:&lt;/code&gt; mapping is ignored when using host networking.&lt;/p&gt;</description>
    </item>
    <item>
      <title>MBTA Dashboard - Frontend Implementation</title>
      <link>https://homelab.nbkelley.com/docs/services/mbta-dashboard-frontend/</link>
      <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>
      <guid>https://homelab.nbkelley.com/docs/services/mbta-dashboard-frontend/</guid>
      <description>&lt;h1 id=&#34;mbta-dashboard---frontend-implementation&#34;&gt;MBTA Dashboard - Frontend Implementation&lt;a class=&#34;anchor&#34; href=&#34;#mbta-dashboard---frontend-implementation&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;what-was-established&#34;&gt;What Was Established&lt;a class=&#34;anchor&#34; href=&#34;#what-was-established&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;A portrait-mode (1080x1920) transit dashboard displaying real-time MBTA departures, commuter rail, weather, news, and a static transit map. Hosted on PLT-MBTADisplay (&lt;code&gt;192.168.168.42&lt;/code&gt;), served via Nginx with a Node/Express proxy that handles all external API calls server-side. Target display is a 1080p vertical screen driven by Anthias on a Raspberry Pi 3B+.&lt;/p&gt;&#xA;&lt;h2 id=&#34;design-evolution&#34;&gt;Design Evolution&lt;a class=&#34;anchor&#34; href=&#34;#design-evolution&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;The dashboard went through several major design iterations:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Initial&lt;/strong&gt;: Line-based cards with MBTA colors, horizontal layout for 1080p TV&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Minimalist redesign&lt;/strong&gt;: White background, grey dividers, MBTA colors only on small abbreviated line pills (RL, OL, BL, GL-B/C/D/E), smaller station-only headers with walk time&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Station-based cards&lt;/strong&gt;: Cards grouped by station (South Station, State Street, Park Street) rather than by line, with all routes listed per station&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Final portrait layout&lt;/strong&gt;: 2-column CSS Grid, flex column containers, flat time-sorted departure list with line pills, full-width static map image at bottom&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;layout-1080x1920-portrait&#34;&gt;Layout (1080x1920 Portrait)&lt;a class=&#34;anchor&#34; href=&#34;#layout-1080x1920-portrait&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;2-column, 3-row card grid above a full-width static map image&lt;/li&gt;&#xA;&lt;li&gt;Cards flex with content height (no preset card height)&lt;/li&gt;&#xA;&lt;li&gt;CSS Grid with &lt;code&gt;grid-template-columns: 1fr 1fr&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;key-features&#34;&gt;Key Features&lt;a class=&#34;anchor&#34; href=&#34;#key-features&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;css-tickermarquee&#34;&gt;CSS Ticker/Marquee&lt;a class=&#34;anchor&#34; href=&#34;#css-tickermarquee&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Header text that overflows its container scrolls seamlessly in a ticker&lt;/li&gt;&#xA;&lt;li&gt;Uses CSS animation with &lt;code&gt;transform: translateX()&lt;/code&gt; — no JavaScript&lt;/li&gt;&#xA;&lt;li&gt;Animation speed set slow for readability&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;station-based-grouping&#34;&gt;Station-Based Grouping&lt;a class=&#34;anchor&#34; href=&#34;#station-based-grouping&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cards grouped by physical station: South Station, State Street, Park Street&lt;/li&gt;&#xA;&lt;li&gt;Each station card shows all lines serving that station&lt;/li&gt;&#xA;&lt;li&gt;Departures sorted by time, each with abbreviated line pill (RL, OL, BL, GL-B, etc.)&lt;/li&gt;&#xA;&lt;li&gt;Filtered to show departures within 10 minutes walking distance&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;server-side-proxy-architecture&#34;&gt;Server-Side Proxy Architecture&lt;a class=&#34;anchor&#34; href=&#34;#server-side-proxy-architecture&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;All API calls proxied through Node/Express on &lt;code&gt;localhost:3000&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Client fetches single &lt;code&gt;/api/data&lt;/code&gt; endpoint — no direct browser-to-MBTA calls&lt;/li&gt;&#xA;&lt;li&gt;API keys stored in environment variables, never exposed to browser&lt;/li&gt;&#xA;&lt;li&gt;Server-side caching with TTL to reduce upstream API calls&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;news-ticker&#34;&gt;News Ticker&lt;a class=&#34;anchor&#34; href=&#34;#news-ticker&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Pulls from multiple RSS feeds (MassLive Boston, State House News)&lt;/li&gt;&#xA;&lt;li&gt;Displays first 3 items from each feed as &lt;code&gt;&amp;lt;title&amp;gt; - &amp;lt;description&amp;gt;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Modular RSS feed configuration for easy addition of new sources&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;static-map&#34;&gt;Static Map&lt;a class=&#34;anchor&#34; href=&#34;#static-map&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Originally Leaflet.js with OpenStreetMap and hardcoded markers&lt;/li&gt;&#xA;&lt;li&gt;Replaced with static image for performance on Raspberry Pi&lt;/li&gt;&#xA;&lt;li&gt;Generated via Snazzy Maps + PowerPoint, served as local &lt;code&gt;webp&lt;/code&gt; file&lt;/li&gt;&#xA;&lt;li&gt;Full-width, placed below the card grid&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;performance--optimization&#34;&gt;Performance &amp;amp; Optimization&lt;a class=&#34;anchor&#34; href=&#34;#performance--optimization&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;memory-constraints-raspberry-pi-3b&#34;&gt;Memory Constraints (Raspberry Pi 3B+)&lt;a class=&#34;anchor&#34; href=&#34;#memory-constraints-raspberry-pi-3b&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Total system memory: 788MB&lt;/li&gt;&#xA;&lt;li&gt;Dockerized Anthias viewer consumes significant resources&lt;/li&gt;&#xA;&lt;li&gt;Strategies employed:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Server-side proxy&lt;/strong&gt;: Eliminates duplicate API calls from browser&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lazy loading&lt;/strong&gt;: Defer non-critical rendering&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;DOM optimization&lt;/strong&gt;: Remove unused elements, minimize reflows&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Image optimization&lt;/strong&gt;: Convert to WebP, use static image over dynamic map&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Cache management&lt;/strong&gt;: Server-side cache with periodic clearing&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Swap avoidance&lt;/strong&gt;: Swap degrades SD card — keep memory footprint below physical RAM&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;font-standardization&#34;&gt;Font Standardization&lt;a class=&#34;anchor&#34; href=&#34;#font-standardization&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Default to Arial for cross-platform consistency&lt;/li&gt;&#xA;&lt;li&gt;Helvetica Neue as preferred fallback&lt;/li&gt;&#xA;&lt;li&gt;Qt WebEngine (Anthias) renders fonts differently than desktop Chrome&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;current-configuration&#34;&gt;Current Configuration&lt;a class=&#34;anchor&#34; href=&#34;#current-configuration&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt;: PLT-MBTADisplay (&lt;code&gt;192.168.168.42&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Web root&lt;/strong&gt;: &lt;code&gt;/var/www/MBTADisplay/public&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Proxy&lt;/strong&gt;: &lt;code&gt;/opt/mbta-proxy/server.js&lt;/code&gt; (Node/Express, port 3000)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Process manager&lt;/strong&gt;: pm2 with systemd service (&lt;code&gt;pm2-administrator&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Deployment&lt;/strong&gt;: Git push → GitHub → manual pull on server&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Access URLs&lt;/strong&gt;: &lt;code&gt;http://transit.intra.plgt.com&lt;/code&gt; (internal), &lt;code&gt;https://mbtadash.nbkelley.com&lt;/code&gt; (Cloudflare)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;historical-notes&#34;&gt;Historical Notes&lt;a class=&#34;anchor&#34; href=&#34;#historical-notes&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Conversation dates: 2026-03-12 to 2026-04-12.&lt;/li&gt;&#xA;&lt;li&gt;Collaborative workflow: Claude AI (creative/prompt engineering) → Claude Code (implementation).&lt;/li&gt;&#xA;&lt;li&gt;Blue Bikes section was added, then fully removed when GBFS feed deprecated.&lt;/li&gt;&#xA;&lt;li&gt;Ferry panel configuration evolved significantly — seasonal routes removed seasonally.&lt;/li&gt;&#xA;&lt;li&gt;Several Claude Code UI hallucinations corrected by user during Bambu Studio discussion.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;related-pages&#34;&gt;Related Pages&lt;a class=&#34;anchor&#34; href=&#34;#related-pages&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://homelab.nbkelley.com/docs/services/mbta-dashboard-setup/&#34;&gt;MBTA Dashboard - Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://homelab.nbkelley.com/docs/services/mbta-kiosk-mode/&#34;&gt;MBTA Dashboard - Kiosk Mode&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://homelab.nbkelley.com/docs/services/bluebikes-api/&#34;&gt;BlueBikes API Feeds Guide&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;a class=&#34;anchor&#34; href=&#34;#sources&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;ingested/chats/159-Create MBTA Train Dashboard with API.md&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Claude Code conversation: &amp;ldquo;MBTADashboard - Prompt Maker&amp;rdquo; (chat 24)&lt;/li&gt;&#xA;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>MBTA Dashboard - Kiosk Mode</title>
      <link>https://homelab.nbkelley.com/docs/services/mbta-kiosk-mode/</link>
      <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>
      <guid>https://homelab.nbkelley.com/docs/services/mbta-kiosk-mode/</guid>
      <description>&lt;h1 id=&#34;mbta-dashboard---kiosk-mode&#34;&gt;MBTA Dashboard - Kiosk Mode&lt;a class=&#34;anchor&#34; href=&#34;#mbta-dashboard---kiosk-mode&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;what-was-established&#34;&gt;What Was Established&lt;a class=&#34;anchor&#34; href=&#34;#what-was-established&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;The MBTA dashboard runs as a 24/7 kiosk display on a Raspberry Pi 3B+ using Anthias (formerly Screenly OSE) in Docker. The display is portrait 1080x1920. The Pi has severe memory constraints (788MB total) requiring aggressive optimization.&lt;/p&gt;&#xA;&lt;h2 id=&#34;anthias-deployment&#34;&gt;Anthias Deployment&lt;a class=&#34;anchor&#34; href=&#34;#anthias-deployment&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;hardware&#34;&gt;Hardware&lt;a class=&#34;anchor&#34; href=&#34;#hardware&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Device&lt;/strong&gt;: Raspberry Pi 3B+ (788MB RAM)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Display&lt;/strong&gt;: 1080x1920 portrait screen&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Software&lt;/strong&gt;: Anthias (Docker-based digital signage)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;display-configuration&#34;&gt;Display Configuration&lt;a class=&#34;anchor&#34; href=&#34;#display-configuration&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Dashboard page set as primary asset&lt;/li&gt;&#xA;&lt;li&gt;Splash page appears for 1 minute every 11 hours 59 minutes as a refresh cycle&lt;/li&gt;&#xA;&lt;li&gt;Page refreshes via cron job to prevent memory leaks&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;cron-refresh&#34;&gt;Cron Refresh&lt;a class=&#34;anchor&#34; href=&#34;#cron-refresh&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Refresh the kiosk page periodically to clear memory&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; */6 * * * docker restart screenly-anthias-viewer-1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;qt-webengine-quirks&#34;&gt;Qt WebEngine Quirks&lt;a class=&#34;anchor&#34; href=&#34;#qt-webengine-quirks&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Anthias uses Qt WebEngine for rendering, which differs from desktop Chrome:&lt;/p&gt;</description>
    </item>
    <item>
      <title>MBTA Dashboard - Setup</title>
      <link>https://homelab.nbkelley.com/docs/services/mbta-dashboard-setup/</link>
      <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>
      <guid>https://homelab.nbkelley.com/docs/services/mbta-dashboard-setup/</guid>
      <description>&lt;h1 id=&#34;mbta-dashboard---setup&#34;&gt;MBTA Dashboard - Setup&lt;a class=&#34;anchor&#34; href=&#34;#mbta-dashboard---setup&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;what-was-established&#34;&gt;What Was Established&lt;a class=&#34;anchor&#34; href=&#34;#what-was-established&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Office transit dashboard deployed on a self-hosted Debian VM (&lt;code&gt;PLT-MBTADisplay&lt;/code&gt;, &lt;code&gt;192.168.168.42&lt;/code&gt;). Nginx serves static files from &lt;code&gt;/var/www/MBTADisplay/public&lt;/code&gt; and proxies &lt;code&gt;/api/&lt;/code&gt; 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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;architecture&#34;&gt;Architecture&lt;a class=&#34;anchor&#34; href=&#34;#architecture&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Browser (Anthias/Desktop)&#xA;    → Nginx (:80) → / → static files (/var/www/MBTADisplay/public)&#xA;                   → /api/ → Node/Express proxy (:3000)&#xA;                                → MBTA v3 API&#xA;                                → OpenWeatherMap API&#xA;                                → RSS feeds&#xA;                                → Caches responses&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;nginx-configuration&#34;&gt;Nginx Configuration&lt;a class=&#34;anchor&#34; href=&#34;#nginx-configuration&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;server {&#xA;    listen 80;&#xA;    server_name transit.intra.plgt.com 192.168.168.42;&#xA;&#xA;    root /var/www/MBTADisplay/public;&#xA;    index index.html;&#xA;&#xA;    location / {&#xA;        try_files $uri $uri/ =404;&#xA;    }&#xA;&#xA;    location /api/ {&#xA;        proxy_pass http://localhost:3000;&#xA;        proxy_http_version 1.1;&#xA;        proxy_set_header Host $host;&#xA;        proxy_set_header X-Real-IP $remote_addr;&#xA;    }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;nodeexpress-proxy&#34;&gt;Node/Express Proxy&lt;a class=&#34;anchor&#34; href=&#34;#nodeexpress-proxy&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;setup&#34;&gt;Setup&lt;a class=&#34;anchor&#34; href=&#34;#setup&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p /opt/mbta-proxy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd /opt/mbta-proxy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm init -y&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm install express node-fetch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;api-key-management&#34;&gt;API Key Management&lt;a class=&#34;anchor&#34; href=&#34;#api-key-management&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;API keys stored in &lt;code&gt;/opt/mbta-proxy/.env&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Loaded via &lt;code&gt;process.env.MBTA_API_KEY&lt;/code&gt; in server.js&lt;/li&gt;&#xA;&lt;li&gt;pm2 started with &lt;code&gt;--env&lt;/code&gt; flag to load .env file&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Critical&lt;/strong&gt;: API key must survive server.js overwrites from GitHub syncs&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;pm2-process-manager&#34;&gt;pm2 Process Manager&lt;a class=&#34;anchor&#34; href=&#34;#pm2-process-manager&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pm2 start server.js --name mbta-proxy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pm2 save&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pm2 startup systemd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;systemd-service-etcsystemdsystempm2-administratorservice&#34;&gt;systemd Service (&lt;code&gt;/etc/systemd/system/pm2-administrator.service&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#systemd-service-etcsystemdsystempm2-administratorservice&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[Unit]&#xA;Description=PM2 process manager&#xA;After=network.target&#xA;&#xA;[Service]&#xA;Type=forking&#xA;User=administrator&#xA;ExecStart=/usr/local/bin/pm2 resurrect&#xA;ExecReload=/usr/local/bin/pm2 reload all&#xA;ExecStop=/usr/local/bin/pm2 kill&#xA;Restart=on-failure&#xA;&#xA;[Install]&#xA;WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;github-deployment&#34;&gt;GitHub Deployment&lt;a class=&#34;anchor&#34; href=&#34;#github-deployment&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;repository&#34;&gt;Repository&lt;a class=&#34;anchor&#34; href=&#34;#repository&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Repo: &lt;code&gt;https://github.com/bich-nguyen/MBTADisplay.git&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Cloned to &lt;code&gt;/var/www/MBTADisplay&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Static files in &lt;code&gt;public/&lt;/code&gt; subdirectory&lt;/li&gt;&#xA;&lt;li&gt;Server files in &lt;code&gt;/opt/mbta-proxy/&lt;/code&gt; (separate from web root)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;ownership&#34;&gt;Ownership&lt;a class=&#34;anchor&#34; href=&#34;#ownership&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chown -R administrator:administrator /var/www/MBTADisplay&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: &lt;code&gt;www-data&lt;/code&gt; ownership breaks git operations from administrator user.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
