Introducing watchdeck
watchdeck is a Stream Deck-style controller for a homelab. A 10.1” touch display shows a grid of buttons. Pressing one runs a shell command or an HTTP request on the computer the display is paired with.
I built it because I wanted a Stream Deck-style controller that was easy to customize and could hook into both my coding workflows and the homelab. The project page is at /projects/watchdeck/ and the source is in rackrot/watchdeck.
It is early. The app is at version 0.1.0 and the repo has no releases, so you build everything from source.
The two parts
The display is an Elecrow CrowPanel Advanced 10.1” with an ESP32-P4 (1024 x 600, 32 MB PSRAM, 16 MB flash). Its firmware is an ESP-IDF v6.1 project. It draws the grid, which can be up to 8 columns by 5 rows, and sends presses over the network. Wi-Fi goes through an ESP32-C6 on the board, so the network has to be 2.4 GHz.
The desktop app is Tauri 2 with a React and TypeScript front end and a Rust back end. It holds the layout, runs the actions, and lives in the tray. A background daemon serves the display’s control connection over TLS on port 47820. Port 47821 is opened for pairing only while the pairing dialog is open.
Configuring buttons
The layout is a config.toml in ~/.config/dev.rackrot.watchdeck/. This is the example from the README: a button that restarts a container over SSH, and a tile that shows disk usage.
version = 1
[layout]
cols = 5
rows = 3
[[button]]
id = "plex"
pos = [0, 0]
label = "Restart Plex"
bg = "#e65100"
action = { kind = "shell", argv = ["ssh", "nas", "docker", "restart", "plex"], timeout_s = 60 }
[[button]]
id = "disk"
pos = [1, 0]
label = "Disk"
action = { kind = "shell", argv = ["sh", "-c", "df -h / | tail -n 1"] }
tile = { every_s = 30, source = { kind = "shell", argv = ["sh", "-c", "df --output=pcent / | tail -n 1"] } }
Save the file and the change reaches the display in about a second. If the edit is invalid, the app refuses it and the last good config stays live. A tile polls its source every every_s seconds and shows the output on the button. HTTP actions can pull header values from a secrets file, so API tokens do not have to sit in config.toml.
Trying it
You need the display, a USB-C cable, a 5 V 2 A supply, and a computer on the same network. The README walks through five steps: clone and mise install, flash the firmware, give the display Wi-Fi, run the desktop app, and pair. The first steps look like this:
git clone https://github.com/rackrot/watchdeck.git
cd watchdeck
mise install
mise run idf:install
mise run fw:flash -- -p /dev/ttyUSB0
Wi-Fi credentials go in a wifi.txt on a microSD card (SSID on line 1, password on line 2) or get compiled into the firmware. The card stores them as plain text and the flash chip can be read back, so treat both as sensitive.
Pairing uses a 6-digit code. In the app, open Devices and click Pair a device. On the display, tap your computer, then type the code shown on the display into the app. The display then asks whether the app says “Paired” for it. Check that it does and tap Yes; nothing is saved until you do.
Where to look
The README covers setup. The docs have the buttons and actions reference, Wi-Fi setup, the device protocol and the device spec. The code is MIT licensed.