From 1ddc5b9148193a9665119b6c11d83bd0a272924a Mon Sep 17 00:00:00 2001 From: Ian Kelling Date: Wed, 5 Mar 2025 04:20:19 -0500 Subject: [PATCH] wip snapshot, bunch of fixes and features --- .../files/simple/usr/local/bin/attach-vm-disk | 105 ++++ .../files/simple/usr/local/bin/create-vm | 540 ++++++++++-------- .../files/simple/usr/local/bin/savannah-virsh | 173 +++++- .../simple/usr/local/bin/unsafe-remove-vm | 24 +- .../simple/usr/local/lib/fsf-virsh-bash-lib | 37 ++ 5 files changed, 628 insertions(+), 251 deletions(-) create mode 100755 roles/kvmhost/files/simple/usr/local/bin/attach-vm-disk create mode 100644 roles/kvmhost/files/simple/usr/local/lib/fsf-virsh-bash-lib diff --git a/roles/kvmhost/files/simple/usr/local/bin/attach-vm-disk b/roles/kvmhost/files/simple/usr/local/bin/attach-vm-disk new file mode 100755 index 0000000..6e58bc1 --- /dev/null +++ b/roles/kvmhost/files/simple/usr/local/bin/attach-vm-disk @@ -0,0 +1,105 @@ +#!/bin/bash + + +# in addition, we need virsh detach, and preferably rm. + +set -e; . /usr/local/lib/bash-bear; set +e +shopt -s nullglob + +# note: you have to run shellcheck from this directory for this to work. kinda dumb. +# shellcheck source=../lib/fsf-virsh-bash-lib +source /usr/local/lib/fsf-virsh-bash-lib + +[[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@" + +usage() { + cat < + +For non-ceph disks, the path is the dev attribute in its vm xml: + + +The SOURCE_DEV_PATH has to be attached to SOURCE_VM_NAME. This is just so we can +filter SOURCE_VM_NAME as a simple access control. + + +Note: Uses util-linux getopt option parsing: spaces between args and +options, short options can be combined, options before args. +EOF + exit $1 +} + +get-attached-devs() { + local tmps vm="$1" + tmps=$(virsh dumpxml $vm) + tmps=$(xmllint --xpath "$xpath" - <<<"$tmps" | sed 's/^[^"]*"// ; s/"[^"]*$//') + mapfile -t attached_devs <<<"$tmps" +} + +check-source-dev() { + local found_source attached_dev + local -a attached_devs + + get-attached-devs $source_vm + + found_source=false + for attached_dev in "${attached_devs[@]}"; do + if [[ $source_dev == "$attached_dev" ]]; then + found_source=true + break + fi + done + + if ! $found_source; then + echo "$0: error: failed to find SOURCE_DEV_PATH in SOURCE_VM_NAME's xml" + exit 1 + fi +} + + +##### begin command line parsing ######## + +# ensure we can handle args with spaces or empty. +ret=0; getopt -T || ret=$? +[[ $ret == 4 ]] || { echo "Install util-linux for enhanced getopt" >&2; exit 1; } + +temp=$(getopt -l help h "$@") || usage 1 +eval set -- "$temp" +while true; do + case $1 in + -h|--help) usage ;; + --) shift; break ;; + *) echo "$0: unexpected args: $*" >&2 ; usage 1 ;; + esac + shift +done +read -r source_vm source_dev target_vm <<<"$@" + +##### end command line parsing ######## + +ceph=false +xpath=/domain/devices/disk/source/@dev +if [[ $HOSTNAME == kvmhost[234] ]]; then + ceph=true + xpath="/domain/devices/disk/source[@protocol='rbd']/@name" +fi + +check-source-dev + +get-attached-devs $target_vm + +disk_letters=( {a..z} ) +letter=${disk_letters[${#attached_devs[@]}]} + +if $ceph; then + add-ceph-disk $source_dev +else + add-local-disk $source_dev +fi diff --git a/roles/kvmhost/files/simple/usr/local/bin/create-vm b/roles/kvmhost/files/simple/usr/local/bin/create-vm index d11fa31..f89cf67 100755 --- a/roles/kvmhost/files/simple/usr/local/bin/create-vm +++ b/roles/kvmhost/files/simple/usr/local/bin/create-vm @@ -20,16 +20,27 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # -if [[ ! -s /usr/local/lib/err ]]; then - echo "$0: error, missing /usr/local/lib/err" >&2 - exit 1 -fi -source /usr/local/lib/err +set -e; . /usr/local/lib/bash-bear; set +e +# shellcheck source=../lib/fsf-virsh-bash-lib +source /usr/local/lib/fsf-virsh-bash-lib #### Begin function definitions #### ## Add to these as needed and keep them in order cleanup_cmds=() +mkdir -p /root/create-vm-logs +cleanup_log=/root/create-vm-logs/cleanup-$(date "+%F_%T").log +cat >$cleanup_log <<'EOF' +#!/bin/bash +set -xe +# To use this cleanup log: +# +# 1. find if there is an end or section that has pop: and remove the no +# longer relevant lines. +# +# 2. tac LOG |tee tmpx; bash -xe tmpx +EOF + err-cleanup() { if (( ${#cleanup_cmds[@]} == 0 )); then return 0 @@ -68,64 +79,60 @@ err() { echo "[$(date +'%Y-%m-%d %H:%M:%S%z')]: $pre: $*" >&2; } usage() { cat <= 1t, - NUMt, truncated to a whole terabyte, or for disks <1t, NUMg - truncated to the nearest gigabyte. - Disk sizes can be seen by running $0 -l or - vgs to show all volume groups without their array groupings. - -todo: add an interactive selection of volume disk array. Example: $0 www.gnu.org 5000 512 2 flidas -supported codenames are aramo, nabia, etiona, flidas, belenos and stretch + +Supported codenames are aramo (recommended), nabia, etiona, flidas, +belenos and stretch. Codenames older than the recommended are more +likely to expose a bug in this script. Example of adding a disk to the above vm: -create-vm -d data,/srv www.gnu.org 8000 +create-vm -d 8000 data,/srv www.gnu.org For migrating an existing vm, use the --import option. After rsyncing the old disk into the new disk, look at the os-prep function below, and @@ -136,7 +143,20 @@ vms have a few extra files compared to other vm hosts. Note: Uses util-linux getopt option parsing: spaces between args and options, short options can be combined, options before args. + EOF + + #potential feature: + #-s SOME_DISK_ARG + # + # + # An option for making a vm like cephmon4, which is backed by mdraid + # -> lvm single volume setup outside of any + # script. vg-disk() in this file was + # maintained for that kind of case, but I commented + # it out because it may be a very long time if ever + # that we want that code, so keep it simpler. + exit $1 } @@ -184,6 +204,12 @@ cli-arg-check() { # set initial global variables set-initial-vars() { + + doceph=false + if [[ $HOSTNAME == kvmhost[234] ]]; then + doceph=true + fi + if $dolist; then return 0 fi @@ -234,39 +260,96 @@ set-initial-vars() { fs_opts=noatime fs_type=ext4 - doswap=false + fs_passno=1 if [[ $disk_mountpoint == none ]]; then - doswap=true fs_type=swap fs_opts=sw - fi - - # These numbers are what man 5 fstab says they should be. - fs_passno=1 - if $doswap; then fs_passno=0 - elif $add_disk; then - fs_passno=2 fi + # These numbers are what man 5 fstab says they should be. vm_internal_disk=/dev/vda inside_vm_cmds=false - gnuhope=false - if [[ $HOSTNAME == kvmhost[234] ]]; then - gnuhope=true - fi +} - doceph=false - if $gnuhope && ! $dovg; then - doceph=true +free-memory() { + local tmps + local -a vm_names + tmps=$(virsh list --name) + mapfile -t vm_names <<<"$tmps" + vm_count="${#vm_names[@]}" + x=1024 + while read -r l; do + x=$(( x + (l /1024) )) # l = kilobytes + done < <(virsh domstats "${vm_names[@]}" |sed -n 's/balloon.maximum=//p') + total_mem=$(( $(awk '/MemTotal/ {print $2}' /proc/meminfo) / 1024 )) + low_estimate=$(( total_mem - (x + 600 * vm_count) )) + high_estimate=$(( total_mem - (x + 200 * vm_count) )) + + # switch to gb for final output. + low_estimate=$(echo "scale=1; $low_estimate /1024"|bc -l) + high_estimate=$(echo "scale=1; $high_estimate /1024"|bc -l) + + echo " +####### Free Memory ######## + +Estimate: ${low_estimate}GB to ${high_estimate}GB memory is safe to allocate to VMs. + + +The range is due to unknown and variable per vm memory overhead, my guess is 200mb to 600mb per VM. + +Note: running free is misleadingly low vm hosts. free -m:" + free -m + +} + +ceph-df() { + tmps=$(ceph df -f json | jq -r '"\(.stats.total_bytes) \(.stats.total_used_bytes)"') + if [[ $( printf "%s\n" "$tmps" | wc -w) != 2 ]]; then + echo "error: unexpected ceph df output" + exit 1 fi + read -r used_bytes total_bytes <<<"$tmps" + max_percent_used=79 + gb_available=$(( ( used_bytes * max_percent_used / 100 - total_bytes ) / ( 3 * 1024 * 1024 * 1024) )) + + cat < gb_available )); then + err "error: requested size $((new_disk_mb * 1024))GB is greater than the available space." exit 1 fi + +} + +push-cleanup() { + cleanup_cmds+=("$*") + e "adding cleanup command to $cleanup_log" + printf "%s\n" "$*" | tee -a $cleanup_log +} +pop-cleanup() { + printf "pop: %s\n" "${cleanup_cmds[-1]}" | tee -a $cleanup_log + unset "cleanup_cmds[-1]" } ceph-disk() { @@ -276,13 +359,18 @@ ceph-disk() { # If adding disk, we reuse the same key if $add_disk; then read -r -p "Enter the VM's luks key so I can cryptsetup luksFormat the new disk " key_data + key_regex='^[[:alnum:]]+$' + if [[ ! $key_data =~ $key_regex ]] || (( ${#key_data} > 1000 || ${#key_data} < 30 )); then + echo "error: bad key" + exit 1 + fi printf "%s" "$key_data" >$keyfile - cleanup_cmds+=("rm -f $keyfile") + push-cleanup "rm -f $keyfile" else e writing generated password to $keyfile pwgen 128 -s -1 | tr -d '\n' >$keyfile - cleanup_cmds+=("rm -f $keyfile") + push-cleanup "rm -f $keyfile" m tee /dev/shm/grub.cfg <>/mnt/$host/etc/fstab m umount-vm $host - unset "cleanup_cmds[-1]" + pop-cleanup fi else @@ -386,17 +474,25 @@ EOF fi } -vg-disk() { - m lvcreate $vg -L $size -n $dname - lvdev=/dev/$vg/$dname - vm_disks=($lvdev) - cleanup_cmds+=("lvremove -f $lvdev") - m mkfs.ext4 $lvdev - m mount $lvdev $target - cleanup_cmds+=("umount $target") -} -nongnuhope-disk() { +# vg-disk() { +# m lvcreate $vg -L $new_disk_mb -n $dname +# lvdev=/dev/$vg/$dname +# vm_disks=($lvdev) +# push-cleanup "lvremove -f $lvdev" +# m mkfs.ext4 $lvdev +# m mount $lvdev $target +# push-cleanup "umount $target" +# } + +nonceph-disk() { + cat <$keyfile fi - cleanup_cmds+=("rm -f $keyfile") + push-cleanup "rm -f $keyfile" # directory is already 700, just being thorough m chmod 600 $keyfile @@ -562,7 +678,7 @@ nongnuhope-disk() { m time integritysetup --batch-mode format $lvdev m integritysetup open --allow-discards $lvdev $integrity_name fi - cleanup_cmds+=("integritysetup close $integrity_name") + push-cleanup "integritysetup close $integrity_name" done mddev=/dev/md/md$dname if [[ -e $mddev ]]; then @@ -587,10 +703,10 @@ nongnuhope-disk() { # reading any errors more confusing. So, at the tradeoff that this # may need changing in the future, do what helps us more now. m mdadm --create -v $mddev --metadata=1.2 --level 1 --raid-devices=3 --bitmap=internal ${integrity_devs[@]} - cleanup_cmds+=("mdadm -v --zero-superblock ${integrity_devs[*]}") + push-cleanup "mdadm -v --zero-superblock ${integrity_devs[*]}" # For background, see comment in unsafe-remove-vm - cleanup_cmds+=("test ! -e /sys/devices/virtual/block/${mddev##*/}") - cleanup_cmds+=("mdadm -v --stop $mddev") + push-cleanup "test ! -e /sys/devices/virtual/block/${mddev##*/}" + push-cleanup "mdadm -v --stop $mddev" fi luks_name=crypt-$dname luks_dev=/dev/mapper/$luks_name @@ -599,14 +715,14 @@ nongnuhope-disk() { if ! grep -Fxq "$l" /etc/crypttab; then echo "$l" | m tee -a /etc/crypttab fi - cleanup_cmds+=("sed -i /^$luks_name/d /etc/crypttab") + push-cleanup "sed -i /^$luks_name/d /etc/crypttab" if [[ -e $luks_dev ]]; then e "skipping creation of existing luks dev: $luks_dev" else # 141 is broken pipe, it is normal when doing yes yes YES | m cryptsetup luksFormat $mddev $keyfile || [[ $? == 141 ]] m cryptdisks_start $luks_name - cleanup_cmds+=("cryptsetup luksClose $luks_dev") + push-cleanup "cryptsetup luksClose $luks_dev" m mkfs.ext4 $luks_dev fi m mount $luks_dev $target @@ -632,26 +748,26 @@ nongnuhope-disk() { fi else echo "$line" | m tee -a /etc/crypttab - cleanup_cmds+=("sed -i /^$luks_name/d /etc/crypttab") + push-cleanup "sed -i /^$luks_name/d /etc/crypttab" fi m cryptdisks_start $luks_name vm_disks+=(/dev/mapper/$luks_name) - cleanup_cmds+=("cryptsetup luksClose /dev/mapper/$luks_name") + push-cleanup "cryptsetup luksClose /dev/mapper/$luks_name" done m mkfs.btrfs -f -m raid1c3 -d raid1c3 ${vm_disks[@]} m mount ${vm_disks[0]} $mountdir - cleanup_cmds+=("umount $mountdir") + push-cleanup "umount $mountdir" m btrfs sub create $mountdir/root m umount $mountdir - unset "cleanup_cmds[-1]" + pop-cleanup m mount -o subvol=root ${vm_disks[0]} $target fi # Note: If $add_disk is true, we did not need to mount to $target, but as the # script is currently written, it would require several conditionals to avoid # it, so just do it anyways for the sake of making the script simpler. - cleanup_cmds+=("umount $target") -} # End nongnuhope-disk + push-cleanup "umount $target" +} # End nonceph-disk @@ -725,15 +841,15 @@ EOF m mount -t proc none $target/proc - cleanup_cmds+=("umount $target/proc") + push-cleanup "umount $target/proc" m mount -o bind /dev $target/dev - cleanup_cmds+=("umount $target/dev") + push-cleanup "umount $target/dev" # This is not needed afaik, just makes output nicer. m mount -o bind /dev/pts $target/dev/pts - cleanup_cmds+=("umount $target/dev/pts") + push-cleanup "umount $target/dev/pts" # This is not needed afaik, but silences some complaints. m mount -o bind /sys $target/sys - cleanup_cmds+=("umount $target/sys") + push-cleanup "umount $target/sys" case $release in stretch) @@ -905,13 +1021,13 @@ EOF rm -f $target/usr/lib/x86_64-linux-gnu/libeatmydata.so m umount $target/sys - unset "cleanup_cmds[-1]" + pop-cleanup m umount $target/dev/pts - unset "cleanup_cmds[-1]" + pop-cleanup m umount $target/dev - unset "cleanup_cmds[-1]" + pop-cleanup m umount $target/proc - unset "cleanup_cmds[-1]" + pop-cleanup } # End os-prep @@ -958,11 +1074,11 @@ create-vm() { vopts+=( --boot kernel=/var/lib/libvirt/images/grub-$host.bin ) - elif $mdraid || $dovg; then + elif $mdraid; then vopts+=( --boot $boot_prefix-bootsym.bin ) - else + else # vg_opt vopts+=( --boot $boot_prefix-btrfs-bootsym.bin ) @@ -997,9 +1113,15 @@ create-vm() { #### End network args #### - if $gnuhope; then + if $doceph; then vopts+=( --memory=$ram,hugepages=true ) else + # Our newer trisquel OSes cant handle hugepages. I assume they could if I spent some time trying to configure it right. Example error: + # + #create-vm: virt-install --name inspect0d.fsf.org ... --memory=3000,hugepages=true + # Starting install... + # ERROR internal error: qemu unexpectedly closed the monitor: 2025-03-05T06:50:59.389686Z qemu-system-x86_64: unable to map backing store for guest RAM: Cannot allocate memory + vopts+=( --memory=$ram ) fi @@ -1011,7 +1133,6 @@ create-vm() { # virt-install, so just add them here. if $doceph; then m virsh destroy $host - ceph-add-disk virsh dumpxml $host > /tmp/tmp.xml #sed "1s#># xmlns:qemu=\'http://libvirt.org/schemas/domain/qemu/1.0'>#; s###" /tmp/tmp.xml -i @@ -1022,7 +1143,7 @@ create-vm() { # or maybe this will work: # --controller type=scsi,model=virtio-scsi,driver.queues=1 sed -i "/virtio-scsi/a " /tmp/tmp.xml - unset "cleanup_cmds[-1]" + pop-cleanup virsh create /tmp/tmp.xml rm -f /tmp/tmp.xml fi @@ -1053,24 +1174,6 @@ EOF } # End create-vm -ceph-add-disk() { - m tee /tmp/disk.xml < - - - - - - - - - - - -EOF - m virsh attach-device $host /tmp/disk.xml --persistent - -} #### End function definitions #### trap 'err-cleanup; trap - INT; kill -s INT "$$"' INT @@ -1080,6 +1183,12 @@ export LC_ALL=C pre="${0##*/}:" +for pkg in jq nmon; do + if ! type -p $pkg &>/dev/null; then + echo "installing missing dependency: jq" + apt-get -y install $pkg + fi +done ##### Begin command line parsing ######## @@ -1090,59 +1199,40 @@ ret=0; getopt -T || ret=$? # Defaults mdraid=false # Says whether we passed a vg name. -dovg=false import=false add_disk=false dolist=false -dosize=false -temp=$(getopt -l help,disk:,vol-group:,import,mdraid,vg_size: hd:g:ilms: "$@") || usage 1 +temp=$(getopt -l help,disk,vol-group:,import,mdraid hdg:ilm "$@") || usage 1 eval set -- "$temp" while true; do case $1 in -d|--disk) - tmp=$2 - disk_name_suf=-${tmp%,*} - disk_mountpoint=${tmp#*,} add_disk=true - shift + # hack to bypass logic + release=aramo ;; -g|--vol-group) vg_opt="$2" - dovg=true shift ;; - # todo: add option to pick which disk array for kvmhost5 -i|--import) import=true ;; -l) dolist=true ;; -m|--mdraid) mdraid=true ;; - -s|--size) - size_opt="$2" - size_regex="^[1-9][0-9]*[tg]$" - if [[ ! $size_opt =~ $size_regex ]]; then - err "size option: $size_opt does not match expected size_regex: $size_regex" - exit 1 - fi - if [[ $size_opt == *g ]]; then - size_opt=${size_opt%g} - elif [[ $size_opt == *t ]]; then - size_opt=$(( ${size_opt%t} * 1000 )) - else - err "something went wrong in size_opt condition. read the source code" - fi - - dosize=true - shift - ;; -h|--help) usage ;; --) shift; break ;; *) e "unexpected args: $*" >&2 ; usage 1 ;; esac shift done -read -r host new_disk_mb ram cpus _ <<<"$@" -release=${5:-aramo} +if $add_disk; then + read -r new_disk_mb disk_arg host <<<"$@" + disk_name_suf=-${disk_arg%%/*} + disk_mountpoint=${disk_arg#*/} +else + read -r new_disk_mb ram cpus release host <<<"$@" +fi if $dolist; then if (( $# != 0 )); then @@ -1150,12 +1240,12 @@ if $dolist; then usage 1 fi elif $add_disk; then - if (( $# != 2 )); then + if (( $# != 3 )); then err "error: expected 2 args with -d, got $#. exiting" usage 1 fi -elif (( $# < 4 || $# > 5 )); then - err "error: expected 4-5 args, got $# exiting" +elif (( $# != 5 )); then + err "error: expected 5 args, got $# exiting" usage 1 fi @@ -1168,7 +1258,14 @@ set-initial-vars ##### Start actually doing things, this goes on till the end of the script #### if $dolist; then - nongnuhope-disk + free-memory + if $doceph; then + ceph-df + else + nonceph-disk + fi + read -r -t 300 -n1 -p "Press any key to start nmon. Tip: press 'l' for a useful cpu graph. (autostarts in 300 seconds)" ||: + nmon exit 0 fi @@ -1177,7 +1274,7 @@ fi pkgs=(libvirt-dev virtinst pwgen libosinfo-bin) for p in ${pkgs[@]}; do if ! dpkg -s -- $p |& grep -Fx "Status: install ok installed" &> /dev/null; then - m apt install -y --no-install-recommends ${pkgs[@]} + m apt-get install -y --no-install-recommends ${pkgs[@]} break fi done @@ -1199,22 +1296,24 @@ bootsym-image ###### End make common grub images if they dont exist ###### - letter=a if $add_disk; then - # In some distro newer than t8, we can do: - # xmllint --xpath "count(/domain/devices/disk)" - disk_num=$(virsh dumpxml $host | grep -cF '') + xpath=/domain/devices/disk/source/@dev + if $doceph; then + xpath="/domain/devices/disk/source[@protocol='rbd']/@name" + fi + tmpf=$(mktemp) + virsh dumpxml $host >$tmpf + disk_num=$( xmllint --xpath "count($xpath)" $tmpf) disk_letters=( {a..z} ) letter=${disk_letters[disk_num]} fi -if $dovg && $gnuhope; then - vg-disk -elif $doceph; then +if $doceph; then + ceph-df ceph-disk else - nongnuhope-disk + nonceph-disk fi if ! $doceph && $add_disk; then @@ -1229,11 +1328,11 @@ EOF else m mount-vm --first-disk $host - cleanup_cmds+=("umount-vm $host") + push-cleanup "umount-vm $host" m mkdir -p /mnt/$host/$disk_mountpoint /mnt/$host/etc echo "/dev/vd$letter $disk_mountpoint $fs_type $fs_opts 0 $fs_passno" >>/mnt/$host/etc/fstab m umount-vm $host - unset "cleanup_cmds[-1]" + pop-cleanup fi fi @@ -1243,21 +1342,21 @@ if ! $import && ! $add_disk; then fi m umount $target -unset "cleanup_cmds[-1]" +pop-cleanup # For ceph, decryption is done inside the VM. if $doceph; then m cryptsetup luksClose $luks_dev - unset "cleanup_cmds[-1]" + pop-cleanup m rbd-nbd unmap $nbd_dev - unset "cleanup_cmds[-1]" + pop-cleanup fi if $add_disk; then if $doceph; then - ceph-add-disk + ceph-add-disk $pool/$dname $host else # Note: On a vg disk in kvmhost2, we had this: # @@ -1273,14 +1372,7 @@ if $add_disk; then disk=${vm_disks[i]} letter=${disk_letters[disk_num+i]} # TODO After we upgrade all our kvmhosts, check out virsh attach-disk. - m tee /tmp/disk.xml < - - - - -EOF - m virsh attach-device $host /tmp/disk.xml --persistent + add-local-disk $disk $host done fi else diff --git a/roles/kvmhost/files/simple/usr/local/bin/savannah-virsh b/roles/kvmhost/files/simple/usr/local/bin/savannah-virsh index 3e9a6c0..3022a43 100755 --- a/roles/kvmhost/files/simple/usr/local/bin/savannah-virsh +++ b/roles/kvmhost/files/simple/usr/local/bin/savannah-virsh @@ -24,45 +24,176 @@ ## Managed by Ansible, changes will be overwritten ## +usage() { + cat <&2' ERR +trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?"' ERR -regex='^[a-z0-9. ]*$' +initial_input_regex='^[[:alnum:]/. _-]+$' +general_arg_regex='^[[:alnum:]._-]+$' +path_regex='^[[:alnum:]/_-]+$' # restricted ssh does not allow arguments, but they exist in this env variable. # The var comes with a leading space, remove it. args="${SSH_ORIGINAL_COMMAND# }" -if [[ ! $args =~ $regex ]]; then - echo "error: bad argument. args=$args" >&2; exit 1 +if [[ ! $args =~ $initial_input_regex ]]; then + arg-die initial regex fi + IFS=" " read -r -a arg_array <<<"$args" +args_len="${#args}" +if (( args_len > 1000 )); then + arg-die args_len 1000 +fi -arg1="${arg_array[0]}" -case "$arg1" in - list) - virsh list --name - exit 0 +arg0="${arg_array[0]}" +declare -i argc="${#arg_array[@]}" +last_arg_i=$((argc - 1)) +host_arg="${arg_array[$last_arg_i]}" # default, overriden for one command + +case "$arg0" in + -h|--help) + usage + ;; + create-vm) + if (( argc > 10 )); then + echo "error: bad argument arc 10"; exit 1 + fi + case "${arg_array[1]}" in + -l|-h|--help) + "${arg_array[@]}" + exit 0 + ;; + esac + check-host-arg + path_arg_i=$(( argc - 2 )) + path_option_i=$(( argc - 4 )) + for (( i=0; i < argc; i+=1 )); do + if [[ ${arg_array[$path_option_i]} == -d && $i == "$path_arg_i" ]]; then + # Having / in user input is more prone to bugs and security problems, + # so don't allow it in variables where it + [[ ${arg_array[$i]} =~ $path_regex ]] || arg-die path_regex + else + [[ ${arg_array[$i]} =~ $general_arg_regex ]] || arg-die arg_regex + fi + done + m "${arg_array[@]}" + ;; + attach-vm-disk) + if (( argc >= 4 )); then + echo "error: bad argument arc 10"; exit 1 + fi + if [[ ! $arg =~ $path_arg_regex ]]; then arg-die path_arg_regex in attach-vm-disk; fi + m "${arg_array[@]}" ;; - console|reboot|reset|start|destroy|create-vm|unsafe-remove-vm) true ;; *) - echo "error: bad argument" >&2; exit 1 + for arg in "${arg_array[@]}"; do + if [[ ! $arg =~ $general_arg_regex ]]; then arg-die general_arg_regex loop; fi + done + + ;;& + + detach-disk) + if (( argc > 3 )); then + echo "error: bad argument count: $argc, wanted 3"; exit 1 + fi + host_arg="${arg_array[argc - 2]}" + check-host-arg + m virsh "${arg_array[@]}" + ;; + list) + m virsh list --name + ;; + list-verbose) + m virsh list --all + ;; + unsafe-remove-vm) + if (( argc != 2 )); then + echo "error: bad argument count: $argc, wanted 2"; exit 1 + fi + check-host-arg + m "${arg_array[@]}" + ;; + console|reboot|reset|start|destroy|dumpxml) + if (( argc != 2 )); then + echo "error: bad argument count: $argc, wanted 2"; exit 1 + fi + check-host-arg + m virsh "${arg_array[@]}" ;; -esac - -arg2="${arg_array[1]}" - - -case "$arg2" in - *.savannah.gnu.org|debbugs2p.gnu.org|debbugs.gnu.org|emacsconfmedia0p.gnu.org|jitsi*.fsf.org|verandah.gnu.org) - true ;; *) - echo "error: bad argument" >&2; exit 1 + arg-die ;; esac -virsh "$arg1" "$arg2" diff --git a/roles/kvmhost/files/simple/usr/local/bin/unsafe-remove-vm b/roles/kvmhost/files/simple/usr/local/bin/unsafe-remove-vm index dabbe88..5577bce 100755 --- a/roles/kvmhost/files/simple/usr/local/bin/unsafe-remove-vm +++ b/roles/kvmhost/files/simple/usr/local/bin/unsafe-remove-vm @@ -6,17 +6,29 @@ # SPDX-License-Identifier: AGPL-3.0-or-later -source /usr/local/lib/err +set -e; . /usr/local/lib/bash-bear; set +e ##### begin command line parsing ######## -if (( $# != 1 )); then + +usage() { cat <$script_gen < + + + + + + + + + + + +EOF + virsh attach-device $target_vm $tmpf --persistent +} + +# Input var: $letter , as in /dev/sd$letter +add-local-disk() { + local tmpf disk_path="$1" target_vm="$2" + tmpf=$(mktemp) + tee $tmpf < + + + + +EOF + virsh attach-device $target_vm $tmpf --persistent + rm $tmpf +} -- 2.25.1