Self-hosted. Desk-sized.

Your desk.
Your signal.

Give your DeskHub something worth a glance.

Your words, live GitHub or weather values, and control of your DeskHub’s display modes. One web panel, running on your own network.

For an existing DeskHub device · Local / trusted private networks

A little signal, less noise Example
D: / TEXT
Hello on a 32-column, 7-row LED grid
32 × 7 · 224 LEDs19 / 32 columns
GET /displayJSON
{
  "content": "Hello"
}
A static example of the panel’s LED preview. Font and alignment are simulated; your DeskHub controls the final rendering. Longer messages can be inspected with the panel’s scroll controls.
Built for DeskHub ESP32 Node.js + Express backend No frontend build step Apache 2.0

01 / The idea

Configure once.
Change your mind anytime.

Your device keeps the same /display URL. Choose what that URL returns from the panel on your server.

  1. Run the backend

    Start the Node.js service or a Docker container on a machine reachable from your DeskHub’s private network.

  2. Choose your content

    Open the web panel, pick a source and preview your draft. Save when you want it to become the active output.

  3. Let DeskHub fetch it

    Use API mode with JSON key content. Set it in the device’s interface or from our Device tab. Saved content changes are available on the next poll.

Device URL · replace the host with your server’s LAN IPhttp://<server-lan-ip>:3005/display
JSON keycontent
Suggested fetch interval60000 ms

Use a host the device can reach. On the DeskHub, localhost refers to the device itself, not your server.

02 / Choose your signal

Four sources. One small screen.

Choose one content source in Display or rotate your favourites. Settings persist across restarts, and your DeskHub uses its API mode to fetch the result.

Your words

A reminder, a focus cue or a message for your desk. Just type, preview and save. No provider keys needed.

Custom text

Your contributions

Today’s GitHub contribution count, with repository details available as a template variable. Counts follow GitHub’s contribution rules and the UTC day. You write any accompanying text.

GitHub · 5-minute cache

Your local weather

Current temperature for your chosen city, in Celsius or Fahrenheit. Add the city or OpenWeatherMap’s condition description through template variables.

Weather · 15-minute cache

Your focus time

A configurable Pomodoro with work, short and long breaks. Start, pause, resume, skip or reset from the panel; the server keeps time even when the browser is closed.

Pomodoro · No provider key

Try a draft before saving

See your draft on a 32 × 7 grid of 224 LED cells, with bitmap letters, a column count and optional scroll playback. The full ASCII output stays readable below it. Preview never changes saved settings or sends content to the hardware.

Manage keys in your own panel

Add or remove provider keys through the web panel. Keys stay on your server, encrypted at rest with AES-256-GCM and a separate local key.

Small stack, familiar tools

Native HTML, CSS and JavaScript with Node.js and Express. The control panel supports English and Spanish, chosen from your browser’s language preferences.

Your words, live values

Mix your own text with variables such as {{commits}} or {{temperature}}. Insert a variable, preview the message, then save. Output stays ASCII-compatible.

03 / Your workspace

A place for every setting.

Four tabs keep content, provider keys, focus sessions and hardware settings easy to find. Switching tabs preserves your unsaved fields, with visible pending-change indicators.

  1. Display

    Choose text, GitHub, weather or Pomodoro, individually or in rotation. Generate a draft preview and save the source served at /display.

  2. Integrations

    Save, replace or remove GitHub and OpenWeatherMap keys. Provider shortcuts help you get a key; saved keys are never shown again.

  3. Pomodoro

    Set work and break durations, automatic starts and a countdown format. Control the persistent timer and track completed sessions. Choose a 1000 ms device fetch interval yourself to see seconds advance.

  4. Device

    Save the hardware’s local IP, check connectivity and apply one of its four built-in display modes, with the options for that mode.

Your message, with live values.

In Display, choose GitHub or weather and edit Message format. Variable buttons insert at the cursor; preview the result before saving.

A little motivation

GitHub message format
Today I made {{commits}} commits. Keep going!

Available variables: {{commits}}, {{repositories}} and {{username}}.

The default is {{commits}}, producing just 7, for example. Add {{repositories}} for repository names and counts. All wording comes from your template.

A bit of outside context

Weather message format
In {{city}}: {{temperature}} {{unit}} - {{condition}}

Available variables: {{city}}, {{temperature}}, {{unit}} and {{condition}}.

The default is {{temperature}} {{unit}}, producing 23 C, for example. Temperature is rounded, units are C or F, and the optional condition is OpenWeatherMap's description, such as clear sky.

Each source keeps its own format, up to 2048 characters. The backend adds no wording or summaries. Reset format restores the source's default in the draft. An empty format produces empty output. Unknown variables are rejected and final text is normalized to printable ASCII.

Older formats using only {{content}} load as the new defaults. Inside custom formats, this legacy variable inserts only the commit count or rounded temperature; your literal text is preserved.

The LED grid has 32 columns and 7 rows. Its proportional bitmap font matches the official “Hello” example and is clipped to seven rows, which can crop descenders and symbols. Text does not wrap or shrink to fit. Play, pause or scrub the scroll simulation to inspect longer messages. Font, alignment and timing remain a simulation rather than a pixel-for-pixel verified firmware replica; preview controls do not change the device’s scroll setting.

Message format reference

One favourite, or a rotation.

Enable Rotate sources and select at least two sources. The order is Text → GitHub → Weather → Pomodoro, with unchecked sources skipped. Pomodoro keeps counting while another source is displayed.

Every source gets the same duration: the last fetch interval applied from Device, or 60 seconds by default. There are no separate timers to configure. Changes made directly in the hardware’s interface cannot be detected.

The device stays in API mode. Unavailable sources are skipped and reported in the panel; if none can render, the response is an error. Rotation follows the server clock, so extra requests do not advance it. The physical display changes on its next poll.

Source rotation reference

04 / The hardware, too

Four modes.
All in your panel.

Optionally link your DeskHub by its private IPv4 address. The server must be able to reach the device over HTTP on port 80.

GitHub

Set a GitHub username for the firmware’s own GitHub integration.

Runs on the device

API

Set the URL, JSON key and fetch interval. Enable or disable scrolling for long messages.

Use /display for panel content

Work Timer

Choose Pacman or a progress bar. Set work and rest durations from 1–240 minutes, and 1–10 loops.

Focus, rest, repeat

X Followers

Enter an X username without @. The firmware notes that the first fetch may take 15–20 seconds.

Runs on the device
  1. Save the IP

    Open Device, enter the DeskHub’s private IP without http://, then select Save IP. This only saves the address on your server.

  2. Check and configure

    Check connection reads the firmware form to check compatibility. Select a mode and fill in its options. In API mode, Use this panel helps fill the URL, key and interval.

  3. Apply to DeskHub

    Send the complete display configuration. This overwrites the device’s display settings, including retained values for inactive modes. Wi-Fi settings are separate and are never submitted.

Use API for custom content

Text, custom GitHub messages, weather and Pomodoro from this panel all use the hardware’s API mode. Native GitHub and X modes use the firmware’s integrations, independently of the keys saved here.

Remembered settings, clearly labelled

The inspected firmware does not expose saved settings. This form starts with defaults or the last acknowledged submission from this panel, not a live reading. Changing the saved IP resets the remembered configuration.

Connect when you choose

Opening this panel does not contact the device. Checking the connection reads its form; applying sends the settings. Clear the saved IP to unlink. Content changes in Display remain available through normal polling.

An honest result

Acknowledgement means the device accepted the HTTP submission, not that its display or persistence was verified. If the response is lost, the result is unconfirmed and the submission is not retried automatically. Check the hardware before sending again.

Device configuration reference

05 / Make it yours

From repository to desk.

Clone or download the repository, then run these commands from its directory.

Native Node.js

Node 26 LTS · npm 11

Install the locked dependencies and start the server.

npm ci
npm start

Startup loads .env. Keep PORT and LOCALHOST configured; the supplied template uses port 3005.

Node.js instructions

OrbStack

macOS development

With OrbStack installed, build and start the local container.

./run_dev.sh

The helper uses .env-dev, persists data in ./data-dev and waits for a healthy service. Run it again to rebuild after source changes.

OrbStack instructions

Next, open http://localhost:3005/ on the server machine, or use its reachable address from another computer. Choose text to get started immediately, or add provider keys in Integrations. Preview and save in Display, then configure API mode through Device or the hardware’s own interface. Use the server’s LAN address in the device URL, not localhost.

06 / A home on your network

Public project.
Private deployment.

This is the project’s static documentation site. GitHub Pages serves this page; it does not run the Express backend or control your device. The application needs your own running server.

Private-server deployment guide

Trusted networks only

There is no login. Anyone who can reach the server can change content, manage integrations and configure a linked device. Keep it on a trusted LAN or private VPN, without public tunnels or router port forwarding. CSRF protection is not access control.

Keep your data with your server

Back up the entire persistent data directory: source settings in data.json (or your configured filename), device metadata in device.json, and both credentials.json and credentials.key. Both credential files are needed to restore keys; someone who can read both can decrypt them.

Deploy the version you expect

Production Compose uses a published image, which may lag local source changes. Follow the deployment guide to build from source when needed, and check migration notes before replacing an older container.

Small screen. Open source.

Make it part of your setup.