#!/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

## 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_BOOT is set also set ARKDEP_NO_BOOTCTL
[[ -n $ARKDEP_BOOT ]] && declare -r ARKDEP_NO_BOOTCTL=1

if [[ ! -d $arkdep_dir ]] && [[ ! $1 == 'init' ]]; then
	printf "\e[1;31m<#>\e[0m\e[1m Arkep 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
	source $arkdep_dir/config

	# Set default variables if config variables are undefined
	[[ -z ${enable_overlay+x} ]] && enable_overlay=1 && 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='https://arkanelinux.org/arkdep' && 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='arkanelinux' && 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=3 && 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=1 && 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=1 && 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=1 && 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=153600 && 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=12582912 && 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=1 && 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=1 && printf '\e[1;33m<!>\e[0m\e[1m backup_user_accounts not defined in config, using default\e[0m\n'
	[[ -z ${latest_image_always_default+x} ]] && latest_image_always_default=0 && printf '\e[1;33m<!>\e[0m\e[1m latest_image_always_default not defined in config, using default\e[0m\n'
	[[ -z ${var_migrate_files+x} ]] && var_migrate_files=('/usrlocal' '/usrliblocale' '/opt' '/srv' '/nm-system-connections' '/lib/AccountsService' '/lib/bluetooth' '/lib/NetworkManager' '/lib/arkane') && printf '\e[1;33m<!>\e[0m\e[1m var_migrate_files not defined in config, using default\e[0m\n'
	[[ -z ${load_extensions+x} ]] && load_extensions=0 && 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=1 && printf '\e[1;33m<!>\e[0m\e[1m remove_tar_after_deployment not defined in config, using default\e[0m\n'
fi

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

	# If any paramters 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'
			exit 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
		btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs/var ro false
		btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs ro false
		btrfs subvolume delete $arkdep_dir/deployments/${data[0]}/rootfs/etc
		btrfs subvolume delete $arkdep_dir/deployments/${data[0]}/rootfs/var
		btrfs subvolume delete $arkdep_dir/deployments/${data[0]}/rootfs
		rm -rfv $arkdep_dir/deployments/${data[0]} \
			$arkdep_boot/arkdep/${data[0]}
		rm -v $arkdep_dir/cache/${data[0]}-*.img \
			$arkdep_boot/loader/entries/${data[0]}.conf
	fi

	exit 1

}

## 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' ]] && exit 0

}

cleanup () {

	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
			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 "failed to make subvol $volume writable\n"
			done

			# Remove the deployment
			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
			rm -v $arkdep_dir/cache/$target
		done
	fi

	exit 0
}

# Always healthcheck on run if requested in config, unless the user explicitely called it
[[ $always_healthcheck -eq 1 ]] && [[ ! $1 == 'healthcheck' ]] && healthcheck

## Error checking
#
# Quit if not root, only run if required
if [[ ! $1 =~ ^(get-available|healthcheck) ]]; 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' &&
		exit 1
	fi
fi

# Check if all dependencies are installed, quit if not
for prog in btrfs wget dracut bootctl curl gpg gpgv; do

	# If ARKDEP_NO_BOOTCTL defined do not enforce bootctl requirement
	if [[ $prog == 'bootctl' ]] && [[ $ARKDEP_NO_BOOTCTL -eq 1 ]]; then
		break
	fi

	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 ]] && exit 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"
		exit 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"
		exit 1
	fi
fi

## Core functions
#
# Initialize the system for arkdep
init () {

	# Ensure systemd-boot is installed before continuing, for it is the only thing we support
	# Do not run if ARKDEP_NO_BOOTCTL defined
	if [[ ! $ARKDEP_NO_BOOTCTL -eq 1 ]]; then
		bootctl -q is-installed || cleanup_and_quit 'systemd-boot seems to not be installed'
	else
		printf '\e[1;33m<!>\e[0m\e[1m Not running bootctl is-installed because overwritten with ARKDEP_NO_BOOTCTL\e[0m\n'
	fi

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

	[[ -d $arkdep_dir ]] && 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=1

	# URL to image repository, do not add trailing slash
	repo_url='https://repo.arkanelinux.org/arkdep'

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

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

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

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

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

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

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

	# Update CPU firmware if newer version available
	update_cpu_microcode=1

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

	# Ensure latest image as defined in the external database is always the default systemd-boot boot entry
	latest_image_always_default=0

	# List of files and folders to be recursively copied over from var to new var, path should start with /
	var_migrate_files=('/usrlocal' '/usrliblocale' '/opt' '/srv' '/nm-system-connections' '/lib/AccountsService' '/lib/bluetooth' '/lib/NetworkManager' '/lib/arkane')

	# Load script extensions from /arkdep/extensions
	load_extensions=0

	# Remove tarball from cache once deployment is finished
	remove_tar_after_deployment=1
	END

	# Add default bootloader config file
	cat <<- END > $arkdep_dir/templates/systemd-boot
	title Arkane GNU/Linux - Arkdep
	linux /arkdep/%target%/vmlinuz
	initrd /amd-ucode.img
	initrd /intel-ucode.img
	initrd /arkdep/%target%/initramfs-linux.img
	options root="LABEL=arkane_root" rootflags=subvol=/arkdep/deployments/%target%/rootfs rw
	END

	exit 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"
			exit 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

	exit 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'
		exit 1
	fi

	for deployment in ${@:2}; 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

		# 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

	if [[ $remove_no_quit -ne 1 ]]; then
		exit 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"

	# Lets first check for server errors, curl does not return an non-zero exit code on server errors
	declare -r status_code=$(curl -s -o /dev/null --write-out "%{http_code}" $repo_url/)

	# Error if server returned a status code other than 200
	if [[ $status_code -ne 200 ]]; then
		printf "\e[1;31m<#>\e[0m Server returned a $status_code status code instead of the expected 200 indicating some type of server error\e[0m\n"
		exit 1
	fi

	# Assumes indexing is available and provided by the webserver is in a non-weird format
	declare index=($(curl -s $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'
		exit 1
	fi

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

	exit 0
}

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

	# Allow for a clean shutdown, later this is blocked once deployment starts
	trap 'echo "User interupt received, interupting download"; exit 4' INT TERM

	# 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 -sf "${repo_url}/${deploy_target}/database" | head -n 1)
		elif [[ $deploy_target != 'cache' ]]; then
			# Only return first hit
			declare curl_data=$(curl -sf "${repo_url}/${deploy_target}/database" | grep -E "^$2" | head -1)
		else
			declare curl_data='cache'
		fi

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

	# 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'
		exit 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'
		exit 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'
			exit 1
		fi
	fi

	# Lets ensure the requested image is not already deployed
	if [[ -e $arkdep_dir/deployments/${data[0]} ]]; then

		# Lets ensure the latest deployment is active, even if it is already deployed
		if [[ latest_image_always_default -eq 1 ]] && [[ ! $ARKDEP_NO_BOOTCTL -eq 1 ]]; then
			# Allow it to error, it is no big deal if it does
			bootctl set-default ${data[0]}.conf || printf '\e[1;33m<!>\e[0m\e[1m Failed to set default bootloader entry on latest_image_always_default\e[0m\n'
		fi

		printf "\e[1;33m<!>\e[0m\e[1m ${data[0]} is already deployed, canceling deployment\e[0m\n"
		exit 0
	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
		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'

		# 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)

		# TODO: Evaluate if this is how we would really like to do this
		#
		# Cancel the update once migration is finished, we are assuming the migration script did all the work for us
		exit 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'

	# 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'

	# 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; do
				if ! cmp --silent $arkdep_dir/overlay/etc/$file /etc/$file; then
					cp -v /etc/$file $arkdep_dir/overlay/etc/$file
				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'
		declare -r overlay_files=($(ls $arkdep_dir/overlay/))

		# Check if only /etc is present, if it is we do not have to unlock the root volume
		for file in ${overlay_files[*]}; do
			if [[ $file != 'etc' ]]; then
				printf "\e[1;33m<!>\e[0m\e[1m Non /etc file or directory detected, root will be temporarily unlocked\e[0m\n"
				overlay_unlock_root=1
			fi
		done

		# Unlock root if required
		if [[ $overlay_unlock_root -eq 1 ]]; then
			btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs ro false
		fi

		cp -rv $arkdep_dir/overlay/* $arkdep_dir/deployments/${data[0]}/rootfs/

		# Lock root again if required
		if [[ $overlay_unlock_root -eq 1 ]]; then
			btrfs property set -f -ts $arkdep_dir/deployments/${data[0]}/rootfs ro true
		fi
	fi

	# Migrate specified files and directories
	if [[ ${#var_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 ${var_migrate_files[@]}; do
			printf "Copying /var$file\n"
			cp -r /var/$file $arkdep_dir/deployments/${data[0]}/rootfs/var/${file%/*}
		done
	fi

	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/))
	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
			# If CPU firmware present in both image and install
			if ! cmp --silent $arkdep_boot/$ucode $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/$ucode; then
				printf "Copying $ucode\n"
				cp $arkdep_dir/deployments/${data[0]}/rootfs/usr/lib/$ucode $arkdep_boot/$ucode ||
					cleanup_and_quit 'Failed to copy microcode'
			fi
		done

	fi

	# Install kernel and generate initramfs
	printf '\e[1;34m-->\e[0m\e[1m Generating initramfs\e[0m\n'
	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'

	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 database\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'
	sed "s/%target%/${data[0]}/" $arkdep_dir/templates/systemd-boot > $arkdep_boot/loader/entries/${data[0]}.conf

	# Set new deployment as default bootloader entry
	printf '\e[1;34m-->\e[0m\e[1m Setting new bootloader entry as default\e[0m\n'
	# Do not set default boot entry if ARKDEP_NO_BOOTCTL is set
	if [[ ! $ARKDEP_NO_BOOTCTL -eq 1 ]]; then
		bootctl set-default ${data[0]}.conf || cleanup_and_quit "Failed to set default bootloader entry"
	else
		printf '\e[1;33m<!>\e[0m\e[1m Not running bootctl set-default because overwritten with ARKDEP_NO_BOOTCTL\e[0m\n'
	fi

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

	# Check if there is an update script available
	if tar -xf $arkdep_dir/cache/${data[0]}.tar.${data[1]} -C $arkdep_dir/cache/ ./${data[0]}-update.sh 2> /dev/null && [[ ! -n $ARKDEP_ROOT ]]; 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)
	fi

	# Remove tarball if configured to remove
	if [[ $remove_tar_after_deployment -eq 1 ]]; then
		printf '\e[1;34m-->\e[0m\e[1m Removing tarball from cache\e[0m\n'
		rm $arkdep_dir/cache/${data[0]}.tar.*
	fi

	# Remove entries outside of keep
	declare -r remove_deployments=($(tail -n +$deploy_keep $arkdep_dir/tracker))

	# 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_no_quit=1
		remove_deployment ${remove_deployments[@]}
	fi

	exit 0

}

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

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