arkdep/arkdep
2024-09-06 05:38:51 +02:00

1186 lines
42 KiB
Bash
Executable file

#!/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
## 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
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=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://repo.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 ${migrate_files+x} ]] && 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') && 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=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'
[[ -z ${update_diff_style+x} ]] && update_diff_style='list' && 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=1 && printf '\e[1;33m<!>\e[0m\e[1m interactive_mode 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
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
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
## 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)$ ]]; 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
init () {
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 root tree to new root filesystem
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')
# Load script extensions from /arkdep/extensions
load_extensions=0
# Remove tarball from cache once deployment is finished
remove_tar_after_deployment=1
# Update diff styling, available styles: 'list'
update_diff_style='list'
# Before making changes to the system show diff and ask for confirmation
interactive_mode=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
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 -sf $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
# Get ID of (partially) defined entry
declare database_hit=$(curl -sf "$repo_url/$diff_target/database" || printf 'ERROR')
if [[ $index == 'ERROR' ]]; then
printf '\e[1;31m<#>\e[0m Failed to download database\e[0m\n'
exit 1
fi
database_hit=$(printf $database_hit | grep -E "^$2" | head -1)
else
# Get ID of latest entry
declare database_hit=$(curl -sf "$repo_url/$diff_target/database" || printf 'ERROR')
if [[ $index == 'ERROR' ]]; then
printf '\e[1;31m<#>\e[0m Failed to download database\e[0m\n'
exit 1
fi
database_hit=$(printf $database_hit | head -n 1)
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 -sf $repo_url/$diff_target/$deployment_id_new.pkgs || printf 'ERROR')
# Get old package list
mapfile old_pkgs < <(curl -sf $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=()
# Process new_pkgs list to generate update diff
for pkg in "${new_pkgs[@]}"; do
# Split package name and package versions in to list
declare spaced=($pkg)
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 [[ ${spaced[0]} == ${old_spaced[0]} ]]; then
if [[ ${spaced[1]} != ${old_spaced[1]} ]]; then
#printf "DIFF ${spaced[1]} >> ${old_spaced[1]}\n"
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)
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 () {
# Allow for a clean shutdown, later this is blocked once deployment starts
trap 'echo "User interupt received, interupting download"; unlock_and_quit 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" || printf 'ERROR')
[[ $curl_data == 'ERROR' ]] && cleanup_and_quit 'Failed to download database file'
declare -r database_hit=$(printf $curl_data | head -n 1)
elif [[ $deploy_target != 'cache' ]]; then
# Only return first hit
declare curl_data=($(curl -sf "${repo_url}/${deploy_target}/database" || 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
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)
# 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'
# 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
# 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))
declare checksum_current=($(sha256sum /etc/$file))
if [[ ! ${checksum_old[0]} == ${checksum_current[0]} ]]; then
printf "Copying $file\n"
cp /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'
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 [[ ${#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 -r /$file $arkdep_dir/deployments/${data[0]}/rootfs/${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
# 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'
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'
# 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 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)
printf '\e[1;34m-->\e[0m\e[1m Removing update script\e[0m\n'
rm $arkdep_dir/cache/${data[0]}-update.sh
fi
# Image deployment finished, allow for interupts again
trap 'echo "User interupt received, canceling cleanup step"; 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
}
[[ $1 == 'init' ]] && init $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
# No valid params were provided
printf '\e[1;31m<#>\e[0m\e[1m No valid parameters provided\e[0m\n'
unlock_and_quit 3