mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
* 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>
140 lines
5.9 KiB
Bash
Executable File
140 lines
5.9 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# LED Matrix Web Interface Service Installer
|
|
# This script installs and enables the web interface systemd service
|
|
|
|
set -e
|
|
|
|
echo "Installing LED Matrix Web Interface Service..."
|
|
|
|
# Get the actual user who invoked sudo
|
|
if [ -n "$SUDO_USER" ]; then
|
|
ACTUAL_USER="$SUDO_USER"
|
|
else
|
|
ACTUAL_USER=$(whoami)
|
|
fi
|
|
|
|
# Determine the Project Root Directory (parent of scripts/install/)
|
|
PROJECT_ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
|
|
|
|
# shellcheck source=scripts/install/lib_systemd_render.sh
|
|
source "$PROJECT_ROOT_DIR/scripts/install/lib_systemd_render.sh"
|
|
|
|
echo "Installing for user: $ACTUAL_USER"
|
|
echo "Project root directory: $PROJECT_ROOT_DIR"
|
|
|
|
# Check if running as root
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo "Please run as root (use sudo)"
|
|
exit 1
|
|
fi
|
|
|
|
# Render the unit from systemd/ledmatrix-web.service. That template is the
|
|
# only description of the unit; this script used to carry its own heredoc copy,
|
|
# and install_service.sh a third, which is how the installed unit on real rigs
|
|
# ended up missing RestartSec and SyslogIdentifier while
|
|
# src/startup_validator.py warned about drift on every boot.
|
|
TEMPLATE="$PROJECT_ROOT_DIR/systemd/ledmatrix-web.service"
|
|
if [ ! -f "$TEMPLATE" ]; then
|
|
echo "ERROR: unit template not found at $TEMPLATE"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Writing service file to /etc/systemd/system/ledmatrix-web.service"
|
|
ESCAPED_PROJECT_ROOT_DIR=$(sed_escape_replacement "$PROJECT_ROOT_DIR")
|
|
ESCAPED_ACTUAL_USER=$(sed_escape_replacement "$ACTUAL_USER")
|
|
sed "s|__PROJECT_ROOT_DIR__|$ESCAPED_PROJECT_ROOT_DIR|g; s|__USER__|$ESCAPED_ACTUAL_USER|g" \
|
|
"$TEMPLATE" > /etc/systemd/system/ledmatrix-web.service
|
|
|
|
# Health check and rollback for the web UI's automatic updates. Its own unit so
|
|
# it survives the web service restart it performs; never enabled -- the web
|
|
# interface starts it after an update. Without it, automatic code updates
|
|
# stay paused rather than running with nothing to undo them.
|
|
for VERIFY_UNIT in ledmatrix-update-verify.service ledmatrix-update-verify.path; do
|
|
VERIFY_TEMPLATE="$PROJECT_ROOT_DIR/systemd/$VERIFY_UNIT"
|
|
if [ -f "$VERIFY_TEMPLATE" ]; then
|
|
echo "Writing unit file to /etc/systemd/system/$VERIFY_UNIT"
|
|
sed "s|__PROJECT_ROOT_DIR__|$ESCAPED_PROJECT_ROOT_DIR|g; s|__USER__|$ESCAPED_ACTUAL_USER|g" \
|
|
"$VERIFY_TEMPLATE" > "/etc/systemd/system/$VERIFY_UNIT"
|
|
chmod 644 "/etc/systemd/system/$VERIFY_UNIT"
|
|
else
|
|
echo "WARNING: $VERIFY_TEMPLATE not found; automatic code updates will stay paused"
|
|
fi
|
|
done
|
|
|
|
# Shared cache directory. The display service (root) and this web service both
|
|
# write here and read each other's files, which are created 0660, so the two
|
|
# share it through the directory's group: ledmatrix when the installing user
|
|
# is in it (first_time_install.sh / setup_cache.sh set that up), otherwise the
|
|
# user's own group. setgid makes new files inherit that group.
|
|
#
|
|
# An existing directory keeps its group whenever the web user can read through
|
|
# it -- ledmatrix, or the user's own group where systemd's old CacheDirectory=
|
|
# left it -- because re-grouping a working directory strands every file already
|
|
# in it on the old group. Only a group the user is not in (root's, or ledmatrix
|
|
# for a user outside it) is replaced. This used to force the user's group on
|
|
# every run, replacing the ledmatrix group setup_cache.sh had set.
|
|
echo "Setting up cache directory..."
|
|
CACHE_DIR="/var/cache/ledmatrix"
|
|
USER_GROUPS=$(id -nG "$ACTUAL_USER" 2>/dev/null | tr ' ' '\n')
|
|
if printf '%s\n' "$USER_GROUPS" | grep -qx ledmatrix; then
|
|
CACHE_GROUP="ledmatrix"
|
|
else
|
|
CACHE_GROUP=$(id -gn "$ACTUAL_USER" 2>/dev/null || echo root)
|
|
fi
|
|
if [ ! -d "$CACHE_DIR" ]; then
|
|
mkdir -p "$CACHE_DIR"
|
|
chown root:"$CACHE_GROUP" "$CACHE_DIR" 2>/dev/null || true
|
|
echo "✓ Cache directory created: $CACHE_DIR"
|
|
else
|
|
DIR_GROUP=$(stat -c %G "$CACHE_DIR" 2>/dev/null)
|
|
if ! printf '%s\n' "$USER_GROUPS" | grep -qx "$DIR_GROUP"; then
|
|
if chgrp "$CACHE_GROUP" "$CACHE_DIR" 2>/dev/null; then
|
|
echo "✓ Cache directory group changed from $DIR_GROUP to $CACHE_GROUP"
|
|
# Files already there keep the old group. The display service
|
|
# re-groups its own files when it starts (DiskCache.share_existing_files,
|
|
# which refuses symlinks and hard links); a recursive chgrp here
|
|
# would not. try-restart does nothing if the service is not running.
|
|
if find "$CACHE_DIR" -maxdepth 1 -name '*.json' -user root ! -group "$CACHE_GROUP" -print -quit 2>/dev/null | grep -q .; then
|
|
systemctl try-restart ledmatrix.service 2>/dev/null || true
|
|
fi
|
|
fi
|
|
fi
|
|
echo "✓ Cache directory exists: $CACHE_DIR"
|
|
fi
|
|
chmod 2775 "$CACHE_DIR" 2>/dev/null || true
|
|
|
|
# Reload systemd to recognize the new service
|
|
echo "Reloading systemd..."
|
|
systemctl daemon-reload
|
|
|
|
# Enable the service to start on boot
|
|
echo "Enabling ledmatrix-web.service..."
|
|
systemctl enable ledmatrix-web.service
|
|
|
|
# The path unit is what starts the health check after an automatic update.
|
|
if [ -f /etc/systemd/system/ledmatrix-update-verify.path ]; then
|
|
echo "Enabling ledmatrix-update-verify.path..."
|
|
systemctl enable --now ledmatrix-update-verify.path || \
|
|
echo "WARNING: could not enable ledmatrix-update-verify.path; automatic code updates will stay paused"
|
|
fi
|
|
|
|
# Start the service
|
|
echo "Starting ledmatrix-web.service..."
|
|
systemctl start ledmatrix-web.service
|
|
|
|
# Check service status
|
|
echo "Checking service status..."
|
|
systemctl status ledmatrix-web.service --no-pager
|
|
|
|
echo ""
|
|
echo "Web interface service installed and started!"
|
|
echo "The web interface will now start automatically when:"
|
|
echo "1. The system boots"
|
|
echo "2. The 'web_display_autostart' setting is true in config/config.json"
|
|
echo ""
|
|
echo "To check the service status: systemctl status ledmatrix-web.service"
|
|
echo "To view logs: journalctl -u ledmatrix-web.service -f"
|
|
echo "To stop the service: systemctl stop ledmatrix-web.service"
|
|
echo "To disable autostart: systemctl disable ledmatrix-web.service"
|