1186 lines
42 KiB
Bash
Executable file
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
|