From 3515d06e0ab0dab43df63003975d8c64ab2f37f9 Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 23:17:47 +0100 Subject: [PATCH] fix(install): clean stale cron spools (recycled-uid "rename: not permitted") MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit userdel does NOT remove /var/spool/cron/crontabs/, so across an uninstall->reinstall the manager's uid can be recycled (e.g. 1001 -> 1003) while the old spool file stays owned by the dead uid. The spool dir is sticky (1730), so the new manager can't rename its temp over the old-uid-owned file → "crontab: crontabs/libreportal: rename: Operation not permitted", and the crontab silently never updates (the "added" success message doesn't check the rename). Same class as the stale easydocker spool left by the pre-rename migration. Two fixes: - runFullUninstall removes each torn-down user's cron spool (+ the legacy easydocker one) so teardown stops leaving orphans. - initUsers defensively drops a manager cron spool owned by a different uid (recycled) before the manager-run crontab setup runs — fixes an already-dirty box and any uid drift, in both modes. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- init.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/init.sh b/init.sh index 729d60d..388ef41 100755 --- a/init.sh +++ b/init.sh @@ -712,6 +712,22 @@ initUsers() sudo systemctl restart docker isSuccessful "User $sudo_user_name created successfully." fi + + # Drop a stale cron spool from a prior install. userdel doesn't remove + # /var/spool/cron/crontabs/, so if the manager's uid was recycled the + # leftover (owned by the dead uid) can't be replaced by the new user in the + # sticky spool dir → "crontab: rename: Operation not permitted". Remove it + # only when it's owned by a different uid (a current-uid spool is valid), plus + # the legacy easydocker artifact, so the manager-run crontab setup writes a + # clean, correctly-owned spool. + local spool_dir="/var/spool/cron/crontabs" + local mgr_uid; mgr_uid=$(id -u "$sudo_user_name" 2>/dev/null) + if [[ -f "$spool_dir/$sudo_user_name" \ + && "$(stat -c %u "$spool_dir/$sudo_user_name" 2>/dev/null)" != "$mgr_uid" ]]; then + sudo rm -f "$spool_dir/$sudo_user_name" + isNotice "Removed a stale cron spool for $sudo_user_name (recycled uid)." + fi + sudo rm -f "$spool_dir/easydocker" # Install-phase sudo: the heavy install runs AS this user (see the handoff in # completeInitMessage) and needs BROAD root — useradd for the docker-install # user, rootless setup, apt, sysctl, etc. So grant a temporary validated @@ -1460,7 +1476,12 @@ runFullUninstall() pkill -9 -u "$u" >/dev/null 2>&1 || true userdel -r "$u" >/dev/null 2>&1 || true [[ -n "$u" ]] && rm -rf "/home/$u" + # userdel does NOT remove the cron spool; a leftover (owned by this + # now-removed uid) blocks the next install's user from replacing it in the + # sticky spool dir → "crontab: rename: Operation not permitted". + [[ -n "$u" ]] && rm -f "/var/spool/cron/crontabs/$u" done + rm -f /var/spool/cron/crontabs/easydocker # legacy pre-rename artifact if [[ "$keep_docker" == "true" ]]; then sed -i "/^${mgr}:/d" /etc/subuid /etc/subgid 2>/dev/null || true isSuccessful "Removed user '$mgr' (+ home); kept '$iuser' + its docker image store"