From 417624216e3e60588b8d8370f0f02f926949dcdb Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Thu, 27 Aug 2026 23:10:36 +0200 Subject: [PATCH 1/4] pdist: run the post-update scripts in the client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scripts to be run after an update live in a directory on the client and are called by a wrapper, which the master has to name on every push: ExecStart=… pdist push $host … --post-command /usr/libexec/post-pdist/post-pdist $ cat /usr/libexec/post-pdist/post-pdist set -e for f in /usr/libexec/post-pdist/[0-9]*; do "$f" done The client knows on its own when an update has finished, so let it walk the directory itself. That removes the wrapper, the argument which has to be threaded from the unit file through ssh to the client, and the [0-9] glob, which only exists to keep the wrapper from calling itself. A push without `--post-command` now runs the scripts as well, which is what one wants for an ad-hoc `pdist push somehost`. The scripts run before `--post-command`, which is kept for anything a single push wants to add. The first failing script stops the run, as `set -e` did in the wrapper, so a script may rely on its predecessors having run. Unlike the wrapper, the failure now also fails the update: the client dies, ssh exits non-zero and `push_single()` picks that up. A post-update script which fixes up the installed system is not something to fail silently. Names are restricted to digits, letters, '-', '.' and '_' so that an editor backup left in the directory is not executed – the old glob would have run `0001-foo~`. A script which is not executable stays an error and is not silently skipped. Tested with a copy of the script pointed at a scratch directory: case 1: directory missing → no output, exit 0 case 2: 0001, 0002, 0010 → run in that order, exit 0 README, 0001-first.orig~, subdirectory 0005-a-directory → ignored case 3: 0002 exits 3 .../0002-second: exit status 3 → 0010 not run, exit 3 case 4: 0002 kills itself with SIGTERM .../0002-second: killed by signal 15 → 0010 not run, exit 255 case 5: 0002 not executable .../0002-second: Permission denied → 0010 not run, exit 13 case 6: --quiet → per-script line gone, failure still reported The directory is empty until the scripts move there, so this commit on its own is a no-op and the wrapper keeps doing the work. Assisted-by: Claude Opus 5 (claude-opus-5) --- pdist/pdist | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pdist/pdist b/pdist/pdist index b82c6cfd..8aad04b1 100755 --- a/pdist/pdist +++ b/pdist/pdist @@ -170,6 +170,7 @@ BEGIN { use constant { DISTFILE => '/root/Distfile', # read FILES,HOSTS,EXCEPTS here + POST_UPDATE_DIR => '/usr/libexec/pdist/post-update.d', # run scripts from here after a successful update }; our $PID=$$; @@ -846,6 +847,27 @@ sub prog_update } } +sub run_post_update_scripts { + my $hostname = hostname; + + unless (opendir DIR, POST_UPDATE_DIR) { + $! == 2 and return; # ENOENT: no scripts installed + die POST_UPDATE_DIR . ": $!\n"; + } + my @scripts = sort grep /^[0-9][-.0-9A-Za-z_]*$/, readdir DIR; + closedir DIR; + + for my $script (@scripts) { + my $path = POST_UPDATE_DIR . '/' . $script; + -f $path or next; # ignore subdirectories + $quiet or warn "$hostname: executing $path\n"; + system $path; + $? == -1 and die "$path: $!\n"; + $? & 127 and die sprintf "%s: killed by signal %d\n", $path, $? & 127; + $? and die sprintf "%s: exit status %d\n", $path, $? >> 8; + } +} + sub prog_client { my ($master,$port)=@_; @@ -944,6 +966,8 @@ sub prog_client unlink ($master_index); unlink ($requestfile); + $fileop_noop or run_post_update_scripts(); + if ($post_command) { $verbose and warn "$hostname: executing $post_command\n"; $fileop_noop or system $post_command; @@ -1179,6 +1203,11 @@ usage: $0 cmd [options] update want-indexfile [tarfile] # update to want-indexfile using tar client master-name port [--msg-prefix] xxx # client server + # after a successful update the client runs all scripts in + # @{[POST_UPDATE_DIR]}/ whose name starts with a digit, in name + # order, and aborts on the first one which fails. --post-command, if + # given, runs afterwards. + # high level push [options] client ... # update client with our snapshot From cfdf9727a4453f3c4bd6348f81920aa10f51fe42 Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Thu, 27 Aug 2026 23:11:10 +0200 Subject: [PATCH 2/4] post-pdist: move the scripts to `pdist/post-update.d` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client walks `/usr/libexec/pdist/post-update.d` now, so move the scripts there and drop the wrapper: /usr/libexec/post-pdist/post-pdist gone /usr/libexec/post-pdist/0001-… → /usr/libexec/pdist/post-update.d/0001-… /usr/libexec/post-pdist/0002-… → /usr/libexec/pdist/post-update.d/0002-… They sit next to the pdist client which runs them, under the name of the program owning the directory rather than under the name of the wrapper which used to. `.d` says a script can be dropped in without editing anything else, which `install.sh` has done since commit 692fa93. `/usr/libexec`, not `/etc`: these are executables shipped with the distribution and not host configuration. `pdistd@.service` no longer has to name the wrapper. The old directory disappears from the clients on the next push, because pdist deletes what the master does not have. Installed into a `DESTDIR` to check the layout: $ DESTDIR=/scratch/... ./install.sh $ find $DESTDIR -path '*post-update*' …/usr/libexec/pdist/post-update.d …/usr/libexec/pdist/post-update.d/0001-convert-default-target …/usr/libexec/pdist/post-update.d/0002-reload-systemd-260.2 $ ls -l .../usr/libexec/pdist/post-update.d/ -rwxr-xr-x 1 … 0001-convert-default-target -rwxr-xr-x 1 … 0002-reload-systemd-260.2 Assisted-by: Claude Opus 5 (claude-opus-5) --- install.sh | 4 ++-- pdist/pdistd@.service | 2 +- .../post-update.d}/0001-convert-default-target | 0 .../post-update.d}/0002-reload-systemd-260.2 | 0 post-pdist/post-pdist | 9 --------- 5 files changed, 3 insertions(+), 12 deletions(-) rename {post-pdist => pdist/post-update.d}/0001-convert-default-target (100%) rename {post-pdist => pdist/post-update.d}/0002-reload-systemd-260.2 (100%) delete mode 100755 post-pdist/post-pdist diff --git a/install.sh b/install.sh index 00b78e41..75b9ad7c 100755 --- a/install.sh +++ b/install.sh @@ -269,8 +269,8 @@ install_exec syncthing/startstop-syncthing.sh "$DESTDIR$usr_sbindir/ install_data dbus-user-session/dbus.service "$DESTDIR$usr_libdir/systemd/user/dbus.service" install_data dbus-user-session/dbus.socket "$DESTDIR$usr_libdir/systemd/user/dbus.socket" install_symlink ../dbus.socket "$DESTDIR$usr_libdir/systemd/user/sockets.target.wants/dbus.socket" -for f in post-pdist/*; do - install_exec "$f" "$DESTDIR$usr_exec_prefix/libexec/post-pdist/$(basename "$f")" +for f in pdist/post-update.d/*; do + install_exec "$f" "$DESTDIR$usr_exec_prefix/libexec/pdist/post-update.d/$(basename "$f")" done postinstall diff --git a/pdist/pdistd@.service b/pdist/pdistd@.service index 186da67b..f246aa4c 100644 --- a/pdist/pdistd@.service +++ b/pdist/pdistd@.service @@ -3,4 +3,4 @@ CollectMode=inactive-or-failed [Service] StandardOutput=socket -ExecStart=bash -c "read ip host <<< $(getent hosts ${REMOTE_ADDR}) ; echo update in progess... ; pdist push $host --timeout 600 --set-pdist-status --post-command /usr/libexec/post-pdist/post-pdist" +ExecStart=bash -c "read ip host <<< $(getent hosts ${REMOTE_ADDR}) ; echo update in progess... ; pdist push $host --timeout 600 --set-pdist-status" diff --git a/post-pdist/0001-convert-default-target b/pdist/post-update.d/0001-convert-default-target similarity index 100% rename from post-pdist/0001-convert-default-target rename to pdist/post-update.d/0001-convert-default-target diff --git a/post-pdist/0002-reload-systemd-260.2 b/pdist/post-update.d/0002-reload-systemd-260.2 similarity index 100% rename from post-pdist/0002-reload-systemd-260.2 rename to pdist/post-update.d/0002-reload-systemd-260.2 diff --git a/post-pdist/post-pdist b/post-pdist/post-pdist deleted file mode 100755 index 439cfda3..00000000 --- a/post-pdist/post-pdist +++ /dev/null @@ -1,9 +0,0 @@ -#! /bin/bash - -# Call scripts after successfull distmaster pdist - -set -e - -for f in /usr/libexec/post-pdist/[0-9]*; do - "$f" -done From 597ae97245c3cc839a334e52be7c4800ea140740 Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Thu, 27 Aug 2026 23:44:22 +0200 Subject: [PATCH 3/4] pdist: post-update.d: add 0005-make-automaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user created while a host is switched off cannot log in on that host after it comes back, although `/etc/passwd` has the entry: $ sudo journalctl -b -u make-automaps.service make-automaps.service skipped, unmet condition check ConditionPathExists=!/etc/automount/auto.scratch pdist distributes `/etc/amd`, but not the autofs maps generated from it: `/etc/automount` is excluded in `/root/Distfile` and built on the client by `make-automaps`. The only thing calling it on a running system is `make-automaps.service`, and its `ConditionPathExists=!/etc/automount/auto.scratch` holds exactly once, on a host which has never built the maps. So the host receives the new `/etc/passwd` entry with the next update, while `/etc/automount/auto.home` keeps the state of its last boot as an installed system, and the home directory of the new user cannot be mounted. `clusterd push --post make-automaps` covers the case where the host is up when the user is created; the host which was down is picked up by whatever update comes later, and that is the pdist update. So rebuild the maps from there. Verified that the client picks the script up, with make-automaps replaced by a stub so nothing outside the scratch directory is touched: …: executing …/post-update.d/0001-convert-default-target …: executing …/post-update.d/0002-reload-systemd-260.2 …: executing …/post-update.d/0005-make-automaps make-automaps stub: rebuilding maps exit=0 and that a failing `make-automaps` fails the update: …/post-update.d/0005-make-automaps: exit status 2 exit=2 Not tested against a real distmaster and a real client. Resolves: https://github.molgen.mpg.de/mariux64/mxtools/issues/574 Assisted-by: Claude Opus 5 (claude-opus-5) --- pdist/post-update.d/0005-make-automaps | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 pdist/post-update.d/0005-make-automaps diff --git a/pdist/post-update.d/0005-make-automaps b/pdist/post-update.d/0005-make-automaps new file mode 100755 index 00000000..f5a92ed9 --- /dev/null +++ b/pdist/post-update.d/0005-make-automaps @@ -0,0 +1,3 @@ +#!/bin/bash + +/usr/sbin/make-automaps From ff280e88e803372167770cc7c300fdeb40b81042 Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Mon, 31 Aug 2026 16:40:11 +0200 Subject: [PATCH 4/4] pdist-bootcheck: drop the make-automaps calls Both update paths call make-automaps right after asking the distmaster for an update: -- bash -c 'netcat $(distmaster) 237 ; make-automaps' netcat $(distmaster) 237 make-automaps That netcat reaches pdistd.socket on the distmaster, which pushes back to this host, and the push runs 0005-make-automaps on the client now. So the maps are already rebuilt when netcat returns. Assisted-by: Claude Opus 5 (claude-opus-5) --- pdist/pdist-bootcheck | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pdist/pdist-bootcheck b/pdist/pdist-bootcheck index 95531ae0..93f8236c 100755 --- a/pdist/pdist-bootcheck +++ b/pdist/pdist-bootcheck @@ -29,7 +29,7 @@ fi if [ "$need_async_pdist" ]; then systemd-run --unit pdist-async-update --service-type=simple --remain-after-exit \ - -- bash -c 'netcat $(distmaster) 237 ; make-automaps' + -- bash -c 'netcat $(distmaster) 237' echo triggered background update elif [ "$need_pdist_and_reboot" ]; then if [[ -e /var/cache/updatecheck.reboot-triggered ]]; then @@ -46,7 +46,6 @@ elif [ "$need_pdist_and_reboot" ]; then sync / while true; do sleep 5; echo "System update in progress. Please be patient..."; done & netcat $(distmaster) 237 - make-automaps mxgrub default systemctl start reboot.target --job-mode=replace-irreversibly fi