From 071132011a8de45e7ec396115abd5249b3c945c6 Mon Sep 17 00:00:00 2001 From: Walter Doekes Date: Thu, 16 Jul 2026 18:10:31 +0200 Subject: [PATCH 1/4] contrib: manual-zfssync/planb-double-backup ssh multiplexing Use a single ssh session for multiple data fetches. This should cut down on the login spam tremendously. And also speed things up a bit. Security implication: root and the running-user can also reuse this socket more easily in the mean time. Obviously, they could have without this change as well. But it's significantly easier now. Change: osso-org/changes#3026 --- contrib/manual-zfssync.sh | 6 +++++- contrib/planb-double-backup | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/contrib/manual-zfssync.sh b/contrib/manual-zfssync.sh index e39c888..225be56 100755 --- a/contrib/manual-zfssync.sh +++ b/contrib/manual-zfssync.sh @@ -11,7 +11,11 @@ # - MANUAL_ZFSSYNC_OVERWRITE_NEWER_SNAPSHOTS=1 ssh_target="$1"; shift # remotebackup@DEST -REMOTE_CMD="/usr/bin/ssh -oLogLevel=error $ssh_target" # options? +REMOTE_CMD_OPTIONS= +if test -n "${SSH_CONTROL_PATH:-}"; then + REMOTE_CMD_OPTIONS="-oControlMaster=no -oControlPath=$SSH_CONTROL_PATH " +fi +REMOTE_CMD="/usr/bin/ssh -oLogLevel=error ${REMOTE_CMD_OPTIONS}${ssh_target}" LOCAL_PREFIX="$1"; shift # "tank" both local and remote REMOTE_PREFIX="$LOCAL_PREFIX" diff --git a/contrib/planb-double-backup b/contrib/planb-double-backup index 78ae8d8..853dc0d 100755 --- a/contrib/planb-double-backup +++ b/contrib/planb-double-backup @@ -9,6 +9,24 @@ RESTART_EVERY=${3:-0} test -z "$USERATHOST" -o -z "$DESTPOOL" && echo "usage: $0 user@host destpool [restartsec]" >&2 && exit 1 +# Multiplexing ssh connections: one master TCP/login for the whole run, so the +# many zfs-list/snapshot/send calls in manual-zfssync.sh don't require +# separate logins (slow and log-spammy). +# WARNING: Cannot contain spaces or awkward shell characters. +SSH_MUX_DIR=$(mktemp -d "${TMPDIR:-/tmp}/planb-ssh.XXXXXX") +SSH_CONTROL_PATH="$SSH_MUX_DIR/ctl" +export SSH_CONTROL_PATH + +start_master() { + ssh -oLogLevel=error -oControlMaster=yes -oControlPath="$SSH_CONTROL_PATH" \ + -oControlPersist=60 -oServerAliveInterval=60 -oServerAliveCountMax=3 \ + -oBatchMode=yes -N -f "$USERATHOST" +} +stop_master() { + ssh -oControlPath="$SSH_CONTROL_PATH" -O exit "$USERATHOST" 2>/dev/null || : +} +trap 'stop_master; rm -rf "$SSH_MUX_DIR"' EXIT INT TERM + while true; do ret=0 DATASETS=$("$here/planb-double-backup-sources.py" "$USERATHOST") || ret=$? @@ -34,7 +52,9 @@ while true; do echo '' $DATASETS echo + start_master "$here/manual-zfssync.sh" "$USERATHOST" "$DESTPOOL" $DATASETS + stop_master # The zfssync wipes this trigger. Re-set it. if test $ret -eq 2 -a -f /etc/zabbix/zabbix_agentd.conf; then From 60f2ce31c0db8015534bf2aa605e464d499dc12f Mon Sep 17 00:00:00 2001 From: Walter Doekes Date: Thu, 16 Jul 2026 18:43:42 +0200 Subject: [PATCH 2/4] contrib: planb-zfsync ssh multiplexing Use a single ssh session for multiple data fetches. This should cut down on the login spam tremendously. And also speed things up a bit. Security implication: root and the running-user can also reuse this socket more easily in the mean time. Obviously, they could have without this change as well. But it's significantly easier now. Change: osso-org/changes#3026 --- contrib/planb-zfssync.sh | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/contrib/planb-zfssync.sh b/contrib/planb-zfssync.sh index 4437baf..fea6046 100755 --- a/contrib/planb-zfssync.sh +++ b/contrib/planb-zfssync.sh @@ -101,6 +101,11 @@ if test "$contains" != "filesystems"; then sudo zfs set planb:contains=filesystems "$planb_storage_name" fi +target_snapshot=$planb_snapshot_target +target_snapshot_prefix=${planb_snapshot_target%-*} +# XXX: do we? +test "$target_snapshot_prefix" = "planb" # (not needed, we use planb:owner) + ssh_target="$1"; shift # remotebackup@DEST (options like -luser disallowed) # XXX: todo: sanitize $1? (no spaces, no funny chars) @@ -114,16 +119,19 @@ else ssh_options="$ssh_options -o StrictHostKeyChecking=no" fi -target_snapshot=$planb_snapshot_target -target_snapshot_prefix=${planb_snapshot_target%-*} -# XXX: do we? -test "$target_snapshot_prefix" = "planb" # (not needed, we use planb:owner) +# Multiplexing ssh connections: one master TCP/login for the whole run, so the +# many zfs-list/snapshot/send calls below don't require separate logins (slow +# and log-spammy). +# WARNING: Cannot contain spaces or awkward shell characters. It's our +# job to clean up ssh_mux_dir now. +ssh_mux_dir=$(mktemp -d "${TMPDIR:-/tmp}/planb-ssh.XXXXXX") +ssh_control_path="$ssh_mux_dir/ctl" # Prepare globals, so we know what to refactor ZFS_SEND_OPTION=$zfs_send_option ZFS_RECV_OPTION=$zfs_recv_option ZFS_RECURSIVE=$zfs_recursive -SSH_OPTIONS=$ssh_options +SSH_OPTIONS="$ssh_options -o ControlMaster=no -o ControlPath=$ssh_control_path" SSH_TARGET=$ssh_target TARGET_SNAPSHOT=$target_snapshot TARGET_SNAPSHOT_PREFIX=$target_snapshot_prefix @@ -281,6 +289,14 @@ prune_remote_snapshots() { } +# Start master ssh connection. +trap 'ssh -o ControlPath="$ssh_control_path" -O exit "$ssh_target" \ + 2>/dev/null || :; rm -rf "$ssh_mux_dir"' EXIT +ssh -o LogLevel=error $ssh_options \ + -o ControlMaster=yes -o ControlPath=$ssh_control_path \ + -o ControlPersist=60 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 \ + -o BatchMode=yes -N -f "$ssh_target" + # Download snapshots (make them on remote if necessary). for arg in "$@"; do process_zfssync_arg download_snapshot "$arg" From b8d3a5f7ee67645feacf83f762c929349c017a12 Mon Sep 17 00:00:00 2001 From: Walter Doekes Date: Fri, 17 Jul 2026 09:12:00 +0200 Subject: [PATCH 3/4] contrib: Small cleanup and EXIT handler consistency --- contrib/planb-double-backup | 15 ++++++++++++--- contrib/planb-zfssync.sh | 12 +++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/contrib/planb-double-backup b/contrib/planb-double-backup index 853dc0d..3f992c7 100755 --- a/contrib/planb-double-backup +++ b/contrib/planb-double-backup @@ -18,14 +18,23 @@ SSH_CONTROL_PATH="$SSH_MUX_DIR/ctl" export SSH_CONTROL_PATH start_master() { - ssh -oLogLevel=error -oControlMaster=yes -oControlPath="$SSH_CONTROL_PATH" \ + ssh -oLogLevel=error -oControlMaster=yes \ + -oControlPath="$SSH_CONTROL_PATH" \ -oControlPersist=60 -oServerAliveInterval=60 -oServerAliveCountMax=3 \ -oBatchMode=yes -N -f "$USERATHOST" } stop_master() { - ssh -oControlPath="$SSH_CONTROL_PATH" -O exit "$USERATHOST" 2>/dev/null || : + ssh -oControlPath="$SSH_CONTROL_PATH" -O exit "$USERATHOST" 2>/dev/null || + true } -trap 'stop_master; rm -rf "$SSH_MUX_DIR"' EXIT INT TERM +cleanup() { + stop_master + rm -rf "$SSH_MUX_DIR" +} +trap 'cleanup' EXIT +trap 'cleanup; trap - INT; kill -INT $$' INT +trap 'cleanup; trap - TERM; kill -TERM $$' TERM + while true; do ret=0 diff --git a/contrib/planb-zfssync.sh b/contrib/planb-zfssync.sh index fea6046..e54c886 100755 --- a/contrib/planb-zfssync.sh +++ b/contrib/planb-zfssync.sh @@ -289,9 +289,15 @@ prune_remote_snapshots() { } -# Start master ssh connection. -trap 'ssh -o ControlPath="$ssh_control_path" -O exit "$ssh_target" \ - 2>/dev/null || :; rm -rf "$ssh_mux_dir"' EXIT +# Setup cleanup handler and start master ssh connection. +stop_ssh() { + ssh -o ControlPath="$ssh_control_path" -O exit "$ssh_target" 2>/dev/null \ + || true + rm -rf "$ssh_mux_dir" +} +trap 'stop_ssh' EXIT +trap 'stop_ssh; trap - INT; kill -INT $$' INT +trap 'stop_ssh; trap - TERM; kill -TERM $$' TERM ssh -o LogLevel=error $ssh_options \ -o ControlMaster=yes -o ControlPath=$ssh_control_path \ -o ControlPersist=60 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 \ From ed171ad7386900ec1ca3e23e4684bf13770d61b1 Mon Sep 17 00:00:00 2001 From: Walter Doekes Date: Fri, 17 Jul 2026 09:25:41 +0200 Subject: [PATCH 4/4] contrib: Cut down planb-double-backup logins from 2 to 1 --- contrib/planb-double-backup | 2 +- contrib/planb-double-backup-sources.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/contrib/planb-double-backup b/contrib/planb-double-backup index 3f992c7..91dcfe8 100755 --- a/contrib/planb-double-backup +++ b/contrib/planb-double-backup @@ -37,6 +37,7 @@ trap 'cleanup; trap - TERM; kill -TERM $$' TERM while true; do + start_master ret=0 DATASETS=$("$here/planb-double-backup-sources.py" "$USERATHOST") || ret=$? @@ -61,7 +62,6 @@ while true; do echo '' $DATASETS echo - start_master "$here/manual-zfssync.sh" "$USERATHOST" "$DESTPOOL" $DATASETS stop_master diff --git a/contrib/planb-double-backup-sources.py b/contrib/planb-double-backup-sources.py index dd23232..049e694 100755 --- a/contrib/planb-double-backup-sources.py +++ b/contrib/planb-double-backup-sources.py @@ -5,6 +5,7 @@ # Right now: # - assuming you're running this as root # - remote has planb access +import os import sys from argparse import ArgumentParser @@ -62,8 +63,14 @@ def __iter__(self): def get_server_datasets(server): + ssh_opts = ['-oLogLevel=error'] + if os.environ.get('SSH_CONTROL_PATH'): + ssh_opts.extend([ + '-oControlMaster=no', + '-oControlPath={}'.format(os.environ['SSH_CONTROL_PATH']), + ]) return check_output( - ['ssh', server, '-oLogLevel=error', 'planb', 'blist', '--double'], + ['ssh'] + ssh_opts + [server, 'planb', 'blist', '--double'], text=True).splitlines()