Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SOM logo

SOM

Status Of Me

Terminal-style personal status dashboard. Three subsystems sharing one data contract.

project/
├── frontend/   Vue 3 + Vite. Monospace terminal UI (Rosé Pine Dawn).
├── server/     Node + ws + node:sqlite. Ingests state, fans out over WebSocket.
├── pusher/     Node daemon. Collects local metrics, pushes to server, SNI tray + config UI.
└── sample-state.json   StatePayload example for tests/manual POST (NOT a runtime seed).

Architecture

pusher ──POST /state (30s)──► server ──WS broadcast──► frontend
       ──POST /admin/config─►        ──GET /api/state─►
       ◄──── WS /ws relay ─────►  (visitor message ⇄ owner reply)
  • pusher runs as a local daemon on your workstation. Embedded HTTP config UI at http://localhost:8765, optional DBus SNI tray icon. Collects cpu/gpu/mem/net/disk + screen-time + keyboard heatmaps and pushes a StatePayload every 30s. Also opens a /ws client to the server so visitor messages arrive as desktop notifications (optionally moderated by an LLM); owner replies are sent back over the same socket.
  • server (VPS) accepts POST /state, persists history to SQLite, broadcasts {type:"state",payload} to all WebSocket clients on /ws. Seed via GET /api/state. Also relays ephemeral {type:"message"} / {type:"reply"} envelopes between clients (never persisted).
  • frontend renders the full terminal dashboard: half-block status banner, 7×24 heatmaps, btop-style braille resource sparklines, screen-time bars, custom text area, and a floating pet overlay (shown when reply is enabled) for the visitor message/reply chat. The browser tab title is StatusOf<your_name> (set your_name in the pusher config UI).

Data contract is frozen in server/src/schemas/state-payload-v5.schema.json.

Requirements: Node ≥ 22 (server and pusher use the built-in node:sqlite, which is stable from Node 22). Linux for the pusher (reads /proc, /sys/class/drm, and uses DBus SNI).

Quick run (development)

# 1. server (VPS or local :8787)
cd server && npm install && node src/index.js

# 2. pusher daemon (local :8765 config UI + tray)
cd pusher && npm install && node src/index.js

# 3. frontend
cd frontend && npm install
npm run dev               # dev server with HMR
npm run build             # static build → dist/  (serve with any static host)

Ports and keys:

  • server listens on server/config.jsonport (default 8787). Set api_key (default change-me-server-key — change it). /state and /admin/config are authenticated by this key.
  • pusher config UI is fixed at http://127.0.0.1:8765. Point the pusher at your server in the UI's Server tab (server_url, api_key) or in pusher/server.json.
  • frontend is a static SPA; it talks to the server over GET /api/state + /ws. Configure the server origin in the frontend build/proxy (Vite dev proxies /api and /ws per frontend/vite.config.*).

Deployment

Server (VPS)

  1. Install Node ≥ 22 on the VPS.
  2. Copy the server/ directory to the host (e.g. /opt/statusofme/server).
  3. cd /opt/statusofme/server && npm install --omit=dev.
  4. Edit config.json: set a strong api_key and the port you want to expose (default 8787). SQLite history is written to server/data/state.db (created automatically; ensure the process user can write the data/ directory).
  5. Run behind a TLS reverse proxy (nginx/Caddy). The server speaks plain HTTP + WS; terminate TLS at the proxy and forward /state, /api/, and the /ws upgrade. Example nginx location for the WebSocket:
    location /ws {
      proxy_pass http://127.0.0.1:8787;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
  6. Persist the process with systemd (see below).

Frontend (static)

cd frontend && npm install && npm run build produces dist/. Serve dist/ from any static host (nginx, Caddy, a CDN, or the same reverse proxy as the server). Ensure the host forwards /api and /ws to the server origin, mirroring the Vite proxy above — otherwise the SPA cannot reach GET /api/state or the /ws socket.

Pusher (workstation)

The pusher runs on the machine whose status you are reporting (Linux). It needs no public ports — it only makes outbound POST requests to the server and serves its config UI on 127.0.0.1:8765. Configure server_url + api_key in the Server tab of the config UI, set your your_name in the Pusher tab, then persist it as a user service (below).

Persisting processes with systemd

Server — system service

Create /etc/systemd/system/statusofme-server.service:

[Unit]
Description=StatusOfMe server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
# Use the absolute path to node (run `which node`); a bare "node" may not be on
# systemd's PATH. nvm/fnm users MUST point at the resolved binary.
ExecStart=/usr/bin/node /opt/statusofme/server/src/index.js
WorkingDirectory=/opt/statusofme/server
Restart=on-failure
RestartSec=5
User=statusofme
Group=statusofme
# Hardening (optional): the only writable path the server needs is data/.
ReadWritePaths=/opt/statusofme/server/data

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now statusofme-server
sudo systemctl status statusofme-server
journalctl -u statusofme-server -f      # follow logs

Pusher — user service (required for the tray)

The pusher's tray icon talks to your session's DBus (org.kde.StatusNotifierItem), so it MUST run inside your graphical login session, NOT as a system service. Use a systemd user unit, not a system unit.

Create ~/.config/systemd/user/statusofme-pusher.service:

[Unit]
Description=StatusOfMe pusher daemon
# Wait for the graphical session so the DBus session bus exists for the SNI tray.
After=graphical-session.target
PartOf=graphical-session.target

[Service]
Type=simple
ExecStart=/usr/bin/node %h/statusofme/pusher/src/index.js
WorkingDirectory=%h/statusofme/pusher
Restart=on-failure
RestartSec=5

[Install]
WantedBy=graphical-session.target

Enable and start (note --user, no sudo):

systemctl --user daemon-reload
systemctl --user enable --now statusofme-pusher
systemctl --user status statusofme-pusher
journalctl --user -u statusofme-pusher -f

If the pusher should keep running after you log out (e.g. a headless metrics box), enable lingering for your user: sudo loginctl enable-linger $USER. Without a graphical session the daemon still runs and pushes metrics — only the tray icon is skipped (it logs a warning and continues, since the tray is fully optional).

System tray icon (pusher)

The pusher registers a StatusNotifierItem (SNI) tray icon automatically on startup — there is nothing to register manually. The icon is the StatusOfMe logo (logo.svg, embedded as raster pixmaps in src/logo-pixmap.js), the same artwork used as the page favicon. Left-clicking it opens the config UI (http://localhost:8765); the menu also offers open-setting and quit. Connection status (connected / stale / disconnected) is surfaced in the icon's tooltip, not by changing the icon.

Requirements for the icon to appear:

  1. dbus-next must be installed — it is a normal dependency, so npm install in pusher/ covers it. If it is missing, the daemon logs [tray] warnings and runs without an icon.
  2. A running session DBus bus — present in any normal desktop login. When the pusher runs as a --user systemd service (above), it inherits the session bus automatically.
  3. An SNI host in your panel — i.e. a "Status Notifier" / "AppIndicator" tray. KDE Plasma and most GNOME setups (with the AppIndicator extension) provide one out of the box. On minimal compositors (niri, sway, hyprland) run a standalone SNI host such as waybar (with the tray module) or snixembed; without a host the SNI is published on DBus but nothing draws it.

Verify the item is published even when no panel shows it:

# lists registered StatusNotifierItem services on the session bus
busctl --user list | grep -i StatusNotifier
# or watch the pusher log line on startup:
#   [tray] registered SNI as org.kde.StatusNotifierItem-<pid>-1

Theme: single Rosé Pine Dawn (light). Switch by replacing frontend/src/theme/rose-pine-dawn.css or the data-theme attribute on index.html.

License

Released under the MIT License © 白鞋油 (@professor-lee).

About

Terminal-style personal status dashboard. Three subsystems sharing one data contract.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages