-
Notifications
You must be signed in to change notification settings - Fork 10
feat: Add option to build image file non-interactively #258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to point this out for the user; the script will install these |
||
|
|
||
| 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). | ||
| Follow the author on [mastodon](https://mas.to/@r0b0). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There already is a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was needed, as during my tests installing a new system it didn't work when this wasn't added
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is weird because your are not in fact copying your |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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}" | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can leave |
||
| 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}) | ||
|
|
@@ -381,6 +385,9 @@ fi | |
| cat <<EOF > ${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 | ||
|
|
||
|
|
@@ -462,7 +469,6 @@ firmware-misc-nonfree | |
| firmware-myricom | ||
| firmware-netronome | ||
| firmware-netxen | ||
| firmware-qcom-soc | ||
| firmware-qlogic | ||
| firmware-realtek | ||
| firmware-ti-connectivity | ||
|
|
@@ -481,6 +487,8 @@ cat <<EOF > ${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- | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,50 +19,182 @@ | |
| 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 <<EOF | ||
| Usage: $(basename "$0") [--non-interactive|-y] | ||
|
|
||
| Options: | ||
| --non-interactive, -y Accept defaults and skip Enter prompts. | ||
| --help, -h Show this help. | ||
|
|
||
| Configuration values are set via environment variables: | ||
| DISK Target disk (default: /dev/vdb) | ||
| IMAGE_FILE Output image file path (if set, creates loop image) | ||
| IMAGE_SIZE Image size (default: 3G) | ||
| USERNAME Default user (default: live) | ||
| DEBIAN_VERSION Debian version (default: trixie) | ||
| TASKSEL_TASKS Space-separated tasksel tasks (default: task-ssh-server) | ||
| Available: task-desktop, task-kde-desktop, task-gnome-desktop, | ||
| task-xfce-desktop, task-ssh-server, task-web-server, etc. | ||
| EOF | ||
| exit 0 | ||
| ;; | ||
| *) | ||
| echo "Unknown argument: $arg" >&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 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am sorry, what does this do? |
||
| 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 <<EOF | ||
| Failed to attach ${IMAGE_FILE} to a loop device. | ||
|
|
||
| If running in a devcontainer, ensure it has the needed privileges/devices: | ||
| - privileged mode (or CAP_SYS_ADMIN + CAP_MKNOD) | ||
| - /dev/loop-control | ||
| - /dev/loop* device nodes | ||
| EOF | ||
| exit 1 | ||
| fi | ||
| DISK=${LOOP_DEVICE} | ||
| udevadm settle | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe move this block after the |
||
| fi | ||
|
|
||
| notify install required packages | ||
| apt update -y | ||
| DEBIAN_FRONTEND=noninteractive apt install -y \ | ||
| btrfs-progs \ | ||
| debootstrap \ | ||
| dosfstools \ | ||
| golang-go \ | ||
| kpartx \ | ||
| npm \ | ||
| systemd-repart \ | ||
| udev \ | ||
| uuid-runtime | ||
|
|
||
| if [ ! -f efi-part.uuid ]; then | ||
| if [ ! -f "${EFI_UUID_FILE}" ]; then | ||
| echo generate uuid for efi partition | ||
| uuidgen > 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 <<EOF > repart.d/01_efi.conf | ||
| cat <<EOF > "${REPART_DIR}/01_efi.conf" | ||
| [Partition] | ||
| Type=esp | ||
| UUID=${efi_uuid} | ||
|
|
@@ -71,7 +203,7 @@ SizeMaxBytes=300M | |
| Format=vfat | ||
| EOF | ||
|
|
||
| cat <<EOF > repart.d/02_baseImage.conf | ||
| cat <<EOF > "${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,15 +429,15 @@ 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) | ||
| if [ ! -f $BOOTSTRAP_IMAGE ]; 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 <<EOF > ${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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.