#!/bin/bash
#  sys/b9 - system bootstrap helper

set -e

source "/home/zwelch/src/mcf/mcsh-release/install/share/mcsh/mcsh.sh"

lib_load 'sys/tool/vbox'
lib_load "mcui"


######
# Native Dependencies

b9_client_packages=( debootstrap build-essential )


######
# Library Configuration
#
# See the reference for b9.conf_ for configuration information.
#
# .. _b9.conf: ../../conf/sys/b9.conf.html

b9_config_init() {
	script_setting_vars \
		bootstrap_mirror \
		bootstrap_fs_size bootstrap_fs_type \
		bootstrap_release bootstrap_arch bootstrap_variant \
		bootstrap_hostname bootstrap_domain \
		bootstrap_admin bootstrap_shell bootstrap_skeldir

	script_setting_arrays \
		bootstrap_users bootstrap_net_ifaces \
		bootstrap_dns_domains bootstrap_dns_servers \
		bootstrap_loader_packages bootstrap_core_packages \
		bootstrap_net_packages bootstrap_packages
}

b9_config_check() {
	[ "$bootstrap_arch" ] || bootstrap_arch=$(dpkg --print-architecture)
}


######
# Bootstrap System Dependencies

# $bootstrap_loader_packages[] - Bootloader packages.
bootstrap_loader_packages=( debconf grub-pc udev )
#;bootstrap_loader_packages=( grub-efi-amd64-signed )

# $bootstrap_core_packages[] - Lists the core system packages.
bootstrap_core_packages=(
		dialog
		less
		linux-image-generic
		screen
		sudo
		vim
	)

# $bootstrap_net_packages[] - Packages required for basic NAT networking
bootstrap_net_packages=(
		ifupdown
		isc-dhcp-client
		net-tools
		openssh-server
		openssh-client
	)


######
# Images

b9_image() { cmd_dispatch "$@"; }
b9_image_usage() {
	cat <<USAGE
<cmd> <name>
Disk Image Commands:
	new <name>			Creates a new disk image.
	delete <name>			Deletes a disk image.

Disk Image Formatting Commands:
	fdisk <name>			Runs fdisk on the image.
	gdisk <name>			Runs gdisk on the image.

Low-Level Disk Image Commands:
	mount <name>			Mount the image via NBD.
	umount <name>			Unmount the image.
	filename <name>			Prints the filename of the image.
USAGE
}

b9_image_new() {
	has_args 1 "$@"
	local name=$1
	run vbox_hd_create "$1" "$bootstrap_fs_size"
}

b9_image_info() { has_args 1 "$@"; qemu_img_info "$(vbox_hd_filename "$1")"; }

b9_image_mount() {
	has_args 1 "$@"
	local name=$1
	local dev
	dev=$(vbox_hd_partition_mount "$name" "raw")
	app_echo "$name: image connected to $dev"
}

b9_image_umount() {
	has_args 1 "$@"
	local name=$1
	vbox_hd_partition_umount "$name" "raw"
	app_echo "$name: image disconnected from NBD"
}

b9_image_fdisk() {
	has_args 1 "$@"
	local name=$1

	local -a opts=( --label=/ --filesystem="$bootstrap_fs_type" )
	local hdfile
	hdfile=$(vbox_hd_filename "$name")
	run_sudo virt-format "${opts[@]}" -a "$hdfile"
	return

	# TODO: do it without virt-format?
	local dev
	dev=$(vbox_hd_partition_mount "$name" "raw")
	local dev=/dev/loop0
	qemu_img_mount "$dev" "$(vbox_hd_filename "$name")"
	run_sudo fdisk -u -l "$dev"
	qemu_img_umount "$dev"
	vbox_hd_partition_umount "$name" "raw"
}

b9_image_gdisk() {
	error "gdisk: umplemented"
}

b9_image_fish() {
	min_args 1 "$@"
	local name=$1
	shift

	local file
	file=$(vbox_hd_filename "$name")
	run_sudo guestfish -a "$file" --ro "$@"
}

b9_image_filename() {
	has_args 1 "$@"
	local name=$1
	vbox_hd_filename "$1"
}


######
# Partitions

partition_dev() {
	has_args 2 "$@"
	local name=$1
	local part=$2
	local dev pdev
	dev=$(vbox_hd_partition_mount "$name" "raw")
	echo "${dev}p${part}"
}

partition_init() {
	has_args 2 "$@"
	local fs=$1
	local pdev=$2
	run_sudo mkfs.$fs "$pdev"
}

partition_delete() {
	false
}

partition_mount() {
	has_args 2 "$@"
	local pdev=$1
	local mnt=$2
	run_mkdir "$mnt"
	run_sudo mount "$pdev" "$mnt"
}
partition_umount() {
	min_args 1 "$@"
	run_sudo umount "$@"
}

b9_partition() { cmd_dispatch "$@"; }
b9_partition_usage() {
	cat <<USAGE
<cmd> ...
Disk Image Partition Commands:
	init <name> <part>		Format the partition
	mount <name> <part> <path>	Mount the partition
	umount <name> <part> <path>	Unmount the partition
USAGE
}

b9_partition_init() {
	has_args 2 "$@"
	local name=$1
	local part=$2
	local pdev
	pdev=$(partition_dev "$name" "$part")
	partition_init "$bootstrap_fs_type" "$pdev"
	vbox_hd_partition_umount "$name" "raw"
}

b9_partition_mount() {
	min_args 2 "$@"
	local name=$1
	local part=$2
	local path=$3
	local pdev mdir
	mdir=$(chroot_filename "$name" "$path")
	vbox_hd_partition_mount "$name" "$part" "$mdir"
}

b9_partition_umount() {
	min_args 2 "$@"
	local name=$1
	local part=$2
	local path=$3
	local mdir
	mdir=$(chroot_filename "$name" "$path")
	vbox_hd_partition_umount "$name" "$part"
	vbox_hd_partition_umount "$name" "raw"
}


######
# Chroot CLI

b9_chroot() { cmd_dispatch "$@"; }
b9_chroot_usage() {
	cat <<USAGE
<cmd> ...
	mount				Sets up mounts inside chroot
	umount				Removes the mounts inside the chroot
	bootstrap			Runs debootstrap
	install				Installs initial packages
	users				Creates initial users
	passwd				Resets user passwords
USAGE
}

b9_chroot_mount() { chroot_mount "$@"; }

b9_chroot_umount() { chroot_umount "$@"; }

b9_chroot_bootstrap() {
	has_args 1 "$@"
	local name=$1
	local -a opts
	[ -z "$bootstrap_arch" ] || \
		list_append opts --arch "$bootstrap_arch"
	[ -z "$bootstrap_variant" ] || \
		list_append opts --variant "$bootstrap_variant"
	local rootdir
	rootdir="$(chroot_filename "$name" "/")"
	run_sudo debootstrap "${opts[@]}" \
		"$bootstrap_release" "$rootdir" "$bootstrap_mirror"
}

b9_chroot_locale() {
	has_args 1 "$@"
	local name=$1
	local lang
	lang=$(source "$(chroot_filename "$name" "/etc/default/locale")" && echo $LANG)
	run_chroot "$name" locale-gen "$lang"
	run_chroot "$name" dpkg-reconfigure -f non-interactive tzdata
}


b9_chroot_install() { cmd_dispatch "$@"; }
b9_chroot_install_usage() {
	cat <<USAGE
<cmd> <name>
Chroot Installation Commands:
	all <name>			Install/update everything into chroot
	base <name>			Install base packages into chroot
	loader <name>			Install bootloader into chroot
	core <name>			Install core packages into chroot
USAGE
}

b9_chroot_install_all() {
	has_args 1 "$@"
	local name=$1
	b9_chroot_install_base "$name"
	b9_chroot_install_loader "$name"
	b9_chroot_install_core "$name"
}
b9_chroot_install_base() {
	has_args 1 "$@"
	local name=$1
	app_echo "$name: updating package cache"
	apt_chroot_update "$name"
	app_echo "$name: installing language pack"
	apt_chroot_install "$name" language-pack-en-base
	app_echo "$name: upgrading base packages"
	apt_chroot_upgrade "$name"
}

b9_chroot_install_core() {
	has_args 1 "$@"
	local name=$1
	app_echo "$name: installing core packages"
	local -a packages
	list_append packages "${bootstrap_core_packages[@]}"
	list_append packages "${bootstrap_net_packages[@]}"
	list_append packages "${bootstrap_packages[@]}"
	apt_chroot_install "$name" "${packages[@]}"
}

b9_gen_grub_device_map() {
	cat <<DEVICEMAP
(hd0) ${dev}
DEVICEMAP
}

b9_chroot_install_loader() {
	has_args 1 "$@"
	local name=$1

#---
#	gen_chroot_install "$name" fstab "/etc/fstab"
#	fstab_hd_type=nbd gen_chroot_install "$name" fstab "/etc/fstab"

#	local dev
#	dev=$(vbox_hd_partition_mount "$name" "raw")
#	vbox_hd_partition_umount "$name" "raw"
#	gen_chroot_install "$name" grub_device_map "/boot/grub/device.map"
#+++

	#; install grub-pc package
	echo "grub-pc grub-pc/install_devices_empty bool true" \
			| run_chroot "$name" debconf-set-selections
	apt_chroot_install "$name" "${bootstrap_loader_packages[@]}"
}

b9_chroot_update_loader() {
	has_args 1 "$@"
	local name=$1
	dev=$(vbox_hd_partition_mount "$name" "raw")
	vbox_hd_partition_umount "$name" "raw"

	#; install grub onto image
	run_chroot "$name" grub-install "$dev"
	run_chroot "$name" update-grub2

	local grubcfg="/boot/grub/grub.cfg"
	run_chroot "$name" sed -i -e "s,${dev}p1,/dev/sda1,g" "$grubcfg"

#;	run_sudo rm -f "$(chroot_filename "$name" "boot/grub/device.map")"

#;	gen_chroot_install "$name" fstab "/etc/fstab"
}

b9_chroot_users() {
	has_args 1 "$@"
	local name=$1
	local u
	for u in "${bootstrap_users[@]}"; do
		local -a opts
		list_append opts -s "$bootstrap_shell"
		list_append opts -k "$bootstrap_skeldir"
		run_chroot "$name" useradd "${opts[@]}" -m "$u"
		run_chroot "$name" gpasswd --add "$u" sudo
	done
	b9_chroot_passwd "$1" root "${bootstrap_users[@]}"
}

b9_chroot_passwd() {
	min_args 2 "$@"
	local name=$1
	shift

	local -a plist
	local file="$chroot_mntdir/$name.passwd"
	run rm -f "$file"

	local u
	for u in "$@"; do
		info "$u: password reset"
		local pass
		pass=$(apg -n 1 -m 12)
		echo "$u:$pass"
		list_append plist "$pass"
	done | tee "$file" | run_chroot "$name" chpasswd
	run chmod 400 "$file"
}

b9_chroot_shell() {
	has_args 1 "$@"
	local name="$1"
	run_chroot "$name" bash -i
}


######
# Chroot File Generation

b9_gen() { cmd_dispatch "$@"; }
b9_gen_usage() {
	cat <<USAGE
<cmd> ...
Chroot File Generation Commands:
	chroot

Configuration File Inspection Commands:
	fstab [<dev>]			Prints generated fstab file
	hostname			Prints generated hostname file
	hosts				Prints generated hosts file
	resolvconf			Prints generated resolv.conf file
	interfaces			Prints generated interfaces file
	localtime			Prints generated localtime file (bin)
	timezone			Prints generated timezone file
	locale				Prints generated locale file
	sources				Prints generated sources.list file
USAGE
}

b9_gen_fstab() {
	local flavor=${fstab_hd_type:-sd}
	local dev
	case "$flavor" in
	(nbd) dev=$(vbox_hd_partition_mount "$name" raw)p1 ;;
	(sd) dev=/dev/sda1
	esac

#---
	cat <<FSTAB
# /etc/fstab: static file system information.
$dev	/	$bootstrap_fs_type	errors=remount-ro 0 0
FSTAB
#+++

	case "$1" in
	(nbd) vbox_hd_partition_umount "$name" raw ;;
	(*) ;;
	esac
}

b9_gen_hostname() {
	echo "$bootstrap_hostname"
}

b9_gen_hosts() {
	cat <<HOSTS
127.0.0.1	localhost.localdomain localhost
127.0.1.1	$bootstrap_hostname.$bootstrap_domain $bootstrap_hostname"
HOSTS
}

b9_gen_resolvconf() {
	local search="${bootstrap_dns_domains[*]}"
	echo "search $search"

	local ns
	for ns in "${bootstrap_dns_servers[@]}"; do
		echo "nameserver $ns"
	done
}

b9_gen_interfaces() {
#---
	cat <<INTERFACES
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback

auto enp0s3
iface enp0s3 inet dhcp
INTERFACES
#+++
}

b9_gen_localtime() {
	local lt
	lt=$(realpath "/etc/localtime")
	cat "$lt"
}

b9_gen_locale() {
	cat "/etc/default/locale"
}

b9_gen_timezone() {
	cat "/etc/timezone"
}

b9_gen_sources() {
	cat "/etc/apt/sources.list"
}

gen_chroot_install() {
	has_args 3 "$@"
	local name=$1
	local part=$2
	local file=$3
	run "b9_gen_$part" | chroot_write_file "$name" "$file"
}

b9_gen_chroot() {
	has_args 1 "$@"
	local name=$1
	local bootstrap_hostname=$1

	gen_chroot_install "$name" fstab "/etc/fstab"
	gen_chroot_install "$name" hostname "/etc/hostname"
	gen_chroot_install "$name" hosts "/etc/hosts"
	gen_chroot_install "$name" resolvconf "/etc/resolv.conf"
	gen_chroot_install "$name" interfaces "/etc/network/interfaces"
	gen_chroot_install "$name" localtime "/etc/localtime"
	gen_chroot_install "$name" locale "/etc/default/locale"
	gen_chroot_install "$name" timezone "/etc/timezone"
	gen_chroot_install "$name" sources "/etc/apt/sources.list"
}


######
# Wizard

b9_wizard() { cmd_dispatch "$@"; }

b9_wizard_usage() {
	cat <<USAGE
<cmd> <name>
Wizard Commands:
	magic <name>			Magically create a new bootable image
	report <name>			Reports information about an image

Wizardly Image Commands:
	init <name>			Repartitions an existing image
	start <name>			Mounts the image before chroot
	finish <name>			Unmounts the image after chroot

Wizardly Chroot Commands:
	bootstrap <name>		Bootstraps the chroot
	generate <name>			Generates chroot configuration files
	install <name>			Installs packages in the chroot
	upgrade <name>			Upgrades packages in the chroot
USAGE
}

b9_wizard_magic() {
	has_args 1 "$@"
	local name=$1
	b9_wizard_init "$name"
	b9_wizard_start "$name"
	b9_wizard_bootstrap "$name"
	b9_wizard_generate "$name"
	b9_wizard_install "$name"
	b9_wizard_finish "$name"
	b9_wizard_report "$name"
}

b9_wizard_init() {
	has_args 1 "$@"
	local name="$1"

	if vbox_hd_exists "$name"; then
		local result
		mcui_yesno_warn result "$name: erase the disk image?"
		[ "$result" = yes ] || error "user canceled, aborting..."
	else
		b9_image_new "$name"
	fi

	b9_image_fdisk "$name"

	return
	#; virt-format does this for us; only needed if we can use fdisk
	b9_image_mount "$name"
	b9_partition_init "$name" 1
	b9_image_umount "$name"
}

b9_wizard_start() {
	has_args 1 "$@"
	local name="$1"
	b9_image_mount "$name"
	b9_partition_mount "$name" 1
}

b9_wizard_bootstrap() {
	has_args 1 "$@"
	local name="$1"
	b9_chroot_bootstrap "$name"
}

b9_wizard_generate() {
	has_args 1 "$@"
	local name="$1"
	b9_gen_chroot "$name"
	b9_chroot_mount "$name"
	b9_chroot_locale "$name"
	b9_chroot_users "$name"
	b9_chroot_umount "$name"
}

b9_wizard_install() {
	has_args 1 "$@"
	local name="$1"
	b9_chroot_mount "$name"
	b9_chroot_install_all "$name"
	b9_chroot_update_loader "$name"
	b9_chroot_umount "$name"
}

b9_wizard_shell() {
	has_args 1 "$@"
	local name="$1"
	b9_wizard_start "$name"
	b9_chroot_mount "$name"
	b9_chroot_shell "$name"
	b9_chroot_umount "$name"
	b9_wizard_finish "$name"
}

b9_wizard_finish() {
	has_args 1 "$@"
	local name="$1"
	b9_partition_umount "$name" 1
	b9_image_umount "$name"
	app_echo "$name: wizard finished!"
}

b9_wizard_report() {
	has_args 1 "$@"
	local name="$1"
	local file="$chroot_mntdir/$name.passwd"
	app_echo "$name: default VM system passwords:"
	cat "$file"
}


######
# Check

b9_check() {
	true
}


######
# Main

b9_desc() { echo "Virtual disk bootstrap helper"; }

b9_usage() {
	cat <<USAGE
<cmd> [...]
Command Groups:
	wizard ...			System image creation commands
	image ...			Disk image commands
	partition ...			Disk partition commands
	chroot ...			Chroot creation commands
	gen ...				Chroot configuration commands
USAGE
}

app_run "$@"

View the Developer Guide Index

View the Reference Manual Index


Generated on Tue Jul 4 17:00:11 PDT 2017 by mcsh d14 v0.21.0.