Update jlmkr.sh

This commit is contained in:
Jip-Hop 2023-01-25 21:06:35 +01:00
parent 65741bec66
commit eb49c0e833
1 changed files with 81 additions and 133 deletions

214
jlmkr.sh
View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
set -eEuo pipefail set -euo pipefail
shopt -s nullglob shopt -s nullglob
ABSOLUTE_SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")" ABSOLUTE_SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
@ -16,9 +16,7 @@ TODO: complete writing usage
JAILS_DIR_PATH='jails' JAILS_DIR_PATH='jails'
JAIL_ROOTFS_NAME='rootfs' JAIL_ROOTFS_NAME='rootfs'
JAIL_PATH=
JAIL_CONFIG_NAME='config' JAIL_CONFIG_NAME='config'
JAIL_START_SCRIPT_NAME='start.sh'
fail() { fail() {
echo -e "$1" >&2 && exit 1 echo -e "$1" >&2 && exit 1
@ -35,33 +33,39 @@ err() {
# Trap errors # Trap errors
trap 'err $LINENO' ERR trap 'err $LINENO' ERR
######################### #####################
# START RUN FUNCTIONALITY # START FUNCTIONALITY
######################### #####################
start_jail() { start_jail() {
local jail_config_path jail_start_script_path docker_compatible gpu_passthrough key value local jail_name="${1}"
local jail_path="${JAILS_DIR_PATH}/${jail_name}"
local jail_config_path="${jail_path}/${JAIL_CONFIG_NAME}"
jail_config_path="${1}/${JAIL_CONFIG_NAME}"
jail_start_script_path="${1}/${JAIL_START_SCRIPT_NAME}"
! [[ -f "${jail_start_script_path}" ]] && fail "ERROR: Couldn't find: ${jail_start_script_path}"
! [[ -f "${jail_config_path}" ]] && fail "ERROR: Couldn't find: ${jail_config_path}" ! [[ -f "${jail_config_path}" ]] && fail "ERROR: Couldn't find: ${jail_config_path}"
echo 'Load the config' echo 'Load the config'
# TODO: also load the additional (user) args for nspawn from this file! local key value
while IFS="=" read -r key value || [ -n "$key" ]; do
while read -r line || [ -n "$line" ]; do
key="${line%%=*}"
value="${line#*=}"
case "${key}" in case "${key}" in
"DOCKER_COMPATIBLE") docker_compatible="$value" ;; "DOCKER_COMPATIBLE") local docker_compatible="$value" ;;
"GPU_PASSTHROUGH") gpu_passthrough="$value" ;; "GPU_PASSTHROUGH") local gpu_passthrough="$value" ;;
"SYSTEMD_NSPAWN_USER_ARGS") local systemd_nspawn_user_args="$value" ;;
"SYSTEMD_RUN_DEFAULT_ARGS") local systemd_run_default_args="$value" ;;
"SYSTEMD_NSPAWN_DEFAULT_ARGS") local systemd_nspawn_default_args="$value" ;;
esac esac
done <"${jail_config_path}" done <"${jail_config_path}"
echo 'Config loaded' echo 'Config loaded'
SYSTEMD_RUN_ADDITIONAL_ARGS=() local systemd_run_additional_args=("--unit='jlmkr-${jail_name}'" "--working-directory='./${jail_path}'" "--description='jailmaker ${jail_name}'")
SYSTEMD_NSPAWN_ADDITIONAL_ARGS=() local systemd_nspawn_additional_args=("--machine='${jail_name}'" "--directory='${JAIL_ROOTFS_NAME}'")
if [[ "${docker_compatible}" -eq 1 ]]; then if [[ "${docker_compatible}" -eq 1 ]]; then
# Enable ip forwarding on the host (docker needs it) # Enable ip forwarding on the host (docker needs it)
@ -69,36 +73,37 @@ start_jail() {
# To properly run docker inside the jail, we need to lift restrictions # To properly run docker inside the jail, we need to lift restrictions
# Without DevicePolicy=auto images with device nodes may not be pulled # Without DevicePolicy=auto images with device nodes may not be pulled
# https://github.com/kinvolk/kube-spawn/pull/328 # https://github.com/kinvolk/kube-spawn/pull/328
SYSTEMD_RUN_ADDITIONAL_ARGS+=(--setenv=SYSTEMD_SECCOMP=0 --property=DevicePolicy=auto) systemd_run_additional_args+=(--setenv=SYSTEMD_SECCOMP=0 --property=DevicePolicy=auto)
# Add additional flags required for docker # Add additional flags required for docker
SYSTEMD_NSPAWN_ADDITIONAL_ARGS+=(--capability=all --system-call-filter='add_key keyctl bpf') systemd_nspawn_additional_args+=(--capability=all "--system-call-filter='add_key keyctl bpf'")
fi fi
if [[ "${gpu_passthrough}" -eq 1 ]]; then if [[ "${gpu_passthrough}" -eq 1 ]]; then
SYSTEMD_NSPAWN_ADDITIONAL_ARGS+=(--property=DeviceAllow='char-drm rw') systemd_nspawn_additional_args+=("--property=DeviceAllow='char-drm rw'")
# Detect intel GPU device and if present add bind flag # Detect intel GPU device and if present add bind flag
[[ -d /dev/dri ]] && SYSTEMD_NSPAWN_ADDITIONAL_ARGS+=(--bind=/dev/dri) [[ -d /dev/dri ]] && systemd_nspawn_additional_args+=(--bind=/dev/dri)
# TODO: add bind mount flags in case of nvidia GPU passthrough # TODO: add bind mount flags in case of nvidia GPU passthrough
fi fi
# Pass the two arrays to the start script local cmd=(systemd-run "${systemd_run_default_args}" "${systemd_run_additional_args[*]}" --
# https://stackoverflow.com/a/43687593 systemd-nspawn "${systemd_nspawn_default_args}" "${systemd_nspawn_additional_args[*]} ${systemd_nspawn_user_args}")
echo "Starting jail..."
"./${jail_start_script_path}" \ echo "Starting jail with command:"
"${#SYSTEMD_RUN_ADDITIONAL_ARGS[@]}" "${SYSTEMD_RUN_ADDITIONAL_ARGS[@]}" \ echo "${cmd[*]}"
"${#SYSTEMD_NSPAWN_ADDITIONAL_ARGS[@]}" "${SYSTEMD_NSPAWN_ADDITIONAL_ARGS[@]}"
eval "${cmd[*]}"
} }
############################ ######################
# START CREATE FUNCTIONALITY # CREATE FUNCTIONALITY
############################ #######################
cleanup() { cleanup() {
# Remove the JAIL_PATH (if set, a directory and not the root directory) # Remove the jail_path if it's a directory
[[ -d "${JAIL_PATH}" ]] && echo -e "\n\nCleaning up: ${JAIL_PATH}\n" && rm -rf "${JAIL_PATH}" local jail_path="${1}"
[[ -d "${jail_path}" ]] && echo -e "\n\nCleaning up: ${jail_path}\n" && rm -rf "${jail_path}"
} }
stat_chmod() { stat_chmod() {
@ -110,87 +115,14 @@ validate_download_script() {
echo "6cca2eda73c7358c232fecb4e750b3bf0afa9636efb5de6a9517b7df78be12a4 ${1}" | sha256sum --check >/dev/null echo "6cca2eda73c7358c232fecb4e750b3bf0afa9636efb5de6a9517b7df78be12a4 ${1}" | sha256sum --check >/dev/null
} }
# Use a function as template instead of heredoc
# This allows syntax highlighting and linting to work
# Template function must return the line number,
# this marks the start of the template
template_start_script() {
echo "$((LINENO + 1))" && return
# TEMPLATE START
#!/bin/bash
set -euo pipefail
# If this script is called from PLACEHOLDER_SCRIPT_NAME
# and DOCKER_COMPATIBLE or GPU_PASSTHROUGH is enabled
# then these arrays will be filled with additional args
SYSTEMD_RUN_ADDITIONAL_ARGS=("${@:2:$1}") && shift "$(($1 + 1))"
SYSTEMD_NSPAWN_ADDITIONAL_ARGS=("${@:2:$1}") && shift "$(($1 + 1))"
# Move into the directory where this script is stored (commands are relative to this directory)
cd "$(dirname "${BASH_SOURCE[0]}")" || exit
# Get the name of the jail from the directory name
JAIL_NAME="$(basename "$(pwd)")"
# Use mostly default settings for systemd-nspawn but with systemd-run instead of a service file
# https://github.com/systemd/systemd/blob/main/units/systemd-nspawn%40.service.in
# TODO: also compare settings for docker: https://github.com/docker/engine/blob/master/contrib/init/systemd/docker.service
systemd-run --property=KillMode=mixed --property=Type=notify --property=RestartForceExitStatus=133 \
--property=SuccessExitStatus=133 --property=Delegate=yes --property=TasksMax=16384 --same-dir \
--collect --setenv=SYSTEMD_NSPAWN_LOCK=0 \
--unit="jlmkr-${JAIL_NAME}" --description="jailmaker ${JAIL_NAME}" \
"${SYSTEMD_RUN_ADDITIONAL_ARGS[@]}" \
-- \
systemd-nspawn --keep-unit --quiet --boot \
--machine="${JAIL_NAME}" --directory='./PLACEHOLDER_ROOTFS_NAME' \
PLACEHOLDER_SYSTEMD_NSPAWN_USER_ARGS \
"${SYSTEMD_NSPAWN_ADDITIONAL_ARGS[@]}"
# TEMPLATE END
}
# Helper function to process the body of a function into a bash string
# which can be written as a new bash script
# Includes find-replace functionality to substitute placeholders in the template
process_template() {
local indent=""
# Read the current script file, starting from the passed line number
while IFS=$'\n' read -r line; do
# Get the indent level from the first line
[[ -z "${indent}" ]] && indent="${line%%#*}" && continue
# Break when we find the end of the template
[[ "$line" = "${indent}# TEMPLATE END" ]] && break
# Remove the indent from the start
line="${line#"$indent"}"
local find replace
# Loop over the additional argument pairs passed
# and find/replace template key with value
for ((n = 2; n <= $#; n++)); do
# https://stackoverflow.com/a/3575950
# Indirect expansion: look up the value of the variable whose name is in the variable
# So this looks up the nth argument passed to this function
find=${!n}
# Increment counter by one to get next argument
((n++))
replace=${!n}
line=${line//$find/$replace}
done
echo "${line}"
done < <(tail -n "+${1}" "${ABSOLUTE_SCRIPT_PATH}")
}
create_jail() { create_jail() {
# TODO: show disclaimer # TODO: show disclaimer
local reply arch distro release lxc_dir_path lxc_cache_path lxc_download_script_path
local jail_config_path jail_start_script_path docker_compatible gpu_passthrough
local jail_name systemd_nspawn_user_args
local arch
arch="$(dpkg --print-architecture)" arch="$(dpkg --print-architecture)"
lxc_dir_path='.lxc' local lxc_dir_path='.lxc'
lxc_cache_path="${lxc_dir_path}/cache" local lxc_cache_path="${lxc_dir_path}/cache"
lxc_download_script_path="${lxc_dir_path}/lxc-download.sh" local lxc_download_script_path="${lxc_dir_path}/lxc-download.sh"
[[ "$(basename "${SCRIPT_DIR_PATH}")" != 'jailmaker' ]] && { [[ "$(basename "${SCRIPT_DIR_PATH}")" != 'jailmaker' ]] && {
echo "${SCRIPT_NAME} needs to create files." echo "${SCRIPT_NAME} needs to create files."
@ -199,6 +131,8 @@ create_jail() {
fail "Please create a dedicated directory called 'jailmaker', store ${SCRIPT_NAME} there and try again." fail "Please create a dedicated directory called 'jailmaker', store ${SCRIPT_NAME} there and try again."
} }
local reply
if [[ $(findmnt --target . --output TARGET --noheadings --first-only) != /mnt/* ]]; then if [[ $(findmnt --target . --output TARGET --noheadings --first-only) != /mnt/* ]]; then
echo "${SCRIPT_NAME} should be on a pool mounted under /mnt (it currently isn't)." echo "${SCRIPT_NAME} should be on a pool mounted under /mnt (it currently isn't)."
echo "Storing it on the boot-pool means losing all jails when updating TrueNAS." echo "Storing it on the boot-pool means losing all jails when updating TrueNAS."
@ -234,6 +168,8 @@ create_jail() {
stat_chmod 700 "${lxc_download_script_path}" stat_chmod 700 "${lxc_download_script_path}"
local distro release
read -p "Install the recommended distro (Debian 11)? [Y/n] " -n 1 -r reply && echo read -p "Install the recommended distro (Debian 11)? [Y/n] " -n 1 -r reply && echo
if [[ "${reply}" =~ ^([Yy]|)$ ]]; then if [[ "${reply}" =~ ^([Yy]|)$ ]]; then
distro='debian' distro='debian'
@ -254,6 +190,8 @@ create_jail() {
read -e -r -p "Release: " release && echo read -e -r -p "Release: " release && echo
fi fi
local jail_name jail_path
while true; do while true; do
read -e -r -p "Enter jail name: " jail_name && echo read -e -r -p "Enter jail name: " jail_name && echo
if ! [[ "${jail_name}" =~ ^[.a-zA-Z0-9-]{1,64}$ && "${jail_name}" != '.'* && "${jail_name}" != *'.' && "${jail_name}" != *'..'* ]]; then if ! [[ "${jail_name}" =~ ^[.a-zA-Z0-9-]{1,64}$ && "${jail_name}" != '.'* && "${jail_name}" != *'.' && "${jail_name}" != *'..'* ]]; then
@ -266,9 +204,9 @@ create_jail() {
EOF EOF
else else
JAIL_PATH="${JAILS_DIR_PATH}/${jail_name}" jail_path="${JAILS_DIR_PATH}/${jail_name}"
if [[ -e "${JAIL_PATH}" ]]; then if [[ -e "${jail_path}" ]]; then
echo "A jail with this name already exists." echo "A jail with this name already exists."
echo echo
else else
@ -278,9 +216,11 @@ create_jail() {
fi fi
done done
# Cleanup on exit, but only once the JAIL_PATH is final # Cleanup on exit, but only once the jail_path is final
# Otherwise we may cleanup the wrong directory # Otherwise we may cleanup the wrong directory
trap cleanup EXIT trap 'cleanup "${jail_path}"' EXIT
local docker_compatible gpu_passthrough systemd_nspawn_user_args
echo "${SCRIPT_NAME} will not install docker for you." echo "${SCRIPT_NAME} will not install docker for you."
echo "But it can configure the jail with the capabilities required to run docker." echo "But it can configure the jail with the capabilities required to run docker."
@ -294,7 +234,7 @@ create_jail() {
# Enter accepts default (no) # Enter accepts default (no)
if ! [[ "${reply}" =~ ^[Yy]$ ]]; then gpu_passthrough=0; else gpu_passthrough=1; fi if ! [[ "${reply}" =~ ^[Yy]$ ]]; then gpu_passthrough=0; else gpu_passthrough=1; fi
# TODO: ask for network setup (host, macvlan, bridge, physical nic) # TODO: ask to show nspawn manual
echo echo
echo "You may pass additional systemd-nspawn flags." echo "You may pass additional systemd-nspawn flags."
echo "For example to mount directories inside the jail you may add:" echo "For example to mount directories inside the jail you may add:"
@ -313,17 +253,17 @@ create_jail() {
# --bind-ro='/mnt/data/weird chars \:?\\"' # --bind-ro='/mnt/data/weird chars \:?\\"'
# Create directory for rootfs # Create directory for rootfs
JAIL_ROOTFS_PATH="${JAIL_PATH}/${JAIL_ROOTFS_NAME}" JAIL_ROOTFS_PATH="${jail_path}/${JAIL_ROOTFS_NAME}"
mkdir -p "${JAIL_ROOTFS_PATH}" mkdir -p "${JAIL_ROOTFS_PATH}"
jail_config_path="${JAIL_PATH}/${JAIL_CONFIG_NAME}" local jail_config_path="${jail_path}/${JAIL_CONFIG_NAME}"
# LXC download script needs to write to this file during install # LXC download script needs to write to this file during install
# but we don't need it so we will remove it later # but we don't need it so we will remove it later
touch "${jail_config_path}" touch "${jail_config_path}"
echo echo
LXC_CACHE_PATH=${lxc_cache_path} "${lxc_download_script_path}" \ LXC_CACHE_PATH=${lxc_cache_path} "${lxc_download_script_path}" \
--name="${jail_name}" --path="${JAIL_PATH}" --rootfs="${JAIL_ROOTFS_PATH}" \ --name="${jail_name}" --path="${jail_path}" --rootfs="${JAIL_ROOTFS_PATH}" \
--arch="${arch}" --dist="${distro}" --release="${release}" || --arch="${arch}" --dist="${distro}" --release="${release}" ||
fail "Aborted creating rootfs..." fail "Aborted creating rootfs..."
echo echo
@ -355,21 +295,28 @@ create_jail() {
# https://github.com/systemd/systemd/issues/852 # https://github.com/systemd/systemd/issues/852
printf 'pts/%d\n' $(seq 0 10) >"${JAIL_ROOTFS_PATH}/etc/securetty" printf 'pts/%d\n' $(seq 0 10) >"${JAIL_ROOTFS_PATH}/etc/securetty"
jail_start_script_path="${JAIL_PATH}/${JAIL_START_SCRIPT_NAME}" # Use mostly default settings for systemd-nspawn but with systemd-run instead of a service file
# https://github.com/systemd/systemd/blob/main/units/systemd-nspawn%40.service.in
# TODO: also compare settings for docker: https://github.com/docker/engine/blob/master/contrib/init/systemd/docker.service
cat <<-EOF >"${jail_config_path}" local systemd_run_default_args=(--property=KillMode=mixed --property=Type=notify --property=RestartForceExitStatus=133
DOCKER_COMPATIBLE=${docker_compatible} --property=SuccessExitStatus=133 --property=Delegate=yes --property=TasksMax=16384 --collect
GPU_PASSTHROUGH=${gpu_passthrough} --setenv=SYSTEMD_NSPAWN_LOCK=0)
EOF
local systemd_nspawn_default_args=(--keep-unit --quiet --boot)
{
echo "DOCKER_COMPATIBLE=${docker_compatible}"
echo "GPU_PASSTHROUGH=${gpu_passthrough}"
echo "SYSTEMD_NSPAWN_USER_ARGS=${systemd_nspawn_user_args}"
echo
echo "# You generally won't need to change the options below"
echo "SYSTEMD_RUN_DEFAULT_ARGS=${systemd_run_default_args[*]}"
echo "SYSTEMD_NSPAWN_DEFAULT_ARGS=${systemd_nspawn_default_args[*]}"
} >"${jail_config_path}"
chmod 700 "${jail_config_path}" chmod 700 "${jail_config_path}"
process_template "$(template_start_script)" \
PLACEHOLDER_ROOTFS_NAME "${JAIL_ROOTFS_NAME}" PLACEHOLDER_SYSTEMD_NSPAWN_USER_ARGS "${systemd_nspawn_user_args}" \
PLACEHOLDER_SCRIPT_NAME "${SCRIPT_NAME}" >"${jail_start_script_path}"
chmod 700 "${jail_start_script_path}"
# Remove the cleanup trap on exit # Remove the cleanup trap on exit
trap - EXIT trap - EXIT
echo "Done creating the jail." echo "Done creating the jail."
@ -377,15 +324,15 @@ create_jail() {
read -p "Start the jail now? [Y/n] " -n 1 -r reply && echo read -p "Start the jail now? [Y/n] " -n 1 -r reply && echo
# Enter accepts default (yes) # Enter accepts default (yes)
if [[ "${reply}" =~ ^([Yy]|)$ ]]; then if [[ "${reply}" =~ ^([Yy]|)$ ]]; then
start_jail "${JAIL_PATH}" start_jail "${jail_name}"
else else
echo 'Skipped starting jail.' echo 'Skipped starting jail.'
fi fi
} }
########################## #######################
# END CREATE FUNCTIONALITY # COMMAND LINE HANDLING
########################## #######################
# TODO document # TODO document
# machinectl shell # machinectl shell
@ -399,6 +346,7 @@ create_jail() {
# machinectl login # machinectl login
# TODO: recommend ssh ;) # TODO: recommend ssh ;)
# TODO: create a jlmkr shell command to try the above in case machinectl shell doesn't work # TODO: create a jlmkr shell command to try the above in case machinectl shell doesn't work
# TODO: document journalctl -u jlmkr-jailname
case "${1-""}" in case "${1-""}" in
@ -418,7 +366,7 @@ create)
;; ;;
start) start)
start_jail "${JAILS_DIR_PATH}/${2}" start_jail "${2}"
;; ;;
*) *)