From 0e92c1698c741ff9acd75147948eff4457941a88 Mon Sep 17 00:00:00 2001 From: Hendrik Bruinsma Date: Thu, 27 Nov 2025 21:44:11 +0100 Subject: [PATCH 1/2] Make sure dracut builds a complete initramfs and add mount options for the root file system, so the new kernel also boots --- installer-files/etc/dracut.conf.d/10-no-hostonly.conf | 3 +++ installer.sh | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 installer-files/etc/dracut.conf.d/10-no-hostonly.conf diff --git a/installer-files/etc/dracut.conf.d/10-no-hostonly.conf b/installer-files/etc/dracut.conf.d/10-no-hostonly.conf new file mode 100644 index 0000000..53e6d33 --- /dev/null +++ b/installer-files/etc/dracut.conf.d/10-no-hostonly.conf @@ -0,0 +1,3 @@ +# Build full initramfs with all drivers instead of host-only +# This ensures the initramfs works across different hardware configurations +hostonly="no" diff --git a/installer.sh b/installer.sh index f25f09e..68ed0f5 100755 --- a/installer.sh +++ b/installer.sh @@ -104,7 +104,6 @@ top_level_mount=/mnt/top_level_mount target=/target kernel_params="rw quiet rootfstype=btrfs rootflags=${FSFLAGS},subvol=@ rd.auto=1 splash" if [ "${DISABLE_LUKS}" != "true" ]; then - kernel_params="rd.luks.options=tpm2-device=auto ${kernel_params}" luks_device_name=root root_device=/dev/mapper/${luks_device_name} else @@ -171,10 +170,15 @@ if [ "${DISABLE_LUKS}" != "true" ]; then rm -f /tmp/passwd cryptsetup luksUUID "${main_partition}" > luks.uuid root_uuid=$(cat luks.uuid) + # Add LUKS parameters to kernel cmdline + kernel_params="rd.luks.uuid=${root_uuid} rd.luks.name=${root_uuid}=${luks_device_name} rd.luks.options=tpm2-device=auto root=${root_device} ${kernel_params}" if [ ! -e ${root_device} ]; then notify open luks on root cryptsetup luksOpen ${main_partition} ${luks_device_name} --key-file $KEYFILE fi +else + # Without LUKS, just set the root device + kernel_params="root=${root_device} ${kernel_params}" fi btrfs_uuid=$(lsblk -no UUID ${root_device}) From 42973dbee69ffa3c55cef4c61023ae42d59c48b4 Mon Sep 17 00:00:00 2001 From: Hendrik Bruinsma Date: Thu, 2 Apr 2026 11:20:21 +0200 Subject: [PATCH 2/2] feat: update `make_image.sh` script to be able to create disk images, with options to feed settings via environment variables Signed-off-by: Hendrik Bruinsma --- README.md | 40 +++++++++- installer.sh | 6 +- make_image.sh | 198 +++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 212 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 9b814eb..62b7f79 100644 --- a/README.md +++ b/README.md @@ -198,11 +198,43 @@ Alternatively to running the whole browser-based GUI, you can run the `installer The end result will be exactly the same. Just don't forget to edit the configuration options (especially the `DISK` variable) before running it. +### Building Locally Or In A VM + +Build compiled components with: + + ./build-compiled-components.sh + +or manually: + + cd frontend && npm run build + cd ../backend && go build -o opinionated-installer + +For installer image work (`make_image.sh`, `installer.sh`), keep in mind: + +- The safest default is to build a raw image file first and only write that image to a USB stick as a separate, explicit step. + +Example flow using the new native image-file mode in `make_image.sh`: + + sudo IMAGE_FILE=/tmp/odin-test-disk.img IMAGE_SIZE=20G ./make_image.sh --non-interactive + +This creates the raw image file, attaches it to a loop device automatically, builds the installer image into it and detaches the loop device on exit. + +To write the resulting image to a USB stick afterwards: + + sudo dd if=/tmp/odin-test-disk.img of=/dev/sdX bs=256M oflag=dsync status=progress + sync + ### Creating Your Own Installer Image - 1. Insert a blank storage device - 2. Edit the **DISK** and other variables at the top of `make_image.sh` - 3. Execute `make_image.sh` as root + 1. Build to a raw image file first (recommended): `sudo IMAGE_FILE=/tmp/opinionated.img IMAGE_SIZE=20G ./make_image.sh --non-interactive` + 2. If you need interactive prompts, remove `--non-interactive` + 3. If you really want to build directly to a block device, set **DISK** and other variables at the top of `make_image.sh` or pass them via environment + 4. Optionally write the generated image file to removable media with `dd` + +Minimal host/VM package set for `make_image.sh`: + + sudo apt update + sudo apt install -y btrfs-progs debootstrap dosfstools golang-go kpartx npm systemd-repart udev uuid-runtime In the first stage of image generation, you will get a _tasksel_ prompt where you can select a different set of packages for your image. @@ -295,4 +327,4 @@ Please set up your torrent client to follow the RSS feed below and seed all new Tell your friends about the installer. If you are active on social media, please share! -Follow the author on [mastodon](https://mas.to/@r0b0). \ No newline at end of file +Follow the author on [mastodon](https://mas.to/@r0b0). diff --git a/installer.sh b/installer.sh index 68ed0f5..de93189 100755 --- a/installer.sh +++ b/installer.sh @@ -385,6 +385,9 @@ fi cat < ${target}/tmp/run1.sh #!/bin/bash export DEBIAN_FRONTEND=noninteractive + +# Ensure package lists are updated before installing from backports +apt update -y apt install -y locales tasksel network-manager sudo apt install -y -t ${BACKPORTS_VERSION} systemd shim-signed systemd-boot systemd-boot-efi-amd64-signed systemd-ukify sbsigntool dracut btrfs-progs cryptsetup tpm2-tools tpm-udev @@ -466,7 +469,6 @@ firmware-misc-nonfree firmware-myricom firmware-netronome firmware-netxen -firmware-qcom-soc firmware-qlogic firmware-realtek firmware-ti-connectivity @@ -485,6 +487,8 @@ cat < ${target}/tmp/run2.sh set -euo pipefail export DEBIAN_FRONTEND=noninteractive +apt update -y || echo "Warning: apt update failed, continuing anyway" + xargs apt install -y < /tmp/packages.txt apt install -t ${BACKPORTS_VERSION} -y dracut initramfs-tools- initramfs-tools-core- initramfs-tools-bin- \ busybox- klibc-utils- libklibc- diff --git a/make_image.sh b/make_image.sh index 89a9f1c..7d2ea17 100755 --- a/make_image.sh +++ b/make_image.sh @@ -19,21 +19,151 @@ set -euo pipefail # edit this: -DISK=/dev/vdb -USERNAME=live -DEBIAN_VERSION=trixie +DISK=${DISK:-/dev/vdb} +IMAGE_FILE=${IMAGE_FILE:-} +IMAGE_SIZE=${IMAGE_SIZE:-5G} +USERNAME=${USERNAME:-live} +DEBIAN_VERSION=${DEBIAN_VERSION:-trixie} BACKPORTS_VERSION=${DEBIAN_VERSION}-backports -FSFLAGS="compress=zstd:15" -BOOTSTRAP_IMAGE=/var/cache/opinionated-debian-installer/bootstrap.btrfs +FSFLAGS=${FSFLAGS:-"compress=zstd:15"} +BOOTSTRAP_IMAGE=${BOOTSTRAP_IMAGE:-/var/cache/opinionated-debian-installer/bootstrap.btrfs} +NON_INTERACTIVE=${NON_INTERACTIVE:-false} +TASKSEL_TASKS=${TASKSEL_TASKS:-"task-ssh-server"} + +for arg in "$@"; do + case "$arg" in + --non-interactive|-y) + NON_INTERACTIVE=true + ;; + -h|--help) + cat <&2 + exit 2 + ;; + esac +done target=/target SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +LOOP_DEVICE= +STATE_DIR=${STATE_DIR:-/var/tmp/opinionated-debian-installer} +REPART_DIR="${STATE_DIR}/repart.d" +EFI_UUID_FILE="${STATE_DIR}/efi-part.uuid" +INSTALLER_IMAGE_UUID_FILE="${STATE_DIR}/installer-image-part.uuid" +DISK_WIPED_MARKER="${STATE_DIR}/disk_wiped.txt" +FIRST_PHASE_DONE_MARKER="${STATE_DIR}/first_phase_done.txt" + +mkdir -p "${STATE_DIR}" + +function cleanup() { + if mountpoint -q "${target}/home"; then + umount "${target}/home" + fi + if mountpoint -q "${target}"; then + umount -R "${target}" + fi + if mountpoint -q "/mnt/btrfs1"; then + umount -R /mnt/btrfs1 + fi + if [ -n "${LOOP_DEVICE}" ] && losetup "${LOOP_DEVICE}" >/dev/null 2>&1; then + losetup -d "${LOOP_DEVICE}" + fi +} + +trap cleanup EXIT function notify { - echo -en "\033[32m$*\033[0m> " - read -r + if [ "${NON_INTERACTIVE}" = "true" ]; then + echo "$*" + else + echo -en "\033[32m$*\033[0m> " + read -r + fi } +function attach_loop_image() { + local image_file="$1" + local loop_dev + + # In containers, /dev/loopN nodes can be sparse (for example missing loop1). + # Try explicitly to attach using existing block devices. + for loop_dev in /dev/loop[0-9]*; do + if [[ "${loop_dev}" =~ p[0-9]+$ ]]; then + continue + fi + if [ ! -b "${loop_dev}" ]; then + continue + fi + # Check if the device is free + if losetup "${loop_dev}" >/dev/null 2>&1; then + continue + fi + # Try to attach the image to this device + if losetup -P --show "${loop_dev}" "${image_file}"; then + return 0 + fi + done + + # If no explicit device works, fall back to losetup -f + # but do NOT try to create missing nodes (leads to permission errors in containers). + losetup -fP --show "${image_file}" +} + +if [ "$(id -u)" -ne 0 ]; then + echo 'This script must be run by root' >&2 + exit 1 +fi + +if [ -n "${IMAGE_FILE}" ]; then + mkdir -p "$(dirname "${IMAGE_FILE}")" + if [ ! -f "${FIRST_PHASE_DONE_MARKER}" ]; then + # Phase 1 not completed - always start with a clean image to avoid + # stale partition tables / corrupt btrfs from a previous failed run. + if [ -f "${IMAGE_FILE}" ]; then + notify removing stale image file ${IMAGE_FILE} to start fresh + rm -f "${IMAGE_FILE}" + fi + rm -f "${DISK_WIPED_MARKER}" + fi + if [ ! -f "${IMAGE_FILE}" ]; then + notify creating raw image file ${IMAGE_FILE} with size ${IMAGE_SIZE} + truncate -s "${IMAGE_SIZE}" "${IMAGE_FILE}" + fi + notify attaching ${IMAGE_FILE} as a loop device + if ! LOOP_DEVICE=$(attach_loop_image "${IMAGE_FILE}"); then + cat >&2 < efi-part.uuid + uuidgen > "${EFI_UUID_FILE}" fi -if [ ! -f installer-image-part.uuid ]; then +if [ ! -f "${INSTALLER_IMAGE_UUID_FILE}" ]; then echo generate uuid for installer image partition - uuidgen > installer-image-part.uuid + uuidgen > "${INSTALLER_IMAGE_UUID_FILE}" fi -efi_uuid=$(cat efi-part.uuid) -installer_image_uuid=$(cat installer-image-part.uuid) +efi_uuid=$(cat "${EFI_UUID_FILE}") +installer_image_uuid=$(cat "${INSTALLER_IMAGE_UUID_FILE}") notify setting up partitions on ${DISK} mkdir -p /mnt/btrfs1 mkdir -p ${target}/home -rm -rf repart.d -mkdir -p repart.d +rm -rf "${REPART_DIR}" +mkdir -p "${REPART_DIR}" -cat < repart.d/01_efi.conf +cat < "${REPART_DIR}/01_efi.conf" [Partition] Type=esp UUID=${efi_uuid} @@ -71,7 +203,7 @@ SizeMaxBytes=300M Format=vfat EOF -cat < repart.d/02_baseImage.conf +cat < "${REPART_DIR}/02_baseImage.conf" [Partition] Type=root Label=Opinionated Debian Installer @@ -84,14 +216,20 @@ GrowFileSystem=on Encrypt=off EOF -if [ ! -f disk_wiped.txt ]; then +if [ ! -f "${DISK_WIPED_MARKER}" ]; then wipefs --all ${DISK} - touch disk_wiped.txt + touch "${DISK_WIPED_MARKER}" fi # sector-size: see https://github.com/systemd/systemd/issues/37801 # remove with systemd 258 -systemd-repart --sector-size=512 --empty=allow --no-pager --definitions=repart.d --dry-run=no ${DISK} +systemd-repart --sector-size=512 --empty=allow --no-pager --definitions="${REPART_DIR}" --dry-run=no ${DISK} + +# Wait for kernel to recognize new partitions and create device symlinks +notify waiting for kernel to probe partitions +sleep 2 +blockdev --rereadpt ${DISK} || true +udevadm settle --timeout=30 root_device=/dev/disk/by-partuuid/${installer_image_uuid} efi_device=/dev/disk/by-partuuid/${efi_uuid} @@ -228,7 +366,6 @@ firmware-misc-nonfree firmware-myricom firmware-netronome firmware-netxen -firmware-qcom-soc firmware-qlogic firmware-realtek firmware-ti-connectivity @@ -252,8 +389,10 @@ xargs apt install -t ${BACKPORTS_VERSION} -y < /tmp/packages_backports.txt EOF chroot ${target}/ bash /tmp/run2.sh -notify running tasksel -chroot ${target}/ tasksel +notify installing tasksel selections: ${TASKSEL_TASKS} +for task in ${TASKSEL_TASKS}; do + chroot ${target}/ apt install -y "${task}" || echo "Warning: failed to install ${task}" +done if mountpoint -q "${target}/var/cache/apt/archives" ; then notify unmounting apt cache directory from target @@ -290,7 +429,7 @@ rm -f ${target}/etc/crypttab rm -f ${target}/var/log/*log rm -f ${target}/var/log/apt/*log -if [ ! -f first_phase_done.txt ]; then +if [ ! -f "${FIRST_PHASE_DONE_MARKER}" ]; then notify create snapshot after first phase (cd /mnt/btrfs1; btrfs subvolume snapshot -r @ opinionated_installer_bootstrap) mkdir -p $(dirname $BOOTSTRAP_IMAGE) @@ -298,7 +437,7 @@ if [ ! -f first_phase_done.txt ]; then notify storing bootstrap data to $BOOTSTRAP_IMAGE btrfs send --compressed-data /mnt/btrfs1/opinionated_installer_bootstrap > $BOOTSTRAP_IMAGE fi - touch first_phase_done.txt + touch "${FIRST_PHASE_DONE_MARKER}" fi function install_file() { @@ -424,7 +563,8 @@ cat < ${target}/tmp/run1.sh set -euo pipefail # see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1095646 -ln -s /dev/null /etc/kernel/install.d/50-dracut.install +mkdir -p /etc/kernel/install.d +ln -sf /dev/null /etc/kernel/install.d/50-dracut.install export DEBIAN_FRONTEND=noninteractive apt -t ${BACKPORTS_VERSION} install linux-image-amd64 -y @@ -493,4 +633,8 @@ sync umount -R ${target} umount -R /mnt/btrfs1 -echo "INSTALLATION FINISHED" +if [ -n "${IMAGE_FILE}" ]; then + echo "INSTALLATION FINISHED: ${IMAGE_FILE}" +else + echo "INSTALLATION FINISHED" +fi