diff --git a/packages/usb-installer/.gitignore b/packages/usb-installer/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/packages/usb-installer/.gitignore @@ -0,0 +1 @@ +build diff --git a/packages/usb-installer/build.sh b/packages/usb-installer/build.sh new file mode 100755 index 000000000..4d0f04be4 --- /dev/null +++ b/packages/usb-installer/build.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +echo "Creating disk image..." +rootfs_tar_size="$(du --block-size 1M /data/build/rootfs.tar | awk '{print $1}')" +rootfs_buffer="512" +disk_size_mb="$((rootfs_tar_size + rootfs_buffer))" +disk_size_sector=$(expr $disk_size_mb \* 1024 \* 1024 / 512) +disk_image="/data/build/umbrelos-amd64-usb-installer.img" +dd if=/dev/zero of="${disk_image}" bs="${disk_size_sector}" count=512 + +echo Creating disk partitions... +gpt_efi="ef00" +gpt_root_amd64="8304" +sgdisk \ + --new 1:2048:+200M \ + --typecode 1:"${gpt_efi}" \ + --change-name 1:ESP \ + --new 2:0:0 \ + --typecode 2:"${gpt_root_amd64}" \ + --change-name 1:ROOTFS \ + "${disk_image}" + +disk_layout=$(fdisk -l "${disk_image}") +echo "${disk_layout}" + +echo Attaching partitions to loopback devices... +efi_start=$(echo "${disk_layout}" -l "${disk_image}" | grep EFI | awk '{print $2}') +efi_size=$(echo "${disk_layout}" -l "${disk_image}" | grep EFI | awk '{print $4}') +root_start=$(echo "${disk_layout}" -l "${disk_image}" | grep root | awk '{print $2}') +root_size=$(echo "${disk_layout}" -l "${disk_image}" | grep root | awk '{print $4}') +efi_device=$(losetup --offset $((512*efi_start)) --sizelimit $((512*efi_size)) --show --find "${disk_image}") +root_device=$(losetup --offset $((512*root_start)) --sizelimit $((512*root_size)) --show --find "${disk_image}") + +echo Formatting partitions... +mkfs.vfat -n "ESP" "${efi_device}" +mkfs.ext4 -L "ROOTFS" "${root_device}" + +echo Mounting partitions... +efi_mount_point="/mnt/efi" +root_mount_point="/mnt/root" +mkdir -p "${efi_mount_point}" +mkdir -p "${root_mount_point}" +mount "${efi_device}" "${efi_mount_point}" +mount -t ext4 "${root_device}" "${root_mount_point}" + +echo Extracting rootfs... +tar -xf /data/build/rootfs.tar --directory "${root_mount_point}" + +echo Copying boot directory over to ESP partition... +cp -r "${root_mount_point}/boot/." "${efi_mount_point}" +tree "${efi_mount_point}" +echo + +echo Unmounting partitions... +umount "${root_mount_point}" +umount "${efi_mount_point}" + +echo Detaching loopback devices... +losetup --detach "${efi_device}" +losetup --detach "${root_device}" + +echo Done! \ No newline at end of file diff --git a/packages/usb-installer/builder.Dockerfile b/packages/usb-installer/builder.Dockerfile new file mode 100644 index 000000000..5c6938b5a --- /dev/null +++ b/packages/usb-installer/builder.Dockerfile @@ -0,0 +1,4 @@ +FROM debian:bookworm + +RUN apt-get -y update +RUN apt-get -y install fdisk gdisk qemu-utils dosfstools tree \ No newline at end of file diff --git a/packages/usb-installer/overlay/etc/systemd/system/custom-tty.service b/packages/usb-installer/overlay/etc/systemd/system/custom-tty.service new file mode 100644 index 000000000..1606ac64f --- /dev/null +++ b/packages/usb-installer/overlay/etc/systemd/system/custom-tty.service @@ -0,0 +1,14 @@ +[Unit] +Description=Custom TTY +After=multi-user.target + +[Service] +ExecStart=/opt/custom-tty +StandardInput=tty +StandardOutput=tty +StandardError=tty +TTYPath=/dev/tty1 +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/packages/usb-installer/overlay/opt/custom-tty b/packages/usb-installer/overlay/opt/custom-tty new file mode 100755 index 000000000..8ed1ae9c1 --- /dev/null +++ b/packages/usb-installer/overlay/opt/custom-tty @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +sleep 1 + +clear + +cat << 'EOF' + + ,;###GGGGGGGGGGl#Sp + ,##GGGlW""^' '`""%GGGG#S, + ,#GGG" "lGG#o + #GGl^ '$GG# + ,#GGb \GGG, + lGG" "GGG + #GGGlGGGl##p,,p##lGGl##p,,p###ll##GGGG + !GGGlW"""*GGGGGGG#""""WlGGGGG#W""*WGGGGS + "" "^ '" "" + umbrelOS USB Installer +EOF +echo +echo "Installing umbrelOS will wipe your entire storage device." +echo +readarray -t devices < <(lsblk --nodeps --output NAME,VENDOR,MODEL,SIZE | sed '1d') +PS3="Select a storage device by number to install umbrelOS on: " +select device in "${devices[@]}" +do + if [[ -n "$device" ]] + then + echo "installing umbrelOS on: $device" + device_path="/dev/$(echo $device | awk '{print $1}')" + xz --decompress --stdout /umbrelos-amd64.img.xz | dd of=$device_path bs=4M status=progress + sync + echo + echo "umbrelOS has been installed!" + printf "Press any key to shutdown, remember to remove the USB drive before turning the device back on." + read -n 1 -s + poweroff + break + else + echo "Invalid choice, please try again." + fi +done \ No newline at end of file diff --git a/packages/usb-installer/run.sh b/packages/usb-installer/run.sh new file mode 100755 index 000000000..b0ee76e0d --- /dev/null +++ b/packages/usb-installer/run.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p build +docker build -f usb-installer.Dockerfile --platform linux/amd64 -t usb-installer ../ +docker export -o build/rootfs.tar $(docker run -d usb-installer /bin/true) +docker build -f builder.Dockerfile -t usb-installer:builder . +docker run -it --entrypoint /data/build.sh -v $PWD:/data --privileged usb-installer:builder + +# qemu-system-x86_64 -net nic -net user -machine accel=tcg -m 2048 -hda build/umbrelos-amd64-usb-installer.img -bios OVMF.fd \ No newline at end of file diff --git a/packages/usb-installer/usb-installer.Dockerfile b/packages/usb-installer/usb-installer.Dockerfile new file mode 100644 index 000000000..39ec84b69 --- /dev/null +++ b/packages/usb-installer/usb-installer.Dockerfile @@ -0,0 +1,57 @@ +FROM debian:bookworm-slim + +RUN echo "root:root" | chpasswd + +RUN apt-get -y update + +# Install Linux kernel, systemd and bootloader +RUN apt-get install --yes --no-install-recommends linux-image-amd64 systemd-sysv systemd-boot xz-utils + +# We can't install the bootloader via `bootctl install` from Docker because it complains +# about an invalid ESP partition. We can't easily fix it with loopback mounts from a Docker +# build environment. Instead we just manually install the bootloader to /boot and +# migrate /boot to an ESP partition in a post processing step outside of Docker. +RUN mkdir -p "/boot/EFI/systemd/" +RUN mkdir -p "/boot/EFI/BOOT/" +RUN cp "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" "/boot/EFI/systemd/systemd-bootx64.efi" +RUN cp "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" "/boot/EFI/BOOT/bootx64.efi" + +# Generate boot config +RUN mkdir -p "/boot/loader/entries" + +RUN echo " \n\ +title Debian \n\ +linux $(ls /boot/vmlinuz-* | sed 's/\/boot//') \n\ +initrd $(ls /boot/initrd.img* | sed 's/\/boot//') \n\ +options root=LABEL=ROOTFS rw quiet loglevel=1" | tee "/boot/loader/entries/debian.conf" + +RUN echo " \n\ +default debian \n\ +timeout 0 \n\ +console-mode max \n\ +editor no" | tee "/boot/loader/loader.conf" + +# Verify boot status +RUN bootctl --esp-path=/boot status + +# Reduce size +# We have to do this extremely aggreseively because we're close to GitHub's 2GB release asset limit +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /tmp/* /usr/share/man /usr/share/doc /usr/share/info /var/log/* +RUN find / -name '*.a' -delete && \ + find / -name '*.so*' -exec strip --strip-debug {} \; +RUN rm -rf /usr/lib/modules/6.1.0-20-amd64/kernel/drivers/gpu +RUN rm -rf /usr/lib/modules/6.1.0-20-amd64/kernel/drivers/net +RUN rm -rf /usr/lib/modules/6.1.0-20-amd64/kernel/drivers/infiniband +RUN rm -rf /usr/lib/modules/6.1.0-20-amd64/kernel/net +RUN rm -rf /usr/lib/modules/6.1.0-20-amd64/kernel/sound + +# Copy in umbrelOS image +COPY os/build/umbrelos-amd64.img.xz / + +# Copy in filesystem overlay +COPY usb-installer/overlay / + +# Configure TTY services +RUN systemctl enable custom-tty.service +RUN systemctl mask console-getty.service +RUN systemctl mask getty@tty1.service \ No newline at end of file