journalctl --since=now -qn2 -f -u "$unit_name" &
jr_pid=$!
# sleep 1 is too fast for x200
- sleep 2
+ sleep 3
$sdrun_prefix systemd-run $sdrun_args --unit "$unit_name" --wait --collect "$cmd" "$@" || ret=$?
# The sleep lets the journal output its last line
# before the prompt comes up.
m s nscd -i hosts
fi
f=/etc/resolv.conf
- echo $f:; ccat $f
- hr; s ss -lpn sport = 53
+ ll $f
+ m ccat $f
+ hr; m s ss -lpn sport = 53
if systemctl is-enabled dnsmasq &>/dev/null || [[ $(systemctl is-active dnsmasq ||:) != inactive ]]; then
# this will fail is dnsmasq is failed
hr; m ser status dnsmasq | cat || :
}
-# sl: ssh, but firsh rsync our bashrc and related files to a special
-# directory on the remote host if needed.
+#### sl: ssh wrapper, but maybe first rsync files that we configure and
+# always source our .bashrc on the remote.
+
+# TODO: this needs better documentation.
+
+# Usage: all the same args as ssh + a few below. Note, combining options
+# that have arguments with ones that dont, like -4oOption is not
+# supported.
+
+## Required setup
-# Some environment variables and files need to be setup for this to work
-# (mine are set at the beginning of this file)
+# Sorry these have no defaults, you can read my settings in this file.
# SL_FILES_DIR: Environment variable. Path to folder which should at
-# least have a .bashrc file or symlink. This dir will be rsynced to ~ on
-# remote hosts (top level symlinks are resolved) unless the host already
-# has a $SL_FILES_DIR/.bashrc. In that case, we assume it is a host you
-# control and sync files to separately and already has the ~/.bashrc you
-# want. The remote bash will also take its .inputrc config from this
-# folder (default of not existing is fine). Mine looks like this:
-# https://iankelling.org/git/?p=distro-setup;a=tree;f=sl/.iank
+# least have a .bashrc file or symlink to it. This dir will be rsynced
+# to ~ on remote hosts (top level symlinks are resolved) unless the host
+# already has a $SL_FILES_DIR/.bashrc. In that case, we assume it is a
+# host you control and sync files to separately and already has the
+# ~/.bashrc you want. The remote bash will also take its .inputrc config
+# from this folder (default of not existing is fine). Mine looks like
+# this: https://iankelling.org/git/?p=distro-setup;a=tree;f=sl/.iank
# SL_INFO_DIR: Environment variable. This folder stores info about what
# we detected on the remote system and when we last synced. It will be created
# remote system, you can use sl --rsync, or the function for that slr
# below.
-# SL_TEST_CMD: Env var. If set, we run this string on the remote host
-# the first time sl is run (or if we run slr), and then the result is
-# passed to SL_TEST_HOOK which gets run locally. It is meant to be used
-# to vary the files synced depending on the remote host. For example,
-# export SL_TEST_CMD=". /etc/os-release ; echo \${VERSION//[^a-zA-Z0-9]/}"
-# SL_TEST_HOOK: Env var. It is run as $SL_TEST_HOOK. This can set
-# $SL_FILES_DIR to vary the files synced.
+## Optional settings
-# SL_RSYNC_ARGS: Env var. String of arguments passed to rsync. For
-# example to exclude files within a directory. Note, excluded
-# files wont be deleted on rsync, you can add --delete-excluded
-# to the rsync command if that is desired.
+# --rsync Forget about any previous rsync we did. The most common case
+# is that the ssh target does not have files we previously rsynced for
+# various reasons. I have a wrapper for this called slr below.
+
+# SL_TEST_CMD / --sl-test-cmd CMD: Env var or cli option. If set, we run
+# this string on the remote host the first time sl is run (or if we run
+# slr). Its standard out is passed to SL_TEST_HOOK which gets run
+# locally. It is meant to be used to vary the files synced depending on
+# the remote host. For example, export SL_TEST_CMD=". /etc/os-release ;
+# echo \${VERSION//[^a-zA-Z0-9]/}". cli option overrides env var.
+
+# SL_TEST_HOOK / --sl-test-hook HOOK: Env var or cli option. See SL_TEST_CMD
+# above. It is run unquoted (with expansion). This can set $SL_FILES_DIR
+# to vary the files synced. cli option overrides env var.
+
+# SL_RSYNC_ARGS / --sl-rsync-args ARGS: Env var or cli option. String of
+# arguments passed to rsync. For example to exclude files within a
+# directory. Note, excluded files wont be deleted on rsync, you can add
+# --delete-excluded to the rsync command if that is desired. cli
+# overrides env var.
# SL_SSH_ARGS: Env var. Default arguments passed to ssh.
# .bashrc. This means the outer shell still ran the default .bashrc,
# but that is the best we can do.
- local now args remote dorsync haveinfo tmpa sshinfo tmp tmp2 type info_sec force_rsync \
+ local now args remote dorsync haveinfo tmpa sshinfo tmp tmp2 host_type info_sec force_rsync \
sync_dirname testcmd extra_info testbool files_sec sl_test_cmd sl_test_hook
declare -a args tmpa
return 1
fi
- dorsync=false
+ do_rsync=false
haveinfo=false
- tmpa=($SL_INFO_DIR/???????????"$remote")
+ tmpa=($SL_INFO_DIR/??????????-????-"$remote")
sshinfo=${tmpa[0]}
if [[ -e $sshinfo ]]; then
if $force_rsync; then
fi
if $haveinfo; then
tmp=${sshinfo[0]##*/}
- tmp2=${tmp::11}
- type=${tmp2: -1}
+ tmp2=${tmp:11} # skip 11 chars
+ host_type=${tmp2%%-*}
extra_info=$(cat $sshinfo)
else
# we test for string to know ssh succeeded
command ssh -v "${args[@]}" "$remote"
fi
if [[ $tmp == y* ]]; then
- type=a
+ # yes test result, no need to rsync this host.
+ host_type=skip
else
- dorsync=true
- type=b
+ do_rsync=true
+ host_type=sync
fi
extra_info="${tmp:1}"
fi
RSYNC_RSH="ssh ${args[*]}" $sl_test_hook "$extra_info" "$remote"
fi
- if $haveinfo && [[ $type == b ]]; then
+ if $haveinfo && [[ $host_type == sync ]]; then
info_sec=${tmp::10}
read -r files_sec _ < <(find -L $SL_FILES_DIR -printf "%T@ %p\n" | sort -nr || [[ $? == 141 || ${PIPESTATUS[0]} == 32 ]] )
files_sec=${files_sec%%.*}
if (( files_sec > info_sec )); then
- dorsync=true
+ do_rsync=true
rm -f $sshinfo
fi
fi
return 1
fi
- if $dorsync; then
+ if $do_rsync; then
RSYNC_RSH="ssh ${args[*]}" m rsync -rptL --delete $sl_rsync_args $SL_FILES_DIR "$remote":
fi
- if $dorsync || ! $haveinfo; then
- sshinfo=$SL_INFO_DIR/$EPOCHSECONDS$type"$remote"
+ if $do_rsync || ! $haveinfo; then
+ sshinfo=$SL_INFO_DIR/$EPOCHSECONDS-$host_type-"$remote"
[[ -e $SL_INFO_DIR ]] || mkdir -p $SL_INFO_DIR
printf "%s\n" "$extra_info" >$sshinfo
chmod 666 $sshinfo
fi
- if [[ $type == b ]]; then
+ if [[ $host_type == sync ]]; then
if (( ${#@} )); then
- # Theres a couple ways to pass arguments, im not sure whats best,
- # but relying on bash 4.4+ escape quoting seems most reliable.
+ # in the past, I used ${@@Q} passed to a bash subshell. Then
+ # later, I noticed it didn't handle spaces as normal ssh does, and
+ # didn't see any benefit to doing it that way (perhaps forgot what
+ # I originally saw in it).
command ssh "${args[@]}" "$remote" \
- LC_USEBASHRC=t bash -c '.\ '$sync_dirname'/.bashrc\;"\"\$@\""' bash ${@@Q}
+ LC_USEBASHRC=t . $sync_dirname/.bashrc\; "$@"
elif [[ ! -t 0 ]]; then
# This case is when commands are being piped to ssh.
# Normally, no bashrc gets sourced.
fi
else
if [[ -t 0 ]]; then
- LC_USEBASHRC=t command ssh "${args[@]}" "$remote" ${@@Q}
+ LC_USEBASHRC=t command ssh "${args[@]}" "$remote" "$@"
else
command ssh "${args[@]}" "$remote" LC_USEBASHRC=t bash
fi
}
# scp a script then ssh and run it.
-#
-# note on use cases: this can be useful for running a commands locally,
-# dump them into a script, then run remotely. Note, there is no way to
-# include an escaped ; in an ssh arg so that it works in the remote
-# command, but we can use eval to make it work. For example:
-#
-# ssh ahost eval cd /mnt/root/btrbk \; ls -1 a.\*
srun() {
scp $2 $1:/tmp
ssh $1 "/tmp/${2##*/}" "$(printf "%q\n" "${@:2}")"
[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
+
+# todo: try adding set -u
set -e; . /usr/local/lib/bash-bear; set +e
shopt -s nullglob
"$@" |& pee cat 'ts "%F %T" >>'$log_path
fi
}
-m() { if $verbose; then printf "$pre %s\n" "$*"; fi; "$@"; }
+m() {
+ if $dry_run; then
+ printf "$pre dry-run: %s\n" "$*"
+ return 0
+ fi
+ if $verbose; then
+ printf "$pre %s\n" "$*"
+ fi
+ "$@"
+}
e() { printf "$pre %s\n" "$*"; }
logq() {
done
if ! $pull_reexec && [[ $source ]] && $pulla && ! $force ; then
- ssh root@$source btrbk-run --check-installed
+ m ssh root@$source btrbk-run --check-installed
fi
#### end pre-checks #####
if ! $pull_reexec && [[ $source ]] && $pulla ; then
- tmpf=$(mktemp)
- m rsync -ra $source:/usr/local/bin/{mount-latest-subvol,check-subvol-stale} /usr/local/bin
- m rsync -ra $source:/usr/local/lib/bash-bear /usr/local/lib
- m rsync $source:/usr/local/bin/btrbk-run $tmpf
- if ! diff -q $tmpf ${BASH_SOURCE[0]}; then
- e "found different version on host $source. reexecing"
- rsync -aSAX --chmod=755 --chown=root:root $tmpf /usr/local/bin/btrbk-run
- m /usr/local/bin/btrbk-run --pull-reexec "${orig_args[@]}"
- mexit 0
+ if $dry_run; then
+ e due to dry run, skipping check for newer files on source host
+ else
+ tmpf=$(mktemp)
+ m rsync -ra $source:/usr/local/bin/{mount-latest-subvol,check-subvol-stale} /usr/local/bin
+ m rsync -ra $source:/usr/local/lib/bash-bear /usr/local/lib
+ m rsync $source:/usr/local/bin/btrbk-run $tmpf
+ if ! diff -q $tmpf ${BASH_SOURCE[0]}; then
+ e "found different version on host $source. reexecing"
+ m rsync -aSAX --chmod=755 --chown=root:root $tmpf /usr/local/bin/btrbk-run
+ m /usr/local/bin/btrbk-run --pull-reexec "${orig_args[@]}"
+ mexit 0
+ fi
fi
fi
local_zone=$(date +%z)
if [[ $source ]]; then
- if $fast; then
- zone=$local_zone
- else
- if ! ssh_info=$(ssh root@$source 'hostname && date +%z'); then
- if $conf_only; then
- echo "$0: warning: failed to ssh to root@$source"
- else
- die failed to ssh to root@$source
- fi
- fi
- { read -r source_hostname; read -r zone; } <<<"$ssh_info"
- if [[ $zone != "$local_zone" ]]; then
- die "error: dont confuse yourself with multiple time zones. $h has different timezone than localhost"
+ if ! ssh_info=$(ssh root@$source 'hostname && date +%z'); then
+ if $conf_only; then
+ echo "$0: warning: failed to ssh to root@$source"
+ else
+ die failed to ssh to root@$source
fi
fi
+ { read -r source_hostname; read -r zone; } <<<"$ssh_info"
+ if [[ ! $source_hostname ]]; then
+ die "error: failed to get source hostname. source=$source"
+ fi
+ if [[ $zone != "$local_zone" ]]; then
+ die "error: dont confuse yourself with multiple time zones. $h has different timezone than localhost"
+ fi
else
sshable=()
if $dry_run; then
m btrbk -c /etc/btrbk$conf_suf.conf -v -n $cmd_arg
- mexit 0
else
logq btrbk -c /etc/btrbk$conf_suf.conf $preserve_arg $verbose_arg $progress_arg $cmd_arg
fi
dirs+=(${x%.hosts})
fi
done
- m rsync -aSAXPH --specials --devices --delete --relative ${dirs[@]} root@$tg:/
+ d rsync -aSAXPH --specials --devices --delete --relative ${dirs[@]} root@$tg:/
;;
esac
done
d mount-latest-subvol "${subvols[@]}"
else
for tg in ${targets[@]}; do
- d /a/exe/mount-latest-remote "$tg" "${subvols[@]}" || ret=$?
+ m /a/exe/mount-latest-remote "$tg" "${subvols[@]}" || ret=$?
done
fi
## run extra commands on targets
+# shellcheck disable=SC2116 # intentional expansion
local_snaps=$(echo $snap_list_glob)
if [[ $ret == 0 ]]; then
for tg in ${targets[@]}; do
+ # todo: get this in ssh_info, less sshing is faster.
h=$(ssh $tg hostname)
remote_snaps=$(ssh root@$tg "shopt -s nullglob; echo $snap_list_glob")
# a check like this will catch the situation we aim to prevent by running purge
printf "%s\n" "$local_snaps" |tr ' ' '\n' >$localtmp
remotetmp=$(mktemp)
printf "%s\n" "$remote_snaps" |tr ' ' '\n' >$remotetmp
- e "error: for $tg, remote and local snaps are different."
- e "local: $local_snaps"
- e "tg:$tg = $remote_snaps"
- e "diff -u local remote"
+
+ if $dry_run; then
+ e "dry run, expected error:"
+ fi
+ cat <<"EOF" | sed "s/^/$pre /"
+error: for $tg, remote and local snaps are different.
+local: $local_snaps
+tg:$tg = $remote_snaps
+diff -u local remote:
+EOF
+
diff -u $localtmp $remotetmp
rm $localtmp $remotetmp
ret=1
d ssh root@$tg 'btrbk-spread-wrap &>/dev/null </dev/null &'
fi
cmd=/usr/local/bin/mail-backup-clean
- ssh root@$tg "if test -x $cmd; then $cmd; fi"
+ m ssh root@$tg "if test -x $cmd; then $cmd; fi"
done
fi
## compress and copy logs in case a machine dies, we still have its logs.
-bzip2 $log_path
+m bzip2 $log_path
for tg in ${targets[@]}; do
# we generated, so its our hostname.
m rsync --mkpath -a -f"- */" -f"+ *" /var/log/btrbk/ root@$tg:/var/log/btrbk/$HOSTNAME
done
if [[ $source ]]; then
+ if [[ ! $source_hostname ]]; then
+ die unexpectedly not set: source_hostname
+ fi
m rsync --mkpath -a -f"- */" -f"+ *" $source:/var/log/btrbk/ /var/log/btrbk/$source_hostname
fi