DistroHopper/q
2026-05-27 17:00:06 +02:00

1132 lines
31 KiB
Bash
Executable file

#!/usr/bin/env bash
# Author: zenobit
# Description: Uses gum to provide a simple TUI for VMs using qemu
# Based of: https://github.com/quickemu-project/quickemu
# License MIT
#set -eu
# Show command runned DEBUG
#set -x
## NEEDED
CHARACTERS="                      "
c2="                        "
c3="               "
OLD=" "
WMS="      "
SHOP=" "
SOFT="  "
DISPLAY="  "
GIT="  "
CHARACTERS="                                                                                                               "
_Q_DIR="$(dirname "$(realpath "$0")")"
# shellcheck source=lib.sh
[ -f "${_Q_DIR}/lib.sh" ] && source "${_Q_DIR}/lib.sh"
_apply_gum_colors() {
export BORDERS_FOREGROUND
export GUM_CHOOSE_CURSOR_FOREGROUND="$BORDERS_FOREGROUND"
export GUM_CHOOSE_SELECTED_FOREGROUND="$BORDERS_FOREGROUND"
export GUM_CONFIRM_PROMPT_FOREGROUND=""
export GUM_CONFIRM_SELECTED_FOREGROUND="$BORDERS_FOREGROUND"
export GUM_CONFIRM_UNSELECTED_FOREGROUND=0
export GUM_FILTER_CURSOR_TEXT_FOREGROUND=""
export GUM_FILTER_HEADER_FOREGROUND=""
export GUM_FILTER_INDICATOR_FOREGROUND="$color2"
export GUM_FILTER_MATCH_FOREGROUND="$color2"
export GUM_FILTER_PROMPT_FOREGROUND="$color2"
export GUM_FILTER_SELECTED_PREFIX_FOREGROUND="$color2"
export GUM_FILTER_SELECTED_PREFIX_BORDER_FOREGROUND="$color2"
}
_migrate_old_config() {
local cfg="${configdir}/config" changed=0
local -A old=( [BORDER]=border [BORDERS_FOREGROUND]=color [spinner]=spinner [QUICKGET]=quickget_cmd )
for key in "${!old[@]}"; do
local f="${configdir}/${old[$key]}"
if [ -f "$f" ] && ! grep -q "^${key}=" "$cfg" 2>/dev/null; then
config_set "$key" "$(cat "$f")" "$cfg"; rm -f "$f"; changed=1
fi
done
if [ -f "${configdir}/icons" ] && ! grep -q '^icons=' "$cfg" 2>/dev/null; then
config_set icons yes "$cfg"; rm -f "${configdir}/icons"; changed=1
fi
[ "$changed" = 1 ] && source "$cfg"
}
define_variables() {
progname="${progname:="${0##*/}"}"
configdir="$HOME/.config/$progname"
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
tmpdir="/tmp"
version='0.70'
# Defaults (overridden by config file below)
BORDER=double
BORDERS_FOREGROUND=$(( RANDOM % 255 + 1 ))
color2=$(( RANDOM % 255 + 1 ))
spinner=globe
icons=no
headers=full
QUICKGET=""
# Load saved config, then migrate any old per-file settings
[ -f "${configdir}/config" ] && source "${configdir}/config"
_migrate_old_config
# color is an alias kept for header/VM-list uses
color="$BORDERS_FOREGROUND"
# Auto-detect QUICKGET if not saved
if [ -z "$QUICKGET" ]; then
if command -v qget >/dev/null 2>&1; then QUICKGET=qget
elif [ -x "${SCRIPT_DIR}/qget" ]; then QUICKGET="${SCRIPT_DIR}/qget"
elif command -v quickget >/dev/null 2>&1; then QUICKGET=quickget
elif [ -x "${SCRIPT_DIR}/quickget" ]; then QUICKGET="${SCRIPT_DIR}/quickget"
else QUICKGET="${SCRIPT_DIR}/qget"
fi
fi
_apply_gum_colors
load_vm_confs
if ! command -v gum >/dev/null 2>&1; then
echo 'You are missing gum! Exiting...' && exit 1
fi
if ! command -v quickemu >/dev/null 2>&1 && ! command -v ./quickemu >/dev/null 2>&1; then
gum style --foreground 1 'You are missing quickemu!'
fi
mainMenuHeight=13
advancedMenuHeight=12
settingsMenuHeight=14
trap 'exit' INT
trap 'exit' EXIT
if command -v termux-info >/dev/null 2>&1; then
TERMUX=1; tmpdir="$(pwd)/tmp"
fi
}
ARCH="x86_64"
generate_supported() {
echo "Extracting OS Editions and Releases..."
rm -rf "$tmpdir/distros"
mkdir -p "$tmpdir/distros"
qget --list-os 2>/dev/null > "$tmpdir/supported"
while read -r get_name; do
echo "$get_name"
qget --show "$get_name" 2>/dev/null > "$tmpdir/distros/${get_name}"
done < "$tmpdir/supported"
}
if_needed() {
if [ ! -f "$tmpdir"/supported ]; then
generate_supported
fi
}
## HELP
show_help() {
clear
show_headers_full
gum style --padding "0 1" --border="$BORDER" --border-foreground="$BORDERS_FOREGROUND" "$title"
}
help_main() {
title=" $progname $version
Uses gum to provide a simple TUI for quickemu and quickget 'https://github.com/quickemu-project/quickemu'
'https://github.com/charmbracelet/gum'
For menus you can use arrow keys or fuzzy filtering and then ENTER
(e + ENTER for exit or b + ENTER for back to main menu)
If is posible choose more options use TAB for highliting desired and then ENTER
Config files are stored at $configdir
As temp folder is used $tmpdir
"
}
## MAIN
gum_choose_os() {
title="Choose OS"
show_header
os=$(gum choose --prompt='Choose OS' < "$tmpdir"/supported)
if [ -z "$os" ]; then
return 1
fi
choices=$("$QUICKGET" "$os" 2>/dev/null | sed 1d | sed '/^$/q')
if [ -z "$choices" ]; then
gum style --foreground 1 "Failed to get OS info for: $os"
return 1
fi
}
gum_choose_release() {
title="Choose release"
show_header
release=$(echo "$choices" | grep 'Releases' | cut -d: -f2 | grep -o '[^ ]*' | gum choose --prompt='Choose release')
}
gum_choose_arch() {
arch_file="$tmpdir/distros/$os"
architectures="x86_64"
if [ -f "$arch_file" ]; then
architectures=$(grep -i "Architectures:" "$arch_file" | cut -d: -f2 | sed "s/^ //")
fi
[ -z "$architectures" ] && architectures="x86_64"
local arch_count
arch_count=$(echo "$architectures" | wc -w)
if [ "$arch_count" -gt 1 ]; then
title="Choose architecture"
show_header
ARCH=$(echo "$architectures" | tr ' ' '\n' | gum choose --prompt="Choose architecture")
ARCH="${ARCH:-x86_64}"
else
ARCH="${architectures}"
fi
}
gum_choose_edition() {
title="Choose edition"
show_header
edition=$(echo "$choices" | grep 'Editions' | cut -d: -f2 | grep -o '[^ ]*' | gum choose --prompt='Choose edition')
}
gum_filter_os() {
os=$(gum filter < "$tmpdir/supported")
choices=$(cat "$tmpdir/distros/${os}")
}
gum_filter_release() {
release=$(echo "$choices" | grep 'Releases:' | cut -d: -f2 | grep -o '[^ ]*' | gum filter --sort)
}
gum_filter_edition() {
edition=$(echo "$choices" | grep 'Editions:' | cut -d: -f2 | grep -o '[^ ]*' | gum filter --sort)
}
gum_filter_arch() {
arch_file="$tmpdir/distros/$os"
architectures="x86_64"
if [ -f "$arch_file" ]; then
architectures=$(grep -i "Architectures:" "$arch_file" | cut -d: -f2 | sed "s/^ //")
fi
[ -z "$architectures" ] && architectures="x86_64"
local arch_count
arch_count=$(echo "$architectures" | wc -w)
if [ "$arch_count" -gt 1 ]; then
title="Filter architecture"
show_header
ARCH=$(echo "$architectures" | tr ' ' '\n' | gum filter --sort --placeholder="architecture (default: x86_64)")
ARCH="${ARCH:-x86_64}"
else
ARCH="${architectures}"
fi
}
gum_choose_VM() {
local names
names=$(list_vm_conf_names)
if [ -n "$names" ]; then
chosen=$(echo "$names" | gum filter --select-if-one)
else
gum style --foreground 1 "Can't!"
fi
}
gum_choose_VM2() {
local names height
names=$(list_vm_conf_names)
if [ -n "$names" ]; then
height=$(echo "$names" | wc -l)
title="Choose VM"
show_header
chosen=$(echo "$names" | gum filter --height "$height")
else
echo "No VMs to run."
fi
#chosen=$(printf '%s\n' "${vms[@]%.conf}" | gum filter --height "$("${vms[@]%.conf}" | wc -l)" --header='Choose VM to run')
}
create_VM() {
gum_filter_os
if [ -z "$os" ]; then exit 100
elif ! echo "$choices" | grep -q "Editions:"; then
gum_filter_release
gum_filter_arch
qget --arch "$ARCH" "$os" "$release"
else
gum_filter_release
gum_filter_edition
gum_filter_arch
qget --arch "$ARCH" "$os" "$release" "$edition"
fi
show_headers
}
create_VM2() {
gum_choose_os
if [ -z "$os" ]; then exit 100
elif [ "$(echo "$choices" | wc -l)" = 1 ]; then
clear
gum_choose_release
gum_choose_arch
qget --arch "$ARCH" "$os" "$release"
if [ -f "${configdir}/default_vm_config" ]; then
echo 'Adding default values to config...'
cat "${configdir}/default_vm_config" >> "$os-$release.conf"
fi
else
clear
gum_choose_release
gum_choose_edition
gum_choose_arch
gum spin --spinner $spinner --show-output --title="Downloading $os $release $edition" -- qget --arch "$ARCH" "$os" "$release" "$edition"
if [ -f "${configdir}/default_vm_config" ]; then
echo 'Adding default values to config...'
cat "${configdir}/default_vm_config" >> "$os-$release-$edition.conf"
fi
fi
echo "
To start your new $os virtual machine use 'run' from menu"
show_headers_small
}
edit_default_VMs_config() {
title="Editing default VM's config..."
show_header
printf 'For example:\nguest_os="linux"\nncpu_cores="4"\nram="4G"\ndisk_size="200G"\ndisplay="spice"\npublic_dir="/home/kuklinistvan/VMs/PublicDirs/linux0"\n'
title="CTRL+D to complete. CTRL+C and esc will cancel"
show_header
gum write > "${configdir}"/default_vm_config
}
edit_VM_config() {
if [ -z "$EDITOR" ]; then
echo "Editor not set! Can't continue!"
else
mapfile -t _vm_list < <(list_vm_conf_names)
height=${#_vm_list[@]}
chosen_name=$(printf '%s\n' "${_vm_list[@]}" | gum filter --height "$height")
${EDITOR} "${chosen_name}.conf"
fi
}
custom_quickemu_command() {
custom=$(echo "edit delete" | grep -o '[^ ]*' | gum choose --header='Edit or delete custom command?')
if [ "$custom" = "edit" ]; then
quickemu
printf '\nEnter quickemu custom command:\n For example:--public-dir ~/Downloads\n:'
read -r command
mkdir -p "$configdir"
echo "$command" > "${configdir}/command"
elif [ "$custom" = "delete" ]; then
rm "${configdir}/command"
fi
}
run_VM() {
if [ -z "$chosen" ]; then
gum style --foreground 1 "No VM selected!"
show_headers
return 1
fi
title="Starting $chosen..."
show_header
if [ -f "${configdir}/command" ]; then
quickemu < "${configdir}/command" -vm "$chosen.conf"
else
quickemu -vm "$chosen.conf"
fi
show_headers
}
gum_choose_running() {
pid_files=( */*.pid )
if [ ${#pid_files[@]} -gt 0 ]; then
mapfile -t running < <(find . -name '*.pid' -printf '%P\n' | sed 's/\.pid$//')
if [ ${#running[@]} -gt 0 ]; then
selected=$(gum choose --select-if-one "${running[@]}")
else
gum style --foreground 1 "Can't!" && selected=""
fi
else
gum style --foreground 1 "Can't!" && selected=""
fi
}
gum_choose_runnings() {
pid_files=( */*.pid )
if [ ${#pid_files[@]} -gt 0 ]; then
mapfile -t running < <(find . -name '*.pid' -printf '%P\n' | sed 's/\.pid$//')
if [ ${#running[@]} -gt 0 ]; then
selected=$(gum choose --select-if-one "${running[@]}")
else
gum style --foreground 1 "Can't!" && selected=""
fi
else
gum style --foreground 1 "Can't!" && selected=""
fi
}
gum_choose_VM_to_delete() {
mapfile -t _vm_list < <(list_vm_conf_names)
GUM_FILTER_HEADER="Choose VM to delete"
GUM_FILTER_HEADER_FOREGROUND=""
if [ ${#_vm_list[@]} -gt 0 ]; then
height=${#_vm_list[@]}
chosen=$(printf '%s\n' "${_vm_list[@]}" | gum filter --height "$height" --no-limit)
echo 'Removing config(s)...'
rm -r "$chosen" ; rm "$chosen".conf
else
echo "No VMs to delete"
fi
}
gum_choose_VM_to_delete2() {
mapfile -t _vm_list < <(list_vm_conf_names)
if [ ${#_vm_list[@]} -gt 0 ]; then
chosen=$(printf '%s\n' "${_vm_list[@]}" | gum choose --select-if-one)
gum confirm "Really delete $chosen" && rm -r "$chosen" && rm "$chosen".conf
show_headers
else
gum style --foreground 1 "No VMs!"
fi
}
gum_choose_VM_to_delete3() {
mapfile -t _vm_list < <(list_vm_conf_names)
if [ ${#_vm_list[@]} -gt 0 ]; then
GUM_FILTER_HEADER="Choose VM to delete"
height=${#_vm_list[@]}
chosen=$(printf '%s\n' "${_vm_list[@]}" | gum filter --height "$height" --no-limit)
delete_VM
else
echo "No VMs to delete"
fi
}
delete_VM() {
if [ -z "$chosen" ]; then
gum style --foreground 1 "No VM selected!"
return 1
fi
for vm_name in $chosen; do
vm_dir="${vm_name}"
vm_conf="${vm_name}.conf"
if [ -d "$vm_dir" ]; then
gum confirm "Really delete $vm_name directory?" && rm -rf "$vm_dir"
fi
if [ -f "$vm_conf" ]; then
gum confirm "Really delete $vm_conf?" && rm -f "$vm_conf"
fi
done
}
## DISK MANAGEMENT
## ADVANCED
# shellcheck disable=SC2016,2034,2153
add_new_distro() {
mkdir -p "$configdir"
echo "add new OS, all lowercase"
NAME="$(gum input --header="NAME" --placeholder="arch")"
echo "add a pretty name for new OS *only if the catch all is not suitable*"
PRETTY_NAME="$(gum input --header="PRETTY_NAME" --placeholder="Arch Linux")"
echo "add a homepage for new OS"
HOMEPAGE="$(gum input --header="HOMEPAGE" --placeholder="https://voidlinux.org/")"
echo "current supported release versions"
RELEASES="$(gum input --header="RELEASES" --placeholder="8 9")"
echo "the editions if new OS has multiple flavours/editions"
EDITIONS="$(gum input --header="EDITIONS" --placeholder="kde gnome")"
echo "base URL for ISO download"
URL="$(gum input --header="URL" --placeholder="https://ddl.bunsenlabs.org/ddl")"
echo "Name of ISO"
ISO="$(gum input --header="ISO" --placeholder="GhostBSD-${RELEASE}-XFCE.iso")"
echo "name of hash file "
CHECKSUM="$(gum input --header="CHECKSUM" --placeholder='${ISO}.sha256sum')"
cat <<EOF > "$configdir/template"
#line 58+
$NAME) PRETTY_NAME="$PRETTY_NAME";;
#line 207+
$NAME \\
#line 292+
$NAME) HOMEPAGE=$HOMEPAGE;;
#line 374+
function releases_$NAME() {
echo $RELEASES
}
function editions_$NAME() {
echo $EDITIONS
}
#line 1176+
function get_$NAME() {
local EDITION="\${1:-}"
local HASH=""
local ISO="$ISO"
local URL="$URL"
HASH="\$(wget -q -O- \${URL}/\$CHECKSUM | grep (\${ISO} | cut -d' ' -f4)"
echo "\${URL}/\${ISO}" "\${HASH}"
}
EOF
diff "$configdir/template" "quickget"
}
# shellcheck disable=SC2154
create_desktop_entry() {
cat <<EOF > "${DESKTOP_FILE}"
[Desktop Entry]
Version=$version
Type=$type
Name=$name
GenericName=$progname
Comment=$comment
Exec=$execmd
Icon=$icon
Terminal=$terminal
X-MultipleArgs=$args
Type=$type
Categories=$categories
StartupNotify=$notify
MimeType=$mime
Keywords=$keyword
EOF
}
test_ISOs_download() {
rm "$tmpdir/test" 2>/dev/null
cd "$tmpdir" || exit
touch "$tmpdir/test"
#qget | sed 1d | cut -d':' -f2 | grep -o '[^ ]*' > supported
os=$(gum filter < "$tmpdir"/supported)
choices=$(qget "$os" | sed 1d)
while read -r get_name; do
echo "Trying $get_name..."
mkdir -p "$tmpdir/_distros/$get_name" && cd "$tmpdir/_distros/$get_name" || exit
releases=$(qget "$get_name" | grep 'Releases:' | cut -f2 | sed 's/^ //' | sed 's/ *$//')
echo "$releases" > releases
editions=$(qget "$get_name" | grep 'Editions:' | cut -f2 | sed 's/^ //' | sed 's/ *$//')
echo "$editions" > editions
if [ -z "$editions" ]; then
for release in $releases; do
echo "$get_name" >> "$tmpdir/test"
timeout 10 qget -t "$get_name" "${release}" >> "$tmpdir/test"
done
else
while read -r release; do
for edition in $editions; do
echo "$get_name" >> "$tmpdir/test"
timeout 10 qget -t "$get_name" "${release}" "${edition}" >> "$tmpdir/test"
done
done < releases
fi
cd "$tmpdir" || exit
done < supported
printf "\nDone"
}
show_ISOs_urls(){
rm -r "$tmpdir/test" 2>/dev/null
cd "$tmpdir" || exit
touch "$tmpdir/test"
qget | sed 1d | cut -d':' -f2 | grep -o '[^ ]*' > supported
while read -r get_name; do
echo "Trying $get_name..."
mkdir -p "$tmpdir/_distros/$get_name" && cd "$tmpdir/_distros/$get_name" || exit
releases=$(qget "$get_name" | grep 'Releases' | cut -d':' -f2 | sed 's/^ //' | sed 's/ *$//')
echo "$releases" > releases
editions=$(qget "$get_name" | grep 'Editions' | cut -d':' -f2 | sed 's/^ //' | sed 's/ *$//')
echo "$editions" > editions
if [ -z "$editions" ]; then
for release in $releases; do
echo "$get_name" >> "$tmpdir/test"
timeout 5 qget -s "$get_name" "${release}" >> "$tmpdir/test" #&& $(killall zsync >> /dev/null)
done
else
while read -r release; do
for edition in $editions; do
echo "$get_name" >> "$tmpdir/test"
timeout 5 qget -s "$get_name" "${release}" "${edition}" >> "$tmpdir/test" #&& $(killall zsync >> /dev/null)
done
done < releases
fi
cd "$tmpdir" || exit
done < supported
printf "\nDone"
}
get_ssh_port() {
port=$(grep 'ssh' < "$selected".ports | cut -d',' -f2)
}
ssh_into() {
gum_choose_running
if [ -n "$selected" ]; then
get_ssh_port
username=$(gum input --prompt "$selected user? ")
ssh "$username"@localhost -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new -p "$port"
show_headers
fi
}
open_distro_homepage(){
gum_filter_os
qget -o "${os}" >/dev/null 2>&1 &
}
kill_vm() {
gum_choose_running
if [ -n "$selected" ]; then
echo "${selected}"
gum confirm "Really kill $selected?" && pid=$(cat "$selected".pid) && kill "$pid"
show_headers
fi
}
kill_vms() {
gum_choose_runnings
if [ -n "$selected" ]; then
for vm_name in "${selected[@]}"; do
gum confirm "Really kill $vm_name?"
pid=$(cat "${vm_name}.pid")
kill "$pid"
done
show_headers
fi
}
headers_small_or() {
printf '\n\nmini:\n'; show_headers_mini
printf '\n\nsmaller:\n'; show_headers_smaller
printf '\n\nsmall:\n'; show_headers_small
printf '\n\nfull:\n'; show_headers_full
printf '\n\ncurrent:\n'; show_headers
local choice
choice=$(printf 'mini\nsmaller\nsmall\nfull\ncurrent' | gum filter --height 9 --header "Your choice?")
[ -z "$choice" ] || [ "$choice" = "current" ] && return
headers="$choice"
config_set headers "$headers" "${configdir}/config"
show_headers
}
change_borders() {
title="Change borders style"
show_header
local val
val=$(printf 'none\nhidden\nnormal\nrounded\nthick\ndouble' | gum filter --height 9)
[ -z "$val" ] && return
BORDER="$val"
config_set BORDER "$BORDER" "${configdir}/config"
}
change_color() {
title="Accent color (1-255 or 'random')"
show_header
local val
val=$(printf 'random\n' | gum filter --height 4 --prompt="Enter number (1-255): " --no-strict)
[ -z "$val" ] && return
if [ "$val" = "random" ]; then
BORDERS_FOREGROUND=$(( RANDOM % 255 + 1 ))
else
BORDERS_FOREGROUND="$val"
fi
color="$BORDERS_FOREGROUND"
config_set BORDERS_FOREGROUND "$BORDERS_FOREGROUND" "${configdir}/config"
_apply_gum_colors
}
change_quickget_cmd() {
title="Choose download command"
show_header
local cmd
cmd=$(printf 'qget\nquickget\n./qget\n./quickget' | gum filter --height 4 --prompt="Choose or type: " --no-strict)
[ -z "$cmd" ] && return
QUICKGET="$cmd"
config_set QUICKGET "$QUICKGET" "${configdir}/config"
}
change_spinner() {
local val
val=$(printf 'line\ndot\nminidot\njump\npulse\npoints\nglobe\nmoon\nmonkey\nmeter\nhamburger' | gum filter --height 14)
[ -z "$val" ] && return
spinner="$val"
config_set spinner "$spinner" "${configdir}/config"
}
icons_or() {
if gum confirm "Use icons? (requires Nerd Fonts)"; then
icons=yes
else
icons=no
fi
config_set icons "$icons" "${configdir}/config"
use_icons
show_headers
}
use_icons() {
[ "${icons:-no}" = "yes" ] && icons=yes || icons=""
}
## HEADERS
show_header() {
gum style --padding "0 1" --border="$BORDER" --border-foreground="$BORDERS_FOREGROUND" "$title"
}
show_version_qemu() {
qemu-x86_64 -version | sed 2d | cut -d' ' -f3
}
show_version_quickemu() {
quickemu --version | grep "ERROR! QEMU not found" && echo "QEMU is missing!" || quickemu --version
}
show_editor() {
if [ -z "$EDITOR" ]; then
echo ' editor Not set!'
else
echo " editor $EDITOR"
fi
}
show_vms() {
if [ ${#vms[@]} -eq 0 ]; then
gum style --foreground 1 "No VMs!"
else
echo "${vms[@]%.*}" | tr " " "\n"
fi
}
show_custom_small() {
if [ -f "${configdir}/command" ]; then
gum style --bold --foreground "$color2" "command:"
gum style "quickemu $(cat "${configdir}/command")"
fi
if [ -f "${configdir}/default_vm_config" ]; then
gum style --bold --foreground "$color2" "default config:"
gum style "$(cat "${configdir}/default_vm_config")"
fi
}
show_custom() {
show_custom_small
gum style --bold --foreground "$color2" "color:"; gum style "$BORDERS_FOREGROUND"
gum style --bold --foreground "$color2" "borders:"; gum style "$BORDER"
gum style --bold --foreground "$color2" "spinner:"; gum style "$spinner"
gum style --bold --foreground "$color2" "icons:"; gum style "${icons:-no}"
gum style --bold --foreground "$color2" "headers:"; gum style "${headers:-full}"
}
show_header_vms() {
pid_files=(*/*.pid)
load_vm_confs
vms_running=()
vms_not=()
vms_vm=$(gum style --bold --foreground "$color2" "ready:")
vms_run=""
if [ -n "$(find . -name '*.pid')" ]; then
for pid_file in "${pid_files[@]}"; do
instance_name=$(basename "$pid_file" .pid)
vms_running+=("$instance_name")
done
if [ "$icons" == yes ]; then
running_logo=$(gum style --foreground "$color" --bold ".")
else
running_logo=$(gum style --foreground "$color" --bold ">")
fi
for instance in "${vms_running[@]}"; do
vms_run+="$running_logo$instance "
done
fi
mapfile -t vms_not < <(comm -23 <(printf "%s\n" "${vms[@]}" | rev | cut -d'.' -f2-9 | rev | sort) <(printf "%s\n" "${vms_running[@]}" | sort))
vms_not_next=$(gum style --align center < <(printf '%s\n' "${vms_not[@]}"))
if [ -n "$(find . -name '*.pid')" ]; then
vms_run_next=$(echo "$vms_run" | tr " " "\n")
vms_header=$(gum join --vertical --align center "$vms_vm" "$vms_run_next" "$vms_not_next")
else
vms_header=$(gum join --vertical --align center "$vms_vm" "$vms_not_next")
fi
vms_border=$(gum style --padding "0 1" --border="$BORDER" --border-foreground "$color" "$vms_header")
header_vms=$(gum join --vertical --align left "$vms_border" "$tip_border")
}
show_header_tip() {
# Random OS
tip3=$(shuf -n 1 "$tmpdir/supported" 2>/dev/null)
if [ -z "$tip3" ]; then
header_tip=$(gum style --padding "0 1" --border="$BORDER" --border-foreground "$color" "No OS data — run regenerate supported")
return
fi
# Show colored Tip:
tip1=$(gum style --bold --foreground "$color2" "Tip: ")
# Show try
tip2=$(gum style "try ")
# Colorize random OS name
tip4=$(gum style --bold --foreground="$color" "$tip3")
# Join together 1
tip5=$(gum join "$tip1" "$tip2" "$tip4")
# Show info of random OS
tip6=$(qget --show "$tip3" 2>/dev/null | awk '
/Website:/ ||
/Description:/ ||
/Releases:/ ||
/Editions:/ ||
/Architectures:/
')
# Set tip lenght (wrap)
tip7=$(gum style --width=77 "$tip6")
# Join again
tip8=$(gum join --vertical --align top "$tip5" "$tip7")
# Show final tip
header_tip=$(gum style --padding "0 1" --border="$BORDER" --border-foreground "$color" "$tip8")
}
show_header_tip_without() {
# Random OS
tip3=$(shuf -n 1 "$tmpdir/supported" 2>/dev/null)
if [ -z "$tip3" ]; then
header_tip=$(gum style --padding "0 1" --border="$BORDER" --border-foreground "$color" "No OS data — run regenerate supported")
return
fi
# Show colored Tip:
tip1=$(gum style --bold --foreground "$color2" "Tip: ")
# Show try
tip2=$(gum style "try ")
# Colorize random OS name
tip4=$(gum style --bold --foreground="$color" "$tip3")
# Join together 1
tip5=$(gum join "$tip1" "$tip2" "$tip4")
# Show info of random OS
tip6=$(qget --show "$tip3" 2>/dev/null | awk '/Website:/ ||
/Releases:/ ||
/Editions:/ ||
/Architectures:/
')
# Join again
tip8=$(gum join --vertical --align top "$tip5" "$tip6")
# Show final tip
header_tip=$(gum style --padding "0 1" --border="$BORDER" --border-foreground "$color" "$tip8")
}
show_headers_full() {
logo0=$(gum style " simple VMs runner")
logo1=$(gum style --foreground "$color2" " ▄▄▄▄ ▄▄▄▄ ▄ ▄ ▄
█ █  █ █ █ █
█ █  █ █ █ █
█▄▀▄  █ █▄▄█ █")
logo2=$(gum style "v$version")
logo3=$(gum style --foreground "$color2" "▀")
logo4=$(gum style " for ")
logo5=$(gum style "quickemu")
logo6=$(gum join --align center --vertical "$logo0" "$logo1")
logo7=$(gum join "$logo2" "$logo3" "$logo4" "$logo5")
logo8=$(gum join --vertical "$logo6" "$logo7")
header_logo=$(gum style --align left --padding "0 1" --border=rounded --border-foreground "$color" "$logo8" )
header_dep=$(gum style --padding "0 1" --border="$BORDER" --border-foreground "$color" " qemu $(show_version_qemu)
quickemu $(show_version_quickemu)
$(show_editor)")
show_header_vms
show_header_tip
custom1=$(gum style --bold --foreground "$color2" "workdir:")
custom2=$(gum style "$(pwd)")
custom3=$(gum join --vertical --align center "$custom1" "$custom2" "$(show_custom)")
header_custom=$(gum style --padding "0 1" --border="$BORDER" --border-foreground="$color" "$custom3")
header1=$(gum join --vertical --align right "$header_logo" "$header_vms")
header2=$(gum join --vertical "$header_dep" "$header_custom" "$header_tip")
gum join --align top "$header1" "$header2"
}
show_headers_small() {
logo1=$(gum style --foreground "$color2" " ▄▄▄▄
█ █
█ █
█▄▀▄")
logo2=$(gum style "v$version")
logo3=$(gum style --foreground "$color2" "▀")
logo4=$(gum join "$logo2" "$logo3")
logo5=$(gum join --vertical "$logo1" "$logo4")
header_logo=$(gum style --padding "0 1" --border=rounded --border-foreground "$color" "$logo5" )
show_header_vms
show_header_tip
custom1=$(gum style --bold --foreground "$color2" "workdir:")
custom2=$(gum style "$(pwd)")
custom3=$(gum join --vertical --align center "$custom1" "$custom2" "$(show_custom_small)")
header_custom=$(gum style --padding "0 1" --border="$BORDER" --border-foreground="$color" "$custom3")
header12=$(gum join --vertical "$header_tip" "$header_custom")
header34=$(gum join "$header_logo" "$header12")
gum join --align top --vertical "$header34" "$header_vms"
}
show_headers_smaller() {
logo1=$(gum style --foreground "$color2" " ▄▄▄▄
█ █
█ █
█▄▀▄")
logo2=$(gum style "v$version")
logo3=$(gum style --foreground "$color2" "▀")
logo4=$(gum join "$logo2" "$logo3")
logo5=$(gum join --vertical "$logo1" "$logo4")
header_logo=$(gum style --padding "0 1" --border=rounded --border-foreground "$color" "$logo5" )
show_header_vms
show_header_tip_without
custom1=$(gum style --bold --foreground "$color2" "workdir:")
custom2=$(gum style "$(pwd)")
custom3=$(gum join --vertical --align center "$custom1" "$custom2" "$(show_custom_small)")
header_custom=$(gum style --padding "0 1" --border="$BORDER" --border-foreground="$color" "$custom3")
header12=$(gum join --vertical "$header_tip" "$header_custom")
header34=$(gum join "$header_logo" "$header12")
gum join --align top --vertical "$header34" "$header_vms"
}
show_headers_mini() {
show_header_vms
show_header_tip_without
custom1=$(gum style --bold --foreground "$color2" "workdir:")
custom2=$(gum style "$(pwd)")
custom3=$(gum join --vertical --align center "$custom1" "$custom2" "$(show_custom_small)")
header_custom=$(gum style --padding "0 1" --border="$BORDER" --border-foreground="$color" "$custom3")
header12=$(gum join --vertical "$header_tip" "$header_custom")
header34=$(gum join "$header_logo" "$header12")
gum join --align top --vertical "$header34" "$header_vms"
}
show_headers() {
case "${headers:-full}" in
mini) show_headers_mini ;;
smaller) show_headers_smaller ;;
small) show_headers_small ;;
*) show_headers_full ;;
esac
}
## MENU
show_menus() {
if [ "$icons" == yes ]; then
show_menu_main_icons
else
show_menu_main
fi
}
show_menu_main() {
while true
do
start=$(echo "create
run
OS homepage
ssh into
kill
delete
advanced
settings
help
exit $progname" | gum filter --height "$mainMenuHeight")
# from choose: --selected '󰜎 run'
case $start in
'create' ) create_VM;;
'run' ) gum_choose_VM && run_VM;;
'OS homepage' ) open_distro_homepage;;
'ssh into' ) ssh_into;;
'kill' ) kill_vm;;
'delete' ) gum_choose_VM_to_delete;;
'advanced' ) show_menu_advanced;;
'settings' ) show_menu_settings;;
'help' ) help_main; show_help;;
"exit $progname" ) exit 0;;
esac
done
}
show_menu_main_icons() {
while true
do
start=$(echo " create
󰜎 run
󰖟 OS homepage
 ssh into
 kill
󰆳 delete
 advanced
 settings
󰘥 help
󰩈 exit $progname" | gum filter --height "$mainMenuHeight")
# from choose: --selected '󰜎 run'
case $start in
' create' ) create_VM;;
'󰜎 run' ) gum_choose_VM && run_VM;;
'󰖟 OS homepage' ) open_distro_homepage;;
' ssh into' ) ssh_into;;
' kill' ) kill_vm;;
'󰆳 delete' ) gum_choose_VM_to_delete;;
' advanced' ) show_menu_advanced_icons;;
' settings' ) show_menu_settings_icons;;
'󰘥 help' ) help_main; show_help;;
"󰩈 exit $progname" ) exit 0;;
esac
done
}
show_menu_advanced() {
while true
do
title="advanced"
show_header
start=$(echo "test ISOs download
show ISOs URLs
set default config for VMs
edit VM config
disk management
custom quickemu command
add new distro
create desktop entry
back to main menu
exit $progname" | gum filter --height "$advancedMenuHeight")
case $start in
'set default config for VMs' ) edit_default_VMs_config;;
'edit VM config' ) edit_VM_config;;
'disk management' ) show_menu_disk;;
'custom quickemu command' ) custom_quickemu_command;;
'add new distro' ) add_new_distro;;
'create desktop entry' ) create_desktop_entry;;
'test ISOs download' ) test_ISOs_download;;
'show ISOs URLs' ) show_ISOs_urls;;
'back to main menu') clear; show_headers; break;;
"exit $progname" ) exit 0;;
esac
done
}
show_menu_advanced_icons() {
while true
do
title="advanced"
show_header
start=$(echo "󰙨 test ISOs download
 show ISOs URLs
 set default config for VMs
󱋆 edit VM config
󰋊 disk management
 custom quickemu command
󰎔 add distro
 back to main menu
󰩈 exit $progname" | gum filter --height "$advancedMenuHeight")
case $start in
' set default config for VMs' ) edit_default_VMs_config;;
'󱋆 edit VM config' ) edit_VM_config;;
'󰋊 disk management' ) show_menu_disk;;
' custom quickemu command' ) custom_quickemu_command;;
'󰎔 add distro' ) add_new_distro;;
'󰙨 test ISOs download' ) test_ISOs_download;;
' show ISOs URLs' ) show_ISOs_urls;;
' back to main menu') clear; show_headers; break;;
"󰩈 exit $progname" ) exit 0;;
esac
done
}
show_menu_settings() {
while true
do
title="settings"
show_header
start=$(echo "update $progname
regenerate supported
download command
icons
accent color
borders style
spinner
headers
back to main menu
exit $progname" | gum filter --height "$settingsMenuHeight")
case $start in
"update $progname" ) update_quicktui;;
'regenerate supported' ) generate_supported;;
'download command' ) change_quickget_cmd;;
'icons' ) icons_or;;
'accent color' ) change_color;;
'borders style' ) change_borders;;
'spinner' ) change_spinner;;
'headers' ) headers_small_or;;
'back to main menu') clear; show_headers; break;;
"exit $progname" ) exit 0;;
esac
done
}
show_menu_settings_icons() {
while true
do
title="settings"
show_header
start=$(echo " update $progname
 regenerate supported
󱑤 download command
󱌝 icons
 accent color
󰴱 borders style
 spinner
󰛼 headers
 back to main menu
󰩈 exit $progname" | gum filter --height "$settingsMenuHeight")
case $start in
" update $progname" ) update_quicktui;;
' regenerate supported' ) generate_supported;;
'󱑤 download command' ) change_quickget_cmd;;
'󱌝 icons' ) icons_or;;
' accent color' ) change_color;;
'󰴱 borders style' ) change_borders;;
' spinner' ) change_spinner;;
'󰛼 headers' ) headers_small_or;;
' back to main menu') clear; show_headers; break;;
"󰩈 exit $progname" ) exit 0;;
esac
done
}
## RUN
define_variables
multi_instance_check
if_needed
use_icons
show_headers
show_menus