#!/usr/bin/env bash # # kvmx virtual machine manager # # Copyright (C) 2017 Silvio Rhatto - rhatto at riseup.net # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published # by the Free Software Foundation, either version 3 of the License, # or any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Basic parameters BASENAME="`basename $0`" DIRNAME="`dirname $0`" ACTION="$1" VM="$2" GLOBAL_USER_CONFIG_FOLDER="$HOME/.config/kvmx" # Alias to be used in config files KVMX_BASE="$DIRNAME" # Run spice client function kvmx_spice { # https://lists.freedesktop.org/archives/spice-devel/2013-September/014643.html SPICE_NOGRAB=1 spicec --host localhost --port $PORT & #spicy -h localhost -p $PORT #remote-viewer spice://localhost:$PORT # Give time to boot sleep 5 # Fix window titles if which /usr/bin/xdotool &> /dev/null; then xdotool search --name "SPICEc:0" set_window --name $VM fi } # Bring virtual machine up function kvmx_up { if kvmx_running; then echo "$BASENAME: guest $VM is already running" exit 1 fi if [ ! -z "$shared_folder" ]; then local shared="-fsdev local,id=shared,path=$shared_folder,security_model=none -device virtio-9p-pci,fsdev=shared,mount_tag=shared" fi if [ ! -z "$port_mapping" ]; then local hostfwd=",$port_mapping" fi # Check if image exists, create otherwise if [ ! -e "$image" ]; then if [ ! -z "$basebox" ]; then if [ -e "$GLOBAL_USER_CONFIG_FOLDER/$basebox" ]; then baseimage="`kvmx list_image $basebox`" if [ ! -e "$baseimage" ]; then echo "$BASENAME: could not find basebox $baseimage. Please create it first." exit 1 fi echo "Copying base image $baseimage to $image" cp $baseimage $image fi else kvmx-create $GLOBAL_USER_CONFIG_FOLDER/$VM fi fi # Run virtual machine kvm -m 2048 -name $VM -drive file=$image,if=virtio -vga qxl $shared \ -spice port=$PORT,addr=127.0.0.1,disable-ticketing,streaming-video=off,jpeg-wan-compression=never,playback-compression=off,zlib-glz-wan-compression=never,image-compression=off \ -device virtio-serial-pci \ -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 \ -chardev spicevmc,id=spicechannel0,name=vdagent \ -smp 2 -soundhw ac97 -cpu host -balloon virtio \ -net nic,model=virtio \ -net user,hostfwd=tcp:127.0.0.1:$SSH-:22$hostfwd &> $LOGFILE & PID="$!" # Save state echo $PID > $PIDFILE echo $PORT > $PORTFILE echo $SSH > $SSHFILE if [ "$run_spice_client" == "1" ]; then kvmx_spice fi } # Display usage function kvmx_usage { echo "usage: $BASENAME [options]" echo "examples:" echo "" echo "$BASENAME list" echo "$BASENAME init [folder]" echo "$BASENAME clone " exit 1 } # Log into the guest using SSH function kvmx_ssh { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi shift 2 SSH="`cat $SSHFILE`" $SSH_COMMAND -p $SSH $SSH_LOGIN@127.0.0.1 $* } # Suspend the virtual machine function kvmx_suspend { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi PID="`cat $PIDFILE`" kill -STOP $PID } # Check if a guest is running function kvmx_running { if [ ! -e "$PIDFILE" ]; then return 1 fi PID="`cat $PIDFILE`" ps $PID &> /dev/null return $? } # Resume the guest function kvmx_resume { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi PID="`cat $PIDFILE`" kill -CONT $PID } # Poweroff the guest function kvmx_poweroff { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi echo /usr/bin/sudo poweroff | kvmx_ssh &> /dev/null sleep 3 kvmx_status } # Reboot the guest function kvmx_reboot { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi echo /usr/bin/sudo reboot | kvmx_ssh &> /dev/null sleep 3 kvmx_status } # Rsync files to the guest function kvmx_rsync { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi ORIG="$3" DEST="$4" SSH="`cat $SSHFILE`" rsync -av -e "$SSH_COMMAND -o Port=$SSH" --rsync-path "sudo rsync" $ORIG/ $SSH_LOGIN@127.0.0.1:$DEST/ } # List guests function kvmx_list { ls $GLOBAL_USER_CONFIG_FOLDER } # Upgrade guest function kvmx_upgrade { echo "sudo apt-get update && sudo apt-get dist-upgrade -y && sudo apt-get autoremove -y" | kvmx_ssh } # Initialize function kvmx_initialize { if [ "$ACTION" == "init" ] || [ "$ACTION" == "list" ]; then return fi if [ -z "$VM" ]; then VM="$(basename `pwd`)" fi # Default parameters PORT="$(($RANDOM + 1024))" SSH="$(($PORT + 22))" SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=FATAL -i $DIRNAME/ssh/insecure_private_key" SSH_LOGIN="user" # Initalize mkdir -p $GLOBAL_USER_CONFIG_FOLDER # Load and check guest config if [ "$ACTION" != "init" ] && [ "$ACTION" != "list" ] && [ "$ACTION" != "edit" ]; then if [ ! -e "$GLOBAL_USER_CONFIG_FOLDER/$VM" ]; then echo "$BASENAME: config not found: $GLOBAL_USER_CONFIG_FOLDER/$VM" exit 1 else source $GLOBAL_USER_CONFIG_FOLDER/$VM fi if [ -z "$image" ]; then image="/var/cache/qemu/$VM/box.img" fi if [ -z "$KVMXFILE" ]; then KVMXFILE="/var/cache/qemu/$VM/kvmxfile" fi # Box and folder config STORAGE="`dirname $image`" STATE_DIR="$STORAGE/state/$VM" PIDFILE="$STATE_DIR/pid" PORTFILE="$STATE_DIR/port" SSHFILE="$STATE_DIR/ssh" LOGFILE="$STATE_DIR/log" mkdir -p $STATE_DIR if [ ! -e "$image" ] && [ "$ACTION" != "up" ] && [ "$ACTION" != "purge" ] && [ "$ACTION" != "destroy" ]; then echo "$BASENAME: file not found: $image" exit 1 fi fi } # Initializes a new guest function kvmx_init { FOLDER="$3" if [ -z "$FOLDER" ]; then if [ -z "$VM" ]; then VM="$(basename `pwd`)" FOLDER="$(dirname `pwd`)/$VM" else FOLDER="$(pwd)/$VM" fi fi if [ -e "$GLOBAL_USER_CONFIG_FOLDER/$VM" ]; then echo "$BASENAME: guest $VM already exists" exit 1 fi if [ ! -d "$FOLDER" ]; then mkdir -p $FOLDER fi # Ensure we have an absolute folder name FOLDER="`cd $FOLDER &> /dev/null && pwd`" # Copy config from template if [ ! -e "$FOLDER/kvmxfile" ]; then cp $DIRNAME/kvmxfile $FOLDER/ sed -i -e "s|hostname=\"machine\"|hostname=\"$VM\"|g" $FOLDER/kvmxfile fi # Create config entry ( cd $GLOBAL_USER_CONFIG_FOLDER && ln -s $FOLDER/kvmxfile $VM ) } # Clone a guest function kvmx_clone { if kvmx_running; then echo "$BASENAME: orig $VM is running, cannot clone." exit 1 fi FOLDER="$3" DEST="`basename $FOLDER`" if [ -z "$FOLDER" ]; then kvmx_usage fi # Check if dest machine exists if [ -e "$GLOBAL_USER_CONFIG_FOLDER/$DEST" ]; then echo "$BASENAME: destination guest $DEST already exists." exit 1 fi if [ -d "$FOLDER" ]; then echo "$BASENAME: destination $FOLDER already exists." exit 1 fi # Ensure we have an absolute folder name mkdir -p $FOLDER FOLDER="`cd $FOLDER &> /dev/null && pwd`" rmdir $FOLDER # Copy image and configuration cp -r `dirname $image` $FOLDER/ ( cd $GLOBAL_USER_CONFIG_FOLDER && ln -s $FOLDER/kvmxfile $DEST ) # Update config file new_image="$FOLDER/`basename $image`" sed -i -e "s|image=\"$image\"|image=\"$new_image\"|g" $GLOBAL_USER_CONFIG_FOLDER/$DEST sed -i -e "s|hostname=\"$VM\"|hostname=\"$DEST\"|g" $GLOBAL_USER_CONFIG_FOLDER/$DEST } # Edit guest config function kvmx_edit { if [ -z "$EDITOR" ]; then EDITOR="vi" fi if [ -e "$GLOBAL_USER_CONFIG_FOLDER/$VM" ]; then $EDITOR $GLOBAL_USER_CONFIG_FOLDER/$VM else echo "$BASENAME: $GLOBAL_USER_CONFIG_FOLDER/$VM: file not found." fi } # Stop a guest function kvmx_stop { if kvmx_running; then PID="`cat $PIDFILE`" kill $PID fi } # Destroy a guest function kvmx_destroy { kvmx_stop rm -f $image rm -rf $STATE_DIR echo "$BASENAME: removed image and state files, but not the whole`dirname $image` folder." } # Purge a guest and all its configuration function kvmx_purge { kvmx_destroy rm -f $GLOBAL_USER_CONFIG_FOLDER/$VM echo "$BASENAME: removed $GLOBAL_USER_CONFIG_FOLDER/$VM config." } # Provision a machine function kvmx_provision { if ! kvmx_running; then echo "$BASENAME: guest $VM is not running" exit 1 fi if [ -z "$provision_command" ]; then echo "$BASENAME: error: parameter provision_command is not configured for $VM." exit 1 fi if [ ! -z "$provision_rsync" ]; then SSH="`cat $SSHFILE`" ORIG="`echo $provision_rsync | cut -d ' ' -f 1`" DEST="`echo $provision_rsync | cut -d ' ' -f 2`" echo "sudo mkdir -p `dirname $DEST`" | kvmx_ssh rsync -av -e "$SSH_COMMAND -o Port=$SSH" --rsync-path "sudo rsync" $ORIG/ $SSH_LOGIN@127.0.0.1:$DEST/ fi echo "$provision_command $hostname $domain $mirror" | kvmx_ssh } # Print guest image file name function kvmx_list_image { echo $image } # Print guest status function kvmx_status { if kvmx_running; then echo "$BASENAME: $VM guest is running" PID="`cat $PIDFILE`" ps $PID else echo "$BASENAME: $VM guest is stopped" fi } # Dispatch if type kvmx_$ACTION 2> /dev/null | grep -q 'function'; then kvmx_initialize kvmx_$ACTION $* else kvmx_usage fi