Skip to content

Repository files navigation

TouchRack

A touch-first, multi-node homelab console for physical Linux TTY displays.

It is designed for small directly attached displays — the reference setup is a 7-inch 1024×600 panel running as a 64×18 virtual console — with no X11, Wayland, browser or desktop environment required.

The application is built with Textual and reads the touchscreen directly through Linux evdev.

Current release: v5.0.0. Read-only by design.

TouchRack running on a physical 64x18 Linux TTY touchscreen

What it shows

  • Multiple nodes — monitor the local TouchRack host and remote Linux SBCs from the same touch-first interface, with a selector paged specifically for the 64×18 physical console.
  • Host — CPU, memory, disk and temperature at a glance.
  • Services — curated checks from containers, systemd, CRUX, HTTP or TCP, paged at up to 12 services per screen on the reference 64×18 layout. HTTP or TCP, scoped to the selected node and paged at up to 12 services per screen on the reference layout.
  • Storage — filesystem capacity plus optional SMART disk health and detail.
  • Containers — Podman/Docker summary, resource hotspots and container detail for local or remote nodes.
  • Diagnostics — runtime display, touch, engine, services, config and sleep state, plus the active node, transport, connectivity and container access mode.
  • Resilient remote state — per-node connectivity (UP, DOWN or --), explicit STALE snapshots after transport failures and a shared SSH circuit breaker that suppresses redundant retries while a node is unavailable.
  • Touch wake guard — the first touch wakes a blanked display without activating the control underneath it; automatic collection is paused while the physical display is blanked and resumes with one fresh active-node refresh.

The UI automatically switches to the dedicated compact-touch layout on small terminals such as the reference 64×18 console.

Remote SBCs do not need a TouchRack agent. HOST, STORAGE and CONTAINERS use portable Linux interfaces and fixed read-only commands over the same hardened OpenSSH transport used by node-scoped service providers. Optional narrowly privileged helpers extend this model to SMART data and root-owned containers without granting general sudo, Docker-group membership or arbitrary engine access.

Requirements

TouchRack itself requires:

  • Linux with virtual consoles (/dev/ttyN)
  • Python 3.11+
  • a Unicode-capable Linux console font
  • openvt and setfont
  • setterm for display blank/wake
  • python3-venv (or equivalent Python venv support) for the supplied installer
  • systemd for the supplied boot-time deployment
  • a touchscreen exposed through /dev/input/event* only when touch is enabled

Optional features add their own external tools:

  • smartctl for SMART health in STORAGE
  • Podman or Docker for CONTAINERS
  • systemctl for systemd entries in SERVICES

On Ubuntu 24.04, the reference host dependencies are:

sudo apt install python3 python3-venv kbd util-linux console-setup-linux

Optional monitoring features:

sudo apt install smartmontools
sudo apt install podman        # or install Docker / docker.io

Remote nodes require the system OpenSSH client on the TouchRack host. Remote SMART monitoring additionally uses the narrowly privileged helper documented in docs/REMOTE_SMART.md.

The reference Ubuntu setup uses:

/dev/tty1
/usr/share/consolefonts/Uni3-Terminus32x16.psf.gz
1024×600 → 64×18 cells

Python runtime dependencies (textual, psutil, evdev on Linux and PyYAML) are declared in pyproject.toml and installed into the TouchRack virtual environment.

See docs/DEPENDENCIES.md for the complete dependency matrix, including build/development and optional feature dependencies.

No X11, Wayland or browser is required.

Install

Clone the repository and create a virtual environment:

python3 -m venv .venv
.venv/bin/pip install -e .

Create local configuration from the examples:

cp config.example.yaml config.yaml
cp services.example.yaml services.yaml

For a complete local + remote SBC setup instead:

cp config.multi-node.example.yaml config.yaml
cp services.multi-node.example.yaml services.yaml

For a quick manual TTY test:

sudo systemctl stop getty@tty1.service

sudo openvt -c 1 -f -s -- \
  env LANG=C.UTF-8 LC_ALL=C.UTF-8 PYTHONUTF8=1 TERM=linux \
  "$(pwd)/.venv/bin/homelab-console"

For normal boot-time use, deploy the supplied systemd unit:

sudo ./scripts/install-systemd.sh
sudo systemctl restart homelab-touch-console.service

The installer builds a non-editable, root-owned runtime in /opt/touchrack, keeps runtime configuration in /etc/touchrack, and points systemd at those paths. The source checkout remains a development tree and is never executed directly by the root service.

On the first deployment, an existing local config.yaml and services.yaml are migrated into /etc/touchrack. Later deployments do not overwrite those files. scripts/uninstall-systemd.sh removes the runtime and unit but deliberately preserves /etc/touchrack.

To enable the service at boot as well:

sudo systemctl enable --now homelab-touch-console.service

Check it with:

systemctl status homelab-touch-console.service
journalctl -u homelab-touch-console.service -b

The versioned service intentionally launches through:

openvt -c 1 -f -s -w

This is the startup mode validated on the reference physical console.

Configuration

config.yaml is optional. Lookup order is:

  1. HOMELAB_CONFIG
  2. ./config.yaml
  3. ~/.config/homelab-console/config.yaml
  4. built-in defaults

Example:

display:
  tty: /dev/tty1
  sleep: 1m

refresh:
  seconds: 30

touch:
  enabled: true
  device: auto

services:
  file: services.yaml

Supported sleep values are 1m, 5m, 15m and on.

touch.device: auto discovers the touchscreen automatically; an explicit /dev/input/eventX path may also be used.

Remote nodes

Remote Linux SBCs are configured as nodes with an SSH transport. TouchRack uses a dedicated unprivileged account, a per-node identity, strict pinned host keys, fixed timeouts and the system OpenSSH client:

nodes:
  - id: touchrack-host
    title: TouchRack Host
    transport:
      type: local

  - id: remote-sbc
    title: Remote SBC
    transport:
      type: ssh
      host: 192.168.50.20
      user: touchrack-monitor
      identity_file: /etc/touchrack/ssh/remote_sbc_ed25519
      known_hosts_file: /etc/touchrack/ssh/known_hosts

The node selector is explicitly paginated for touch use on the 64×18 console: three nodes are shown per page with PREV / NEXT controls and a page indicator. Startup opens the page containing the active node rather than assuming the whole fleet can fit on screen.

HOST, STORAGE, CONTAINERS and node-scoped SERVICES reuse the same node runtime and transport. Remote HOST collection is agentless and reads portable Linux interfaces such as /proc, /sys, df and ip. Remote SERVICES supports both systemd and native CRUX /etc/rc.d/<service> status providers, while CONTAINERS prefers Podman and falls back to Docker.

Connectivity is tracked independently per node and shown as UP, DOWN or --. HOST, STORAGE and CONTAINERS retain their last successful snapshot after a transport failure and mark it explicitly as STALE, preserving the original collection time so its age continues to increase. A shared SSH circuit breaker prevents each capability from repeatedly opening its own connection while a node is unavailable and permits a single recovery probe after cooldown.

When TouchRack blanks the physical display, scheduled collection is paused as well: no periodic SSH sessions are opened, missed intervals are not replayed, and waking the console triggers one immediate refresh for the active node before the normal interval resumes.

See docs/REMOTE_NODES.md for the complete secure provisioning procedure, capability matrix, CRUX examples and failure semantics. Root-owned Podman or Docker workloads can be monitored through the opt-in, fixed-command helper described in docs/REMOTE_CONTAINERS.md, without granting Docker-group membership or general container-engine access. The two-node tested example lives in config.multi-node.example.yaml.

Services

SERVICES is a curated dashboard rather than another inventory. Define the things that matter in services.yaml:

services:
  - id: dashboard
    title: Dashboard
    provider: container
    target: dashboard
    metric: memory
    pinned: true
    priority: 10

  - id: web
    title: Web
    provider: http
    target: http://127.0.0.1:8080/health
    expect: 200
    timeout: 2
    pinned: true
    priority: 20

Available providers:

Provider Check
container Podman/Docker state and optional CPU/memory metric
crux CRUX /etc/rc.d/<target> status over the node transport
systemd systemctl is-active
http HTTP status
tcp TCP connection to host:port

Every provider is normalized to the same UI states: OK, IDLE, WARN, ERROR, UNKNOWN.

Provider failures do not bring down the dashboard; they are represented as service state instead.

Architecture

flowchart LR
    SYS["systemd + openvt<br/>Linux TTY"] --> APP["TouchRack<br/>Textual app"]
    CFG["config.yaml<br/>services.yaml"] --> APP

    TOUCH["Touchscreen<br/>evdev"] --> TR["TouchReader"]
    TR --> APP

    APP --> UI["Screens<br/>Host · Services · Storage · Containers · Diagnostics"]

    APP --> NR["NodeRegistry"]
    NR --> RUN["NodeRuntime"]
    RUN --> TRANS["local / SSH<br/>CommandRunner"]
    RUN --> HOST["HostProvider"]
    RUN --> STORE["StorageProvider"]
    RUN --> SMART["DiskHealthProvider"]
    RUN --> CONT["ContainersProvider"]
    RUN --> SM["ServicesManager"]

    HOST --> LINUX["Linux / psutil"]
    STORE --> LINUX
    SMART --> SCT["smartctl (optional)"]
    CONT --> ENGINE["Podman / Docker"]

    SM --> CP["container"]
    SM --> SP["systemd"]
    SM --> CR["CRUX rc.d"]
    SM --> HP["HTTP"]
    SM --> TP["TCP"]

    CP --> ENGINE

    APP --> BLANK["Screen blank / wake guard"]
    BLANK --> VC["setterm<br/>virtual console"]
Loading

The key boundary is simple: screens consume models/state; providers know how to obtain it. UI code does not need to know whether a service came from systemd, a container, HTTP or TCP.

The project is intentionally read-only by default. Monitoring failures should degrade to UNKNOWN/ERROR, not crash the interface.

Project layout

src/homelab_console/  # internal Python package
├── app.py               application coordination and navigation
├── config.py            application configuration
├── models.py            host/container state models
├── providers/           host, storage, SMART and container providers
├── nodes/               node identity, registry, runtime and composition
├── transports/          local/SSH command runners and circuit breaker
├── screens/             Host, Services, Storage and Containers UI
├── services.py          service registry/providers/normalization
├── touch.py             direct evdev touchscreen input
├── screen_blank.py      Linux VC sleep/wake helpers
├── single_instance.py   single-instance lock
├── button_actions.py    pure button-routing classification
├── refresh_policy.py    pure refresh interval policy
├── runtime_diagnostics.py  diagnostics formatting
└── console.tcss         Textual stylesheet

Development

Install development dependencies:

.venv/bin/pip install -e '.[dev]'

Run the suite:

.venv/bin/pytest

The UI smoke tests use Textual's test driver at the physical target size (64×18) so stylesheet/parser regressions are caught before deployment.

CORE-LOGIC-SHA256.txt records the known-good hashes of the sensitive touch, blanking and single-instance modules.

Local files

These are intentionally not versioned:

config.yaml
services.yaml
.venv/

Start from config.example.yaml and services.example.yaml instead.

About

A touch-first, modular homelab console for physical Linux TTY displays.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages