#!/usr/bin/env bash

#    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
#    (at your option) any later version.

set -o pipefail

## Distributor configurables
#
# These are the default settings utilized as both a fallback in case a
# setting is not configured and as the default settings a new system will
# be initialized with.
declare -r dist_enable_overlay=1
declare -r dist_repo_url='https://repo.arkanelinux.org/arkdep'
declare -r dist_repo_default_image='arkanelinux'
declare -r dist_deploy_keep=3
declare -r dist_clean_cache_on_remove=1
declare -r dist_always_healthcheck=1
declare -r dist_gpg_signature_check=1
declare -r dist_minimum_available_boot_storage=153600
declare -r dist_minimum_available_root_storage=12582912
declare -r dist_update_cpu_microcode=1
declare -r dist_backup_user_accounts=1
declare -r dist_migrate_files="'var/usrlocal' 'var/opt' 'var/srv' 'var/lib/AccountsService' 'var/lib/bluetooth' 'var/lib/NetworkManager' 'var/lib/arkane' 'var/lib/power-profiles-daemon' 'var/db' 'etc/localtime' 'etc/locale.gen' 'etc/locale.conf' 'etc/NetworkManager/system-connections' 'etc/ssh' 'etc/fstab' 'etc/crypttab' 'etc/luks-keys'"
declare -r dist_load_extensions=0
declare -r dist_remove_tar_after_deployment=1
declare -r dist_update_diff_style='list'
declare -r dist_interactive_mode=1
declare -r dist_package_layer_command='pacman -Syy --needed --noconfirm'
declare -r dist_package_unlayer_command='pacman -Rsn --noconfirm'


# Default configuration file settings used for init
# systemd-boot configuration
declare -r boot_entry_title='Arkane Linux - Arkdep'
declare -r kernel_params='root="LABEL=arkane_root" rootflags=subvol=/arkdep/deployments/%target%/rootfs rw'

## Check if program is locked, if yes do not run
#
if [[ -f /tmp/arkdep.lock ]]; then
	printf '\e[1;31m<#>\e[0m\e[1m /tmp/arkdep.lock exists, another instance of the program might be running\e[0m\n'
	exit 5
fi

## Set common variables
#
declare -r arkdep_dir="$(readlink -m $ARKDEP_ROOT/arkdep)"

# Override arkdep_boot if set, if not assume located inside of root
if [[ -n $ARKDEP_BOOT ]]; then
	declare -r arkdep_boot="$(readlink -m $ARKDEP_BOOT)"
else
	declare -r arkdep_boot="$(readlink -m $ARKDEP_ROOT/boot)"
fi

# If ARKDEP_ROOT is defined we imply confirm
[[ -n $ARKDEP_ROOT ]] && declare -r ARKDEP_CONFIRM=1

# Override database name if ARKDEP_DATABASE is defined
if [[ -n $ARKDEP_DATABASE ]]; then
	declare -r database_name=$ARKDEP_DATABASE
else
	declare -r database_name='database'
fi

if [[ ! -d $arkdep_dir ]] && [[ ! $1 == 'init' ]]; then
	printf "\e[1;31m<#>\e[0m\e[1m Arkdep does not seem to be managing this system or the provided file path is incorrect for $arkdep_dir was not found\e[0m\n"
	exit 1
fi

## Load config file, unless we are running init
#
if [[ ! $1 == 'init' ]]; then
	if [[ -z ${ARKDEP_CONFIG+x} ]]; then
		# Ensure file exists
		if [[ ! -f $arkdep_dir/config ]]; then
			printf "\e[1;31m<#>\e[0m\e[1m $arkdep_dir/config configuration file does not exist\e[0m\n"
			exit 1
		fi
		source $arkdep_dir/config
	else
		# Ensure file exists
		if [[ ! -f $ARKDEP_CONFIG ]]; then
			printf "\e[1;31m<#>\e[0m\e[1m $ARKDEP_CONFIG configuration file does not exist\e[0m\n"
			exit 1
		fi
		source $ARKDEP_CONFIG
	fi

	# Set default variables if config variables are undefined
	[[ -z ${enable_overlay+x} ]] &&
		enable_overlay=$dist_enable_overlay &&
		printf '\e[1;33m<!>\e[0m\e[1m enable_overlay not defined in config, using default\e[0m\n'
	[[ -z ${repo_url+x} ]] &&
		repo_url=$dist_repo_url &&
		printf '\e[1;33m<!>\e[0m\e[1m repo_url not defined in config, using default\e[0m\n'
	[[ -z ${repo_default_image+x} ]] &&
		repo_default_image=$dist_repo_default_image &&
		printf '\e[1;33m<!>\e[0m\e[1m repo_default_image not defined in config, using default\e[0m\n'
	[[ -z ${deploy_keep+x} ]] &&
		deploy_keep=$dist_deploy_keep &&
		printf '\e[1;33m<!>\e[0m\e[1m deploy_keep not defined in config, using default\e[0m\n'
	[[ -z ${clean_cache_on_remove+x} ]] &&
		clean_cache_on_remove=$dist_clean_cache_on_remove &&
		printf '\e[1;33m<!>\e[0m\e[1m clean_cache_on_remove not defined in config, using default\e[0m\n'
	[[ -z ${always_healthcheck+x} ]] &&
		always_healthcheck=$dist_always_healthcheck &&
		printf '\e[1;33m<!>\e[0m\e[1m always_healthcheck not defined in config, using default\e[0m\n'
	[[ -z ${gpg_signature_check+x} ]] &&
		gpg_signature_check=$dist_gpg_signature_check &&
		printf '\e[1;33m<!>\e[0m\e[1m gpg_signature_check not defined in config, using default\e[0m\n'
	[[ -z ${minimum_available_boot_storage+x} ]] &&
		minimum_available_boot_storage=$dist_minimum_available_boot_storage &&
		printf '\e[1;33m<!>\e[0m\e[1m minimum_available_boot_storage not defined in config, using default\e[0m\n'
	[[ -z ${minimum_available_root_storage+x} ]] &&
		minimum_available_root_storage=$dist_minimum_available_root_storage &&
		printf '\e[1;33m<!>\e[0m\e[1m minimum_available_root_storage not defined in config, using default\e[0m\n'
	[[ -z ${update_cpu_microcode+x} ]] &&
		update_cpu_microcode=$dist_update_cpu_microcode &&
		printf '\e[1;33m<!>\e[0m\e[1m update_cpu_microcode not defined in config, using default\e[0m\n'
	[[ -z ${backup_user_accounts+x} ]] &&
		backup_user_accounts=$dist_backup_user_accounts &&
		printf '\e[1;33m<!>\e[0m\e[1m backup_user_accounts not defined in config, using default\e[0m\n'
	[[ -z ${migrate_files+x} ]] &&
		migrate_files=($(printf "${dist_migrate_files//\'}")) &&
		printf '\e[1;33m<!>\e[0m\e[1m migrate_files not defined in config, using default\e[0m\n'
	[[ -z ${load_extensions+x} ]] &&
		load_extensions=$dist_load_extensions &&
		printf '\e[1;33m<!>\e[0m\e[1m load_extensions not defined in config, using default\e[0m\n'
	[[ -z ${remove_tar_after_deployment+x} ]] &&
		remove_tar_after_deployment=$dist_remove_tar_after_deployment &&
		printf '\e[1;33m<!>\e[0m\e[1m remove_tar_after_deployment not defined in config, using default\e[0m\n'
	[[ -z ${update_diff_style+x} ]] &&
		update_diff_style=$dist_update_diff_style &&
		printf '\e[1;33m<!>\e[0m\e[1m update_diff_style not defined in config, using default\e[0m\n'
	[[ -z ${interactive_mode+x} ]] &&
		interactive_mode=$dist_interactive_mode &&
		printf '\e[1;33m<!>\e[0m\e[1m interactive_mode not defined in config, using default\e[0m\n'
	[[ -z ${package_layer_command+x} ]] &&
		package_layer_command=$dist_package_layer_command &&
		printf '\e[1;33m<!>\e[0m\e[1m package_layer_command not defined in config, using default\e[0m\n'
	[[ -z ${package_unlayer_command+x} ]] &&
		package_unlayer_command=$dist_package_unlayer_command &&
		printf '\e[1;33m<!>\e[0m\e[1m package_unlayer_command not defined in config, using default\e[0m\n'
fi

## Common functions
#
# Cleanup and quit if error
cleanup_and_quit () {

	# If any parameters are passed we will assume it to be an error message
	[[ -n $1 ]] && printf "\e[1;31m<#>\e[0m $*\e[0m\n" >&2

	# Ensure we do not try to remove our current deployment
	if [[ ! -z ${data[0]+x} ]]; then
		if grep -q ${data[0]} /proc/cmdline; then
			printf '\e[1;33m<!>\e[0m\e[1m Cleanup target is current active deployment, skipping\e[0m\n'
			unlock_and_quit 1
		fi
	fi

	# Remove the subvolume we were working on
	# TODO: Make this a generic function and share with the removal of old images?
	if [[ -n ${data[0]} ]]; then
		btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs/etc ro false 2> /dev/null
		btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs/var ro false 2> /dev/null
		btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs ro false 2> /dev/null
		btrfs subvolume delete $arkdep_dir/deployments/${data[0]}/rootfs/etc 2> /dev/null
		btrfs subvolume delete $arkdep_dir/deployments/${data[0]}/rootfs/var 2> /dev/null
		btrfs subvolume delete $arkdep_dir/deployments/${data[0]}/rootfs 2> /dev/null
		rm -rfv $arkdep_dir/deployments/${data[0]} \
			$arkdep_boot/arkdep/${data[0]} 2> /dev/null
		rm -v $arkdep_dir/cache/${data[0]}-*.img \
			$arkdep_boot/loader/entries/*${data[0]}*.conf 2> /dev/null
	fi

	unlock_and_quit 1

}

# Exit and release lock file
# Takes exit code as $1
unlock_and_quit () {
	rm /tmp/arkdep.lock
	exit $1
}

touch /tmp/arkdep.lock

# Script-wide trap interupt
trap 'echo "User interupt received"; unlock_and_quit 4' INT TERM

## Healthcheck
#
# Set common variables for healthcheck and cleanup,
# only set all these vars if they will actually be used
if [[ always_healthcheck -eq 1 ]] || [[ $1 =~ ^(healthcheck|cleanup)$ ]]; then
	# Gather tracked deployments
	declare -r tracker=($(cat $arkdep_dir/tracker))
	declare -r deployed=($(ls $arkdep_dir/deployments/))
	declare untracked=${deployed[@]}

	# Check for hanging cache files
	declare -r cached=($(ls $arkdep_dir/cache/))
	declare hanging_cache=()

	# Generate grep regex for cache check
	declare cache_regex=$(printf "|%s" "${tracker[@]}")
	cache_regex=${cache_regex:1}

	# Compare items in tracker to actual deployed
	for tracked in ${tracker[@]}; do
		untracked=("${untracked[@]/$tracked}")
	done

	for cached_item in ${cached[@]}; do
		hanging_cache+=($(echo $cached_item | grep -v -E "$cache_regex"))
	done

	# Clean whitespaces
	untracked=($(echo ${untracked[@]} | xargs))
fi

# Check for and report on any issues such as untracked deployments or hanging files in cache
healthcheck () {

	if [[ -n $untracked ]]; then
		printf '\e[1;33m<!>\e[0m\e[1m The following deployments were found but are untracked\n\e[0m'
		for t in ${untracked[@]}; do
			printf "$t\n"
		done
	fi

	if [[ -n $hanging_cache ]]; then
		printf '\e[1;33m<!>\e[0m\e[1m The following hanging images were found in cache\n\e[0m'
		for t in ${hanging_cache[@]}; do
			printf "$t\n"
		done
	fi

	# Warn if gpg check is enabled but no keys are installed
	if [[ ! $gpg_signature_check -eq 0 ]] && [[ ! -s $arkdep_dir/keys/trusted-keys ]]; then
		printf "\e[1;33m<!>\e[0m\e[1m gpg_signature_check is enabled but $arkdep_dir/keys/trusted-keys does not exist or is empty\n\e[0m"
	fi

	# If $1 is healthcheck it was manually called by the user
	[[ $1 == 'healthcheck' ]] && unlock_and_quit 1

}

cleanup () {

	# Run healthcheck, if always_healthcheck is enabled it will have previously not run
	healthcheck

	# Ensure there is actually something to clean up
	if [[ ${#untracked[@]} -eq 0 ]] && [[ ${#hanging_cache[@]} -eq 0 ]]; then
		printf '\e[1;33m<!>\e[0m\e[1m There is nothing to clean up\n\e[0m'
		unlock_and_quit 1
	fi

	if [[ $interactive_mode -eq 1 ]] && [[ $ARKDEP_CONFIRM -ne 1 ]]; then
		printf "The above listed items will be removed.\n\n"
		read -p 'Proceed with removal? [Y/n] ' remove_confirm

		if [[ ! $remove_confirm =~ ^(y|Y|yes|YES|)$ ]]; then
			unlock_and_quit 1
		fi
	fi

	if [[ -n $untracked ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Cleaning up untracked deployments\e[0m\n'

		for target in ${untracked[@]}; do
			if [[ $target == *recovery* ]]; then
				printf '\e[1;33m<!>\e[0m\e[1m Detected untracked recovery entry, ignoring\n\e[0m'
				continue
			fi

			# Ensure deployment is not currently active
			if grep -q "$arkdep_dir/deployments/$target/rootfs" /proc/cmdline; then
				printf '\e[1;33m<!>\e[0m\e[1m Target is currently active deployment\n\e[0m'
				continue
			fi

			# Remove bootloader entry
			[[ -f $arkdep_boot/loader/entries/*$target*.conf ]] &&
				printf "Removing $target bootloader entry\n" &&
				rm -rf $arkdep_boot/loader/entries/*$target*.conf
			[[ -f $arkdep_boot/arkdep/$target ]] &&
				printf "Removing $arkdep_boot/arkdep/$target\n" &&
				rm -rf $arkdep_boot/arkdep/$target

			# Ensure the deployment and all sub-volumes are writable
			for volume in $(btrfs subvolume list / | grep -oE '[^ ]+$' | grep $target); do
				printf "Unlocking $volume\n"
				btrfs property set -f -ts $(readlink -m $ARKDEP_ROOT/$volume) ro false ||
					printf "failed to make subvol $volume writable\n"
			done

			# Remove the deployment
			printf "Removing $arkdep_dir/deployments/$target\n" &&
				rm -rf $arkdep_dir/deployments/$target
		done
	fi

	if [[ -n $hanging_cache ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Cleaning up hanging cache\e[0m\n'
		for target in ${hanging_cache[@]}; do
			printf "Removing $arkdep_dir/cache/$target\n" &&
				rm $arkdep_dir/cache/$target
		done
	fi

	unlock_and_quit 0

}

# Always healthcheck on run if requested in config, unless the user explicitely called it or the program is going to call it
[[ $always_healthcheck -eq 1 ]] &&
	[[ ! $1 =~ ^(healthcheck|cleanup)$ ]] &&
	healthcheck

## Error checking
#
# Quit if not root, only run if required
if [[ ! $1 =~ ^(get-available|diff|healthcheck|layer-ls)$ ]]; then
	if [[ ! $EUID -eq 0 ]]; then
		printf '\e[1;31m<#>\e[0m\e[1m This program has to be run as root\n\e[0m' &&
		unlock_and_quit 1
	fi
fi

# Check if all dependencies are installed, quit if not
for prog in btrfs wget dracut curl gpgv; do
	if ! command -v $prog > /dev/null; then
		printf "\e[1;31m<#>\e[0m\e[1m Failed to locate $prog, ensure it is installed\e[0m\n"
		# Do not immediately exit to log all missing programs
		err=1
	fi

	[[ $err ]] && unlock_and_quit 1
done

# Ensure minimum required storage is available, only run if new deployment will be made
if [[ $1 == 'deploy'  ]]; then
	declare boot_storage_available=($(df --output=avail $arkdep_boot))
	boot_storage_available=${boot_storage_available[1]}
	declare root_storage_available=($(df --output=avail $ARKDEP_ROOT/))
	root_storage_available=${root_storage_available[1]}

	# Check amount of available boot storage, do not run if set to 0
	if [[ $boot_storage_available -lt $minimum_available_boot_storage ]] && [[ $minimum_available_boot_storage -ne 0 ]]; then
		printf "\e[1;31m<#>\e[0m\e[1m Less than ${minimum_available_boot_storage}Kib available on boot partition\e[0m\n"
		unlock_and_quit 1
	fi

	# Check amount of available root storage, do not run if set to 0
	if [[ $root_storage_available -lt $minimum_available_root_storage ]] && [[ $minimum_available_root_storage -ne 0 ]] ; then
		printf "\e[1;31m<#>\e[0m\e[1m Less than ${minimum_available_root_storage}Kib available on root partition\e[0m\n"
		unlock_and_quit 1
	fi
fi

## Core functions
#
# Initialize the system for arkdep, either on a new Linux install
# when using ARKDEP_BOOT and ARKDEP_ROOT, or the current system
# if called without said variables defined
init_new_system () {

	printf '\e[1;34m-->\e[0m\e[1m Initializing arkdep\e[0m\n'

	# Ensure Arkdep is not already installed, unless we are installing
	# to a new system
	[[ -d $arkdep_dir ]] && [[ -z ${ARKDEP_ROOT+x} ]] &&
		cleanup_and_quit "$arkdep_dir already exists"

	# Create the /arkdep subvolume
	printf "\e[1;34m-->\e[0m\e[1m Creating $arkdep_dir subvolume\e[0m\n"
	btrfs subvolume create $arkdep_dir || cleanup_and_quit "Failed to create btrfs subvolume"

	# Create directory structure
	printf '\e[1;34m-->\e[0m\e[1m Creating directory structure\e[0m\n'
	mkdir -pv $arkdep_dir/deployments \
		$arkdep_dir/deployments \
		$arkdep_dir/cache \
		$arkdep_dir/templates \
		$arkdep_dir/overlay \
		$arkdep_dir/keys \
		$arkdep_dir/extensions \
		$arkdep_dir/shared ||
		cleanup_and_quit "Failed to create $arkdep_dir and related directories"

	# Create empty database files
	touch $arkdep_dir/tracker
	touch $arkdep_dir/keys/trusted-keys

	# Add home shared subvolume and make writable
	btrfs subvolume create $arkdep_dir/shared/home || cleanup_and_quit "Failed to create home subvolume"
	btrfs subvolume create $arkdep_dir/shared/root || cleanup_and_quit "Failed to create root subvolume"
	btrfs subvolume create $arkdep_dir/shared/flatpak || cleanup_and_quit "Failed to create flatpak subvolume"
	btrfs property set -f -ts $arkdep_dir/shared/home ro false
	btrfs property set -f -ts $arkdep_dir/shared/root ro false
	btrfs property set -f -ts $arkdep_dir/shared/flatpak ro false

	# Ensure permissions on root home directory are set properly
	chmod 700 $arkdep_dir/shared/root

	# Write default config file
	printf '\e[1;34m-->\e[0m\e[1m Adding default config file\e[0m\n'
	cat <<- END > $arkdep_dir/config
	# Write /arkdep/overlay overlay to new deployments
	enable_overlay=$dist_enable_overlay

	# URL to image repository, do not add trailing slash
	repo_url='$dist_repo_url'

	# Default image pulled from repo if nothing defined
	repo_default_image='$dist_repo_default_image'

	# Keep the latest N deployments, remove anything older
	deploy_keep=$dist_deploy_keep

	# Remove images from the cache when their deployments are removed
	clean_cache_on_remove=$dist_clean_cache_on_remove

	# Check for untracked deployments and other issues on run
	always_healthcheck=$dist_always_healthcheck

	# Perform a GPG signature check on remote sources
	# 1 = enabled but optional, 2 = required
	gpg_signature_check=$dist_gpg_signature_check

	# Minimum amount of storage which needs to be available on /boot in Kib
	minimum_available_boot_storage=$dist_minimum_available_boot_storage

	# Minimum amount of storage which needs to be available on / in Kib
	minimum_available_root_storage=$dist_minimum_available_root_storage

	# Update CPU firmware if newer version available
	update_cpu_microcode=$dist_update_cpu_microcode

	# Automatically make a copy of passwd, shadow and group files if they differ from overlay
	backup_user_accounts=$dist_backup_user_accounts

	# List of files and folders to be recursively copied over from root tree to new root filesystem
	migrate_files=($(printf "$dist_migrate_files"))

	# Load script extensions from /arkdep/extensions
	load_extensions=$dist_load_extensions

	# Remove tarball from cache once deployment is finished
	remove_tar_after_deployment=$dist_remove_tar_after_deployment

	# Update diff styling, available styles: 'list'
	update_diff_style='$dist_update_diff_style'

	# Before making changes to the system show diff and ask for confirmation
	interactive_mode=$dist_interactive_mode

	# What command should be executed to install packages
	package_layer_command="$dist_package_layer_command"

	# What command should be executed to uninstall packages
	package_unlayer_command="$dist_package_unlayer_command"
	END

	# Add default bootloader config file
	cat <<- END > $arkdep_dir/templates/systemd-boot
	title $boot_entry_title
	linux /arkdep/%target%/vmlinuz
	initrd /amd-ucode.img
	initrd /intel-ucode.img
	initrd /arkdep/%target%/initramfs-linux.img
	options $kernel_params
	END

	unlock_and_quit 0

}

teardown () {

	cat <<- END
	WARNING: Removing arkdep may leave your system in an unbootable state and you
	may have to manually reconfigure your bootloader etc.. Only proceed if you know
	what you are doing!

	The following changes will be made to your system;
	- All subvolumes under $arkdep_dir will be deleted
	- All systemd-boot bootloader entries containing the word "arkdep" will be removed
	- Kernel and initramfs storage location /boot/arkdep will be removed

	END

	# Ensure user knows what they are doing
	read -p 'Type "I KNOW WHAT I AM DOING" in uppercase to confirm that you know what you are doing: ' input_confirm

	if [[ $input_confirm == 'I KNOW WHAT I AM DOING' ]]; then

		printf '\e[1;34m-->\e[0m\e[1m Tearing down arkdep\e[0m\n'

		# Quit with error if $arkdep_dir does not exist
		if [[ ! -d $arkdep_dir ]]; then
			printf "\e[1;31m<#>\e[0m $arkdep_dir does not exist, there is nothing to tear down\n\e[0m"
			unlock_and_quit 1
		fi

		# Remove all bootloader entries
		rm -v $(grep -ril arkdep $arkdep_boot/loader/entries)

		# Remove kernels and initramfs deployed by Arkdep
		rm -rfv $arkdep_boot/arkdep

		# Ensure all nested volumes in arkdep are writable and remove
		for volume in $(btrfs subvolume list / | grep -oE '[^ ]+$' | grep "^$arkdep_dir" | tac); do
			btrfs property set -f -ts $(readlink -m /$volume) ro false
			btrfs subvolume delete $volume
		done

	else
		printf '\e[1;33m<!>\e[0m\e[1m Teardown canceled, no changes made to system\e[0m\n'
	fi

	unlock_and_quit 0

}

remove_deployment () {

	# Ensure required vars are set
	if [[ -z $1 ]]; then
		printf '\e[1;31m<#>\e[0m\e[1m No deployment defined\n\e[0m'
		unlock_and_quit 1
	fi

	# Check if remove is called in scriptmode
	if [[ $remove_scriptmode -eq 1 ]]; then
		declare -r remove_targets=(${@:1})
	else
		declare -r remove_targets=(${@:2})
	fi

	for deployment in ${remove_targets[@]}; do
		# Ensure requested deployment is tracked
		declare hits=($(grep $deployment $arkdep_dir/tracker))

		if [[ ${#hits[@]} -gt 1 ]]; then
			# Check if there is an exact match
			for hit in ${hits[@]}; do
				if [[ $1 == $hit ]]; then
					declare -r exact_match_found=1
					# Set first hit to exact match
					hits[0]=$hit
				fi
			done

			if [[ ! $exact_match_found -eq 1 ]]; then
				printf '\e[1;33m<!>\e[0m\e[1m Multiple deployments match target, be more specific or provide an exact match\e[0m\n'
				continue
			fi

		elif [[ ${#hits[@]} -lt 1 ]]; then
			printf '\e[1;33m<!>\e[0m\e[1m No deployments match target\e[0m\n'
			continue
		fi

		declare target=${hits[0]}

		printf "Removing $target\n"

		# Ensure deployment is not currently active
		if grep -q "$arkdep_dir/deployments/$target/rootfs" /proc/cmdline; then
			printf '\e[1;33m<!>\e[0m\e[1m Target is current active deployment\e[0m\n'
			continue
		fi

		if [[ $interactive_mode -eq 1 ]] && [[ $cleanup_no_confirm -ne 1 ]] && [[ $ARKDEP_CONFIRM -ne 1 ]]; then
			printf "$target will be removed.\n\n"
			read -p 'Proceed with removal? [Y/n] ' remove_confirm

			if [[ ! $remove_confirm =~ ^(y|Y|yes|YES|)$ ]]; then
				unlock_and_quit 1
			fi
		fi

		# Remove bootloader entry
		rm -rfv $arkdep_boot/loader/entries/*$target*.conf
		rm -rfv $arkdep_boot/arkdep/$target

		# Ensure the deployment and all sub-volumes are writable
		for volume in $(btrfs subvolume list / | grep -oE '[^ ]+$' | grep $target); do
			btrfs property set -f -ts $(readlink -m $ARKDEP_ROOT/$volume) ro false ||
				printf "\e[1;33m<!>\e[0m\e[1m Failed to make subvolume $volume writable\e[0m\n"
		done

		# Remove the deployment
		rm -rf $arkdep_dir/deployments/$target

		# Remove from tracker
		grep -v $target $arkdep_dir/tracker > $arkdep_dir/tracker_tmp
		declare tracker_write_exit_code=$?

		# Grep may return a 1 if the file is empty
		if [[ $tracker_write_exit_code -eq 1 ]]; then
			# No matches, this means file is now empty
			truncate -s 0 $arkdep_dir/tracker
		elif [[ $tracker_write_exit_code -eq 2 ]]; then
			# An error occured in grep
			cleanup_and_quit 'Failed to update tracker file'
		fi

		mv $arkdep_dir/tracker_tmp $arkdep_dir/tracker ||
			cleanup_and_quit 'Failed to move tracker_tmp file to tracker'

		# Remove images from cache if requested
		if [[ $clean_cache_on_remove -eq 1 ]]; then
			# Only attempt remove if file exists
			if ls $arkdep_dir/cache/ | grep $target; then
				rm -v $arkdep_dir/cache/$target.tar.*
			fi
		fi
	done

	# Do not exit if run in scriptmode
	if [[ $remove_scriptmode -ne 1 ]]; then
		unlock_and_quit 0
	fi

}

# Scrape all variants from from the repo index page
get_available () {
	printf "\e[1;34m-->\e[0m\e[1m Scraping index from $repo_url/\e[0m\n"

	# Assumes indexing is available and provided by the webserver is in a non-weird format
	declare index=($(curl -sfL $repo_url/ |
		grep -o 'href=".*"' | # Match only anchor tags
		grep -v '"/' | # Exclude root and parent directories
		grep '/"')) # Exclude files

	# Ensure we had at least a single match
	if [[ ${#index} -lt 1 ]]; then
		printf '\e[1;31m<#>\e[0m Index found no matches\e[0m\n'
		unlock_and_quit 1
	fi

	# Extract variant names from index hits and print
	for i in ${index[*]}; do
		i=${i#*\"}
		echo ${i%/*}
	done

	unlock_and_quit 0
}

# Process .pkgs files in provided by server and generate update diff between current and another deployment version
diff () {

	# TODO: Very basic implementation, expand later

	# Set default new variant as diff_target unless param provided
	if [[ -n $1 ]] && [[ $1 != '-' ]]; then
		declare -r diff_target=$1
	else
		declare -r diff_target=$repo_default_image
	fi

	# Set default variant version if not provided, otherwise override of provided
	if [[ -n $2 ]] && [[ $2 != '-' ]]; then
		# Param was provided, set target to param
		# Get ID of (partially) defined entry
		declare curl_data=($(curl -sfL "$repo_url/$diff_target/$database_name" || printf 'ERROR'))

		if [[ $curl_data == 'ERROR' ]]; then
			printf '\e[1;31m<#>\e[0m Failed to download database\e[0m\n'
			unlock_and_quit 1
		fi

		for db_entry in ${curl_data[@]}; do
			if [[ $db_entry == $2* ]]; then
				declare -r database_hit=$db_entry
			fi
		done

	else
		# No param was provided, set first db entry as target
		# Get ID of latest entry
		declare curl_data=($(curl -sfL "$repo_url/$diff_target/$database_name" || printf 'ERROR'))

		if [[ $curl_data == 'ERROR' ]]; then
			printf '\e[1;31m<#>\e[0m Failed to download database\e[0m\n'
			unlock_and_quit 1
		fi

		database_hit=${curl_data[0]}
	fi

	# Ensure data properly received
	if [[ ${#database_hit} -eq 0 ]]; then
		printf 'Failed to find any matching database entries\n'
		unlock_and_quit 1
	fi

	# Process returned data from database
	readarray -d : -t data <<< "$database_hit"
	declare -r deployment_id_new=${data[0]}

	# Process mountinfo to determine current deployment ID
	declare mountinfo=($(cat /proc/self/mountinfo | head -n 1))
	mountinfo=${mountinfo[3]} # Get subvol location
	mountinfo=${mountinfo%/*} # Remove everything after ID
	declare -r deployment_id_old=${mountinfo##*/} # Remove everything before ID

	# Check if we are already running the latest update
	if [[ $deployment_id_old == $deployment_id_new ]]; then
		printf 'Already on latest version\n'
		unlock_and_quit 0
	fi

	# Get new package list
	mapfile new_pkgs < <(curl -sfL $repo_url/$diff_target/$deployment_id_new.pkgs || printf 'ERROR')
	# Get old package list
	mapfile old_pkgs < <(curl -sfL $repo_url/$repo_default_image/$deployment_id_old.pkgs || printf 'ERROR')

	if [[ $new_pkgs == 'ERROR' ]]; then
		printf '\e[1;31m<#>\e[0m Failed to download new pkgs file\e[0m\n'
		unlock_and_quit 1
	fi

	if [[ $old_pkgs == 'ERROR' ]]; then
		printf '\e[1;31m<#>\e[0m Failed to download old pkgs file\e[0m\n'
		unlock_and_quit 1
	fi

	declare changed=()
	declare old_ver=()
	declare new_ver=()
	declare removed=()
	declare new=()

	# Find updated packages
	for pkg in "${new_pkgs[@]}"; do
		# Split package name and package versions in to list
		declare spaced=($pkg)

		# Find new packages, if a package from the new image is not in the
		# package list of the current version it is new
		if [[ ! "${old_pkgs[@]}" =~ "${spaced[0]}" ]]; then
			new+=("${spaced[0]}")
			continue
		fi

		# Compare new pkgs to old ones
		for old_pkg in "${old_pkgs[@]}"; do
			# Split package name and package versions in to list
			declare old_spaced=($old_pkg)

			# Find matchings packages, compare versions
			# If versions do not match they have been updated/changed/downgraded
			if [[ ${spaced[0]} == ${old_spaced[0]} ]]; then
				if [[ ${spaced[1]} != ${old_spaced[1]} ]]; then
					changed+=("${spaced[0]}")
					old_ver+=("${old_spaced[1]}")
					new_ver+=("${spaced[1]}")
					break
				fi
			fi
		done
	done

	# Find removed packages
	for old_pkg in "${old_pkgs[@]}"; do
		declare old_spaced=($old_pkg)

		# Compare all packages from new image to current package version,
		# current package is not in the new_pkgs list it has been removed
		if [[ ! "${new_pkgs[@]}" =~ "${old_spaced[0]}" ]]; then
			removed+=("${old_spaced[0]}")
		fi
	done

	# Print changed packages and diff
	if [[ $update_diff_style == 'list' ]]; then

		if [[ ${#changed} -ne 0 ]]; then
			declare num=0
			printf 'Changed:\n'
			while [[ $num -lt ${#changed[@]} ]]; do
				printf "  ${changed[$num]}  \e[34m${old_ver[$num]}\e[0m -> \e[32m${new_ver[$num]}\e[0m\n"
				num=$(($num + 1))
			done
		fi

		# Print new packages
		if [[ ${#new[@]} -ne 0 ]]; then
			printf '\nNew:\n'
			for n in "${new[@]}"; do
				printf "  $n\n"
			done
		fi

		# Print removed packages
		if [[ ${#removed[@]} -ne 0 ]]; then
			printf '\nRemoved:\n'
			for rem in "${removed[@]}"; do
				printf "  $rem\n"
			done
		fi
	else
		printf "Update diff style \"$update_diff_style\" does not exist\n"
		unlock_and_quit 1
	fi

	unlock_and_quit 0

}

# Deploy a new or update an existing deployment
deploy () {

	# target and version are optional, if not defined default to primary as defined in
	# /arkdep/config and latest
	if [[ -n $1 ]] && [[ $1 != '-' ]]; then
		declare -r deploy_target=$1
	else
		declare -r deploy_target=$repo_default_image
	fi

	if [[ -n $2 ]]; then
		declare -r deploy_version=$2
	else
		declare -r deploy_version='latest'
	fi

	# If cache requested version may not be latest
	if [[ $1 == 'cache' ]] && [[ $deploy_version == 'latest' ]]; then
		cleanup_and_quit '"latest" and undefined are not a valid version definitions for a cache source'
	fi

	printf "\e[1;34m-->\e[0m\e[1m Deploying $deploy_target $deploy_version\e[0m\n"

	# Split latest_version at the delimiter, creating an array with data.0=package ver, data.1=compression method, data.2=sha1 hash
	# only run if request target is not cache
	if [[ $1 != 'cache' ]]; then

		# If latest is requested grab database and get first line
		printf '\e[1;34m-->\e[0m\e[1m Downloading database\e[0m\n'
		if [[ $deploy_version == 'latest' ]]; then
			declare curl_data=($(curl -sfL "$repo_url/$deploy_target/$database_name" || printf 'ERROR'))

			[[ $curl_data == 'ERROR' ]] && cleanup_and_quit 'Failed to download database file'

			declare -r database_hit=${curl_data[0]}
		elif [[ $deploy_target != 'cache' ]]; then
			# Only return first hit
			declare curl_data=($(curl -sfL "$repo_url/$deploy_target/$database_name" || printf 'ERROR'))

			[[ $curl_data == 'ERROR' ]] && cleanup_and_quit 'Failed to download database file'

			# Find matching database entry
			for db_entry in ${curl_data[@]}; do
				if [[ $db_entry == $2* ]]; then
					declare -r database_hit=$db_entry
				fi
			done

		else
			declare database_hit='cache'
		fi

		readarray -d : -t data <<< "$database_hit"

	# If target is cache
	else

		# Find full name in cache, exclude sig files, if no hit quit with error
		declare cache_hits=($(ls $arkdep_dir/cache | grep -E "^$deploy_version" | grep -v '.sig$'))

		# Temporary var to store the delimited file found in cache
		declare data_inter=()

		# Check if none or more than a single hit, we only expect a single item to match
		[[ ${#cache_hits[@]} -gt 1 ]] && cleanup_and_quit 'More than a single item in cache matched requested version'
		[[ ${#cache_hits[@]} -lt 1 ]] && cleanup_and_quit 'No item in cache matched requested version'

		# Split filename at delimiter
		readarray -d . -t data_inter <<< "$cache_hits"

		# Set expected vars for remainder of script
		data[0]=${data_inter[0]}
		data[1]=${data_inter[2]}
		data[2]='-'

	fi

	# Ensure none of the vars contain whitespaces
	data[0]=${data[0]//[$'\t\r\n']}
	data[1]=${data[1]//[$'\t\r\n']}
	data[2]=${data[2]//[$'\t\r\n']}

	# Lets do a bunch of checks to ensure the data is all present
	if [[ -z ${data[0]+x} ]] || [[ ! -n ${data[0]} ]]; then
		printf '\e[1;31m<#>\e[0m\e[1m No target found\n\e[0m'
		unlock_and_quit 1
	fi

	if [[ -z ${data[1]+x} ]] || [[ ! -n ${data[1]} ]]; then
		printf '\e[1;31m<#>\e[0m\e[1m No compression method found\n\e[0m'
		unlock_and_quit 1
	fi

	if [[ -z ${data[2]+x} ]] || [[ ! -n ${data[2]} ]]; then
		# Do not trigger if hash is -, is used for cache deployments
		if [[ $deploy_target != '-' ]]; then
			printf '\e[1;31m<#>\e[0m\e[1m No checksum found\n\e[0m'
			unlock_and_quit 1
		fi
	fi

	# Lets ensure the requested image is not already deployed
	if [[ -e $arkdep_dir/deployments/${data[0]} ]]; then
		printf "\e[1;33m<!>\e[0m\e[1m ${data[0]} is already deployed, canceling deployment\e[0m\n"
		unlock_and_quit 0
	fi

	if [[ $interactive_mode -eq 1 ]] && [[ $ARKDEP_CONFIRM -ne 1 ]]; then
		printf "${data[0]} from $deploy_target will be deployed.\n\n"
		read -p 'Proceed with deployment? [Y/n] ' remove_confirm

		# To skip confirmation on the cleanup step later
		declare -r cleanup_no_confirm=1

		if [[ ! $remove_confirm =~ ^(y|Y|yes|YES|)$ ]]; then
			unlock_and_quit 1
		fi
	fi

	# Check if requested version is already downloaded
	if [[ -e $arkdep_dir/cache/${data[0]}.tar.${data[1]} ]] && [[ ! -e $arkdep_dir/cache/${data[0]}.tar.${data[1]}.run ]]; then
		printf "\e[1;34m-->\e[0m\e[1m ${data[0]} already in cache, skipping download\e[0m\n"
	else
		printf "\e[1;34m-->\e[0m\e[1m Downloading disk image\e[0m\n"
		# Download the tarball if not yet downloaded

		# Write .run file to indicate process is ongoing and not yet finished, can be used to resume download later
		touch $arkdep_dir/cache/${data[0]}.tar.${data[1]}.run

		# Start the download, prevent shutdown if systemd-inhibit is available
		if command -v systemd-inhibit > /dev/null; then
			systemd-inhibit --who='arkdep deploy' --what='idle:sleep:shutdown' --why='Arkdep is downloading an image' \
				wget -c -q --show-progress -P $arkdep_dir/cache/ "$repo_url/$deploy_target/${data[0]}.tar.${data[1]}" ||
				cleanup_and_quit 'Failed to download tarball'
		else
			wget -c -q --show-progress -P $arkdep_dir/cache/ "$repo_url/$deploy_target/${data[0]}.tar.${data[1]}" ||
			cleanup_and_quit 'Failed to download tarball'
		fi
		# Download GPG signature, only perform check if not disabled by user and keychain exists
		if [[ ! $gpg_signature_check -eq 0 ]] && [[ -s $arkdep_dir/keys/trusted-keys ]]; then

			# Download gpg signature if not yet in cache
			if [[ ! -s $arkdep_dir/cache/${data[0]}.tar.${data[1]}.sig ]]; then
				wget -c -q --show-progress -P $arkdep_dir/cache/ "$repo_url/$deploy_target/${data[0]}.tar.${data[1]}.sig"
				sig_exitcode=$?
			fi

			if [[ ! $sig_exitcode -eq 0 ]] && [[ $gpg_signature_check -eq 1 ]]; then
				# Sig download is allowed to fail
				printf "\e[1;33m<!>\e[0m\e[1m Failed to download GPG signature, signature check will be skipped\e[0m\n"
			elif [[ ! $sig_exitcode -eq 0 ]] && [[ $gpg_signature_check -eq 2 ]]; then
				# gpg_signature_check = 2, error and quit the program on fail
				cleanup_and_quit 'GPG signature check configured to quit on download failure'
			fi

		fi

		# Remove the .run file
		rm $arkdep_dir/cache/${data[0]}.tar.${data[1]}.run

	fi

	if [[ $gpg_signature_check -eq 2 ]] && [[ ! -s $arkdep_dir/cache/${data[0]}.tar.${data[1]}.sig ]]; then
		# if GPG check required but file not present error and quit
		cleanup_and_quit 'GPG signature expected but none were provided'
	elif [[ ! -s $arkdep_dir/cache/${data[0]}.tar.${data[1]}.sig ]]; then
		skip_gpg_check=1
	fi

	# If not configured to skip by previous error handeling check the signature to the downloaded image
	if [[ ! $skip_gpg_check -eq 1 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Checking GPG signature\e[0m\n'

		# Perform GPG signature check
		gpgv --keyring $arkdep_dir/keys/trusted-keys $arkdep_dir/cache/${data[0]}.tar.${data[1]}.sig $arkdep_dir/cache/${data[0]}.tar.${data[1]} ||
			cleanup_and_quit 'gpg check failed'

	elif [[ ${data[2]} != '-' ]]; then
		# If GPG check not triggered instead check hash, unless defined as -

		printf '\e[1;34m-->\e[0m\e[1m Validating integrity\e[0m\n'

		# Identify used checksum method
		if [[ ${#data[2]} -eq 40 ]]; then
			# If it is a sha-1
			sha1sum $arkdep_dir/cache/${data[0]}.tar.${data[1]} |
				grep "${data[2]}" ||
				cleanup_and_quit 'SHA-1 checksum does not match the one defined in database\e[0m\n'
		elif [[ ${#data[2]} -eq 56 ]]; then
			# If it is sha-224
			sha224sum $arkdep_dir/cache/${data[0]}.tar.${data[1]} |
				grep "${data[2]}" ||
				cleanup_and_quit 'SHA-224 checksum does not match the one defined in database\e[0m\n'
		elif [[ ${#data[2]} -eq 64 ]]; then
			# If it is sha-256
			sha256sum $arkdep_dir/cache/${data[0]}.tar.${data[1]} |
				grep "${data[2]}" ||
				cleanup_and_quit 'SHA-256 checksum does not match the one defined in database\e[0m\n'
		elif [[ ${#data[2]} -eq 96 ]]; then
			# If it is sha-384
			sha384sum $arkdep_dir/cache/${data[0]}.tar.${data[1]} |
				grep "${data[2]}" ||
				cleanup_and_quit 'SHA-384 checksum does not match the one defined in database\e[0m\n'
		elif [[ ${#data[2]} -eq 128 ]]; then
			# If it is a sha-512
			sha512sum $arkdep_dir/cache/${data[0]}.tar.${data[1]} |
				grep "${data[2]}" ||
				cleanup_and_quit 'SHA-512 Checksum does not match the one defined in database\e[0m\n'
		else
			cleanup_and_quit 'Failed to identify SHA checksum type'
		fi

	fi

	# We will now start writing the images to disk, prevent the user from shutting down the script
	trap '' INT TERM

	# Check if there is a migration script available
	if tar -xf $arkdep_dir/cache/${data[0]}.tar.${data[1]} -C $arkdep_dir/cache/ ./${data[0]}-migration.sh 2> /dev/null; then
		printf '\e[1;34m-->\e[0m\e[1m Running migration script\e[0m\n'
		# Run the migration script if provided
		(source $arkdep_dir/cache/${data[0]}-migration.sh)

		# It is assumed that the migration script will do its own cleanup on migration related files

		printf '\e[1;34m-->\e[0m\e[1m Removing migration script\e[0m\n'
		rm $arkdep_dir/cache/${data[0]}-migration.sh

		unlock_and_quit 0
	fi

	# Extract the root image if not yet extracted
	printf '\e[1;34m-->\e[0m\e[1m Writing root\e[0m\n'

	# Create directory using unique deployment name
	mkdir -p $arkdep_dir/deployments/${data[0]} || cleanup_and_quit 'Failed to create deployment directory'

	# Extra image from tarball to stdin and receive
	tar -xOf $arkdep_dir/cache/${data[0]}.tar.${data[1]} "./${data[0]}-rootfs.img" |
		btrfs receive $arkdep_dir/deployments/${data[0]} ||
		cleanup_and_quit 'Failed to receive root'

	# Extract the etc image if not yet extracted
	printf '\e[1;34m-->\e[0m\e[1m Writing etc\e[0m\n'

	# Write the etc image and create var directory, we have to unlock rootfs temporarily to do this
	btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs ro false ||
		cleanup_and_quit 'Failed to unlock root to write etc'

	# Extra image from tarball to stdin and receive
	tar -xOf $arkdep_dir/cache/${data[0]}.tar.${data[1]} "./${data[0]}-etc.img" |
		btrfs receive $arkdep_dir/deployments/${data[0]}/rootfs/ ||
		cleanup_and_quit 'Failed to receive etc'

	# Unlock the etc deployment
	btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs/etc ro false ||
		cleanup_and_quit 'Failed to unlock root to write etc'

	# Write the var image
	printf '\e[1;34m-->\e[0m\e[1m Writing var\e[0m\n'

	# Extra image from tarball to stdin and receive
	tar -xOf $arkdep_dir/cache/${data[0]}.tar.${data[1]} "./${data[0]}-var.img" |
		btrfs receive $arkdep_dir/deployments/${data[0]}/rootfs/ ||
		cleanup_and_quit 'Failed to receive var'

	# Make var writable
	btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs/var ro false ||
		cleanup_and_quit 'Failed to unlock var'

	# Add overlay if enabled
	if [[ $enable_overlay -eq 1 ]]; then
		# If backup_user_accounts is enabled automatically perform a backup, do not run if custom root is defined
		if [[ $backup_user_accounts -eq 1 ]] && [[ ! -n $ARKDEP_ROOT ]]; then

			printf '\e[1;34m-->\e[0m\e[1m Copying user account files to overlay if changed\e[0m\n'

			for file in passwd shadow group subuid subgid; do
				# Hash old and new file to compare
				# No need to handle file not exist scenario, we can assume /etc/$file to always exist
				declare checksum_old=($(sha256sum $arkdep_dir/overlay/etc/$file 2> /dev/null))
				declare checksum_current=($(sha256sum /etc/$file 2> /dev/null))

				if [[ ! ${checksum_old[0]} == ${checksum_current[0]} ]]; then
					printf "Copying $file\n"
					cp -p /etc/$file $arkdep_dir/overlay/etc/$file ||
						printf "Failed to copy $file\n"
				fi
			done

			# Ensure shadow file permissions are set properly
			chmod 600 $arkdep_dir/overlay/etc/shadow

		fi
		printf '\e[1;34m-->\e[0m\e[1m Copying overlay to deployment\e[0m\n'
		cp -rvp $arkdep_dir/overlay/* $arkdep_dir/deployments/${data[0]}/rootfs/
	fi

	# Layer addition packages if defined
	declare -r layer_list=($(cat $arkdep_dir/layer))

	if [[ ${#layer_list[@]} -ne 0 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Layering additional packages\e[0m\n'

		# Bind mind, otherwise arch-chroot complains
		mount --bind $arkdep_dir/deployments/${data[0]}/rootfs $arkdep_dir/deployments/${data[0]}/rootfs

		# Install the layer packages
		arch-chroot $arkdep_dir/deployments/${data[0]}/rootfs pacman -S --noconfirm ${layer_list[@]} ||
			printf "\e[1;33m<!>\e[0m\e[1m Error during package installation, validate your package layer configuration\e[0m\n"

		# Unmount the installation target again
		umount $arkdep_dir/deployments/${data[0]}/rootfs
	fi

	# Migrate specified files and directories
	if [[ ${#migrate_files[@]} -ge 1 ]] && [[ ! -n $ARKDEP_ROOT ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Migrating local files to new deployment\e[0m\n'
		for file in ${migrate_files[@]}; do
			[[ ! -e /$file ]] && continue
			printf "Copying $file\n"
			cp -rp /$file $arkdep_dir/deployments/${data[0]}/rootfs/${file%/*}
		done
	fi

	printf '\e[1;34m-->\e[0m\e[1m Locking new root partition\e[0m\n'
	# Lock the root volume again
	btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs ro true ||
		cleanup_and_quit 'Failed to lock root'

	printf '\e[1;34m-->\e[0m\e[1m Installing kernel image to boot\e[0m\n'
	# Get list of all available kernels
	kernels_installed=($(ls $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/modules/))

	# Error if no kernels installed
	if [[ ${#kernels_installed[@]} -eq 0 ]]; then
		cleanup_and_quit 'Provided image has no kernel installed'
	fi

	mkdir -p $arkdep_boot/arkdep/${data[0]}
	# Deploy kernel to /boot, deploy first hit of kernels_installed
	printf "Copying ${kernels_installed[0]}/vmlinuz\n"
	cp $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/modules/${kernels_installed[0]}/vmlinuz $arkdep_boot/arkdep/${data[0]}/ ||
		cleanup_and_quit 'Failed to copy kernel image'

	# Deploy CPU firmware to boot
	if [[ $update_cpu_microcode -eq 1 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Checking for CPU microcode updates\e[0m\n'

		for ucode in $(ls $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/ | grep ucode); do
			# Hash current and new file to compare
			# No need to handle file not exist scenario, we can assume /etc/$file to always exist
			declare checksum_new=($(sha256sum $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/$ucode))
			declare checksum_current=($(sha256sum $arkdep_boot/$ucode))

			if [[ ! ${checksum_new[0]} == ${checksum_current[0]} ]]; then
				printf "Copying $ucode\n"
				cp $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/$ucode $arkdep_boot/$ucode ||
					printf "Failed to copy $ucode\n"
			fi
		done
	fi

	# Install kernel and generate initramfs
	printf '\e[1;34m-->\e[0m\e[1m Generating initramfs\e[0m\n'

	# If ARKDEP_BOOT is defined we will revert back to legacy behaviour and build
	# the initramfs against the host
	if [[ -n $ARKDEP_BOOT ]] || [[ $ARKDEP_LEGACY_INITRAMFS -eq 1 ]]; then

		dracut -q -k $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/modules/${kernels_installed[0]} \
			-c $arkdep_dir/deployments/${data[0]}/rootfs/etc/dracut.conf \
			--confdir $arkdep_dir/deployments/${data[0]}/rootfs/etc/dracut.conf.d \
			--kernel-image $arkdep_boot/arkdep/${data[0]}/vmlinuz \
			--kver ${kernels_installed[0]} \
			--force \
			$arkdep_boot/arkdep/${data[0]}/initramfs-linux.img || cleanup_and_quit 'Failed to generate initramfs'

	else
		# Bind mind, otherwise arch-chroot complains
		mount --bind $arkdep_dir/deployments/${data[0]}/rootfs $arkdep_dir/deployments/${data[0]}/rootfs

		# Temporarily mount /boot in to new root
		mount --bind $arkdep_boot $arkdep_dir/deployments/${data[0]}/rootfs/${arkdep_boot//"$ARKDEP_ROOT"}

		# Generate new initramfs
		# TODO: Make sure this implementation is not iffy
		arch-chroot $arkdep_dir/deployments/${data[0]}/rootfs \
			dracut -q -k /usr/lib/modules/${kernels_installed[0]} \
			-c /etc/dracut.conf \
			--confdir /etc/dracut.conf.d \
			--kernel-image ${arkdep_boot//"$ARKDEP_ROOT"}/arkdep/${data[0]}/vmlinuz \
			--kver ${kernels_installed[0]} \
			--force \
			${arkdep_boot//"$ARKDEP_ROOT"}/arkdep/${data[0]}/initramfs-linux.img || cleanup_and_quit 'Failed to generate initramfs'

		# Unmount the initramfs target again
		umount $arkdep_dir/deployments/${data[0]}/rootfs/boot
		umount $arkdep_dir/deployments/${data[0]}/rootfs
	fi

	if [[ $load_extensions -eq 1 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Running extensions\e[0m\n'

		extensions=($(ls $arkdep_dir/extensions/))

		for extension in ${extensions[@]}; do
			(source $arkdep_dir/extensions/$extension)
		done
	fi

	# Add to database
	printf '\e[1;34m-->\e[0m\e[1m Updating deployment tracker\e[0m\n'
	printf "${data[0]}\n$(cat $(readlink -m $arkdep_dir/tracker))" |
		tee $arkdep_dir/tracker_tmp
	mv $arkdep_dir/tracker_tmp $arkdep_dir/tracker

	# Deploy bootloader configuration
	# also insert newline
	printf '\n\e[1;34m-->\e[0m\e[1m Adding bootloader entry\e[0m\n'

	# Ensure bootloader entry is not already present, if it us replace it
	if [[ -f $arkdep_boot/loader/entries/${data[0]}.conf ]]; then
		printf "\e[1;33m<!>\e[0m\e[1m A bootloader entry for ${data[0]} is already installed, bootloader entry will be regenerated\e[0m\n"
		rm $arkdep_boot/loader/entries/${data[0]}.conf
	fi

	# Load systemd-boot template in to list
	mapfile systemd_boot_template < $arkdep_dir/templates/systemd-boot ||
		cleanup_and_quit 'Failed to read systemd-boot template file'

	# Write bootloader entry
	for line in "${systemd_boot_template[@]}"; do
		echo ${line/\%target\%/${data[0]}} >> $arkdep_boot/loader/entries/$(date +%Y%m%d-%H%M%S)-${data[0]}+3.conf ||
			cleanup_and_quit 'Failed to write systemd-boot entry'
	done

	# Check if there is an update script available
	if [[ ! -n $ARKDEP_ROOT ]]; then
		if tar -xf $arkdep_dir/cache/${data[0]}.tar.${data[1]} -C $arkdep_dir/cache/ ./${data[0]}-update.sh 2> /dev/null; then
			printf '\e[1;34m-->\e[0m\e[1m Running update script\e[0m\n'
			# Run the migration script if provided
			(source $arkdep_dir/cache/${data[0]}-update.sh)

			printf '\e[1;34m-->\e[0m\e[1m Removing update script\e[0m\n'
			rm $arkdep_dir/cache/${data[0]}-update.sh
		fi
	fi

	# Image deployment finished, allow for interupts again
	trap 'echo "User interupt received"; unlock_and_quit 4' INT TERM

	# Get list of all tarballs in cache
	declare -r tarball_hits=($(ls $arkdep_dir/cache/ | grep -E '.*.tar..*'))

	# Remove tarball if configured to remove we have hits
	if [[ $remove_tar_after_deployment -eq 1 ]] && [[ ${#tarball_hits[@]} -gt 0 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Removing tarball from cache\e[0m\n'

		for tarball in ${tarball_hits[@]}; do
			printf "Removing $tarball\n"
			rm $arkdep_dir/cache/$tarball
		done
	fi

	# Remove entries outside of keep, ignore first hit to allow N in config file
	# instead of requiring N+1 to be configured
	declare remove_deployments=($(tail -n +$deploy_keep $arkdep_dir/tracker))
	remove_deployments=(${remove_deployments[@]:1})

	# Remove old deployments
	if [[ ${#remove_deployments[@]} -ge 1 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Cleaning up old deployments\e[0m\n'
		declare -r remove_scriptmode=1
		remove_deployment ${remove_deployments[@]}
	fi

	unlock_and_quit 0

}

# Install a package and add it to the layer tracker
layer () {

	# If no installation candidates provided
	if [[ $# -eq 1 ]]; then
		cleanup_and_quit 'No layer candidates provided'
	fi

	# By default, as it is discouraged, the layer tracker file is only created upon calling this function
	# Create layer tracker file if it does not yet exist
	if [[ ! -f $arkdep_dir/layer ]]; then
		printf "\e[1;33m<!>\e[0m\e[1m Creating new layer tracker file\e[0m\n"
		touch $arkdep_dir/layer
	fi

	# Index layer tracker list
	declare -r layered=($(cat $arkdep_dir/layer))

	# Program list with duplicates removed
	declare progs_clean=()

	# Check if prog is already in list, if not add to progs_clean
	if [[ ${#layered[@]} -gt 0 ]]; then
		for prog in ${@:2}; do
			for layer in ${layered[@]}; do
				# If program is already present in layer tracker it can be ignored
				if [[ $layer == $prog ]]; then
					printf "\e[1;33m<!>\e[0m\e[1m $prog is already in layer tracker file and will be ignored\e[0m\n"
					declare layer_matched=1
					break
				fi
			done

			# Add program to layer list unless duplicate
			if [[ $layer_matched -ne 1 ]]; then
				progs_clean+=($prog)
			fi

			unset layer_matched
		done
	else
		progs_clean=(${@:2})
	fi

	# If progs_clean is zero, error and quit
	if [[ ${#progs_clean[@]} -eq 0 ]]; then
		printf "\e[1;33m<!>\e[0m\e[1m All provided layer candidates are already layered\e[0m\n"
		unlock_and_quit 0
	fi

	# Notify user of changes and ask for permission if interactive_mode is enabled
	printf '\e[1;34m-->\e[0m\e[1m The following packages will be layered\e[0m\n'
	echo "${progs_clean[@]}" | column
	if [[ $interactive_mode -eq 1 ]]; then
		read -p 'Proceed with installation? [Y/n] ' remove_confirm

		if [[ ! $remove_confirm =~ ^(y|Y|yes|YES|)$ ]]; then
			unlock_and_quit 1
		fi
	fi

	# Check if root should be locked again after running
	if btrfs property get / 2> /dev/null | grep -q 'ro=true'; then
		declare -r lock_when_done=1
	fi

	# Unlock root
	if [[ $lock_when_done -eq 1 ]]; then
		btrfs property set -f -ts / ro false ||
			cleanup_and_quit 'Failed to unlock root partition'
	fi

	# Install the packages
	$package_layer_command ${progs_clean[@]} ||
		cleanup_and_quit 'Failed to install requested packages'

	# Lock root if it was previously locked
	if [[ $lock_when_done -eq 1 ]]; then
		btrfs property set -f -ts / ro true ||
			cleanup_and_quit 'Failed to unlock root partition'
	fi

	# If installation was successful add to layer tracker file
	printf "%s\n" "${progs_clean[@]}" >> $arkdep_dir/layer

	unlock_and_quit 0

}

layer-ls () {
	cat $arkdep_dir/layer | column
	unlock_and_quit 0
}

unlayer () {
	# Index layer tracker list
	declare -r layered=($(cat $arkdep_dir/layer))

	# Program list with duplicates removed
	declare progs_matched=()
	declare progs_nomatch=()

	# Check if prog is already in list, if they are add to progs_matched
	if [[ ${#layered[@]} -gt 0 ]]; then
		for prog in ${@:2}; do
			for layer in ${layered[@]}; do
				# If prog matched add to matched list
				if [[ $layer == $prog ]]; then
					progs_matched+=($prog)
					declare layer_matched=1
					break
				fi
			done

			# Add program to nomatch if no match was found
			if [[ $layer_matched -ne 1 ]]; then
				progs_nomatch+=($prog)
			fi

			unset layer_matched
		done
	else
		printf "\e[1;33m<!>\e[0m\e[1m No packages are layered, thus there are none to remove from the layer\e[0m\n"
		unlock_and_quit 0
	fi

	# Notify user if provided target progs are not layered
	if [[ ${#progs_nomatch[@]} -ne 0 ]]; then
		printf "\e[1;33m<!>\e[0m\e[1m The following packages were provided by are not currently layered\e[0m\n"
		echo "${progs_nomatch[@]}" | column
	fi

	# Confirm if user wishes to remove packages from layer
	printf '\e[1;34m-->\e[0m\e[1m The following packages will be removed from the layered\e[0m\n'
	echo "${progs_matched[@]}" | column
	if [[ $interactive_mode -eq 1 ]]; then
		read -p 'Proceed with removal? [Y/n] ' remove_confirm

		if [[ ! $remove_confirm =~ ^(y|Y|yes|YES|)$ ]]; then
			unlock_and_quit 1
		fi
	fi

	# Check if root should be locked again after running
	if btrfs property get / 2> /dev/null | grep -q 'ro=true'; then
		declare -r lock_when_done=1
	fi

	# Unlock root
	if [[ $lock_when_done -eq 1 ]]; then
		btrfs property set -f -ts / ro false ||
			cleanup_and_quit 'Failed to unlock root partition'
	fi

	$package_unlayer_command ${progs_matched[@]} ||
		cleanup_and_quit 'failed to remove packages from layer'

	# Lock root if it was previously locked
	if [[ $lock_when_done -eq 1 ]]; then
		btrfs property set -f -ts / ro true ||
			cleanup_and_quit 'Failed to unlock root partition'
	fi

	# Remove package from layer tracker
	declare -r grep_string=$(printf %s\| ${progs_matched[@]})
	declare -r new_layer_tracker=$(grep -Ev "${grep_string::-1}" $arkdep_dir/layer)
	printf "%s\n" "$new_layer_tracker" > $arkdep_dir/layer

	unlock_and_quit 0
}

[[ $1 == 'init' ]] && init_new_system $2
[[ $1 == 'teardown' ]] && teardown
[[ $1 == 'get-available' ]] && get_available
[[ $1 == 'diff' ]] && diff $2 $3
[[ $1 == 'deploy' ]] && deploy $2 $3
[[ $1 == 'remove' ]] && remove_deployment $@
[[ $1 == 'healthcheck' ]] && healthcheck $1
[[ $1 == 'cleanup' ]] && cleanup
[[ $1 == 'layer' ]] && layer $@
[[ $1 == 'layer-ls' ]] && layer-ls
[[ $1 == 'unlayer' ]] && unlayer $@

# No valid params were provided
printf '\e[1;31m<#>\e[0m\e[1m No valid parameters provided\e[0m\n'
unlock_and_quit 3
