From 8c49f178dadab09a809de430134837f4d5624102 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 29 Nov 2025 16:10:32 +0100 Subject: [PATCH 01/25] Rework module setup Common functionality from post modules got their own module reducing code duplication. The code is now also reorganized as we differentiate between functions called exclusively by tik itself and functions called by its modules. --- usr/bin/tik | 15 +- usr/lib/tik/lib/tik-core | 366 ++++++++++++++++++ usr/lib/tik/lib/tik-functions | 411 ++++----------------- usr/lib/tik/modules/post/00-setup | 58 +++ usr/lib/tik/modules/post/10-sicu | 101 +---- usr/lib/tik/modules/post/15-encrypt | 226 ++++------- usr/lib/tik/modules/post/20-mig | 17 +- usr/lib/tik/modules/post/99-cleanup | 25 ++ usr/lib/tik/modules/pre/05-setup-gnome-env | 27 ++ usr/lib/tik/modules/pre/10-welcome | 62 ++-- usr/lib/tik/modules/pre/20-mig | 139 +++---- 11 files changed, 734 insertions(+), 713 deletions(-) create mode 100644 usr/lib/tik/lib/tik-core create mode 100644 usr/lib/tik/modules/post/00-setup create mode 100644 usr/lib/tik/modules/post/99-cleanup create mode 100644 usr/lib/tik/modules/pre/05-setup-gnome-env diff --git a/usr/bin/tik b/usr/bin/tik index 7ddfb13..f008a07 100755 --- a/usr/bin/tik +++ b/usr/bin/tik @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: Copyright 2023-2024 SUSE LLC # SPDX-FileCopyrightText: Copyright 2023-2024 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens # Define variables # Style notes @@ -12,8 +13,10 @@ tik_log=~/tik.log tik_dir=/usr/lib/tik tik_module="tik" -# Read libraries +# Common helpers used by tik and modules . ${tik_dir}/lib/tik-functions +# Core installer logic used only by tik and phase scripts +. ${tik_dir}/lib/tik-core # Start logging exec 2> >(exec tee -i -a "${tik_log}" >&2) @@ -27,7 +30,7 @@ fi # Check if graphical display is available XDG_SESSION="${XDG_SESSION_TYPE:=unspecified}" -if [ $XDG_SESSION_TYPE = "wayland" ] || [ $XDG_SESSION_TYPE = "x11" ] ; then +if [ "${XDG_SESSION_TYPE}" = "wayland" ] || [ "${XDG_SESSION_TYPE}" = "x11" ] ; then gui=true else gui=false @@ -58,16 +61,18 @@ cleanup() { } trap cleanup EXIT +# Run pre modules +tik_init_phase_modules "pre" load_modules "pre" load_modules "pre" "custom" +# Select installation disk and image, then deploy image to disk get_disk get_img dump_image "${TIK_INSTALL_IMAGE}" "${TIK_INSTALL_DEVICE}" reread_partitiontable +# Run post modules +tik_init_phase_modules "post" load_modules "post" load_modules "post" "custom" - -wipe_keyfile -set_boot_target diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core new file mode 100644 index 0000000..bda0363 --- /dev/null +++ b/usr/lib/tik/lib/tik-core @@ -0,0 +1,366 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens + +get_disk() { + tik_volid="TIKINSTALL" + local disk_id="by-id" + local disk_size + local disk_device + local disk_device_by_id + local disk_meta + local disk_list + local device_array + local list_items + local blk_opts="-p -n -r -o NAME,SIZE,TYPE" + local message + local blk_opts_plus_label="${blk_opts},LABEL" + local tik_install_disk_part + local part_meta + local part_count + local part_size + local part_info + local part_fs + local blk_opts_part_info="${blk_opts_plus_label},FSTYPE" + local usb_match_1="usb" + local usb_match_2=":0" + + tik_install_disk_part=$( + eval lsblk "${blk_opts_plus_label}" | \ + tr -s ' ' ":" | \ + grep ":${tik_volid}" | \ + cut -f1 -d: + ) + + for disk_meta in $( + eval lsblk "${blk_opts}" | grep -E "disk|raid" | tr ' ' ":" + ); do + disk_size=$(echo "${disk_meta}" | cut -f2 -d:) + if [[ "${disk_size}" == "0B" ]]; then + continue + fi + disk_device="$(echo "${disk_meta}" | cut -f1 -d:)" + part_count=0 + part_info="" + for part_meta in $( + eval lsblk "${blk_opts_part_info}" | grep -E "${disk_device}.+part.+" | tr ' ' ":" + ); do + part_count=$(expr $part_count + 1) + part_size=$(echo "${part_meta}" | cut -f2 -d:) + part_fs=$(echo "${part_meta}" | cut -f5 -d:) + if [ -n "${part_info}" ]; then + part_info="${part_info}," + fi + if [ -n "${part_fs}" ]; then + part_info="${part_info}${part_fs}(${part_size})" + else + part_info="${part_info}unknown(${part_size})" + fi + done + if [[ ${part_count} -eq 0 ]]; then + part_info="none" + fi + if [[ "${tik_install_disk_part}" == "${disk_device}"* ]]; then + continue + fi + if [[ ${disk_device} =~ ^/dev/fd ]]; then + continue + fi + if [[ ${disk_device} =~ ^/dev/zram ]]; then + continue + fi + disk_device_by_id=$( + get_persistent_device_from_unix_node "${disk_device}" "${disk_id}" + ) + if [[ ( "${TIK_ALLOW_USB_INSTALL_DEVICES}" -ne 1 ) && ( "${disk_device_by_id}" == *"${usb_match_1}"* || "${disk_device_by_id}" == *"${usb_match_2}"* ) ]]; then + continue + fi + if [ -n "${disk_device_by_id}" ]; then + disk_device=${disk_device_by_id} + fi + list_items="${list_items} $(basename ${disk_device}) ${disk_size} ${part_count} ${part_info}" + disk_list="${disk_list} $(basename ${disk_device}) ${disk_size}" + done + + if [ -n "${TIK_INSTALL_DEVICE}" ]; then + local device=${TIK_INSTALL_DEVICE} + local device_meta + local device_size + if [ ! -e "${device}" ]; then + local no_dev="Given device ${device} does not exist." + error "${no_dev}" + fi + if [ ! -b "${device}" ]; then + local no_block_dev="Given device ${device} is not a block special." + error "${no_block_dev}" + fi + device_meta=$( + eval lsblk "${blk_opts}" "${device}" |\ + grep -E "disk|raid" | tr ' ' ":" + ) + device_size=$(echo "${device_meta}" | cut -f2 -d:) + list_items="$(basename ${device}) ${device_size}" + disk_list="$(basename ${device}) ${device_size}" + message="tik installation device set to to: ${device}" + log "${message}" + fi + + if [ -z "${list_items}" ]; then + local no_device_text="No device(s) for installation found." + error "${no_device_text}" + fi + + if [ -n "${disk_list}" ]; then + local count=0 + local device_index=0 + for entry in ${disk_list}; do + if [ $((count % 2)) -eq 0 ]; then + device_array[${device_index}]=${entry} + device_index=$((device_index + 1)) + fi + count=$((count + 1)) + done + if [ "${device_index}" -eq 1 ]; then + TIK_INSTALL_DEVICE="/dev/disk/${disk_id}/${device_array[0]}" + if [ ! -e "${TIK_INSTALL_DEVICE}" ]; then + TIK_INSTALL_DEVICE="/dev/${device_array[0]}" + fi + else + d --list --column=Disk --column=Size --column=Partitions --column=Filesystems --width=1050 --height=340 --title="Select A Disk" --text="Select the disk to install the operating system to. Make sure any important documents and files have been backed up.\n" ${list_items} + TIK_INSTALL_DEVICE="/dev/disk/${disk_id}/${result}" + if [ ! -e "${TIK_INSTALL_DEVICE}" ]; then + TIK_INSTALL_DEVICE="/dev/${result}" + fi + fi + fi +} + +get_img() { + local list_items + local message + local img_meta + local img_list + local img_array + local file_type + + for file_type in '*.raw.xz' '*.raw'; do + for img_meta in $(cd "${TIK_IMG_DIR}" && (stat --printf="%n\t%s\n" ${file_type} 2>/dev/null | tr ' ' ":")); do + img_filename="$(echo "${img_meta}" | cut -f1 -d:)" + img_size="$(echo "${img_meta}" | cut -f2 -d:)" + list_items="${list_items} ${img_filename} ${img_size}" + done + done + + if [ -n "${TIK_INSTALL_IMAGE}" ]; then + local img=${TIK_INSTALL_IMAGE} + local img_size + if [ ! -e "${img}" ]; then + local no_img="Given image ${img} does not exist." + error "${no_img}" + fi + if [ ! -s "${img}" ]; then + local empty_img="Given image ${img} is empty." + error "${empty_img}" + fi + img_meta=$( + eval cd "${TIK_IMG_DIR}" && (stat --printf="%n\t%s\n" "${img}" | tr ' ' ":") + ) + img_filename="$(echo "${img_meta}" | cut -f1 -d:)" + img_size="$(echo "${img_meta}" | cut -f2 -d:)" + list_items="${list_items} ${img_filename} ${img_size}" + message="tik installation image set to to: ${img}" + log "${message}" + fi + + if [ -z "${list_items}" ]; then + TIK_INSTALL_IMAGE='TIK_SELFDEPLOY' + fi + + img_list=${list_items} + if [ -n "${img_list}" ]; then + local count=0 + local img_index=0 + for entry in ${img_list}; do + if [ $((count % 2)) -eq 0 ]; then + img_array[${img_index}]=${entry} + img_index=$((img_index + 1)) + fi + count=$((count + 1)) + done + if [ "${img_index}" -eq 1 ]; then + TIK_INSTALL_IMAGE="${img_array[0]}" + else + d --list --column=Image --column=Size --title="Select A Image" --text="Select the operating system image to install.\n" ${list_items} + TIK_INSTALL_IMAGE="${result}" + fi + fi +} + +reread_partitiontable() { + log "[reread_partitiontable] Re-reading partition table" + sleep 3 + prun /usr/sbin/blockdev --rereadpt "${TIK_INSTALL_DEVICE}" + sleep 3 +} + +create_keyfile() { + tik_keyfile=$(prun mktemp /tmp/tik.XXXXXXXXXX) + log "[create_keyfile] Creating keyfile ${tik_keyfile}" + /usr/bin/base64 -w 0 /dev/urandom | head -c 1k | prun tee "${tik_keyfile}" + prun /usr/bin/chmod 400 "${tik_keyfile}" + tik_keyid=$(prun cat "${tik_keyfile}" | prun keyctl padd user cryptenroll @u) +} + +wipe_keyfile() { + log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}" + probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" + if [ -n "${probedpart}" ]; then + prun /usr/bin/systemd-cryptenroll --unlock-key-file="${tik_keyfile}" --wipe-slot=0 "${probedpart}" + fi + prun /usr/bin/rm "${tik_keyfile}" + prun-opt keyctl revoke "${tik_keyid}" + prun-opt keyctl reap +} + +dump_image() { + local image_source_files=$1 + local image_target=$2 + + d --question --no-wrap --title="Begin Installation?" --text="Once the installation begins the changes to the selected disk are irreversible.\n\nProceeding will fully erase the disk.\n\nContinue with installation?" + + case "${image_source_files}" in + *.raw.xz) + dump_image_dd "${image_source_files}" "${image_target}" + ;; + *.raw) + dump_image_repart_image "${image_source_files}" "${image_target}" + ;; + TIK_SELFDEPLOY) + dump_image_repart_self "${image_target}" + ;; + *) + error "invalid image type provided" + esac +} + +dump_image_dd() { + local image_source_files=$1 + local image_target=$2 + log "[dump_image_dd] deploying ${TIK_IMG_DIR}/${image_source_files}" + (xzcat "${TIK_IMG_DIR}/${image_source_files}" | pv -f -F "# %b copied in %t %r" | prun /usr/bin/dd of="${image_target}" bs=64k) 2>&1 | d --progress --title="Installing ${TIK_OS_NAME}" --pulsate --auto-close --no-cancel --width=400 + prun /usr/bin/sync | d --progress --title="Syncing" --pulsate --auto-close --no-cancel --width=400 +} + +dump_image_repart_image() { + local image_source_files=$1 + local image_target=$2 + local success=0 + local max_attempts=5 + local attempt_num=1 + + create_keyfile + log "[dump_image_repart_image] deploying ${TIK_IMG_DIR}/${image_source_files}" + + while [ ${success} = 0 ] && [ ${attempt_num} -lt ${max_attempts} ]; do + prun-opt systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --image="${TIK_IMG_DIR}/${image_source_files}" --image-policy=root=unprotected "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) + if [ ${retval} -eq 0 ]; then + success=1 + else + log "[dump_image_repart_image] systemd-repart attempt ${attempt_num} failed. Trying again..." + sleep 1 + attempt_num=$(( attempt_num + 1 )) + fi + done + if [ ${success} = 1 ]; then + log "[dump_image_repart_image] systemd-repart succeeded after ${attempt_num} attempts" + else + error "systemd-repart failed" + fi +} + +dump_image_repart_self() { + local image_target=$1 + create_keyfile + log "[dump_image_repart_self] self-deploying" + prun systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --generate-fstab=/etc/fstab.repart "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) +} + +set_boot_target() { + local efipartnum + if [ "${debug}" == "1" ]; then + log "[debug] Not setting EFI boot target" + elif [ -n "${efi_already_set}" ]; then + log "[set_boot_target] boot target already set, not setting again" + else + prun-opt /usr/sbin/efibootmgr -B -L "openSUSE Boot Manager" + prun-opt /usr/sbin/efibootmgr -B -L "${TIK_OS_NAME} Boot Manager" + prun /usr/sbin/efibootmgr -O + log "[set_boot_target] searching for ESP partition containing /EFI/systemd/shim.efi on ${TIK_INSTALL_DEVICE}" + probe_partitions "${TIK_INSTALL_DEVICE}" "vfat" "/EFI/systemd/shim.efi" + if [ -z "${probedpart}" ]; then + error "esp partition not found" + fi + efipartnum=$(lsblk "${probedpart}" -p -n -r -o PARTN) + log "[set_boot_target] found ESP on ${probedpart}, partition number ${efipartnum}" + prun /usr/sbin/efibootmgr -c -L "${TIK_OS_NAME} Boot Manager" -d "${TIK_INSTALL_DEVICE}" -l "\EFI\systemd\shim.efi" -p ${efipartnum} + log "[set_boot_target] $(prun /usr/sbin/efibootmgr)" + efi_already_set=1 + fi +} + +tik_init_phase_modules() { + local phase=$1 + local dir + TIK_CURRENT_PHASE="${phase}" + TIK_TOTAL_MODULES=0 + TIK_CURRENT_MODULE_INDEX=0 + + for dir in "${tik_dir}/modules/${phase}" "${TIK_CUSTOM_DIR}/modules/${phase}"; do + if [ -d "${dir}" ]; then + for f in "${dir}"/*; do + [ -f "${f}" ] || continue + TIK_TOTAL_MODULES=$((TIK_TOTAL_MODULES + 1)) + done + fi + done + + case $phase in + "pre") TIK_PROGRESS_TITLE="Preparing Installation" ;; + "post") TIK_PROGRESS_TITLE="Finishing Installation" ;; + *) TIK_PROGRESS_TITLE="Installation" ;; + esac + + export TIK_PROGRESS_TITLE + + log "[tik_init_phase_modules] phase=${phase} total_modules=${TIK_TOTAL_MODULES}" +} + +load_modules() { + local phase=$1 + local module_dir + + if [[ $2 = "custom" ]]; then + module_dir=$TIK_CUSTOM_DIR/modules/$phase + else + module_dir=$tik_dir/modules/$phase + fi + + if [ -n "$(ls -A "$module_dir" 2>/dev/null)" ]; then + for f in "$module_dir"/*; do + [ -f "$f" ] || continue + TIK_CURRENT_MODULE_INDEX=$((TIK_CURRENT_MODULE_INDEX + 1)) + tik_module="$f" + log "[START] $module_dir/$f (phase=${phase} module_index=${TIK_CURRENT_MODULE_INDEX}/${TIK_TOTAL_MODULES})" + . "$f" + log "[STOP] $module_dir/$f" + + # If this was the last module of the phase, close the phase's progress UI + if [ -n "${TIK_TOTAL_MODULES}" ] && [ "${TIK_TOTAL_MODULES}" -gt 0 ] && \ + [ "${TIK_CURRENT_MODULE_INDEX}" -eq "${TIK_TOTAL_MODULES}" ]; then + log "[load_modules] last module of phase '${phase}' completed, closing progress" + tik_close_progress + fi + done + fi + tik_module="tik" +} diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index c243342..fc51cdf 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -1,10 +1,11 @@ # SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: Copyright 2023-2024 SUSE LLC -# SPDX-FileCopyrightText: Copyright 2023-2024 Richard Brown +# SPDX-FileCopyrightText: Copyright 2023-2025 SUSE LLC +# SPDX-FileCopyrightText: Copyright 2023-2025 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens . /usr/lib/tik/lib/cenity -log(){ +log() { if $logging; then echo "[${tik_module}][$(date +"%Y%m%d-%T")][LOG] $*" 1>&2 fi @@ -21,7 +22,7 @@ error() { exit 1 } -d(){ +d() { while true do retval=0 @@ -47,7 +48,7 @@ d(){ done } -d_opt(){ +d_opt() { retval=0 if $gui; then result="$(zenity "$@")" || retval=$? @@ -86,7 +87,7 @@ get_persistent_device_from_unix_node() { local persistent_name node=$(basename "${unix_device}") for persistent_name in /dev/disk/"${schema}"/*; do - if [ "$(basename "$(readlink "${persistent_name}")")" = "${node}" ];then + if [ "$(basename "$(readlink "${persistent_name}")")" = "${node}" ]; then if [[ ${persistent_name} =~ ^/dev/disk/"${schema}"/nvme-eui ]]; then # Filter out nvme-eui nodes as they are not descriptive to the user continue @@ -106,13 +107,16 @@ probe_partitions() { local device=$1 local mountops local part + if [[ "${filesystem_type}" == "btrfs" ]]; then mountops="-o compress=zstd:1" fi + prun /usr/bin/mkdir -p ${probe_dir}/mnt probedpart="" - for part in $(lsblk ${device} -p -n -r -o ID-LINK,FSTYPE|tr -s ' ' ";"|grep ";${filesystem_type}"|cut -d\; -f1); do - if [ -z ${filematch} ]; then + + for part in $(lsblk ${device} -p -n -r -o ID-LINK,FSTYPE | tr -s ' ' ";" | grep ";${filesystem_type}" | cut -d\; -f1); do + if [ -z "${filematch}" ]; then log "[probe_partitions] no file match required" # Fallback to unix device in order to fix issue with USB devices probedpart="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" @@ -123,7 +127,7 @@ probe_partitions() { prun /usr/bin/mount ${mountops} ${part} "${probe_dir}/mnt" if [ -f ${probe_dir}/mnt/${filematch} ]; then log "[probe_partitions] File ${filematch} found" - # Fallback to unix device in order to fix issue with USB devices + # Fallback to unix device in order to fix issue with USB devices probedpart="${part}" log "[probe_partitions] Partition ${probedpart} found" if grep -q 'PRETTY_NAME="openSUSE MicroOS"' ${probe_dir}/mnt/${filematch} && [ -f ${probe_dir}/mnt/usr/bin/gnome-shell ]; then @@ -138,359 +142,80 @@ probe_partitions() { prun /usr/bin/rmdir ${probe_dir}/mnt } -get_disk() { - # Volume label for the tik install media must be set to "TIKINSTALL" to filter it out from the device list - tik_volid="TIKINSTALL" - local disk_id="by-id" - local disk_size - local disk_device - local disk_device_by_id - local disk_meta - local disk_list - local device_array - local list_items - local blk_opts="-p -n -r -o NAME,SIZE,TYPE" - local message - local blk_opts_plus_label="${blk_opts},LABEL" - local tik_install_disk_part - local part_meta - local part_count - local part_size - local part_info - local part_fs - local blk_opts_part_info="${blk_opts_plus_label},FSTYPE" - local usb_match_1="usb" - local usb_match_2=":0" - - tik_install_disk_part=$( - eval lsblk "${blk_opts_plus_label}" | \ - tr -s ' ' ":" | \ - grep ":${tik_volid}" | \ - cut -f1 -d: - ) - - for disk_meta in $( - eval lsblk "${blk_opts}" | grep -E "disk|raid" | tr ' ' ":" - );do - disk_size=$(echo "${disk_meta}" | cut -f2 -d:) - if [[ "${disk_size}" == "0B" ]]; then - # ignore disks with no size, e.g. empty SD card readers - continue - fi - disk_device="$(echo "${disk_meta}" | cut -f1 -d:)" - # find partitions and info for this disk - part_count=0 - part_info="" - for part_meta in $( - eval lsblk "${blk_opts_part_info}" | grep -E "${disk_device}.+part.+" | tr ' ' ":" - );do - part_count=$(expr $part_count + 1) - part_size=$(echo "${part_meta}" | cut -f2 -d:) - part_fs=$(echo "${part_meta}" | cut -f5 -d:) - if [ -n "${part_info}" ]; then - part_info="${part_info}," - fi - if [ -n "${part_fs}" ]; then - part_info="${part_info}${part_fs}(${part_size})" - else - part_info="${part_info}unknown(${part_size})" - fi - done - if [[ ${part_count} -eq 0 ]]; then - part_info="none" - fi - if [[ "${tik_install_disk_part}" == "${disk_device}"* ]]; then - # ignore install source device - continue - fi - if [[ ${disk_device} =~ ^/dev/fd ]];then - # ignore floppy disk devices - continue - fi - if [[ ${disk_device} =~ ^/dev/zram ]];then - # ignore zram devices - continue - fi - disk_device_by_id=$( - get_persistent_device_from_unix_node "${disk_device}" "${disk_id}" - ) - if [[ ( "${TIK_ALLOW_USB_INSTALL_DEVICES}" -ne 1 ) && ( "{$disk_device_by_id}" == *"${usb_match_1}"* || "{$disk_device_by_id}" == *"${usb_match_2}"* ) ]]; then - # ignore USB devices if TIK_ALLOW_USB_INSTALL_DEVICES not set in config - continue - fi - if [ -n "${disk_device_by_id}" ];then - disk_device=${disk_device_by_id} - fi - list_items="${list_items} $(basename ${disk_device}) ${disk_size} ${part_count} ${part_info}" - disk_list="${disk_list} $(basename ${disk_device}) ${disk_size}" - done - if [ -n "${TIK_INSTALL_DEVICE}" ];then - # install device overwritten by config. - local device=${TIK_INSTALL_DEVICE} - local device_meta - local device_size - if [ ! -e "${device}" ];then - local no_dev="Given device ${device} does not exist." - error "${no_dev}" - fi - if [ ! -b "${device}" ];then - local no_block_dev="Given device ${device} is not a block special." - error "${no_block_dev}" - fi - device_meta=$( - eval lsblk "${blk_opts}" "${device}" |\ - grep -E "disk|raid" | tr ' ' ":" - ) - device_size=$(echo "${device_meta}" | cut -f2 -d:) - # this case is not shown in manual selection, threfore we don't need partition info - list_items="$(basename ${device}) ${device_size}" - disk_list="$(basename ${device}) ${device_size}" - message="tik installation device set to to: ${device}" - log "${message}" - fi - if [ -z "${list_items}" ];then - local no_device_text="No device(s) for installation found." - error "${no_device_text}" - fi - if [ -n "${disk_list}" ];then - local count=0 - local device_index=0 - for entry in ${disk_list};do - if [ $((count % 2)) -eq 0 ];then - device_array[${device_index}]=${entry} - device_index=$((device_index + 1)) - fi - count=$((count + 1)) - done - if [ "${device_index}" -eq 1 ];then - # one single disk device found, use it - # Add back full path to it - TIK_INSTALL_DEVICE="/dev/disk/${disk_id}/${device_array[0]}" - - # Fallback to unix device in case by-id does not exist - # see get_persistent_device_from_unix_node, it does fallback like this. - if [ ! -e "${TIK_INSTALL_DEVICE}" ]; then - TIK_INSTALL_DEVICE="/dev/${device_array[0]}" - fi - else - # manually select from storage list - d --list --column=Disk --column=Size --column=Partitions --column=Filesystems --width=1050 --height=340 --title="Select A Disk" --text="Select the disk to install the operating system to. Make sure any important documents and files have been backed up.\n" ${list_items} - # Add back full path to it - TIK_INSTALL_DEVICE="/dev/disk/${disk_id}/${result}" +mount_etc_for_root() { + local root=$1 - # Fallback to unix device in case by-id does not exist - # see get_persistent_device_from_unix_node, it does fallback like this. - if [ ! -e "${TIK_INSTALL_DEVICE}" ]; then - TIK_INSTALL_DEVICE="/dev/${result}" - fi - fi + if grep -qF 'overlay /etc' "${root}/etc/fstab" ; then + local etcmountcmd + etcmountcmd=$(grep "overlay /etc" "${root}/etc/fstab" \ + | sed "s#/sysroot/#${root}/#g" \ + | sed "s#/work-etc.*#/work-etc ${root}/etc#g" \ + | sed 's#overlay /etc overlay#/usr/bin/mount -t overlay overlay -o#') + eval prun "${etcmountcmd}" + else + prun /usr/bin/mount -o bind "${root}/etc" "${root}/etc" fi } -get_img() { - local list_items - local message - local img_meta - local img_item - local img_list - local img_array - local file_type - # Images are assumed to be named to the following standard - # $ProductName.$Version.raw.xz for block devices - # $ProductName.$Version.raw for systemd-repart images - # Any extraneous fields may confuse tik's detection, selection and presentation of the image to the user - for file_type in '*.raw.xz' '*.raw';do - for img_meta in $(cd $TIK_IMG_DIR && (stat --printf="%n\t%s\n" ${file_type} | tr ' ' ":"));do - img_filename="$(echo $img_meta | cut -f1 -d:)" - img_size="$(echo $img_meta | cut -f2 -d:)" - list_items="${list_items} ${img_filename} ${img_size}" - done - done - if [ -n "${TIK_INSTALL_IMAGE}" ];then - # install image overwritten by config. - local img=${TIK_INSTALL_IMAGE} - local img_meta - local img_size - if [ ! -e "${img}" ];then - local no_img="Given image ${img} does not exist." - error "${no_img}" - fi - if [ ! -s "${img}" ];then - local empty_img="Given image ${img} is empty." - error "${empty_img}" - fi - img_meta=$( - eval cd $TIK_IMG_DIR && (stat --printf="%n\t%s\n" $img | tr ' ' ":") - ) - img_filename="$(echo $img_meta | cut -f1 -d:)" - img_size="$(echo $img_meta | cut -f2 -d:)" - list_items="${list_items} ${img_filename} ${img_size}" - message="tik installation image set to to: ${img}" - log "${message}" - fi - if [ -z "${list_items}" ];then - TIK_INSTALL_IMAGE='TIK_SELFDEPLOY' - fi - img_list=${list_items} - if [ -n "${img_list}" ];then - local count=0 - local img_index=0 - for entry in ${img_list};do - if [ $((count % 2)) -eq 0 ];then - img_array[${img_index}]=${entry} - img_index=$((img_index + 1)) - fi - count=$((count + 1)) - done - if [ "${img_index}" -eq 1 ];then - # one single disk image found, use it - TIK_INSTALL_IMAGE="${img_array[0]}" - else - # manually select from storage list - d --list --column=Image --column=Size --title="Select A Image" --text="Select the operating system image to install.\n" ${list_items} - TIK_INSTALL_IMAGE="$result" - fi +tik_monitor_progress() { + if [ -z "${TIK_PIPE}" ]; then + return 0 fi + log "[progress] Monitoring installation progress" + (tail -f "${TIK_PIPE}") | d --progress --title="${TIK_PROGRESS_TITLE}" --auto-close --no-cancel --width=400 + log "[progress] Progress UI closed" } -reread_partitiontable() { - # We've just done a lot to $TIK_INSTALL_DEVICE and it's probably a good idea to make sure the partition table is clearly read so tools like dracut dont get confused. - log "[reread_partitiontable] Re-reading partition table" - # sleeps added to let the system finish doing partition stuff before rereading, and then a few secs to finish reading the table. Honestly, I'm not sure the 2nd one is needed, but doesn't hurt, so *shrug* - sleep 3 - prun /usr/sbin/blockdev --rereadpt ${TIK_INSTALL_DEVICE} - sleep 3 -} +tik_prepare_progress_pipe() { + TIK_PIPE=/tmp/tikpipe + export TIK_PIPE + [ -p "${TIK_PIPE}" ] || mkfifo "${TIK_PIPE}" -create_keyfile() { - # Even if there's no partitions using encryption, systemd-repart will need a key-file defined for the --key-file parameter. - tik_keyfile=$(prun mktemp /tmp/tik.XXXXXXXXXX) - log "[create_keyfile] Creating keyfile ${tik_keyfile}" - /usr/bin/base64 -w 0 /dev/urandom | head -c 1k | prun tee ${tik_keyfile} - prun /usr/bin/chmod 400 ${tik_keyfile} - # Add the key to roots cryptenroll keyring, and record tik_keyid for either interactions later - tik_keyid=$(prun cat ${tik_keyfile} | prun keyctl padd user cryptenroll @u) -} + tik_monitor_progress & + TIK_PROGRESS_PID=$! -wipe_keyfile() { - # We made a keyfile and need to clean it up at the end of the installation, possibly wiping it from the newly installed device - log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}" - probe_partitions ${TIK_INSTALL_DEVICE} "crypto_LUKS" - if [ -n "${probedpart}" ]; then - # Assumes Slot 0 is always by the key-file at enrolment - prun /usr/bin/systemd-cryptenroll --unlock-key-file=${tik_keyfile} --wipe-slot=0 ${probedpart} - fi - # We're done with the key-file, so remove it from the filesystem and keyring - prun /usr/bin/rm ${tik_keyfile} - prun-opt keyctl revoke ${tik_keyid} - prun-opt keyctl reap + log "[tik_prepare_progress_pipe] progress pipe ready (pid=${TIK_PROGRESS_PID})" } -dump_image() { - local image_source_files=$1 - local image_target=$2 - - d --question --no-wrap --title="Begin Installation?" --text="Once the installation begins the changes to the selected disk are irreversible.\n\nProceeding will fully erase the disk.\n\nContinue with installation?" +tik_close_progress() { + if [ -n "${TIK_PROGRESS_PID}" ]; then + log "[tik_close_progress] stopping progress monitor pid=${TIK_PROGRESS_PID}" + kill "${TIK_PROGRESS_PID}" 2>/dev/null || true + wait "${TIK_PROGRESS_PID}" 2>/dev/null || true + unset TIK_PROGRESS_PID + fi - case "${image_source_files}" in - *.raw.xz) - dump_image_dd ${image_source_files} ${image_target} - ;; - *.raw) - dump_image_repart_image ${image_source_files} ${image_target} - ;; - TIK_SELFDEPLOY) - dump_image_repart_self ${image_target} - ;; - *) - error "invalid image type provided" - esac + if [ -n "${TIK_PIPE}" ] && [ -p "${TIK_PIPE}" ]; then + log "[tik_close_progress] removing progress pipe ${TIK_PIPE}" + rm -f "${TIK_PIPE}" 2>/dev/null || true + fi + unset TIK_PIPE } -dump_image_dd() { - local image_source_files=$1 - local image_target=$2 - log "[dump_image_dd] deploying ${TIK_IMG_DIR}/${image_source_files}" - (xzcat ${TIK_IMG_DIR}/${image_source_files} | pv -f -F "# %b copied in %t %r" | prun /usr/bin/dd of=${image_target} bs=64k) 2>&1 | d --progress --title="Installing ${TIK_OS_NAME}" --pulsate --auto-close --no-cancel --width=400 - prun /usr/bin/sync | d --progress --title="Syncing" --pulsate --auto-close --no-cancel --width=400 -} +tik_progress_step() { + # Create progress UI when first used in this phase + [ -n "${TIK_PROGRESS_PID}" ] || tik_prepare_progress_pipe -dump_image_repart_image() { - local image_source_files=$1 - local image_target=$2 - local success=0 - local max_attempts=5 - local attempt_num=1 - create_keyfile - log "[dump_image_repart_image] deploying ${TIK_IMG_DIR}/${image_source_files}" - # systemd-repart doesn't always parse the contents of the image perfectly first time, so retry a few times before declaring it a failure - while [ ${success} = 0 ] && [ ${attempt_num} -lt ${max_attempts} ]; do - prun-opt systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file=${tik_keyfile} --image=${TIK_IMG_DIR}/${image_source_files} --image-policy=root=unprotected ${image_target} > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) - if [ ${retval} -eq 0 ]; then - success=1 - else - # repart couldn't find a root partition - log "[dump_image_repart_image] systemd-repart attempt $attempt_num failed. Trying again..." - sleep 1 - # Increment the attempt counter - attempt_num=$(( attempt_num + 1 )) - fi - done - if [ ${success} = 1 ]; then - log "[dump_image_repart_image] systemd-repart succeeded after $attempt_num attempts" - else - error "systemd-repart failed" + local message=$1 + local module_percent=$2 + + if [ -z "${TIK_PIPE}" ]; then + return 0 fi -} -dump_image_repart_self() { - local image_target=$1 - create_keyfile - log "[dump_image_repart_self] self-deploying" - prun systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file=${tik_keyfile} --generate-fstab=/etc/fstab.repart ${image_target} > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) -} + local overall_percent -set_boot_target() { - local efipartnum - if [ "${debug}" == "1" ]; then - log "[debug] Not setting EFI boot target" - elif [ -n "${efi_already_set}" ]; then - log "[set_boot_target] boot target already set, not setting again" + if [ -n "${TIK_TOTAL_MODULES}" ] && [ "${TIK_TOTAL_MODULES}" -gt 0 ] && \ + [ -n "${TIK_CURRENT_MODULE_INDEX}" ] && [ "${TIK_CURRENT_MODULE_INDEX}" -gt 0 ]; then + # Map module-local 0–100% into global 0–100% based on module index + # overall = ((index-1)*100 + module_percent) / total_modules + local base=$(( (TIK_CURRENT_MODULE_INDEX - 1) * 100 )) + local num=$(( base + module_percent )) + overall_percent=$(( num / TIK_TOTAL_MODULES )) else - # Cleanup any existing openSUSE boot entries - prun-opt /usr/sbin/efibootmgr -B -L "openSUSE Boot Manager" - # Cleanup any existing ${TIK_OS_NAME} boot entries - prun-opt /usr/sbin/efibootmgr -B -L "${TIK_OS_NAME} Boot Manager" - prun /usr/sbin/efibootmgr -O - log "[set_boot_target] searching for ESP partition containing /EFI/systemd/shim.efi on ${TIK_INSTALL_DEVICE}" - probe_partitions ${TIK_INSTALL_DEVICE} "vfat" "/EFI/systemd/shim.efi" - if [ -z "${probedpart}" ]; then - error "esp partition not found" - fi - efipartnum=$(lsblk ${probedpart} -p -n -r -o PARTN) - log "[set_boot_target] found ESP on ${probedpart}, partition number ${efipartnum}" - prun /usr/sbin/efibootmgr -c -L "${TIK_OS_NAME} Boot Manager" -d ${TIK_INSTALL_DEVICE} -l "\EFI\systemd\shim.efi" -p ${efipartnum} - # Log to show the resulting eficonfig - log "[set_boot_target] $(prun /usr/sbin/efibootmgr)" - efi_already_set=1 + overall_percent=${module_percent} fi -} -load_modules() { -local module_dir -if [[ $2 = "custom" ]]; then - module_dir=$TIK_CUSTOM_DIR/modules/$1 -else - module_dir=$tik_dir/modules/$1 -fi -if [ -n "$(ls -A $module_dir)" ]; then -for f in $module_dir/* - do - tik_module="$f" - log "[START] $module_dir/$f" - . $f - log "[STOP] $module_dir/$f" - done -fi -tik_module="tik" + echo "# ${message}" > "${TIK_PIPE}" + echo "${overall_percent}" > "${TIK_PIPE}" } diff --git a/usr/lib/tik/modules/post/00-setup b/usr/lib/tik/modules/post/00-setup new file mode 100644 index 0000000..f4469ea --- /dev/null +++ b/usr/lib/tik/modules/post/00-setup @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC +# SPDX-FileCopyrightText: Copyright 2025 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens + +TIK_ROOT_MNT=/var/lib/tik/root +export TIK_ROOT_MNT +prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}" + +# Find ESP partition +probe_partitions "${TIK_INSTALL_DEVICE}" "vfat" +if [ -z "${probedpart}" ]; then + error "esp partition not found" +fi +TIK_ESP_PART="${probedpart}" +export TIK_ESP_PART +log "[post-setup] ESP partition is ${TIK_ESP_PART}" + +# Find LUKS partition (if any) and open it +probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" +if [ -n "${probedpart}" ]; then + TIK_CRYPT_PART="${probedpart}" + export TIK_CRYPT_PART + tik_progress_step "Opening encrypted root" 0 + log "[post-setup] opening encrypted partition ${TIK_CRYPT_PART}" + prun /usr/sbin/cryptsetup luksOpen --key-file="${tik_keyfile}" "${TIK_CRYPT_PART}" aeon_root + TIK_ROOT_DEV="/dev/mapper/aeon_root" +else + TIK_CRYPT_PART="" + export TIK_CRYPT_PART + tik_progress_step "Locating root filesystem" 25 + probe_partitions "${TIK_INSTALL_DEVICE}" "btrfs" "/usr/lib/os-release" + if [ -z "${probedpart}" ]; then + error "root partition not found" + fi + TIK_ROOT_DEV="${probedpart}" +fi +export TIK_ROOT_DEV +log "[post-setup] root device is ${TIK_ROOT_DEV}" + +tik_progress_step "Mounting installed system" 50 + +prun /usr/bin/mount -o compress=zstd:1 "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" +prun /usr/bin/mount -t proc /proc "${TIK_ROOT_MNT}/proc" +prun /usr/bin/mount --bind /sys "${TIK_ROOT_MNT}/sys" +prun /usr/bin/mount -t securityfs securityfs "${TIK_ROOT_MNT}/sys/kernel/security" +prun /usr/bin/mount -t efivarfs efivarfs "${TIK_ROOT_MNT}/sys/firmware/efi/efivars" +prun /usr/bin/mount --bind /dev "${TIK_ROOT_MNT}/dev" +prun /usr/bin/mount --bind /run "${TIK_ROOT_MNT}/run" +prun /usr/bin/mount --bind /tmp "${TIK_ROOT_MNT}/tmp" +prun /usr/bin/mount -o compress=zstd:1,subvol=/@/.snapshots "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}/.snapshots" +prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}/var" + +mount_etc_for_root "${TIK_ROOT_MNT}" + +prun /usr/bin/mount "${TIK_ESP_PART}" "${TIK_ROOT_MNT}/boot/efi" + +tik_progress_step "Installed system mounted" 100 diff --git a/usr/lib/tik/modules/post/10-sicu b/usr/lib/tik/modules/post/10-sicu index acb601a..644f51b 100644 --- a/usr/lib/tik/modules/post/10-sicu +++ b/usr/lib/tik/modules/post/10-sicu @@ -1,106 +1,25 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: Copyright 2025 SUSE LLC # SPDX-FileCopyrightText: Copyright 2025 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens # Module that cleans up various things from a SelfInstall deployed system that otherwise can't be filtered out using repart.d config -sicu_dir=/var/lib/tik/sicu -sicu_pipe=/tmp/sicupipe -if [ ! -d ${sicu_dir}/mnt ]; then - prun /usr/bin/mkdir -p ${sicu_dir}/mnt -fi -if [ ! -p ${sicu_pipe} ]; then - mkfifo ${sicu_pipe} -fi - -sicu_progress() { - log "[sicu_progress] Monitoring SelfInstall Clean Up progress" - (tail -f ${sicu_pipe}) | d --progress --title="Cleaning up installation" --auto-close --no-cancel --width=400 - rm ${sicu_pipe} - log "[sicu_progress] SelfInstall Clean Up progress reached 100%" -} - -find_crypt() { - echo "# Finding encrypted partition" > ${sicu_pipe} - log "[find_crypt] finding encrypted partition" - probe_partitions ${TIK_INSTALL_DEVICE} "crypto_LUKS" - if [ -z "${probedpart}" ]; then - error "encrypted partition not found" - fi - cryptpart=${probedpart} - log "[find_crypt] found ${cryptpart}" - echo "14" > ${sicu_pipe} -} - -find_esp() { - echo "# Finding ESP partition" > ${sicu_pipe} - log "[find_esp] finding ESP" - probe_partitions ${TIK_INSTALL_DEVICE} "vfat" - if [ -z "${probedpart}" ]; then - error "esp partition not found" - fi - esppart=${probedpart} - log "[find_esp] found ${esppart}" - echo "28" > ${sicu_pipe} -} - -open_partition() { - echo "# Opening ${cryptpart}" > ${sicu_pipe} - log "[open_partition] opening ${cryptpart} and mounting for chroot" - prun /usr/sbin/cryptsetup luksOpen --key-file=${tik_keyfile} ${cryptpart} aeon_root - echo "35" > ${sicu_pipe} - prun /usr/bin/mount -o compress=zstd:1 /dev/mapper/aeon_root ${sicu_dir}/mnt - prun /usr/bin/mount -t proc /proc "${sicu_dir}/mnt/proc" - prun /usr/bin/mount --bind /sys "${sicu_dir}/mnt/sys" - prun /usr/bin/mount -t securityfs securityfs "${sicu_dir}/mnt/sys/kernel/security" - prun /usr/bin/mount -t efivarfs efivarfs "${sicu_dir}/mnt/sys/firmware/efi/efivars" - prun /usr/bin/mount --bind /dev "${sicu_dir}/mnt/dev" - prun /usr/bin/mount --bind /run "${sicu_dir}/mnt/run" - prun /usr/bin/mount --bind /tmp "${sicu_dir}/mnt/tmp" - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/.snapshots /dev/mapper/aeon_root ${sicu_dir}/mnt/.snapshots - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var /dev/mapper/aeon_root ${sicu_dir}/mnt/var - # Detect whether /etc is overlay else assume it's a T-U 5.0+ later bind mount - if grep -qF 'overlay /etc' ${sicu_dir}/mnt/etc/fstab ; then - etcmountcmd=$(cat ${sicu_dir}/mnt/etc/fstab | grep "overlay /etc" | sed 's/\/sysroot\//${sicu_dir}\/mnt\//g' | sed 's/\/work-etc.*/\/work-etc ${sicu_dir}\/mnt\/etc\//' | sed 's/overlay \/etc overlay/\/usr\/bin\/mount -t overlay overlay -o/') - eval prun "$etcmountcmd" - else - prun /usr/bin/mount -o bind ${sicu_dir}/mnt/etc ${sicu_dir}/mnt/etc - fi - prun /usr/bin/mount ${esppart} ${sicu_dir}/mnt/boot/efi - echo "42" > ${sicu_pipe} -} - sicu() { - echo "# Writing fstab" > ${sicu_pipe} + tik_progress_step "Writing fstab" 0 log "[sicu] Writing fstab" - prun /usr/bin/cat ${sicu_dir}/mnt/etc/fstab.repart | prun tee ${sicu_dir}/mnt/etc/fstab - echo "/etc /etc none bind,x-initrd.mount 0 0" | prun tee -a ${sicu_dir}/mnt/etc/fstab + prun /usr/bin/cat "${TIK_ROOT_MNT}/etc/fstab.repart" | prun tee "${TIK_ROOT_MNT}/etc/fstab" + echo "/etc /etc none bind,x-initrd.mount 0 0" | prun tee -a "${TIK_ROOT_MNT}/etc/fstab" prun /usr/bin/rm /etc/fstab.repart - echo "56" > ${sicu_pipe} - echo "# Cleaning up tik installer" > ${sicu_pipe} + + tik_progress_step "Cleaning up installer user" 50 log "[sicu] Deleting tik user" - prun /usr/bin/chroot ${sicu_dir}/mnt userdel -r tik + prun /usr/bin/chroot "${TIK_ROOT_MNT}" userdel -r tik log "[sicu] Enabling initial-setup" - prun /usr/bin/rm ${sicu_dir}/mnt/var/lib/gdm/block-initial-setup + prun /usr/bin/rm "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" log "[sicu] Disabling tik autologin" - prun /usr/bin/sed -i 's/DISPLAYMANAGER_AUTOLOGIN="tik"/DISPLAYMANAGER_AUTOLOGIN=""/' ${sicu_dir}/mnt/etc/sysconfig/displaymanager - echo "70" > ${sicu_pipe} -} - -close_partition() { - echo "# Closing ${cryptpart}" > ${sicu_pipe} - log "[close_partition] unmounting and closing ${cryptpart}" - for i in proc dev tmp 'boot/efi' etc var '.snapshots' 'sys/kernel/security' 'sys/firmware/efi/efivars' sys run; do - prun /usr/bin/umount "${sicu_dir}/mnt/$i" - done - prun /usr/bin/umount "${sicu_dir}/mnt" - prun /usr/sbin/cryptsetup luksClose aeon_root - echo "100" > ${sicu_pipe} + prun /usr/bin/sed -i 's/DISPLAYMANAGER_AUTOLOGIN="tik"/DISPLAYMANAGER_AUTOLOGIN=""/' "${TIK_ROOT_MNT}/etc/sysconfig/displaymanager" } -sicu_progress & -find_crypt -find_esp -open_partition sicu -close_partition +tik_progress_step "Installer user cleaned up" 100 diff --git a/usr/lib/tik/modules/post/15-encrypt b/usr/lib/tik/modules/post/15-encrypt index a7b53f0..f67170c 100644 --- a/usr/lib/tik/modules/post/15-encrypt +++ b/usr/lib/tik/modules/post/15-encrypt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: Copyright 2024 SUSE LLC # SPDX-FileCopyrightText: Copyright 2024 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens # Module does not actually do any encryption, but is intended to finish installation of an encrypted image, such as one deployed via systemd-repart # Module expects to find a single ESP partition (find_esp) and a single LUKS2 partition (find_crypt) on $TIK_INSTALL_DEVICE, upon which it will do the following @@ -18,195 +19,118 @@ # - Remove the temporary key-file and replace it either with TPM enrollment or a user-supplied passphrase (add_key) # It is expected the LUKS2 partition is already encrypted with a key-file in the only populated keyslot. -encrypt_dir=/var/lib/tik/encrypt -encrypt_pipe=/tmp/encryptpipe -if [ ! -d ${encrypt_dir}/mnt ]; then - prun /usr/bin/mkdir -p ${encrypt_dir}/mnt -fi -if [ ! -p ${encrypt_pipe} ]; then - mkfifo ${encrypt_pipe} -fi - -crypt_progress() { - log "[crypt_progress] Monitoring encryption progress" - (tail -f ${encrypt_pipe}) | d --progress --title="Configuring Encryption" --auto-close --no-cancel --width=400 - rm ${encrypt_pipe} - log "[crypt_progress] Encryption progress reached 100%" +generate_recoveryKey() { + tik_progress_step "Generating recovery key" 0 + log "[generate_recoveryKey] generating recovery key" + modhex=('c' 'b' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'n' 'r' 't' 'u' 'v') + mapfile -t raw_key < <(hexdump -v --format '1/1 "%u\n"' -n 32 /dev/random) + [ "${#raw_key[@]}" = 32 ] + key="" + for ((i=0;i<"${#raw_key[@]}";++i)); do + [ "$i" -gt 0 ] && [ "$((i%4))" -eq 0 ] && key="${key}-" + c="${raw_key[i]}" + key="${key}${modhex[$((c>>4))]}${modhex[$((c&15))]}" + done + log "[generate_recoveryKey] adding recovery key to roots sdbootutil user keyring" + logging=false + pkexec keyctl add user sdbootutil ${key} @u + logging=true } -find_crypt() { - echo "# Finding encrypted partition" > ${encrypt_pipe} - log "[find_crypt] finding encrypted partition" - probe_partitions ${TIK_INSTALL_DEVICE} "crypto_LUKS" - if [ -z "${probedpart}" ]; then - error "encrypted partition not found" - fi - cryptpart=${probedpart} - log "[find_crypt] found ${cryptpart}" - echo "14" > ${encrypt_pipe} +display_recoveryKey() { + local defaultmsg="This ${TIK_OS_NAME} system is encrypted and checks its own integrity on every boot\nIn the event of these integrity checks failing, you will need to use the Recovery Key provided below to enter this system\n\nLikely reasons for integrity checks failing include:\n\n• Secure Boot changed from enabled or disabled\n• Boot drive was moved to a different computer\n• Disk partitions were changed\n• Boot loader or initrd were altered unexpectedly\n\nIf you are unaware as to why the system is requesting the recovery key, this systems security may have been compromised\nThe best course of action may be to not unlock the disk until you can determine what changed to require the Recovery Key\n\nThis systems Recovery Key is:\n\n ${key}\n\nPlease save this secret Recovery Key in a secure location\n\n" + local fallbackmsg="In addition to your Passphrase a Recovery Key has been generated:\n\n ${key}\n\nPlease save this secret Recovery Key in a secure location\nIt may be used to regain access to this system if the other Passphrase becomes lost or forgotten\n\n" + local message + [ "${tik_encrypt_mode}" == 0 ] && message=${defaultmsg} + [ "${tik_encrypt_mode}" == 1 ] && message=${fallbackmsg} + log "[display_recoveryKey] displaying recovery key" + logging=false + d --width=500 --height=500 --no-wrap --warning --icon=security-high-symbolic --title="Encryption Recovery Key" --text="${message}You may optionally scan the recovery key off screen:\n$(qrencode ${key} -t UTF8i)\nFor more information please visit https://aeondesktop.org/encrypt" + logging=true + log "[display_recoveryKey] recovery key dialogue dismissed" } -find_esp() { - echo "# Finding encrypted partition" > ${encrypt_pipe} - log "[find_esp] finding ESP" - probe_partitions ${TIK_INSTALL_DEVICE} "vfat" - if [ -z "${probedpart}" ]; then - error "esp partition not found" - fi - esppart=${probedpart} - log "[find_esp] found ${esppart}" - echo "28" > ${encrypt_pipe} -} +configure_encryption() { + tik_progress_step "Configuring encryption and boot" 20 + log "[configure_encryption] configuring cmdline, crypttab, PCR policy, fstab and populating ${TIK_ESP_PART}" -open_partition() { - echo "# Opening ${cryptpart}" > ${encrypt_pipe} - log "[open_partition] opening ${cryptpart} and mounting for chroot" - prun /usr/sbin/cryptsetup luksOpen --key-file=${tik_keyfile} ${cryptpart} aeon_root - echo "35" > ${encrypt_pipe} - prun /usr/bin/mount -o compress=zstd:1 /dev/mapper/aeon_root ${encrypt_dir}/mnt - prun /usr/bin/mount -t proc /proc "${encrypt_dir}/mnt/proc" - prun /usr/bin/mount --bind /sys "${encrypt_dir}/mnt/sys" - prun /usr/bin/mount -t securityfs securityfs "${encrypt_dir}/mnt/sys/kernel/security" - prun /usr/bin/mount -t efivarfs efivarfs "${encrypt_dir}/mnt/sys/firmware/efi/efivars" - prun /usr/bin/mount --bind /dev "${encrypt_dir}/mnt/dev" - prun /usr/bin/mount --bind /run "${encrypt_dir}/mnt/run" - prun /usr/bin/mount --bind /tmp "${encrypt_dir}/mnt/tmp" - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/.snapshots /dev/mapper/aeon_root ${encrypt_dir}/mnt/.snapshots - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var /dev/mapper/aeon_root ${encrypt_dir}/mnt/var - # Detect whether /etc is overlay else assume it's a T-U 5.0+ later bind mount - if grep -qF 'overlay /etc' ${encrypt_dir}/mnt/etc/fstab ; then - etcmountcmd=$(cat ${encrypt_dir}/mnt/etc/fstab | grep "overlay /etc" | sed 's/\/sysroot\//${encrypt_dir}\/mnt\//g' | sed 's/\/work-etc.*/\/work-etc ${encrypt_dir}\/mnt\/etc\//' | sed 's/overlay \/etc overlay/\/usr\/bin\/mount -t overlay overlay -o/') - eval prun "$etcmountcmd" - else - prun /usr/bin/mount -o bind ${encrypt_dir}/mnt/etc ${encrypt_dir}/mnt/etc - fi - prun /usr/bin/mount ${esppart} ${encrypt_dir}/mnt/boot/efi - echo "42" > ${encrypt_pipe} -} + espUUID=$(lsblk -n -r -o UUID "${TIK_ESP_PART}") + prun /usr/bin/gawk -v espUUID="${espUUID}" -i inplace '$2 == "/boot/efi" { $1 = "UUID="espUUID } { print $0 }' "${TIK_ROOT_MNT}/etc/fstab" -configure_encryption() { - echo "# Writing cmdline, crypttab, and fstab" > ${encrypt_pipe} - log "[configure_encryption] configuring cmdline, crypttab, PCR policy, fstab and populating ${esppart}" - espUUID=$(lsblk -n -r -o UUID ${esppart}) - prun /usr/bin/gawk -v espUUID=$espUUID -i inplace '$2 == "/boot/efi" { $1 = "UUID="espUUID } { print $0 }' ${encrypt_dir}/mnt/etc/fstab # root=UUID= cmdline definition is a hard requirement of sdbootutil for updating predictions - rootUUID=$(lsblk -n -r -o UUID /dev/mapper/aeon_root) - prun /usr/bin/sed -i -e "s,\$, root=UUID=${rootUUID}," ${encrypt_dir}/mnt/etc/kernel/cmdline + rootUUID=$(lsblk -n -r -o UUID "${TIK_ROOT_DEV}") + prun /usr/bin/sed -i -e "s,\$, root=UUID=${rootUUID}," "${TIK_ROOT_MNT}/etc/kernel/cmdline" + # /etc/crypttab is a hard requirement of sdbootutil for updating predictions - cryptUUID=$(lsblk -n -r -d -o UUID ${cryptpart}) - echo "aeon_root UUID=${cryptUUID} none x-initrd.attach" | prun tee ${encrypt_dir}/mnt/etc/crypttab - echo "# Installing boot loader" > ${encrypt_pipe} + cryptUUID=$(lsblk -n -r -d -o UUID "${TIK_CRYPT_PART}") + echo "aeon_root UUID=${cryptUUID} none x-initrd.attach" | prun tee "${TIK_ROOT_MNT}/etc/crypttab" + # FIXME: Dracut gets confused by previous installations on occasion with the default config, override the problematic option temporarily - /usr/bin/echo 'hostonly_cmdline="no"' | prun tee ${encrypt_dir}/mnt/etc/dracut.conf.d/99-tik.conf + echo "hostonly_cmdline=\"no\"" | prun tee "${TIK_ROOT_MNT}/etc/dracut.conf.d/99-tik.conf" + # Install bootloader with sdbootutil - prun /usr/bin/chroot ${encrypt_dir}/mnt sdbootutil -vv --esp-path /boot/efi --no-variables install 1>&2 - echo "56" > ${encrypt_pipe} - echo "# Enrolling recovery key" > ${encrypt_pipe} - prun /usr/bin/chroot ${encrypt_dir}/mnt sdbootutil -vv --esp-path /boot/efi --method=recovery-key enroll 1>&2 - echo "70" > ${encrypt_pipe} + prun /usr/bin/chroot "${TIK_ROOT_MNT}" sdbootutil -vv --esp-path /boot/efi --no-variables install 1>&2 + + tik_progress_step "Enrolling recovery key" 40 + prun /usr/bin/chroot "${TIK_ROOT_MNT}" sdbootutil -vv --esp-path /boot/efi --method=recovery-key enroll 1>&2 + # If Default mode has been detected, configure PCR policy. # `etc/sysconfig/fde-tools` must be created before any calls to sdbtools, # because sdbootutil expects at least one of the configuration files being # present. See # https://github.com/openSUSE/sdbootutil/commit/8d3db8b01f5681c11054c37145aad3e3973a7741 if [ "${tik_encrypt_mode}" == 0 ]; then - echo "# Enrolling TPM key" > ${encrypt_pipe} + tik_progress_step "Enrolling TPM key" 60 # Explaining the chosen PCR list below # - 4 - Bootloader and drivers, should never recovery key as bootloader should only be updated with new PCR measurements # - 5 - GPT Partition table, should never require recovery key as partition layout shouldn't change # - 7 - SecureBoot state, will require recovery key if SecureBoot is enabled/disabled # - 9 - initrd - should never require recovery key as initrd should only be updated with new PCR measurements - echo "FDE_SEAL_PCR_LIST=4,5,7,9" | prun tee ${encrypt_dir}/mnt/etc/sysconfig/fde-tools + echo "FDE_SEAL_PCR_LIST=4,5,7,9" | prun tee "${TIK_ROOT_MNT}/etc/sysconfig/fde-tools" # Explaining why the following PCRs were not used # - 0 - UEFI firmware, will require recovery key after firmware update and is particularly painful to re-enrol # - 1 - Not only changes with CPU/RAM/hardware changes, but also when UEFI config changes are made, which is too common to lockdown # - 2 - Includes option ROMs on pluggable hardware, such as external GPUs. Attaching a GPU to your laptop shouldn't hinder booting. # - 3 - Firmware from pluggable hardware. Attaching hardware to your laptop shouldn't hinder booting - prun /usr/bin/chroot ${encrypt_dir}/mnt sdbootutil -vv --esp-path /boot/efi --method=tpm2 enroll 1>&2 - else - echo "# Enrolling fallback password" > ${encrypt_pipe} + prun /usr/bin/chroot "${TIK_ROOT_MNT}" sdbootutil -vv --esp-path /boot/efi --method=tpm2 enroll 1>&2 + else + tik_progress_step "Setting encryption passphrase" 60 d --width=500 --height=300 --no-wrap --warning --icon=security-high-symbolic --title="Set Encryption Passphrase" --text="This ${TIK_OS_NAME} system is encrypted and will require a Passphrase on every boot\n\nYou will be prompted to set the Passphrase on the next screen\n\nFor more information please visit https://aeondesktop.org/encrypt" - log "[configure_encryption] Fallback Mode - Prompting user for passphrase for ${cryptpart}" - # Not using 'd' function to avoid logging the password - # FIXME - Now use 'd' function and logging=false - while true - do - if $gui; then - pw=$(zenity --password --title='Set Encryption Passphrase') - pw_check=$(zenity --password --title='Type Passphrase Again') - else - cenity pw --password --title="Set Encryption Passphrase" - cenity pw_check --password --title="Type Passphrase Again" + log "[configure_encryption] Fallback Mode - Prompting user for passphrase for ${TIK_CRYPT_PART}" + + while true; do + logging=false + d_opt --password --title="Set Encryption Passphrase" + pw="${result}" + d_opt --password --title="Type Passphrase Again" + pw_check="${result}" + logging=true + + # User cancelled both dialogs -> no passphrase set, just exit loop + if [ -z "${pw}" ] && [ -z "${pw_check}" ]; then + break fi - # Ask again, and double check the user is putting the right passphrase again. + if [ "${pw}" != "${pw_check}" ]; then d --warning --no-wrap --title="Passphrase did not match" --text="Please try again" - # Reset variable, so we can try again pw="" + pw_check="" + continue fi - if [ -n "${pw}" ]; then - prun /usr/sbin/cryptsetup luksAddKey --key-file=${tik_keyfile} --batch-mode --force-password "${cryptpart}" <<<"${pw}" - # Initrd wasn't generated by install or enroll as no TPM interaction, so do it now. - prun /usr/bin/chroot ${encrypt_dir}/mnt sdbootutil -vv --esp-path /boot/efi mkinitrd 1>&2 - fi + + prun /usr/sbin/cryptsetup luksAddKey --key-file="${tik_keyfile}" --batch-mode --force-password "${TIK_CRYPT_PART}" <<<"${pw}" + # Initrd wasn't generated by install or enroll as no TPM interaction, so do it now. + prun /usr/bin/chroot "${TIK_ROOT_MNT}" sdbootutil -vv --esp-path /boot/efi mkinitrd 1>&2 break done fi - # FIXME: Dracut gets confused by previous installations on occasion with the default config, remove override now initrd done - prun /usr/bin/rm ${encrypt_dir}/mnt/etc/dracut.conf.d/99-tik.conf - echo "84" > ${encrypt_pipe} - -} - -close_partition() { - echo "# Closing ${cryptpart}" > ${encrypt_pipe} - log "[close_partition] unmounting and closing ${cryptpart}" - for i in proc dev tmp 'boot/efi' etc var '.snapshots' 'sys/kernel/security' 'sys/firmware/efi/efivars' sys; do - prun /usr/bin/umount "${encrypt_dir}/mnt/$i" - done - prun /usr/bin/umount -l ${encrypt_dir}/mnt/run - prun /usr/bin/umount ${encrypt_dir}/mnt - prun /usr/sbin/cryptsetup luksClose aeon_root - echo "100" > ${encrypt_pipe} -} -generate_recoveryKey() { - echo "# Generating recovery key" > ${encrypt_pipe} - log "[generate_recoveryKey] generating recovery key" - modhex=('c' 'b' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'n' 'r' 't' 'u' 'v') - mapfile -t raw_key < <(hexdump -v --format '1/1 "%u\n"' -n 32 /dev/random) - [ "${#raw_key[@]}" = 32 ] - key="" - for ((i=0;i<"${#raw_key[@]}";++i)); do - [ "$i" -gt 0 ] && [ "$((i%4))" -eq 0 ] && key="${key}-" - c="${raw_key[i]}" - key="${key}${modhex[$((c>>4))]}${modhex[$((c&15))]}" - done - log "[generate_recoveryKey] adding recovery key to roots sdbootutil user keyring" - logging=false - pkexec keyctl add user sdbootutil ${key} @u - logging=true - echo "50" > ${encrypt_pipe} -} - -display_recoveryKey() { - local defaultmsg="This ${TIK_OS_NAME} system is encrypted and checks its own integrity on every boot\nIn the event of these integrity checks failing, you will need to use the Recovery Key provided below to enter this system\n\nLikely reasons for integrity checks failing include:\n\n• Secure Boot changed from enabled or disabled\n• Boot drive was moved to a different computer\n• Disk partitions were changed\n• Boot loader or initrd were altered unexpectedly\n\nIf you are unaware as to why the system is requesting the recovery key, this systems security may have been compromised\nThe best course of action may be to not unlock the disk until you can determine what changed to require the Recovery Key\n\nThis systems Recovery Key is:\n\n ${key}\n\nPlease save this secret Recovery Key in a secure location\n\n" - local fallbackmsg="In addition to your Passphrase a Recovery Key has been generated:\n\n ${key}\n\nPlease save this secret Recovery Key in a secure location\nIt may be used to regain access to this system if the other Passphrase becomes lost or forgotten\n\n" - local message - [ "${tik_encrypt_mode}" == 0 ] && message=${defaultmsg} - [ "${tik_encrypt_mode}" == 1 ] && message=${fallbackmsg} - log "[display_recoveryKey] displaying recovery key" - logging=false - d --width=500 --height=500 --no-wrap --warning --icon=security-high-symbolic --title="Encryption Recovery Key" --text="${message}You may optionally scan the recovery key off screen:\n$(qrencode ${key} -t UTF8i)\nFor more information please visit https://aeondesktop.org/encrypt" - logging=true - log "[display_recoveryKey] recovery key dialogue dismissed" + # FIXME: Dracut gets confused by previous installations on occasion with the default config, remove override now initrd done + prun /usr/bin/rm "${TIK_ROOT_MNT}/etc/dracut.conf.d/99-tik.conf" + tik_progress_step "Encryption configuration complete" 80 } -crypt_progress & -find_crypt -find_esp -open_partition generate_recoveryKey configure_encryption -close_partition display_recoveryKey +tik_progress_step "Encryption configured" 100 diff --git a/usr/lib/tik/modules/post/20-mig b/usr/lib/tik/modules/post/20-mig index bf23504..acec9ee 100644 --- a/usr/lib/tik/modules/post/20-mig +++ b/usr/lib/tik/modules/post/20-mig @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: Copyright 2024 SUSE LLC # SPDX-FileCopyrightText: Copyright 2024 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens writemigdesktop() { prun-opt /usr/bin/tee $1/.config/autostart/aeon-mig-firstboot.desktop << "EOF" @@ -16,11 +17,7 @@ EOF prun-opt /usr/bin/chmod 666 $1/.config/autostart/aeon-mig-firstboot.desktop } - if [ "${migrate}" == 1 ]; then - probe_partitions ${TIK_INSTALL_DEVICE} "crypto_LUKS" - [ -z "${probedpart}" ] || prun /usr/sbin/cryptsetup luksOpen --key-file=${tik_keyfile} ${cryptpart} aeon_root - probe_partitions $TIK_INSTALL_DEVICE "btrfs" "/usr/lib/os-release" [ -n "${probedpart}" ] || error "MIGRATION FAILED: New Installation NOT FOUND" @@ -29,14 +26,9 @@ if [ "${migrate}" == 1 ]; then prun /usr/bin/mount -o compress=zstd:1 ${probedpart} ${mig_dir}/mnt prun /usr/bin/systemd-repart --pretty 0 --root ${mig_dir}/mnt --dry-run=0 ${probedpart} prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var ${probedpart} ${mig_dir}/mnt/var - prun /lib/systemd/systemd-growfs ${mig_dir}/mnt/var - # Detect whether /etc is overlay else assume it's a T-U 5.0+ later bind mount - if grep -qF 'overlay /etc' ${mig_dir}/mnt/etc/fstab ; then - etcmountcmd=$(cat ${mig_dir}/mnt/etc/fstab | grep "overlay /etc" | sed 's/\/sysroot\//${mig_dir}\/mnt\//g' | sed 's/\/work-etc.*/\/work-etc ${mig_dir}\/mnt\/etc\//' | sed 's/overlay \/etc overlay/\/usr\/bin\/mount -t overlay overlay -o/') - eval prun "$etcmountcmd" - else - prun /usr/bin/mount -o bind ${mig_dir}/mnt/etc ${mig_dir}/mnt/etc - fi + + mount_etc_for_root "${mig_dir}/mnt" + prun /usr/bin/cat ${mig_dir}/passwd.out | prun tee -a ${mig_dir}/mnt/etc/passwd prun /usr/bin/cat ${mig_dir}/group.out | prun tee -a ${mig_dir}/mnt/etc/group prun /usr/bin/cat ${mig_dir}/shadow.out | prun tee -a ${mig_dir}/mnt/etc/shadow @@ -71,5 +63,4 @@ if [ "${migrate}" == 1 ]; then done prun /usr/bin/umount ${mig_dir}/mnt prun /usr/bin/rmdir ${mig_dir}/mnt - [ ! -e /dev/mapper/aeon_root ] || prun /usr/sbin/cryptsetup luksClose aeon_root fi diff --git a/usr/lib/tik/modules/post/99-cleanup b/usr/lib/tik/modules/post/99-cleanup new file mode 100644 index 0000000..c43ef42 --- /dev/null +++ b/usr/lib/tik/modules/post/99-cleanup @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC +# SPDX-FileCopyrightText: Copyright 2025 Richard Brown +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens + +tik_progress_step "Cleaning up system mounts" 0 + +for i in proc dev tmp 'boot/efi' etc var '.snapshots' 'sys/kernel/security' 'sys/firmware/efi/efivars' sys; do + prun-opt /usr/bin/umount "${TIK_ROOT_MNT}/$i" +done +prun-opt /usr/bin/umount -l "${TIK_ROOT_MNT}/run" +prun-opt /usr/bin/umount "${TIK_ROOT_MNT}" + +if [ -n "${TIK_CRYPT_PART}" ]; then + log "[post-cleanup] closing encrypted root for ${TIK_CRYPT_PART}" + prun-opt /usr/sbin/cryptsetup luksClose aeon_root +fi + +tik_progress_step "Finalizing boot configuration" 50 +wipe_keyfile +set_boot_target + +tik_progress_step "Installation complete" 100 + +rm -f "${TIK_PIPE}" diff --git a/usr/lib/tik/modules/pre/05-setup-gnome-env b/usr/lib/tik/modules/pre/05-setup-gnome-env new file mode 100644 index 0000000..20b95d0 --- /dev/null +++ b/usr/lib/tik/modules/pre/05-setup-gnome-env @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC +# SPDX-FileCopyrightText: Copyright 2025 Richard Brown +# SPDX-FileCopyrightText: Copyright 2024 Raymond Yip +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens + +setup_env() { + # Setup environment for installation, mostly setting GNOME/gsettings vars to prevent screenlocking, etc + log "[setup_env] Setting up environment for Installation" + gsettings set org.gnome.shell favorite-apps [''] + gsettings set org.gnome.desktop.session idle-delay '0' + gsettings set org.gnome.desktop.screensaver lock-enabled 'false' + gsettings set org.gnome.desktop.screensaver user-switch-enabled 'false' + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing' + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' + gsettings set org.gnome.desktop.lockdown disable-lock-screen 'true' + gsettings set org.gnome.desktop.lockdown disable-log-out 'true' + gsettings set org.gnome.desktop.lockdown disable-printing 'true' + gsettings set org.gnome.desktop.lockdown disable-print-setup 'true' + gsettings set org.gnome.desktop.lockdown disable-user-switching 'true' + gsettings set org.gnome.desktop.lockdown user-administration-disabled 'true' + gsettings set org.gnome.software allow-updates 'false' + gsettings set org.gnome.software download-updates 'false' + gsettings set org.gnome.software download-updates-notify 'false' +} + +setup_env diff --git a/usr/lib/tik/modules/pre/10-welcome b/usr/lib/tik/modules/pre/10-welcome index 4d35b80..44286f2 100644 --- a/usr/lib/tik/modules/pre/10-welcome +++ b/usr/lib/tik/modules/pre/10-welcome @@ -2,6 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2025 SUSE LLC # SPDX-FileCopyrightText: Copyright 2025 Richard Brown # SPDX-FileCopyrightText: Copyright 2024 Raymond Yip +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens proceedInstall() { d --info --ok-label="Install Now" --no-wrap --width=300 --height=300 --icon=distributor-logo-Aeon-symbolic --title="" --text="Welcome to ${TIK_OS_NAME}\n\nPlease press Install Now to continue" @@ -12,27 +13,27 @@ displayACWarningMsg() { } checkLaptop() { - chassis=`cat /sys/class/dmi/id/chassis_type` - #Test for respectively Handheld, Notebook, Laptop, and Portable - #if chassis variable matches 8 9 10 or 11 function continues else it proceeds to test AC power and Battery - [[ "$chassis" =~ ^(8|9|10|11)$ ]] || return - #Tested machine is confirmed mobile - givePowerRecommendation=false - #Only check for AC and Battery power connections with upower - updevices=`/usr/bin/upower -e|grep -E 'AC|BAT'` - for pdev in $updevices; do - #Get detailed info for each AC and BAT device in upower - upinfo=`/usr/bin/upower -i $pdev|grep -E 'online|state'` - #Check for discharging state or AC power offline which is equal to no state - if [[ "$upinfo" =~ (discharging|no) ]]; then - #Give power recommendation only once, so set this to true - givePowerRecommendation=true - fi - done - if [ "$givePowerRecommendation" = true ]; then - log "AC Power disconnected and Battery is not charging" - displayACWarningMsg + chassis=$(cat /sys/class/dmi/id/chassis_type) + # Test for respectively Handheld, Notebook, Laptop, and Portable + # If chassis variable matches 8 9 10 or 11 function continues else it proceeds to test AC power and Battery + [[ "$chassis" =~ ^(8|9|10|11)$ ]] || return + # Tested machine is confirmed mobile + givePowerRecommendation=false + # Only check for AC and Battery power connections with upower + updevices=$(/usr/bin/upower -e | grep -E 'AC|BAT') + for pdev in $updevices; do + # Get detailed info for each AC and BAT device in upower + upinfo=$(/usr/bin/upower -i "$pdev" | grep -E 'online|state') + # Check for discharging state or AC power offline which is equal to no state + if [[ "$upinfo" =~ (discharging|no) ]]; then + # Give power recommendation only once, so set this to true + givePowerRecommendation=true fi + done + if [ "$givePowerRecommendation" = true ]; then + log "AC Power disconnected and Battery is not charging" + displayACWarningMsg + fi } verify_efi() { @@ -44,27 +45,6 @@ verify_efi() { fi } -setup_env() { - # Setup environment for installation, mostly setting GNOME/gsettings vars to prevent screenlocking, etc - log "[setup_env] Setting up environment for Installation" - gsettings set org.gnome.shell favorite-apps [''] - gsettings set org.gnome.desktop.session idle-delay '0' - gsettings set org.gnome.desktop.screensaver lock-enabled 'false' - gsettings set org.gnome.desktop.screensaver user-switch-enabled 'false' - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing' - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' - gsettings set org.gnome.desktop.lockdown disable-lock-screen 'true' - gsettings set org.gnome.desktop.lockdown disable-log-out 'true' - gsettings set org.gnome.desktop.lockdown disable-printing 'true' - gsettings set org.gnome.desktop.lockdown disable-print-setup 'true' - gsettings set org.gnome.desktop.lockdown disable-user-switching 'true' - gsettings set org.gnome.desktop.lockdown user-administration-disabled 'true' - gsettings set org.gnome.software allow-updates 'false' - gsettings set org.gnome.software download-updates 'false' - gsettings set org.gnome.software download-updates-notify 'false' -} - -setup_env proceedInstall verify_efi checkLaptop diff --git a/usr/lib/tik/modules/pre/20-mig b/usr/lib/tik/modules/pre/20-mig index 5073fc6..3e63622 100644 --- a/usr/lib/tik/modules/pre/20-mig +++ b/usr/lib/tik/modules/pre/20-mig @@ -46,38 +46,38 @@ fi get_disk if [ -z "${skipbackup}" ]; then - # Although Legacy Aeon didn't officially support LUKS encrypted installations, - # some users might have nevertheless enabled encryption anyway. - # Search for existing crypto_LUKS partitions and, if found, prompt the user - # to unlock those so that the migration module can find existing data. - for encrypted_partition in $(lsblk ${TIK_INSTALL_DEVICE} -p -n -r -o ID-LINK,FSTYPE|tr -s ' ' ";"|grep ";crypto_LUKS"|cut -d\; -f1); do - if [ -e /dev/mapper/crypt_${encrypted_partition} ]; then - # Already opened for some reason... do not prompt for the passphrase - # but ensure we will clean up afterwards - crypt_opened="${crypt_opened} crypt_${encrypted_partition}" - else - while [ 1 ]; do - if $gui; then - passphrase=$(zenity --password --title="Encrypted partition (${encrypted_partition}) detected" --cancel-label="Skip") || break - else - cenity passphrase --password --title="Encrypted partition (${encrypted_partition}) detected" --cancel-label="Skip" || break - fi - if [ -n "${passphrase}" ]; then - echo -n "${passphrase}" | prun /usr/sbin/cryptsetup luksOpen /dev/disk/by-id/${encrypted_partition} crypt_${encrypted_partition} - if [ "${?}" -eq 0 ]; then - crypt_opened="${crypt_opened} crypt_${encrypted_partition}" - # Wait for the mapped device to appear - wait_count=0 - while [ ! -e /dev/mapper/crypt_${encrypted_partition} ] && [ ${wait_count} -lt 5 ]; do - sleep 1 - wait_count=$((wait_count + 1)) - done - break - fi + # Although Legacy Aeon didn't officially support LUKS encrypted installations, + # some users might have nevertheless enabled encryption anyway. + # Search for existing crypto_LUKS partitions and, if found, prompt the user + # to unlock those so that the migration module can find existing data. + for encrypted_partition in $(lsblk ${TIK_INSTALL_DEVICE} -p -n -r -o ID-LINK,FSTYPE|tr -s ' ' ";"|grep ";crypto_LUKS"|cut -d\; -f1); do + if [ -e /dev/mapper/crypt_${encrypted_partition} ]; then + # Already opened for some reason... do not prompt for the passphrase + # but ensure we will clean up afterwards + crypt_opened="${crypt_opened} crypt_${encrypted_partition}" + else + while [ 1 ]; do + if $gui; then + passphrase=$(zenity --password --title="Encrypted partition (${encrypted_partition}) detected" --cancel-label="Skip") || break + else + cenity passphrase --password --title="Encrypted partition (${encrypted_partition}) detected" --cancel-label="Skip" || break + fi + if [ -n "${passphrase}" ]; then + echo -n "${passphrase}" | prun /usr/sbin/cryptsetup luksOpen /dev/disk/by-id/${encrypted_partition} crypt_${encrypted_partition} + if [ "${?}" -eq 0 ]; then + crypt_opened="${crypt_opened} crypt_${encrypted_partition}" + # Wait for the mapped device to appear + wait_count=0 + while [ ! -e /dev/mapper/crypt_${encrypted_partition} ] && [ ${wait_count} -lt 5 ]; do + sleep 1 + wait_count=$((wait_count + 1)) + done + break fi - done - fi - done + fi + done + fi + done unset passphrase # Probe selected disk for a btrfs partition containing /usr/lib/os-release @@ -127,44 +127,45 @@ if [ -z "${skipbackup}" ]; then if [ -d ${mig_dir}/mnt/${snap_dir} ]; then prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${snap_dir} fi - prun /usr/sbin/btrfs subvolume snapshot -r ${mig_dir}/mnt ${mig_dir}/mnt/${snap_dir} - (prun /usr/sbin/btrfs send ${mig_dir}/mnt/${snap_dir} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}) 2>&1 | d --progress --title="Backing up /home" --pulsate --auto-close --no-cancel --width=400 - prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${snap_dir} - # Probe for subvolumes nested beneath /home and back them up also - if (prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/mnt | grep -q "ID "); then - prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro false - for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/mnt --sort=path | rev | cut -f1 -d' ' | rev | sed 's/^@\/home//'); do - subsubvolname=$(basename $subsubvol) - subsubdirname=$(dirname $subsubvol) - prun /usr/sbin/btrfs subvolume snapshot -r ${mig_dir}/mnt/${subsubvol} ${mig_dir}/mnt/${subsubvolname} - (prun /usr/sbin/btrfs send ${mig_dir}/mnt/${subsubvolname} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/${snap_dir}/${subsubdirname}) 2>&1 | d --progress --title="Backing up containers" --pulsate --auto-close --no-cancel --width=400 - prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${subsubvolname} - done - prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro true - fi - prun /usr/bin/umount ${mig_dir}/mnt - prun /usr/bin/mount -o compress=zstd:1 ${probedpart} ${mig_dir}/mnt - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var ${probedpart} ${mig_dir}/mnt/var - etcmntcmd=$(cat ${mig_dir}/mnt/etc/fstab | grep "overlay /etc" | sed 's/\/sysroot\//${mig_dir}\/mnt\//g' | sed 's/\/work-etc.*/\/work-etc ${mig_dir}\/mnt\/etc\//' | sed 's/overlay \/etc overlay/\/usr\/bin\/mount -t overlay overlay -o/') - eval prun "$etcmntcmd" - prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534)' ${mig_dir}/mnt/etc/passwd | prun /usr/bin/awk -F':' '{ $7="/bin/bash"; print };' OFS=':' | prun tee ${mig_dir}/passwd.out - prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534 && $3 != 65533)' ${mig_dir}/mnt/etc/group | prun tee ${mig_dir}/group.out - prun /usr/bin/awk -F'[/:]' '{if ($3 >= 1000 && $3 != 65534) print $1}' ${mig_dir}/mnt/etc/passwd | prun /usr/bin/grep -f - ${mig_dir}/mnt/etc/shadow | prun tee ${mig_dir}/shadow.out - prun /usr/bin/cp -a ${mig_dir}/mnt/etc/subuid ${mig_dir}/subuid - prun /usr/bin/cp -a ${mig_dir}/mnt/etc/subgid ${mig_dir}/subgid - # It's not guaranteed that the system will have existing network configs, custom localtime or AccountsService - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/NetworkManager/system-connections ${mig_dir}/system-connections - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/localtime ${mig_dir}/localtime - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/AccountsService/users ${mig_dir}/users - prun-opt /usr/bin/chmod 744 ${mig_dir}/users - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/AccountsService/icons ${mig_dir}/icons - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/bluetooth ${mig_dir}/bluetooth - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/fprint ${mig_dir}/fprint - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/openvpn ${mig_dir}/openvpn - prun-opt /usr/bin/umount ${mig_dir}/mnt/etc - prun /usr/bin/umount ${mig_dir}/mnt/var - prun /usr/bin/umount ${mig_dir}/mnt - prun /usr/bin/rmdir ${mig_dir}/mnt + prun /usr/sbin/btrfs subvolume snapshot -r ${mig_dir}/mnt ${mig_dir}/mnt/${snap_dir} + (prun /usr/sbin/btrfs send ${mig_dir}/mnt/${snap_dir} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}) 2>&1 | d --progress --title="Backing up /home" --pulsate --auto-close --no-cancel --width=400 + prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${snap_dir} + # Probe for subvolumes nested beneath /home and back them up also + if (prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/mnt | grep -q "ID "); then + prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro false + for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/mnt --sort=path | rev | cut -f1 -d' ' | rev | sed 's/^@\/home//'); do + subsubvolname=$(basename $subsubvol) + subsubdirname=$(dirname $subsubvol) + prun /usr/sbin/btrfs subvolume snapshot -r ${mig_dir}/mnt/${subsubvol} ${mig_dir}/mnt/${subsubvolname} + (prun /usr/sbin/btrfs send ${mig_dir}/mnt/${subsubvolname} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/${snap_dir}/${subsubdirname}) 2>&1 | d --progress --title="Backing up containers" --pulsate --auto-close --no-cancel --width=400 + prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${subsubvolname} + done + prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro true + fi + prun /usr/bin/umount ${mig_dir}/mnt + prun /usr/bin/mount -o compress=zstd:1 ${probedpart} ${mig_dir}/mnt + prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var ${probedpart} ${mig_dir}/mnt/var + + mount_etc_for_root "${mig_dir}/mnt" + + prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534)' ${mig_dir}/mnt/etc/passwd | prun /usr/bin/awk -F':' '{ $7="/bin/bash"; print };' OFS=':' | prun tee ${mig_dir}/passwd.out + prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534 && $3 != 65533)' ${mig_dir}/mnt/etc/group | prun tee ${mig_dir}/group.out + prun /usr/bin/awk -F'[/:]' '{if ($3 >= 1000 && $3 != 65534) print $1}' ${mig_dir}/mnt/etc/passwd | prun /usr/bin/grep -f - ${mig_dir}/mnt/etc/shadow | prun tee ${mig_dir}/shadow.out + prun /usr/bin/cp -a ${mig_dir}/mnt/etc/subuid ${mig_dir}/subuid + prun /usr/bin/cp -a ${mig_dir}/mnt/etc/subgid ${mig_dir}/subgid + # It's not guaranteed that the system will have existing network configs, custom localtime or AccountsService + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/NetworkManager/system-connections ${mig_dir}/system-connections + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/localtime ${mig_dir}/localtime + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/AccountsService/users ${mig_dir}/users + prun-opt /usr/bin/chmod 744 ${mig_dir}/users + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/AccountsService/icons ${mig_dir}/icons + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/bluetooth ${mig_dir}/bluetooth + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/fprint ${mig_dir}/fprint + prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/openvpn ${mig_dir}/openvpn + prun-opt /usr/bin/umount ${mig_dir}/mnt/etc + prun /usr/bin/umount ${mig_dir}/mnt/var + prun /usr/bin/umount ${mig_dir}/mnt + prun /usr/bin/rmdir ${mig_dir}/mnt fi # Close eventual mapped LUKS devices From 5801c954460d7816704137f1bbed50a709597227 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 11:35:05 +0100 Subject: [PATCH 02/25] Make mount/unmount core functions The mount/unmount functions now use the fstab of the final installed system to determine mount options and what to mount. Special cases like bind mounts or overlay mounts are handled as well. The root filesystem is mounted temporarily to get its mount correct options. The sicu module doesn't rewrite fstab anymore. --- usr/bin/tik | 4 + usr/lib/tik/lib/tik-core | 272 ++++++++++++++++++++++++++++ usr/lib/tik/lib/tik-functions | 15 -- usr/lib/tik/modules/post/00-setup | 58 ------ usr/lib/tik/modules/post/10-sicu | 8 +- usr/lib/tik/modules/post/99-cleanup | 25 --- 6 files changed, 277 insertions(+), 105 deletions(-) delete mode 100644 usr/lib/tik/modules/post/00-setup delete mode 100644 usr/lib/tik/modules/post/99-cleanup diff --git a/usr/bin/tik b/usr/bin/tik index f008a07..4b1b7ae 100755 --- a/usr/bin/tik +++ b/usr/bin/tik @@ -72,7 +72,11 @@ get_img dump_image "${TIK_INSTALL_IMAGE}" "${TIK_INSTALL_DEVICE}" reread_partitiontable +tik_target_mount + # Run post modules tik_init_phase_modules "post" load_modules "post" load_modules "post" "custom" + +tik_target_unmount diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index bda0363..9f3f534 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -335,6 +335,278 @@ tik_init_phase_modules() { log "[tik_init_phase_modules] phase=${phase} total_modules=${TIK_TOTAL_MODULES}" } +tik_is_opt_set() { + echo ",$1," | grep -q ",$2," +} + +tik_rewrite_overlay_opts_for_target() { + local opts="$1" + local root="$2" + + opts="$(echo "$opts" | sed -E "s#lowerdir=/#lowerdir=${root}/#g; s#upperdir=/#upperdir=${root}/#g; s#workdir=/#workdir=${root}/#g")" + opts="$(echo "$opts" | sed -E "s#lowerdir=${root}//#lowerdir=${root}/#g; s#upperdir=${root}//#upperdir=${root}/#g; s#workdir=${root}//#workdir=${root}/#g")" + opts="$(echo "$opts" | sed -E "s#lowerdir=(${root}/[^,]*):/#lowerdir=\1:${root}/#g")" + + echo "$opts" +} + +tik_target_mount_prepare() { + TIK_ROOT_MNT="${TIK_ROOT_MNT:=/var/lib/tik/root}" + export TIK_ROOT_MNT + prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}" + + TIK_MOUNTED_POINTS="" + TIK_MOUNTED_TARGET=0 + export TIK_MOUNTED_TARGET +} + +tik_target_find_root_partition() { + local fs + for fs in btrfs ext4 xfs f2fs; do + probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/fstab" + if [ -n "${probedpart}" ]; then + echo "${probedpart}" + return 0 + fi + probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/os-release" + if [ -n "${probedpart}" ]; then + echo "${probedpart}" + return 0 + fi + probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/usr/lib/os-release" + if [ -n "${probedpart}" ]; then + echo "${probedpart}" + return 0 + fi + done + return 1 +} + +tik_target_open_luks_if_present() { + TIK_CRYPT_PART="" + TIK_ROOT_DEV="" + + probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" + if [ -n "${probedpart}" ]; then + TIK_CRYPT_PART="${probedpart}" + export TIK_CRYPT_PART + + # TODO: mapper name should be distro-specific + local mapper_name="tik_root" + + tik_progress_step "Opening encrypted root" 0 + log "[tik_target_open_luks_if_present] opening encrypted partition ${TIK_CRYPT_PART} as ${mapper_name}" + + if [ -z "${tik_keyfile}" ] || [ ! -f "${tik_keyfile}" ]; then + error "Encrypted root found but tik_keyfile is missing" + fi + + prun /usr/sbin/cryptsetup luksOpen --key-file="${tik_keyfile}" "${TIK_CRYPT_PART}" "${mapper_name}" + TIK_ROOT_DEV="/dev/mapper/${mapper_name}" + export TIK_ROOT_DEV + return 0 + fi + + export TIK_CRYPT_PART + return 1 +} + +tik_target_mount_root() { + local rootdev=$1 + local mnt=$2 + + tik_progress_step "Mounting target root" 25 + + local fstype + fstype="$(lsblk -n -r -o FSTYPE "${rootdev}" 2>/dev/null | head -n1)" + + # Temporarily mount the root to determine the real mount options + local tmp_mnt + local root_opts + tmp_mnt="$(prun /usr/bin/mktemp -d /tmp/tik-rootprobe.XXXXXXXXXX)" + log "[tik_target_mount_root] probing mount options for / by temporarily mounting ${rootdev} on ${tmp_mnt}" + + prun /usr/bin/mount -o ro "${rootdev}" "${tmp_mnt}" + + if [ -f "${tmp_mnt}/etc/fstab" ]; then + # Read the mount options for the '/' entry + root_opts="$(/usr/bin/awk ' + $0 ~ /^[[:space:]]*#/ { next } + NF < 4 { next } + $2 == "/" { print $4; exit } + ' "${tmp_mnt}/etc/fstab")" + log "[tik_target_mount_root] probed root mount options: '${root_opts}'" + else + log "[tik_target_mount_root] no ${tmp_mnt}/etc/fstab found while probing, falling back to default mount options" + root_opts="" + fi + + prun-opt /usr/bin/umount "${tmp_mnt}" + prun-opt /usr/bin/rmdir "${tmp_mnt}" + + if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then + prun /usr/bin/mount -o "${root_opts}" "${rootdev}" "${mnt}" + else + prun /usr/bin/mount "${rootdev}" "${mnt}" + fi + + # track for unmount + TIK_MOUNTED_POINTS="${mnt} +${TIK_MOUNTED_POINTS}" +} + +tik_target_mount_from_fstab() { + local mnt=$1 + local fstab="${mnt}/etc/fstab" + + if [ ! -f "${fstab}" ]; then + log "[tik_target_mount_from_fstab] no fstab at ${fstab}, skipping fstab mounts" + return 0 + fi + + tik_progress_step "Mounting filesystems from fstab" 50 + log "[tik_target_mount_from_fstab] reading ${fstab}" + + while IFS= read -r line; do + line="${line#"${line%%[![:space:]]*}"}" + [ -z "${line}" ] && continue + [[ "${line}" = \#* ]] && continue + + set -- ${line} + local spec="$1" + local mp="$2" + local fstype="$3" + local opts="$4" + + [ -z "${spec}" ] && continue + [ -z "${mp}" ] && continue + [ -z "${fstype}" ] && continue + + [ "${mp}" = "/" ] && continue + [ "${fstype}" = "swap" ] && continue + + case "${fstype}" in + proc|sysfs|devtmpfs|devpts|tmpfs|cgroup|cgroup2|securityfs|efivarfs) + continue + ;; + esac + + local target="${mnt}${mp}" + prun /usr/bin/mkdir -p "${target}" + + if [ "${fstype}" = "none" ] && (tik_is_opt_set "${opts}" "bind" || tik_is_opt_set "${opts}" "rbind"); then + local src="${mnt}${spec}" + if tik_is_opt_set "${opts}" "rbind"; then + prun /usr/bin/mount --rbind "${src}" "${target}" + else + prun /usr/bin/mount --bind "${src}" "${target}" + fi + if tik_is_opt_set "${opts}" "ro" || tik_is_opt_set "${opts}" "nosuid" || tik_is_opt_set "${opts}" "nodev" || tik_is_opt_set "${opts}" "noexec"; then + prun /usr/bin/mount -o "remount,${opts}" "${target}" + fi + + elif [ "${fstype}" = "overlay" ]; then + local newopts + newopts="$(tik_rewrite_overlay_opts_for_target "${opts}" "${mnt}")" + prun /usr/bin/mount -t overlay overlay -o "${newopts}" "${target}" + + else + if [ -n "${opts}" ] && [ "${opts}" != "-" ]; then + prun /usr/bin/mount -t "${fstype}" -o "${opts}" "${spec}" "${target}" + else + prun /usr/bin/mount -t "${fstype}" "${spec}" "${target}" + fi + fi + + # Track mountpoint for reverse unmount + TIK_MOUNTED_POINTS="${target} +${TIK_MOUNTED_POINTS}" + done < "${fstab}" +} + +tik_target_mount_pseudofs() { + local mnt=$1 + + tik_progress_step "Binding pseudo filesystems" 75 + + prun /usr/bin/mount -t proc /proc "${mnt}/proc" + TIK_MOUNTED_POINTS="${mnt}/proc +${TIK_MOUNTED_POINTS}" + + prun /usr/bin/mount --bind /sys "${mnt}/sys" + TIK_MOUNTED_POINTS="${mnt}/sys +${TIK_MOUNTED_POINTS}" + + prun /usr/bin/mount --bind /dev "${mnt}/dev" + TIK_MOUNTED_POINTS="${mnt}/dev +${TIK_MOUNTED_POINTS}" + + prun /usr/bin/mount --bind /run "${mnt}/run" + TIK_MOUNTED_POINTS="${mnt}/run +${TIK_MOUNTED_POINTS}" + + prun /usr/bin/mount --bind /tmp "${mnt}/tmp" + TIK_MOUNTED_POINTS="${mnt}/tmp +${TIK_MOUNTED_POINTS}" +} + +tik_target_mount() { + [ "${TIK_MOUNTED_TARGET}" = "1" ] && return 0 + + tik_target_mount_prepare + + # determine root device + tik_target_open_luks_if_present || true + + if [ -z "${TIK_ROOT_DEV}" ]; then + tik_progress_step "Locating root filesystem" 0 + TIK_ROOT_DEV="$(tik_target_find_root_partition)" || error "root partition not found" + export TIK_ROOT_DEV + fi + + log "[tik_target_mount] root device is ${TIK_ROOT_DEV}" + + # mount root + tik_target_mount_root "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" + + # mount everything described by target fstab + tik_target_mount_from_fstab "${TIK_ROOT_MNT}" + + # bind pseudo fs for chroot usage + tik_target_mount_pseudofs "${TIK_ROOT_MNT}" + + tik_progress_step "Target system mounted" 100 + + TIK_MOUNTED_TARGET=1 + export TIK_MOUNTED_TARGET +} + +tik_target_unmount() { + [ "${TIK_MOUNTED_TARGET}" = "1" ] || return 0 + + tik_progress_step "Unmounting target system" 0 + + # Unmount in reverse order using tracked list + local mp + while IFS= read -r mp; do + [ -z "${mp}" ] && continue + prun-opt /usr/bin/umount "${mp}" + done <<< "${TIK_MOUNTED_POINTS}" + + # As a last resort, unmount the root mount lazily if still busy + prun-opt /usr/bin/umount -l "${TIK_ROOT_MNT}" || true + + # Close luks mapping + if [ -n "${TIK_CRYPT_PART}" ]; then + log "[tik_target_unmount] closing encrypted root for ${TIK_CRYPT_PART}" + prun-opt /usr/sbin/cryptsetup luksClose tik_root + fi + + TIK_MOUNTED_POINTS="" + TIK_MOUNTED_TARGET=0 + export TIK_MOUNTED_TARGET +} + load_modules() { local phase=$1 local module_dir diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index fc51cdf..a59d593 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -142,21 +142,6 @@ probe_partitions() { prun /usr/bin/rmdir ${probe_dir}/mnt } -mount_etc_for_root() { - local root=$1 - - if grep -qF 'overlay /etc' "${root}/etc/fstab" ; then - local etcmountcmd - etcmountcmd=$(grep "overlay /etc" "${root}/etc/fstab" \ - | sed "s#/sysroot/#${root}/#g" \ - | sed "s#/work-etc.*#/work-etc ${root}/etc#g" \ - | sed 's#overlay /etc overlay#/usr/bin/mount -t overlay overlay -o#') - eval prun "${etcmountcmd}" - else - prun /usr/bin/mount -o bind "${root}/etc" "${root}/etc" - fi -} - tik_monitor_progress() { if [ -z "${TIK_PIPE}" ]; then return 0 diff --git a/usr/lib/tik/modules/post/00-setup b/usr/lib/tik/modules/post/00-setup deleted file mode 100644 index f4469ea..0000000 --- a/usr/lib/tik/modules/post/00-setup +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC -# SPDX-FileCopyrightText: Copyright 2025 Richard Brown -# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens - -TIK_ROOT_MNT=/var/lib/tik/root -export TIK_ROOT_MNT -prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}" - -# Find ESP partition -probe_partitions "${TIK_INSTALL_DEVICE}" "vfat" -if [ -z "${probedpart}" ]; then - error "esp partition not found" -fi -TIK_ESP_PART="${probedpart}" -export TIK_ESP_PART -log "[post-setup] ESP partition is ${TIK_ESP_PART}" - -# Find LUKS partition (if any) and open it -probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" -if [ -n "${probedpart}" ]; then - TIK_CRYPT_PART="${probedpart}" - export TIK_CRYPT_PART - tik_progress_step "Opening encrypted root" 0 - log "[post-setup] opening encrypted partition ${TIK_CRYPT_PART}" - prun /usr/sbin/cryptsetup luksOpen --key-file="${tik_keyfile}" "${TIK_CRYPT_PART}" aeon_root - TIK_ROOT_DEV="/dev/mapper/aeon_root" -else - TIK_CRYPT_PART="" - export TIK_CRYPT_PART - tik_progress_step "Locating root filesystem" 25 - probe_partitions "${TIK_INSTALL_DEVICE}" "btrfs" "/usr/lib/os-release" - if [ -z "${probedpart}" ]; then - error "root partition not found" - fi - TIK_ROOT_DEV="${probedpart}" -fi -export TIK_ROOT_DEV -log "[post-setup] root device is ${TIK_ROOT_DEV}" - -tik_progress_step "Mounting installed system" 50 - -prun /usr/bin/mount -o compress=zstd:1 "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" -prun /usr/bin/mount -t proc /proc "${TIK_ROOT_MNT}/proc" -prun /usr/bin/mount --bind /sys "${TIK_ROOT_MNT}/sys" -prun /usr/bin/mount -t securityfs securityfs "${TIK_ROOT_MNT}/sys/kernel/security" -prun /usr/bin/mount -t efivarfs efivarfs "${TIK_ROOT_MNT}/sys/firmware/efi/efivars" -prun /usr/bin/mount --bind /dev "${TIK_ROOT_MNT}/dev" -prun /usr/bin/mount --bind /run "${TIK_ROOT_MNT}/run" -prun /usr/bin/mount --bind /tmp "${TIK_ROOT_MNT}/tmp" -prun /usr/bin/mount -o compress=zstd:1,subvol=/@/.snapshots "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}/.snapshots" -prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}/var" - -mount_etc_for_root "${TIK_ROOT_MNT}" - -prun /usr/bin/mount "${TIK_ESP_PART}" "${TIK_ROOT_MNT}/boot/efi" - -tik_progress_step "Installed system mounted" 100 diff --git a/usr/lib/tik/modules/post/10-sicu b/usr/lib/tik/modules/post/10-sicu index 644f51b..af0f8e7 100644 --- a/usr/lib/tik/modules/post/10-sicu +++ b/usr/lib/tik/modules/post/10-sicu @@ -6,13 +6,7 @@ # Module that cleans up various things from a SelfInstall deployed system that otherwise can't be filtered out using repart.d config sicu() { - tik_progress_step "Writing fstab" 0 - log "[sicu] Writing fstab" - prun /usr/bin/cat "${TIK_ROOT_MNT}/etc/fstab.repart" | prun tee "${TIK_ROOT_MNT}/etc/fstab" - echo "/etc /etc none bind,x-initrd.mount 0 0" | prun tee -a "${TIK_ROOT_MNT}/etc/fstab" - prun /usr/bin/rm /etc/fstab.repart - - tik_progress_step "Cleaning up installer user" 50 + tik_progress_step "Cleaning up installer user" 0 log "[sicu] Deleting tik user" prun /usr/bin/chroot "${TIK_ROOT_MNT}" userdel -r tik log "[sicu] Enabling initial-setup" diff --git a/usr/lib/tik/modules/post/99-cleanup b/usr/lib/tik/modules/post/99-cleanup deleted file mode 100644 index c43ef42..0000000 --- a/usr/lib/tik/modules/post/99-cleanup +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC -# SPDX-FileCopyrightText: Copyright 2025 Richard Brown -# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens - -tik_progress_step "Cleaning up system mounts" 0 - -for i in proc dev tmp 'boot/efi' etc var '.snapshots' 'sys/kernel/security' 'sys/firmware/efi/efivars' sys; do - prun-opt /usr/bin/umount "${TIK_ROOT_MNT}/$i" -done -prun-opt /usr/bin/umount -l "${TIK_ROOT_MNT}/run" -prun-opt /usr/bin/umount "${TIK_ROOT_MNT}" - -if [ -n "${TIK_CRYPT_PART}" ]; then - log "[post-cleanup] closing encrypted root for ${TIK_CRYPT_PART}" - prun-opt /usr/sbin/cryptsetup luksClose aeon_root -fi - -tik_progress_step "Finalizing boot configuration" 50 -wipe_keyfile -set_boot_target - -tik_progress_step "Installation complete" 100 - -rm -f "${TIK_PIPE}" From ebd6c074fb6f053962ecd974396681012aecaa4e Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 14:36:09 +0100 Subject: [PATCH 03/25] Allow modifying fstab before mount When /etc/fstab.repart exists, it is now used as fstab file. After the system is installed, a special /etc/fstab.tik file can be used to inject or override fstab entries systemd-repart couldn't generate. It is expected that this file exists in the installed system right after deployment and before any post module runs. --- usr/lib/tik/lib/tik-core | 109 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 9f3f534..dc55358 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -455,6 +455,112 @@ tik_target_mount_root() { ${TIK_MOUNTED_POINTS}" } +tik_target_fstab_has_mountpoint() { + local fstab=$1 + local mp=$2 + /usr/bin/awk -v want="${mp}" ' + $0 ~ /^[[:space:]]*#/ { next } + NF < 2 { next } + $2 == want { found=1; exit } + END { exit(found ? 0 : 1) } + ' "${fstab}" +} + +tik_target_fstab_is_safe_extra_mount() { + local spec=$1 + local mp=$2 + local fstype=$3 + + # Require absolute mountpoints + if [ -z "${mp}" ] || [ "${mp#"/"}" = "${mp}" ]; then + return 1 + fi + + # Basic path traversal guard + if echo "${mp}" | grep -qE '(^|/)\.\.($|/)'; then + return 1 + fi + + # Do not allow overriding the root mount or pseudo filesystems + case "${mp}" in + "/"|"/proc"|"/sys"|"/dev"|"/run"|"/tmp") + return 1 + ;; + esac + + return 0 +} + +tik_target_fstab_override_mountpoint() { + local fstab=$1 + local mp=$2 + local newline=$3 + + local tmp + tmp="$(prun /usr/bin/mktemp /tmp/tik-fstab.XXXXXXXXXX)" + + # Replace the first non-comment entry with the same mountpoint, otherwise append. + /usr/bin/awk -v want="${mp}" -v rep="${newline}" ' + BEGIN { replaced=0 } + { + if ($0 ~ /^[[:space:]]*#/ || NF < 2) { print; next } + if (!replaced && $2 == want) { print rep; replaced=1; next } + print + } + END { if (!replaced) print rep } + ' "${fstab}" | prun /usr/bin/tee "${tmp}" >/dev/null + + prun /usr/bin/cp -a "${tmp}" "${fstab}" + prun /usr/bin/rm -f "${tmp}" +} + +tik_target_fstab_apply_repart_and_tik() { + local mnt=$1 + + local fstab="${mnt}/etc/fstab" + local fstab_repart="${mnt}/etc/fstab.repart" + local fstab_tik="${mnt}/etc/fstab.tik" + local fstab_backup="${mnt}/etc/fstab.tik.orig" + + # Prefer systemd-repart generated fstab if present + if [ -f "${fstab_repart}" ]; then + log "[tik_target_fstab_apply_repart_and_tik] found ${fstab_repart}, overriding ${fstab}" + if [ -f "${fstab}" ] && [ ! -f "${fstab_backup}" ]; then + prun /usr/bin/cp -a "${fstab}" "${fstab_backup}" + log "[tik_target_fstab_apply_repart_and_tik] backed up original fstab to ${fstab_backup}" + fi + prun /usr/bin/cp -a "${fstab_repart}" "${fstab}" + fi + + # Apply /etc/fstab.tik if present + if [ -f "${fstab_tik}" ]; then + log "[tik_target_fstab_apply_repart_and_tik] found ${fstab_tik}, applying extra mounts to ${fstab}" + + while IFS= read -r line; do + line="${line#"${line%%[![:space:]]*}"}" + [ -z "${line}" ] && continue + [[ "${line}" = \#* ]] && continue + + set -- ${line} + local spec="$1" + local mp="$2" + local fstype="$3" + + [ -z "${spec}" ] && continue + [ -z "${mp}" ] && continue + [ -z "${fstype}" ] && continue + + if ! tik_target_fstab_is_safe_extra_mount "${spec}" "${mp}" "${fstype}"; then + log "[tik_target_fstab_apply_repart_and_tik] skipping unsafe fstab.tik entry: ${line}" + continue + fi + + tik_target_fstab_override_mountpoint "${fstab}" "${mp}" "${line}" + log "[tik_target_fstab_apply_repart_and_tik] applied override/append for ${mp}" + done < <(prun /usr/bin/cat "${fstab_tik}") + fi +} + tik_target_mount_from_fstab() { local mnt=$1 local fstab="${mnt}/etc/fstab" @@ -569,6 +675,9 @@ tik_target_mount() { # mount root tik_target_mount_root "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" + # Prefer fstab.repart and apply fstab.tik before mounting from fstab + tik_target_fstab_apply_repart_and_tik "${TIK_ROOT_MNT}" + # mount everything described by target fstab tik_target_mount_from_fstab "${TIK_ROOT_MNT}" From 0231559d96410a4e788a117ae54697aa892b6a80 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 15:28:11 +0100 Subject: [PATCH 04/25] Assemble fstab outside installed system This allows assembling the fstab even if /etc of the installed is read only. When the fstab is completely assembled, its mounts are applied and the fstab copied over to the installed system, if possible. If not, it is kept so e.g. post modules can handle it. --- usr/lib/tik/lib/tik-core | 113 +++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index dc55358..751b046 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -358,6 +358,11 @@ tik_target_mount_prepare() { TIK_MOUNTED_POINTS="" TIK_MOUNTED_TARGET=0 export TIK_MOUNTED_TARGET + + TIK_TARGET_FSTAB="" + export TIK_TARGET_FSTAB + TIK_ASSEMBLED_FSTAB="" + export TIK_ASSEMBLED_FSTAB } tik_target_find_root_partition() { @@ -455,17 +460,6 @@ tik_target_mount_root() { ${TIK_MOUNTED_POINTS}" } -tik_target_fstab_has_mountpoint() { - local fstab=$1 - local mp=$2 - /usr/bin/awk -v want="${mp}" ' - $0 ~ /^[[:space:]]*#/ { next } - NF < 2 { next } - $2 == want { found=1; exit } - END { exit(found ? 0 : 1) } - ' "${fstab}" -} - tik_target_fstab_is_safe_extra_mount() { local spec=$1 local mp=$2 @@ -514,27 +508,46 @@ tik_target_fstab_override_mountpoint() { prun /usr/bin/rm -f "${tmp}" } -tik_target_fstab_apply_repart_and_tik() { +tik_target_fstab_can_write_etc() { + local mnt=$1 + local probe="${mnt}/etc/.tik-write-probe.$$" + prun-opt /usr/bin/sh -c "touch '${probe}' && rm -f '${probe}'" + [ "${retval}" = "0" ] +} + +tik_target_fstab_assemble() { local mnt=$1 local fstab="${mnt}/etc/fstab" local fstab_repart="${mnt}/etc/fstab.repart" local fstab_tik="${mnt}/etc/fstab.tik" - local fstab_backup="${mnt}/etc/fstab.tik.orig" - # Prefer systemd-repart generated fstab if present + local assembled + assembled="$(prun /usr/bin/mktemp /tmp/tik-assembled-fstab.XXXXXXXXXX)" + TIK_ASSEMBLED_FSTAB="${assembled}" + export TIK_ASSEMBLED_FSTAB + + # Prefer systemd-repart generated fstab if present, otherwise use existing fstab. if [ -f "${fstab_repart}" ]; then - log "[tik_target_fstab_apply_repart_and_tik] found ${fstab_repart}, overriding ${fstab}" - if [ -f "${fstab}" ] && [ ! -f "${fstab_backup}" ]; then - prun /usr/bin/cp -a "${fstab}" "${fstab_backup}" - log "[tik_target_fstab_apply_repart_and_tik] backed up original fstab to ${fstab_backup}" - fi - prun /usr/bin/cp -a "${fstab_repart}" "${fstab}" + log "[tik_target_fstab_assemble] using ${fstab_repart} as base fstab" + prun /usr/bin/cp -a "${fstab_repart}" "${assembled}" + elif [ -f "${fstab}" ]; then + log "[tik_target_fstab_assemble] using ${fstab} as base fstab" + prun /usr/bin/cp -a "${fstab}" "${assembled}" + else + log "[tik_target_fstab_assemble] no base fstab found, leaving assembled fstab empty at ${assembled}" + prun /usr/bin/tee "${assembled}" >/dev/null </dev/null while IFS= read -r line; do line="${line#"${line%%[![:space:]]*}"}" @@ -551,19 +564,55 @@ tik_target_fstab_apply_repart_and_tik() { [ -z "${fstype}" ] && continue if ! tik_target_fstab_is_safe_extra_mount "${spec}" "${mp}" "${fstype}"; then - log "[tik_target_fstab_apply_repart_and_tik] skipping unsafe fstab.tik entry: ${line}" + log "[tik_target_fstab_assemble] skipping unsafe fstab.tik entry: ${line}" continue fi - tik_target_fstab_override_mountpoint "${fstab}" "${mp}" "${line}" - log "[tik_target_fstab_apply_repart_and_tik] applied override/append for ${mp}" - done < <(prun /usr/bin/cat "${fstab_tik}") + tik_target_fstab_override_mountpoint "${assembled}" "${mp}" "${line}" + log "[tik_target_fstab_assemble] applied override/append for ${mp}" + done < "${tik_tmp}" + + prun /usr/bin/rm -f "${tik_tmp}" + else + log "[tik_target_fstab_assemble] no ${fstab_tik} found, not applying tik overrides" + fi + + echo "${assembled}" +} + +tik_target_fstab_install_if_possible() { + local mnt=$1 + local assembled=$2 + local fstab="${mnt}/etc/fstab" + local backup="${mnt}/etc/fstab.tik.orig" + + if tik_target_fstab_can_write_etc "${mnt}"; then + log "[tik_target_fstab_install_if_possible] /etc is writable, installing assembled fstab to ${fstab}" + if [ -f "${fstab}" ] && [ ! -f "${backup}" ]; then + prun /usr/bin/cp -a "${fstab}" "${backup}" + log "[tik_target_fstab_install_if_possible] backed up original fstab to ${backup}" + fi + prun /usr/bin/cp -a "${assembled}" "${fstab}" + TIK_TARGET_FSTAB="${fstab}" + export TIK_TARGET_FSTAB + return 0 fi + + log "[tik_target_fstab_install_if_possible] /etc is not writable yet" + TIK_TARGET_FSTAB="${assembled}" + export TIK_TARGET_FSTAB + return 1 } tik_target_mount_from_fstab() { local mnt=$1 - local fstab="${mnt}/etc/fstab" + local fstab + + if [ -n "${TIK_TARGET_FSTAB}" ]; then + fstab="${TIK_TARGET_FSTAB}" + else + fstab="${mnt}/etc/fstab" + fi if [ ! -f "${fstab}" ]; then log "[tik_target_mount_from_fstab] no fstab at ${fstab}, skipping fstab mounts" @@ -675,12 +724,18 @@ tik_target_mount() { # mount root tik_target_mount_root "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" - # Prefer fstab.repart and apply fstab.tik before mounting from fstab - tik_target_fstab_apply_repart_and_tik "${TIK_ROOT_MNT}" + # Assemble fstab in a writable location + local assembled + assembled="$(tik_target_fstab_assemble "${TIK_ROOT_MNT}")" + TIK_TARGET_FSTAB="${assembled}" + export TIK_TARGET_FSTAB - # mount everything described by target fstab + # mount everything described by assembled fstab tik_target_mount_from_fstab "${TIK_ROOT_MNT}" + # After mounts are active, try to install the assembled fstab into the target + tik_target_fstab_install_if_possible "${TIK_ROOT_MNT}" "${assembled}" || true + # bind pseudo fs for chroot usage tik_target_mount_pseudofs "${TIK_ROOT_MNT}" From 3626918b94e53ba9a63bdb5b664da876336b1212 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 15:40:07 +0100 Subject: [PATCH 05/25] Remove /etc/fstab.repart before installation In selfinstall mode, systemd-repart writes /etc/fstab.repart to the host's filesystem, so it needs to be removed if it exists before installation or systemd-repart will fail.. --- usr/lib/tik/lib/tik-core | 1 + 1 file changed, 1 insertion(+) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 751b046..a7bf0fa 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -281,6 +281,7 @@ dump_image_repart_image() { dump_image_repart_self() { local image_target=$1 create_keyfile + prun-opt rm -rf /etc/fstab.repart log "[dump_image_repart_self] self-deploying" prun systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --generate-fstab=/etc/fstab.repart "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) } From 53ae6de199ec2215b815bd0734876568ea429d5e Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 16:07:29 +0100 Subject: [PATCH 06/25] Make assembled fstab world-readable The fstab shouldn't contain any secrets, so it's safe to make it world-readable. Since the fstab.repart file is only readable by root, it needs to be handled with root permissions. --- usr/lib/tik/lib/tik-core | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index a7bf0fa..d86f25c 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -578,6 +578,8 @@ EOF log "[tik_target_fstab_assemble] no ${fstab_tik} found, not applying tik overrides" fi + prun /usr/bin/chmod 0644 "${assembled}" + echo "${assembled}" } @@ -594,6 +596,7 @@ tik_target_fstab_install_if_possible() { log "[tik_target_fstab_install_if_possible] backed up original fstab to ${backup}" fi prun /usr/bin/cp -a "${assembled}" "${fstab}" + prun /usr/bin/chmod 0644 "${fstab}" TIK_TARGET_FSTAB="${fstab}" export TIK_TARGET_FSTAB return 0 From 6fce79e2fa5f6747d2624f7b757c20e21f8bfb23 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 16:25:17 +0100 Subject: [PATCH 07/25] Do not use progress_pipe in core functions --- usr/lib/tik/lib/tik-core | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index d86f25c..1957116 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -400,7 +400,6 @@ tik_target_open_luks_if_present() { # TODO: mapper name should be distro-specific local mapper_name="tik_root" - tik_progress_step "Opening encrypted root" 0 log "[tik_target_open_luks_if_present] opening encrypted partition ${TIK_CRYPT_PART} as ${mapper_name}" if [ -z "${tik_keyfile}" ] || [ ! -f "${tik_keyfile}" ]; then @@ -421,8 +420,6 @@ tik_target_mount_root() { local rootdev=$1 local mnt=$2 - tik_progress_step "Mounting target root" 25 - local fstype fstype="$(lsblk -n -r -o FSTYPE "${rootdev}" 2>/dev/null | head -n1)" @@ -623,7 +620,6 @@ tik_target_mount_from_fstab() { return 0 fi - tik_progress_step "Mounting filesystems from fstab" 50 log "[tik_target_mount_from_fstab] reading ${fstab}" while IFS= read -r line; do @@ -686,7 +682,7 @@ ${TIK_MOUNTED_POINTS}" tik_target_mount_pseudofs() { local mnt=$1 - tik_progress_step "Binding pseudo filesystems" 75 + log "[tik_target_mount_pseudofs] Binding pseudo filesystems" prun /usr/bin/mount -t proc /proc "${mnt}/proc" TIK_MOUNTED_POINTS="${mnt}/proc @@ -718,7 +714,6 @@ tik_target_mount() { tik_target_open_luks_if_present || true if [ -z "${TIK_ROOT_DEV}" ]; then - tik_progress_step "Locating root filesystem" 0 TIK_ROOT_DEV="$(tik_target_find_root_partition)" || error "root partition not found" export TIK_ROOT_DEV fi @@ -743,7 +738,7 @@ tik_target_mount() { # bind pseudo fs for chroot usage tik_target_mount_pseudofs "${TIK_ROOT_MNT}" - tik_progress_step "Target system mounted" 100 + log "[tik_target_mount] Target system mounted" TIK_MOUNTED_TARGET=1 export TIK_MOUNTED_TARGET @@ -752,7 +747,7 @@ tik_target_mount() { tik_target_unmount() { [ "${TIK_MOUNTED_TARGET}" = "1" ] || return 0 - tik_progress_step "Unmounting target system" 0 + log "[tik_target_unmount] Unmounting target system" # Unmount in reverse order using tracked list local mp From dbb57c41c12001182988b301bb72d08592a87107 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 16:44:05 +0100 Subject: [PATCH 08/25] Fix permissions in while loops --- usr/lib/tik/lib/tik-core | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 1957116..2dfb1dc 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -543,11 +543,7 @@ EOF if [ -f "${fstab_tik}" ]; then log "[tik_target_fstab_assemble] found ${fstab_tik}, applying overrides into ${assembled}" - local tik_tmp - tik_tmp="$(prun /usr/bin/mktemp /tmp/tik-fstab-tik.XXXXXXXXXX)" - prun /usr/bin/cat "${fstab_tik}" | prun /usr/bin/tee "${tik_tmp}" >/dev/null - - while IFS= read -r line; do + prun /usr/bin/cat "${fstab_tik}" | while IFS= read -r line; do line="${line#"${line%%[![:space:]]*}"}" [ -z "${line}" ] && continue [[ "${line}" = \#* ]] && continue @@ -568,15 +564,11 @@ EOF tik_target_fstab_override_mountpoint "${assembled}" "${mp}" "${line}" log "[tik_target_fstab_assemble] applied override/append for ${mp}" - done < "${tik_tmp}" - - prun /usr/bin/rm -f "${tik_tmp}" + done else log "[tik_target_fstab_assemble] no ${fstab_tik} found, not applying tik overrides" fi - prun /usr/bin/chmod 0644 "${assembled}" - echo "${assembled}" } @@ -622,7 +614,7 @@ tik_target_mount_from_fstab() { log "[tik_target_mount_from_fstab] reading ${fstab}" - while IFS= read -r line; do + prun /usr/bin/cat "${fstab}" | while IFS= read -r line; do line="${line#"${line%%[![:space:]]*}"}" [ -z "${line}" ] && continue [[ "${line}" = \#* ]] && continue @@ -676,7 +668,7 @@ tik_target_mount_from_fstab() { # Track mountpoint for reverse unmount TIK_MOUNTED_POINTS="${target} ${TIK_MOUNTED_POINTS}" - done < "${fstab}" + done } tik_target_mount_pseudofs() { From 9cc1cd060c2d35988089d101ed4d98d90c7fb291 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 16:47:18 +0100 Subject: [PATCH 09/25] Ignore mount options that break installation For example, ro=vfs or ro are not wanted during installation as they break chroot operations. This also further fixes permission issues --- usr/lib/tik/lib/tik-core | 79 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 2dfb1dc..30276ca 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -416,6 +416,60 @@ tik_target_open_luks_if_present() { return 1 } +tik_target_mountopts_filter() { + local opts="$1" + local ignore_list="$2" + + if [ -z "${opts}" ] || [ "${opts}" = "-" ]; then + echo "${opts}" + return 0 + fi + + /usr/bin/awk -v opts="${opts}" -v ignore="${ignore_list}" ' + BEGIN { + n=split(ignore, ig, /,/) + for (i=1;i<=n;i++) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", ig[i]) + if (ig[i] != "") drop[ig[i]]=1 + } + + m=split(opts, a, /,/) + out="" + for (j=1;j<=m;j++) { + o=a[j] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", o) + if (o=="") continue + + # exact match (e.g. ro) + if (drop[o]) continue + + # key=value match (e.g. ro=vfs) where ignore has 'ro' or 'ro=' or 'ro=vfs' + split(o, kv, /=/) + k=kv[1] + if (drop[k]) continue + if (drop[k"="]) continue + if (drop[o]) continue + + if (out=="") out=o + else out=out","o + } + if (out=="") out="-" + print out + } + ' +} + +tik_target_mountopts_ignore_list() { + echo "${TIK_MOUNTOPTS_IGNORE_LIST:-ro,ro=vfs}" +} + +tik_target_mountopts_apply_filter() { + local opts="$1" + local ignore_opts + ignore_opts="$(tik_target_mountopts_ignore_list)" + tik_target_mountopts_filter "${opts}" "${ignore_opts}" +} + tik_target_mount_root() { local rootdev=$1 local mnt=$2 @@ -432,8 +486,7 @@ tik_target_mount_root() { prun /usr/bin/mount -o ro "${rootdev}" "${tmp_mnt}" if [ -f "${tmp_mnt}/etc/fstab" ]; then - # Read the mount options for the '/' entry - root_opts="$(/usr/bin/awk ' + root_opts="$(prun /usr/bin/awk ' $0 ~ /^[[:space:]]*#/ { next } NF < 4 { next } $2 == "/" { print $4; exit } @@ -447,6 +500,11 @@ tik_target_mount_root() { prun-opt /usr/bin/umount "${tmp_mnt}" prun-opt /usr/bin/rmdir "${tmp_mnt}" + if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then + root_opts="$(tik_target_mountopts_apply_filter "${root_opts}")" + log "[tik_target_mount_root] filtered root mount options: '${root_opts}'" + fi + if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then prun /usr/bin/mount -o "${root_opts}" "${rootdev}" "${mnt}" else @@ -491,8 +549,7 @@ tik_target_fstab_override_mountpoint() { local tmp tmp="$(prun /usr/bin/mktemp /tmp/tik-fstab.XXXXXXXXXX)" - # Replace the first non-comment entry with the same mountpoint, otherwise append. - /usr/bin/awk -v want="${mp}" -v rep="${newline}" ' + prun /usr/bin/awk -v want="${mp}" -v rep="${newline}" ' BEGIN { replaced=0 } { if ($0 ~ /^[[:space:]]*#/ || NF < 2) { print; next } @@ -543,7 +600,7 @@ EOF if [ -f "${fstab_tik}" ]; then log "[tik_target_fstab_assemble] found ${fstab_tik}, applying overrides into ${assembled}" - prun /usr/bin/cat "${fstab_tik}" | while IFS= read -r line; do + while IFS= read -r line; do line="${line#"${line%%[![:space:]]*}"}" [ -z "${line}" ] && continue [[ "${line}" = \#* ]] && continue @@ -564,7 +621,7 @@ EOF tik_target_fstab_override_mountpoint "${assembled}" "${mp}" "${line}" log "[tik_target_fstab_assemble] applied override/append for ${mp}" - done + done < <(prun /usr/bin/cat "${fstab_tik}") else log "[tik_target_fstab_assemble] no ${fstab_tik} found, not applying tik overrides" fi @@ -659,7 +716,15 @@ tik_target_mount_from_fstab() { else if [ -n "${opts}" ] && [ "${opts}" != "-" ]; then - prun /usr/bin/mount -t "${fstype}" -o "${opts}" "${spec}" "${target}" + # Ignore mount options that should not be applied during installation + local filtered_opts + filtered_opts="$(tik_target_mountopts_apply_filter "${opts}")" + + if [ -n "${filtered_opts}" ] && [ "${filtered_opts}" != "-" ]; then + prun /usr/bin/mount -t "${fstype}" -o "${filtered_opts}" "${spec}" "${target}" + else + prun /usr/bin/mount -t "${fstype}" "${spec}" "${target}" + fi else prun /usr/bin/mount -t "${fstype}" "${spec}" "${target}" fi From 0fcfc4cac09d99c7b9d008c898a93c54d0645a2f Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 17:58:13 +0100 Subject: [PATCH 10/25] Mount security and efivars into installed system --- usr/lib/tik/lib/tik-core | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 30276ca..d2e0f2b 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -749,6 +749,16 @@ ${TIK_MOUNTED_POINTS}" TIK_MOUNTED_POINTS="${mnt}/sys ${TIK_MOUNTED_POINTS}" + prun /usr/bin/mkdir -p "${mnt}/sys/kernel/security" + prun /usr/bin/mount -t securityfs securityfs "${mnt}/sys/kernel/security" + TIK_MOUNTED_POINTS="${mnt}/sys/kernel/security +${TIK_MOUNTED_POINTS}" + + prun /usr/bin/mkdir -p "${mnt}/sys/firmware/efi/efivars" + prun /usr/bin/mount -t efivarfs efivarfs "${mnt}/sys/firmware/efi/efivars" + TIK_MOUNTED_POINTS="${mnt}/sys/firmware/efi/efivars +${TIK_MOUNTED_POINTS}" + prun /usr/bin/mount --bind /dev "${mnt}/dev" TIK_MOUNTED_POINTS="${mnt}/dev ${TIK_MOUNTED_POINTS}" From a54e7de7e1066654dccc7d16f70221f9a41dafd2 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 18:20:36 +0100 Subject: [PATCH 11/25] Close progress UI explicitely after installation Also stick to aeon_root for now --- usr/lib/tik/lib/tik-core | 2 +- usr/lib/tik/lib/tik-functions | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index d2e0f2b..67ce7d4 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -398,7 +398,7 @@ tik_target_open_luks_if_present() { export TIK_CRYPT_PART # TODO: mapper name should be distro-specific - local mapper_name="tik_root" + local mapper_name="aeon_root" log "[tik_target_open_luks_if_present] opening encrypted partition ${TIK_CRYPT_PART} as ${mapper_name}" diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index a59d593..ec4018a 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -165,8 +165,16 @@ tik_prepare_progress_pipe() { tik_close_progress() { if [ -n "${TIK_PROGRESS_PID}" ]; then log "[tik_close_progress] stopping progress monitor pid=${TIK_PROGRESS_PID}" - kill "${TIK_PROGRESS_PID}" 2>/dev/null || true + + if [ -n "${TIK_PIPE}" ] && [ -p "${TIK_PIPE}" ]; then + echo "100" > "${TIK_PIPE}" 2>/dev/null || true + fi + + kill -TERM -- "-${TIK_PROGRESS_PID}" 2>/dev/null || true wait "${TIK_PROGRESS_PID}" 2>/dev/null || true + + kill -KILL -- "-${TIK_PROGRESS_PID}" 2>/dev/null || true + unset TIK_PROGRESS_PID fi From 1b337588f0cba3b95b5adf661db61a779a9f583c Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sat, 13 Dec 2025 19:10:55 +0100 Subject: [PATCH 12/25] Find ESP partition once --- usr/lib/tik/lib/tik-core | 26 ++++++++++++++++++-------- usr/lib/tik/modules/post/15-encrypt | 4 +++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 67ce7d4..37a7def 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -288,6 +288,7 @@ dump_image_repart_self() { set_boot_target() { local efipartnum + if [ "${debug}" == "1" ]; then log "[debug] Not setting EFI boot target" elif [ -n "${efi_already_set}" ]; then @@ -296,13 +297,9 @@ set_boot_target() { prun-opt /usr/sbin/efibootmgr -B -L "openSUSE Boot Manager" prun-opt /usr/sbin/efibootmgr -B -L "${TIK_OS_NAME} Boot Manager" prun /usr/sbin/efibootmgr -O - log "[set_boot_target] searching for ESP partition containing /EFI/systemd/shim.efi on ${TIK_INSTALL_DEVICE}" - probe_partitions "${TIK_INSTALL_DEVICE}" "vfat" "/EFI/systemd/shim.efi" - if [ -z "${probedpart}" ]; then - error "esp partition not found" - fi - efipartnum=$(lsblk "${probedpart}" -p -n -r -o PARTN) - log "[set_boot_target] found ESP on ${probedpart}, partition number ${efipartnum}" + + # TIK_ESP_PART is already set during mounting + efipartnum=$(lsblk "${TIK_ESP_PART}" -p -n -r -o PARTN) prun /usr/sbin/efibootmgr -c -L "${TIK_OS_NAME} Boot Manager" -d "${TIK_INSTALL_DEVICE}" -l "\EFI\systemd\shim.efi" -p ${efipartnum} log "[set_boot_target] $(prun /usr/sbin/efibootmgr)" efi_already_set=1 @@ -364,6 +361,9 @@ tik_target_mount_prepare() { export TIK_TARGET_FSTAB TIK_ASSEMBLED_FSTAB="" export TIK_ASSEMBLED_FSTAB + + TIK_ESP_PART="" + export TIK_ESP_PART } tik_target_find_root_partition() { @@ -799,6 +799,16 @@ tik_target_mount() { # mount everything described by assembled fstab tik_target_mount_from_fstab "${TIK_ROOT_MNT}" + # Probe ESP + log "[tik_target_mount] probing for ESP partition on ${TIK_INSTALL_DEVICE}" + probe_partitions "${TIK_INSTALL_DEVICE}" "vfat" + if [ -z "${probedpart}" ]; then + error "esp partition not found" + fi + TIK_ESP_PART="${probedpart}" + export TIK_ESP_PART + log "[tik_target_mount] found ESP ${TIK_ESP_PART}" + # After mounts are active, try to install the assembled fstab into the target tik_target_fstab_install_if_possible "${TIK_ROOT_MNT}" "${assembled}" || true @@ -829,7 +839,7 @@ tik_target_unmount() { # Close luks mapping if [ -n "${TIK_CRYPT_PART}" ]; then log "[tik_target_unmount] closing encrypted root for ${TIK_CRYPT_PART}" - prun-opt /usr/sbin/cryptsetup luksClose tik_root + prun-opt /usr/sbin/cryptsetup luksClose aeon_root fi TIK_MOUNTED_POINTS="" diff --git a/usr/lib/tik/modules/post/15-encrypt b/usr/lib/tik/modules/post/15-encrypt index f67170c..86951b1 100644 --- a/usr/lib/tik/modules/post/15-encrypt +++ b/usr/lib/tik/modules/post/15-encrypt @@ -52,9 +52,11 @@ display_recoveryKey() { configure_encryption() { tik_progress_step "Configuring encryption and boot" 20 + log "[configure_encryption] configuring cmdline, crypttab, PCR policy, fstab and populating ${TIK_ESP_PART}" - espUUID=$(lsblk -n -r -o UUID "${TIK_ESP_PART}") + espUUID="$(lsblk -n -r -o UUID "${TIK_ESP_PART}" | head -n1)" + [ -n "${espUUID}" ] || error "ESP UUID could not be determined for ${TIK_ESP_PART}" prun /usr/bin/gawk -v espUUID="${espUUID}" -i inplace '$2 == "/boot/efi" { $1 = "UUID="espUUID } { print $0 }' "${TIK_ROOT_MNT}/etc/fstab" # root=UUID= cmdline definition is a hard requirement of sdbootutil for updating predictions From 8b279185782710f6872ef1df2fa6050802dda9db Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sun, 14 Dec 2025 00:38:59 +0100 Subject: [PATCH 13/25] Remove Aeon easter egg --- usr/lib/tik/lib/tik-functions | 5 ----- usr/lib/tik/modules/pre/20-mig | 3 --- 2 files changed, 8 deletions(-) diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index ec4018a..f4b0446 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -130,11 +130,6 @@ probe_partitions() { # Fallback to unix device in order to fix issue with USB devices probedpart="${part}" log "[probe_partitions] Partition ${probedpart} found" - if grep -q 'PRETTY_NAME="openSUSE MicroOS"' ${probe_dir}/mnt/${filematch} && [ -f ${probe_dir}/mnt/usr/bin/gnome-shell ]; then - # Found legacy Aeon, activate easter egg - log "Legacy Aeon Install FOUND" - legacy_aeon=1 - fi fi prun-opt /usr/bin/umount ${probe_dir}/mnt fi diff --git a/usr/lib/tik/modules/pre/20-mig b/usr/lib/tik/modules/pre/20-mig index 3e63622..9a2ba2b 100644 --- a/usr/lib/tik/modules/pre/20-mig +++ b/usr/lib/tik/modules/pre/20-mig @@ -106,9 +106,6 @@ if [ -z "${skipbackup}" ]; then prun /usr/bin/rmdir ${mig_dir}/mnt # partition found, /home subvolume found, no known reason to not migrate, so ask the user if [ -z "${migrate}" ]; then - if [ "${legacy_aeon}" == 1 ]; then - d --info --width=300 --height=300 --icon=distributor-logo-Aeon-symbolic --no-wrap --title="Message from the Aeon Team" --text="We'd like to thank you for adopting openSUSE Aeon so early in it's development,\nbefore we fully understood what we were building or how we wanted it to look\n\nWe are sorry that you need to reinstall your system\n\nThank you so much for your support.\nWe hope you enjoy the new look openSUSE Aeon" - fi d_opt --question --no-wrap --title="Backup users from the existing install?" --text="These users will be restored to the new installation." migrateyn=$? if [ "${migrateyn}" == 0 ]; then From 8ccb21bbcf4d3153f2b6b1956c186b41e2cbc41a Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sun, 14 Dec 2025 00:50:21 +0100 Subject: [PATCH 14/25] Auto-detect system name The name of the to be installed system is now automatically discovered, unless explicitely provided thrugh the config. For self deploy mode, /etc/os-release of the current system is read and ID and PRETTY_NAME are used. For raw images, Tik will try to read /etc/os-release to get these information or derive them from the image name after a 1s timeout/failure. The name from the images is only used if it is the same for all images available for tik. This minimized aeon-specific code in tik-core and tik-functions --- usr/bin/tik | 3 + usr/lib/tik/lib/tik-core | 241 +++++++++++++++++++++++++++- usr/lib/tik/modules/post/15-encrypt | 3 +- 3 files changed, 243 insertions(+), 4 deletions(-) diff --git a/usr/bin/tik b/usr/bin/tik index 4b1b7ae..9bf02a5 100755 --- a/usr/bin/tik +++ b/usr/bin/tik @@ -45,6 +45,9 @@ if [ ! -d "${TIK_IMG_DIR}" ]; then error "${TIK_IMG_DIR} does not exist" fi +# Determine name of to-be-installed system +tik_detect_identity + cleanup() { retval=$? log "[STOP][${retval}] $0" diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 37a7def..efb6c0f 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -2,6 +2,234 @@ # SPDX-FileCopyrightText: Copyright 2025 SUSE LLC # SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens +tik_sanitize_os_id() { + # output: sanitized id (lowercase, underscores, letters only; digits removed) + local raw="$1" + local s + + s="$(echo "${raw}" | tr '[:upper:]' '[:lower:]')" + s="$(echo "${s}" | sed -E 's/[[:space:]-]+/_/g')" + s="$(echo "${s}" | sed -E 's/[0-9]+//g')" + s="$(echo "${s}" | sed -E 's/[^a-z_]+/_/g')" + s="$(echo "${s}" | sed -E 's/_+/_/g; s/^_+//; s/_+$//')" + + echo "${s}" +} + +tik_read_info_from_root() { + local osr="/etc/os-release" + [ -f "${osr}" ] || return 1 + + TIK_DETECTED_PRETTY_NAME="$(. "${osr}" 2>/dev/null; echo "${PRETTY_NAME}")" + TIK_DETECTED_ID="$(. "${osr}" 2>/dev/null; echo "${ID}")" + + [ -n "${TIK_DETECTED_PRETTY_NAME}" ] || return 1 + [ -n "${TIK_DETECTED_ID}" ] || return 1 + return 0 +} + +tik_read_info_from_image() { + local image_path="$1" + local mnt + local osr + + TIK_DETECTED_PRETTY_NAME="" + TIK_DETECTED_ID="" + + mnt="$(mktemp -d /tmp/tik-dissect.XXXXXXXXXX)" + osr="${mnt}/etc/os-release" + + # If this takes too long or fails, we just give up and fall back. + prun-opt /usr/bin/timeout 1s /usr/bin/systemd-dissect --quiet --mount "${image_path}" "${mnt}" + if [ "${retval}" != "0" ]; then + prun-opt /usr/bin/rmdir "${mnt}" + return 1 + fi + + if [ -f "${osr}" ]; then + TIK_DETECTED_PRETTY_NAME="$(. "${osr}" 2>/dev/null; echo "${PRETTY_NAME}")" + TIK_DETECTED_ID="$(. "${osr}" 2>/dev/null; echo "${ID}")" + fi + + prun-opt /usr/bin/umount "${mnt}" + prun-opt /usr/bin/rmdir "${mnt}" + + [ -n "${TIK_DETECTED_PRETTY_NAME}" ] || return 1 + [ -n "${TIK_DETECTED_ID}" ] || return 1 + return 0 +} + +tik_set_identity() { + local pretty="$1" + local raw_id="$2" + local sid + + sid="$(tik_sanitize_os_id "${raw_id}")" + if [ -z "${sid}" ]; then + sid="cr" + fi + + TIK_OS_NAME="${pretty}" + export TIK_OS_NAME + + TIK_OS_ID="${sid}" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="${TIK_OS_ID}_root" + export TIK_CRYPT_MAPPER +} + +tik_set_identity_unknown() { + TIK_OS_NAME="UNKNOWN SYSTEM" + export TIK_OS_NAME + + TIK_OS_ID="cr" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="cr_root" + export TIK_CRYPT_MAPPER +} + +tik_set_identity_for_selfdeploy() { + if tik_read_info_from_root; then + tik_set_identity "${TIK_DETECTED_PRETTY_NAME}" "${TIK_DETECTED_ID}" + return 0 + fi + tik_set_identity_unknown + return 0 +} + +tik_set_identity_for_image() { + local img="$1" + local base + local token + + if tik_read_info_from_image "${TIK_IMG_DIR}/${img}"; then + tik_set_identity "${TIK_DETECTED_PRETTY_NAME}" "${TIK_DETECTED_ID}" + return 0 + fi + + base="$(basename "${img}")" + base="${base%.raw}" + base="${base%.xz}" + + token="$(echo "${base}" | sed -nE 's/^tik-osimage-([^-\.\ ]+).*/\1/p')" + if [ -n "${token}" ]; then + TIK_OS_NAME="${token}" + export TIK_OS_NAME + + TIK_OS_ID="$(tik_sanitize_os_id "${token}")" + [ -n "${TIK_OS_ID}" ] || TIK_OS_ID="cr" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="${TIK_OS_ID}_root" + export TIK_CRYPT_MAPPER + return 0 + fi + + tik_set_identity_unknown + return 0 +} + +tik_read_info_from_images() { + # If multiple images are available and all have the same ID & PRETTY_NAME, use that. + # Returns 0 if identity was set, otherwise 1. + local img + local first_pretty="" + local first_id="" + local ok=1 + + for img in "$@"; do + if ! tik_read_info_from_image "${TIK_IMG_DIR}/${img}"; then + ok=0 + break + fi + if [ -z "${first_pretty}" ]; then + first_pretty="${TIK_DETECTED_PRETTY_NAME}" + first_id="${TIK_DETECTED_ID}" + else + if [ "${TIK_DETECTED_PRETTY_NAME}" != "${first_pretty}" ] || [ "${TIK_DETECTED_ID}" != "${first_id}" ]; then + ok=0 + break + fi + fi + done + + if [ "${ok}" = "1" ] && [ -n "${first_pretty}" ] && [ -n "${first_id}" ]; then + tik_set_identity "${first_pretty}" "${first_id}" + return 0 + fi + return 1 +} + +tik_detect_identity() { + # establish TIK_OS_NAME + TIK_CRYPT_MAPPER. + # Priority: + # 0. Config provided => keep it + # 1. Selfdeploy (no images found) => read /etc/os-release + # 2. One image => try read os-release from image; else name fallback + # 3. Multiple images => if all match => use that; else fallback to UNKNOWN now + + # If config already provided identity, prefer it + if [ -n "${TIK_OS_NAME}" ]; then + export TIK_OS_NAME + + if [ -n "${TIK_CRYPT_MAPPER}" ]; then + export TIK_CRYPT_MAPPER + return 0 + fi + + # Only OS name provided -> derive mapper from sanitized name + local sid + sid="$(tik_sanitize_os_id "${TIK_OS_NAME}")" + [ -n "${sid}" ] || sid="cr" + + TIK_OS_ID="${sid}" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="${sid}_root" + export TIK_CRYPT_MAPPER + return 0 + fi + + local imgs="" + local file_type + local img_meta + local img_filename + local count=0 + + for file_type in '*.raw.xz' '*.raw'; do + for img_meta in $(cd "${TIK_IMG_DIR}" && (stat --printf="%n\t%s\n" ${file_type} 2>/dev/null | tr ' ' ":")); do + img_filename="$(echo "${img_meta}" | cut -f1 -d:)" + imgs="${imgs} ${img_filename}" + count=$((count + 1)) + done + done + + if [ -n "${TIK_INSTALL_IMAGE}" ]; then + # If config pins an image, treat as single-image identity source. + tik_set_identity_for_image "${TIK_INSTALL_IMAGE}" + return 0 + fi + + if [ "${count}" -eq 0 ]; then + tik_set_identity_for_selfdeploy + return 0 + fi + + if [ "${count}" -eq 1 ]; then + tik_set_identity_for_image ${imgs} + return 0 + fi + + if tik_read_info_from_images ${imgs}; then + return 0 + fi + + tik_set_identity_unknown + return 0 +} + get_disk() { tik_volid="TIKINSTALL" local disk_id="by-id" @@ -174,6 +402,7 @@ get_img() { if [ -z "${list_items}" ]; then TIK_INSTALL_IMAGE='TIK_SELFDEPLOY' + return 0 fi img_list=${list_items} @@ -194,6 +423,13 @@ get_img() { TIK_INSTALL_IMAGE="${result}" fi fi + + # If identity was UNKNOWN due to multi-image mismatch, finalize it now from the selected image. + if [ "${TIK_INSTALL_IMAGE}" != "TIK_SELFDEPLOY" ]; then + if [ "${TIK_OS_NAME}" = "UNKNOWN SYSTEM" ] || [ -z "${TIK_CRYPT_MAPPER}" ]; then + tik_set_identity_for_image "${TIK_INSTALL_IMAGE}" + fi + fi } reread_partitiontable() { @@ -397,8 +633,7 @@ tik_target_open_luks_if_present() { TIK_CRYPT_PART="${probedpart}" export TIK_CRYPT_PART - # TODO: mapper name should be distro-specific - local mapper_name="aeon_root" + local mapper_name="${TIK_CRYPT_MAPPER:-cr_root}" log "[tik_target_open_luks_if_present] opening encrypted partition ${TIK_CRYPT_PART} as ${mapper_name}" @@ -839,7 +1074,7 @@ tik_target_unmount() { # Close luks mapping if [ -n "${TIK_CRYPT_PART}" ]; then log "[tik_target_unmount] closing encrypted root for ${TIK_CRYPT_PART}" - prun-opt /usr/sbin/cryptsetup luksClose aeon_root + prun-opt /usr/sbin/cryptsetup luksClose "${TIK_CRYPT_MAPPER:-cr_root}" fi TIK_MOUNTED_POINTS="" diff --git a/usr/lib/tik/modules/post/15-encrypt b/usr/lib/tik/modules/post/15-encrypt index 86951b1..83edb5f 100644 --- a/usr/lib/tik/modules/post/15-encrypt +++ b/usr/lib/tik/modules/post/15-encrypt @@ -65,7 +65,8 @@ configure_encryption() { # /etc/crypttab is a hard requirement of sdbootutil for updating predictions cryptUUID=$(lsblk -n -r -d -o UUID "${TIK_CRYPT_PART}") - echo "aeon_root UUID=${cryptUUID} none x-initrd.attach" | prun tee "${TIK_ROOT_MNT}/etc/crypttab" + cryptName="${TIK_CRYPT_MAPPER:-cr_root}" + echo "${cryptName} UUID=${cryptUUID} none x-initrd.attach" | prun tee "${TIK_ROOT_MNT}/etc/crypttab" # FIXME: Dracut gets confused by previous installations on occasion with the default config, override the problematic option temporarily echo "hostonly_cmdline=\"no\"" | prun tee "${TIK_ROOT_MNT}/etc/dracut.conf.d/99-tik.conf" From 052a2891accde349ba127a1d2467f0a4a633a73e Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sun, 14 Dec 2025 01:23:23 +0100 Subject: [PATCH 15/25] Fix issues I accidentally removed the mount_etc_for_root function earlier, but it's still required by the mig modules. So here it is. Also, XDG_SESSION is never used, so I assume XDG_SESSION_TYPE was meant instead. --- usr/bin/tik | 2 +- usr/lib/tik/lib/tik-functions | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/usr/bin/tik b/usr/bin/tik index 9bf02a5..489203b 100755 --- a/usr/bin/tik +++ b/usr/bin/tik @@ -29,7 +29,7 @@ if [[ $1 == "--debug" ]]; then fi # Check if graphical display is available -XDG_SESSION="${XDG_SESSION_TYPE:=unspecified}" +XDG_SESSION_TYPE="${XDG_SESSION_TYPE:=unspecified}" if [ "${XDG_SESSION_TYPE}" = "wayland" ] || [ "${XDG_SESSION_TYPE}" = "x11" ] ; then gui=true else diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index f4b0446..ff8367a 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -137,6 +137,21 @@ probe_partitions() { prun /usr/bin/rmdir ${probe_dir}/mnt } +mount_etc_for_root() { + local root=$1 + + if grep -qF 'overlay /etc' "${root}/etc/fstab" ; then + local etcmountcmd + etcmountcmd=$(grep "overlay /etc" "${root}/etc/fstab" \ + | sed "s#/sysroot/#${root}/#g" \ + | sed "s#/work-etc.*#/work-etc ${root}/etc#g" \ + | sed 's#overlay /etc overlay#/usr/bin/mount -t overlay overlay -o#') + eval prun "${etcmountcmd}" + else + prun /usr/bin/mount -o bind "${root}/etc" "${root}/etc" + fi +} + tik_monitor_progress() { if [ -z "${TIK_PIPE}" ]; then return 0 From 6590a7a019c2173b22ad3e26f134361ee212d06e Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Mon, 15 Dec 2025 02:19:24 +0100 Subject: [PATCH 16/25] Make mount/unmount functions more generic This allows further reusability, so the mount/unmount functions can be used to mount/unmount the old system in the pre/20-mig module. --- usr/bin/tik | 5 +- usr/lib/tik/lib/tik-core | 751 +-------------------------- usr/lib/tik/lib/tik-core-helper | 356 +++++++++++++ usr/lib/tik/lib/tik-functions | 289 +++++++---- usr/lib/tik/lib/tik-functions-helper | 505 ++++++++++++++++++ usr/lib/tik/modules/post/10-sicu | 3 + usr/lib/tik/modules/post/15-encrypt | 2 + usr/lib/tik/modules/post/20-mig | 88 ++-- usr/lib/tik/modules/pre/10-welcome | 2 +- usr/lib/tik/modules/pre/20-mig | 153 ++---- 10 files changed, 1165 insertions(+), 989 deletions(-) create mode 100644 usr/lib/tik/lib/tik-core-helper create mode 100644 usr/lib/tik/lib/tik-functions-helper diff --git a/usr/bin/tik b/usr/bin/tik index 489203b..e2924ac 100755 --- a/usr/bin/tik +++ b/usr/bin/tik @@ -75,11 +75,10 @@ get_img dump_image "${TIK_INSTALL_IMAGE}" "${TIK_INSTALL_DEVICE}" reread_partitiontable -tik_target_mount - # Run post modules tik_init_phase_modules "post" load_modules "post" load_modules "post" "custom" -tik_target_unmount +wipe_keyfile +set_boot_target \ No newline at end of file diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index efb6c0f..8a772f2 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -2,165 +2,8 @@ # SPDX-FileCopyrightText: Copyright 2025 SUSE LLC # SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens -tik_sanitize_os_id() { - # output: sanitized id (lowercase, underscores, letters only; digits removed) - local raw="$1" - local s - - s="$(echo "${raw}" | tr '[:upper:]' '[:lower:]')" - s="$(echo "${s}" | sed -E 's/[[:space:]-]+/_/g')" - s="$(echo "${s}" | sed -E 's/[0-9]+//g')" - s="$(echo "${s}" | sed -E 's/[^a-z_]+/_/g')" - s="$(echo "${s}" | sed -E 's/_+/_/g; s/^_+//; s/_+$//')" - - echo "${s}" -} - -tik_read_info_from_root() { - local osr="/etc/os-release" - [ -f "${osr}" ] || return 1 - - TIK_DETECTED_PRETTY_NAME="$(. "${osr}" 2>/dev/null; echo "${PRETTY_NAME}")" - TIK_DETECTED_ID="$(. "${osr}" 2>/dev/null; echo "${ID}")" - - [ -n "${TIK_DETECTED_PRETTY_NAME}" ] || return 1 - [ -n "${TIK_DETECTED_ID}" ] || return 1 - return 0 -} - -tik_read_info_from_image() { - local image_path="$1" - local mnt - local osr - - TIK_DETECTED_PRETTY_NAME="" - TIK_DETECTED_ID="" - - mnt="$(mktemp -d /tmp/tik-dissect.XXXXXXXXXX)" - osr="${mnt}/etc/os-release" - - # If this takes too long or fails, we just give up and fall back. - prun-opt /usr/bin/timeout 1s /usr/bin/systemd-dissect --quiet --mount "${image_path}" "${mnt}" - if [ "${retval}" != "0" ]; then - prun-opt /usr/bin/rmdir "${mnt}" - return 1 - fi - - if [ -f "${osr}" ]; then - TIK_DETECTED_PRETTY_NAME="$(. "${osr}" 2>/dev/null; echo "${PRETTY_NAME}")" - TIK_DETECTED_ID="$(. "${osr}" 2>/dev/null; echo "${ID}")" - fi - - prun-opt /usr/bin/umount "${mnt}" - prun-opt /usr/bin/rmdir "${mnt}" - - [ -n "${TIK_DETECTED_PRETTY_NAME}" ] || return 1 - [ -n "${TIK_DETECTED_ID}" ] || return 1 - return 0 -} - -tik_set_identity() { - local pretty="$1" - local raw_id="$2" - local sid - - sid="$(tik_sanitize_os_id "${raw_id}")" - if [ -z "${sid}" ]; then - sid="cr" - fi - - TIK_OS_NAME="${pretty}" - export TIK_OS_NAME - - TIK_OS_ID="${sid}" - export TIK_OS_ID - - TIK_CRYPT_MAPPER="${TIK_OS_ID}_root" - export TIK_CRYPT_MAPPER -} - -tik_set_identity_unknown() { - TIK_OS_NAME="UNKNOWN SYSTEM" - export TIK_OS_NAME - - TIK_OS_ID="cr" - export TIK_OS_ID - - TIK_CRYPT_MAPPER="cr_root" - export TIK_CRYPT_MAPPER -} - -tik_set_identity_for_selfdeploy() { - if tik_read_info_from_root; then - tik_set_identity "${TIK_DETECTED_PRETTY_NAME}" "${TIK_DETECTED_ID}" - return 0 - fi - tik_set_identity_unknown - return 0 -} - -tik_set_identity_for_image() { - local img="$1" - local base - local token - - if tik_read_info_from_image "${TIK_IMG_DIR}/${img}"; then - tik_set_identity "${TIK_DETECTED_PRETTY_NAME}" "${TIK_DETECTED_ID}" - return 0 - fi - - base="$(basename "${img}")" - base="${base%.raw}" - base="${base%.xz}" - - token="$(echo "${base}" | sed -nE 's/^tik-osimage-([^-\.\ ]+).*/\1/p')" - if [ -n "${token}" ]; then - TIK_OS_NAME="${token}" - export TIK_OS_NAME - - TIK_OS_ID="$(tik_sanitize_os_id "${token}")" - [ -n "${TIK_OS_ID}" ] || TIK_OS_ID="cr" - export TIK_OS_ID - - TIK_CRYPT_MAPPER="${TIK_OS_ID}_root" - export TIK_CRYPT_MAPPER - return 0 - fi - - tik_set_identity_unknown - return 0 -} - -tik_read_info_from_images() { - # If multiple images are available and all have the same ID & PRETTY_NAME, use that. - # Returns 0 if identity was set, otherwise 1. - local img - local first_pretty="" - local first_id="" - local ok=1 - - for img in "$@"; do - if ! tik_read_info_from_image "${TIK_IMG_DIR}/${img}"; then - ok=0 - break - fi - if [ -z "${first_pretty}" ]; then - first_pretty="${TIK_DETECTED_PRETTY_NAME}" - first_id="${TIK_DETECTED_ID}" - else - if [ "${TIK_DETECTED_PRETTY_NAME}" != "${first_pretty}" ] || [ "${TIK_DETECTED_ID}" != "${first_id}" ]; then - ok=0 - break - fi - fi - done - - if [ "${ok}" = "1" ] && [ -n "${first_pretty}" ] && [ -n "${first_id}" ]; then - tik_set_identity "${first_pretty}" "${first_id}" - return 0 - fi - return 1 -} +# Helper functions for tik-core +. ${tik_dir}/lib/tik-core-helper tik_detect_identity() { # establish TIK_OS_NAME + TIK_CRYPT_MAPPER. @@ -172,23 +15,14 @@ tik_detect_identity() { # If config already provided identity, prefer it if [ -n "${TIK_OS_NAME}" ]; then - export TIK_OS_NAME - if [ -n "${TIK_CRYPT_MAPPER}" ]; then + export TIK_OS_NAME export TIK_CRYPT_MAPPER return 0 fi - # Only OS name provided -> derive mapper from sanitized name - local sid - sid="$(tik_sanitize_os_id "${TIK_OS_NAME}")" - [ -n "${sid}" ] || sid="cr" - - TIK_OS_ID="${sid}" - export TIK_OS_ID - - TIK_CRYPT_MAPPER="${sid}_root" - export TIK_CRYPT_MAPPER + # Only OS name provided -> derive ID + mapper from sanitized name + tik_set_identity_from_name "${TIK_OS_NAME}" return 0 fi @@ -329,7 +163,7 @@ get_disk() { device_size=$(echo "${device_meta}" | cut -f2 -d:) list_items="$(basename ${device}) ${device_size}" disk_list="$(basename ${device}) ${device_size}" - message="tik installation device set to to: ${device}" + message="[get_disk] tik installation device set to to: ${device}" log "${message}" fi @@ -396,7 +230,7 @@ get_img() { img_filename="$(echo "${img_meta}" | cut -f1 -d:)" img_size="$(echo "${img_meta}" | cut -f2 -d:)" list_items="${list_items} ${img_filename} ${img_size}" - message="tik installation image set to to: ${img}" + message="[get_disk] tik installation image set to to: ${img}" log "${message}" fi @@ -439,25 +273,6 @@ reread_partitiontable() { sleep 3 } -create_keyfile() { - tik_keyfile=$(prun mktemp /tmp/tik.XXXXXXXXXX) - log "[create_keyfile] Creating keyfile ${tik_keyfile}" - /usr/bin/base64 -w 0 /dev/urandom | head -c 1k | prun tee "${tik_keyfile}" - prun /usr/bin/chmod 400 "${tik_keyfile}" - tik_keyid=$(prun cat "${tik_keyfile}" | prun keyctl padd user cryptenroll @u) -} - -wipe_keyfile() { - log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}" - probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" - if [ -n "${probedpart}" ]; then - prun /usr/bin/systemd-cryptenroll --unlock-key-file="${tik_keyfile}" --wipe-slot=0 "${probedpart}" - fi - prun /usr/bin/rm "${tik_keyfile}" - prun-opt keyctl revoke "${tik_keyid}" - prun-opt keyctl reap -} - dump_image() { local image_source_files=$1 local image_target=$2 @@ -479,49 +294,6 @@ dump_image() { esac } -dump_image_dd() { - local image_source_files=$1 - local image_target=$2 - log "[dump_image_dd] deploying ${TIK_IMG_DIR}/${image_source_files}" - (xzcat "${TIK_IMG_DIR}/${image_source_files}" | pv -f -F "# %b copied in %t %r" | prun /usr/bin/dd of="${image_target}" bs=64k) 2>&1 | d --progress --title="Installing ${TIK_OS_NAME}" --pulsate --auto-close --no-cancel --width=400 - prun /usr/bin/sync | d --progress --title="Syncing" --pulsate --auto-close --no-cancel --width=400 -} - -dump_image_repart_image() { - local image_source_files=$1 - local image_target=$2 - local success=0 - local max_attempts=5 - local attempt_num=1 - - create_keyfile - log "[dump_image_repart_image] deploying ${TIK_IMG_DIR}/${image_source_files}" - - while [ ${success} = 0 ] && [ ${attempt_num} -lt ${max_attempts} ]; do - prun-opt systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --image="${TIK_IMG_DIR}/${image_source_files}" --image-policy=root=unprotected "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) - if [ ${retval} -eq 0 ]; then - success=1 - else - log "[dump_image_repart_image] systemd-repart attempt ${attempt_num} failed. Trying again..." - sleep 1 - attempt_num=$(( attempt_num + 1 )) - fi - done - if [ ${success} = 1 ]; then - log "[dump_image_repart_image] systemd-repart succeeded after ${attempt_num} attempts" - else - error "systemd-repart failed" - fi -} - -dump_image_repart_self() { - local image_target=$1 - create_keyfile - prun-opt rm -rf /etc/fstab.repart - log "[dump_image_repart_self] self-deploying" - prun systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --generate-fstab=/etc/fstab.repart "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) -} - set_boot_target() { local efipartnum @@ -569,517 +341,30 @@ tik_init_phase_modules() { log "[tik_init_phase_modules] phase=${phase} total_modules=${TIK_TOTAL_MODULES}" } -tik_is_opt_set() { - echo ",$1," | grep -q ",$2," -} - -tik_rewrite_overlay_opts_for_target() { - local opts="$1" - local root="$2" - - opts="$(echo "$opts" | sed -E "s#lowerdir=/#lowerdir=${root}/#g; s#upperdir=/#upperdir=${root}/#g; s#workdir=/#workdir=${root}/#g")" - opts="$(echo "$opts" | sed -E "s#lowerdir=${root}//#lowerdir=${root}/#g; s#upperdir=${root}//#upperdir=${root}/#g; s#workdir=${root}//#workdir=${root}/#g")" - opts="$(echo "$opts" | sed -E "s#lowerdir=(${root}/[^,]*):/#lowerdir=\1:${root}/#g")" - - echo "$opts" -} - -tik_target_mount_prepare() { - TIK_ROOT_MNT="${TIK_ROOT_MNT:=/var/lib/tik/root}" - export TIK_ROOT_MNT - prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}" - - TIK_MOUNTED_POINTS="" - TIK_MOUNTED_TARGET=0 - export TIK_MOUNTED_TARGET - - TIK_TARGET_FSTAB="" - export TIK_TARGET_FSTAB - TIK_ASSEMBLED_FSTAB="" - export TIK_ASSEMBLED_FSTAB - - TIK_ESP_PART="" - export TIK_ESP_PART -} - -tik_target_find_root_partition() { - local fs - for fs in btrfs ext4 xfs f2fs; do - probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/fstab" - if [ -n "${probedpart}" ]; then - echo "${probedpart}" - return 0 - fi - probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/os-release" - if [ -n "${probedpart}" ]; then - echo "${probedpart}" - return 0 - fi - probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/usr/lib/os-release" - if [ -n "${probedpart}" ]; then - echo "${probedpart}" - return 0 - fi - done - return 1 -} - -tik_target_open_luks_if_present() { - TIK_CRYPT_PART="" - TIK_ROOT_DEV="" - - probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" - if [ -n "${probedpart}" ]; then - TIK_CRYPT_PART="${probedpart}" - export TIK_CRYPT_PART - - local mapper_name="${TIK_CRYPT_MAPPER:-cr_root}" - - log "[tik_target_open_luks_if_present] opening encrypted partition ${TIK_CRYPT_PART} as ${mapper_name}" - - if [ -z "${tik_keyfile}" ] || [ ! -f "${tik_keyfile}" ]; then - error "Encrypted root found but tik_keyfile is missing" - fi - - prun /usr/sbin/cryptsetup luksOpen --key-file="${tik_keyfile}" "${TIK_CRYPT_PART}" "${mapper_name}" - TIK_ROOT_DEV="/dev/mapper/${mapper_name}" - export TIK_ROOT_DEV - return 0 - fi - - export TIK_CRYPT_PART - return 1 -} - -tik_target_mountopts_filter() { - local opts="$1" - local ignore_list="$2" - - if [ -z "${opts}" ] || [ "${opts}" = "-" ]; then - echo "${opts}" - return 0 - fi - - /usr/bin/awk -v opts="${opts}" -v ignore="${ignore_list}" ' - BEGIN { - n=split(ignore, ig, /,/) - for (i=1;i<=n;i++) { - gsub(/^[[:space:]]+|[[:space:]]+$/, "", ig[i]) - if (ig[i] != "") drop[ig[i]]=1 - } - - m=split(opts, a, /,/) - out="" - for (j=1;j<=m;j++) { - o=a[j] - gsub(/^[[:space:]]+|[[:space:]]+$/, "", o) - if (o=="") continue - - # exact match (e.g. ro) - if (drop[o]) continue - - # key=value match (e.g. ro=vfs) where ignore has 'ro' or 'ro=' or 'ro=vfs' - split(o, kv, /=/) - k=kv[1] - if (drop[k]) continue - if (drop[k"="]) continue - if (drop[o]) continue - - if (out=="") out=o - else out=out","o - } - if (out=="") out="-" - print out - } - ' -} - -tik_target_mountopts_ignore_list() { - echo "${TIK_MOUNTOPTS_IGNORE_LIST:-ro,ro=vfs}" -} - -tik_target_mountopts_apply_filter() { - local opts="$1" - local ignore_opts - ignore_opts="$(tik_target_mountopts_ignore_list)" - tik_target_mountopts_filter "${opts}" "${ignore_opts}" -} - -tik_target_mount_root() { - local rootdev=$1 - local mnt=$2 - - local fstype - fstype="$(lsblk -n -r -o FSTYPE "${rootdev}" 2>/dev/null | head -n1)" - - # Temporarily mount the root to determine the real mount options - local tmp_mnt - local root_opts - tmp_mnt="$(prun /usr/bin/mktemp -d /tmp/tik-rootprobe.XXXXXXXXXX)" - log "[tik_target_mount_root] probing mount options for / by temporarily mounting ${rootdev} on ${tmp_mnt}" - - prun /usr/bin/mount -o ro "${rootdev}" "${tmp_mnt}" - - if [ -f "${tmp_mnt}/etc/fstab" ]; then - root_opts="$(prun /usr/bin/awk ' - $0 ~ /^[[:space:]]*#/ { next } - NF < 4 { next } - $2 == "/" { print $4; exit } - ' "${tmp_mnt}/etc/fstab")" - log "[tik_target_mount_root] probed root mount options: '${root_opts}'" - else - log "[tik_target_mount_root] no ${tmp_mnt}/etc/fstab found while probing, falling back to default mount options" - root_opts="" - fi - - prun-opt /usr/bin/umount "${tmp_mnt}" - prun-opt /usr/bin/rmdir "${tmp_mnt}" - - if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then - root_opts="$(tik_target_mountopts_apply_filter "${root_opts}")" - log "[tik_target_mount_root] filtered root mount options: '${root_opts}'" - fi - - if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then - prun /usr/bin/mount -o "${root_opts}" "${rootdev}" "${mnt}" - else - prun /usr/bin/mount "${rootdev}" "${mnt}" - fi - - # track for unmount - TIK_MOUNTED_POINTS="${mnt} -${TIK_MOUNTED_POINTS}" -} - -tik_target_fstab_is_safe_extra_mount() { - local spec=$1 - local mp=$2 - local fstype=$3 - - # Require absolute mountpoints - if [ -z "${mp}" ] || [ "${mp#"/"}" = "${mp}" ]; then - return 1 - fi - - # Basic path traversal guard - if echo "${mp}" | grep -qE '(^|/)\.\.($|/)'; then - return 1 - fi - - # Do not allow overriding the root mount or pseudo filesystems - case "${mp}" in - "/"|"/proc"|"/sys"|"/dev"|"/run"|"/tmp") - return 1 - ;; - esac - - return 0 -} - -tik_target_fstab_override_mountpoint() { - local fstab=$1 - local mp=$2 - local newline=$3 - - local tmp - tmp="$(prun /usr/bin/mktemp /tmp/tik-fstab.XXXXXXXXXX)" - - prun /usr/bin/awk -v want="${mp}" -v rep="${newline}" ' - BEGIN { replaced=0 } - { - if ($0 ~ /^[[:space:]]*#/ || NF < 2) { print; next } - if (!replaced && $2 == want) { print rep; replaced=1; next } - print - } - END { if (!replaced) print rep } - ' "${fstab}" | prun /usr/bin/tee "${tmp}" >/dev/null - - prun /usr/bin/cp -a "${tmp}" "${fstab}" - prun /usr/bin/rm -f "${tmp}" -} - -tik_target_fstab_can_write_etc() { - local mnt=$1 - local probe="${mnt}/etc/.tik-write-probe.$$" - prun-opt /usr/bin/sh -c "touch '${probe}' && rm -f '${probe}'" - [ "${retval}" = "0" ] -} - -tik_target_fstab_assemble() { - local mnt=$1 - - local fstab="${mnt}/etc/fstab" - local fstab_repart="${mnt}/etc/fstab.repart" - local fstab_tik="${mnt}/etc/fstab.tik" - - local assembled - assembled="$(prun /usr/bin/mktemp /tmp/tik-assembled-fstab.XXXXXXXXXX)" - TIK_ASSEMBLED_FSTAB="${assembled}" - export TIK_ASSEMBLED_FSTAB - - # Prefer systemd-repart generated fstab if present, otherwise use existing fstab. - if [ -f "${fstab_repart}" ]; then - log "[tik_target_fstab_assemble] using ${fstab_repart} as base fstab" - prun /usr/bin/cp -a "${fstab_repart}" "${assembled}" - elif [ -f "${fstab}" ]; then - log "[tik_target_fstab_assemble] using ${fstab} as base fstab" - prun /usr/bin/cp -a "${fstab}" "${assembled}" - else - log "[tik_target_fstab_assemble] no base fstab found, leaving assembled fstab empty at ${assembled}" - prun /usr/bin/tee "${assembled}" >/dev/null </dev/null; echo "${PRETTY_NAME}")" + TIK_DETECTED_ID="$(. "${osr}" 2>/dev/null; echo "${ID}")" + + [ -n "${TIK_DETECTED_PRETTY_NAME}" ] || return 1 + [ -n "${TIK_DETECTED_ID}" ] || return 1 + return 0 +} + +tik_read_info_from_image() { + local image_path="$1" + local mnt + local osr + + TIK_DETECTED_PRETTY_NAME="" + TIK_DETECTED_ID="" + + mnt="$(mktemp -d /tmp/tik-dissect.XXXXXXXXXX)" + osr="${mnt}/etc/os-release" + + # If this takes too long or fails, we just give up and fall back. + prun-opt /usr/bin/timeout 1s /usr/bin/systemd-dissect --quiet --mount "${image_path}" "${mnt}" + if [ "${retval}" != "0" ]; then + prun-opt /usr/bin/rmdir "${mnt}" + return 1 + fi + + if [ -f "${osr}" ]; then + TIK_DETECTED_PRETTY_NAME="$(. "${osr}" 2>/dev/null; echo "${PRETTY_NAME}")" + TIK_DETECTED_ID="$(. "${osr}" 2>/dev/null; echo "${ID}")" + fi + + prun-opt /usr/bin/umount "${mnt}" + prun-opt /usr/bin/rmdir "${mnt}" + + [ -n "${TIK_DETECTED_PRETTY_NAME}" ] || return 1 + [ -n "${TIK_DETECTED_ID}" ] || return 1 + return 0 +} + +tik_set_identity() { + local pretty="$1" + local raw_id="$2" + local sid + + sid="$(tik_sanitize_os_id "${raw_id}")" + if [ -z "${sid}" ]; then + sid="cr" + fi + + TIK_OS_NAME="${pretty}" + export TIK_OS_NAME + + TIK_OS_ID="${sid}" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="${TIK_OS_ID}_root" + export TIK_CRYPT_MAPPER +} + +tik_set_identity_unknown() { + TIK_OS_NAME="UNKNOWN SYSTEM" + export TIK_OS_NAME + + TIK_OS_ID="cr" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="cr_root" + export TIK_CRYPT_MAPPER +} + +tik_set_identity_for_selfdeploy() { + if tik_read_info_from_root; then + tik_set_identity "${TIK_DETECTED_PRETTY_NAME}" "${TIK_DETECTED_ID}" + return 0 + fi + tik_set_identity_unknown + return 0 +} + +tik_set_identity_for_image() { + local img="$1" + local base + local token + + if tik_read_info_from_image "${TIK_IMG_DIR}/${img}"; then + tik_set_identity "${TIK_DETECTED_PRETTY_NAME}" "${TIK_DETECTED_ID}" + return 0 + fi + + base="$(basename "${img}")" + base="${base%.raw}" + base="${base%.xz}" + + token="$(echo "${base}" | sed -nE 's/^tik-osimage-([^-\.\ ]+).*/\1/p')" + if [ -n "${token}" ]; then + TIK_OS_NAME="${token}" + export TIK_OS_NAME + + TIK_OS_ID="$(tik_sanitize_os_id "${token}")" + [ -n "${TIK_OS_ID}" ] || TIK_OS_ID="cr" + export TIK_OS_ID + + TIK_CRYPT_MAPPER="${TIK_OS_ID}_root" + export TIK_CRYPT_MAPPER + return 0 + fi + + tik_set_identity_unknown + return 0 +} + +tik_read_info_from_images() { + # If multiple images are available and all have the same ID & PRETTY_NAME, use that. + # Returns 0 if identity was set, otherwise 1. + local img + local first_pretty="" + local first_id="" + local ok=1 + + for img in "$@"; do + if ! tik_read_info_from_image "${TIK_IMG_DIR}/${img}"; then + ok=0 + break + fi + if [ -z "${first_pretty}" ]; then + first_pretty="${TIK_DETECTED_PRETTY_NAME}" + first_id="${TIK_DETECTED_ID}" + else + if [ "${TIK_DETECTED_PRETTY_NAME}" != "${first_pretty}" ] || [ "${TIK_DETECTED_ID}" != "${first_id}" ]; then + ok=0 + break + fi + fi + done + + if [ "${ok}" = "1" ] && [ -n "${first_pretty}" ] && [ -n "${first_id}" ]; then + tik_set_identity "${first_pretty}" "${first_id}" + return 0 + fi + return 1 +} + +get_persistent_device_from_unix_node() { + local unix_device=$1 + local schema=$2 + local node + local persistent_name + node=$(basename "${unix_device}") + for persistent_name in /dev/disk/"${schema}"/*; do + if [ "$(basename "$(readlink "${persistent_name}")")" = "${node}" ]; then + if [[ ${persistent_name} =~ ^/dev/disk/"${schema}"/nvme-eui ]]; then + # Filter out nvme-eui nodes as they are not descriptive to the user + continue + fi + echo "${persistent_name}" + return + fi + done + warn "Could not find ${schema} representation of ${node}. Using original device ${unix_device}" + echo "${unix_device}" +} + +probe_partitions() { + local probe_dir=/var/lib/tik/probe + local filesystem_type=$2 + local filematch=$3 + local device=$1 + local mountops + local part + + if [[ "${filesystem_type}" == "btrfs" ]]; then + mountops="-o compress=zstd:1" + fi + + prun /usr/bin/mkdir -p ${probe_dir}/mnt + probedpart="" + + for part in $(lsblk ${device} -p -n -r -o ID-LINK,FSTYPE | tr -s ' ' ";" | grep ";${filesystem_type}" | cut -d\; -f1); do + if [ -z "${filematch}" ]; then + log "[probe_partitions] no file match required" + # Fallback to unix device in order to fix issue with USB devices + probedpart="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" + log "[probe_partitions] Partition ${probedpart} found" + else # Check if ${filematch} exists + # Fallback to unix device in order to fix issue with USB devices + part="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" + prun /usr/bin/mount ${mountops} ${part} "${probe_dir}/mnt" + if [ -f ${probe_dir}/mnt/${filematch} ]; then + log "[probe_partitions] File ${filematch} found" + # Fallback to unix device in order to fix issue with USB devices + probedpart="${part}" + log "[probe_partitions] Partition ${probedpart} found" + fi + prun-opt /usr/bin/umount ${probe_dir}/mnt + fi + done + prun /usr/bin/rmdir ${probe_dir}/mnt +} + +create_keyfile() { + tik_keyfile=$(prun mktemp /tmp/tik.XXXXXXXXXX) + log "[create_keyfile] Creating keyfile ${tik_keyfile}" + /usr/bin/base64 -w 0 /dev/urandom | head -c 1k | prun tee "${tik_keyfile}" + prun /usr/bin/chmod 400 "${tik_keyfile}" + tik_keyid=$(prun cat "${tik_keyfile}" | prun keyctl padd user cryptenroll @u) +} + +wipe_keyfile() { + log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}" + + local crypt_part="${TIK_CRYPT_PART}" + + if [ -z "${crypt_part}" ]; then + probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" + crypt_part="${probedpart}" + fi + + if [ -n "${crypt_part}" ] && [ -n "${tik_keyfile}" ] && [ -f "${tik_keyfile}" ]; then + prun /usr/bin/systemd-cryptenroll --unlock-key-file="${tik_keyfile}" --wipe-slot=0 "${crypt_part}" + else + log "[wipe_keyfile] no LUKS partition or keyfile found, skipping slot wipe" + fi + + prun-opt /usr/bin/rm "${tik_keyfile}" + prun-opt keyctl revoke "${tik_keyid}" + prun-opt keyctl reap +} + +dump_image_dd() { + local image_source_files=$1 + local image_target=$2 + log "[dump_image_dd] deploying ${TIK_IMG_DIR}/${image_source_files}" + (xzcat "${TIK_IMG_DIR}/${image_source_files}" | pv -f -F "# %b copied in %t %r" | prun /usr/bin/dd of="${image_target}" bs=64k) 2>&1 | d --progress --title="Installing ${TIK_OS_NAME}" --pulsate --auto-close --no-cancel --width=400 + prun /usr/bin/sync | d --progress --title="Syncing" --pulsate --auto-close --no-cancel --width=400 +} + +dump_image_repart_image() { + local image_source_files=$1 + local image_target=$2 + local success=0 + local max_attempts=5 + local attempt_num=1 + + create_keyfile + log "[dump_image_repart_image] deploying ${TIK_IMG_DIR}/${image_source_files}" + + while [ ${success} = 0 ] && [ ${attempt_num} -lt ${max_attempts} ]; do + prun-opt systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --image="${TIK_IMG_DIR}/${image_source_files}" --image-policy=root=unprotected "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) + if [ ${retval} -eq 0 ]; then + success=1 + else + log "[dump_image_repart_image] systemd-repart attempt ${attempt_num} failed. Trying again..." + sleep 1 + attempt_num=$(( attempt_num + 1 )) + fi + done + if [ ${success} = 1 ]; then + log "[dump_image_repart_image] systemd-repart succeeded after ${attempt_num} attempts" + else + error "systemd-repart failed" + fi +} + +dump_image_repart_self() { + local image_target=$1 + create_keyfile + prun-opt rm -rf /etc/fstab.repart + log "[dump_image_repart_self] self-deploying" + prun systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --generate-fstab=/etc/fstab.repart "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) +} + +tik_cleanup_mounts() { + if [ -n "${TIK_MOUNTED_POINTS}" ] || [ "${TIK_MOUNTED_TARGET}" = "1" ]; then + log "[tik_cleanup_mounts] mounts detected after phase '${TIK_CURRENT_PHASE}', unmounting" + tik_target_unmount + fi +} + +tik_rewrite_overlay_opts() { + local opts="$1" + local root="$2" + + opts="$(echo "$opts" | sed -E "s#lowerdir=/#lowerdir=${root}/#g; s#upperdir=/#upperdir=${root}/#g; s#workdir=/#workdir=${root}/#g")" + opts="$(echo "$opts" | sed -E "s#lowerdir=${root}//#lowerdir=${root}/#g; s#upperdir=${root}//#upperdir=${root}/#g; s#workdir=${root}//#workdir=${root}/#g")" + opts="$(echo "$opts" | sed -E "s#lowerdir=(${root}/[^,]*):/#lowerdir=\1:${root}/#g")" + + echo "$opts" +} + +tik_is_opt_set() { + echo ",$1," | grep -q ",$2," +} + +tik_close_progress() { + if [ -n "${TIK_PROGRESS_PID}" ]; then + log "[tik_close_progress] stopping progress monitor pid=${TIK_PROGRESS_PID}" + + if [ -n "${TIK_PIPE}" ] && [ -p "${TIK_PIPE}" ]; then + echo "100" > "${TIK_PIPE}" 2>/dev/null || true + fi + + kill -TERM -- "-${TIK_PROGRESS_PID}" 2>/dev/null || true + wait "${TIK_PROGRESS_PID}" 2>/dev/null || true + + kill -KILL -- "-${TIK_PROGRESS_PID}" 2>/dev/null || true + + unset TIK_PROGRESS_PID + fi + + if [ -n "${TIK_PIPE}" ] && [ -p "${TIK_PIPE}" ]; then + log "[tik_close_progress] removing progress pipe ${TIK_PIPE}" + rm -f "${TIK_PIPE}" 2>/dev/null || true + fi + unset TIK_PIPE +} diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index ff8367a..6e3ffd5 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -5,6 +5,9 @@ . /usr/lib/tik/lib/cenity +# Helper functions for tik-functions +. ${tik_dir}/lib/tik-functions-helper + log() { if $logging; then echo "[${tik_module}][$(date +"%Y%m%d-%T")][LOG] $*" 1>&2 @@ -80,145 +83,209 @@ prun() { fi } -get_persistent_device_from_unix_node() { - local unix_device=$1 - local schema=$2 - local node - local persistent_name - node=$(basename "${unix_device}") - for persistent_name in /dev/disk/"${schema}"/*; do - if [ "$(basename "$(readlink "${persistent_name}")")" = "${node}" ]; then - if [[ ${persistent_name} =~ ^/dev/disk/"${schema}"/nvme-eui ]]; then - # Filter out nvme-eui nodes as they are not descriptive to the user - continue - fi - echo "${persistent_name}" - return - fi - done - warn "Could not find ${schema} representation of ${node}. Using original device ${unix_device}" - echo "${unix_device}" -} +tik_progress_step() { + # Create progress UI when first used in this phase + [ -n "${TIK_PROGRESS_PID}" ] || tik_prepare_progress_pipe -probe_partitions() { - local probe_dir=/var/lib/tik/probe - local filesystem_type=$2 - local filematch=$3 - local device=$1 - local mountops - local part + local message=$1 + local module_percent=$2 - if [[ "${filesystem_type}" == "btrfs" ]]; then - mountops="-o compress=zstd:1" + if [ -z "${TIK_PIPE}" ]; then + return 0 fi - prun /usr/bin/mkdir -p ${probe_dir}/mnt - probedpart="" - - for part in $(lsblk ${device} -p -n -r -o ID-LINK,FSTYPE | tr -s ' ' ";" | grep ";${filesystem_type}" | cut -d\; -f1); do - if [ -z "${filematch}" ]; then - log "[probe_partitions] no file match required" - # Fallback to unix device in order to fix issue with USB devices - probedpart="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" - log "[probe_partitions] Partition ${probedpart} found" - else # Check if ${filematch} exists - # Fallback to unix device in order to fix issue with USB devices - part="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" - prun /usr/bin/mount ${mountops} ${part} "${probe_dir}/mnt" - if [ -f ${probe_dir}/mnt/${filematch} ]; then - log "[probe_partitions] File ${filematch} found" - # Fallback to unix device in order to fix issue with USB devices - probedpart="${part}" - log "[probe_partitions] Partition ${probedpart} found" - fi - prun-opt /usr/bin/umount ${probe_dir}/mnt - fi - done - prun /usr/bin/rmdir ${probe_dir}/mnt -} - -mount_etc_for_root() { - local root=$1 + local overall_percent - if grep -qF 'overlay /etc' "${root}/etc/fstab" ; then - local etcmountcmd - etcmountcmd=$(grep "overlay /etc" "${root}/etc/fstab" \ - | sed "s#/sysroot/#${root}/#g" \ - | sed "s#/work-etc.*#/work-etc ${root}/etc#g" \ - | sed 's#overlay /etc overlay#/usr/bin/mount -t overlay overlay -o#') - eval prun "${etcmountcmd}" + if [ -n "${TIK_TOTAL_MODULES}" ] && [ "${TIK_TOTAL_MODULES}" -gt 0 ] && \ + [ -n "${TIK_CURRENT_MODULE_INDEX}" ] && [ "${TIK_CURRENT_MODULE_INDEX}" -gt 0 ]; then + # Map module-local 0–100% into global 0–100% based on module index + # overall = ((index-1)*100 + module_percent) / total_modules + local base=$(( (TIK_CURRENT_MODULE_INDEX - 1) * 100 )) + local num=$(( base + module_percent )) + overall_percent=$(( num / TIK_TOTAL_MODULES )) else - prun /usr/bin/mount -o bind "${root}/etc" "${root}/etc" + overall_percent=${module_percent} fi -} -tik_monitor_progress() { - if [ -z "${TIK_PIPE}" ]; then - return 0 - fi - log "[progress] Monitoring installation progress" - (tail -f "${TIK_PIPE}") | d --progress --title="${TIK_PROGRESS_TITLE}" --auto-close --no-cancel --width=400 - log "[progress] Progress UI closed" + echo "# ${message}" > "${TIK_PIPE}" + echo "${overall_percent}" > "${TIK_PIPE}" } -tik_prepare_progress_pipe() { - TIK_PIPE=/tmp/tikpipe - export TIK_PIPE - [ -p "${TIK_PIPE}" ] || mkfifo "${TIK_PIPE}" +tik_unmount() { + local prefix="$1" + local listvar="${2:-TIK_MOUNTED_POINTS}" + local mp + + [ -n "${prefix}" ] || return 0 - tik_monitor_progress & - TIK_PROGRESS_PID=$! + local -n _ml="${listvar}" - log "[tik_prepare_progress_pipe] progress pipe ready (pid=${TIK_PROGRESS_PID})" + log "[tik_unmount] unmounting ${prefix} (list=${listvar})" + while IFS= read -r mp; do + [ -z "${mp}" ] && continue + case "${mp}" in + "${prefix}"|${prefix}/*) + prun-opt /usr/bin/umount "${mp}" + ;; + esac + done <<< "${_ml}" + + tik_untrack_mountpoint "${prefix}" "${listvar}" } -tik_close_progress() { - if [ -n "${TIK_PROGRESS_PID}" ]; then - log "[tik_close_progress] stopping progress monitor pid=${TIK_PROGRESS_PID}" +tik_mount() { + # Mount a device and track it. + # If the 2nd argument is an absolute path, mount there. + # Otherwise, mount at a subfolder of TIK_ROOT_MNT. + local dev="$1" + local path="$2" + local opts="$3" + local fstype="$4" + local listvar="${5:-TIK_MOUNTED_POINTS}" + local overlay_root_prefix="$6" - if [ -n "${TIK_PIPE}" ] && [ -p "${TIK_PIPE}" ]; then - echo "100" > "${TIK_PIPE}" 2>/dev/null || true - fi + [ -n "${dev}" ] || error "tik_mount: missing device" + [ -n "${path}" ] || error "tik_mount: missing target/subpath" - kill -TERM -- "-${TIK_PROGRESS_PID}" 2>/dev/null || true - wait "${TIK_PROGRESS_PID}" 2>/dev/null || true + local target="" - kill -KILL -- "-${TIK_PROGRESS_PID}" 2>/dev/null || true + case "${path}" in + /*) + target="${path}" + ;; + *) + [ -n "${TIK_ROOT_MNT}" ] || error "tik_mount: TIK_ROOT_MNT not set" + if echo "${path}" | grep -qE '(^|/)\.\.($|/)'; then + error "tik_mount: unsafe subpath '${path}'" + fi + target="${TIK_ROOT_MNT%/}/${path}" + ;; + esac - unset TIK_PROGRESS_PID - fi + case "${target}" in + /*) ;; + *) error "tik_mount: target must be absolute (got '${target}')" ;; + esac + + log "[tik_mount] mounting ${dev} at ${target} opts='${opts}' fstype='${fstype}' (list=${listvar})" + prun /usr/bin/mkdir -p "${target}" + + if [ "${fstype}" = "none" ] && (tik_is_opt_set "${opts}" "bind" || tik_is_opt_set "${opts}" "rbind"); then + if tik_is_opt_set "${opts}" "rbind"; then + prun /usr/bin/mount --rbind "${dev}" "${target}" + else + prun /usr/bin/mount --bind "${dev}" "${target}" + fi + if tik_is_opt_set "${opts}" "ro" || tik_is_opt_set "${opts}" "nosuid" || tik_is_opt_set "${opts}" "nodev" || tik_is_opt_set "${opts}" "noexec"; then + prun /usr/bin/mount -o "remount,${opts}" "${target}" + fi + + elif [ "${fstype}" = "overlay" ]; then + local newopts="${opts}" + if [ -n "${overlay_root_prefix}" ]; then + newopts="$(tik_rewrite_overlay_opts "${opts}" "${overlay_root_prefix}")" + fi + prun /usr/bin/mount -t overlay overlay -o "${newopts}" "${target}" - if [ -n "${TIK_PIPE}" ] && [ -p "${TIK_PIPE}" ]; then - log "[tik_close_progress] removing progress pipe ${TIK_PIPE}" - rm -f "${TIK_PIPE}" 2>/dev/null || true + else + if [ -n "${fstype}" ]; then + if [ -n "${opts}" ]; then + prun /usr/bin/mount -t "${fstype}" -o "${opts}" "${dev}" "${target}" + else + prun /usr/bin/mount -t "${fstype}" "${dev}" "${target}" + fi + else + if [ -n "${opts}" ]; then + prun /usr/bin/mount -o "${opts}" "${dev}" "${target}" + else + prun /usr/bin/mount "${dev}" "${target}" + fi + fi fi - unset TIK_PIPE + + tik_track_mountpoint "${target}" "${listvar}" } -tik_progress_step() { - # Create progress UI when first used in this phase - [ -n "${TIK_PROGRESS_PID}" ] || tik_prepare_progress_pipe +tik_target_mount() { + # $1 = mapper override + # $2 = mode: required | optional + local mapper_override="$1" + local mode="${2:-required}" - local message=$1 - local module_percent=$2 + [ "${TIK_MOUNTED_TARGET}" = "1" ] && return 0 - if [ -z "${TIK_PIPE}" ]; then - return 0 + tik_target_mount_prepare + + # open root device + tik_target_open "${mapper_override}" "${mode}" + case "$?" in + 0) ;; + 1) + if [ "${mode}" = "required" ]; then + error "No encrypted system found where one was required" + fi + return 1 + ;; + 2) + return 2 + ;; + esac + + if [ -z "${TIK_ROOT_DEV}" ]; then + if ! TIK_ROOT_DEV="$(tik_target_find_root_partition)"; then + if [ "${mode}" = "required" ]; then + error "No existing system found" + fi + return 1 + fi + export TIK_ROOT_DEV fi - local overall_percent + log "[tik_target_mount] root device is ${TIK_ROOT_DEV}" - if [ -n "${TIK_TOTAL_MODULES}" ] && [ "${TIK_TOTAL_MODULES}" -gt 0 ] && \ - [ -n "${TIK_CURRENT_MODULE_INDEX}" ] && [ "${TIK_CURRENT_MODULE_INDEX}" -gt 0 ]; then - # Map module-local 0–100% into global 0–100% based on module index - # overall = ((index-1)*100 + module_percent) / total_modules - local base=$(( (TIK_CURRENT_MODULE_INDEX - 1) * 100 )) - local num=$(( base + module_percent )) - overall_percent=$(( num / TIK_TOTAL_MODULES )) + # mount root + tik_target_mount_root "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" + + # Assemble fstab in a writable location + local assembled + assembled="$(tik_target_fstab_assemble "${TIK_ROOT_MNT}")" + TIK_TARGET_FSTAB="${assembled}" + export TIK_TARGET_FSTAB + TIK_ASSEMBLED_FSTAB="${assembled}" + export TIK_ASSEMBLED_FSTAB + + # mount everything described by assembled fstab + tik_target_mount_from_fstab "${TIK_ROOT_MNT}" + + # Probe ESP + log "[tik_target_mount] probing for ESP partition on ${TIK_INSTALL_DEVICE}" + probe_partitions "${TIK_INSTALL_DEVICE}" "vfat" + if [ -n "${probedpart}" ]; then + TIK_ESP_PART="${probedpart}" + export TIK_ESP_PART + log "[tik_target_mount] found ESP ${TIK_ESP_PART}" else - overall_percent=${module_percent} + log "[tik_target_mount] no ESP found (continuing)" + TIK_ESP_PART="" + export TIK_ESP_PART fi - echo "# ${message}" > "${TIK_PIPE}" - echo "${overall_percent}" > "${TIK_PIPE}" + # bind pseudo fs for chroot usage + tik_target_mount_pseudofs "${TIK_ROOT_MNT}" + + log "[tik_target_mount] Target system mounted" + + TIK_MOUNTED_TARGET=1 + export TIK_MOUNTED_TARGET +} + +tik_target_write_fstab() { + [ "${TIK_MOUNTED_TARGET}" = "1" ] || error "tik_target_write_fstab: target system not mounted" + + # Install assembled fstab into the target if possible + if [ -n "${TIK_ASSEMBLED_FSTAB}" ] && [ -f "${TIK_ASSEMBLED_FSTAB}" ]; then + tik_target_fstab_install "${TIK_ROOT_MNT}" "${TIK_ASSEMBLED_FSTAB}" || true + else + error "tik_target_write_fstab: no assembled fstab available" + fi } diff --git a/usr/lib/tik/lib/tik-functions-helper b/usr/lib/tik/lib/tik-functions-helper new file mode 100644 index 0000000..ae9ef9d --- /dev/null +++ b/usr/lib/tik/lib/tik-functions-helper @@ -0,0 +1,505 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: Copyright 2025 SUSE LLC +# SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens + +tik_prompt_passphrase() { + # Prompt the user for a passphrase + local title="$1" + local cancel_label="${2:-Cancel}" + local passphrase="" + + if $gui; then + passphrase="$(zenity --password --title="${title}" --cancel-label="${cancel_label}")" || true + else + cenity result --password --title="${title}" --cancel-label="${cancel_label}" || true + passphrase="${result}" + fi + + echo -n "${passphrase}" +} + +tik_target_mount_prepare() { + TIK_ROOT_MNT="${TIK_ROOT_MNT:=/var/lib/tik/root}" + export TIK_ROOT_MNT + prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}" + + TIK_MOUNTED_POINTS="" + TIK_MOUNTED_TARGET=0 + export TIK_MOUNTED_TARGET + + TIK_TARGET_FSTAB="" + export TIK_TARGET_FSTAB + TIK_ASSEMBLED_FSTAB="" + export TIK_ASSEMBLED_FSTAB + + TIK_ESP_PART="" + export TIK_ESP_PART + + TIK_OPENED_MAPPER="" + export TIK_OPENED_MAPPER +} + +tik_target_find_root_partition() { + local fs + for fs in btrfs ext4 xfs f2fs; do + probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/fstab" + if [ -n "${probedpart}" ]; then + echo "${probedpart}" + return 0 + fi + probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/os-release" + if [ -n "${probedpart}" ]; then + echo "${probedpart}" + return 0 + fi + probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/usr/lib/os-release" + if [ -n "${probedpart}" ]; then + echo "${probedpart}" + return 0 + fi + done + return 1 +} + +tik_target_open() { + # $1 = mapper override + # $2 = mode: "required" | "optional" + local mapper_override="$1" + local mode="${2:-required}" + + TIK_CRYPT_PART="" + TIK_ROOT_DEV="" + + probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" + if [ -z "${probedpart}" ]; then + return 1 + fi + + TIK_CRYPT_PART="${probedpart}" + export TIK_CRYPT_PART + + local mapper_name="${mapper_override:-${TIK_CRYPT_MAPPER:-cr_root}}" + + log "[tik_target_open] encrypted partition detected: ${TIK_CRYPT_PART} (mapper=${mapper_name})" + + if [ -e "/dev/mapper/${mapper_name}" ]; then + TIK_ROOT_DEV="/dev/mapper/${mapper_name}" + export TIK_ROOT_DEV + TIK_OPENED_MAPPER="${mapper_name}" + export TIK_OPENED_MAPPER + return 0 + fi + + if [ -n "${tik_keyfile}" ] && [ -f "${tik_keyfile}" ]; then + prun /usr/sbin/cryptsetup luksOpen --key-file="${tik_keyfile}" "${TIK_CRYPT_PART}" "${mapper_name}" + TIK_ROOT_DEV="/dev/mapper/${mapper_name}" + export TIK_ROOT_DEV + TIK_OPENED_MAPPER="${mapper_name}" + export TIK_OPENED_MAPPER + return 0 + fi + + while true; do + local pw + local cancel_label="Cancel" + + if [ "${mode}" = "optional" ]; then + cancel_label="Skip" + fi + + pw="$(tik_prompt_passphrase "Encrypted partition (${TIK_CRYPT_PART}) detected" "${cancel_label}")" + + if [ -z "${pw}" ]; then + if [ "${mode}" = "optional" ]; then + log "[tik_target_open] user skipped unlocking" + return 2 + fi + error "Encrypted system detected but no passphrase provided" + fi + + printf "%s" "${pw}" | prun /usr/sbin/cryptsetup luksOpen "${TIK_CRYPT_PART}" "${mapper_name}" + if [ "${retval}" = "0" ]; then + TIK_ROOT_DEV="/dev/mapper/${mapper_name}" + export TIK_ROOT_DEV + TIK_OPENED_MAPPER="${mapper_name}" + export TIK_OPENED_MAPPER + return 0 + fi + + d --warning --no-wrap --title="Incorrect passphrase" --text="Failed to unlock encrypted partition ${TIK_CRYPT_PART}." + done +} + +tik_mountopts_filter() { + local opts="$1" + local ignore_list="$2" + + if [ -z "${opts}" ] || [ "${opts}" = "-" ]; then + echo "${opts}" + return 0 + fi + + /usr/bin/awk -v opts="${opts}" -v ignore="${ignore_list}" ' + BEGIN { + n=split(ignore, ig, /,/) + for (i=1;i<=n;i++) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", ig[i]) + if (ig[i] != "") drop[ig[i]]=1 + } + + m=split(opts, a, /,/) + out="" + for (j=1;j<=m;j++) { + o=a[j] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", o) + if (o=="") continue + + # exact match (e.g. ro) + if (drop[o]) continue + + # key=value match (e.g. ro=vfs) where ignore has 'ro' or 'ro=' or 'ro=vfs' + split(o, kv, /=/) + k=kv[1] + if (drop[k]) continue + if (drop[k"="]) continue + if (drop[o]) continue + + if (out=="") out=o + else out=out","o + } + if (out=="") out="-" + print out + } + ' +} + +tik_mountopts_ignore_list() { + echo "${TIK_MOUNTOPTS_IGNORE_LIST:-ro,ro=vfs}" +} + +tik_mountopts_apply_filter() { + local opts="$1" + local ignore_opts + ignore_opts="$(tik_mountopts_ignore_list)" + tik_mountopts_filter "${opts}" "${ignore_opts}" +} + +tik_target_mount_root() { + local rootdev=$1 + local mnt=$2 + + # Temporarily mount the root to determine the real mount options + local tmp_mnt + local root_opts + tmp_mnt="$(prun /usr/bin/mktemp -d /tmp/tik-rootprobe.XXXXXXXXXX)" + log "[tik_target_mount_root] probing mount options for / by temporarily mounting ${rootdev} on ${tmp_mnt}" + + prun /usr/bin/mount -o ro "${rootdev}" "${tmp_mnt}" + + if [ -f "${tmp_mnt}/etc/fstab" ]; then + root_opts="$(prun /usr/bin/awk ' + $0 ~ /^[[:space:]]*#/ { next } + NF < 4 { next } + $2 == "/" { print $4; exit } + ' "${tmp_mnt}/etc/fstab")" + log "[tik_target_mount_root] probed root mount options: '${root_opts}'" + else + log "[tik_target_mount_root] no ${tmp_mnt}/etc/fstab found while probing, falling back to default mount options" + root_opts="" + fi + + prun-opt /usr/bin/umount "${tmp_mnt}" + prun-opt /usr/bin/rmdir "${tmp_mnt}" + + if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then + root_opts="$(tik_mountopts_apply_filter "${root_opts}")" + log "[tik_target_mount_root] filtered root mount options: '${root_opts}'" + fi + + if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then + tik_mount "${rootdev}" "${mnt}" "${root_opts}" + else + tik_mount "${rootdev}" "${mnt}" + fi +} + +tik_is_safe_mount() { + local spec=$1 + local mp=$2 + local fstype=$3 + + # Require absolute mountpoints + if [ -z "${mp}" ] || [ "${mp#"/"}" = "${mp}" ]; then + return 1 + fi + + # Basic path traversal guard + if echo "${mp}" | grep -qE '(^|/)\.\.($|/)'; then + return 1 + fi + + # Do not allow overriding the root mount or pseudo filesystems + case "${mp}" in + "/"|"/proc"|"/sys"|"/dev"|"/run"|"/tmp") + return 1 + ;; + esac + + return 0 +} + +tik_override_mountpoint() { + local fstab=$1 + local mp=$2 + local newline=$3 + + local tmp + tmp="$(prun /usr/bin/mktemp /tmp/tik-fstab.XXXXXXXXXX)" + + prun /usr/bin/awk -v want="${mp}" -v rep="${newline}" ' + BEGIN { replaced=0 } + { + if ($0 ~ /^[[:space:]]*#/ || NF < 2) { print; next } + if (!replaced && $2 == want) { print rep; replaced=1; next } + print + } + END { if (!replaced) print rep } + ' "${fstab}" | prun /usr/bin/tee "${tmp}" >/dev/null + + prun /usr/bin/cp -a "${tmp}" "${fstab}" + prun /usr/bin/rm -f "${tmp}" +} + +tik_target_can_write_etc() { + local mnt=$1 + local probe="${mnt}/etc/.tik-write-probe.$$" + prun-opt /usr/bin/sh -c "touch '${probe}' && rm -f '${probe}'" + [ "${retval}" = "0" ] +} + +tik_target_fstab_assemble() { + local mnt=$1 + + local fstab="${mnt}/etc/fstab" + local fstab_repart="${mnt}/etc/fstab.repart" + local fstab_tik="${mnt}/etc/fstab.tik" + + local assembled + assembled="$(prun /usr/bin/mktemp /tmp/tik-assembled-fstab.XXXXXXXXXX)" + TIK_ASSEMBLED_FSTAB="${assembled}" + export TIK_ASSEMBLED_FSTAB + + # Prefer systemd-repart generated fstab if present, otherwise use existing fstab. + if [ -f "${fstab_repart}" ]; then + log "[tik_target_fstab_assemble] using ${fstab_repart} as base fstab" + prun /usr/bin/cp -a "${fstab_repart}" "${assembled}" + elif [ -f "${fstab}" ]; then + log "[tik_target_fstab_assemble] using ${fstab} as base fstab" + prun /usr/bin/cp -a "${fstab}" "${assembled}" + else + log "[tik_target_fstab_assemble] no base fstab found, leaving assembled fstab empty at ${assembled}" + prun /usr/bin/tee "${assembled}" >/dev/null <&1 | d --progress --title="Restoring /home" --pulsate --auto-close --no-cancel --width=400 - prun /usr/bin/mv ${mig_dir}/mnt/${snap_dir} ${mig_dir}/mnt/home - prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/mnt/home ro false - for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/${snap_dir} --sort=path | rev | cut -f1 -d' ' | rev | sed 's/^@//'); do - subsubvolname=$(basename $subsubvol) - subsubdirname=$(dirname $subsubvol | awk -F "${mig_dir}/${snap_dir}" '{print $2}') - (prun /usr/sbin/btrfs send ${subsubvol} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/mnt/home/${subsubdirname} ) 2>&1 | d --progress --title="Restoring containers" --pulsate --auto-close --no-cancel --width=400 - prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/mnt/home/${subsubdirname}/${subsubvolname} ro false - prun-opt /usr/bin/sed -i 's/driver = "overlay"/driver = "btrfs"/g' ${mig_dir}/mnt/etc/containers/storage.conf + prun-opt /usr/bin/cp -a "${mig_dir}/system-connections/"* "${TIK_ROOT_MNT}/etc/NetworkManager/system-connections" + prun-opt /usr/bin/cp -a "${mig_dir}/localtime" "${TIK_ROOT_MNT}/etc/localtime" + prun-opt /usr/bin/cp -a "${mig_dir}/users/"* "${TIK_ROOT_MNT}/var/lib/AccountsService/users" + prun-opt /usr/bin/cp -a "${mig_dir}/icons/"* "${TIK_ROOT_MNT}/var/lib/AccountsService/icons" + prun-opt /usr/bin/cp -a "${mig_dir}/bluetooth/"* "${TIK_ROOT_MNT}/var/lib/bluetooth" + prun-opt /usr/bin/cp -a "${mig_dir}/fprint/"* "${TIK_ROOT_MNT}/var/lib/fprint" + prun-opt /usr/bin/cp -a "${mig_dir}/openvpn/"* "${TIK_ROOT_MNT}/etc/openvpn" + + log "[mig] deleting existing /home subvolume under ${TIK_ROOT_MNT}/mnt" + prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/mnt/home" + + (prun /usr/sbin/btrfs send "${mig_dir}/${snap_dir}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive "${TIK_ROOT_MNT}/mnt") 2>&1 \ + | d --progress --title="Restoring /home" --pulsate --auto-close --no-cancel --width=400 + + prun /usr/bin/mv "${TIK_ROOT_MNT}/mnt/${snap_dir}" "${TIK_ROOT_MNT}/mnt/home" + prun /usr/sbin/btrfs property set -f -ts "${TIK_ROOT_MNT}/mnt/home" ro false + + for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o "${mig_dir}/${snap_dir}" --sort=path | rev | cut -f1 -d' ' | rev | sed 's/^@//'); do + subsubvolname="$(basename "$subsubvol")" + subsubdirname="$(dirname "$subsubvol" | awk -F "${mig_dir}/${snap_dir}" '{print $2}')" + + (prun /usr/sbin/btrfs send "${subsubvol}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive "${TIK_ROOT_MNT}/mnt/home/${subsubdirname}") 2>&1 \ + | d --progress --title="Restoring containers" --pulsate --auto-close --no-cancel --width=400 + + prun /usr/sbin/btrfs property set -f -ts "${TIK_ROOT_MNT}/mnt/home/${subsubdirname}/${subsubvolname}" ro false + prun-opt /usr/bin/sed -i 's/driver = "overlay"/driver = "btrfs"/g' "${TIK_ROOT_MNT}/mnt/etc/containers/storage.conf" done - for userhome in ${mig_dir}/mnt/home/*/; do - writemigdesktop $userhome + tik_mount "${TIK_ROOT_DEV}" "home" "compress=zstd:1,subvol=/@/home" + + for userhome in "${TIK_ROOT_MNT}/mnt/home"/*/; do + writemigdesktop "${userhome}" done - prun /usr/bin/umount ${mig_dir}/mnt - prun /usr/bin/rmdir ${mig_dir}/mnt + + tik_unmount "${TIK_ROOT_MNT}/mnt" fi diff --git a/usr/lib/tik/modules/pre/10-welcome b/usr/lib/tik/modules/pre/10-welcome index 44286f2..2c46683 100644 --- a/usr/lib/tik/modules/pre/10-welcome +++ b/usr/lib/tik/modules/pre/10-welcome @@ -31,7 +31,7 @@ checkLaptop() { fi done if [ "$givePowerRecommendation" = true ]; then - log "AC Power disconnected and Battery is not charging" + log "[welcome] AC Power disconnected and Battery is not charging" displayACWarningMsg fi } diff --git a/usr/lib/tik/modules/pre/20-mig b/usr/lib/tik/modules/pre/20-mig index 9a2ba2b..1c34510 100644 --- a/usr/lib/tik/modules/pre/20-mig +++ b/usr/lib/tik/modules/pre/20-mig @@ -4,19 +4,21 @@ mig_dir=/var/lib/tik/mig snap_dir=homebk +home_size=0 +tik_stick_size=0 if [ ! -d ${mig_dir} ]; then prun /usr/bin/mkdir -p ${mig_dir} fi if [ ! -z "$(ls -A ${mig_dir})" ]; then - log "existing backup found" + log "[mig] existing backup found" d_opt --question --no-wrap --cancel-label="No, Delete Backup" --title="Existing user backup detected" --text="These users can be restored to the new installation\n\nWould you like to use this backup?" oldbackupyn=$? - log "[oldbackupyn][${oldbackupyn}]" + log "[mig] [oldbackupyn][${oldbackupyn}]" if [ "${oldbackupyn}" == 0 ]; then skipbackup=1 migrate=1 - log "backup skipped, migration will use existing backup" + log "[mig] backup skipped, migration will use existing backup" else prun-opt /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro false for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/${snap_dir} --sort=path | rev | cut -f1 -d' ' | rev | sed "s/^@//"); do @@ -46,65 +48,38 @@ fi get_disk if [ -z "${skipbackup}" ]; then - # Although Legacy Aeon didn't officially support LUKS encrypted installations, - # some users might have nevertheless enabled encryption anyway. - # Search for existing crypto_LUKS partitions and, if found, prompt the user - # to unlock those so that the migration module can find existing data. - for encrypted_partition in $(lsblk ${TIK_INSTALL_DEVICE} -p -n -r -o ID-LINK,FSTYPE|tr -s ' ' ";"|grep ";crypto_LUKS"|cut -d\; -f1); do - if [ -e /dev/mapper/crypt_${encrypted_partition} ]; then - # Already opened for some reason... do not prompt for the passphrase - # but ensure we will clean up afterwards - crypt_opened="${crypt_opened} crypt_${encrypted_partition}" - else - while [ 1 ]; do - if $gui; then - passphrase=$(zenity --password --title="Encrypted partition (${encrypted_partition}) detected" --cancel-label="Skip") || break - else - cenity passphrase --password --title="Encrypted partition (${encrypted_partition}) detected" --cancel-label="Skip" || break - fi - if [ -n "${passphrase}" ]; then - echo -n "${passphrase}" | prun /usr/sbin/cryptsetup luksOpen /dev/disk/by-id/${encrypted_partition} crypt_${encrypted_partition} - if [ "${?}" -eq 0 ]; then - crypt_opened="${crypt_opened} crypt_${encrypted_partition}" - # Wait for the mapped device to appear - wait_count=0 - while [ ! -e /dev/mapper/crypt_${encrypted_partition} ] && [ ${wait_count} -lt 5 ]; do - sleep 1 - wait_count=$((wait_count + 1)) - done - break - fi - fi - done - fi - done - unset passphrase - - # Probe selected disk for a btrfs partition containing /usr/lib/os-release - probe_partitions $TIK_INSTALL_DEVICE "btrfs" "/usr/lib/os-release" + # Mount the existing system + tik_target_mount "tik_old_root" "optional" + case "$?" in + 0) ;; + *) + log "[mig] no migratable system found, skipping migration" + migrate=0 + return 0 + ;; + esac - if [ -n "${probedpart}" ]; then - prun /usr/bin/mkdir ${mig_dir}/mnt - prun-opt /usr/bin/mount -o compress=zstd:1,subvol=/@/home ${probedpart} ${mig_dir}/mnt - if [ ${retval} -eq 0 ]; then - prun /usr/sbin/btrfs quota rescan -w ${mig_dir}/mnt | d --progress --title="Detected existing /home subvolume.." --pulsate --auto-close --no-cancel --width=400 - home_size=$(prun /usr/sbin/btrfs qgroup show --raw -f ${mig_dir}/mnt | grep @/home$ | awk '{print $2}') + # Determine whether migration is feasible by checking /home size + if [ -d "${TIK_ROOT_MNT}/home" ]; then + # If /home is btrfs, run quota rescan and check qgroup usage. + prun-opt /usr/sbin/btrfs quota rescan -w "${TIK_ROOT_MNT}/home" | d --progress --title="Detected existing /home.." --pulsate --auto-close --no-cancel --width=400 + if [ "${retval}" -eq 0 ]; then + home_size=$(prun /usr/sbin/btrfs qgroup show --raw -f "${TIK_ROOT_MNT}/home" | awk 'NR==2{print $2}') tik_stick_size=$(prun /usr/sbin/btrfs fi usage --raw ${mig_dir} | grep estimated | awk '{print $3}') - if [ ${home_size} -gt ${tik_stick_size} ]; then + if [ "${home_size}" -gt "${tik_stick_size}" ]; then # Not enough space to offer migration migrate=0 fi - if [ ${home_size} -le 16384 ]; then + if [ "${home_size}" -le 16384 ]; then # /home subvolume is empty migrate=0 fi - prun /usr/bin/umount ${mig_dir}/mnt else - log "no @/home subvolume found on ${probedpart}" + log "[mig] no usable btrfs quota info found under ${TIK_ROOT_MNT}/home" migrate=0 fi - prun /usr/bin/rmdir ${mig_dir}/mnt - # partition found, /home subvolume found, no known reason to not migrate, so ask the user + + # partition found, /home found, no known reason to not migrate, so ask the user if [ -z "${migrate}" ]; then d_opt --question --no-wrap --title="Backup users from the existing install?" --text="These users will be restored to the new installation." migrateyn=$? @@ -114,71 +89,47 @@ if [ -z "${skipbackup}" ]; then migrate=0 fi fi + else + log "[mig] no /home found on mounted system, migration disabled" + migrate=0 fi if [ "${migrate}" == 1 ]; then # We're migrating, lets go! - prun /usr/bin/mkdir ${mig_dir}/mnt - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/home ${probedpart} ${mig_dir}/mnt # Check for existing snapshot from interrupted backup and delete if it exists boo#1224824 - if [ -d ${mig_dir}/mnt/${snap_dir} ]; then - prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${snap_dir} + if [ -d "${TIK_ROOT_MNT}/home/${snap_dir}" ]; then + prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/home/${snap_dir}" fi - prun /usr/sbin/btrfs subvolume snapshot -r ${mig_dir}/mnt ${mig_dir}/mnt/${snap_dir} - (prun /usr/sbin/btrfs send ${mig_dir}/mnt/${snap_dir} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}) 2>&1 | d --progress --title="Backing up /home" --pulsate --auto-close --no-cancel --width=400 - prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${snap_dir} + prun /usr/sbin/btrfs subvolume snapshot -r "${TIK_ROOT_MNT}/home" "${TIK_ROOT_MNT}/home/${snap_dir}" + (prun /usr/sbin/btrfs send "${TIK_ROOT_MNT}/home/${snap_dir}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}) 2>&1 | d --progress --title="Backing up /home" --pulsate --auto-close --no-cancel --width=400 + prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/home/${snap_dir}" + # Probe for subvolumes nested beneath /home and back them up also - if (prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/mnt | grep -q "ID "); then + if (prun-opt /usr/sbin/btrfs subvolume list -o "${TIK_ROOT_MNT}/home" | grep -q "ID "); then prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro false - for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o ${mig_dir}/mnt --sort=path | rev | cut -f1 -d' ' | rev | sed 's/^@\/home//'); do + for subsubvol in $(prun-opt /usr/sbin/btrfs subvolume list -o "${TIK_ROOT_MNT}/home" --sort=path | rev | cut -f1 -d' ' | rev | sed 's/^@\/home//'); do subsubvolname=$(basename $subsubvol) subsubdirname=$(dirname $subsubvol) - prun /usr/sbin/btrfs subvolume snapshot -r ${mig_dir}/mnt/${subsubvol} ${mig_dir}/mnt/${subsubvolname} - (prun /usr/sbin/btrfs send ${mig_dir}/mnt/${subsubvolname} | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/${snap_dir}/${subsubdirname}) 2>&1 | d --progress --title="Backing up containers" --pulsate --auto-close --no-cancel --width=400 - prun /usr/sbin/btrfs subvolume delete ${mig_dir}/mnt/${subsubvolname} + prun /usr/sbin/btrfs subvolume snapshot -r "${TIK_ROOT_MNT}/home/${subsubvol}" "${TIK_ROOT_MNT}/home/${subsubvolname}" + (prun /usr/sbin/btrfs send "${TIK_ROOT_MNT}/home/${subsubvolname}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/${snap_dir}/${subsubdirname}) 2>&1 | d --progress --title="Backing up containers" --pulsate --auto-close --no-cancel --width=400 + prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/home/${subsubvolname}" done prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro true fi - prun /usr/bin/umount ${mig_dir}/mnt - prun /usr/bin/mount -o compress=zstd:1 ${probedpart} ${mig_dir}/mnt - prun /usr/bin/mount -o compress=zstd:1,subvol=/@/var ${probedpart} ${mig_dir}/mnt/var - - mount_etc_for_root "${mig_dir}/mnt" - prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534)' ${mig_dir}/mnt/etc/passwd | prun /usr/bin/awk -F':' '{ $7="/bin/bash"; print };' OFS=':' | prun tee ${mig_dir}/passwd.out - prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534 && $3 != 65533)' ${mig_dir}/mnt/etc/group | prun tee ${mig_dir}/group.out - prun /usr/bin/awk -F'[/:]' '{if ($3 >= 1000 && $3 != 65534) print $1}' ${mig_dir}/mnt/etc/passwd | prun /usr/bin/grep -f - ${mig_dir}/mnt/etc/shadow | prun tee ${mig_dir}/shadow.out - prun /usr/bin/cp -a ${mig_dir}/mnt/etc/subuid ${mig_dir}/subuid - prun /usr/bin/cp -a ${mig_dir}/mnt/etc/subgid ${mig_dir}/subgid + prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534)' "${TIK_ROOT_MNT}/etc/passwd" | prun /usr/bin/awk -F':' '{ $7="/bin/bash"; print };' OFS=':' | prun tee ${mig_dir}/passwd.out + prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534 && $3 != 65533)' "${TIK_ROOT_MNT}/etc/group" | prun tee ${mig_dir}/group.out + prun /usr/bin/awk -F'[/:]' '{if ($3 >= 1000 && $3 != 65534) print $1}' "${TIK_ROOT_MNT}/etc/passwd" | prun /usr/bin/grep -f - "${TIK_ROOT_MNT}/etc/shadow" | prun tee ${mig_dir}/shadow.out + prun /usr/bin/cp -a "${TIK_ROOT_MNT}/etc/subuid" ${mig_dir}/subuid + prun /usr/bin/cp -a "${TIK_ROOT_MNT}/etc/subgid" ${mig_dir}/subgid # It's not guaranteed that the system will have existing network configs, custom localtime or AccountsService - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/NetworkManager/system-connections ${mig_dir}/system-connections - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/localtime ${mig_dir}/localtime - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/AccountsService/users ${mig_dir}/users + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/etc/NetworkManager/system-connections" ${mig_dir}/system-connections + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/etc/localtime" ${mig_dir}/localtime + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/var/lib/AccountsService/users" ${mig_dir}/users prun-opt /usr/bin/chmod 744 ${mig_dir}/users - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/AccountsService/icons ${mig_dir}/icons - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/bluetooth ${mig_dir}/bluetooth - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/var/lib/fprint ${mig_dir}/fprint - prun-opt /usr/bin/cp -a ${mig_dir}/mnt/etc/openvpn ${mig_dir}/openvpn - prun-opt /usr/bin/umount ${mig_dir}/mnt/etc - prun /usr/bin/umount ${mig_dir}/mnt/var - prun /usr/bin/umount ${mig_dir}/mnt - prun /usr/bin/rmdir ${mig_dir}/mnt + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/var/lib/AccountsService/icons" ${mig_dir}/icons + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/var/lib/bluetooth" ${mig_dir}/bluetooth + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/var/lib/fprint" ${mig_dir}/fprint + prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/etc/openvpn" ${mig_dir}/openvpn fi - - # Close eventual mapped LUKS devices - if [ -n "${crypt_opened}" ]; then - for mapped_partition in ${crypt_opened}; do - if [ -e /dev/mapper/crypt_${encrypted_partition} ]; then - # We are going to replace the encrypted partition anyway, so if - # we're unable to gracefully close the device after 5 seconds, - # just give up (hence the prun-opt usage) - wait_count=0 - while ! prun-opt /usr/sbin/cryptsetup luksClose /dev/mapper/${mapped_partition} && [ ${wait_count} -lt 5 ]; do - sleep 1 - wait_count=$((wait_count + 1)) - done - fi - done - fi - fi From ad50a2d40f22f74e2baad05ef2bc66cd42a40c43 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Tue, 16 Dec 2025 01:05:23 +0100 Subject: [PATCH 17/25] Migration module rework The migration module assumes BTRFS quotas are enabled, which might not be always the case. This also let's the mig modules use the global progress pipe instead of opening a pulsateing progress window. --- usr/lib/tik/modules/post/20-mig | 16 +++++++++---- usr/lib/tik/modules/pre/20-mig | 40 ++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/usr/lib/tik/modules/post/20-mig b/usr/lib/tik/modules/post/20-mig index 34515fd..63e00ac 100644 --- a/usr/lib/tik/modules/post/20-mig +++ b/usr/lib/tik/modules/post/20-mig @@ -23,13 +23,17 @@ if [ "${migrate}" == 1 ]; then [ -n "${TIK_ROOT_MNT}" ] || error "MIGRATION FAILED: TIK_ROOT_MNT not set" [ -n "${TIK_ROOT_DEV}" ] || error "MIGRATION FAILED: TIK_ROOT_DEV not set" + tik_progress_step "Preparing migration restore" 0 + prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}/mnt" tik_unmount "${TIK_ROOT_MNT}/home" tik_mount "${TIK_ROOT_DEV}" "mnt" "compress=zstd:1,subvol=/@" + tik_progress_step "Applying target repart configuration" 10 prun /usr/bin/systemd-repart --pretty 0 --root "${TIK_ROOT_MNT}" --dry-run=0 "${TIK_ROOT_DEV}" + tik_progress_step "Restoring user/account data" 25 prun /usr/bin/cat "${mig_dir}/passwd.out" | prun /usr/bin/tee -a "${TIK_ROOT_MNT}/etc/passwd" prun /usr/bin/cat "${mig_dir}/group.out" | prun /usr/bin/tee -a "${TIK_ROOT_MNT}/etc/group" prun /usr/bin/cat "${mig_dir}/shadow.out" | prun /usr/bin/tee -a "${TIK_ROOT_MNT}/etc/shadow" @@ -46,10 +50,11 @@ if [ "${migrate}" == 1 ]; then prun-opt /usr/bin/cp -a "${mig_dir}/openvpn/"* "${TIK_ROOT_MNT}/etc/openvpn" log "[mig] deleting existing /home subvolume under ${TIK_ROOT_MNT}/mnt" + tik_progress_step "Removing existing /home" 35 prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/mnt/home" - (prun /usr/sbin/btrfs send "${mig_dir}/${snap_dir}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive "${TIK_ROOT_MNT}/mnt") 2>&1 \ - | d --progress --title="Restoring /home" --pulsate --auto-close --no-cancel --width=400 + tik_progress_step "Restoring /home" 55 + (prun /usr/sbin/btrfs send "${mig_dir}/${snap_dir}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive "${TIK_ROOT_MNT}/mnt") 2>&1 >/dev/null prun /usr/bin/mv "${TIK_ROOT_MNT}/mnt/${snap_dir}" "${TIK_ROOT_MNT}/mnt/home" prun /usr/sbin/btrfs property set -f -ts "${TIK_ROOT_MNT}/mnt/home" ro false @@ -58,17 +63,20 @@ if [ "${migrate}" == 1 ]; then subsubvolname="$(basename "$subsubvol")" subsubdirname="$(dirname "$subsubvol" | awk -F "${mig_dir}/${snap_dir}" '{print $2}')" - (prun /usr/sbin/btrfs send "${subsubvol}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive "${TIK_ROOT_MNT}/mnt/home/${subsubdirname}") 2>&1 \ - | d --progress --title="Restoring containers" --pulsate --auto-close --no-cancel --width=400 + tik_progress_step "Restoring containers" 70 + (prun /usr/sbin/btrfs send "${subsubvol}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive "${TIK_ROOT_MNT}/mnt/home/${subsubdirname}") 2>&1 >/dev/null prun /usr/sbin/btrfs property set -f -ts "${TIK_ROOT_MNT}/mnt/home/${subsubdirname}/${subsubvolname}" ro false prun-opt /usr/bin/sed -i 's/driver = "overlay"/driver = "btrfs"/g' "${TIK_ROOT_MNT}/mnt/etc/containers/storage.conf" done tik_mount "${TIK_ROOT_DEV}" "home" "compress=zstd:1,subvol=/@/home" + tik_progress_step "Writing firstboot autostart entries" 90 for userhome in "${TIK_ROOT_MNT}/mnt/home"/*/; do writemigdesktop "${userhome}" done tik_unmount "${TIK_ROOT_MNT}/mnt" + + tik_progress_step "Migration restore complete" 100 fi diff --git a/usr/lib/tik/modules/pre/20-mig b/usr/lib/tik/modules/pre/20-mig index 1c34510..c1ea37e 100644 --- a/usr/lib/tik/modules/pre/20-mig +++ b/usr/lib/tik/modules/pre/20-mig @@ -62,10 +62,27 @@ if [ -z "${skipbackup}" ]; then # Determine whether migration is feasible by checking /home size if [ -d "${TIK_ROOT_MNT}/home" ]; then # If /home is btrfs, run quota rescan and check qgroup usage. - prun-opt /usr/sbin/btrfs quota rescan -w "${TIK_ROOT_MNT}/home" | d --progress --title="Detected existing /home.." --pulsate --auto-close --no-cancel --width=400 + # Quotas may not be enabled, so qgroup show can fail. Fall back to du in that case. + prun-opt /usr/sbin/btrfs quota show "${TIK_ROOT_MNT}/home" >/dev/null 2>&1 if [ "${retval}" -eq 0 ]; then - home_size=$(prun /usr/sbin/btrfs qgroup show --raw -f "${TIK_ROOT_MNT}/home" | awk 'NR==2{print $2}') - tik_stick_size=$(prun /usr/sbin/btrfs fi usage --raw ${mig_dir} | grep estimated | awk '{print $3}') + tik_progress_step "Scanning existing /home (btrfs quotas)" 10 + prun-opt /usr/sbin/btrfs quota rescan -w "${TIK_ROOT_MNT}/home" >/dev/null 2>&1 + if [ "${retval}" -eq 0 ]; then + home_size="$(prun-opt /usr/sbin/btrfs qgroup show --raw -f "${TIK_ROOT_MNT}/home" | awk 'NR==2{print $2}')" + fi + else + home_size="" + fi + + if [ -z "${home_size}" ] || [ "${home_size}" = "0" ]; then + log "[mig] btrfs quotas not enabled (or qgroup size unavailable), falling back to du -sb for /home size" + tik_progress_step "Scanning existing /home size" 15 + home_size="$(prun /usr/bin/du -sb "${TIK_ROOT_MNT}/home" 2>/dev/null | awk '{print $1}')" + fi + + tik_progress_step "Checking available space" 25 + tik_stick_size=$(prun /usr/sbin/btrfs fi usage --raw ${mig_dir} | grep estimated | awk '{print $3}') + if [ -n "${home_size}" ] && [ -n "${tik_stick_size}" ]; then if [ "${home_size}" -gt "${tik_stick_size}" ]; then # Not enough space to offer migration migrate=0 @@ -75,7 +92,7 @@ if [ -z "${skipbackup}" ]; then migrate=0 fi else - log "[mig] no usable btrfs quota info found under ${TIK_ROOT_MNT}/home" + log "[mig] could not determine /home size or stick size reliably, migration disabled" migrate=0 fi @@ -97,11 +114,16 @@ if [ -z "${skipbackup}" ]; then if [ "${migrate}" == 1 ]; then # We're migrating, lets go! # Check for existing snapshot from interrupted backup and delete if it exists boo#1224824 + tik_progress_step "Preparing /home snapshot" 35 if [ -d "${TIK_ROOT_MNT}/home/${snap_dir}" ]; then prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/home/${snap_dir}" fi prun /usr/sbin/btrfs subvolume snapshot -r "${TIK_ROOT_MNT}/home" "${TIK_ROOT_MNT}/home/${snap_dir}" - (prun /usr/sbin/btrfs send "${TIK_ROOT_MNT}/home/${snap_dir}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}) 2>&1 | d --progress --title="Backing up /home" --pulsate --auto-close --no-cancel --width=400 + + tik_progress_step "Backing up /home" 45 + (prun /usr/sbin/btrfs send "${TIK_ROOT_MNT}/home/${snap_dir}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}) 2>&1 >/dev/null + + tik_progress_step "Cleaning up snapshot" 55 prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/home/${snap_dir}" # Probe for subvolumes nested beneath /home and back them up also @@ -111,12 +133,16 @@ if [ -z "${skipbackup}" ]; then subsubvolname=$(basename $subsubvol) subsubdirname=$(dirname $subsubvol) prun /usr/sbin/btrfs subvolume snapshot -r "${TIK_ROOT_MNT}/home/${subsubvol}" "${TIK_ROOT_MNT}/home/${subsubvolname}" - (prun /usr/sbin/btrfs send "${TIK_ROOT_MNT}/home/${subsubvolname}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/${snap_dir}/${subsubdirname}) 2>&1 | d --progress --title="Backing up containers" --pulsate --auto-close --no-cancel --width=400 + + tik_progress_step "Backing up containers" 65 + (prun /usr/sbin/btrfs send "${TIK_ROOT_MNT}/home/${subsubvolname}" | pv -f -F "# %b copied in %t %r" | prun /usr/sbin/btrfs receive ${mig_dir}/${snap_dir}/${subsubdirname}) 2>&1 >/dev/null + prun /usr/sbin/btrfs subvolume delete "${TIK_ROOT_MNT}/home/${subsubvolname}" done prun /usr/sbin/btrfs property set -f -ts ${mig_dir}/${snap_dir} ro true fi + tik_progress_step "Exporting user/account data" 80 prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534)' "${TIK_ROOT_MNT}/etc/passwd" | prun /usr/bin/awk -F':' '{ $7="/bin/bash"; print };' OFS=':' | prun tee ${mig_dir}/passwd.out prun /usr/bin/awk -F'[/:]' '($3 >= 1000 && $3 != 65534 && $3 != 65533)' "${TIK_ROOT_MNT}/etc/group" | prun tee ${mig_dir}/group.out prun /usr/bin/awk -F'[/:]' '{if ($3 >= 1000 && $3 != 65534) print $1}' "${TIK_ROOT_MNT}/etc/passwd" | prun /usr/bin/grep -f - "${TIK_ROOT_MNT}/etc/shadow" | prun tee ${mig_dir}/shadow.out @@ -131,5 +157,7 @@ if [ -z "${skipbackup}" ]; then prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/var/lib/bluetooth" ${mig_dir}/bluetooth prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/var/lib/fprint" ${mig_dir}/fprint prun-opt /usr/bin/cp -a "${TIK_ROOT_MNT}/etc/openvpn" ${mig_dir}/openvpn + + tik_progress_step "Migration backup prepared" 100 fi fi From b844152d1889a16612b7be436c60a3eca7c7e222 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Tue, 16 Dec 2025 01:11:52 +0100 Subject: [PATCH 18/25] Make password request show by-id disk name again --- usr/lib/tik/lib/tik-functions-helper | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/usr/lib/tik/lib/tik-functions-helper b/usr/lib/tik/lib/tik-functions-helper index ae9ef9d..5e40f1d 100644 --- a/usr/lib/tik/lib/tik-functions-helper +++ b/usr/lib/tik/lib/tik-functions-helper @@ -70,12 +70,27 @@ tik_target_open() { TIK_CRYPT_PART="" TIK_ROOT_DEV="" - probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" - if [ -z "${probedpart}" ]; then + local encrypted_partition="" + encrypted_partition="$( + lsblk "${TIK_INSTALL_DEVICE}" -p -n -r -o ID-LINK,FSTYPE | tr -s ' ' ";" | grep ";crypto_LUKS" | cut -d\; -f1 | head -n1 + )" + + if [ -z "${encrypted_partition}" ]; then return 1 fi - TIK_CRYPT_PART="${probedpart}" + local crypt_byid="/dev/disk/by-id/${encrypted_partition}" + local crypt_real="" + + if [ -e "${crypt_byid}" ]; then + crypt_real="$(/usr/bin/readlink -f "${crypt_byid}")" + else + crypt_real="$(/usr/bin/readlink -f "/dev/disk/by-id/${encrypted_partition}")" + fi + + [ -n "${crypt_real}" ] || crypt_real="${crypt_byid}" + + TIK_CRYPT_PART="${crypt_real}" export TIK_CRYPT_PART local mapper_name="${mapper_override:-${TIK_CRYPT_MAPPER:-cr_root}}" @@ -107,7 +122,7 @@ tik_target_open() { cancel_label="Skip" fi - pw="$(tik_prompt_passphrase "Encrypted partition (${TIK_CRYPT_PART}) detected" "${cancel_label}")" + pw="$(tik_prompt_passphrase "Encrypted partition (${encrypted_partition}) detected" "${cancel_label}")" if [ -z "${pw}" ]; then if [ "${mode}" = "optional" ]; then @@ -117,7 +132,7 @@ tik_target_open() { error "Encrypted system detected but no passphrase provided" fi - printf "%s" "${pw}" | prun /usr/sbin/cryptsetup luksOpen "${TIK_CRYPT_PART}" "${mapper_name}" + echo -n "${pw}" | prun /usr/sbin/cryptsetup luksOpen "${crypt_byid}" "${mapper_name}" if [ "${retval}" = "0" ]; then TIK_ROOT_DEV="/dev/mapper/${mapper_name}" export TIK_ROOT_DEV @@ -126,7 +141,7 @@ tik_target_open() { return 0 fi - d --warning --no-wrap --title="Incorrect passphrase" --text="Failed to unlock encrypted partition ${TIK_CRYPT_PART}." + d --warning --no-wrap --title="Incorrect passphrase" --text="Failed to unlock encrypted partition ${encrypted_partition}." done } From 3372c7db70ea2e24608b8eb363ef6fb26c036e81 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Wed, 17 Dec 2025 00:17:51 +0100 Subject: [PATCH 19/25] Refactor and fixes Now that all functionality is implemented, it needed to be refactored to make it more maintainable, easier to understand and easier to read. In optional mode, not unlocking an encrypted partition no longer stops the complete operation and all (encrypted) partition on a disk are now checked for a root partition. probe_partitions also stops now after finding the first match. --- usr/lib/tik/lib/tik-core | 54 ++--- usr/lib/tik/lib/tik-core-helper | 70 +++--- usr/lib/tik/lib/tik-functions | 93 +++++--- usr/lib/tik/lib/tik-functions-helper | 344 ++++++++++++++++++--------- usr/lib/tik/modules/post/10-sicu | 2 +- 5 files changed, 354 insertions(+), 209 deletions(-) diff --git a/usr/lib/tik/lib/tik-core b/usr/lib/tik/lib/tik-core index 8a772f2..4fd3de3 100644 --- a/usr/lib/tik/lib/tik-core +++ b/usr/lib/tik/lib/tik-core @@ -11,7 +11,7 @@ tik_detect_identity() { # 0. Config provided => keep it # 1. Selfdeploy (no images found) => read /etc/os-release # 2. One image => try read os-release from image; else name fallback - # 3. Multiple images => if all match => use that; else fallback to UNKNOWN now + # 3. Multiple images => if all match => use that; else fallback to TIK now # If config already provided identity, prefer it if [ -n "${TIK_OS_NAME}" ]; then @@ -258,9 +258,9 @@ get_img() { fi fi - # If identity was UNKNOWN due to multi-image mismatch, finalize it now from the selected image. + # If identity was TIK due to multi-image mismatch, finalize it now from the selected image. if [ "${TIK_INSTALL_IMAGE}" != "TIK_SELFDEPLOY" ]; then - if [ "${TIK_OS_NAME}" = "UNKNOWN SYSTEM" ] || [ -z "${TIK_CRYPT_MAPPER}" ]; then + if [ "${TIK_OS_NAME}" = "TIK" ] || [ -z "${TIK_CRYPT_MAPPER}" ]; then tik_set_identity_for_image "${TIK_INSTALL_IMAGE}" fi fi @@ -336,37 +336,12 @@ tik_init_phase_modules() { *) TIK_PROGRESS_TITLE="Installation" ;; esac + export TIK_CURRENT_PHASE export TIK_PROGRESS_TITLE log "[tik_init_phase_modules] phase=${phase} total_modules=${TIK_TOTAL_MODULES}" } -tik_target_unmount() { - [ "${TIK_MOUNTED_TARGET}" = "1" ] || return 0 - - log "[tik_target_unmount] Unmounting target system" - - # Unmount everything mounted under the target mount root - tik_unmount "${TIK_ROOT_MNT}" - - # As a last resort, unmount the root mount lazily if still busy - prun-opt /usr/bin/umount -l "${TIK_ROOT_MNT}" || true - - # Close luks mapping - if [ -n "${TIK_CRYPT_PART}" ]; then - local mapper_name="${TIK_OPENED_MAPPER:-${TIK_CRYPT_MAPPER:-cr_root}}" - log "[tik_target_unmount] closing encrypted root for ${TIK_CRYPT_PART} (mapper=${mapper_name})" - prun-opt /usr/sbin/cryptsetup luksClose "${mapper_name}" - fi - - TIK_MOUNTED_POINTS="" - TIK_MOUNTED_TARGET=0 - export TIK_MOUNTED_TARGET - - TIK_OPENED_MAPPER="" - export TIK_OPENED_MAPPER -} - load_modules() { local phase=$1 local module_dir @@ -396,3 +371,24 @@ load_modules() { fi tik_module="tik" } + +wipe_keyfile() { + log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}" + + local crypt_part="${TIK_CRYPT_PART}" + + if [ -z "${crypt_part}" ]; then + probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" + crypt_part="${probedpart}" + fi + + if [ -n "${crypt_part}" ] && [ -n "${tik_keyfile}" ] && [ -f "${tik_keyfile}" ]; then + prun /usr/bin/systemd-cryptenroll --unlock-key-file="${tik_keyfile}" --wipe-slot=0 "${crypt_part}" + else + log "[wipe_keyfile] no LUKS partition or keyfile found, skipping slot wipe" + fi + + prun-opt /usr/bin/rm "${tik_keyfile}" + prun-opt keyctl revoke "${tik_keyid}" + prun-opt keyctl reap +} diff --git a/usr/lib/tik/lib/tik-core-helper b/usr/lib/tik/lib/tik-core-helper index 32facf0..fb4e6a9 100644 --- a/usr/lib/tik/lib/tik-core-helper +++ b/usr/lib/tik/lib/tik-core-helper @@ -20,7 +20,7 @@ tik_set_identity_from_name() { local pretty="$1" local sid - [ -n "${pretty}" ] || pretty="UNKNOWN SYSTEM" + [ -n "${pretty}" ] || pretty="TIK" sid="$(tik_sanitize_os_id "${pretty}")" [ -n "${sid}" ] || sid="cr" @@ -99,7 +99,7 @@ tik_set_identity() { } tik_set_identity_unknown() { - TIK_OS_NAME="UNKNOWN SYSTEM" + TIK_OS_NAME="TIK" export TIK_OS_NAME TIK_OS_ID="cr" @@ -222,6 +222,7 @@ probe_partitions() { # Fallback to unix device in order to fix issue with USB devices probedpart="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" log "[probe_partitions] Partition ${probedpart} found" + break else # Check if ${filematch} exists # Fallback to unix device in order to fix issue with USB devices part="$(/usr/bin/readlink -f "/dev/disk/by-id/""${part}")" @@ -231,10 +232,13 @@ probe_partitions() { # Fallback to unix device in order to fix issue with USB devices probedpart="${part}" log "[probe_partitions] Partition ${probedpart} found" + prun-opt /usr/bin/umount ${probe_dir}/mnt + break fi prun-opt /usr/bin/umount ${probe_dir}/mnt fi done + prun /usr/bin/rmdir ${probe_dir}/mnt } @@ -246,27 +250,6 @@ create_keyfile() { tik_keyid=$(prun cat "${tik_keyfile}" | prun keyctl padd user cryptenroll @u) } -wipe_keyfile() { - log "[wipe_keyfile] Deleting keyfile ${tik_keyfile}" - - local crypt_part="${TIK_CRYPT_PART}" - - if [ -z "${crypt_part}" ]; then - probe_partitions "${TIK_INSTALL_DEVICE}" "crypto_LUKS" - crypt_part="${probedpart}" - fi - - if [ -n "${crypt_part}" ] && [ -n "${tik_keyfile}" ] && [ -f "${tik_keyfile}" ]; then - prun /usr/bin/systemd-cryptenroll --unlock-key-file="${tik_keyfile}" --wipe-slot=0 "${crypt_part}" - else - log "[wipe_keyfile] no LUKS partition or keyfile found, skipping slot wipe" - fi - - prun-opt /usr/bin/rm "${tik_keyfile}" - prun-opt keyctl revoke "${tik_keyid}" - prun-opt keyctl reap -} - dump_image_dd() { local image_source_files=$1 local image_target=$2 @@ -310,26 +293,37 @@ dump_image_repart_self() { prun systemd-repart --no-pager --pretty=0 --empty=force --dry-run=no --key-file="${tik_keyfile}" --generate-fstab=/etc/fstab.repart "${image_target}" > >(d --progress --title="Installing ${TIK_OS_NAME}" --text="Deploying OS Image" --pulsate --auto-close --no-cancel --width=400) } -tik_cleanup_mounts() { - if [ -n "${TIK_MOUNTED_POINTS}" ] || [ "${TIK_MOUNTED_TARGET}" = "1" ]; then - log "[tik_cleanup_mounts] mounts detected after phase '${TIK_CURRENT_PHASE}', unmounting" - tik_target_unmount - fi -} +tik_target_unmount() { + [ "${TIK_MOUNTED_TARGET}" = "1" ] || return 0 -tik_rewrite_overlay_opts() { - local opts="$1" - local root="$2" + log "[tik_target_unmount] Unmounting target system" - opts="$(echo "$opts" | sed -E "s#lowerdir=/#lowerdir=${root}/#g; s#upperdir=/#upperdir=${root}/#g; s#workdir=/#workdir=${root}/#g")" - opts="$(echo "$opts" | sed -E "s#lowerdir=${root}//#lowerdir=${root}/#g; s#upperdir=${root}//#upperdir=${root}/#g; s#workdir=${root}//#workdir=${root}/#g")" - opts="$(echo "$opts" | sed -E "s#lowerdir=(${root}/[^,]*):/#lowerdir=\1:${root}/#g")" + # Unmount everything mounted under the target mount root + tik_unmount "${TIK_ROOT_MNT}" + + # As a last resort, unmount the root mount lazily if still busy + prun-opt /usr/bin/umount -l "${TIK_ROOT_MNT}" || true + + # Close luks mapping + if [ -n "${TIK_CRYPT_PART}" ]; then + local mapper_name="${TIK_OPENED_MAPPER:-${TIK_CRYPT_MAPPER:-cr_root}}" + log "[tik_target_unmount] closing encrypted root for ${TIK_CRYPT_PART} (mapper=${mapper_name})" + prun-opt /usr/sbin/cryptsetup luksClose "${mapper_name}" + fi - echo "$opts" + TIK_MOUNTED_POINTS="" + TIK_MOUNTED_TARGET=0 + export TIK_MOUNTED_TARGET + + TIK_OPENED_MAPPER="" + export TIK_OPENED_MAPPER } -tik_is_opt_set() { - echo ",$1," | grep -q ",$2," +tik_cleanup_mounts() { + if [ -n "${TIK_MOUNTED_POINTS}" ] || [ "${TIK_MOUNTED_TARGET}" = "1" ]; then + log "[tik_cleanup_mounts] mounts detected after phase '${TIK_CURRENT_PHASE}', unmounting" + tik_target_unmount + fi } tik_close_progress() { diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index 6e3ffd5..b7cae59 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -214,48 +214,83 @@ tik_target_mount() { [ "${TIK_MOUNTED_TARGET}" = "1" ] && return 0 - tik_target_mount_prepare - - # open root device - tik_target_open "${mapper_override}" "${mode}" - case "$?" in - 0) ;; - 1) - if [ "${mode}" = "required" ]; then - error "No encrypted system found where one was required" + tik_mount_prepare + + # open encrypted device(s) + local crypt_parts="" + crypt_parts="$( + lsblk "${TIK_INSTALL_DEVICE}" -p -n -r -o ID-LINK,FSTYPE | tr -s ' ' ";" | grep ";crypto_LUKS" | cut -d\; -f1 + )" + + if [ -n "${crypt_parts}" ]; then + local crypt_part + for crypt_part in ${crypt_parts}; do + tik_crypt_open "${mapper_override}" "${mode}" "${crypt_part}" + case "$?" in + 0) ;; + *) + return "$?" + ;; + esac + + # If we opened something, check whether it looks like the root filesystem + if [ -n "${TIK_ROOT_DEV}" ]; then + if tik_is_root_partition "${TIK_ROOT_DEV}"; then + break + fi + + # Not root -> close and try the next encrypted partition + if [ -n "${TIK_OPENED_MAPPER}" ]; then + prun-opt /usr/sbin/cryptsetup luksClose "${TIK_OPENED_MAPPER}" + fi + TIK_CRYPT_PART="" + export TIK_CRYPT_PART + TIK_ROOT_DEV="" + export TIK_ROOT_DEV + TIK_OPENED_MAPPER="" + export TIK_OPENED_MAPPER fi - return 1 - ;; - 2) - return 2 - ;; - esac + done + else + # No encrypted partitions found -> search all partitions for a root filesystem + local part + local parts="" + parts="$( + lsblk "${TIK_INSTALL_DEVICE}" -p -n -r -o NAME,TYPE | awk '$2=="part"{print $1}' + )" + + for part in ${parts}; do + if tik_is_root_partition "${part}"; then + TIK_ROOT_DEV="${part}" + export TIK_ROOT_DEV + break + fi + done + fi if [ -z "${TIK_ROOT_DEV}" ]; then - if ! TIK_ROOT_DEV="$(tik_target_find_root_partition)"; then - if [ "${mode}" = "required" ]; then - error "No existing system found" - fi - return 1 + if [ "${mode}" = "required" ]; then + error "No existing system found" fi - export TIK_ROOT_DEV + return 1 fi + export TIK_ROOT_DEV log "[tik_target_mount] root device is ${TIK_ROOT_DEV}" # mount root - tik_target_mount_root "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" + tik_mount_root "${TIK_ROOT_DEV}" "${TIK_ROOT_MNT}" # Assemble fstab in a writable location local assembled - assembled="$(tik_target_fstab_assemble "${TIK_ROOT_MNT}")" + assembled="$(tik_fstab_assemble "${TIK_ROOT_MNT}")" TIK_TARGET_FSTAB="${assembled}" export TIK_TARGET_FSTAB TIK_ASSEMBLED_FSTAB="${assembled}" export TIK_ASSEMBLED_FSTAB # mount everything described by assembled fstab - tik_target_mount_from_fstab "${TIK_ROOT_MNT}" + tik_mount_from_fstab "${TIK_ROOT_MNT}" # Probe ESP log "[tik_target_mount] probing for ESP partition on ${TIK_INSTALL_DEVICE}" @@ -271,7 +306,7 @@ tik_target_mount() { fi # bind pseudo fs for chroot usage - tik_target_mount_pseudofs "${TIK_ROOT_MNT}" + tik_mount_pseudofs "${TIK_ROOT_MNT}" log "[tik_target_mount] Target system mounted" @@ -279,13 +314,13 @@ tik_target_mount() { export TIK_MOUNTED_TARGET } -tik_target_write_fstab() { - [ "${TIK_MOUNTED_TARGET}" = "1" ] || error "tik_target_write_fstab: target system not mounted" +tik_write_fstab() { + [ "${TIK_MOUNTED_TARGET}" = "1" ] || error "tik_write_fstab: target system not mounted" # Install assembled fstab into the target if possible if [ -n "${TIK_ASSEMBLED_FSTAB}" ] && [ -f "${TIK_ASSEMBLED_FSTAB}" ]; then - tik_target_fstab_install "${TIK_ROOT_MNT}" "${TIK_ASSEMBLED_FSTAB}" || true + tik_fstab_install "${TIK_ROOT_MNT}" "${TIK_ASSEMBLED_FSTAB}" || true else - error "tik_target_write_fstab: no assembled fstab available" + error "tik_write_fstab: no assembled fstab available" fi } diff --git a/usr/lib/tik/lib/tik-functions-helper b/usr/lib/tik/lib/tik-functions-helper index 5e40f1d..a85e3b1 100644 --- a/usr/lib/tik/lib/tik-functions-helper +++ b/usr/lib/tik/lib/tik-functions-helper @@ -18,7 +18,7 @@ tik_prompt_passphrase() { echo -n "${passphrase}" } -tik_target_mount_prepare() { +tik_mount_prepare() { TIK_ROOT_MNT="${TIK_ROOT_MNT:=/var/lib/tik/root}" export TIK_ROOT_MNT prun /usr/bin/mkdir -p "${TIK_ROOT_MNT}" @@ -39,20 +39,19 @@ tik_target_mount_prepare() { export TIK_OPENED_MAPPER } -tik_target_find_root_partition() { +tik_is_root_partition() { + local dev="$1" local fs + + [ -n "${dev}" ] || return 1 + for fs in btrfs ext4 xfs f2fs; do - probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/fstab" - if [ -n "${probedpart}" ]; then - echo "${probedpart}" - return 0 - fi - probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/etc/os-release" + probe_partitions "${dev}" "${fs}" "/etc/fstab" if [ -n "${probedpart}" ]; then echo "${probedpart}" return 0 fi - probe_partitions "${TIK_INSTALL_DEVICE}" "${fs}" "/usr/lib/os-release" + probe_partitions "${dev}" "${fs}" "/etc/os-release" if [ -n "${probedpart}" ]; then echo "${probedpart}" return 0 @@ -61,31 +60,25 @@ tik_target_find_root_partition() { return 1 } -tik_target_open() { +tik_crypt_open() { # $1 = mapper override # $2 = mode: "required" | "optional" local mapper_override="$1" local mode="${2:-required}" + local crypt_part="$3" TIK_CRYPT_PART="" TIK_ROOT_DEV="" - local encrypted_partition="" - encrypted_partition="$( - lsblk "${TIK_INSTALL_DEVICE}" -p -n -r -o ID-LINK,FSTYPE | tr -s ' ' ";" | grep ";crypto_LUKS" | cut -d\; -f1 | head -n1 - )" + [ -n "${crypt_part}" ] || return 0 - if [ -z "${encrypted_partition}" ]; then - return 1 - fi - - local crypt_byid="/dev/disk/by-id/${encrypted_partition}" + local crypt_byid="/dev/disk/by-id/${crypt_part}" local crypt_real="" if [ -e "${crypt_byid}" ]; then crypt_real="$(/usr/bin/readlink -f "${crypt_byid}")" else - crypt_real="$(/usr/bin/readlink -f "/dev/disk/by-id/${encrypted_partition}")" + crypt_real="$(/usr/bin/readlink -f "/dev/disk/by-id/${crypt_part}")" fi [ -n "${crypt_real}" ] || crypt_real="${crypt_byid}" @@ -95,14 +88,27 @@ tik_target_open() { local mapper_name="${mapper_override:-${TIK_CRYPT_MAPPER:-cr_root}}" - log "[tik_target_open] encrypted partition detected: ${TIK_CRYPT_PART} (mapper=${mapper_name})" + log "[tik_crypt_open] encrypted partition detected: ${TIK_CRYPT_PART} (mapper=${mapper_name})" if [ -e "/dev/mapper/${mapper_name}" ]; then - TIK_ROOT_DEV="/dev/mapper/${mapper_name}" - export TIK_ROOT_DEV - TIK_OPENED_MAPPER="${mapper_name}" - export TIK_OPENED_MAPPER - return 0 + # Mapper already exists. Only reuse it if it maps to the partition we are trying to open. + local mapped_dev="" + mapped_dev="$(/usr/sbin/cryptsetup status "${mapper_name}" 2>/dev/null | /usr/bin/awk -F': *' '$1=="device"{print $2; exit}')" + + if [ -n "${mapped_dev}" ]; then + mapped_dev="$(/usr/bin/readlink -f "${mapped_dev}")" + fi + + if [ -n "${mapped_dev}" ] && [ "${mapped_dev}" = "${TIK_CRYPT_PART}" ]; then + log "[tik_crypt_open] mapper ${mapper_name} already open for ${mapped_dev}, reusing" + TIK_ROOT_DEV="/dev/mapper/${mapper_name}" + export TIK_ROOT_DEV + TIK_OPENED_MAPPER="${mapper_name}" + export TIK_OPENED_MAPPER + return 0 + fi + + error "Mapper ${mapper_name} is already open for ${mapped_dev:-unknown}, cannot open ${TIK_CRYPT_PART}." fi if [ -n "${tik_keyfile}" ] && [ -f "${tik_keyfile}" ]; then @@ -122,12 +128,12 @@ tik_target_open() { cancel_label="Skip" fi - pw="$(tik_prompt_passphrase "Encrypted partition (${encrypted_partition}) detected" "${cancel_label}")" + pw="$(tik_prompt_passphrase "Encrypted partition (${crypt_part}) detected" "${cancel_label}")" if [ -z "${pw}" ]; then if [ "${mode}" = "optional" ]; then - log "[tik_target_open] user skipped unlocking" - return 2 + log "[tik_crypt_open] user skipped unlocking" + return 0 fi error "Encrypted system detected but no passphrase provided" fi @@ -141,7 +147,7 @@ tik_target_open() { return 0 fi - d --warning --no-wrap --title="Incorrect passphrase" --text="Failed to unlock encrypted partition ${encrypted_partition}." + d --warning --no-wrap --title="Incorrect passphrase" --text="Failed to unlock encrypted partition ${crypt_part}." done } @@ -154,38 +160,65 @@ tik_mountopts_filter() { return 0 fi - /usr/bin/awk -v opts="${opts}" -v ignore="${ignore_list}" ' - BEGIN { - n=split(ignore, ig, /,/) - for (i=1;i<=n;i++) { - gsub(/^[[:space:]]+|[[:space:]]+$/, "", ig[i]) - if (ig[i] != "") drop[ig[i]]=1 - } - - m=split(opts, a, /,/) - out="" - for (j=1;j<=m;j++) { - o=a[j] - gsub(/^[[:space:]]+|[[:space:]]+$/, "", o) - if (o=="") continue - - # exact match (e.g. ro) - if (drop[o]) continue - - # key=value match (e.g. ro=vfs) where ignore has 'ro' or 'ro=' or 'ro=vfs' - split(o, kv, /=/) - k=kv[1] - if (drop[k]) continue - if (drop[k"="]) continue - if (drop[o]) continue - - if (out=="") out=o - else out=out","o - } - if (out=="") out="-" - print out - } - ' + # Build ignore tables from ignore_list + local -A drop_exact=() + local -A drop_key=() + local ig + + IFS=',' read -r -a _ignore_arr <<< "${ignore_list}" + for ig in "${_ignore_arr[@]}"; do + ig="${ig#"${ig%%[![:space:]]*}"}" + ig="${ig%"${ig##*[![:space:]]}"}" + [ -n "${ig}" ] || continue + + drop_exact["${ig}"]=1 + + # If ignore entry is "key=" drop any key=... + if [[ "${ig}" == *"=" ]]; then + drop_key["${ig%=}"]=1 + # If ignore entry is "key=value" also mark key for dropping + elif [[ "${ig}" == *"="* ]]; then + drop_key["${ig%%=*}"]=1 + else + drop_key["${ig}"]=1 + fi + done + + local out=() + local o k + IFS=',' read -r -a _opts_arr <<< "${opts}" + for o in "${_opts_arr[@]}"; do + o="${o#"${o%%[![:space:]]*}"}" + o="${o%"${o##*[![:space:]]}"}" + [ -n "${o}" ] || continue + + # Exact ignore match + if [ -n "${drop_exact[${o}]}" ]; then + continue + fi + + # key=value case + if [[ "${o}" == *"="* ]]; then + k="${o%%=*}" + if [ -n "${drop_key[${k}]}" ] || [ -n "${drop_exact[${k}=]}" ]; then + continue + fi + else + # plain key case + if [ -n "${drop_key[${o}]}" ]; then + continue + fi + fi + + out+=("${o}") + done + + if [ "${#out[@]}" -eq 0 ]; then + echo "-" + else + local joined + (IFS=','; joined="${out[*]}"; echo "${joined}") + fi } tik_mountopts_ignore_list() { @@ -199,7 +232,61 @@ tik_mountopts_apply_filter() { tik_mountopts_filter "${opts}" "${ignore_opts}" } -tik_target_mount_root() { +tik_fstab_get_mount_opts() { + local fstab="$1" + local mountpoint="$2" + local line spec mp fstype opts rest + + [ -f "${fstab}" ] || return 1 + + while IFS= read -r line; do + # strip leading whitespace + line="${line#"${line%%[![:space:]]*}"}" + [ -n "${line}" ] || continue + [[ "${line}" = \#* ]] && continue + + # split into fields + read -r spec mp fstype opts rest <<< "${line}" + [ -n "${spec}" ] || continue + [ -n "${mp}" ] || continue + + if [ "${mp}" = "${mountpoint}" ]; then + echo "${opts}" + return 0 + fi + done < "${fstab}" + + return 1 +} + +tik_fstab_read_entry() { + # Reads a single fstab-like line and outputs 4 fields via globals: + # TIK_FSTAB_SPEC, TIK_FSTAB_MP, TIK_FSTAB_FSTYPE, TIK_FSTAB_OPTS + # Returns: + # 0 = parsed entry + # 1 = skip (blank/comment/invalid) + local line="$1" + local rest + + TIK_FSTAB_SPEC="" + TIK_FSTAB_MP="" + TIK_FSTAB_FSTYPE="" + TIK_FSTAB_OPTS="" + + line="${line#"${line%%[![:space:]]*}"}" + [ -n "${line}" ] || return 1 + [[ "${line}" = \#* ]] && return 1 + + read -r TIK_FSTAB_SPEC TIK_FSTAB_MP TIK_FSTAB_FSTYPE TIK_FSTAB_OPTS rest <<< "${line}" + + [ -n "${TIK_FSTAB_SPEC}" ] || return 1 + [ -n "${TIK_FSTAB_MP}" ] || return 1 + [ -n "${TIK_FSTAB_FSTYPE}" ] || return 1 + + return 0 +} + +tik_mount_root() { local rootdev=$1 local mnt=$2 @@ -207,19 +294,15 @@ tik_target_mount_root() { local tmp_mnt local root_opts tmp_mnt="$(prun /usr/bin/mktemp -d /tmp/tik-rootprobe.XXXXXXXXXX)" - log "[tik_target_mount_root] probing mount options for / by temporarily mounting ${rootdev} on ${tmp_mnt}" + log "[tik_mount_root] probing mount options for / by temporarily mounting ${rootdev} on ${tmp_mnt}" prun /usr/bin/mount -o ro "${rootdev}" "${tmp_mnt}" if [ -f "${tmp_mnt}/etc/fstab" ]; then - root_opts="$(prun /usr/bin/awk ' - $0 ~ /^[[:space:]]*#/ { next } - NF < 4 { next } - $2 == "/" { print $4; exit } - ' "${tmp_mnt}/etc/fstab")" - log "[tik_target_mount_root] probed root mount options: '${root_opts}'" + root_opts="$(tik_fstab_get_mount_opts "${tmp_mnt}/etc/fstab" "/")" + log "[tik_mount_root] probed root mount options: '${root_opts}'" else - log "[tik_target_mount_root] no ${tmp_mnt}/etc/fstab found while probing, falling back to default mount options" + log "[tik_mount_root] no ${tmp_mnt}/etc/fstab found while probing, falling back to default mount options" root_opts="" fi @@ -228,7 +311,7 @@ tik_target_mount_root() { if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then root_opts="$(tik_mountopts_apply_filter "${root_opts}")" - log "[tik_target_mount_root] filtered root mount options: '${root_opts}'" + log "[tik_mount_root] filtered root mount options: '${root_opts}'" fi if [ -n "${root_opts}" ] && [ "${root_opts}" != "-" ]; then @@ -263,6 +346,21 @@ tik_is_safe_mount() { return 0 } +tik_rewrite_overlay_opts() { + local opts="$1" + local root="$2" + + opts="$(echo "$opts" | sed -E "s#lowerdir=/#lowerdir=${root}/#g; s#upperdir=/#upperdir=${root}/#g; s#workdir=/#workdir=${root}/#g")" + opts="$(echo "$opts" | sed -E "s#lowerdir=${root}//#lowerdir=${root}/#g; s#upperdir=${root}//#upperdir=${root}/#g; s#workdir=${root}//#workdir=${root}/#g")" + opts="$(echo "$opts" | sed -E "s#lowerdir=(${root}/[^,]*):/#lowerdir=\1:${root}/#g")" + + echo "$opts" +} + +tik_is_opt_set() { + echo ",$1," | grep -q ",$2," +} + tik_override_mountpoint() { local fstab=$1 local mp=$2 @@ -271,28 +369,55 @@ tik_override_mountpoint() { local tmp tmp="$(prun /usr/bin/mktemp /tmp/tik-fstab.XXXXXXXXXX)" - prun /usr/bin/awk -v want="${mp}" -v rep="${newline}" ' - BEGIN { replaced=0 } - { - if ($0 ~ /^[[:space:]]*#/ || NF < 2) { print; next } - if (!replaced && $2 == want) { print rep; replaced=1; next } - print - } - END { if (!replaced) print rep } - ' "${fstab}" | prun /usr/bin/tee "${tmp}" >/dev/null + local replaced=0 + local line spec cur_mp rest + + { + while IFS= read -r line || [ -n "${line}" ]; do + # Keep comments as-is + if [[ "${line}" =~ ^[[:space:]]*# ]]; then + printf '%s\n' "${line}" + continue + fi + + # If we can't read at least 2 fields, keep as-is + spec="" + cur_mp="" + rest="" + read -r spec cur_mp rest <<< "${line}" + if [ -z "${spec}" ] || [ -z "${cur_mp}" ]; then + printf '%s\n' "${line}" + continue + fi + + # Replace first matching mountpoint + if [ "${replaced}" -eq 0 ] && [ "${cur_mp}" = "${mp}" ]; then + printf '%s\n' "${newline}" + replaced=1 + continue + fi + + printf '%s\n' "${line}" + done < <(prun /usr/bin/cat "${fstab}") + + # If not replaced, append new entry + if [ "${replaced}" -eq 0 ]; then + printf '%s\n' "${newline}" + fi + } | prun /usr/bin/tee "${tmp}" >/dev/null prun /usr/bin/cp -a "${tmp}" "${fstab}" prun /usr/bin/rm -f "${tmp}" } -tik_target_can_write_etc() { +tik_can_write_etc() { local mnt=$1 local probe="${mnt}/etc/.tik-write-probe.$$" prun-opt /usr/bin/sh -c "touch '${probe}' && rm -f '${probe}'" [ "${retval}" = "0" ] } -tik_target_fstab_assemble() { +tik_fstab_assemble() { local mnt=$1 local fstab="${mnt}/etc/fstab" @@ -306,13 +431,13 @@ tik_target_fstab_assemble() { # Prefer systemd-repart generated fstab if present, otherwise use existing fstab. if [ -f "${fstab_repart}" ]; then - log "[tik_target_fstab_assemble] using ${fstab_repart} as base fstab" + log "[tik_fstab_assemble] using ${fstab_repart} as base fstab" prun /usr/bin/cp -a "${fstab_repart}" "${assembled}" elif [ -f "${fstab}" ]; then - log "[tik_target_fstab_assemble] using ${fstab} as base fstab" + log "[tik_fstab_assemble] using ${fstab} as base fstab" prun /usr/bin/cp -a "${fstab}" "${assembled}" else - log "[tik_target_fstab_assemble] no base fstab found, leaving assembled fstab empty at ${assembled}" + log "[tik_fstab_assemble] no base fstab found, leaving assembled fstab empty at ${assembled}" prun /usr/bin/tee "${assembled}" >/dev/null < Date: Thu, 18 Dec 2025 20:54:06 +0100 Subject: [PATCH 20/25] Search all partitions if no enrypted root partition is found Otherwise it won't check regular partitions when there is 1 encrypted partition. --- usr/lib/tik/lib/tik-functions | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/usr/lib/tik/lib/tik-functions b/usr/lib/tik/lib/tik-functions index b7cae59..233f1fe 100644 --- a/usr/lib/tik/lib/tik-functions +++ b/usr/lib/tik/lib/tik-functions @@ -251,8 +251,9 @@ tik_target_mount() { export TIK_OPENED_MAPPER fi done - else - # No encrypted partitions found -> search all partitions for a root filesystem + fi + # No enrypted root partition found -> search all partitions for a root filesystem + if [ -z "$TIK_ROOT_DEV" ]; then local part local parts="" parts="$( @@ -266,9 +267,7 @@ tik_target_mount() { break fi done - fi - - if [ -z "${TIK_ROOT_DEV}" ]; then + # Also no root partition found if [ "${mode}" = "required" ]; then error "No existing system found" fi From 48b509d72296007282528c31fd7d86cccf481ec3 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sun, 21 Dec 2025 14:39:19 +0100 Subject: [PATCH 21/25] Make welcome module reusable for non-Aeon systems We can use the symbolic icon symlink instead of hardoding Aeon's icon --- usr/lib/tik/modules/pre/10-welcome | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/tik/modules/pre/10-welcome b/usr/lib/tik/modules/pre/10-welcome index 2c46683..568a857 100644 --- a/usr/lib/tik/modules/pre/10-welcome +++ b/usr/lib/tik/modules/pre/10-welcome @@ -5,7 +5,7 @@ # SPDX-FileCopyrightText: Copyright 2025 Tobias Görgens proceedInstall() { - d --info --ok-label="Install Now" --no-wrap --width=300 --height=300 --icon=distributor-logo-Aeon-symbolic --title="" --text="Welcome to ${TIK_OS_NAME}\n\nPlease press Install Now to continue" + d --info --ok-label="Install Now" --no-wrap --width=300 --height=300 --icon=distributor-logo-symbolic --title="" --text="Welcome to ${TIK_OS_NAME}\n\nPlease press Install Now to continue" } displayACWarningMsg() { From 01bd2b73e304d114022b5dd0093e440c15f854d8 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Sun, 21 Dec 2025 15:15:57 +0100 Subject: [PATCH 22/25] Only enable GNOMe initial setup if it was disabled --- usr/lib/tik/modules/post/10-sicu | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/usr/lib/tik/modules/post/10-sicu b/usr/lib/tik/modules/post/10-sicu index 6c52605..61b3e8a 100644 --- a/usr/lib/tik/modules/post/10-sicu +++ b/usr/lib/tik/modules/post/10-sicu @@ -12,8 +12,10 @@ sicu() { tik_progress_step "Cleaning up installer user" 0 log "[sicu] Deleting tik user" prun /usr/bin/chroot "${TIK_ROOT_MNT}" userdel -r tik - log "[sicu] Enabling initial-setup" - prun /usr/bin/rm "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" + if [ -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" ]; then + log "[sicu] Enabling initial-setup" + prun /usr/bin/rm "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" + fi log "[sicu] Disabling tik autologin" prun /usr/bin/sed -i 's/DISPLAYMANAGER_AUTOLOGIN="tik"/DISPLAYMANAGER_AUTOLOGIN=""/' "${TIK_ROOT_MNT}/etc/sysconfig/displaymanager" } From 66d12a31ab5ba17eb06515b18de1b59a9ba5d1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6rgens?= <19935382+TobiPeterG@users.noreply.github.com> Date: Sat, 14 Feb 2026 23:30:28 +0100 Subject: [PATCH 23/25] Fix gnome-initial-setup test --- usr/lib/tik/modules/post/10-sicu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/tik/modules/post/10-sicu b/usr/lib/tik/modules/post/10-sicu index 61b3e8a..c7724a1 100644 --- a/usr/lib/tik/modules/post/10-sicu +++ b/usr/lib/tik/modules/post/10-sicu @@ -12,7 +12,7 @@ sicu() { tik_progress_step "Cleaning up installer user" 0 log "[sicu] Deleting tik user" prun /usr/bin/chroot "${TIK_ROOT_MNT}" userdel -r tik - if [ -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" ]; then + if prun /usr/bin/test -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup"; then log "[sicu] Enabling initial-setup" prun /usr/bin/rm "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" fi From 10706e2f7c71258fa09ac310b7370e0ffd846812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6rgens?= <19935382+TobiPeterG@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:57:08 +0100 Subject: [PATCH 24/25] Fix sicu file test --- usr/lib/tik/modules/post/10-sicu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/tik/modules/post/10-sicu b/usr/lib/tik/modules/post/10-sicu index c7724a1..ba9e0d3 100644 --- a/usr/lib/tik/modules/post/10-sicu +++ b/usr/lib/tik/modules/post/10-sicu @@ -12,7 +12,7 @@ sicu() { tik_progress_step "Cleaning up installer user" 0 log "[sicu] Deleting tik user" prun /usr/bin/chroot "${TIK_ROOT_MNT}" userdel -r tik - if prun /usr/bin/test -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup"; then + if prun-opt /usr/bin/test -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup"; then log "[sicu] Enabling initial-setup" prun /usr/bin/rm "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" fi From 6057dc536b021973eb40d6be8c3abb74ac60fb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6rgens?= <19935382+TobiPeterG@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:31:54 +0100 Subject: [PATCH 25/25] Fix check one more time --- usr/lib/tik/modules/post/10-sicu | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usr/lib/tik/modules/post/10-sicu b/usr/lib/tik/modules/post/10-sicu index ba9e0d3..3fb9f00 100644 --- a/usr/lib/tik/modules/post/10-sicu +++ b/usr/lib/tik/modules/post/10-sicu @@ -12,7 +12,8 @@ sicu() { tik_progress_step "Cleaning up installer user" 0 log "[sicu] Deleting tik user" prun /usr/bin/chroot "${TIK_ROOT_MNT}" userdel -r tik - if prun-opt /usr/bin/test -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup"; then + prun-opt /usr/bin/test -e "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" + if [ "$retval" = "0" ]; then log "[sicu] Enabling initial-setup" prun /usr/bin/rm "${TIK_ROOT_MNT}/var/lib/gdm/block-initial-setup" fi