diff --git a/packaging/install.sh b/packaging/install.sh index f841ba1..a908fef 100755 --- a/packaging/install.sh +++ b/packaging/install.sh @@ -82,13 +82,17 @@ install -m 644 "$SCRIPT_DIR/pimonitor-apt-update.service" /etc/systemd/system/pi install -m 644 "$SCRIPT_DIR/pimonitor-apt-update.timer" /etc/systemd/system/pimonitor-apt-update.timer # Capture this before daemon-reload/enable so an upgrade of an already-running -# service is detected: 'enable --now' is a no-op when the unit is already -# active, which would otherwise leave the old binary running until an -# unrelated reboot/restart. +# service/timer is detected: 'enable --now' is a no-op when the unit is +# already active, which would otherwise leave the old binary/schedule loaded +# until an unrelated reboot/restart. SERVICE_WAS_ACTIVE=0 if systemctl is-active --quiet pimonitor.service; then SERVICE_WAS_ACTIVE=1 fi +TIMER_WAS_ACTIVE=0 +if systemctl is-active --quiet pimonitor-apt-update.timer; then + TIMER_WAS_ACTIVE=1 +fi systemctl daemon-reload @@ -100,9 +104,15 @@ if [[ "$SERVICE_WAS_ACTIVE" -eq 1 ]]; then echo "pimonitor.service was already running; restarting to pick up the new binary/unit..." systemctl restart pimonitor.service fi +if [[ "$TIMER_WAS_ACTIVE" -eq 1 ]]; then + echo "pimonitor-apt-update.timer was already active; restarting to pick up any schedule change..." + systemctl restart pimonitor-apt-update.timer +fi echo "Running an initial apt cache refresh so the update count isn't empty on first view..." -systemctl start pimonitor-apt-update.service || true +if ! systemctl start pimonitor-apt-update.service; then + echo "warning: initial apt cache refresh failed; check 'systemctl status pimonitor-apt-update.service'" >&2 +fi echo echo "Done. Check status with:" diff --git a/packaging/pimonitor-apt-update.service b/packaging/pimonitor-apt-update.service index b030ea8..d21e5f8 100644 --- a/packaging/pimonitor-apt-update.service +++ b/packaging/pimonitor-apt-update.service @@ -8,3 +8,37 @@ Type=oneshot # This is the only part of PiMonitor that touches apt with write intent; # the main pimonitor.service never runs this itself and never needs root. ExecStart=/usr/bin/apt-get update + +# Hardening. apt-get update needs root (to write the package lists/cache +# and to drop privileges to the _apt sandbox user for the actual network +# fetch) so this can't be locked down as tightly as pimonitor.service, but +# the following cheap directives still apply. +NoNewPrivileges=true +# strict (not full) so ReadWritePaths= below actually confines writes +# instead of being a no-op - traced (strace) against a live 'apt-get +# update': the only write-mode opens are under /var/lib/apt/lists (index +# files) and /tmp (apt-key's gpg homedir, already covered by PrivateTmp). +# /var/cache/apt is included for portability across apt versions that +# rebuild pkgcache.bin here. Nothing is written under /var/log/apt - that's +# only touched by install/remove, not update. +ProtectSystem=strict +ReadWritePaths=/var/lib/apt /var/cache/apt +ProtectHome=true +PrivateTmp=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true + +# Trimmed from the default (full) capability set for root. Verified against +# a live 'apt-get update' run: apt drops to the _apt user for the actual +# download (CAP_SETUID/CAP_SETGID), then chowns/chmods the fetched index +# files (CAP_CHOWN/CAP_FOWNER) and needs CAP_DAC_OVERRIDE to replace +# lock/partial files it doesn't own as _apt. Dropping any one of these five +# makes apt-get update fail outright or silently leave files root-owned +# instead of _apt-owned. +CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_SETGID CAP_SETUID + +# The 6-hourly refresh shouldn't compete with foreground workloads on slow +# SD cards. +Nice=19 +IOSchedulingClass=idle