#!/bin/bash
set -e

# fully automated creation of an alpine vm
# v1, 2020-05-31, ed <irc.rizon.net>, MIT-Licensed
# https://ocv.me/dev/?install-alpine-vm.sh
#
# currently supports libvirt only,
# will add virtualbox Soon


# last assignment wins
export LIBVIRT_DEFAULT_URI=qemu:///session  # current user only
export LIBVIRT_DEFAULT_URI=qemu:///system   # shared with all users


iso="$1"
name="$2"
ip="$3"

[ -z "$ip" ] &&
	echo "arg 3 is blank, will use dhcp"

[ -z "$name" ] && {
	echo "need arg 1: alpine iso path"
	echo "need arg 2: vm name"
	echo "optional 3: host-only ip  (default: nat/dhcp)"
	exit 1
}

subnet="${ip%.*}"

vx() {
	printf '\n\033[1;30;47mavm>\033[1;37;44m %s\033[0m\n' "$*" >&2
	"$@"
}


##
## define apkovl

td=/dev/shm/$name
rm -rf $td/etc
mkdir -p $td/etc/local.d
mkdir -p $td/etc/runlevels/{sysinit,boot,shutdown,default}

# enable services, especially local
# https://github.com/alpinelinux/mkinitfs/blob/master/initramfs-init.in#L597
(cd $td/etc/runlevels/sysinit && ln -s /etc/init.d/{devfs,dmesg,mdev,hwdrivers,modloop} .)
(cd $td/etc/runlevels/boot && ln -s /etc/init.d/{modules,sysctl,hostname,bootmisc,syslog} .)
(cd $td/etc/runlevels/shutdown && ln -s /etc/init.d/{mount-ro,killprocs,savecache} .)
(cd $td/etc/runlevels/default && ln -s /etc/init.d/{firstboot,local} .)

# have local start the install script
f=$td/etc/local.d/install.start
cat >$f <<'EOF'
#!/bin/ash
(/etc/asetup.sh &)&
rm $0
EOF
chmod 755 $f

# the install script
f=$td/etc/asetup.sh
cat >$f <<'EOF'
#!/bin/ash
while sleep 0.2; do
	ps aux | grep -qE tty1$ && break
done
sleep 0.2
(
	printf '\n\n'

	SWAP_SIZE=0 \
	BOOTFS=ext2 \
	ROOTFS=ext4 \
	ERASE_DISKS=/dev/sda \
	setup-alpine -e -f /etc/asetup.cfg
	
	/sbin/poweroff
) >/dev/tty1 2>&1
EOF
chmod 755 $f

# the answers file for setup-alpine (EXPANDS)
f=$td/etc/asetup.cfg
cat >$f <<EOF
KEYMAPOPTS="us us-altgr-intl"
HOSTNAMEOPTS="-n $name"
DNSOPTS="1.1.1.1"
TIMEZONEOPTS="-z UTC"
APKREPOSOPTS="-1"
SSHDOPTS="-c openssh"
NTPOPTS="-c chrony"
DISKOPTS="-m sys /dev/sda"

INTERFACESOPTS="auto lo
iface lo inet loopback

auto eth0
EOF

[ -z "$ip" ] && cat >>$f <<EOF
iface eth0 inet dhcp
    hostname $name
"
EOF

[ -z "$ip" ] || cat >>$f <<EOF
iface eth0 inet static
    hostname $name
    address $ip
    netmask 255.255.255.0
    gateway $subnet.1
"
EOF


##
## create apkovl iso

(cd $td
tar -czf $name.apkovl.tar.gz etc
vx genisoimage -o ao.iso -r -J -V CDR $name.apkovl.tar.gz
)

##
## the installers

mk_libvirt() {
	vx virsh destroy  $name || true
	vx virsh undefine $name || true
	
	hdd="/var/lib/libvirt/images/$name.qcow2"
	rm -f "$hdd"
	vx qemu-img create -f qcow2 "$hdd" 128G
	
	cmd=(virt-install)
	
	[ -z "$ip" ] || {
		netname="$(
			virsh net-list --all --name |
			grep . |
			while IFS= read -r n; do
				virsh net-dumpxml "$n" |
				grep -qF "'$subnet.1'" &&
					echo $n && break
			done
		)" || true
		
		[ -z "$netname" ] && {
			netname=$subnet
			vx virsh net-define /dev/stdin <<EOF
			<network>
			  <name>$netname</name>
			  <domain name="$netname" />
			  <bridge name="$netname" stp="on" delay="0" />
			  <ip address="$subnet.1" netmask="255.255.255.0">
				<dhcp>
				  <range start="$subnet.128" end="$subnet.254" />
				</dhcp>
			  </ip>
			</network>
EOF
		}
		vx virsh net-start "$netname" || true
		cmd+=(
			-w bridge=$netname,model=virtio
		)
	}
	
	
	# tip: determine os-variant with `osinfo-query os`
	cmd+=(
		--os-variant alpinelinux3.8
		--qemu-commandline ' -enable-kvm'
		--virt-type kvm
		--vcpus $(nproc)
		--memory 512
		--video virtio
		--graphics spice,listen=none
		--input keyboard,bus=virtio
		--input tablet,bus=virtio
		--cpu host
		--boot hd,cdrom
		--disk "$iso",device=cdrom,bus=sata
		--disk $td/ao.iso,device=cdrom,bus=sata
		--disk "$hdd",sparse=yes,format=qcow2,device=disk,bus=scsi,driver.discard=unmap,driver.detect_zeroes=unmap
		--controller type=scsi,model=virtio-scsi
		-n "$name"
		--print-xml
	)
	vx "${cmd[@]}" | virsh define /dev/stdin
	vx virsh start $name
	vx virt-viewer -wa --domain-name $name
}

mk_libvirt

exit 0

# ----------------------------------------------------------------------
# notes
# ----------------------------------------------------------------------

# prep on alpine hosts
apk add virt-manager libvirt-qemu qemu-system-x86_64 ovmf
service libvirtd start
modprobe tun

# prep on arch hosts
pacman -Syu ebtables dnsmasq firewalld virt-manager virt-viewer libvirt ovmf qemu cdrtools
systemctl start ebtables
systemctl restart firewalld
systemctl restart libvirtd

# download alpine iso
cd /var/lib/libvirt/images
u="https://mirrors.edge.kernel.org/alpine/latest-stable/releases/x86_64/"
iso="$(wget -qO- $u/latest-releases.yaml | awk '/-virt-.*\.iso$/ {print $NF;exit}')"
wget -c "$u/$iso"

# create initial setup.cfg
setup-alpine -c setup.cfg

# https://wiki.alpinelinux.org/wiki/Qemu#Live_mode
fallocate -z -l $((32*1024*1024)) /dev/shm/fv
modprobe loop
fv=$(losetup -f)
losetup $fv /dev/shm/fv
mkdosfs $fv
mkdir -p /media/vda1
echo "$fv /media/vda1 vfat rw 0 0" >> /etc/fstab
mount -a
setup-alpine
# use /media/vda1 for config
lbu commit
# creates /media/vda1/avm.apkovl.tar.gz
