87 lines
3.0 KiB
Bash
Executable File
87 lines
3.0 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Incremental replication of ALL Incus instances (incus copy --refresh;
|
|
# with ZFS on both ends only deltas travel, so stopped instances cost
|
|
# nothing after their first copy).
|
|
# https://linuxcontainers.org/incus/docs/main/howto/move_instances/
|
|
#
|
|
# Three shapes, per where it runs and where the replicas land:
|
|
# incus-copy.sh -d <dest-remote> [-m pull|push|relay] [-s <dest-pool>]
|
|
# push all local instances to a remote (runs on the source host)
|
|
# incus-copy.sh -r <src-remote> [-m pull|push|relay] [-s <dest-pool>]
|
|
# pull all of a remote's instances to the local host (runs on the
|
|
# backup host; the source only needs its API reachable)
|
|
# incus-copy.sh -p <target-project> -s <dest-pool>
|
|
# copy all local instances into another local project + pool
|
|
# (on-host backup onto a different disk)
|
|
#
|
|
# Snapshot history/retention is handled on the source instances
|
|
# (snapshots.schedule / snapshots.expiry); --refresh-exclude-older keeps
|
|
# refreshes from dragging expired snapshots around.
|
|
|
|
set -u
|
|
|
|
INCUS=/usr/bin/incus
|
|
LOCKFILE=/run/lock/incus-copy.lock
|
|
SRC=""
|
|
DEST=""
|
|
MODE=pull
|
|
POOL=""
|
|
PROJECT=""
|
|
|
|
usage() {
|
|
echo "Usage: $0 [-d <dest-remote> | -r <src-remote>] [-m pull|push|relay] [-s <dest-storage-pool>] [-p <dest-project>]" >&2
|
|
exit 2
|
|
}
|
|
|
|
while getopts d:r:m:s:p: flag; do
|
|
case "${flag}" in
|
|
d) DEST=${OPTARG};;
|
|
r) SRC=${OPTARG};;
|
|
m) MODE=${OPTARG};;
|
|
s) POOL=${OPTARG};;
|
|
p) PROJECT=${OPTARG};;
|
|
*) usage;;
|
|
esac
|
|
done
|
|
# need somewhere for replicas to go, and one side must stay local
|
|
[ -n "$DEST" ] || [ -n "$SRC" ] || [ -n "$PROJECT" ] || usage
|
|
[ -n "$DEST" ] && [ -n "$SRC" ] && usage
|
|
case "$MODE" in pull|push|relay) ;; *) usage;; esac
|
|
|
|
# refuse to overlap with a previous, still-running invocation
|
|
exec 9> "$LOCKFILE"
|
|
if ! flock -n 9; then
|
|
echo "another incus-copy run holds $LOCKFILE, aborting" >&2
|
|
exit 1
|
|
fi
|
|
|
|
INSTANCES=$($INCUS list ${SRC:+"${SRC}:"} -c n -f csv) || {
|
|
echo "cannot list instances${SRC:+ on ${SRC}:}, aborting" >&2
|
|
exit 1
|
|
}
|
|
|
|
RC=0
|
|
for CT in $INSTANCES; do
|
|
echo "[$(date '+%F %T')] copy ${SRC:+$SRC:}$CT -> ${DEST:+$DEST:}$CT${PROJECT:+ (project $PROJECT)}"
|
|
# --mode only applies to remote transfers; --quiet suppresses the
|
|
# \r-progress meter that turns log files into mangled one-liners
|
|
if $INCUS copy --quiet "${SRC:+$SRC:}$CT" "${DEST:+$DEST:}$CT" \
|
|
--refresh --refresh-exclude-older \
|
|
${SRC:+--mode "$MODE"} ${DEST:+--mode "$MODE"} \
|
|
${POOL:+--storage "$POOL"} \
|
|
${PROJECT:+--target-project "$PROJECT"}; then
|
|
# the replica inherits boot.autostart from the source and would
|
|
# start itself on the backup host after a reboot (or, for the
|
|
# local-project shape, fight the live instance for its static
|
|
# IP on the same bridge) — neutralize it
|
|
$INCUS config set ${PROJECT:+--project "$PROJECT"} \
|
|
"${DEST:+$DEST:}$CT" boot.autostart=false || RC=1
|
|
else
|
|
echo "[$(date '+%F %T')] FAILED: $CT" >&2
|
|
RC=1
|
|
fi
|
|
done
|
|
|
|
exit $RC
|