Skip to content

Status TV: Setup from scratch

How to rebuild the Status TV (or set up a second one) on a fresh Raspberry Pi. The content of every file mentioned below is on Status TV Config files. Those are exact copies of what runs on the production Pi.

Faster way: the devops-status-tv repository has an Ansible playbook that does steps 2 and 4–6 for you. Do step 1 (flash the Pi) and step 3 (service account) by hand, point inventory.ini at the new Pi, fill .env from Proton Pass, and run ./deploy.sh --diff. The manual steps below explain what the playbook does.

Tested with Raspberry Pi 4 Model B (4 GB), Raspberry Pi OS (Debian 13 "trixie", 64-bit, desktop), labwc 0.20, Chromium 154, nginx 1.26 and Grafana 12.4.

1. Prepare the Pi

  1. Flash Raspberry Pi OS (64-bit) with desktop using Raspberry Pi Imager. In the Imager settings, set:
    • hostname status-tv
    • user statusadmin and a password (store it in Proton Pass)
    • SSH enabled (password authentication)
    • timezone Europe/Helsinki, keyboard fi
  2. Boot it on the wired network. Make sure the desktop logs in automatically. That's the default on Pi OS; otherwise run sudo raspi-config → System → Boot / Auto login → Desktop Autologin.
  3. Screen blanking must be off. That's the default on current Pi OS. Check with raspi-config nonint get_blanking: 1 means it's disabled. If it isn't, run sudo raspi-config nonint do_blanking 1.

2. Fix the clock (do this first)

The Pi has no battery-backed clock, and public NTP (UDP 123) is blocked on this network, so out of the box the clock is days off. A wrong clock breaks both apt (signature errors like "not live until …") and Grafana: relative times such as now-12h come from the browser's clock, so the TV would show old data.

The internal DNS servers also serve NTP. Create /etc/systemd/timesyncd.conf.d/10-local-ntp.conf (content on the config page), then:

sudo systemctl restart systemd-timesyncd
timedatectl          # → "System clock synchronized: yes"

3. Grafana service account

In Grafana (needs the Admin role):

  1. Administration → Users and access → Service accounts → Add service account
    • Name: indola-status-tv (or similar), Role: Viewer
  2. Add service account token, with expiry Never (or a long expiry and a reminder to renew it). Store the token in Proton Pass right away, because Grafana shows it only once.
  3. If a dashboard is in a folder with restricted permissions, give the service account View permission on that folder.

Test the token:

curl -s -H "Authorization: Bearer <token>" https://dash.aholadigital.com/api/user
# → "login":"sa-1-indola-status-tv" ...

4. nginx token proxy

sudo apt-get update && sudo apt-get install -y nginx
  1. Create /etc/nginx/grafana-token.conf with the token from Proton Pass (template on the config page). It must be readable by root only:
    sudo chown root:root /etc/nginx/grafana-token.conf
    sudo chmod 600 /etc/nginx/grafana-token.conf
    
  2. Create /etc/nginx/sites-available/status-tv (content on the config page), then enable it and disable the default site:
    sudo ln -sf /etc/nginx/sites-available/status-tv /etc/nginx/sites-enabled/status-tv
    sudo rm -f /etc/nginx/sites-enabled/default    # don't expose port 80
    sudo nginx -t && sudo systemctl enable --now nginx && sudo systemctl reload nginx
    curl -s http://127.0.0.1:3000/api/user         # → sa-1-indola-status-tv
    

What the proxy config does:

Directive Why
listen 127.0.0.1:3000 Only the Pi itself can use the token
include grafana-token.conf Adds Authorization: Bearer … to every request
Host / Origin = dash.aholadigital.com Grafana checks Origin on POST requests (queries) and Live websockets
Cookie "" Ignores any browser session cookie, so the token is always used
Upgrade / Connection, proxy_read_timeout 3600s Grafana Live websockets
proxy_ssl_verify on Verifies Grafana's TLS certificate

5. Screens (kanshi)

Create ~/.config/kanshi/config (content on the config page). It has two profiles: single (only HDMI-A-2 connected) and dual (both screens). kanshi picks the matching one automatically at login and when a screen is plugged in or out. On a Pi 4, HDMI-A-1 is the micro-HDMI port next to the USB-C power port.

Check the result with WAYLAND_DISPLAY=wayland-0 wlr-randr.

6. Kiosk

Create these files as user statusadmin (content on the config page):

File Purpose
~/status-tv/kiosk.sh (chmod 755) Runs one Chromium kiosk and restarts it if it exits
~/status-tv/restart.sh (chmod 755) Restarts all kiosks listed in urls.conf
~/status-tv/urls.conf Which URL each screen shows
~/.config/autostart/status-tv-left.desktop Starts the left kiosk at login
~/.config/autostart/status-tv-right.desktop Starts the right kiosk at login
~/.config/labwc/rc.xml Pins each kiosk window to its HDMI output

How the pieces fit together:

  • urls.conf has one line per screen: <kiosk name> <url>. The URLs are kept in a separate file because Grafana URLs contain $__all, which a shell silently replaces with an empty string.
  • kiosk.sh <name> waits until the proxy responds, then runs Chromium in --kiosk mode with its own profile (~/.config/chromium-<name>) and --class=<name>, which becomes the window's Wayland app_id. It loops forever, so Chromium comes back if it crashes.
  • rc.xml has labwc window rules: app_id status-tv-left goes to HDMI-A-1 and status-tv-right to HDMI-A-2. labwc on Pi OS runs with -m, so this file is merged with /etc/xdg/labwc/rc.xml instead of replacing it.

If you create new playlists, put their UIDs in urls.conf. You can list them on the Pi with curl -s http://127.0.0.1:3000/api/playlists.

Load the window rules and start everything without rebooting:

LABWC_PID=$(pgrep -x labwc) WAYLAND_DISPLAY=wayland-0 labwc --reconfigure
~/status-tv/restart.sh

7. Test with a reboot

sudo reboot

About a minute later both screens should show their playlists. Check from your laptop:

ssh statusadmin@10.10.75.9 '
  timedatectl | grep synchronized
  systemctl is-active nginx
  pgrep -fa "kiosk.s[h]"
  WAYLAND_DISPLAY=wayland-0 wlr-randr | grep -E "^\S|current|Position"'
  • A case with a heatsink or fan. With two dashboards the Pi runs at about 70 °C, and it starts slowing itself down at about 80 °C.
  • SSH keys instead of the password: see SSH access with your own key.