From 0cc121eb893ff98bd2350938cc57b15080c5b0e0 Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Tue, 27 Apr 2021 12:48:10 +0200 Subject: [PATCH 1/5] remove hardcoded partitions --- bash/dup-live-disk.sh | 423 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 411 insertions(+), 12 deletions(-) mode change 100644 => 100755 bash/dup-live-disk.sh diff --git a/bash/dup-live-disk.sh b/bash/dup-live-disk.sh old mode 100644 new mode 100755 index 7cc9dd9..f8559e2 --- a/bash/dup-live-disk.sh +++ b/bash/dup-live-disk.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# dup-live-disk.sh - duplicate live system partitions on new disk. +# dup-live-disk.sh - duplicate (possibly live) system partitions # # (C) Bruno Raoult ("br"), 2007-2021 # Licensed under the Mozilla Public License (MPL) version 2.0. @@ -11,18 +11,389 @@ # # SPDX-License-Identifier: MPL-2.0 # -# This script will not work for all situations, I strongly suggest you -# don't use it if you don't *fully* understand it. +#%MAN_BEGIN% +# NAME +# dup-live-disk.sh - duplicate (possibly live) system partitions +# +# SYNOPSIS +# dup-live-disk.sh [OPTIONS] [SRC] DST +# +# DESCRIPTION +# Duplicate SRC disk partitions to same structured DST disk ones. +# if SRC is omitted, tue running system disk (where root partition resides) will +# be used. +# Both SRC and DST *must* have same partition base LABELs - as 'LABEL' field for +# lsblk(1) and blkid(1), with an ending character (unique per disk) to +# differentiate them. +# For example, if partitions base labels are 'root', 'export', and 'swap', +# SRC disk the ending character '1' and DST disk the character '2', SRC +# partitions must be 'root1', 'export1, and 'swap1', and DST partitions must be +# 'root2', 'export2, and 'swap2'. +# +# OPTIONS +# -d, -n, --dry-run, --no +# Dry-run: nothing will be written to disk. +# +# -h, --help +# Display short help and exit. +# +# -m, --man +# Display a "man-like" description and exit. +# +# -r, --root=PARTNUM +# Mandatory if SRC is provided, forbidden otherwise. +# PARTNUM is root partition number on SRC disk. +# +# -y, --yes +# Do not ask for actions confirmation. Default is to display next +# action and ask user to [y] do it, [q] quit, [s] skip. +# +# EXAMPLES +# Copy sda to sdb, root partition is partition (sda1/sdb1) +# $ sudo dup-live-disk.sh --root 1 sda sdb +# +# Copy live system (where / is mounted) to sdb +# $ sudo dup-live-disk.sh sdb +# +# BUGS +# Cannot generate grub with a separate /boot partition. +# This script will not work for all situations, I strongly suggest you +# don't use it if you don't *fully* understand it. +# +# TODO +# Write about autofs. +#%MAN_END% -# dest device (used for grub) -DSTDISK=/dev/sdb +# command line +SCRIPT="${0}" +CMD="${0##*/}" -# partitions suffixes, for source and destination partitions. -# For example, if we want to copy XX partition, source partition will be -# /mnt/XX${SRCNUM}, and destination will be /mnt/XX${DSTNUM} -SRCNUM=2 -DSTNUM=1 +# valid filesystems +# shellcheck disable=2034 +VALIDFS=(ext3 ext4 btrfs vfat reiserfs) +function man { + sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!p}' "$SCRIPT" | sed -E 's/^# ?//' +} + +function usage { + cat <<_EOF +Usage: $CMD [OPTIONS] [SRC] DST +Duplicate SRC (or live system) disk partitions to DST disk partitions. + +Options: + -d, -n, --dry-run, --no dry-run: nothing will be written to disk + -h, --help this help + -m, --man display a "man-like" page and exit + -r, --root=PARTNUM root partition number on SRC device + mandatory if and only if SRC is provided + -y, --yes DANGER ! perform all actions without user + confirmation + +SRC and DST have strong constraints on partitions schemes and naming. +Type '$CMD --man" for more details" +_EOF + exit 0 +} + +# log function +# parameters: +# -l, -s: long, or short prefix (default: none). Last one is used. +# -t: timestamp +# -n: no newline +function log { + local timestr="" prefix="" opt=y newline=y + while [[ $opt = y ]]; do + case $1 in + -l) prefix=$(printf "*%.s" {1..30});; + -s) prefix=$(printf "*%.s" {1..5});; + -n) newline=n;; + -t) timestr=$(date "+%F %T%z - ");; + *) opt=n;; + esac + [[ $opt = y ]] && shift + done + [[ $prefix != "" ]] && printf "%s " "$prefix" + printf "%s" "$timestr" + printf "$@" + [[ $newline = y ]] && printf "\n" + return 0 +} + +# prints out and run a command. +function echorun { + log "%s" "$@" + "$@" + return $? +} + +function error_handler { + local ERROR=$2 + log "FATAL: Error line $1, exit code $2. Aborting." + exit "$ERROR" +} +trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM + +function check_block_device { + local devtype="$1" + local mode="$2" + local dev="$3" + + if [[ ! -b "$dev" ]]; then + log "$CMD: $devtype '$dev' is not a block device." >&2 + exit 1 + fi + if [[ ! -r "$dev" ]]; then + log "$CMD: $devtype '$dev' is not readable." >&2 + exit 1 + fi + if [[ $mode = "w" && ! -w "$dev" ]]; then + log "$CMD: $devtype '$dev' is not writable." >&2 + exit 1 + fi + return 0 +} + +# check if $1 is in array $2 ($2 is by reference) +function in_array { + local elt=$1 i + local -n arr=$2 + for ((i=0; i<${#arr[@]}; ++i)); do + [[ ${arr[$i]} == "$elt" ]] && return 0 + done + return 1 +} + +# source and destination devices, root partition +SRC="" +DST="" +SRCROOT="" +ROOTPARTNUM="" +DOIT=manual + +# short and long options +SOPTS="dnhmr:y" +LOPTS="dry-run,no,help,man,root:,yes" + +if ! TMP=$(getopt -o "$SOPTS" -l "$LOPTS" -n "$CMD" -- "$@"); then + log "Use '$CMD --help' or '$CMD --man' for help." + exit 1 +fi +# if (( $? > 1 )); then +# echo 'Terminating...' >&2 +# exit 1 +# fi + +eval set -- "$TMP" +unset TMP + +while true; do + case "$1" in + '-d'|'-n'|'--dry-run'|'--no') + DOIT=no + shift + continue + ;; + '-h'|'--help') + usage + exit 0 + ;; + '-m'|'--man') + man + exit 0 + ;; + '-r'|'--root') + ROOTPARTNUM="$2" + if ! [[ "$ROOTPARTNUM" =~ ^[[:digit:]]+$ ]]; then + log "$CMD: $ROOTPARTNUM must be a partition number." >&2 + exit 1 + fi + shift 2 + continue + ;; + '-y'|'--yes') + DOIT=yes + shift + continue + ;; + '--') + shift + break + ;; + *) + usage + log 'Internal error!' >&2 + exit 1 + ;; + esac +done + + +case "$#" in + 1) + if [[ -n "$ROOTPARTNUM" ]]; then + log "$CMD: cannot have --root option for live system." >&2 + log "Use '$CMD --help' or '$CMD --man' for help." >&2 + exit 1 + fi + # guess root partition disk name + SRCROOT=$(findmnt -no SOURCE -M /) + ROOTPARTNUM=${SRCROOT: -1} + SRC="/dev/"$(lsblk -no pkname "$SRCROOT") + DST="/dev/$1" + ;; + 2) + if [[ -z "$ROOTPARTNUM" ]]; then + log "$CMD: missing --root option for non live system." >&2 + log "Use '$CMD --help' or '$CMD --man' for help." >&2 + exit 1 + fi + SRC="/dev/$1" + SRCROOT="$SRC$ROOTPARTNUM" + DST="/dev/$2" + ;; + *) + usage >&2 + exit 1 +esac + +# check SRC and DST are different, find out their characteristics +if [[ "$SRC" = "$DST" ]]; then + log "%s: Fatal: destination disk (%s) cannot be source.\n" "$CMD" "$SRC" + log "Use '%s --help' or '%s --man' for help." "$CMD" "$CMD" + exit 1 +fi +check_block_device "source disk" r "$SRC" +check_block_device "destination disk" w "$DST" +check_block_device "source root partition" r "$SRCROOT" + +SRCROOTLABEL=$(lsblk -no label "$SRCROOT") +SRCCHAR=${SRCROOTLABEL: -1} +ROOTLABEL=${SRCROOTLABEL:0:-1} +# find out all partitions labels on SRC disk... +# shellcheck disable=SC2207 +declare -a SRCLABELS=($(lsblk -lno LABEL "$SRC")) +# shellcheck disable=SC2206 +declare -a LABELS=(${SRCLABELS[@]%?}) + +#log "SRCLABELS=${#SRCLABELS[@]} - ${SRCLABELS[*]}" +#log "LABELS=${#LABELS[@]} - ${LABELS[*]}" + + +declare -a SRCDEVS SRCFS SRCDOIT +# ... and corresponding partition device and fstype +for ((i=0; i<${#LABELS[@]}; ++i)); do + TMP="${LABELS[$i]}$SRCCHAR" + TMP="${SRCLABELS[$i]}" + TMPDEV=$(findfs LABEL="$TMP") + TMPFS=$(lsblk -no fstype "$TMPDEV") + log "found LABEL=$TMP DEV=$TMPDEV FSTYPE=$TMPFS" + SRCDEVS[$i]="$TMPDEV" + SRCFS[$i]="$TMPFS" + SRCDOIT[$i]=n + in_array "$TMPFS" VALIDFS && SRCDOIT[$i]=y + unset TMP TMPDEV TMPFS +done + + +DSTROOT="$DST$ROOTPARTNUM" +check_block_device "destination root partition" w "$DSTROOT" +DSTROOTLABEL=$(lsblk -no label "$DSTROOT") +DSTCHAR=${DSTROOTLABEL: -1} + +# check DSTROOTLABEL is compatible with ROOTLABEL +if [[ "$DSTROOTLABEL" != "$ROOTLABEL$DSTCHAR" ]]; then + log "%s: Fatal: %s != %s%s." "$CMD" "$DSTROOTLABEL" "$ROOTLABEL" "$DSTCHAR" + exit 1 +fi + +# log "SRC=%s DST=%s" "$SRC" "$DST" +# log "SRCROOT=%s DSTROOT=%s" "$SRCROOT" "$DSTROOT" +# log "ROOTLABEL=$ROOTLABEL" +# log "SRCROOTLABEL=%s DSTROOTLABEL=%s" "$SRCROOTLABEL" "$DSTROOTLABEL" +# log "SRCCHAR=%s DSTCHAR=%s" "$SRCCHAR" "$DSTCHAR" +# log "DOIT=%s\n" "$DOIT" + +declare -a DSTLABELS DSTDEVS DSTFS DSTDOIT +# Do the same for correponding DST partitions labels, device, and fstype +for ((i=0; i<${#LABELS[@]}; ++i)); do + TMP="${LABELS[$i]}$DSTCHAR" + log -n "Looking for [%s] label... " "$TMP" + if ! TMPDEV=$(findfs LABEL="$TMP"); then + log "not found." + exit 1 + fi + TMPDISK=${TMPDEV%?} + log -n "DEV=%s... DISK=%s..." "$TMPDEV" "$TMPDISK" + if [[ "$TMPDISK" != "$DST" ]]; then + log "wrong disk (%s != %s)" "$TMPDISK" "$DST" + exit 1 + fi + TMPFS=$(lsblk -no fstype "$TMPDEV") + log "FSTYPE=%s" "$TMPFS" + DSTLABELS[$i]="$TMP" + DSTDEVS[$i]="$TMPDEV" + DSTFS[$i]="$TMPFS" + DSTDOIT[$i]=n + in_array "$TMPFS" VALIDFS && DSTDOIT[$i]=y + unset TMP TMPDEV TMPFS +done +echo ZOB "${SRCDEVS[0]}" "${DSTDEVS[0]}" +for ((i=0; i<${#LABELS[@]}; ++i)); do + log -n "%s %s " "${SRCDEVS[$i]}" "${DSTDEVS[$i]}" + log -n "%s %s " "${SRCLABELS[$i]}" "${DSTLABELS[$i]}" + log -n "%s %s " "${SRCFS[$i]}" "${DSTFS[$i]}" + log "%s %s" "${SRCDOIT[$i]}" "${DSTDOIT[$i]}" + echo +done | column -N DEV1,DEV2,LABEL1,LABEL2,FS1,FS2,SDOIT,DDOIT -t -o " | " + +RSYNCOPTS="-axH --delete --delete-excluded" +FILTER=--filter="dir-merge .rsync-disk-copy" +# copy loop +for ((i=0; i<${#LABELS[@]}; ++i)); do + if [[ "${SRCDOIT[$i]}" != y ]] || [[ "${DSTDOIT[$i]}" != y ]]; then + log "skipping label %s" "${LABELS[$i]}" + continue + fi + SRCPART=/mnt/${SRCLABELS[$i]}/ + DSTPART=/mnt/${DSTLABELS[$i]} + + log -n "%s -> %s : " "$SRCPART" "$DSTPART" + #log "\t%s %s %s %s %s" rsync "${RSYNCOPTS}" "$FILTER" "$SRCPART" "$DSTPART" + skip=y + case "$DOIT" in + yes) + skip=n + ;; + no) + log "skipping (dry run)." + ;; + manual) + log -n "proceed ? [y/N/q] " + read -r key + case "$key" in + y|Y) + log "copying..." + skip=n + ;; + q|Q) + log "aborting..." + exit 0 + ;; + n|N|*) + log "skipping..." + ;; + esac + esac + if [[ "$skip" == n ]]; then + # shellcheck disable=SC2086 + echorun rsync "$FILTER" ${RSYNCOPTS} "$SRCPART" "$DSTPART" + fi + log "" +done + + +exit 0 # array of partitions to copy TO_COPY=(root export EFI) @@ -42,8 +413,6 @@ GRUB_ROOT=/mnt/root${DSTNUM} GRUB_DEV=/dev/$(lsblk -no pkname /dev/disk/by-label/root${DSTNUM}) # we will use ".rsync-disk-copy" files to exclude files/dirs -RSYNCOPTS="-axH --delete --delete-excluded" -FILTER=--filter="dir-merge .rsync-disk-copy" # stop what could be problematic (databases, etc...) systemctl stop mysql @@ -71,3 +440,33 @@ chroot ${GRUB_ROOT} grub-install ${GRUB_DEV} # restart stopped process (db, etc...) systemctl start mysql + +exit 0 +###############declare -a DSTLABELS_CHECK=(${SRCLABELS[@]/%?/$DSTCHAR}) + +# find corresponding LABELS on DEST disk +# declare -a LABELS=(${SRCLABELS[@]/%?/}) +# for ((i=0; i<${#SRCLABELS[@]}; ++i)); do +# TMP="${LABELS[$i]}$DSTCHAR" +# echo -n "looking for partition 'LABEL=$TMP'... " +# if ! DSTFS=$(findfs LABEL="$TMP"); then +# echo "not found." +# exit 1 +# fi +# echo "$DSTFS" +# DSTLABELS[$i]="$TMP" + +# done + +# #DSTLABELS=($(lsblk -lno LABEL "$DST")) +# # check all partitions types +# for ((i=0; i<${#SRCLABELS[@]}; ++1)); do +# check_block_device "source ${LABELS[$i]} partition" r "${SRCLABELS[$i]}" +# #check_block_device "destination ${LABELS[$i]} partition" w "${DSTLABELS[$i]}" + +# done + +# echo "DSTLABELS=${#DSTLABELS[@]} - ${DSTLABELS[*]}" + + +exit 0 From 9b3fb864cdade49ef29c8b077d8e1d52754deefd Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Wed, 28 Apr 2021 10:47:11 +0200 Subject: [PATCH 2/5] Add grub management / remove old dead code. --- bash/dup-live-disk.sh | 234 +++++++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/bash/dup-live-disk.sh b/bash/dup-live-disk.sh index f8559e2..2f2be49 100755 --- a/bash/dup-live-disk.sh +++ b/bash/dup-live-disk.sh @@ -20,26 +20,34 @@ # # DESCRIPTION # Duplicate SRC disk partitions to same structured DST disk ones. -# if SRC is omitted, tue running system disk (where root partition resides) will -# be used. -# Both SRC and DST *must* have same partition base LABELs - as 'LABEL' field for -# lsblk(1) and blkid(1), with an ending character (unique per disk) to -# differentiate them. +# if SRC is omitted, tue running system disk (where root partition +# resides) will be used. +# Both SRC and DST *must* have same partition base LABELs - as 'LABEL' +# field for lsblk(1) and blkid(1), with an ending character (unique per +# disk) to differentiate them. # For example, if partitions base labels are 'root', 'export', and 'swap', # SRC disk the ending character '1' and DST disk the character '2', SRC -# partitions must be 'root1', 'export1, and 'swap1', and DST partitions must be -# 'root2', 'export2, and 'swap2'. +# partitions must be 'root1', 'export1, and 'swap1', and DST partitions +# must be 'root2', 'export2, and 'swap2'. # # OPTIONS # -d, -n, --dry-run, --no # Dry-run: nothing will be written to disk. # +# -g, --grub +# Install grub on destination disk. +# Warning: Only works if root partition contains all necessary for +# grub: /boot, /usr, etc... +# # -h, --help # Display short help and exit. # # -m, --man # Display a "man-like" description and exit. # +# --mariadb +# Stop mysql/mariadb before effective copies, restart after. +# # -r, --root=PARTNUM # Mandatory if SRC is provided, forbidden otherwise. # PARTNUM is root partition number on SRC disk. @@ -61,16 +69,18 @@ # don't use it if you don't *fully* understand it. # # TODO -# Write about autofs. +# Write about autofs configuration. +# Log levels +# Separate dry-run and copies/mysql/grub #%MAN_END% # command line -SCRIPT="${0}" +SCRIPT="$0" CMD="${0##*/}" # valid filesystems # shellcheck disable=2034 -VALIDFS=(ext3 ext4 btrfs vfat reiserfs) +VALIDFS=(ext3 ext4 btrfs vfat reiserfs xfs zfs) function man { sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!p}' "$SCRIPT" | sed -E 's/^# ?//' @@ -83,8 +93,11 @@ Duplicate SRC (or live system) disk partitions to DST disk partitions. Options: -d, -n, --dry-run, --no dry-run: nothing will be written to disk + -g, --grub install grub on destination disk -h, --help this help -m, --man display a "man-like" page and exit + --mariadb stop and restart mysql/mariadb server before and + after copies -r, --root=PARTNUM root partition number on SRC device mandatory if and only if SRC is provided -y, --yes DANGER ! perform all actions without user @@ -96,6 +109,25 @@ _EOF exit 0 } +# mariadb start/stop +function mariadb_maybe_stop { + if [[ $MARIADB == yes ]] && systemctl is-active --quiet mysql; then + log -n "stopping mariadb/mysql... " + systemctl stop mariadb + # bug if script stops here + MARIADBSTOPPED=yes + log "done." + fi +} +function mariadb_maybe_start { + if [[ $MARIADB == yes && $MARIADBSTOPPED == yes ]]; then + log -n "restarting mariadb/mysql... " + systemctl start mariadb + MARIADBSTOPPED=no + log "done." + fi +} + # log function # parameters: # -l, -s: long, or short prefix (default: none). Last one is used. @@ -115,6 +147,7 @@ function log { done [[ $prefix != "" ]] && printf "%s " "$prefix" printf "%s" "$timestr" + # shellcheck disable=SC2059 printf "$@" [[ $newline = y ]] && printf "\n" return 0 @@ -134,21 +167,33 @@ function error_handler { } trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM +function exit_handler { + log "exit handler (at line $1)" + mariadb_maybe_start + if [[ -v DSTMNT ]]; then + umount "$DSTMNT/dev" + umount "$DSTMNT/proc" + umount "$DSTMNT/sys" + fi + +} +trap 'exit_handler $LINENO' EXIT + function check_block_device { local devtype="$1" local mode="$2" local dev="$3" if [[ ! -b "$dev" ]]; then - log "$CMD: $devtype '$dev' is not a block device." >&2 + log "$CMD: $devtype '$dev' is not a block device." exit 1 fi if [[ ! -r "$dev" ]]; then - log "$CMD: $devtype '$dev' is not readable." >&2 + log "$CMD: $devtype '$dev' is not readable." exit 1 fi if [[ $mode = "w" && ! -w "$dev" ]]; then - log "$CMD: $devtype '$dev' is not writable." >&2 + log "$CMD: $devtype '$dev' is not writable." exit 1 fi return 0 @@ -164,25 +209,47 @@ function in_array { return 1 } +# get y/n/q user input +function yesno { + local input + while true; do + printf "%s " "$1" + read -r input + case "$input" in + y|Y) + return 0 + ;; + q|Q) + log "aborting..." + exit 0 + ;; + n|N) + return 1 + ;; + *) + printf "invalid answer. " + esac + done +} + # source and destination devices, root partition SRC="" DST="" SRCROOT="" ROOTPARTNUM="" DOIT=manual +MARIADB=no +MARIADBSTOPPED=no +GRUBINSTALL=no # short and long options -SOPTS="dnhmr:y" -LOPTS="dry-run,no,help,man,root:,yes" +SOPTS="dnghmr:y" +LOPTS="dry-run,no,grub,help,man,mariadb,root:,yes" if ! TMP=$(getopt -o "$SOPTS" -l "$LOPTS" -n "$CMD" -- "$@"); then log "Use '$CMD --help' or '$CMD --man' for help." exit 1 fi -# if (( $? > 1 )); then -# echo 'Terminating...' >&2 -# exit 1 -# fi eval set -- "$TMP" unset TMP @@ -194,6 +261,10 @@ while true; do shift continue ;; + '-g'|'--grub') + GRUBINSTALL=yes + shift + ;; '-h'|'--help') usage exit 0 @@ -202,10 +273,14 @@ while true; do man exit 0 ;; + '--mariadb') + MARIADB=yes + shift + ;; '-r'|'--root') ROOTPARTNUM="$2" if ! [[ "$ROOTPARTNUM" =~ ^[[:digit:]]+$ ]]; then - log "$CMD: $ROOTPARTNUM must be a partition number." >&2 + log "$CMD: $ROOTPARTNUM must be a partition number." exit 1 fi shift 2 @@ -222,7 +297,7 @@ while true; do ;; *) usage - log 'Internal error!' >&2 + log 'Internal error!' exit 1 ;; esac @@ -232,8 +307,8 @@ done case "$#" in 1) if [[ -n "$ROOTPARTNUM" ]]; then - log "$CMD: cannot have --root option for live system." >&2 - log "Use '$CMD --help' or '$CMD --man' for help." >&2 + log "$CMD: cannot have --root option for live system." + log "Use '$CMD --help' or '$CMD --man' for help." exit 1 fi # guess root partition disk name @@ -244,8 +319,8 @@ case "$#" in ;; 2) if [[ -z "$ROOTPARTNUM" ]]; then - log "$CMD: missing --root option for non live system." >&2 - log "Use '$CMD --help' or '$CMD --man' for help." >&2 + log "$CMD: missing --root option for non live system." + log "Use '$CMD --help' or '$CMD --man' for help." exit 1 fi SRC="/dev/$1" @@ -253,7 +328,7 @@ case "$#" in DST="/dev/$2" ;; *) - usage >&2 + usage exit 1 esac @@ -295,7 +370,6 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do unset TMP TMPDEV TMPFS done - DSTROOT="$DST$ROOTPARTNUM" check_block_device "destination root partition" w "$DSTROOT" DSTROOTLABEL=$(lsblk -no label "$DSTROOT") @@ -338,14 +412,15 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do in_array "$TMPFS" VALIDFS && DSTDOIT[$i]=y unset TMP TMPDEV TMPFS done -echo ZOB "${SRCDEVS[0]}" "${DSTDEVS[0]}" + for ((i=0; i<${#LABELS[@]}; ++i)); do log -n "%s %s " "${SRCDEVS[$i]}" "${DSTDEVS[$i]}" log -n "%s %s " "${SRCLABELS[$i]}" "${DSTLABELS[$i]}" log -n "%s %s " "${SRCFS[$i]}" "${DSTFS[$i]}" - log "%s %s" "${SRCDOIT[$i]}" "${DSTDOIT[$i]}" + log -n "%s %s " "${SRCDOIT[$i]}" "${DSTDOIT[$i]}" + [[ "$DSTROOTLABEL" == "${DSTLABELS[$i]}" ]] && log "*" echo -done | column -N DEV1,DEV2,LABEL1,LABEL2,FS1,FS2,SDOIT,DDOIT -t -o " | " +done | column -N DEV1,DEV2,LABEL1,LABEL2,FS1,FS2,SDOIT,DDOIT,ROOT -t -o " | " RSYNCOPTS="-axH --delete --delete-excluded" FILTER=--filter="dir-merge .rsync-disk-copy" @@ -369,104 +444,29 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do log "skipping (dry run)." ;; manual) - log -n "proceed ? [y/N/q] " - read -r key - case "$key" in - y|Y) - log "copying..." - skip=n - ;; - q|Q) - log "aborting..." - exit 0 - ;; - n|N|*) - log "skipping..." - ;; - esac + yesno "proceed ? [y/n/q]" && skip=n + ;; esac if [[ "$skip" == n ]]; then # shellcheck disable=SC2086 + mariadb_maybe_stop echorun rsync "$FILTER" ${RSYNCOPTS} "$SRCPART" "$DSTPART" fi log "" done - -exit 0 -# array of partitions to copy -TO_COPY=(root export EFI) - -# An alternative to SRCNUM, DSTNUM, and TO_COPY variables would be to have -# an array containing src and destination partitions: -# (partsrc1 partdst1 partsrc2 partdst2 etc...) -# example: -# TO_COPY=(root2 root1 export2 export1) -# declare -i i -# for ((i=0; i<${#TO_COPY[@]}; i+=2)); do -# SRC=${#TO_COPY[$i]} -# DST=${#TO_COPY[$i + 1]} -# etc... - -# where we will configure/install grub: mount point, device -GRUB_ROOT=/mnt/root${DSTNUM} -GRUB_DEV=/dev/$(lsblk -no pkname /dev/disk/by-label/root${DSTNUM}) - -# we will use ".rsync-disk-copy" files to exclude files/dirs - -# stop what could be problematic (databases, etc...) -systemctl stop mysql - -# partitions copy -for part in ${TO_COPY[@]}; do - SRCPART=/mnt/${part}${SRCNUM}/ - DSTPART=/mnt/${part}${DSTNUM} - - echo copy from $SRCPART to $DSTPART - echo -n "press a key to continue..." - read -r key - echo rsync ${RSYNCOPTS} "$FILTER" "$SRCPART" "$DSTPART" - rsync ${RSYNCOPTS} "$FILTER" "$SRCPART" "$DSTPART" -done - # grub install # mount virtual devices -mount -o bind /sys ${GRUB_ROOT}/sys -mount -o bind /proc ${GRUB_ROOT}/proc -mount -o bind /dev ${GRUB_ROOT}/dev +if [[ $GRUBINSTALL == yes ]]; then + log "installing grub on $DST..." + DSTMNT="/mnt/$DSTROOTLABEL" + mount -o bind /sys "$DSTMNT/sys" + mount -o bind /proc "$DSTMNT/proc" + mount -o bind /dev "$DSTMNT/dev" -chroot ${GRUB_ROOT} update-grub -chroot ${GRUB_ROOT} grub-install ${GRUB_DEV} - -# restart stopped process (db, etc...) -systemctl start mysql - -exit 0 -###############declare -a DSTLABELS_CHECK=(${SRCLABELS[@]/%?/$DSTCHAR}) - -# find corresponding LABELS on DEST disk -# declare -a LABELS=(${SRCLABELS[@]/%?/}) -# for ((i=0; i<${#SRCLABELS[@]}; ++i)); do -# TMP="${LABELS[$i]}$DSTCHAR" -# echo -n "looking for partition 'LABEL=$TMP'... " -# if ! DSTFS=$(findfs LABEL="$TMP"); then -# echo "not found." -# exit 1 -# fi -# echo "$DSTFS" -# DSTLABELS[$i]="$TMP" - -# done - -# #DSTLABELS=($(lsblk -lno LABEL "$DST")) -# # check all partitions types -# for ((i=0; i<${#SRCLABELS[@]}; ++1)); do -# check_block_device "source ${LABELS[$i]} partition" r "${SRCLABELS[$i]}" -# #check_block_device "destination ${LABELS[$i]} partition" w "${DSTLABELS[$i]}" - -# done - -# echo "DSTLABELS=${#DSTLABELS[@]} - ${DSTLABELS[*]}" + chroot "$DSTMNT" update-grub + chroot "$DSTMNT" grub-install "$DST" +fi exit 0 From edbdab3a208a2f2f12615e05f0e9911ae0611156 Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Wed, 28 Apr 2021 14:55:50 +0200 Subject: [PATCH 3/5] better dry-run handling, separate "--copy" option. --- bash/dup-live-disk.sh | 174 ++++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 84 deletions(-) diff --git a/bash/dup-live-disk.sh b/bash/dup-live-disk.sh index 2f2be49..899c395 100755 --- a/bash/dup-live-disk.sh +++ b/bash/dup-live-disk.sh @@ -20,7 +20,7 @@ # # DESCRIPTION # Duplicate SRC disk partitions to same structured DST disk ones. -# if SRC is omitted, tue running system disk (where root partition +# if SRC is omitted, the running system disk (where root partition # resides) will be used. # Both SRC and DST *must* have same partition base LABELs - as 'LABEL' # field for lsblk(1) and blkid(1), with an ending character (unique per @@ -31,8 +31,17 @@ # must be 'root2', 'export2, and 'swap2'. # # OPTIONS -# -d, -n, --dry-run, --no -# Dry-run: nothing will be written to disk. +# -a, --autofs=DIR +# Use DIR as autofs "LABEL-based" automount. See AUTOFS below. Default +# is /mnt. +# +# -c, --copy=ACTION +# ACTION can be 'yes' (all eligible partitions will be copied), 'no' +# (no partition will be copied), or 'ask' (will ask for all eligible +# partitions). Default is 'no'. +# +# -d, --dry-run +# Dry-run: nothing will be really be done. # # -g, --grub # Install grub on destination disk. @@ -52,10 +61,6 @@ # Mandatory if SRC is provided, forbidden otherwise. # PARTNUM is root partition number on SRC disk. # -# -y, --yes -# Do not ask for actions confirmation. Default is to display next -# action and ask user to [y] do it, [q] quit, [s] skip. -# # EXAMPLES # Copy sda to sdb, root partition is partition (sda1/sdb1) # $ sudo dup-live-disk.sh --root 1 sda sdb @@ -92,16 +97,17 @@ Usage: $CMD [OPTIONS] [SRC] DST Duplicate SRC (or live system) disk partitions to DST disk partitions. Options: - -d, -n, --dry-run, --no dry-run: nothing will be written to disk - -g, --grub install grub on destination disk - -h, --help this help - -m, --man display a "man-like" page and exit - --mariadb stop and restart mysql/mariadb server before and - after copies - -r, --root=PARTNUM root partition number on SRC device - mandatory if and only if SRC is provided - -y, --yes DANGER ! perform all actions without user - confirmation + -a, --autofs=DIR autofs "LABEL-based" directory. Default is '/mnt'. + -c, --copy=ACTION do partitions copies ('yes'), do not copy then ('no') + or ask for each of them ('ask'). Default is 'no'. + -d, --dry-run dry-run: nothing will be written to disk + -g, --grub install grub on destination disk + -h, --help this help + -m, --man display a "man-like" page and exit + --mariadb stop and restart mysql/mariadb server before and + after copies + -r, --root=PARTNUM root partition number on SRC device + mandatory if and only if SRC is provided SRC and DST have strong constraints on partitions schemes and naming. Type '$CMD --man" for more details" @@ -112,19 +118,19 @@ _EOF # mariadb start/stop function mariadb_maybe_stop { if [[ $MARIADB == yes ]] && systemctl is-active --quiet mysql; then - log -n "stopping mariadb/mysql... " - systemctl stop mariadb + #log -n "stopping mariadb/mysql... " + echorun systemctl stop mariadb # bug if script stops here MARIADBSTOPPED=yes - log "done." + #log "done." fi } function mariadb_maybe_start { if [[ $MARIADB == yes && $MARIADBSTOPPED == yes ]]; then - log -n "restarting mariadb/mysql... " - systemctl start mariadb + #log -n "restarting mariadb/mysql... " + echorun systemctl start mariadb MARIADBSTOPPED=no - log "done." + #log "done." fi } @@ -155,9 +161,12 @@ function log { # prints out and run a command. function echorun { - log "%s" "$@" - "$@" - return $? + if [[ "$DRYRUN" == 'yes' ]]; then + log "%s " "dry-run: " "$@" + else + log "%s " "$@" + "$@" + fi } function error_handler { @@ -168,15 +177,19 @@ function error_handler { trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM function exit_handler { + local mnt + log "exit handler (at line $1)" mariadb_maybe_start - if [[ -v DSTMNT ]]; then - umount "$DSTMNT/dev" - umount "$DSTMNT/proc" - umount "$DSTMNT/sys" + if [[ -n "$DSTMNT" ]] && mountpoint -q "$DSTMNT"; then + for mnt in "$DSTMNT"/{dev,proc,sys}; do + if mountpoint -q "$mnt"; then + echorun umount "$mnt" + fi + done fi - } + trap 'exit_handler $LINENO' EXIT function check_block_device { @@ -237,14 +250,16 @@ SRC="" DST="" SRCROOT="" ROOTPARTNUM="" -DOIT=manual -MARIADB=no -MARIADBSTOPPED=no -GRUBINSTALL=no + +DRYRUN=no # dry-run +GRUB=no # install grub +COPY=no # do FS copies +MARIADB=no # stop/start mysql/mariadb +MARIADBSTOPPED=no # mysql stopped ? # short and long options -SOPTS="dnghmr:y" -LOPTS="dry-run,no,grub,help,man,mariadb,root:,yes" +SOPTS="c:dghmr:" +LOPTS="copy:,dry-run,grub,help,man,mariadb,root:" if ! TMP=$(getopt -o "$SOPTS" -l "$LOPTS" -n "$CMD" -- "$@"); then log "Use '$CMD --help' or '$CMD --man' for help." @@ -256,14 +271,22 @@ unset TMP while true; do case "$1" in - '-d'|'-n'|'--dry-run'|'--no') - DOIT=no + '-c'|'--copy') + case "$2" in + "no") COPY=no;; + "yes") COPY=yes;; + "ask") COPY=ask;; + *) log "invalid '$2' --copy flag" + usage + exit 1 + esac shift - continue + ;; + '-d'|'--dry-run') + DRYRUN=yes ;; '-g'|'--grub') - GRUBINSTALL=yes - shift + GRUB=yes ;; '-h'|'--help') usage @@ -275,7 +298,6 @@ while true; do ;; '--mariadb') MARIADB=yes - shift ;; '-r'|'--root') ROOTPARTNUM="$2" @@ -283,13 +305,7 @@ while true; do log "$CMD: $ROOTPARTNUM must be a partition number." exit 1 fi - shift 2 - continue - ;; - '-y'|'--yes') - DOIT=yes shift - continue ;; '--') shift @@ -301,9 +317,9 @@ while true; do exit 1 ;; esac + shift done - case "$#" in 1) if [[ -n "$ROOTPARTNUM" ]]; then @@ -355,7 +371,7 @@ declare -a LABELS=(${SRCLABELS[@]%?}) #log "LABELS=${#LABELS[@]} - ${LABELS[*]}" -declare -a SRCDEVS SRCFS SRCDOIT +declare -a SRCDEVS SRCFS SRC_VALID_FS # ... and corresponding partition device and fstype for ((i=0; i<${#LABELS[@]}; ++i)); do TMP="${LABELS[$i]}$SRCCHAR" @@ -365,8 +381,8 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do log "found LABEL=$TMP DEV=$TMPDEV FSTYPE=$TMPFS" SRCDEVS[$i]="$TMPDEV" SRCFS[$i]="$TMPFS" - SRCDOIT[$i]=n - in_array "$TMPFS" VALIDFS && SRCDOIT[$i]=y + SRC_VALID_FS[$i]=n + in_array "$TMPFS" VALIDFS && SRC_VALID_FS[$i]=y unset TMP TMPDEV TMPFS done @@ -386,9 +402,8 @@ fi # log "ROOTLABEL=$ROOTLABEL" # log "SRCROOTLABEL=%s DSTROOTLABEL=%s" "$SRCROOTLABEL" "$DSTROOTLABEL" # log "SRCCHAR=%s DSTCHAR=%s" "$SRCCHAR" "$DSTCHAR" -# log "DOIT=%s\n" "$DOIT" -declare -a DSTLABELS DSTDEVS DSTFS DSTDOIT +declare -a DSTLABELS DSTDEVS DSTFS DST_VALID_FS # Do the same for correponding DST partitions labels, device, and fstype for ((i=0; i<${#LABELS[@]}; ++i)); do TMP="${LABELS[$i]}$DSTCHAR" @@ -408,8 +423,8 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do DSTLABELS[$i]="$TMP" DSTDEVS[$i]="$TMPDEV" DSTFS[$i]="$TMPFS" - DSTDOIT[$i]=n - in_array "$TMPFS" VALIDFS && DSTDOIT[$i]=y + DST_VALID_FS[$i]=n + in_array "$TMPFS" VALIDFS && DST_VALID_FS[$i]=y unset TMP TMPDEV TMPFS done @@ -417,56 +432,47 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do log -n "%s %s " "${SRCDEVS[$i]}" "${DSTDEVS[$i]}" log -n "%s %s " "${SRCLABELS[$i]}" "${DSTLABELS[$i]}" log -n "%s %s " "${SRCFS[$i]}" "${DSTFS[$i]}" - log -n "%s %s " "${SRCDOIT[$i]}" "${DSTDOIT[$i]}" + log -n "%s %s " "${SRC_VALID_FS[$i]}" "${DST_VALID_FS[$i]}" [[ "$DSTROOTLABEL" == "${DSTLABELS[$i]}" ]] && log "*" echo -done | column -N DEV1,DEV2,LABEL1,LABEL2,FS1,FS2,SDOIT,DDOIT,ROOT -t -o " | " +done | column -N DEV1,DEV2,LABEL1,LABEL2,FS1,FS2,SVALID\?,DVALID\?,ROOT -t -o " | " RSYNCOPTS="-axH --delete --delete-excluded" FILTER=--filter="dir-merge .rsync-disk-copy" # copy loop for ((i=0; i<${#LABELS[@]}; ++i)); do - if [[ "${SRCDOIT[$i]}" != y ]] || [[ "${DSTDOIT[$i]}" != y ]]; then + if [[ "${SRC_VALID_FS[$i]}" != y ]] || [[ "${DST_VALID_FS[$i]}" != y ]]; then log "skipping label %s" "${LABELS[$i]}" continue fi SRCPART=/mnt/${SRCLABELS[$i]}/ DSTPART=/mnt/${DSTLABELS[$i]} - log -n "%s -> %s : " "$SRCPART" "$DSTPART" + #log -n "%s -> %s : " "$SRCPART" "$DSTPART" #log "\t%s %s %s %s %s" rsync "${RSYNCOPTS}" "$FILTER" "$SRCPART" "$DSTPART" - skip=y - case "$DOIT" in - yes) - skip=n - ;; - no) - log "skipping (dry run)." - ;; - manual) - yesno "proceed ? [y/n/q]" && skip=n - ;; - esac - if [[ "$skip" == n ]]; then - # shellcheck disable=SC2086 + copy="$COPY" + if [[ "$COPY" == 'ask' ]]; then + yesno "Copy $SRCPART to $DSTPART ? [y/n/q]" && copy=yes || copy=no + fi + if [[ "$copy" == yes ]]; then mariadb_maybe_stop + # shellcheck disable=SC2086 echorun rsync "$FILTER" ${RSYNCOPTS} "$SRCPART" "$DSTPART" fi - log "" + #log "" done # grub install -# mount virtual devices -if [[ $GRUBINSTALL == yes ]]; then +if [[ "$GRUB" == yes ]]; then log "installing grub on $DST..." DSTMNT="/mnt/$DSTROOTLABEL" - mount -o bind /sys "$DSTMNT/sys" - mount -o bind /proc "$DSTMNT/proc" - mount -o bind /dev "$DSTMNT/dev" - - chroot "$DSTMNT" update-grub - chroot "$DSTMNT" grub-install "$DST" + # mount virtual devices + echorun mount -o bind /sys "$DSTMNT/sys" + echorun mount -o bind /proc "$DSTMNT/proc" + echorun mount -o bind /dev "$DSTMNT/dev" + echorun chroot "$DSTMNT" update-grub + echorun chroot "$DSTMNT" grub-install "$DST" fi exit 0 From 6aa4b2cf97155d51fda805e7147b7394194928c4 Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Thu, 29 Apr 2021 12:20:35 +0200 Subject: [PATCH 4/5] Add autofs option (aumount directory) --- bash/dup-live-disk.sh | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/bash/dup-live-disk.sh b/bash/dup-live-disk.sh index 899c395..2c5ee5c 100755 --- a/bash/dup-live-disk.sh +++ b/bash/dup-live-disk.sh @@ -76,7 +76,7 @@ # TODO # Write about autofs configuration. # Log levels -# Separate dry-run and copies/mysql/grub +# Check existence of fstab for new disk and install it #%MAN_END% # command line @@ -250,6 +250,7 @@ SRC="" DST="" SRCROOT="" ROOTPARTNUM="" +AUTOFS_DIR=/mnt DRYRUN=no # dry-run GRUB=no # install grub @@ -258,8 +259,8 @@ MARIADB=no # stop/start mysql/mariadb MARIADBSTOPPED=no # mysql stopped ? # short and long options -SOPTS="c:dghmr:" -LOPTS="copy:,dry-run,grub,help,man,mariadb,root:" +SOPTS="a:c:dghmr:" +LOPTS="autofs:,copy:,dry-run,grub,help,man,mariadb,root:" if ! TMP=$(getopt -o "$SOPTS" -l "$LOPTS" -n "$CMD" -- "$@"); then log "Use '$CMD --help' or '$CMD --man' for help." @@ -271,6 +272,10 @@ unset TMP while true; do case "$1" in + '-a'|'--autofs') + AUTOFS_DIR="$2" + shift + ;; '-c'|'--copy') case "$2" in "no") COPY=no;; @@ -361,18 +366,15 @@ check_block_device "source root partition" r "$SRCROOT" SRCROOTLABEL=$(lsblk -no label "$SRCROOT") SRCCHAR=${SRCROOTLABEL: -1} ROOTLABEL=${SRCROOTLABEL:0:-1} + # find out all partitions labels on SRC disk... # shellcheck disable=SC2207 declare -a SRCLABELS=($(lsblk -lno LABEL "$SRC")) # shellcheck disable=SC2206 declare -a LABELS=(${SRCLABELS[@]%?}) -#log "SRCLABELS=${#SRCLABELS[@]} - ${SRCLABELS[*]}" -#log "LABELS=${#LABELS[@]} - ${LABELS[*]}" - - -declare -a SRCDEVS SRCFS SRC_VALID_FS # ... and corresponding partition device and fstype +declare -a SRCDEVS SRCFS SRC_VALID_FS for ((i=0; i<${#LABELS[@]}; ++i)); do TMP="${LABELS[$i]}$SRCCHAR" TMP="${SRCLABELS[$i]}" @@ -397,14 +399,8 @@ if [[ "$DSTROOTLABEL" != "$ROOTLABEL$DSTCHAR" ]]; then exit 1 fi -# log "SRC=%s DST=%s" "$SRC" "$DST" -# log "SRCROOT=%s DSTROOT=%s" "$SRCROOT" "$DSTROOT" -# log "ROOTLABEL=$ROOTLABEL" -# log "SRCROOTLABEL=%s DSTROOTLABEL=%s" "$SRCROOTLABEL" "$DSTROOTLABEL" -# log "SRCCHAR=%s DSTCHAR=%s" "$SRCCHAR" "$DSTCHAR" - declare -a DSTLABELS DSTDEVS DSTFS DST_VALID_FS -# Do the same for correponding DST partitions labels, device, and fstype +# Do the same for corresponding DST partitions labels, device, and fstype for ((i=0; i<${#LABELS[@]}; ++i)); do TMP="${LABELS[$i]}$DSTCHAR" log -n "Looking for [%s] label... " "$TMP" @@ -445,8 +441,8 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do log "skipping label %s" "${LABELS[$i]}" continue fi - SRCPART=/mnt/${SRCLABELS[$i]}/ - DSTPART=/mnt/${DSTLABELS[$i]} + SRCPART="${AUTOFS_DIR}/${SRCLABELS[$i]}/" + DSTPART="$AUTOFS_DIR/${DSTLABELS[$i]}" #log -n "%s -> %s : " "$SRCPART" "$DSTPART" #log "\t%s %s %s %s %s" rsync "${RSYNCOPTS}" "$FILTER" "$SRCPART" "$DSTPART" @@ -465,7 +461,7 @@ done # grub install if [[ "$GRUB" == yes ]]; then log "installing grub on $DST..." - DSTMNT="/mnt/$DSTROOTLABEL" + DSTMNT="$AUTOFS_DIR/$DSTROOTLABEL" # mount virtual devices echorun mount -o bind /sys "$DSTMNT/sys" echorun mount -o bind /proc "$DSTMNT/proc" From ec99a02d4ffe0d638f863fa06e7da206237da637 Mon Sep 17 00:00:00 2001 From: Bruno Raoult Date: Fri, 30 Apr 2021 11:58:41 +0200 Subject: [PATCH 5/5] rewrite sed script to avoid unnecessary pipe in '--man' option --- bash/dup-live-disk.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/bash/dup-live-disk.sh b/bash/dup-live-disk.sh index 2c5ee5c..08bc58e 100755 --- a/bash/dup-live-disk.sh +++ b/bash/dup-live-disk.sh @@ -69,14 +69,16 @@ # $ sudo dup-live-disk.sh sdb # # BUGS -# Cannot generate grub with a separate /boot partition. -# This script will not work for all situations, I strongly suggest you -# don't use it if you don't *fully* understand it. +# * Cannot generate grub with a separate /boot partition. +# * This script will not work for all situations, I strongly suggest you +# don't use it if you don't *fully* understand it. +# * Extended attributes are not preserved (easy fix, but I cannot test) # # TODO -# Write about autofs configuration. -# Log levels -# Check existence of fstab for new disk and install it +# * Write about autofs configuration. +# * Log levels +# * Check existence of prepared fstab on source root partition for +# destination root partition, and enable it. #%MAN_END% # command line @@ -88,7 +90,7 @@ CMD="${0##*/}" VALIDFS=(ext3 ext4 btrfs vfat reiserfs xfs zfs) function man { - sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!p}' "$SCRIPT" | sed -E 's/^# ?//' + sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$SCRIPT" } function usage { @@ -179,7 +181,7 @@ trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM function exit_handler { local mnt - log "exit handler (at line $1)" + # log "exit handler (at line $1)" mariadb_maybe_start if [[ -n "$DSTMNT" ]] && mountpoint -q "$DSTMNT"; then for mnt in "$DSTMNT"/{dev,proc,sys}; do