Last active
October 3, 2023 11:46
-
-
Save OmegaRogue/fbe11f82a9037238104ea216ecda6d78 to your computer and use it in GitHub Desktop.
small POSIX shell script to safely image drives while filtering out unwanted drives and asking for confirmation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# libalpine https://github.com/alpinelinux/alpine-conf/blob/master/libalpine.sh.in | |
PREFIX=@PREFIX@ | |
PROGRAM=$(basename $0) | |
: ${ROOT:=/} | |
[ "${ROOT}" = "${ROOT%/}" ] && ROOT="${ROOT}/" | |
[ "${ROOT}" = "${ROOT#/}" ] && ROOT="${PWD}/${ROOT}" | |
# echo if in verbose mode | |
vecho() { | |
if [ -n "$VERBOSE" ]; then | |
echo "$@" | |
fi | |
} | |
# echo unless quiet mode | |
qecho() { | |
if [ -z "$QUIET" ]; then | |
echo "$@" | |
fi | |
} | |
# echo to stderr | |
eecho() { | |
echo "$@" >&2 | |
} | |
# echo to stderr and exit with error | |
die() { | |
eecho "$@" | |
exit 1 | |
} | |
init_tmpdir() { | |
local omask="$(umask)" | |
local __tmpd="/tmp/$PROGRAM-${$}-$(date +%s)-$RANDOM" | |
umask 077 || die "umask" | |
mkdir -p "$__tmpd" || exit 1 | |
trap "rc=\$?; rm -fr \"$__tmpd\"; exit \$rc" 0 | |
umask $omask | |
eval "$1=\"$__tmpd\"" | |
} | |
default_read() { | |
local n | |
read n | |
[ -z "$n" ] && n="$2" | |
eval "$1=\"$n\"" | |
} | |
cfg_add() { | |
$MOCK lbu_add "$@" | |
} | |
# return true if given value is Y, y, Yes, yes YES etc | |
yesno() { | |
case $1 in | |
[Yy]|[Yy][Ee][Ss]) return 0;; | |
esac | |
return 1 | |
} | |
# Detect if we are running Xen | |
is_xen() { | |
test -d /proc/xen | |
} | |
# Detect if we are running Xen Dom0 | |
is_xen_dom0() { | |
is_xen && \ | |
grep -q "control_d" /proc/xen/capabilities 2>/dev/null | |
} | |
# list of available network interfaces that aren't part of any bridge or bond | |
available_ifaces() { | |
local iflist= ifpath= iface= i= | |
if ! [ -d "$ROOT"/sys/class/net ]; then | |
ip link | awk -F: '$1 ~ /^[0-9]+$/ {printf "%s",$2}' | |
return | |
fi | |
sorted_ifindexes=$( | |
for i in "$ROOT"/sys/class/net/*/ifindex; do | |
[ -e "$i" ] || continue | |
printf "%s\t%s\n" "$(cat $i)" $i; | |
done | sort -n | awk '{print $2}') | |
for i in $sorted_ifindexes; do | |
ifpath=${i%/*} | |
iface=${ifpath##*/} | |
# skip interfaces that are part of a bond or bridge | |
if [ -d "$ifpath"/master/bonding ] || [ -d "$ifpath"/brport ]; then | |
continue | |
fi | |
iflist="${iflist}${iflist:+ }$iface" | |
done | |
echo $iflist | |
} | |
# from OpenBSD installer | |
# Ask for a password, saving the input in $resp. | |
# Display $1 as the prompt. | |
# *Don't* allow the '!' options that ask does. | |
# *Don't* echo input. | |
# *Don't* interpret "\" as escape character. | |
askpass() { | |
printf %s "$1 " | |
set -o noglob | |
$MOCK stty -echo | |
read -r resp | |
$MOCK stty echo | |
set +o noglob | |
echo | |
} | |
# Ask for a password twice, saving the input in $_password | |
askpassword() { | |
local _oifs="$IFS" | |
IFS= | |
while :; do | |
askpass "Password for $1 account? (will not echo)" | |
_password=$resp | |
askpass "Password for $1 account? (again)" | |
# N.B.: Need quotes around $resp and $_password to preserve leading | |
# or trailing spaces. | |
[ "$resp" = "$_password" ] && break | |
echo "Passwords do not match, try again." | |
done | |
IFS=$_oifs | |
} | |
# test the first argument against the remaining ones, return success on a match | |
isin() { | |
local _a="$1" _b | |
shift | |
for _b; do | |
[ "$_a" = "$_b" ] && return 0 | |
done | |
return 1 | |
} | |
# remove all occurrences of first argument from list formed by | |
# the remaining arguments | |
rmel() { | |
local _a="$1" _b | |
shift | |
for _b; do | |
[ "$_a" != "$_b" ] && printf %s "$_b " | |
done | |
} | |
# Issue a read into the global variable $resp. | |
_ask() { | |
local _redo=0 | |
read resp | |
case "$resp" in | |
!) echo "Type 'exit' to return to setup." | |
sh | |
_redo=1 | |
;; | |
!*) eval "${resp#?}" | |
_redo=1 | |
;; | |
esac | |
return $_redo | |
} | |
# Ask for user input. | |
# | |
# $1 = the question to ask the user | |
# $2 = the default answer | |
# | |
# Save the user input (or the default) in $resp. | |
# | |
# Allow the user to escape to shells ('!') or execute commands | |
# ('!foo') before entering the input. | |
ask() { | |
local _question="$1" _default="$2" | |
while :; do | |
printf %s "$_question " | |
[ -z "$_default" ] || printf "[%s] " "$_default" | |
_ask && : ${resp:=$_default} && break | |
done | |
} | |
# Ask for user input until a non-empty reply is entered. | |
# | |
# $1 = the question to ask the user | |
# $2 = the default answer | |
# | |
# Save the user input (or the default) in $resp. | |
ask_until() { | |
resp= | |
while [ -z "$resp" ] ; do | |
ask "$1" "$2" | |
done | |
} | |
# Ask for user for y/n until y, yes, n or no is responded | |
# | |
# $1 = the question to ask the user | |
# $2 = the default answer | |
# | |
# Returns true/sucess if y/yes was responded. false othewise | |
ask_yesno() { | |
while true; do | |
ask "$1" "$2" | |
case "$resp" in | |
y|yes|n|no) break;; | |
esac | |
done | |
yesno "$resp" | |
} | |
# Ask for the user to select one value from a list, or 'done'. | |
# | |
# $1 = name of the list items (disk, cd, etc.) | |
# $2 = question to ask | |
# $3 = list of valid choices | |
# $4 = default choice, if it is not specified use the first item in $3 | |
# | |
# N.B.! $3 and $4 will be "expanded" using eval, so be sure to escape them | |
# if they contain spooky stuff | |
# | |
# At exit $resp holds selected item, or 'done' | |
ask_which() { | |
local _name="$1" _query="$2" _list="$3" _def="$4" _dynlist _dyndef | |
while :; do | |
# Put both lines in ask prompt, rather than use a | |
# separate 'echo' to ensure the entire question is | |
# re-ask'ed after a '!' or '!foo' shell escape. | |
eval "_dynlist=\"$_list\"" | |
eval "_dyndef=\"$_def\"" | |
# Clean away whitespace and determine the default | |
set -o noglob | |
set -- $_dyndef; _dyndef="$1" | |
set -- $_dynlist; _dynlist="$*" | |
set +o noglob | |
[ $# -lt 1 ] && resp=done && return | |
: ${_dyndef:=$1} | |
echo "Available ${_name}s are: $_dynlist." | |
printf "Which one %s? (or 'done') " "$_query" | |
[ -n "$_dyndef" ] && printf "[%s] " "$_dyndef" | |
_ask || continue | |
[ -z "$resp" ] && resp="$_dyndef" | |
# Quote $resp to prevent user from confusing isin() by | |
# entering something like 'a a'. | |
isin "$resp" $_dynlist done && break | |
echo "'$resp' is not a valid choice." | |
done | |
} | |
find_modloop_media() { | |
devnum=$(mountpoint -d /.modloop) || return | |
test -n "$devnum" || return | |
modloop_file=$(cat /sys/dev/block/$devnum/loop/backing_file) || return | |
test -n "$modloop_file" || return | |
# assume that device name and mount point don't contain spaces | |
modloop_media=$(df "$modloop_file" | awk 'NR==2{print $6}') || return | |
test -n "$modloop_media" || return | |
echo "$modloop_media" | |
} | |
# Extract fully qualified domain name from current hostname. If none is | |
# currently set, use the provided fallback. | |
get_fqdn() { | |
local _dn | |
_dn=$(hostname -f 2>/dev/null) | |
_dn=${_dn#$(hostname -s 2>/dev/null)} | |
_dn=${_dn#.} | |
echo "${_dn:=$1}" | |
} | |
# alpine setup-disk https://github.com/alpinelinux/alpine-conf/blob/master/setup-disk.in | |
in_list() { | |
local i="$1" | |
shift | |
while [ $# -gt 0 ]; do | |
[ "$i" = "$1" ] && return 0 | |
shift | |
done | |
return 1 | |
} | |
all_in_list() { | |
local needle="$1" | |
local i | |
[ -z "$needle" ] && return 1 | |
shift | |
for i in $needle; do | |
in_list "$i" $@ || return 1 | |
done | |
return 0 | |
} | |
# wrapper to only show given device | |
_blkid() { | |
blkid | grep "^$1:" | |
} | |
is_nvme_dev() { | |
local i | |
for i; do | |
case ${i##*/} in | |
nvme*) return 0;; | |
esac | |
done | |
return 1 | |
} | |
has_mounted_part() { | |
local p | |
local sysfsdev="$(echo ${1#/dev/} | sed 's:/:!:g')" | |
# parse /proc/mounts for mounted devices | |
for p in $(awk '$1 ~ /^\/dev\// {gsub("/dev/", "", $1); gsub("/", "!", $1); print $1}' \ | |
"$ROOT"/proc/mounts 2>/dev/null); do | |
[ "$p" = "$sysfsdev" ] && return 0 | |
[ -e "$ROOT"/sys/block/$sysfsdev/$p ] && return 0 | |
done | |
return 1 | |
} | |
has_holders() { | |
local i | |
# check if device is used by any md devices | |
for i in $1/holders/* $1/*/holders/*; do | |
[ -e "$i" ] && return 0 | |
done | |
return 1 | |
} | |
is_available_disk() { | |
local dev="$1" | |
# check so it does not have mounted partitions | |
has_mounted_part $dev && return 1 | |
# check so its not part of an md setup | |
if has_holders "$ROOT"/sys/block/$dev; then | |
[ -n "$USE_RAID" ] && echo "Warning: $dev is part of a running raid" >&2 | |
return 1 | |
fi | |
return 0 | |
} | |
find_disks() { | |
local p= | |
for p in "$ROOT"/sys/block/*/device; do | |
local dev="${p%/device}" | |
dev=${dev#*/sys/block/} | |
if is_available_disk "$dev"; then | |
printf %s " $dev" | |
fi | |
done | |
} | |
all_in_list() { | |
local needle="$1" | |
local i | |
[ -z "$needle" ] && return 1 | |
shift | |
for i in $needle; do | |
in_list "$i" $@ || return 1 | |
done | |
return 0 | |
} | |
# ask for a root or data disk | |
# returns response in global variable $resp | |
ask_disk() { | |
local prompt="$1" | |
local help_func="$2" | |
local i= | |
shift 2 | |
local default_disk="${DEFAULT_DISK:-$1}" | |
resp= | |
while ! all_in_list "$resp" $@ "none" "abort"; do | |
echo "Available disks are:" | |
show_disk_info "$@" | |
ask "$prompt" "$default_disk" | |
case "$resp" in | |
'abort') exit 0;; | |
'none') return 0;; | |
'?') $help_func;; | |
*) for i in $resp; do | |
if ! [ -b "/dev/$i" ]; then | |
echo "/dev/$i is not a block device" >&2 | |
resp= | |
fi | |
done;; | |
esac | |
done | |
} | |
confirm_erase() { | |
local erasedisks="$@" | |
if [ "$ERASE_DISKS" = "$erasedisks" ]; then | |
return 0 | |
fi | |
echo "WARNING: The following disk(s) will be erased:" | |
show_disk_info $@ | |
ask_yesno "WARNING: Erase the above disk(s) and continue? (y/n)" n | |
} | |
show_disk_info() { | |
local disk= vendor= model= d= size= busid= | |
for disk in $@; do | |
local dev="${disk#/dev/}" | |
d=$(echo $dev | sed 's:/:!:g') | |
vendor=$(cat "$ROOT"/sys/block/$d/device/vendor 2>/dev/null) | |
model=$(cat "$ROOT"/sys/block/$d/device/model 2>/dev/null) | |
busid=$(readlink -f "$ROOT"/sys/block/$d/device 2>/dev/null) | |
size=$(awk '{gb = ($1 * 512)/1000000000; printf "%.1f GB\n", gb}' "$ROOT"/sys/block/$d/size 2>/dev/null) | |
if is_dasd $dev eckd; then | |
echo " $dev ($size $vendor ${busid##*/})" | |
else | |
echo " $dev ($size $vendor $model)" | |
fi | |
done | |
} | |
diskselect_help() { | |
cat <<-__EOF__ | |
The disk will be erased. | |
__EOF__ | |
} | |
get_bootopt() { | |
local opt="$1" | |
set -- $(cat /proc/cmdline) | |
for i; do | |
case "$i" in | |
"$opt"|"$opt"=*) echo "${i#*=}"; break;; | |
esac | |
done | |
} | |
# dasd-functions https://github.com/alpinelinux/alpine-conf/blob/master/dasd-functions.sh.in | |
eckd_dasd= | |
fba_dasd= | |
_dasdfmt() { | |
local block="$(ls /sys/bus/ccw/devices/"$1"/block 2>/dev/null)" | |
if ! [ -b "/dev/$block" ]; then | |
echo "/dev/$block is not a block device" >&2 | |
else | |
if ask_yesno "WARNING: Erase ECKD DASD $1? (y/n)" "n"; then | |
dasdfmt -b 4096 -d cdl -yp "/dev/$block" | |
fi | |
fi | |
} | |
eckdselect_help() { | |
cat <<-__EOF__ | |
Enter each available DASD's address (e.g. 0.0.02d0) to format that DASD. | |
Enter multiple addresses separated by a space to format multiple DASDs. | |
Enter 'all' to format all available DASDs. | |
WARNING: Data will be lost after formatted! | |
Enter 'done' or 'none' to finish formatting. | |
Enter 'abort' to quit the installer. | |
__EOF__ | |
} | |
show_dasd_info() { | |
local busid= vendor= block= devtype= cutype= | |
for busid in $@; do | |
vendor=$(cat /sys/bus/ccw/devices/$busid/vendor 2>/dev/null) | |
devtype=$(cat /sys/bus/ccw/devices/$busid/devtype 2>/dev/null) | |
cutype=$(cat /sys/bus/ccw/devices/$busid/cutype 2>/dev/null) | |
block="$(ls /sys/bus/ccw/devices/$busid/block 2>/dev/null)" | |
echo " $busid ($devtype $cutype $vendor)" | |
done | |
} | |
ask_eckd(){ | |
local prompt="$1" | |
local help_func="$2" | |
shift 2 | |
local default_dasd="all" | |
apk add --quiet s390-tools | |
resp= | |
while ! all_in_list "$resp" $@ "$default_dasd" "abort" "done" "none"; do | |
echo "Available ECKD DASD(s) are:" | |
show_dasd_info "$@" | |
ask "$prompt" "$default_dasd" | |
case "$resp" in | |
'abort') exit 0;; | |
'done'|'none') return 0;; | |
'?') $help_func;; | |
'all') for busid in $@; do _dasdfmt $busid; done;; | |
*) for busid in $resp; do _dasdfmt $busid; done;; | |
esac | |
done | |
} | |
check_dasd() { | |
eckd_dasd= fba_dasd= | |
local dasd="$(get_bootopt dasd)" | |
for _dasd in $( echo $dasd | tr ',' ' '); do | |
[ -e /sys/bus/ccw/drivers/dasd-eckd/$_dasd ] && eckd_dasd="$eckd_dasd $_dasd" | |
[ -e /sys/bus/ccw/drivers/dasd-fba/$_dasd ] && fba_dasd="$fba_dasd $_dasd" | |
done | |
if [ -n "$eckd_dasd" ]; then | |
ask_eckd \ | |
"Which ECKD DASD(s) would you like to be formatted using dasdfmt? (enter '?' for help)" \ | |
eckdselect_help "$eckd_dasd" | |
fi | |
} | |
is_dasd() { | |
local disk="${1#*\/dev\/}" dasd_type="$2" | |
for _dasd in $(eval "echo \$${dasd_type}_dasd"); do | |
[ -e /sys/bus/ccw/drivers/dasd-$dasd_type/$_dasd/block/$disk ] && return 0 | |
done | |
return 1 | |
} | |
setup_zipl() { | |
local mnt="$1" root="$2" modules="$3" kernel_opts="$4" | |
local parameters="root=$root modules=$modules $kernel_opts" | |
local dasd="$(echo $eckd_dasd $fba_dasd | tr ' ' ',')" | |
local s390x_net="$(get_bootopt s390x_net)" | |
[ -n "$dasd" ] && parameters="$parameters dasd=$dasd" | |
[ -n "$s390x_net" ] && parameters="$parameters s390x_net=$s390x_net" | |
cat > "$mnt"/etc/zipl.conf <<- EOF | |
[defaultboot] | |
defaultauto | |
prompt=1 | |
timeout=5 | |
default=linux | |
target=/boot | |
[linux] | |
image=/boot/vmlinuz-$KERNEL_FLAVOR | |
ramdisk=/boot/initramfs-$KERNEL_FLAVOR | |
parameters="$parameters" | |
EOF | |
} | |
setup_partitions_eckd() { | |
local blocks_per_track=12 tracks_per_cylinder=15 boot_track= swap_track= | |
local diskdev="$1" boot_size="$2" swap_size="$3" sys_type="$4" | |
boot_track=$(($boot_size * 1024 / 4 / blocks_per_track)) | |
[ "$swap_size" != 0 ] && swap_track=$(($swap_size * 1024 / 4 / blocks_per_track + boot_track + 1)) | |
local conf="$(mktemp)" | |
if [ -n "$swap_track" ]; then | |
cat > "$conf" <<- EOF | |
[first,$boot_track,native] | |
[$((boot_track + 1)),$swap_track,swap] | |
[$((swap_track + 1)),last,$sys_type] | |
EOF | |
else | |
cat > "$conf" <<- EOF | |
[first,$boot_track,native] | |
[$((boot_track + 1)),last,$sys_type] | |
EOF | |
fi | |
fdasd -s -c "$conf" $diskdev | |
rm $conf | |
} | |
image="$1" | |
if [[ -z $image ]]; then | |
printf '%s\n' "no file provided" >&2 | |
exit 1 | |
fi | |
if [ ! -f $image ]; then | |
printf '%s\n' "file not found: $image" >&2 | |
exit 1 | |
fi | |
# block_devs="$(lsblk --nodeps -brno name,rm,size,vendor,model)" | |
# block_devs="$(printf '%s' "$block_devs" | tr ' ' "\t")" | |
# | |
# OLDIFS=$IFS | |
# IFS=$(printf "\n\b") | |
# for dev in $block_devs; do | |
# name="$(printf '%s' "$dev" | cut -f 1)" | |
# if [ -f /sys/block/$name/device/power/runtime_active_time ]; then | |
# atime="$(cat /sys/block/$name/device/power/runtime_active_time)" | |
# if [ $(printf '%s' "$dev" | cut -f 2 ) -eq 1 ]; then | |
# if [ $(printf '%s' "$dev" | cut -f 3 ) -ne 0 ]; then | |
# info=$(paste <(printf '%s' "$dev") <(printf "$atime\n\n")) | |
# #removable="$(printf '%s' "$dev$removable")" | |
# #removable="$(printf '%s' "$info$removable")" | |
# fi | |
# fi | |
# fi | |
# done | |
# | |
# removable=$(printf '%s' "$removable" | sed 's/^[ \t]*//;s/[ \t]*$//' | sort -nk 6) | |
# | |
# for dev in $removable; do | |
# name=$(printf "$dev" | cut -f 1) | |
# size=$(printf "$dev" | cut -f 3) | |
# model="$(printf "$dev" | cut -f 4,5 | xargs)" | |
# hsize=$(numfmt --to=iec-i --suffix=B $size) | |
# nsize=$(numfmt --grouping $size) | |
# out=$(printf "/dev/$name\t$model\t$hsize\t($nsize bytes)\n$out") | |
# done | |
# IFS=$OLDIFS | |
# | |
# nlines=$(printf "$out\n" | wc -l) | |
check_dasd | |
diskdevs= | |
# no disks so lets exit quietly. | |
while [ -z "$disks" ]; do | |
disks=$(find_disks) | |
[ -n "$disks" ] && break | |
# ask to unmount the boot mmc for rpi and similar | |
modloop_media=$(find_modloop_media) | |
if [ -z "$modloop_media" ] || ! ask_yesno "No disks available. Try boot media $modloop_media? (y/n)" n; then | |
[ -z "$QUIET" ] && echo "No disks found." >&2 | |
exit 0 | |
fi | |
DO_UMOUNT=1 modloop_media=$modloop_media copy-modloop | |
done | |
ask_disk "Which disk(s) would you like to use? (or '?' for help or 'none')" diskselect_help $disks | |
# sel=$(printf "$out" | | |
# column -ts "$(printf "\t")" | | |
# fzf --height=$(($nlines+3)) --header="Please select the drive to flash" | | |
# tr -s ' ' "\t" | | |
# cut -f 1) | |
rsync --write-devices -vh --progress $image /dev/$resp | |
sync | |
#echo $resp |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment