aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile17
-rw-r--r--README.md83
-rw-r--r--default.conf13
-rwxr-xr-xvms.sh231
4 files changed, 344 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..052bd4d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+VMS_CMD = vms
+VMS_CMD_PATH = $(DESTDIR)$(PREFIX)/bin/$(VMS_CMD)
+VMS_BASH_FILE = $(VMS_CMD).sh
+
+.PHONY: all install uninstall
+
+all: install
+
+install: $(VMS_BASH_FILE)
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp $(VMS_BASH_FILE) $(VMS_CMD_PATH)
+ chmod 755 $(VMS_CMD_PATH)
+
+uninstall:
+ rm -f $(VMS_CMD_PATH)
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..abca406
--- /dev/null
+++ b/README.md
@@ -0,0 +1,83 @@
+# vms
+
+This script is a simple tool that uses QEMU to create, run, and manage virtual
+machines.
+
+It is a tool to simplify the management of multiple headless VMs without a
+graphical interface, using tools like `ssh`, `sshfs`, and `rsync`. The script
+uses minimal QEMU arguments to avoid excessive CPU usage. If you want to use
+`Spice` or other tools with QEMU, feel free to open a pull request to integrate
+that into the configuration.
+
+## Prerequisites
+
+Ensure you have the following dependencies installed:
+
+- `qemu-img`
+- `qemu-system-x86_64`
+
+## Installation
+
+Run:
+
+```sh
+$ make PREFIX=/home/USER/.local install
+```
+
+## Usage
+
+The first time you run `vms` command, it will create a `vms` directory under
+your home directory to store the images, configs, and ISOs for the VMs.
+
+First, download the ISO image, for example, [arch linux](https://archlinux.org/download/).
+
+The following command will create a new image in `vms` direcotry using `qemu-img`
+with the provided size, and create a config file.
+
+
+```sh
+$ vms create arch 50G
+```
+
+Read/Modify the config file in: `/home/USER/vms/arch.conf`
+
+Then you can boot from the ISO:
+
+```sh
+$ vms boot arch /home/USER/download/arch.iso
+```
+
+After installing, you can run this command whenever you want to run the VM:
+
+```sh
+$ vms run arch
+```
+
+You can list all VMs by using this command:
+
+```sh
+$ vms list
+```
+
+## Configuration
+
+By default, the script uses the following configurations for the virtual
+machines:
+
+```sh
+### Default vm configuration
+# ram=12G
+# cpu=host
+# graphic=yes
+# audio=no
+#
+# Forward host port 10022 to guest port 22 and host port 8080 to guest port 80
+# ports=10022:22 8080:80
+#
+# display=sdl,grab-mod=rctrl
+# vgadevices=VGA,vgamem_mb=64
+# audiodevices=intel-hda hda-duplex
+# devices=
+```
+
+These default settings can be customized by modifying config files.
diff --git a/default.conf b/default.conf
new file mode 100644
index 0000000..d0d03e0
--- /dev/null
+++ b/default.conf
@@ -0,0 +1,13 @@
+### Default vm configuration
+# audio=no
+# devices=
+# ram=12G
+# cpu=host
+# bios_path=/usr/share/qemu/bios.bin
+# audiodevices=intel-hda hda-duplex
+# graphic=yes
+# ports=10022:22 8080:80
+# net=nic
+# boot=menu=on
+# display=sdl,grab-mod=rctrl
+# vgadevices=VGA,vgamem_mb=64
diff --git a/vms.sh b/vms.sh
new file mode 100755
index 0000000..10568dd
--- /dev/null
+++ b/vms.sh
@@ -0,0 +1,231 @@
+#!/bin/bash
+
+set -e
+
+vms_path=$(realpath ~/vms)
+
+# Default vm configuration
+# Declare an associative array
+declare -A config
+config=(
+ [graphic]="yes"
+ [audio]="no"
+ [boot]="menu=on"
+ [ram]="12G"
+ [cpu]="host"
+ [display]="sdl,grab-mod=rctrl"
+ [ports]="10022:22 8080:80"
+ [audiodevices]="intel-hda hda-duplex"
+ [vgadevices]="VGA,vgamem_mb=64"
+ [devices]=""
+ [bios_path]="/usr/share/qemu/bios.bin"
+ [net]="nic"
+)
+
+# Load config from a file
+load_config() {
+ local file="$1" value
+ while read -r value; do
+ # Check if the line contains an equals sign
+ if [[ $value = *?=* ]]; then
+ # skip if it's a comment
+ if [[ "$value" == \#* ]]; then
+ continue
+ fi
+ local key=${value%%=*}
+ config[$key]=${value#*=}
+ fi
+ done <"$file"
+}
+
+# Save config to a file
+save_config() {
+ local file="$1" key
+ printf "### Default vm configuration\n" >"$file"
+ for key in "${!config[@]}"; do
+ printf "# %s=%s\n" "$key" "${config[$key]}" >>"$file"
+ done
+}
+
+# Print the current config
+print_config() {
+ local key
+ for key in "${!config[@]}"; do
+ printf "# %s=%s\n" "$key" "${config[$key]}"
+ done
+}
+
+# Print error message to stderr
+printerr() { printf "%s\n" "$*" >&2; }
+
+# Check if a file exists
+file_exists() {
+ if [ ! -f "$1" ]; then
+ printerr "error: file $1 not exist"
+ exit 1
+ fi
+}
+
+# Check if the number of parameters is correct
+check_params() {
+ if [ "$1" -lt "$2" ]; then
+ printerr "error: wrong parameters"
+ exit 1
+ fi
+}
+
+# Print help information
+usage() {
+ printf "vms:
+ vms path: %s
+ commands:
+ $ vms run IMAGE_NAME
+ $ vms run arch
+ $ vms boot IMAGE_NAME ISO_PATH
+ $ vms boot arch /path/to/arch.iso
+ $ vms create IMAGE_NAME SIZE_OF_IMAGE
+ $ vms create arch 50g
+ $ vms list\n" "$vms_path"
+ exit 1
+}
+
+# Run QEMU
+run_qemu() {
+
+ load_config "$1"
+
+ # Define the QEMU arguments
+ local qemu_args=(
+ --enable-kvm
+ -bios "${config[bios_path]}"
+ -boot "${config[boot]}"
+ -m "${config[ram]}"
+ -cpu "${config[cpu]}"
+ )
+
+ if [ -n "${config[net]}" ]; then
+ qemu_args+=(-net "${config[net]}")
+ local qemu_net_arg="user" ports
+ for ports in ${config[ports]}; do
+ IFS=':' read -ra p <<<"$ports"
+ if [ ${#p[@]} != 2 ]; then
+ printerr "error: wrong port: ${p[*]}"
+ exit 1
+ fi
+ qemu_net_arg+=",hostfwd=tcp::${p[0]}-:${p[1]}"
+ done
+
+ qemu_args+=(-net "$qemu_net_arg")
+ fi
+
+ local device
+
+ if [[ "${config[graphic]}" == "yes" ]]; then
+ qemu_args+=(-display "${config[display]}")
+ for device in ${config[vgadevices]}; do
+ qemu_args+=(-device "${device}")
+ done
+
+ if [[ "${config[audio]}" == "yes" ]]; then
+ for device in ${config[audiodevices]}; do
+ qemu_args+=(-device "${device}")
+ done
+ fi
+ else
+ qemu_args+=(-nographic)
+ fi
+
+ for device in ${config[devices]}; do
+ qemu_args+=(-device "${device}")
+ done
+
+ qemu-system-x86_64 "${qemu_args[@]}" "${@:2}"
+}
+
+# Create a new vm
+create_new_vm() {
+ qemu-img create "$vms_path/$1.img" "$2"
+}
+
+# Create the vms directory if it doesn't exists
+if [ ! -d "$vms_path" ]; then
+ printf "Creating vms directory in %s\n" "$vms_path"
+ mkdir -p "$vms_path"
+fi
+
+# Print usage information if no arguments are provided
+if [ -z "$1" ]; then
+ usage
+fi
+
+# Ensure required commands are available
+for cmd in qemu-img qemu-system-x86_64; do
+ if ! command -v "$cmd" &>/dev/null; then
+ printerr "error: $cmd is not installed"
+ exit 1
+ fi
+done
+
+case "$1" in
+"run")
+ check_params $# 2
+
+ img_path="$vms_path/$2.img"
+ conf_path="$vms_path/$2.conf"
+
+ file_exists "$img_path"
+ file_exists "$conf_path"
+
+ img_args=(
+ -drive "file=$img_path,format=raw"
+ )
+
+ run_qemu "$conf_path" "${img_args[@]}"
+ ;;
+
+"boot")
+ check_params $# 3
+
+ img_path="$vms_path/$2.img"
+ conf_path="$vms_path/$2.conf"
+ iso_path=$(realpath "$3")
+
+ file_exists "$img_path"
+ file_exists "$iso_path"
+ file_exists "$conf_path"
+
+ img_args=(
+ -drive "file=$img_path,format=raw"
+ -cdrom "$iso_path"
+ )
+
+ run_qemu "$conf_path" "${img_args[@]}"
+ ;;
+
+"create")
+ check_params $# 3
+
+ iso_path="$vms_path/$2.iso"
+
+ create_new_vm "$2" "$3"
+
+ save_config "$vms_path/$2.conf"
+
+ printf "Created %s Successfully!\n" "$2"
+ ;;
+
+"list")
+ for img in "$vms_path"/*.img; do
+ printf " - %s\n" "$(basename "$img" .img)"
+ done
+ ;;
+
+"help")
+ usage
+ ;;
+
+*)
+ printerr "error: command $1 not found"
+ usage
+ ;;
+esac