#!/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 [-m pull|push|relay] [-s ] # push all local instances to a remote (runs on the source host) # incus-copy.sh -r [-m pull|push|relay] [-s ] # 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 -s # 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 | -r ] [-m pull|push|relay] [-s ] [-p ]" >&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