From: Plom Heller Date: Mon, 31 Aug 2026 21:10:55 +0000 (+0200) Subject: Split into separate tools for setting up LUKS LVM/partitions, Debian, etc. X-Git-Url: https://plomlompom.com/repos/%22https:/validator.w3.org/static/%7Broute%7D?a=commitdiff_plain;h=d4f298e9b3e4b6e5ec41921faef1d3e04a91eeb8;p=confplom Split into separate tools for setting up LUKS LVM/partitions, Debian, etc. --- diff --git a/_lib.sh b/_lib.sh new file mode 100644 index 0000000..94e5288 --- /dev/null +++ b/_lib.sh @@ -0,0 +1,151 @@ +set -Ceu +SCRIPT_NAME=$0 + +# constants unlikely to change +NAME_DEV=dev +PATH_DEV="/${NAME_DEV}" +TO_RBIND="${NAME_DEV} proc sys" + +# constants we might want to change at some point +NAME_DATA=data +PATH_MNT=/mnt/debinst + +# path constructors +path_luks_mapper() { + printf '%s/mapper/%s' "${PATH_DEV}" "$1" +} +path_vg() { + printf '%s/%s' "${PATH_DEV}" "$1" +} + +# helpers: logging, testing, failing basics +msg_nonl() { + printf '[## %s ##] ' "${SCRIPT_NAME}" + # shellcheck disable=SC2059 + # (assume we'll always pass a literal format string) + printf -- "$@" +} +msg() { + msg_nonl "$@" + printf '\n' +} +die() { + msg '%s' "$*" >&2 + exit 1 +} +error() { + die "error: $*" +} +try_quiet() { + "$@" >/dev/null 2>&1 +} + +# helpers: more involved testing +check_chars_legality() { + case "$1" in *[!A-Za-z0-9_.-]*|"") + error "illegal characters in $2 '$1'" ;; + esac +} +check_tools() { + for CMD in "$@"; do + try_quiet command -v "${CMD}"\ + || error "required tool not found: ${CMD}" + done +} +check_input_partition_mountable() { + local PARTITION=$1 + [ -b "${PARTITION}" ]\ + || error "${PARTITION} is not a block device" + try_quiet findmnt --source "${PARTITION}"\ + && error "${PARTITION} is already mounted" + true +} +check_input_new_luksvg() { + local NAME_LUKSVG=$1 + local PATH_LUKS_MAPPER + PATH_LUKS_MAPPER=$(path_luks_mapper "${NAME_LUKSVG}") + check_chars_legality "${NAME_LUKSVG}" "volume group name" + try_quiet vgs "${NAME_LUKSVG}"\ + && error "volume group '${NAME_LUKSVG}' already exists" + [ -e "${PATH_LUKS_MAPPER}" ]\ + && error "${PATH_LUKS_MAPPER} already exists" + true +} +check_input_openable_luksvg() { + local PARTITION=$1 + local NAME_LUKSVG=$2 + check_input_new_luksvg "${NAME_LUKSVG}" + try_quiet cryptsetup isLuks "${PARTITION}"\ + || error "${PARTITION} not a LUKS container" +} + +# helpers: luks open/close +open_luksvg () { + local PARTITION=$1 + local LUKSVG=$2 + msg 'Opening LUKS container as "%s" …' "${LUKSVG}" + cryptsetup luksOpen "${PARTITION}" "${LUKSVG}" +} +close_luksvg () { + local LUKSVG=$1 + msg 'Deactivating volume group and closing LUKS container …' + vgchange -an "${LUKSVG}" + cryptsetup luksClose "${LUKSVG}" +} + +# helpers: mount/unmount root +mount_privately() { + local TO_MOUNT=$1 + # to facilitate later unmounting of the chrooted system: isolate our mounts + # against propagation to services sandboxed with PrivateMounts=yes (e.g. + # systemd-udevd), into whose private namespaces our later umount might fail + # to reach for closing their references e.g. into what we'll want to + # vgchange -an, only to be blocked by referenced devices claimed as "busy" + msg "Privatize script's process' mount namespace …" + mount --make-rprivate / + + # mount and install base + msg 'Mounting %s at %s …' "${TO_MOUNT}" "${PATH_MNT}" + mkdir -p "${PATH_MNT}" + mount "${TO_MOUNT}" "${PATH_MNT}" +} +await_path() { + local TO_AWAIT=$1 + msg_nonl 'Waiting for %s to appear …' "${TO_AWAIT}" + while [ ! -e "${TO_AWAIT}" ]; do + printf " …" + sleep 0.5 + done + printf ' there it is!\n' +} +rbind_mnt() { + for NAME in ${TO_RBIND}; do + local PATH_NAME="/${NAME}" + local SLAVE="${PATH_MNT}${PATH_NAME}" + msg 'For working chroot also mounting %s into there …' "${PATH_NAME}" + mount --rbind "${PATH_NAME}" "${SLAVE}" + mount --make-rslave "${SLAVE}" + done +} +unmount_unrbind() { + msg 'Unmounting chroot environment …' + for NAME in ${TO_RBIND}; do + umount -R "${PATH_MNT}/${NAME}" + done + umount "${PATH_MNT}" +} + +# helpers: miscellaneous +chroot_sh() { + LANG=C.UTF-8 chroot "${PATH_MNT}" /bin/sh -c "$@" +} +usage() { + local COUNT_INPUTS=$1 + shift + local MSG="usage: ${SCRIPT_NAME}" + for PARAMETER in "$@"; do + MSG="${MSG} <${PARAMETER}>" + done + [ "${COUNT_INPUTS}" -eq $# ]\ + || die "${MSG}" +} diff --git a/chrooted_command.sh b/chrooted_command.sh new file mode 100755 index 0000000..e90665c --- /dev/null +++ b/chrooted_command.sh @@ -0,0 +1,33 @@ +#!/bin/sh +. ./_lib.sh + +# inputs to confirm +usage $# "partition" "volume-group-name" "root-name" "command" +PARTITION=$1 +NAME_LUKSVG=$2 +NAME_ROOT=$3 +COMMAND=$4 + +# constants derived from changeables +PATH_VG_ROOT=$(path_vg "${NAME_LUKSVG}")/${NAME_ROOT} + +# sanity checks +check_tools cryptsetup vgs +check_input_partition_mountable "${PARTITION}" +check_input_openable_luksvg "${PARTITION}" "${NAME_LUKSVG}" + +# mount +open_luksvg "${PARTITION}" "${NAME_LUKSVG}" +await_path "${PATH_VG_ROOT}" +mount_privately "${PATH_VG_ROOT}" +rbind_mnt + +# enact command +RC=0 +chroot_sh "${COMMAND}" || RC=$? + +# clean up mounts +unmount_unrbind +close_luksvg "${NAME_LUKSVG}" +msg 'Finished! (command exit status: %s)' "${RC}" +exit "${RC}" diff --git a/install_debian.sh b/install_debian.sh index 3898862..987c834 100755 --- a/install_debian.sh +++ b/install_debian.sh @@ -1,185 +1,137 @@ #!/bin/sh -set -Ceu -SCRIPT_NAME=$0 +. ./_lib.sh # constants unlikely to change -NAME_DEV=dev -NAME_INITRD=initrd.img -NAME_VMLINUZ=vmlinuz +FNAME_INITRD=initrd.img +FNAME_NM_CONN=wifi.nmconnection +FNAME_VMLINUZ=vmlinuz PATH_CRYPTTAB=/etc/crypttab -PATH_DEV="/${NAME_DEV}" PATH_EFI=/boot/efi PATH_FSTAB=/etc/fstab PATH_INTERFACES=/etc/network/interfaces -TO_RBIND="${NAME_DEV} proc sys" +PATH_NM_CONNECTIONS=/etc/NetworkManager/system-connections # constants we might want to change at some point DEB_SUITE=trixie -PATH_BOOT_DEVICE="${PATH_DEV}/nvme0n1" IDX_BOOT_PARTITION=1 -PATH_MNT=/mnt/debinst -NAME_ROOT=root - -# helpers -msg_nonl() { - printf '[## %s ##] ' "${SCRIPT_NAME}" - # shellcheck disable=SC2059 - # (assume we'll always pass a literal format string) - printf -- "$@" -} -msg() { - msg_nonl "$@" - printf '\n' -} -die() { - msg '%s' "$*" >&2 - exit 1 -} -error() { - die "error: $*" -} -try_quiet() { - "$@" >/dev/null 2>&1 -} -check_chars_legality() { - case "$1" in *[!A-Za-z0-9_.-]*|"") - error "illegal characters in $2 '$1'" ;; - esac -} -chroot_sh() { - LANG=C.UTF-8 chroot "${PATH_MNT}" /bin/sh -c "$@" -} +PATH_BOOT_DEVICE="${PATH_DEV}/nvme0n1" # inputs to confirm -[ $# -eq 2 ]\ - || die "usage: $0 " +usage $# "partition" "volume-group-name" "boot-name" PARTITION=$1 -NAME_VOLGROUP=$2 -NAME_BOOT="${NAME_VOLGROUP}" -NAME_LUKS="${NAME_VOLGROUP}" +NAME_LUKSVG=$2 +NAME_BOOT=$3 +NAME_ROOT="${NAME_BOOT}" # constants derived from changeables -LOGVOL_ROOT="${PATH_DEV}/${NAME_VOLGROUP}/${NAME_ROOT}" PATH_EFI_NAME_BOOT="${PATH_EFI}/${NAME_BOOT}" -PATH_LUKS_MAPPER="${PATH_DEV}/mapper/${NAME_LUKS}" PATH_MNT_CRYPTTAB="${PATH_MNT}${PATH_CRYPTTAB}" PATH_MNT_FSTAB="${PATH_MNT}${PATH_FSTAB}" -PATH_MNT_INTERFACES="${PATH_MNT}${PATH_INTERFACES}" - -# sanity checks: required tools -for CMD in \ - cryptsetup debootstrap efibootmgr lvcreate pvcreate vgcreate vgs \ -; do - try_quiet command -v "${CMD}" ||\ - error "required tool not found: ${CMD}" -done - -# inputs sanity check: PARTITION -[ -b "${PARTITION}" ] ||\ - error "${PARTITION} is not a block device" -try_quiet findmnt --source "${PARTITION}" &&\ - error "${PARTITION} is already mounted" -try_quiet cryptsetup isLuks "${PARTITION}" &&\ - error "${PARTITION} is already a LUKS container" -# inputs sanity check: NAME_VOLGROUP -check_chars_legality "${NAME_VOLGROUP}" "volume group name" -try_quiet vgs "${NAME_VOLGROUP}" &&\ - error "volume group '${NAME_VOLGROUP}' already exists" -# inputs sanity check: NAME_LUKS -[ -e "${PATH_LUKS_MAPPER}" ] &&\ - error "${PATH_LUKS_MAPPER} already exists" -# inputs sanity check: NAME_BOOT +PATH_MNT_NM_CONN="${PATH_MNT}${PATH_NM_CONNECTIONS}/${FNAME_NM_CONN}" +PATH_MNT_NM_CONNECTIONS="${PATH_MNT}${PATH_NM_CONNECTIONS}" +PATH_VG=$(path_vg "${NAME_LUKSVG}") +PATH_VG_DATA=${PATH_VG}/${NAME_DATA} +PATH_VG_ROOT=${PATH_VG}/${NAME_ROOT} + +# sanity checks +check_tools cryptsetup debootstrap efibootmgr lvcreate mkfs.ext4 vgs +check_input_partition_mountable "${PARTITION}" +check_input_openable_luksvg "${PARTITION}" "${NAME_LUKSVG}" check_chars_legality "${NAME_BOOT}" "boot label" -[ -e "${PATH_EFI}/${NAME_BOOT}" ] &&\ - error "${PATH_EFI}/${NAME_BOOT} already exists" +[ -e "${PATH_EFI}/${NAME_BOOT}" ]\ + && error "${PATH_EFI}/${NAME_BOOT} already exists" efibootmgr \ | sed -n 's/^Boot[0-9A-Fa-f]\{4\}[* ] //p' \ | awk '{print $1}' \ - | grep -Fxq "${NAME_BOOT}" &&\ - error "an EFI boot entry named '${NAME_BOOT}' already exists" + | grep -Fxq "${NAME_BOOT}"\ + && error "an EFI boot entry named '${NAME_BOOT}' already exists" # run inputs by user and ask for confirmation -msg 'Your installation setup choices:' -msg '- target partition (WILL BE ERASED!): [ %s ]' "${PARTITION}" -msg '- how to name new volume group, boot option, LUKS mapper: [ %s ]' \ - "${NAME_VOLGROUP}" -msg_nonl 'To continue, type "YES!" (all caps, exclamation mark, no quotes): ' -read -r CONFIRM -[ "${CONFIRM}" = 'YES!' ] ||\ - die 'ABORTED: expected confirmation not given.' - -# encrypt partition -msg 'Formatting %s as LUKS container …' "${PARTITION}" -cryptsetup luksFormat "${PARTITION}" -msg 'Opening LUKS container as "%s" …' "${NAME_LUKS}" -cryptsetup luksOpen "${PARTITION}" "${NAME_LUKS}" - -# set up LVM and filesystem -msg 'Creating volume group "%s" there …' "${NAME_VOLGROUP}" -vgcreate "${NAME_VOLGROUP}" "${PATH_LUKS_MAPPER}" +msg 'Your installation choices:' +msg '- target partition: [ %s ]' "${PARTITION}" +msg '- target volume group: [ %s ]' "${NAME_LUKSVG}" +msg '- name for new boot option: [ %s ]' "${NAME_BOOT}" + +# set up logival volume and filesystem +open_luksvg "${PARTITION}" "${NAME_LUKSVG}" msg 'Creating logical volume "%s" there …' "${NAME_ROOT}" -lvcreate -l '100%FREE' -n "${NAME_ROOT}" "${NAME_VOLGROUP}" +lvcreate -L '10G' -n "${NAME_ROOT}" "${NAME_LUKSVG}" +await_path "${PATH_VG_ROOT}" msg 'Creating EXT4 filesystem …' -mkfs.ext4 -q "${LOGVOL_ROOT}" - -# to facilitate later unmounting of the chrooted system: isolate our mounts -# against propagation to services sandboxed with PrivateMounts=yes (e.g. -# systemd-udevd), into whose private namespaces our later umount might fail to -# reach for closing their references e.g. into what we'll want to vgchange -an, -# which would then be blocked by referenced devices still claimed as "busy" … -msg "Privatize script's process' mount namespace …" -mount --make-rprivate / +mkfs.ext4 -q "${PATH_VG_ROOT}" # mount and install base -msg 'Mounting %s at %s …' "${LOGVOL_ROOT}" "${PATH_MNT}" -mkdir -p "${PATH_MNT}" -mount "${LOGVOL_ROOT}" "${PATH_MNT}" +mount_privately "${PATH_VG_ROOT}" msg 'Installing Debian Suite "%s" there via debootstrap …' "${DEB_SUITE}" debootstrap "${DEB_SUITE}" "${PATH_MNT}" -for NAME in ${TO_RBIND}; do - PATH_NAME="/${NAME}" - SLAVE="${PATH_MNT}${PATH_NAME}" - msg 'For chroot environment also mounting %s into there …' "${PATH_NAME}" - mount --rbind "${PATH_NAME}" "${SLAVE}" - mount --make-rslave "${SLAVE}" -done +rbind_mnt # set up minimal fstab msg 'Writing fstab …' -printf '%s / ext4 errors=remount-ro 0 1\n' "${LOGVOL_ROOT}"\ +printf '%s / ext4 errors=remount-ro 0 1\n' "${PATH_VG_ROOT}"\ >| "${PATH_MNT_FSTAB}" +printf '%s /data ext4 errors=remount-ro 0 2\n' "${PATH_VG_DATA}"\ + >> "${PATH_MNT_FSTAB}" # set up crypttab msg 'Writing crypttab …' printf '%s UUID=%s none luks\n' \ - "${NAME_LUKS}" "$(cryptsetup luksUUID "${PARTITION}")" \ + "${NAME_LUKSVG}" "$(cryptsetup luksUUID "${PARTITION}")" \ >| "${PATH_MNT_CRYPTTAB}" -cat "${PATH_MNT_CRYPTTAB}" -# copy local networking config into target -msg 'Copying networking configuration …' -cp "${PATH_INTERFACES}" "${PATH_MNT_INTERFACES}" -chmod 600 "${PATH_MNT_INTERFACES}" - -# enable non-free-firmware component for firmware-iwlwifi below: debootstrap -# only enables "main" by default +# enable non-free-firmware component for firmware-iwlwifi below (as debootstrap +# only enables "main" by default) msg 'Enabling non-free-firmware component in target sources.list …' sed -i 's/ main$/ main non-free-firmware/' "${PATH_MNT}/etc/apt/sources.list" -# set up kernel, initrd -msg 'Into chroot environment installing LVM tools, kernel, initrd …' +# set up kernel, initrd etc. +msg 'Into chroot environment installing LVM tools, kernel, initrd etc. …' chroot_sh "apt-get -qq update" chroot_sh \ "DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \ - cryptsetup cryptsetup-initramfs firmware-iwlwifi ifupdown lvm2 \ - linux-image-amd64 wpasupplicant" -msg 'Enabling networking service …' -chroot_sh "systemctl enable networking" + linux-image-amd64 \ + cryptsetup cryptsetup-initramfs lvm2 \ + network-manager wpasupplicant" + +# pre-seed known wifi network into NetworkManager +msg 'Extracting wifi credentials from %s …' "${PATH_INTERFACES}" +NAME_WIFI_SSID=$(sed -n 's/^[[:space:]]*wpa-ssid[[:space:]]*//p' \ + "${PATH_INTERFACES}" | sed 's/[[:space:]]*$//') +NAME_WIFI_PSK=$(sed -n 's/^[[:space:]]*wpa-psk[[:space:]]*//p' \ + "${PATH_INTERFACES}" | sed 's/[[:space:]]*$//') +if [ -z "${NAME_WIFI_SSID}" ] || [ -z "${NAME_WIFI_PSK}" ]; then + error "could not extract wifi SSID/PSK from ${PATH_INTERFACES}" +fi +msg 'Writing NetworkManager connection profile for "%s" …' \ + "${NAME_WIFI_SSID}" +mkdir -p "${PATH_MNT_NM_CONNECTIONS}" +chmod 700 "${PATH_MNT_NM_CONNECTIONS}" +cat <| "${PATH_MNT_NM_CONN}" +[connection] +id=${NAME_WIFI_SSID} +uuid=$(cat /proc/sys/kernel/random/uuid) +type=wifi + +[wifi] +mode=infrastructure +ssid=${NAME_WIFI_SSID} + +[wifi-security] +key-mgmt=wpa-psk +psk=${NAME_WIFI_PSK} + +[ipv4] +method=auto + +[ipv6] +method=auto +EOF +chmod 600 "${PATH_MNT_NM_CONN}" # install kernel and initrd into EFI tree/vars msg 'EFI setup: copying kernel and initrd into %s …' "${PATH_EFI_NAME_BOOT}" mkdir "${PATH_EFI_NAME_BOOT}" -for FILENAME in "${NAME_INITRD}" "${NAME_VMLINUZ}"; do +for FILENAME in "${FNAME_INITRD}" "${FNAME_VMLINUZ}"; do cp "${PATH_MNT}/${FILENAME}" "${PATH_EFI_NAME_BOOT}/" done msg 'EFI setup: adding boot entry %s …' "${NAME_BOOT}" @@ -189,20 +141,14 @@ efibootmgr \ --disk "${PATH_BOOT_DEVICE}" \ --part "${IDX_BOOT_PARTITION}" \ --label "${NAME_BOOT}" \ - --loader "${NAME_BOOT}/${NAME_VMLINUZ}" \ - --unicode "root=${LOGVOL_ROOT} ro initrd=${NAME_BOOT}\\${NAME_INITRD}" + --loader "${NAME_BOOT}/${FNAME_VMLINUZ}" \ + --unicode "root=${PATH_VG_ROOT} ro initrd=${NAME_BOOT}\\${FNAME_INITRD}" # ask root login at latest possible moment, so that on fails: less left undone msg 'Setting up root login …' chroot_sh "passwd" # clean up mounts -msg 'Mostly done! Now unmounting chroot environment …' -for NAME in ${TO_RBIND}; do - umount -R "${PATH_MNT}/${NAME}" -done -umount "${PATH_MNT}" -msg 'Deactivating volume group and closing LUKS container …' -vgchange -an "${NAME_VOLGROUP}" -cryptsetup luksClose "${NAME_LUKS}" +unmount_unrbind +close_luksvg "${NAME_LUKSVG}" msg 'Finished!' diff --git a/make_luksvg.sh b/make_luksvg.sh new file mode 100755 index 0000000..674e0b9 --- /dev/null +++ b/make_luksvg.sh @@ -0,0 +1,44 @@ +#!/bin/sh +. ./_lib.sh + +# inputs to confirm +usage $# "partition" "volume-group-name" +PARTITION=$1 +NAME_LUKSVG=$2 + +# constants derived from changeables +PATH_LUKS_MAPPER=$(path_luks_mapper "${NAME_LUKSVG}") +PATH_VG_DATA=$(path_vg "${NAME_LUKSVG}")/${NAME_DATA} + +# sanity checks +check_tools cryptsetup lvcreate mkfs.ext4 pvcreate vgcreate vgs +check_input_partition_mountable "${PARTITION}" +check_input_new_luksvg "${NAME_LUKSVG}" +try_quiet cryptsetup isLuks "${PARTITION}"\ + && error "${PARTITION} is already a LUKS container" + +# run inputs by user and ask for confirmation +msg 'Your formatting choices:' +msg '- target partition (WILL BE ERASED!): [ %s ]' "${PARTITION}" +msg '- name for new volume group and its LUKS mapper: [ %s ]' "${NAME_LUKSVG}" +msg_nonl 'To continue, type "YES!" (all caps, exclamation mark, no quotes): ' +read -r CONFIRM; [ "${CONFIRM}" = 'YES!' ]\ + || die 'ABORTED: expected confirmation not given.' + +# encrypt partition +msg 'Formatting %s as LUKS container …' "${PARTITION}" +cryptsetup luksFormat --batch-mode "${PARTITION}" +open_luksvg "${PARTITION}" "${NAME_LUKSVG}" + +# set up LVM and filesystem +msg 'Creating volume group "%s" inside LUKS container …' "${NAME_LUKSVG}" +vgcreate "${NAME_LUKSVG}" "${PATH_LUKS_MAPPER}" + +msg 'Creating logical volume "%s" inside volume group …' "${NAME_DATA}" +lvcreate -L '256G' -n "${NAME_DATA}" "${NAME_LUKSVG}" +msg 'Creating EXT4 filesystem …' +mkfs.ext4 -q "${PATH_VG_DATA}" + +# clean-up +close_luksvg "${NAME_LUKSVG}" +msg 'Finished!'