#! /usr/bin/env bash # Generate systemd .service file for QEMU/KVM virtual machines. # # Normally the machines are run from a dedicated user account with its home # directory containing all the relevant files (management scripts, images, # configurations, and sockets). However, this can be overriden with the # following options # # --home # The virtual machines "home" directory. If unspecified, the user's home # directory is assumed. # # --bin # The virtual machines management scripts directory. If unspecified, # /bin is assumed. If specified as relative, then assumed relative # to . # # --etc # The virtual machines configuration files directory. If unspecified, # /etc is assumed. If specified as relative, then assumed relative # to . # # --var # The virtual machines image files directory. If unspecified, /var is # assumed. If specified as relative, then assumed relative to . # # --run # The virtual machines sockets directory. If unspecified, /run is # assumed. If specified as relative, then assumed relative to . # # If is unspecified, the current user is assumed. If is # unspecified, the user's primary group is assumed. # usage="usage: $0 [] [] []" owd="$(pwd)" trap "{ cd '$owd'; exit 1; }" ERR set -o errtrace # Trap in functions. function info () { echo "$*" 1>&2; } function error () { info "$*"; exit 1; } home= bin= etc= var= run= while [ "$#" -gt 0 ]; do case "$1" in --home) shift home="$1" shift ;; --bin) shift bin="$1" shift ;; --etc) shift etc="$1" shift ;; --var) shift var="$1" shift ;; --run) shift run="$1" shift ;; *) break ;; esac done user="$1" if [ -z "$user" ]; then user="$(id -un)" fi group="$2" if [ -z "$group" ]; then group="$(id -un "$user")" fi if [ -z "$home" ]; then home="$(eval echo ~$user)" fi function complete_dir () # { local r if [ -z "$3" ]; then r="$2/$1" elif [ "${3:0:1}" != "/" ]; then r="$2/$3" else r="$3" fi echo "$(realpath --no-symlinks --canonicalize-missing "$r")" } bin="$(complete_dir bin "$home" "$bin")" etc="$(complete_dir etc "$home" "$etc")" var="$(complete_dir var "$home" "$var")" run="$(complete_dir run "$home" "$run")" name="vm-$user" file="$name@.service" # Thinks that must be \-escaped: # # - $ (including in comments) # - \ (e.g., in line continuations) # cat <"$file" # $file -- QEMU/KVM machine service template for systemd # # user: $user # group: $group # bin: $bin # etc: $etc # var: $var # run: $run # # To install: # # sudo cp $file /etc/systemd/system/ # sudo chmod 644 /etc/systemd/system/$file # # cp ... $var/.img # nano $etc/.conf # Specify RAM, CPU, TAP, MAC, etc. # # sudo systemctl start $name@ # sudo systemctl status $name@ # login-machine $run/-con.sock # sudo systemctl stop $name@ # # sudo systemctl enable $name@ [Unit] Description=QEMU/KVM virtual machine %I Wants=network-online.target #After=network-online.target After=multi-user.target [Service] User=$user Group=$user UMask=0007 WorkingDirectory=~ Environment=CPU=1 Environment=RAM=2G # These MUST be specific in EnvironmentFile! # #Environment=TAP= #Environment=MAC= # Note that using variable expansion in EnvironmentFile does not work (at # least not with systemd 229). # EnvironmentFile=$etc/%i.conf # Note that the first word of ExecStart cannot contain variable expansions. # ExecStart=$bin/vm-start \\ --cpu \${CPU} \\ --ram \${RAM} \\ --tap \${TAP} \\ --mac \${MAC} \\ --monitor $run/%i-mon.sock \\ --console $run/%i-con.sock \\ $var/%i.img ExecStop=$bin/vm-stop $run/%i-mon.sock # Make systemd wait for ExecStop completion. # KillMode=none TimeoutStopSec=60 [Install] WantedBy=multi-user.target EOF info "generated $file for" info " user: $user" info " group: $group" info " bin: $bin" info " etc: $etc" info " var: $var" info " run: $run"