mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
test(dashboard): add unit tests for systemd unit and script validation
- Ensure dashboard service doesn't create an ordering cycle with `multi-user.target`. - Verify service safely attaches to a virtual terminal (VT) and passes VT to Xorg. - Ensure no `su` usage by default in the service. - Validate script behavior for passing VT and `-keeptty` options to Xorg. chore(dashboard): update systemd unit to avoid VT conflicts and improve logging - Remove `After` and `Wants` directives for `multi-user.target` to prevent conflicts. - Add `TTYPath`, `StandardInput`, and related options for VT attachment. - Ensure `ExecStopPost` restores getty service for the dashboard VT. fix(dashboard): improve Xorg resolution and clean up script logic - Dynamically determine Xorg executable location in `dashboard.sh`. - Pass VT, display, and `-keeptty` to Xorg to improve script reliability. fix(mariadb): correct exFAT mount options for external storage - Avoid `chown` failures by setting root ownership with group access for `mysql`. - Update external mount options to `uid=0,gid=mysql,umask=0002`. cleanup(mariadb): remove unnecessary owner/group permissions from migration marker task
This commit is contained in:
parent
3c2a2d23a1
commit
17cb827be5
@ -15,6 +15,11 @@ This is a temporary, host-specific setup for `merlin.prole.org` to move MariaDB
|
||||
- The disk is borrowed and formatted as `exFAT`.
|
||||
- Running MariaDB on `exFAT` is not ideal (permissions/ownership/ACLs/xattrs are limited), but this is an accepted short-term tradeoff until a better USB device is installed.
|
||||
|
||||
In particular:
|
||||
|
||||
- `exFAT` does **not** support POSIX ownership, so `chown` will fail.
|
||||
- The mount options should not force everything to `mysql:mysql` with a restrictive umask. For `merlin`, we mount as `uid=0,gid=mysql` with a permissive umask so the `mysql` group can still write.
|
||||
|
||||
## Verify the mount
|
||||
|
||||
On `merlin`:
|
||||
|
||||
@ -18,7 +18,9 @@ mariadb_external_enabled: true
|
||||
mariadb_external_device: /dev/sda1
|
||||
mariadb_external_mountpoint: /external
|
||||
mariadb_external_fstype: exfat
|
||||
mariadb_external_mount_opts: "defaults,nofail,x-systemd.device-timeout=10,uid=mysql,gid=mysql,umask=0077"
|
||||
# exFAT does not support POSIX ownership/permissions.
|
||||
# Use a root-owned mount with group access for `mysql`, instead of forcing everything to `mysql:mysql`.
|
||||
mariadb_external_mount_opts: "defaults,nofail,x-systemd.device-timeout=10,uid=0,gid=mysql,umask=0002"
|
||||
|
||||
mariadb_external_src_datadir: /srv/mariadb/mariadb
|
||||
mariadb_external_dst_datadir: /external/mariadb
|
||||
|
||||
@ -1,18 +1,33 @@
|
||||
[Unit]
|
||||
Description=Prole console dashboard (X + conky)
|
||||
After=multi-user.target systemd-user-sessions.service
|
||||
Wants=multi-user.target
|
||||
After=systemd-user-sessions.service
|
||||
Wants=systemd-user-sessions.service
|
||||
|
||||
# If a graphical desktop is running, starting this service will stop it.
|
||||
Conflicts=graphical.target
|
||||
Before=graphical.target
|
||||
|
||||
# If a getty is running on the dashboard VT, stop it so Xorg can take over.
|
||||
Conflicts=getty@tty{{ dashboard_vt }}.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
# Run the dashboard on the primary console (VT{{ dashboard_vt }}).
|
||||
# openvt gives the process a controlling TTY so Xorg can take over the display.
|
||||
ExecStart=/usr/bin/openvt -c {{ dashboard_vt }} -f -- /bin/su -l {{ dashboard_user }} -c {{ dashboard_script_dest | quote }}
|
||||
# Attach the service to the dashboard VT so Xorg can take over the display.
|
||||
TTYPath=/dev/tty{{ dashboard_vt }}
|
||||
StandardInput=tty
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
TTYReset=yes
|
||||
TTYVHangup=yes
|
||||
TTYVTDisallocate=yes
|
||||
|
||||
Environment=DASHBOARD_VT={{ dashboard_vt }}
|
||||
|
||||
ExecStart={{ dashboard_script_dest }}
|
||||
|
||||
# When the dashboard stops, try to restore the login prompt on that VT.
|
||||
ExecStopPost=-/usr/bin/systemctl --no-block start getty@tty{{ dashboard_vt }}.service
|
||||
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
@ -142,9 +142,6 @@
|
||||
- name: Write migration marker
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ mariadb_external_marker }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
migrated_from={{ mariadb_external_src }}
|
||||
migrated_to={{ mariadb_external_dst }}
|
||||
|
||||
54
tests/test_dashboard_systemd_unit.py
Normal file
54
tests/test_dashboard_systemd_unit.py
Normal file
@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _project_root() -> Path:
|
||||
# This test file lives in `<root>/tests/`.
|
||||
return Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _render_dashboard_service(*, vt: int = 1) -> str:
|
||||
template_path = (
|
||||
_project_root()
|
||||
/ "infrastructure/roles/dashboard/templates/dashboard.service.j2"
|
||||
)
|
||||
template = template_path.read_text(encoding="utf-8")
|
||||
|
||||
# Keep this test dependency-free (no Jinja2 import required).
|
||||
return (
|
||||
template.replace("{{ dashboard_vt }}", str(vt))
|
||||
.replace("{{ dashboard_script_dest }}", "/usr/local/bin/dashboard.sh")
|
||||
.replace("{{ dashboard_user }}", "pi")
|
||||
)
|
||||
|
||||
|
||||
def test_dashboard_service_is_safe_for_multi_user_target() -> None:
|
||||
rendered = _render_dashboard_service(vt=1)
|
||||
|
||||
# Avoid an ordering cycle with the target that wants the service.
|
||||
assert "After=multi-user.target" not in rendered
|
||||
assert "Wants=multi-user.target" not in rendered
|
||||
assert "WantedBy=multi-user.target" in rendered
|
||||
|
||||
|
||||
def test_dashboard_service_attaches_to_a_vt_and_passes_vt_to_script() -> None:
|
||||
rendered = _render_dashboard_service(vt=1)
|
||||
|
||||
assert "TTYPath=/dev/tty1" in rendered
|
||||
assert "StandardInput=tty" in rendered
|
||||
assert "Environment=DASHBOARD_VT=1" in rendered
|
||||
|
||||
|
||||
def test_dashboard_service_does_not_use_su_by_default() -> None:
|
||||
rendered = _render_dashboard_service(vt=1)
|
||||
assert "su -l" not in rendered
|
||||
|
||||
|
||||
def test_dashboard_script_passes_vt_and_keeptty_to_xorg() -> None:
|
||||
script_path = _project_root() / "tools/dashboard.sh"
|
||||
script = script_path.read_text(encoding="utf-8")
|
||||
|
||||
assert "DASHBOARD_VT" in script
|
||||
assert "vt${DASHBOARD_VT}" in script
|
||||
assert "-keeptty" in script
|
||||
@ -1,6 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DASHBOARD_DISPLAY="${DASHBOARD_DISPLAY:-:0}"
|
||||
DASHBOARD_VT="${DASHBOARD_VT:-1}"
|
||||
|
||||
XORG_BIN="${DASHBOARD_XORG_BIN:-}"
|
||||
if [[ -z "$XORG_BIN" ]]; then
|
||||
if command -v Xorg >/dev/null 2>&1; then
|
||||
XORG_BIN="$(command -v Xorg)"
|
||||
elif [[ -x /usr/lib/xorg/Xorg ]]; then
|
||||
XORG_BIN=/usr/lib/xorg/Xorg
|
||||
elif [[ -x /usr/bin/Xorg ]]; then
|
||||
XORG_BIN=/usr/bin/Xorg
|
||||
else
|
||||
echo "dashboard.sh: Xorg not found in PATH and common locations" >&2
|
||||
exit 127
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create a temp conky config file
|
||||
CFG="$(mktemp /tmp/conky-dashboard.XXXXXX.conf)"
|
||||
cleanup() { rm -f "$CFG"; }
|
||||
@ -37,7 +54,7 @@ ${color 666666}Procs:${color} $processes ${color 666666}Running:${color} $runn
|
||||
EOF
|
||||
|
||||
# Start X just for this dashboard. Conky runs in the foreground; Ctrl-C exits X.
|
||||
exec xinit /usr/bin/env bash -lc '
|
||||
exec /usr/bin/xinit /usr/bin/env bash -lc '
|
||||
# Disable screen blanking + power management
|
||||
xset s off
|
||||
xset -dpms
|
||||
@ -45,4 +62,4 @@ exec xinit /usr/bin/env bash -lc '
|
||||
|
||||
xsetroot -solid "#202020"
|
||||
exec conky -c "'"$CFG"'"
|
||||
' -- :0
|
||||
' -- "$XORG_BIN" "$DASHBOARD_DISPLAY" "vt${DASHBOARD_VT}" -keeptty -nolisten tcp
|
||||
|
||||
Loading…
Reference in New Issue
Block a user