diff --git a/README.md b/README.md index 75f3277..a2cf843 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# qemu-arch-linux-automation +# vmctl -Scripts and playbooks to automate the installation, configuration and -management of Arch Linux VMs in qemu. +`vmctl` is a script that automates the installation, provisioning and +management of Arch Linux virtual machines. ## Dependencies @@ -11,33 +11,59 @@ management of Arch Linux VMs in qemu. ## Installation -- Add the path to the `src` directory to your `PATH` environment variable -- Create a symbolic link to `src/` directory to your `PATH` environment variable +Just copy the `vmctl` script anywhere in your `PATH`, or clone the repository +and create a symbolic link to `vmctl` in your `PATH`. ## Usage ### Install an Arch Linux virtual machine +The `vmctl install` command can be used to create an Arch Linux virtual machine +on the fly, wherever you are. It does the following: + +- It downloads the latest Arch Linux installer ISO image if none was downloaded + or a new one is available. +- It creates a `qemu` disk file. +- It boots a new KVM that uses the ISO file as as CDROM and automates the + installation of Arch Linux in the virtual machine (no user prompt required). +- It can install extra packages and run custom post-installation provisioning scripts. + ```text -Usage: qemu-arch install [-o ] [-a ] [-s ] +Usage: vmctl install [-o ] [-a ] [-s ] [-m ] [-h ] [-P ] [-u ] [-p ] [-z ] [-l ] [-M ] --o Path of the output disk image (default: ./arch.img) --a Target architecture (default: x86_64) --s Disk size (default: 8G) --m RAM size in KB (default: 2048) --h VM hostname (default: qemu) --P Root password. If not specified it will be prompted --u Username for the main non-root user --p Password for the non-root user. If not specified - it will be prompted --z System timezone (default: UTC) --l System locale (default: en_US.UTF-8) --M Arch Linux download mirror URL - (default: http://mirror.cj2.nl/archlinux/iso/latest/) - Consult https://archlinux.org/download/ for a - full list of the available download mirrors. +-o Path of the output disk image (default: ./arch.img) +-a Target architecture (default: x86_64) +-s Disk size (default: 8G) +-m RAM size in KB (default: 2048) +-h VM hostname (default: qemu) +-P Root password. If not specified it will be prompted +-u Username for the main non-root user +-p Password for the non-root user. If not specified it will be prompted +-z System timezone (default: UTC) +-l System locale (default: en_US.UTF-8) +-M Arch Linux download mirror URL (default: http://mirror.cj2.nl/archlinux/iso/latest/) + Consult https://archlinux.org/download/ for a full list of the available download mirrors. +``` + +If a required option is not specified on the command line then it will be +interactively prompted to the user (defaults are available for most of the +options). + +Non-interactive example: + +```bash +vmctl install \ + -h my_vm \ + -a x86_64 \ + -s 16G \ + -m 2048 \ + -P root \ + -u myuser \ + -p password \ + -z Europe/Amsterdam \ + -o arch-base.img ``` If you want to install an extra list of packages besides the default ones, then @@ -52,11 +78,6 @@ as the disk image file. The keyring population process may currently (as of March 2022) take a long time. This is a [known issue](https://www.reddit.com/r/archlinux/comments/rbjbcr/pacman_keyring_update_taking_too_long/). -As a workaround, if you want to speed up the OS installation process, you can -temporarily disable pacman keyring checks upon package installation by -uncommenting the relevant lines in `src/helpers/install.sh` (function: -`install_os`). - ### Resize an existing image ```bash diff --git a/src/actions/install.sh b/src/actions/install.sh deleted file mode 100755 index 753fef7..0000000 --- a/src/actions/install.sh +++ /dev/null @@ -1,109 +0,0 @@ -export default_imgfile=arch.img -export default_architecture=x86_64 -export default_disk_size=8G -export default_memory=2048 -export default_hostname=qemu -export default_root_password=root -export default_username=user -export default_user_password=password -export default_timezone=UTC -export default_locale=en_US.UTF-8 -export default_img_download_page='http://mirror.cj2.nl/archlinux/iso/latest/' -export isofile=archlinux-latest.iso - -imgfile= -architecture= -disk_size= -memory= -hostname= -root_password= -username= -user_password= -timezone= -locale="$default_locale" -img_download_page="$default_img_download_page" - -function usage() { - echo "Install an Arch Linux system on a QEMU disk image" - echo - echo "Usage: $(basename "$0") install [-o ] [-a ] [-s ]" - echo -e "\t[-m ] [-h ] [-P ] [-u ]" - echo -e "\t[-p ] [-z ] [-l ] [-M ]" - echo - echo -e "-o \t\tPath of the output disk image (default: ./arch.img)" - echo -e "-a \t\tTarget architecture (default: x86_64)" - echo -e "-s \t\t\tDisk size (default: 8G)" - echo -e "-m \t\t\tRAM size in KB (default: 2048)" - echo -e "-h \t\t\tVM hostname (default: qemu)" - echo -e "-P \t\tRoot password. If not specified it will be prompted" - echo -e "-u \t\tUsername for the main non-root user" - echo -e "-p \tPassword for the non-root user. If not specified it will be prompted" - echo -e "-z \t\t\tSystem timezone (default: UTC)" - echo -e "-l \t\t\tSystem locale (default: en_US.UTF-8)" - echo -e "-M \t\tArch Linux download mirror URL (default: http://mirror.cj2.nl/archlinux/iso/latest/)" - echo -e "\t\t\t\tConsult https://archlinux.org/download/ for a full list of the available download mirrors." - echo - echo "If you want to install an extra list of packages besides the default ones, then" - echo "specify them in a file named PKGLIST in the same directory as the disk image file." - echo - echo "If you want to run a custom post-installation script after the core system has been" - echo "installed, then create a custom script named post-install.sh in the same directory as" - echo "the disk image file". - exit 1 -} - - -optstring=':o:a:s:m:h:P:u:p:z:l:M:' -[[ "$1" == '--help' ]] && usage - -while getopts ${optstring} arg; do - case ${arg} in - o) imgfile="${OPTARG}";; - a) architecture="${OPTARG}";; - s) disk_size="${OPTARG}";; - m) memory="${OPTARG}";; - h) hostname="${OPTARG}";; - P) root_password="${OPTARG}";; - u) username="${OPTARG}";; - p) user_password="${OPTARG}";; - z) timezone="${OPTARG}";; - l) locale="${OPTARG}";; - M) img_download_page="${OPTARG}";; - ?) - echo "Invalid option: -${OPTARG}" >&2 - usage;; - esac -done - -[ -z "$imgfile" ] && read -p "Output disk image file [$default_imgfile]: " imgfile -[ -z "$architecture" ] && read -p "Architecture [$default_architecture]: " architecture -[ -z "$disk_size" ] && read -p "Disk size [$default_disk_size]: " disk_size -[ -z "$memory" ] && read -p "Memory in KB [$default_memory]: " memory -[ -z "$hostname" ] && read -p "Hostname [$default_hostname]: " hostname -[ -z "$root_password" ] && read -sp "Root password [$default_root_password]: " root_password && echo -[ -z "$username" ] && read -p "Non-admin username [$default_username]: " username -[ -z "$user_password" ] && read -sp "Non-admin user password [$default_user_password]: " user_password && echo -[ -z "$timezone" ] && read -p "Timezone [$default_timezone]: " timezone -[ -z "$locale" ] && read -p "Locale [$default_locale]: " locale - -for var in imgfile \ - architecture \ - disk_size \ - memory \ - hostname \ - root_password \ - username \ - user_password \ - timezone \ - locale - do - default_var=default_$var - [ -z "${!var}" ] && declare ${var}=${!default_var} - export $var - done - -source "$srcdir/helpers/install.sh" -download_latest_arch_iso -create_disk_image -install_os - diff --git a/src/helpers/common.sh b/src/helpers/common.sh deleted file mode 100644 index 5b49080..0000000 --- a/src/helpers/common.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -export GREEN='\033[0;32m' -export RED='\033[0;31m' -export WHITE='\033[0;37m' -export NORMAL='\033[0m' - diff --git a/src/qemu-arch b/src/qemu-arch deleted file mode 100755 index 1d664dd..0000000 --- a/src/qemu-arch +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -function usage() { - actions=$(find "$srcdir/actions" -maxdepth 1 -name '*.sh' | xargs basename | cut -d. -f1) - cat < - -Run $(basename "$0") --help for more details. -Available actions: - -$actions -EOF - - exit 1 -} - -if [ -L "$0" ]; then - export srcdir="$(dirname "$(readlink -f "$0")")" -else - export srcdir="$(cd "$(dirname "$0")" && pwd)" -fi - -source "$srcdir/helpers/common.sh" -action=$1 -shift - -case "$action" in - install) source "$srcdir/actions/install.sh";; - *) usage;; -esac - diff --git a/src/helpers/install.sh b/vmctl old mode 100644 new mode 100755 similarity index 63% rename from src/helpers/install.sh rename to vmctl index eefbf6b..c645ea1 --- a/src/helpers/install.sh +++ b/vmctl @@ -1,3 +1,77 @@ +#!/bin/bash + +######################### +# Shared constants +export GREEN='\033[0;32m' +export RED='\033[0;31m' +export WHITE='\033[0;37m' +export NORMAL='\033[0m' +######################### + + +############################################ +# Default values for configuration variables + +export default_imgfile=arch.img +export default_architecture=x86_64 +export default_disk_size=8G +export default_memory=2048 +export default_hostname=qemu +export default_root_password=root +export default_username=user +export default_user_password=password +export default_timezone=UTC +export default_locale=en_US.UTF-8 +export default_img_download_page='http://mirror.cj2.nl/archlinux/iso/latest/' +export isofile=archlinux-latest.iso +############################################ + + +################ +# install script + +# Shared variables +imgfile= +architecture= +disk_size= +memory= +hostname= +root_password= +username= +user_password= +timezone= +locale="$default_locale" +img_download_page="$default_img_download_page" + +function install_usage() { + echo "Install an Arch Linux system on a QEMU disk image" + echo + echo "Usage: $(basename "$0") install [-o ] [-a ] [-s ]" + echo -e "\t[-m ] [-h ] [-P ] [-u ]" + echo -e "\t[-p ] [-z ] [-l ] [-M ]" + echo + echo -e "-o\t\t\tPath of the output disk image (default: ./arch.img)" + echo -e "-a\t\t\t\tTarget architecture (default: x86_64)" + echo -e "-s\t\t\t\tDisk size (default: 8G)" + echo -e "-m\t\t\t\tRAM size in KB (default: 2048)" + echo -e "-h\t\t\t\tVM hostname (default: qemu)" + echo -e "-P\t\t\t\tRoot password. If not specified it will be prompted" + echo -e "-u\t\t\tUsername for the main non-root user" + echo -e "-p\t\tPassword for the non-root user. If not specified it will be prompted" + echo -e "-z\t\t\t\tSystem timezone (default: UTC)" + echo -e "-l\t\t\t\tSystem locale (default: en_US.UTF-8)" + echo -e "-M\t\t\tArch Linux download mirror URL (default: http://mirror.cj2.nl/archlinux/iso/latest/)" + echo -e "\t\t\t\t\tConsult https://archlinux.org/download/ for a full list of the available download mirrors." + echo + echo "If you want to install an extra list of packages besides the default ones, then" + echo "specify them in a file named PKGLIST in the same directory as the disk image file." + echo + echo "If you want to run a custom post-installation script after the core system has been" + echo "installed, then create a custom script named post-install.sh in the same directory as" + echo "the disk image file". + exit 1 +} + function download_latest_arch_iso() { latest_iso=$( curl -s "$img_download_page" | @@ -283,3 +357,90 @@ EOF echo "--- Log closed at $(date)" >> "$logfile" } +function install() { + optstring=':o:a:s:m:h:P:u:p:z:l:M:' + [[ "$1" == '--help' ]] && install_usage + + while getopts ${optstring} arg; do + case ${arg} in + o) imgfile="${OPTARG}";; + a) architecture="${OPTARG}";; + s) disk_size="${OPTARG}";; + m) memory="${OPTARG}";; + h) hostname="${OPTARG}";; + P) root_password="${OPTARG}";; + u) username="${OPTARG}";; + p) user_password="${OPTARG}";; + z) timezone="${OPTARG}";; + l) locale="${OPTARG}";; + M) img_download_page="${OPTARG}";; + ?) + echo "Invalid option: -${OPTARG}" >&2 + install_usage;; + esac + done + + [ -z "$imgfile" ] && read -p "Output disk image file [$default_imgfile]: " imgfile + [ -z "$architecture" ] && read -p "Architecture [$default_architecture]: " architecture + [ -z "$disk_size" ] && read -p "Disk size [$default_disk_size]: " disk_size + [ -z "$memory" ] && read -p "Memory in KB [$default_memory]: " memory + [ -z "$hostname" ] && read -p "Hostname [$default_hostname]: " hostname + [ -z "$root_password" ] && read -sp "Root password [$default_root_password]: " root_password && echo + [ -z "$username" ] && read -p "Non-admin username [$default_username]: " username + [ -z "$user_password" ] && read -sp "Non-admin user password [$default_user_password]: " user_password && echo + [ -z "$timezone" ] && read -p "Timezone [$default_timezone]: " timezone + [ -z "$locale" ] && read -p "Locale [$default_locale]: " locale + + for var in imgfile \ + architecture \ + disk_size \ + memory \ + hostname \ + root_password \ + username \ + user_password \ + timezone \ + locale + do + default_var=default_$var + [ -z "${!var}" ] && declare ${var}=${!default_var} + export $var + done + + download_latest_arch_iso + create_disk_image + install_os +} +################ + + +################## +# Main script + +function usage() { + actions=(install) + cat < + +Run $(basename "$0") --help for more details. +Available actions: + +$actions +EOF + + exit 1 +} + +function main() { + action=$1 + shift + + case "$action" in + install) install $*;; + *) usage;; + esac +} + +main $* + +##################