The display serves a control socket (/run/ledmatrix/control.sock) carrying versioned JSON commands, one per line, each answered. Stage 1 covers on-demand start, stop and status; commands are queued on the socket thread and applied on the render thread through the mailbox's own handler, and the web interface falls back to the file mailbox when the socket is unavailable. Protocol and security model: docs/IPC_CONTROL_SOCKET.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.4 KiB
Permissions
Who owns what on an installed system, which privileged commands the web
interface may run, and how to repair ownership when it goes wrong. The
installer, first_time_install.sh, sets all of
this up; this page describes the result.
Users and groups
| Account | Used by | Why |
|---|---|---|
root |
ledmatrix.service (the display) |
The LED matrix library needs direct GPIO access |
The installing user (e.g. ledpi) |
ledmatrix-web.service, ledmatrix-update-verify.service |
A web server should not run as root |
ledmatrix group |
shared files | Members: the installing user, root, and daemon if it exists. Created by setup_cache.sh and the installer |
The installer also adds the web user to systemd-journal and adm so the
Logs tab can read the journal. Group changes apply after the user logs
in again (services pick them up on restart).
Files and directories
| Path | Owner | Mode | Notes |
|---|---|---|---|
| Project directory | web user | dirs 755, files 644, *.sh 755 |
Set in the installer's "Normalize project file permissions" step |
config/ |
web user | 2775 |
|
config/config.json |
web user | 644 |
Written by the web interface |
config/config_secrets.json |
web user : ledmatrix |
640 |
Owned by the web user because the web interface writes it; root reads it regardless of mode |
plugin-repos/, plugins/ |
web user | dirs 2775, files 664 |
The web interface installs and removes plugins |
assets/ |
web user | dirs 755, files 644 |
Root writes downloaded logos regardless |
/var/cache/ledmatrix/ |
root:ledmatrix |
2775 (setgid) |
Shared cache: see below |
| Cache files | creator : ledmatrix |
660 |
|
/run/ledmatrix/ |
root |
755 |
tmpfs; RuntimeDirectory= in ledmatrix.service, removed when the display stops |
/run/ledmatrix/control.sock |
root : cache directory's group (ledmatrix) |
660 |
The display's control socket; only root and that group can connect. See IPC_CONTROL_SOCKET.md |
scripts/fix_perms/safe_plugin_rm.sh, safe_pip_install.sh |
root:root |
755 |
Run as root through sudo, so the web user must not be able to edit them |
/etc/sudoers.d/ledmatrix_web, ledmatrix_wifi |
root |
440 |
What keeps it that way at runtime:
- Config files. Saves go through
src/config_manager_atomic.py, which appliesget_config_file_mode()(640for secrets,644otherwise) and, when running as root, moves the file's group to the project directory's group (ensure_shared_group_ownership()insrc/common/permission_utils.py). - Cache files.
src/cache/disk_cache.pysets every file it writes to0660and gives it the cache directory's group, without relying on the setgid bit. So a file root writes stays readable by the web user. - Plugin directories.
run.pysetssys.dont_write_bytecode, because root-owned__pycache__directories inside a plugin stop the web user updating or removing it.
ledmatrix-web.service deliberately has no CacheDirectory=: systemd would
re-own /var/cache/ledmatrix to the web user and its primary group, and the
web interface could no longer read what the display writes (see the comment
in systemd/ledmatrix-web.service).
If /var/cache/ledmatrix is not usable, CacheManager falls back to
~/.ledmatrix_cache, /opt/ledmatrix/cache or a temp directory
(src/cache_manager.py). The two services then
may not share a cache, and the web UI shows stale or empty display status,
on-demand state and plugin health. Fix the directory rather than living
with the fallback.
sudo rules
/etc/sudoers.d/ledmatrix_web
Generated by web_sudoers_rules() in
scripts/install/lib_sudoers.sh, the
only place these rules are defined. Installed by the installer and by
configure_web_sudo.sh, both of
which check them with visudo -c first. The web user may run, without a
password:
reboot,poweroffsystemctl start|stop|restart|enable|disable|status ledmatrix.service,systemctl is-active ledmatrix[.service]systemctl start|stop|restart ledmatrix-web.servicebash <project>/scripts/fix_perms/safe_plugin_rm.sh *— removes a directory only if it resolves to a child ofplugin-repos/orplugins/bash <project>/scripts/fix_perms/safe_pip_install.sh *— installs arequirements.txtonly if it is the project's own or one underplugin-repos/orplugins/, so the root display service can import the packagesjournalctl -u ledmatrix.service *,-u ledmatrix *,-t ledmatrix *, taggedNOEXEC: journalctl opens a pager on a terminal, and a shell escape from that pager would be a root shell
/etc/sudoers.d/ledmatrix_wifi
Written by
scripts/install/configure_wifi_permissions.sh
(run as the web user; the installer calls it). It refuses to grant a binary
that is not root-owned or is group/world-writable. The rules cover:
nmcli device wifi connect|disconnect *,nmcli device connect|disconnect *,nmcli radio wifi on|offsystemctl start|stop|restart hostapd,... dnsmasq,systemctl restart NetworkManagersysctl -w net.ipv4.ip_forward=0|1nft add|delete table ip ledmatrixrfkill unblock wifimkdir -p /etc/NetworkManager/dnsmasq-shared.dcpof/tmp/hostapd.confand/tmp/dnsmasq.confto their fixed destinations, andrm -f /etc/dnsmasq.d/ledmatrix-captive.confcp /tmp/ledmatrix-nm-dnsmasq.confto/etc/NetworkManager/dnsmasq-shared.d/ledmatrix-captive.conf, andrm -fof that file
iptables is deliberately not granted. The captive portal's rules are
built from the interface name and port, so a rule covering them would need a
trailing wildcard, and iptables --modprobe=<path> runs <path> as root: a
wildcard grant is a root shell for the web user. Doing it safely needs a
wrapper script that builds the rules itself, like safe_plugin_rm.sh. On a
stock Raspberry Pi OS image the default user's blanket NOPASSWD rule
(/etc/sudoers.d/010_pi-nopasswd) hides this gap.
polkit
The same script installs /etc/polkit-1/rules.d/10-ledmatrix-wifi.rules,
which lets the web user perform any org.freedesktop.NetworkManager.*
action without authentication.
Repair scripts
In scripts/fix_perms/. Run them from the project
directory.
| Script | Run as | What it does | Notes |
|---|---|---|---|
fix_plugin_permissions.sh |
sudo |
plugins/ and plugin-repos/ to root:<user>, dirs 2775, files 664; makes a 700 home directory 755 so root can traverse it |
Safe. Group-writable, so the web user keeps write access |
fix_assets_permissions.sh |
sudo |
assets/ to <user>:<group>, mode 777 recursively |
Works, but looser than the installer's 755/644 |
fix_cache_permissions.sh |
sudo |
Runs setup_cache.sh for /var/cache/ledmatrix (root:ledmatrix, 2775, files 660), then makes ~/.ledmatrix_cache (the fallback cache) <user>:<group> mode 777 |
Safe. The ~/.ledmatrix_cache mode is still 777 |
fix_web_permissions.sh |
the web user, without sudo |
Resets project file ownership for the web user (it calls sudo itself), then makes safe_plugin_rm.sh and safe_pip_install.sh root:root 755 again and restores config_secrets.json to its owner, group ledmatrix, mode 640 |
Refuses to run as root. It does not write sudoers rules |
safe_plugin_rm.sh, safe_pip_install.sh |
— | Called by the web interface through sudo | Not for manual use |
To reinstall the sudoers rules, run
./scripts/install/configure_web_sudo.sh (web rules) or
./scripts/install/configure_wifi_permissions.sh (WiFi rules and polkit) as
the web user, not with sudo.
After any of these, restart both services:
sudo systemctl restart ledmatrix.service ledmatrix-web.service
Checking
ls -ld /var/cache/ledmatrix # drwxrwsr-x root ledmatrix
stat -c '%U:%G %a %n' config/config.json config/config_secrets.json
id # web user should list ledmatrix
sudo -l # lists the NOPASSWD rules