The display process now serves a Unix socket, /run/ledmatrix/control.sock, carrying versioned newline-delimited JSON commands that are acknowledged. Stage 1 moves on-demand start/stop (plus status, hello and ping) onto it; the cache-file mailbox stays as the fallback for one release. - src/ipc/contract.py: typed request/response envelopes, command args, error codes, NDJSON framing with a 64 KiB limit, socket path rules. - src/ipc/server.py: threaded server owned by the display. Handlers only queue onto a bounded queue and ack with the request id; the render thread drains it where it reads the mailbox. Bounded clients, timeouts, garbage/oversize/disconnect handling; 0660 socket in the cache dir's group plus SO_PEERCRED checks; skips cleanly on Windows or when off. - src/ipc/client.py: one short-timeout request; any failure raises ControlError(reason). - api_v3/display.py: on-demand start/stop try the socket, fall back to the mailbox exactly as before, and report transport/socket_error. - display_controller.py: start/close the server; the mailbox handler body is extracted into _handle_on_demand_request and shared by both paths. - docs/IPC_CONTROL_SOCKET.md: protocol, security model, stage plan. 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