Skip to content

Instantly share code, notes, and snippets.

@OmegaRogue
Last active October 3, 2023 11:46
Show Gist options
  • Save OmegaRogue/fbe11f82a9037238104ea216ecda6d78 to your computer and use it in GitHub Desktop.
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
#!/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