Skip to content

Commit

Permalink
Add helpers for creating VMs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jackenmen committed Mar 30, 2024
1 parent f73f7dd commit 8682b55
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 3 deletions.
57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ A few *little* scripts for things I do often so that I can type less
or make fewer clicks.\
I'm just a bit lazy...

Only tested on Windows.\
`git-` scripts are Bash scripts so they should work on Unix systems as well,
but the AutoHotkey and Batch scripts will obviously only work on Windows.
Scripts that aren't OS-specific (`bin/virt-*`, `bin/*.cmd`, `ahk_scripts/*`) have been tested on Linux and Windows.\
`bin/virt-*` scripts are Linux-specific and require `libvirt` and `virtinst` packages.\
`bin/*.cmd` and `ahk_scripts/*` scripts are Windows-specific.

## Usage/Installation

Expand Down Expand Up @@ -111,6 +111,57 @@ before all other branches. This is meant to make it easier to find *your* recent
Each entry on the branch list takes two lines which, while making it less concise,
allows it to show more of the commit message and is, arguably, more readable.

### vm-run

This scripts run a temporary VM with the given Linux image.

Image's default user is set up with the `~/.ssh/id_rsa` key and additionally,
`vm` user with `vm` password and the aforementioned key is also created.

See documentation of `vm-image-add` for details about image management.

Usage: `vm-run <image_name>`

The domain (VM) name will be set to `tmp-<image_name>`, optionally suffixed with
a number (`tmp-<image_name>-2`) when a machine with same name exists.

### vm-connect

Connect to a VM created by `vm-run` or `libvirt`.

In case of VMs created by `vm-run`, `<vm_name>` is equivalent to image name
with a `tmp-` prefix and an optional `-N` suffix when multiple VMs with same name
are running at the same time, e.g. `tmp-ubuntu2204` or `tmp-ubuntu2204-2`.

Usage: `vm-connect <vm_name>`

### vm-image-add

Add a Linux VM image for use by `vm-run`.

Usage: `vm-image-add <image_name> <sums_url> <sum_type> <filename_pattern>`

The script expects to be able to download a Linux image based on
a provided `CHECKSUMS` file in a format returned by `sha256sum`/`sha512sum` program
ran with or without `--tag` option. `<filename_pattern>` is used to extract
the relevant checksum. This setup is generally supported by the data that is provided
by the distro maintainers though there are 2 notable outliers:
- Red Hat Enterprise Linux - requires subscription
- Oracle Linux - does not provide checksum file in a parsable format

You can find examples for different distros at:
https://github.com/Jackenmen/dotfiles/tree/main/private_dot_config/little-helpers-vm-images

### vm-image-fetch

Fetch latest Linux VM image (added through `vm-image-add`) with the given name.

This tool is typically invoked automatically.

Usage: `vm-image-fetch [-f] <image_name>`

Without the `-f` flag, the image only gets fetched when it isn't already in cache.

## Available AHK scripts (`ahk_scripts/` directory)

### win_terminal_hotkeys
Expand Down
40 changes: 40 additions & 0 deletions bin/vm-connect
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/bash
#
# Connect to a VM created by `vm-run` or `libvirt`.
#
# In case of VMs created by `vm-run`, `<vm_name>` is equivalent to image name
# with a `tmp-` prefix and an optional `-N` suffix when multiple VMs with same name
# are running at the same time, e.g. `tmp-ubuntu2204` or `tmp-ubuntu2204-2`.
#
# Usage: `vm-connect <vm_name>`
#
#
# Copyright 2024 Jakub Kuczys (https://github.com/Jackenmen)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eo pipefail

help() {
echo 'Usage: vm-connect <vm_name>'
}

vm_name=$1
if [[ -z "$vm_name" ]]; then
help
exit 2
fi

vm_ip=$(virsh domifaddr "$vm_name" | awk -F'[ /]+' '/^[[:blank:]]vnet/ {print $5}' | head -1)

ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "vm@$vm_ip"
81 changes: 81 additions & 0 deletions bin/vm-image-add
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/bin/bash
#
# Add a Linux VM image for use by `vm-run`.
#
# Usage: `vm-image-add <image_name> <sums_url> <sum_type> <filename_pattern>`
#
# The script expects to be able to download a Linux image based on
# a provided `CHECKSUMS` file in a format returned by `sha256sum`/`sha512sum` program
# ran with or without `--tag` option. `<filename_pattern>` is used to extract
# the relevant checksum. This setup is generally supported by the data that is provided
# by the distro maintainers though there are 2 notable outliers:
# - Red Hat Enterprise Linux - requires subscription
# - Oracle Linux - does not provide checksum file in a parsable format
#
# You can find examples for different distros at:
# https://github.com/Jackenmen/dotfiles/tree/main/private_dot_config/little-helpers-vm-images
#
#
# Copyright 2024 Jakub Kuczys (https://github.com/Jackenmen)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eo pipefail

help() {
echo 'Usage: vm-image-add <image_name> <sums_url> <sum_type> <filename_pattern>'
}

image_name=$1
if [[ -z "$image_name" ]]; then
help
exit 2
fi
sums_url=$2
if [[ -z "$sums_url" ]]; then
help
exit 2
fi
sum_type=$3
if [[ -z "$sum_type" ]]; then
help
exit 2
fi
filename_pattern=$4
if [[ -z "$filename_pattern" ]]; then
help
exit 2
fi

image_cfg_dir="${XDG_CONFIG_HOME:-$HOME/.config}/little-helpers-vm-images"
mkdir -p "$image_cfg_dir"
image_file="$image_cfg_dir/$image_name"
if [[ -e "$image_file" ]]; then
echo 'Image with this name already exists:'
echo "- Image name: $(sed '1q;d' "$image_file")"
echo "- Checksums file URL: $(sed '2q;d' "$image_file")"
echo "- Checksum type: $(sed '3q;d' "$image_file")"
echo "- Filename pattern: $(sed '4q;d' "$image_file")"
exit 1
fi

echo "$image_name" > "$image_file"
echo "$sums_url" >> "$image_file"

Check failure on line 73 in bin/vm-image-add

View workflow job for this annotation

GitHub Actions / Run pre-commit

Consider using { cmd1; cmd2; } >> file instead of individual redirects.
echo "$sum_type" >> "$image_file"
echo "$filename_pattern" >> "$image_file"

if ! vm-image-fetch "$image_name"; then
exit_code=$?
rm "$image_file"
exit "$exit_code"
fi
99 changes: 99 additions & 0 deletions bin/vm-image-fetch
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/bin/bash
#
# Fetch latest Linux VM image (added through `vm-image-add`) with the given name.
#
# This tool is typically invoked automatically.
#
# Usage: `vm-image-fetch [-f] <image_name>`
#
# Without the `-f` flag, the image only gets fetched when it isn't already in cache.
#
#
# Copyright 2024 Jakub Kuczys (https://github.com/Jackenmen)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eo pipefail

help() {
echo 'Usage: vm-image-fetch [-f] <image_name>'
}

force_download=0
OPTIND=1
while getopts 'f' flag; do
case "$flag" in
f)
force_download=1
;;
*)
help
exit 2
;;
esac
done
shift "$((OPTIND-1))"

image_name=$1
if [[ -z "$image_name" ]]; then
help
exit 2
fi

image_cfg_dir="${XDG_CONFIG_HOME:-$HOME/.config}/little-helpers-vm-images"
image_file="$image_cfg_dir/$image_name"
if [[ ! -f "$image_file" ]]; then
echo 'ERROR: Image with this name does not exist!'
exit 2
fi

sums_url=$(sed '2q;d' "$image_file")
if [[ -z "$sums_url" ]]; then
if [[ ! -f "$image_file" ]]; then
echo 'ERROR: No sum URL set but the image does not already exist.'
exit 1
fi
echo 'Using cached image as no sums URL is defined.'
exit 0
fi

sum_type=$(sed '3q;d' "$image_file")
filename_pattern=$(sed '4q;d' "$image_file")

checksums=$(curl -L "$sums_url")
checksums+=$(echo '' && echo "$checksums" | sed -nE "s/^${sum_type^^} \(($filename_pattern)\) = ([^ \t]+)\$/\2 \1/p")
matching_files=$(echo "$checksums" | grep -P "^[^ \t]+[ \t]+\*?$filename_pattern\$")
if [[ "$(echo "$matching_files" | wc -l)" -ne 1 ]]; then
echo 'ERROR: Expected only one filename to match but got these matches instead:'
echo "$matching_files"
exit 1
fi

base_url="${sums_url%/*}"
new_checksum=$(echo "$matching_files" | awk '{print $1}')
filename=$(echo "$matching_files" | awk '{print $2}')
filename="${filename#\*}"
image_url="$base_url/$filename"

cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/little-helpers-vm-images"
mkdir -p "$cache_dir"
old_checksum=$( (cat "$cache_dir/$image_name.${sum_type}sum" 2>/dev/null || echo '') | awk '{print $1}' )

if [[ "$force_download" -eq 1 || "$old_checksum" != "$new_checksum" ]]; then
echo 'Downloading new version of the image...'
wget -O "$cache_dir/$image_name.img" "$image_url"
echo "$new_checksum $image_name.img" > "$cache_dir/$image_name.${sum_type}sum"
echo 'Image downloaded and cached.'
else
echo 'Cached image already up-to-date.'
fi
103 changes: 103 additions & 0 deletions bin/vm-run
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/bash
#
# This scripts run a temporary VM with the given Linux image.
#
# Image's default user is set up with the `~/.ssh/id_rsa` key and additionally,
# `vm` user with `vm` password and the aforementioned key is also created.
#
# See documentation of `vm-image-add` for details about image management.
#
# Usage: `vm-run <image_name>`
#
# The domain (VM) name will be set to `tmp-<image_name>`, optionally suffixed with
# a number (`tmp-<image_name>-2`) when a machine with same name exists.
#
#
# Copyright 2024 Jakub Kuczys (https://github.com/Jackenmen)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eo pipefail

DELETE_DOMAIN=0

help() {
echo 'Usage: vm-run <image_name>'
}

cleanup() {
if [[ "$DELETE_DOMAIN" -eq 1 ]]; then
echo "Undefining VM's domain..."
virsh undefine "$domain_name"
echo 'Shutting down VM...'
if ! virsh shutdown "$domain_name" 2>/dev/null; then
echo 'VM was already powered off.'
fi
fi
echo "Removing VM's temporary directory..."
rm -rf "$vm_dir"
echo 'Removed.'
}

image_name=$1
if [[ -z "$image_name" ]]; then
help
exit 2
fi

vm-image-fetch "$image_name"

vm_dir=$(mktemp -d)
trap cleanup EXIT
chgrp libvirt-qemu "$vm_dir"
chmod 0770 "$vm_dir"
base_image_file="${XDG_CACHE_HOME:-$HOME/.cache}/little-helpers-vm-images/$image_name.img"

image_file="$vm_dir/disk.img"
qemu-img convert -O qcow2 "$base_image_file" "$image_file"
qemu-img resize "$image_file" 50G
chgrp libvirt-qemu "$image_file"
chmod 0660 "$image_file"

user_data_file="$vm_dir/user-data"
echo '#cloud-config' > "$user_data_file"
echo 'power_state:' >> "$user_data_file"

Check failure on line 74 in bin/vm-run

View workflow job for this annotation

GitHub Actions / Run pre-commit

Consider using { cmd1; cmd2; } >> file instead of individual redirects.
echo ' mode: poweroff' >> "$user_data_file"
echo ' condition: true' >> "$user_data_file"
echo 'ssh_authorized_keys:' >> "$user_data_file"
echo " - $(<"$HOME/.ssh/id_rsa.pub")" >> "$user_data_file"
echo 'users:' >> "$user_data_file"
echo ' - default' >> "$user_data_file"
echo ' - name: vm' >> "$user_data_file"
echo ' plain_text_passwd: vm' >> "$user_data_file"
echo ' lock_passwd: false' >> "$user_data_file"
echo ' sudo: ALL=(ALL) NOPASSWD:ALL' >> "$user_data_file"
echo ' shell: /bin/bash' >> "$user_data_file"
echo ' ssh_authorized_keys:' >> "$user_data_file"
echo " - $(<"$HOME/.ssh/id_rsa.pub")" >> "$user_data_file"

domain_name="tmp-$image_name"
i=1
while virsh dominfo "$domain_name" 2>/dev/null; do
domain_name="tmp-$image_name-$i"
done

DELETE_DOMAIN=1
virt-install \
--memory 2048 --osinfo linux2016 \
--name "$domain_name" \
--events on_reboot=restart \
--cloud-init "user-data=$user_data_file" \
--disk "$image_file"

virsh start --console "$domain_name"

0 comments on commit 8682b55

Please sign in to comment.