home · contact · privacy
Split into separate tools for setting up LUKS LVM/partitions, Debian, etc.
authorPlom Heller <plom@plomlompom.com>
Mon, 31 Aug 2026 21:10:55 +0000 (23:10 +0200)
committerPlom Heller <plom@plomlompom.com>
Mon, 31 Aug 2026 21:10:55 +0000 (23:10 +0200)
_lib.sh [new file with mode: 0644]
chrooted_command.sh [new file with mode: 0755]
install_debian.sh
make_luksvg.sh [new file with mode: 0755]

diff --git a/_lib.sh b/_lib.sh
new file mode 100644 (file)
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 (executable)
index 0000000..e90665c
--- /dev/null
@@ -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}"
index 38988621345bdff53bec3ca933a15f5ab37871b8..987c834c73579980394f5d940828c57715eb18e6 100755 (executable)
 #!/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 <partition> <volume-group-name>"
+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 <<EOF >| "${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 (executable)
index 0000000..674e0b9
--- /dev/null
@@ -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!'