#!/bin/sh # vm is a tool to manage and operate virtual machines. vm_version='vm-0.1' # TODO: # - DONE: fetch, build and install vftool in .vm/vftool # - setup a config file per VM # - creation of hdd image # - script install from CDROM iso: # - setup system, including static IP address # - setup user account from host # - setup ssh from host # - time synchronization from host # unset CDPATH export LC_ALL=C IFS=' ' add() { usage 'add name' 'Add a new VM' && return [ -d "$dir/$1" ] && die "vm $1 already exists in $dir/$1" init_alpine || die "could not init alpine iso" mkdir -p "$dir/$1" && cd "$dir/$1" || die "add $1 failed" pwd } console() { usage 'console name' 'Attach a console to a VM' && return [ "$1" ] || die 'console: name is missing' cd "$dir/$1" || die 'console $1 failed' [ -f vftool.pid ] || die "vm $1 is not active" screen -r "$1" } del() { usage 'del name' 'Delete a VM' && return echo 'not implemented yet' } die() { [ "$1" ] && echo "$@" >&2; exit 1; } help() { usage 'help' 'Print this help text' && return echo "$vm_version\nManage virtual machines\nUsage: vm command [options] [args]" Opth=1; for c in $Cmdlist; do $c; done } info() { usage 'info name' 'Print informations on a VM' && return echo 'not implemented yet' } # CAUTION: be careful to preserve tabs in the following Makefile template string. alpine_makefile='# Generated by "vm". DO NOT EDIT. # Check https://alpinelinux.org/downloads for possible upgrades iso_url = https://dl-cdn.alpinelinux.org/alpine/v3.13/releases/aarch64/alpine-virt-3.13.2-aarch64.iso iso = $(notdir $(iso_url)) isoinfo ?= /opt/homebrew/bin/isoinfo all: initrd vmlinux initrd: $(iso) $(isoinfo) isoinfo -i $(iso) -J -x /boot/initramfs-virt > $@ vmlinux: $(iso) $(isoinfo) isoinfo -i $(iso) -J -x /boot/vmlinuz-virt | gunzip > $@ $(iso): curl -LO $(iso_url) || { rm -f $@; false; } ln -sf $(iso) iso $(isoinfo): brew install cdrtools ' init_alpine() { mkdir -p "$dir/alpine-iso" && cd "$dir/alpine-iso" || die 'init alpine failed' [ -f 'Makefile' ] || echo "$alpine_makefile" > Makefile make -s all } init_vftool() { [ -x "$dir/vftool" ] && return cd '/tmp' && git clone --depth=1 'https://github.com/evansm7/vftool' && cd 'vftool' && make && cp 'build/vftool' "$dir/vftool" rm -rf '/tmp/vftool' } log() { usage 'log name' 'print logs of a VM' && return [ "$1" ] || die "log failed: name missing" cd "$dir/$1" || die "log $1 failed" cat vftool.log.old vftool.log 2>/dev/null } ls() { usage 'ls' 'list VMs' && return [ -d "$dir" ] && cd "$dir" || return for i in */; do i=${i%/} [ -f "$i/vftool.pid" ] && state=active || state=stopped printf "%-20s %s\n" "$i" "$state" done } start() { usage 'start [-a][-c vm] name' 'Start a VM' && return while getopts :ac: opt; do case $opt in (a) attach=1 ;; (c) from=$OPTARG ;; (*) Opth=1 start; exit;; esac done shift $((OPTIND - 1)) [ "$1" ] || die "start failed: name missing" init_vftool cd "$dir/$1" || die "start $1 failed" [ -f vftool.pid ] && die "Error: process $(cat vftool.pid) is active or $PWD/vftool.pid should be removed" start_vm & } start_vm() ( trap 'rm -f vftool.pid' EXIT [ -f vftool.log ] && cat vftool.log >> vftool.log.old "$dir/vftool" -k vmlinux -i initrd -c iso >vftool.log 2>&1 & sleep 1 exec 1>>vftool.log 2>&1 echo "$!" >vftool.pid screen -S "${PWD##*/}" -d -m "$(grep -om 1 '\/dev\/tty.*' vftool.log)" wait ) stop() { usage 'stop name' 'Stop a VM' && return [ "$1" ] || die 'stop: name missing' cd "$dir/$1" || die 'stop $1 failed' [ -f vftool.pid ] || die "stop: vm $1 is not active" kill $(cat vftool.pid) } usage() { [ "$Opth" ] && printf " %-34s %s\n" "$1" "$2"; } version() { usage 'version' 'Print version' && return echo "$vm_version" } # Main starts here. dir="$HOME/.vm" Cmdlist='add console del info help ls log start stop version' [ "$1" ] && C=$1 && shift 1 || { help; exit 1; } for c in $Cmdlist; do case $c in ($C) cmd=$c; break ;; ($C*) [ "$cmd" ] && die "ambiguous command $C" || cmd=$c ;; esac done [ "$cmd" ] || { help; exit 1; } && $cmd "$@"