--- /dev/null
+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}"
+}
#!/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}"
--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!'