Files
LEDMatrix/web_interface
ChuckandClaude Opus 5 f475038895 fix(cache): web UI can read what the display service caches again (#593)
* fix(cache): web UI can read what the display service caches again

ledmatrix-web.service carried CacheDirectory=ledmatrix. With User= set to
the installing user, systemd re-owns /var/cache/ledmatrix and everything
in it to that user and its primary group whenever the directory's owner
differs -- for a directory root created, on the first start. That erased
the root:ledmatrix setgid layout the installers set up, so every file the
display service (root) wrote afterwards was root:root 0660 and unreadable
by the web interface:

  WARNING - Permission denied loading cache for display_current_state ...

Since #547 install_service.sh renders the web unit from the template, so
every fresh install hit this. Measured on one rig: 392 unreadable files,
and the web UI's display status, on-demand state and plugin health empty.

Existing installs only receive `git pull`, never a reinstalled unit, so
the fix for them is in the code the root display service runs:

- DiskCache.set gives each file the directory's group (when the directory
  is group-writable) and 0660 on the open descriptor before the rename,
  independent of setgid. This also closes a window where a fresh file was
  visible as mkstemp's 0600.
- DiskCache.share_existing_files repairs files an older version left
  behind, once per process from the cleanup thread. It works through
  O_NOFOLLOW descriptors and skips hard links and other users' files: the
  directory is writable by the web user, and root must not be steered
  into changing a file outside it.

For new installs, the web unit drops CacheDirectory=/CacheDirectoryMode=,
and install_web_service.sh stops replacing an existing directory's
ledmatrix group with the user's group.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(web): on-demand and current-display status read the display's latest state

Found testing the cache-permission fix on a rig: once the web interface
could read display_on_demand_state at all, /display/on-demand/status kept
answering "active" for over 100 seconds while the file on disk said
"idle". Both status routes read the display service's keys through the
web process's memory tier, which serves the first copy it read for the
full max_age (120s). Read them with memory_ttl=0, as every other
cross-process reader (plugin health/metrics, the on-demand mailbox)
already does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(install): re-group the cache dir whenever the web user is outside its group

install_web_service.sh replaced an existing cache directory's group only
when it was root's. A directory in any other group the web user is not a
member of -- root:ledmatrix, for a user who is not in ledmatrix -- was left
alone, and every file root wrote there stayed unreadable to the web
interface. Replace the group whenever the installing user is not in it.

A directory whose group the user is already in (ledmatrix, or the user's
own group where CacheDirectory= left it) is still left as it is: re-grouping
a working directory strands the files already in it on the old group.

When the group does change and root-owned JSON files carrying the old group
are present, try-restart ledmatrix.service so DiskCache.share_existing_files
re-groups them through its symlink- and hard-link-safe path, rather than a
recursive chgrp.

Verified under WSL's systemd for seven directory states (user group,
ledmatrix member, ledmatrix non-member with and without root files,
root:root, missing, unnamed gid); the previous version left the non-member
case unchanged.

Addresses CodeRabbit review on #593.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:38:39 -04:00
..

LED Matrix Web Interface V3

Modern, production web interface for controlling the LED Matrix display.

Overview

This directory contains the active V3 web interface with the following features:

  • Real-time display preview via Server-Sent Events (SSE)
  • Plugin management and configuration
  • System monitoring and logs
  • Modern, responsive UI
  • RESTful API

Directory Structure

web_interface/
├── app.py                    # Main Flask application
├── start.py                  # Startup script
├── run.sh                    # Shell runner script
├── requirements.txt          # Python dependencies
├── blueprints/               # Flask blueprints
│   ├── api_v3.py            # API endpoints
│   └── pages_v3.py          # Page routes
├── templates/                # HTML templates
│   └── v3/
│       ├── base.html
│       ├── index.html
│       └── partials/
└── static/                   # CSS/JS assets
    └── v3/
        ├── app.css
        ├── app.js
        ├── manifest.json     # PWA manifest
        ├── plugins_manager.js
        ├── icons/            # PWA / touch icons
        ├── js/               # Alpine, htmx, app shell, widgets, utils
        └── vendor/           # codemirror, fontawesome

Running the Web Interface

Standalone (Development)

From the project root:

python3 web_interface/start.py

Or using the shell script:

./web_interface/run.sh

As a Service (Production)

The web interface can run as a systemd service that starts automatically based on the web_display_autostart configuration setting:

sudo systemctl start ledmatrix-web
sudo systemctl enable ledmatrix-web  # Start on boot

Accessing the Interface

Once running, access the web interface at:

Configuration

The web interface reads configuration from:

  • config/config.json - Main configuration
  • config/config_secrets.json - API keys and secrets

API Documentation

The V3 API is mounted at /api/v3/ (app.py:144). For the complete list and request/response formats, see docs/REST_API_REFERENCE.md. Quick reference for the most common endpoints:

Configuration

  • GET /api/v3/config/main - Get main configuration
  • POST /api/v3/config/main - Save main configuration
  • GET /api/v3/config/secrets - Get secrets configuration
  • POST /api/v3/config/raw/main - Save raw main config (Config Editor)
  • POST /api/v3/config/raw/secrets - Save raw secrets

Display & System Control

  • GET /api/v3/system/status - System status
  • POST /api/v3/system/action - Control display (action body: start_display, stop_display, restart_display_service, restart_web_service, git_pull, reboot_system, shutdown_system, enable_autostart, disable_autostart)
  • GET /api/v3/display/current - Current display frame
  • GET /api/v3/display/on-demand/status - On-demand status
  • POST /api/v3/display/on-demand/start - Trigger on-demand display
  • POST /api/v3/display/on-demand/stop - Clear on-demand

Plugins

  • GET /api/v3/plugins/installed - List installed plugins
  • GET /api/v3/plugins/config?plugin_id=<id> - Get plugin config
  • POST /api/v3/plugins/config - Update plugin configuration
  • GET /api/v3/plugins/schema?plugin_id=<id> - Get plugin schema
  • POST /api/v3/plugins/toggle - Enable/disable plugin
  • POST /api/v3/plugins/install - Install from registry
  • POST /api/v3/plugins/install-from-url - Install from GitHub URL
  • POST /api/v3/plugins/uninstall - Uninstall plugin
  • POST /api/v3/plugins/update - Update plugin

Plugin Store

  • GET /api/v3/plugins/store/list - List available registry plugins
  • GET /api/v3/plugins/store/github-status - GitHub authentication status
  • POST /api/v3/plugins/store/refresh - Refresh registry from GitHub

Real-time Streams (SSE)

SSE stream endpoints are defined directly on the Flask app (app.py:607-619 — includes the CSRF exemption and rate-limit hookup alongside the three route definitions), not on the api_v3 blueprint:

  • GET /api/v3/stream/stats - System statistics stream
  • GET /api/v3/stream/display - Display preview stream
  • GET /api/v3/stream/logs - Service logs stream

Development

When making changes to the web interface:

  1. Edit files in this directory
  2. Test changes by running python3 web_interface/start.py
  3. Restart the service if running: sudo systemctl restart ledmatrix-web

Notes

  • Templates and static files use the v3/ prefix to allow for future versions
  • The interface uses Flask blueprints for modular organization
  • SSE streams provide real-time updates without polling