Fix Void package lifecycle on host
This commit is contained in:
@@ -52,6 +52,11 @@ VENDOR_DIR="${STAGE_DIR}/opt/fenris/vendor"
|
||||
mkdir -p "${VENDOR_DIR}"
|
||||
python3 -m pip install --disable-pip-version-check --no-compile \
|
||||
--target "${VENDOR_DIR}" -r "${REPO_ROOT}/requirements.txt" "${WHEEL}"
|
||||
# Package files must be importable by unprivileged Fenris users regardless of
|
||||
# the builder's umask. pip otherwise preserves a restrictive umask in the
|
||||
# vendored runtime, which makes the installed CLI fail before it can read the
|
||||
# observation store.
|
||||
chmod -R a+rX "${VENDOR_DIR}"
|
||||
|
||||
# --- Inject version into wrapper from pyproject.toml ---
|
||||
# The wrapper has a hardcoded version string; patch it for packaging.
|
||||
|
||||
+10
-14
@@ -1,33 +1,29 @@
|
||||
#!/bin/sh
|
||||
# XBPS REMOVE script — pre-remove and purge paths.
|
||||
# XBPS REMOVE script — pre-remove path.
|
||||
#
|
||||
# Arguments: $1=ACTION $2=PKGNAME $3=VERSION $4=UPDATE $5=CONF_FILE $6=ARCH
|
||||
#
|
||||
# Actions: pre (before files removed), post (after files removed),
|
||||
# purge (after metadata removed, for config cleanup)
|
||||
# Actions: pre (before files removed)
|
||||
set -eu
|
||||
|
||||
ACTION="$1"
|
||||
|
||||
UPDATE="$4"
|
||||
case "${ACTION}" in
|
||||
pre)
|
||||
# Sanctioned disable — close monitoring period
|
||||
# Only a real erase is a sanctioned disable. An upgrade must preserve
|
||||
# both monitoring intent and the active runit service.
|
||||
if [ "${UPDATE}" = "no" ]; then
|
||||
# Close the monitoring period while the store is still available.
|
||||
if [ -x /usr/libexec/fenris/fenris-monitor ]; then
|
||||
/usr/libexec/fenris/fenris-monitor disable --now 2>/dev/null || true
|
||||
fi
|
||||
# Configuration and the observation store are deliberately not
|
||||
# package-owned. Leave them in place: XBPS does not guarantee a
|
||||
# post-remove callback before its cleanup action.
|
||||
# runit: remove the service symlink and mark dormant
|
||||
rm -f /var/service/fenris-collect
|
||||
touch /etc/sv/fenris-collect/down 2>/dev/null || true
|
||||
;;
|
||||
purge)
|
||||
# Full cleanup — remove config, store, and runit service directories
|
||||
rm -rf /etc/fenris
|
||||
rm -rf /var/lib/fenris
|
||||
if getent group fenris > /dev/null 2>&1; then
|
||||
groupdel fenris 2>/dev/null || true
|
||||
fi
|
||||
rm -rf /etc/sv/fenris-collect 2>/dev/null || true
|
||||
rm -rf /var/log/fenris-collect 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -319,16 +319,37 @@ def _runit_is_enabled() -> bool:
|
||||
def _runit_is_running() -> bool:
|
||||
"""Check if the runit service is currently running.
|
||||
|
||||
Looks for a 'supervise/pid' file in the service directory.
|
||||
Looks for a 'supervise/pid' file in the service directory. Void creates
|
||||
that directory root-only, so unprivileged dashboard reads fall back to
|
||||
the public process table when they cannot traverse it.
|
||||
"""
|
||||
pid_file = FENRIS_SV_DIR / "supervise" / "pid"
|
||||
if not pid_file.exists():
|
||||
def runsv_process_exists() -> bool:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["pgrep", "-f", "^runsv fenris-collect$"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
||||
return False
|
||||
|
||||
pid_file = FENRIS_SV_DIR / "supervise" / "pid"
|
||||
# On Void, Path.exists() is false for an unprivileged process when it
|
||||
# cannot traverse runit's root-only supervise directory.
|
||||
if not pid_file.exists():
|
||||
return FENRIS_SERVICE_LINK.exists() and runsv_process_exists()
|
||||
try:
|
||||
pid = int(pid_file.read_text().strip())
|
||||
# Check if the process is alive
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except PermissionError:
|
||||
# runsv's supervisor state is root-only on Void. Its process command
|
||||
# is still observable, which gives the read-only UI the same runtime
|
||||
# fact without granting it service-control permissions.
|
||||
return FENRIS_SERVICE_LINK.exists() and runsv_process_exists()
|
||||
except (ValueError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
@@ -471,7 +471,9 @@ class TestRunitIsRunning:
|
||||
sv_dir = tmp_path / "sv" / "fenris-collect"
|
||||
sv_dir.mkdir(parents=True)
|
||||
|
||||
with patch("fenris.init_system.FENRIS_SV_DIR", sv_dir):
|
||||
with patch("fenris.init_system.FENRIS_SV_DIR", sv_dir), \
|
||||
patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value.returncode = 1
|
||||
assert _runit_is_running() is False
|
||||
|
||||
def test_is_running_false_dead_process(self, tmp_path):
|
||||
@@ -485,6 +487,37 @@ class TestRunitIsRunning:
|
||||
patch("os.kill", side_effect=OSError("No such process")):
|
||||
assert _runit_is_running() is False
|
||||
|
||||
def test_is_running_uses_process_table_when_supervise_is_unreadable(self, tmp_path):
|
||||
"""Void keeps runit's supervise directory root-only for normal users."""
|
||||
sv_dir = tmp_path / "sv" / "fenris-collect"
|
||||
supervise_dir = sv_dir / "supervise"
|
||||
supervise_dir.mkdir(parents=True)
|
||||
pid_file = supervise_dir / "pid"
|
||||
pid_file.write_text("12345")
|
||||
service_link = tmp_path / "service" / "fenris-collect"
|
||||
service_link.parent.mkdir(parents=True)
|
||||
service_link.symlink_to(sv_dir)
|
||||
|
||||
original_read_text = Path.read_text
|
||||
|
||||
def deny_pid(path, *args, **kwargs):
|
||||
if path == pid_file:
|
||||
raise PermissionError("supervise is root-only")
|
||||
return original_read_text(path, *args, **kwargs)
|
||||
|
||||
with patch("fenris.init_system.FENRIS_SV_DIR", sv_dir), \
|
||||
patch("fenris.init_system.FENRIS_SERVICE_LINK", service_link), \
|
||||
patch.object(Path, "read_text", autospec=True, side_effect=deny_pid), \
|
||||
patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value.returncode = 0
|
||||
assert _runit_is_running() is True
|
||||
mock_run.assert_called_once_with(
|
||||
["pgrep", "-f", "^runsv fenris-collect$"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API dispatching
|
||||
|
||||
Reference in New Issue
Block a user