diff --git a/.gitignore b/.gitignore index bf4b825..c1d8ee9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ tailscaled.state* cache *test* *ssh_host* -.code-workspace \ No newline at end of file +*.code-workspace +authorized_keys \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 43f477b..85d932b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -95,7 +95,7 @@ ARG ZBM_COMMIT_HASH RUN <<-EOF # Record a commit hash if one was provided if [ -n "${ZBM_COMMIT_HASH}" ]; then - echo "Using zfsbootmenu commit hash: ${ZQUICKINIT_COMMIT_HASH}" + echo "Using zfsbootmenu commit hash: ${ZBM_COMMIT_HASH}" echo "${ZBM_COMMIT_HASH}" > /etc/zbm-commit-hash mkdir -p /zbm echo "Cloning https://github.com/zbm-dev/zfsbootmenu.git" @@ -104,14 +104,16 @@ RUN <<-EOF fi EOF +COPY . /input/ # ZQuickInit source +ARG ZQUICKINIT_COMMIT_HASH RUN <<-EOF - ZQUICKINIT_COMMIT_HASH=$(git rev-parse HEAD) - echo "Using zquickinit commit hash: ${ZQUICKINIT_COMMIT_HASH}" - echo "${ZQUICKINIT_COMMIT_HASH}" > /etc/zquickinit-commit-hash + if [ -n "${ZQUICKINIT_COMMIT_HASH}" ]; then + echo "Using zquickinit commit hash: ${ZQUICKINIT_COMMIT_HASH}" + echo "${ZQUICKINIT_COMMIT_HASH}" > /etc/zquickinit-commit-hash + fi EOF -COPY . /input/ COPY --chmod=755 zquickinit.sh / # Run the build script with no arguments by default diff --git a/recipes/zquick_core/fs/libexec/hooks/early-setup.d/lvm_setup.sh b/recipes/zquick_core/fs/libexec/hooks/early-setup.d/lvm_setup.sh new file mode 100755 index 0000000..4eb2e5d --- /dev/null +++ b/recipes/zquick_core/fs/libexec/hooks/early-setup.d/lvm_setup.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +log() { + echo "zquick: $1" > /dev/kmsg +} + +if command -v lvm >/dev/null 2>&1; then + + log "Scanning for LVM" + modprobe dm_thin_pool + lvm vgscan -v 2>/dev/null + lvm vgchange -a y 2>/dev/null + + lvm_volumes=$(lvm lvscan 2>/dev/null | awk '{print $2}' | tr -d "'") + for volume in $lvm_volumes; do + # Get the logical volume path + lv_path=$(lvm lvdisplay "${volume}" 2>/dev/null | grep "LV Path" | awk '{print $3}') + + if [[ $(lsblk -no FSTYPE "${lv_path}") = "swap" ]]; then + log "Skipping swap: ${lv_path}" + continue; + fi + + # Get the volume path + volume=$(echo "${lv_path}" | awk -F'/' '{for(i=3;i<=NF;i++) printf "/%s", $i}') + + # Create the mount point directory + mkdir -p "/mnt${volume}" + + # Mount the volume + log "Mounting ${lv_path} on /mnt${volume}" + mount "${lv_path}" "/mnt${volume}" + done +else + log "Skipping LVM setup, lvm not part of this image" +fi \ No newline at end of file diff --git a/recipes/zquick_end/initcpio/install/zquick_end b/recipes/zquick_end/initcpio/install/zquick_end new file mode 100755 index 0000000..db0fe99 --- /dev/null +++ b/recipes/zquick_end/initcpio/install/zquick_end @@ -0,0 +1,5 @@ +#!/bin/bash + +build() { + rm -rf "${BUILDROOT}/input" +} diff --git a/recipes/zquick_end/recipe.yaml b/recipes/zquick_end/recipe.yaml new file mode 100644 index 0000000..d2d5305 --- /dev/null +++ b/recipes/zquick_end/recipe.yaml @@ -0,0 +1 @@ +help: "Required - last thing to be built" \ No newline at end of file diff --git a/recipes/zquick_fsextras/initcpio/install/zquick_fsextras b/recipes/zquick_fsextras/initcpio/install/zquick_fsextras index 03f60f3..77744e8 100755 --- a/recipes/zquick_fsextras/initcpio/install/zquick_fsextras +++ b/recipes/zquick_fsextras/initcpio/install/zquick_fsextras @@ -15,6 +15,7 @@ build() { add_binary mkfs.vfat add_binary efibootmgr add_binary cryptsetup + add_binary tune2fs add_module ext4 add_module ext3 diff --git a/recipes/zquick_installer/fs/zquick/libexec/installer/installer.sh b/recipes/zquick_installer/fs/zquick/libexec/installer/installer.sh index 51de7f2..5b834b5 100755 --- a/recipes/zquick_installer/fs/zquick/libexec/installer/installer.sh +++ b/recipes/zquick_installer/fs/zquick/libexec/installer/installer.sh @@ -21,6 +21,9 @@ EXISTING= CHROOT_MNT= # INSTALLER_DIR=/mnt/qemu-host/recipes/zquick_installer/fs/zquick/libexec/installer zquick_installer.sh +# zpool create -O acltype=posixacl -o compatibility=openzfs-2.0-linux TMP /dev/sdb + +STDOPTS=( "-o compression=zstd" "-o atime=off" "-o acltype=posixacl" "-o aclinherit=passthrough" "-o xattr=sa" "-o mountpoint=/" "-o canmount=noauto") log() { if [ $# -eq 0 ]; then @@ -39,17 +42,21 @@ xlog() { xspin() { gum style --faint "$*" - if ! gum spin --spinner=points --title='' -- bash -c "$* > /tmp/tmp 2>&1"; then - ret=$? - out="$(cat /tmp/tmp)" - gum style --foreground=1 "${out}" - exit $ret + ret=0 + gum spin --spinner=points --title='' -- bash -c "$* > /tmp/tmp 2>&1" || ret=$? + out='' + [[ -e /tmp/tmp ]] && out="$(cat /tmp/tmp)" + if ((ret!=0)); then + [[ -n "$out" ]] && gum style --foreground=1 "${out}" else - out="$(cat /tmp/tmp)" - [[ -n "$out" ]] && gum style --faint --foreground=#ff9770 "${out}" + [[ -n "$out" ]] && gum style --faint --foreground=#ffe2cc "${out}" fi rm -rf /tmp/tmp - return 0 + return "$ret" +} + +zpool_export() { + xspin zpool export "$1" } cleanup() { @@ -63,7 +70,7 @@ cleanup() { if [ -n "${POOLNAME}" ]; then echo "Exporting pool '${POOLNAME}'" - zpool export "${POOLNAME}" + zpool_export "${POOLNAME}" unset POOLNAME fi @@ -105,6 +112,8 @@ mount_esp() { import_tmproot_pool() { trap cleanup EXIT INT TERM + # sometimes zfs gets confused on where to find the pools, so specify all devices to help it + #devs=$(lsblk -no path | awk '{print "-d " $0}') CHROOT_MNT="$( mktemp -d )" || exit 1 if ! xspin zpool import -o cachefile=none -R "${CHROOT_MNT}" "${POOLNAME}"; then echo "ERROR: unable to import ZFS pool ${POOLNAME}" @@ -163,15 +172,19 @@ install_esp() { gum style "Copying QuickInitZFS to /efi" gum style "" mount_esp "$ESP" - mkdir -p /mnt - efi=$(find /mnt -type f -name '*.efi' -printf '%f\t%p\n' | sort -k1 | cut -d$'\t' -f2 | tail -n1) + # start QEMU envs + if [[ -d /mnt/qemu-host ]]; then + efi=$(find /mnt/qemu-host/output -type f -name '*.efi' -printf '%f\t%p\n' | sort -k1 | cut -d$'\t' -f2 | tail -n1) + [[ ! -x "${INJECT_SCRIPT}" ]] && INJECT_SCRIPT=$(find /mnt/qemu-host -type f -name 'zquickinit.sh' -print -quit) + fi + # end QEMU envs ( export INSTALLER_MODE=1 xspin "$INJECT_SCRIPT" inject /efi/EFI/zquickinit.efi "$efi" ) - echo "title ZFSQuickInit" > /efi/loader/entries/ZFSQuickInit.conf - echo "options zfsbootmenu ro loglevel=6 zbm.autosize=0" >> /efi/loader/entries/ZFSQuickInit.conf - echo "linux /EFI/ZFSQuickInit.efi" >> /efi/loader/entries/ZFSQuickInit.conf + echo "title zquickinit" > /efi/loader/entries/zquickinit.conf + echo "options zfsbootmenu ro loglevel=6 zbm.autosize=0" >> /efi/loader/entries/zquickinit.conf + echo "linux /EFI/zquickinit.efi" >> /efi/loader/entries/zquickinit.conf echo "timeout 3" > /efi/loader/loader.conf gum style "" fi @@ -201,11 +214,12 @@ partition_drive() { tput sc gum style --bold "CAUTION!!! ALL DATA WILL BE ERASED" if confirm --default=no "Proceed with partitioning $DEV?"; then + log umount "$DEV"?* umount "$DEV"?* >/dev/null 2>&1 || true gum spin --spinner=points --title="Clearing partition" -- sgdisk -og "$DEV" > /dev/null - gum spin --spinner=points --title="Creating BIOS" -- sgdisk -n 1:2048:+1M -c 1:"BIOS Boot Partition" -t 1:ef02 "$DEV" > /dev/null - gum spin --spinner=points --title="Creating ESP" -- sgdisk -n 2:0:+512M -c 2:"EFI System Partition" -t 2:ef00 "$DEV" > /dev/null - gum spin --spinner=points --title="Creating ZFS" -- sgdisk -I -n 3:0:-1M -c 3:"ZFS Root Partition" -t 3:bf01 "$DEV" > /dev/null + gum spin --spinner=points --title="Creating BIOS Boot Partition" -- sgdisk -n 1:2048:+1M -c 1:"BIOS Boot Partition" -t 1:ef02 "$DEV" > /dev/null + gum spin --spinner=points --title="Creating EFI System Partition" -- sgdisk -n 2:0:+512M -c 2:"EFI System Partition" -t 2:ef00 "$DEV" > /dev/null + gum spin --spinner=points --title="Creating ZFS Partition" -- sgdisk -I -n 3:0:-1M -c 3:"ZFS Root Partition" -t 3:bf01 "$DEV" > /dev/null tput rc tput ed log sgdisk -og "$DEV" @@ -214,13 +228,28 @@ partition_drive() { log sgdisk -I -n 3:0:-1M -c 3:"ZFS Root Partition" -t 3:bf01 "$DEV" gum style "Partitioning successful" gum style "" + xspin wipefs -a "$DEV"1 + xspin wipefs -a "$DEV"2 + xspin wipefs -a "$DEV"3 xspin sgdisk -p "$DEV" xspin sgdisk -v "$DEV" gum style "" + scan_parts "$DEV" fi fi } +scan_parts() { + log kpartx -u "$1" + kpartx -u "$1" || true + # dmsetup will try to map these drives, so remove them + if command -v dmsetup >/dev/null 2>&1; then + dev=/$(echo "$1" | cut -d/ -f3-) + log dmsetup remove /dev/mapper/"${dev}"* + dmsetup remove /dev/mapper/"${dev}"* >/dev/null 2>&1 || true + fi +} + create_pool() { gum style --bold "Choosing partition for new ZFS root pool" local zparts= @@ -257,10 +286,24 @@ create_pool() { -O aclinherit=passthrough \ -O xattr=sa \ -O atime=off \ + -o ashift=12 \ -o autotrim=on \ -o cachefile=none \ -o compatibility=openzfs-2.0-linux \ "${POOLNAME}" "${DEV}"; + log "Trying zpool secure trim" + if xspin zpool trim -d -w "${POOLNAME}"; then + log "Secure trim success" + else + log "Trying zpool trim (not secure)" + if xspin zpool trim -w "${POOLNAME}"; then + log "Zpool trim success" + else + log "Zpool trim did not return successfully: continuing" + fi + fi + log "Initializing zpool..." + xspin zpool initialize -w "${POOLNAME}" || true else exit 1 fi @@ -268,7 +311,7 @@ create_pool() { } select_pool() { - gum style --bold "Choose existing ZFS pool:" + gum style --bold "Choose ZFS pool:" local pools pools=$(zpool list -H -o name) readarray -t choices < <(printf %s "$pools") @@ -288,7 +331,7 @@ pick_or_create_pool() { defaultEsp=yes else gum style "* Existing Pool" - select_pool + select_poolz fi install_esp $defaultEsp } @@ -307,6 +350,7 @@ encrypt_ROOT() { for set in "${sets[@]}"; do under_root=${set#"${POOLNAME}"/ROOT/} size=$(zfs send -RvnP "${set}@copy" | tail -n1 | awk '{print $NF}') + log zfs send -R "${set}@copy" | pv -Wpbafte -s "$size" -i 1 | zfs receive -o encryption=on "${POOLNAME}/COPY/${under_root}" zfs send -R "${set}@copy" | pv -Wpbafte -s "$size" -i 1 | zfs receive -o encryption=on "${POOLNAME}/COPY/${under_root}" done xspin zfs destroy -R "${POOLNAME}/ROOT" @@ -315,9 +359,15 @@ encrypt_ROOT() { } create_encrypted_dataset() { + local ret=0 log zfs create ${2:-} -o encryption=on -o keyformat=passphrase -o keylocation=prompt "$1" - zfs create ${2:-} -o encryption=on -o keyformat=passphrase -o keylocation=prompt "$1" - # Sfter setting the passphrase, change keylocation to be a file which doesn't exist. ZFSBootMenu/ZFS will take care + for i in $(seq 1 3); do + ((i>1)) && echo "Retrying $i of 3..." + zfs create ${2:-} -o encryption=on -o keyformat=passphrase -o keylocation=prompt "$1" && ret=$? || ret=$? + ((ret==0)) && break + done + ((ret > 0)) && exit $ret + # After setting the passphrase, change keylocation to be a file which doesn't exist. ZFSBootMenu/ZFS will take care # of loading they key using 'zfs load-key -L prompt' to ask for the key even with a missing file for keylocation. # This path will be used by quick_loadkey to temporarily store the key so that it can be loaded by the chainloaded # kernel (i.e. proxmox, or another OS) @@ -401,31 +451,29 @@ create_dataset() { local set set=$(input --value=pve1 --prompt="Enter name for boot environment, without paths > ") - local stdopts=() - stdopts+=( "-o compression=zstd" "-o atime=off" "-o acltype=posixacl" "-o aclinherit=passthrough" "-o xattr=sa" "-o mountpoint=/" "-o canmount=noauto") if encroot="$( be_has_encroot "${POOLNAME}/ROOT" )"; then if confirm --affirmative="Inherit" --negative="New Passphrase" "${POOLNAME}/ROOT is encrypted. Do you want to inherit encryption or set a new passphrase?"; then - xspin zfs create ${stdopts[@]} "${POOLNAME}/ROOT/${set}" + xspin zfs create ${STDOPTS[@]} "${POOLNAME}/ROOT/${set}" else - create_encrypted_dataset "${POOLNAME}/ROOT/${set}" "${stdopts[*]}" + create_encrypted_dataset "${POOLNAME}/ROOT/${set}" "${STDOPTS[*]}" fi else if confirm --default=no "Do you want to encrypt ${POOLNAME}/ROOT/${set}"?; then - create_encrypted_dataset "${POOLNAME}/ROOT/${set}" "${stdopts[*]}" + create_encrypted_dataset "${POOLNAME}/ROOT/${set}" "${STDOPTS[*]}" else - xspin zfs create ${stdopts[@]} "${POOLNAME}/ROOT/${set}" + xspin zfs create ${STDOPTS[@]} "${POOLNAME}/ROOT/${set}" fi fi xspin zpool set bootfs="${POOLNAME}/ROOT/${set}" "${POOLNAME}" DATASET="${POOLNAME}/ROOT/${set}" - xspin zpool export "${POOLNAME}" + zpool_export "${POOLNAME}" import_tmproot_pool mount_dataset } import_tmproot_pool_dataset() { - xspin zpool export "${POOLNAME}" + zpool_export "${POOLNAME}" import_tmproot_pool mount_dataset } @@ -542,12 +590,152 @@ install() { gum style "" } +# unneeded for now, since its done as part of boot +mountLVM() { + if command -v lvm >/dev/null 2>&1; then + + echo "Scanning for LVM" + lvm vgscan -v 2>/dev/null + lvm vgchange -a y 2>/dev/null + + lvm_volumes=$(lvm lvscan 2>/dev/null | awk '{print $2}' | tr -d "'") + for volume in $lvm_volumes; do + # Get the logical volume path + lv_path=$(lvm lvdisplay "${volume}" 2>/dev/null | grep "LV Path" | awk '{print $3}') + + if [[ $(lsblk -no FSTYPE "${lv_path}") = "swap" ]]; then + log "Skipping swap: ${lv_path}" + continue; + fi + + # Get the volume path + volume=$(echo "${lv_path}" | awk -F'/' '{for(i=3;i<=NF;i++) printf "/%s", $i}') + + # Create the mount point directory + mkdir -p "/mnt${volume}" + + # Mount the volume + echo "Mounting ${lv_path} on /mnt${volume}" + mount "${lv_path}" "/mnt${volume}" + done + else + echo "Skipping LVM setup, lvm not part of this image" + fi +} +convertLVM() { + # first we need a temp root dataset, so pick or create one + gum style "To convert a boot drive from LVM to ZFS, this script will: " \ + "1) Create new encrypted ZFS dataset on a ZFS pool (not on the boot device)" \ + "2) Copy the LVM filesystem to this ZFS dataset" \ + "3) Partition the boot device (GPT style with 3 partitions: BIOS, ESP, ZFS)" \ + "4) Create a new ZFS ROOT pool on the boot device" \ + "5) Copy ZFS dataset to boot device, keeping encryption or replace with inherited encryption." \ + "" \ + "CAUTION: This script will only copy the root partition. If you are using LVM-thin" \ + "stop now and manually migrate them from the boot devices" \ + "" \ + "NOTE: If you are using swap on the LVM partition, this will be removed. The /etc/fstab" \ + "file will be updated to comment out the LVM mounts including swap." \ + "" + if ! confirm "Ready to start converting LVM boot to ZFS boot?"; then + exit 1 + fi + gum style "1) First, select a temporary ZFS pool - DO NOT select one on the boot drive" + select_pool + + gum style "2) Create a new ecrypted dataset: In a later step, you can keep this" \ + "encryption key or replace it with one inherited from the parent" \ + "ROOT dataset." \ + "" + set=$(input --value=pve-1 --prompt="Enter name for dataset (on ${POOLNAME}) > ") + create_encrypted_dataset "${POOLNAME}/${set}" "${STDOPTS[*]}" + + xspin mkdir -p "/mnt/${set}" + xspin mount -t zfs -o zfsutil "${POOLNAME}/${set}" "/mnt/${set}" + dataset_mnt="/mnt/${set}" + + # save the pool name for later use + tmppool="${POOLNAME}" + gum style "Select the LVM partition to copy:" + readarray -t choices <<<"$(mount -t ext4 | awk '{print $3}')" + if (( ${#choices[@]} == 0 )); then + gum style "No ext4 filesystems found!" + exit 1 + fi + filesystem=$(choose "${choices[@]}") + files=$(find "${filesystem}" | wc -l) + gum style "3) Copying ${filesystem} to ${dataset_mnt}" + gum style --faint -- "rsync -avxHAX ${filesystem}/ ${dataset_mnt}" + rsync -avxHAX "${filesystem}/" "${dataset_mnt}" | pv -lep -s "${files}" > /dev/null + + gum style "Commenting out LVM mounts in /etc/fstab" + xspin "sed -i 's|^/dev/pve|# Removed by zquickinit: &|' ${dataset_mnt}/etc/fstab" + gum style "Commenting out /boot/efi mounts in /etc/fstab" + xspin "sed -i 's|.*/boot/efi|# Removed by zquickinit: &|' ${dataset_mnt}/etc/fstab" + + gum style "Unmounting ${POOLNAME}/${set}" + xspin zfs unmount "${POOLNAME}/${set}" + xspin zfs set mountpoint=/ canmount=noauto "${POOLNAME}/${set}" + + gum style "Unmounting and deactiving all LVM volumes" + xspin umount "${filesystem}" + xspin lvm vgchange -an + + partition_drive + create_pool + defaultEsp=yes + install_esp $defaultEsp + + if ! has_roots; then + ensure_ROOT + fi + + recv=() + send=() + if encroot="$( be_has_encroot "${POOLNAME}/ROOT" )"; then + if confirm --affirmative="Inherit" --negative="Use existing" "${POOLNAME}/ROOT is encrypted. Do you want to inherit encryption or use existing?"; then + recv+=(-o encryption=on "${STDOPTS[@]}") + gum style "Dataset will inherit ${POOLNAME}/ROOT encryption" + else + send+=(-cewp) + gum style "Dataset will be copied with original encryption (raw)" + fi + else + gum style "NOTE: ${POOLNAME}/ROOT is not encrypted" + gum style "Dataset will be copied with original encryption" + send+=(-cewp) + fi + xspin zfs snapshot -r "${tmppool}/${set}@snapshot" + size=$(zfs send -vnP ${send[@]} "${tmppool}/${set}@snapshot" | tail -n1 | awk '{print $NF}') + log "zfs send ${send[@]} ${tmppool}/${set}@snapshot | pv -Wpbafte -s $size -i 1 | zfs receive ${recv[@]} ${POOLNAME}/ROOT/${set}" + zfs send ${send[@]} "${tmppool}/${set}@snapshot" | pv -Wpbafte -s "$size" -i 1 | zfs receive ${recv[@]} "${POOLNAME}/ROOT/${set}" + xspin zpool set bootfs="${POOLNAME}/ROOT/${set}" "${POOLNAME}" + zpool_export -a + + devs=$(lsblk --nodeps -n -o path) + for dev in ${devs}; do + log kpartx -u "$dev" + kpartx -u "$dev" || true + done + xspin zpool import -a + for dev in ${devs}; do + # dmsetup will try to map these drives, so remove them + if command -v dmsetup >/dev/null 2>&1; then + dev=/$(echo "$dev" | cut -d/ -f3-) + log dmsetup remove /dev/mapper/"${dev}"* + dmsetup remove /dev/mapper/"${dev}"* >/dev/null 2>&1 || true + fi + done + gum style "Finished!" +} + gum style --bold --border double --align center \ --width 50 --margin "1 2" --padding "0 2" "ZFSQuickInit Proxmox Installer" choices=("Install Proxmox 8.x" "Chroot into ZFS root dataset" "Encrypt existing dataset" + "Convert LVM root to ZFS root dataset" # "Rollback to pre install and run install" "Exit to shell") @@ -570,10 +758,16 @@ if [[ "$choice" =~ ^Encrypt.* ]]; then gum style "* Encrypt Existing Dataset" zpool import -a select_pool - zpool export "$POOLNAME" + zpool_export "${POOLNAME}" zpool import "$POOLNAME" encrypt_ROOT fi +if [[ "$choice" =~ ^Convert.* ]]; then + gum style "* Encrypt Existing Dataset" + zpool_export -a + zpool import -a + convertLVM +fi if [[ "$choice" =~ ^Rollback.* ]]; then gum style "* Rollback and install" zpool import -a >/dev/null 2>&1 diff --git a/recipes/zquick_installer/fs/zquick/zquick_installer.sh b/recipes/zquick_installer/fs/zquick/zquick_installer.sh index d97f506..81bf08d 100755 --- a/recipes/zquick_installer/fs/zquick/zquick_installer.sh +++ b/recipes/zquick_installer/fs/zquick/zquick_installer.sh @@ -3,6 +3,11 @@ : "${DEBUG:=}" : "${INSTALLER_DIR:=/zquick/libexec/installer}" +if [[ -d /mnt/qemu-host/recipes/zquick_installer/fs/zquick/libexec/installer ]]; then + echo "Running from qemu-host" + INSTALLER_DIR=/mnt/qemu-host/recipes/zquick_installer/fs/zquick/libexec/installer +fi + envs=( DEBUG="${DEBUG}" INSTALLER_DIR="${INSTALLER_DIR}" ) env "${envs[@]}" "${INSTALLER_DIR}"/installer.sh "$@" diff --git a/recipes/zquick_installer/initcpio/install/zquick_installer b/recipes/zquick_installer/initcpio/install/zquick_installer index e45056a..84cd978 100755 --- a/recipes/zquick_installer/initcpio/install/zquick_installer +++ b/recipes/zquick_installer/initcpio/install/zquick_installer @@ -5,7 +5,9 @@ build() { zquick_add_fs # shellcheck disable=SC2154 add_file "${zquickinit}/zquickinit.sh" /zquick/libexec/installer 755 - + add_binary rsync + add_binary blockdev + add_full_dir /usr/share/git-core add_full_dir /usr/libexec/git-core add_binary git @@ -22,6 +24,8 @@ build() { add_module nls_cp437 add_module nls_iso8859-1 add_module nls_utf8 + add_binary hdparm + add_binary blkdiscard add_binary ar add_binary mountpoint diff --git a/recipes/zquick_installer/recipe.yaml b/recipes/zquick_installer/recipe.yaml index 594e536..11a5926 100644 --- a/recipes/zquick_installer/recipe.yaml +++ b/recipes/zquick_installer/recipe.yaml @@ -6,4 +6,7 @@ xbps-packages: - pv - pax - findutils + - rsync + - util-linux + - hdparm help: "Download and install proxmox to root partition" \ No newline at end of file diff --git a/recipes/zquick_loadkey/core_patch b/recipes/zquick_loadkey/core_patch index 04466e0..3e6fa74 100644 --- a/recipes/zquick_loadkey/core_patch +++ b/recipes/zquick_loadkey/core_patch @@ -51,6 +51,7 @@ _zquickinit_loadkey() { return 0 fi done + mkdir -p "/zquick/run/load_key/skip/$keydir" echo "$keyinput" > "/zquick/run/load_key/skip/$keydir/$keyfile" fi return 1 diff --git a/recipes/zquick_reboot/fs/usr/sbin/poweroff b/recipes/zquick_reboot/fs/usr/sbin/poweroff new file mode 100755 index 0000000..4c901bd --- /dev/null +++ b/recipes/zquick_reboot/fs/usr/sbin/poweroff @@ -0,0 +1,28 @@ +#!/bin/bash + +trap "echo 'Poweroff interrupted.'; exit 1" INT SIGINT + +function reboot { + echo "Poweroff..." + for i in 3 2 1; do + echo $i + sleep 1 + done + + echo "Unmounting filesystems" + pools=$(zpool list -H -o name) + for pool in $pools; do + zpool export "$pool" + done + + echo "Running reboot hooks" + /zquick/libexec/run_hooks.sh reboot.d + + echo "Poweroff" + sleep 0.5s + echo s > /proc/sysrq-trigger + echo u > /proc/sysrq-trigger + echo o > /proc/sysrq-trigger +} + +reboot diff --git a/recipes/zquick_sshd/initcpio/install/zquick_sshd b/recipes/zquick_sshd/initcpio/install/zquick_sshd index c41e702..4bb39fe 100755 --- a/recipes/zquick_sshd/initcpio/install/zquick_sshd +++ b/recipes/zquick_sshd/initcpio/install/zquick_sshd @@ -22,7 +22,7 @@ build() { add_file /etc/ssh/ssh_host_ed25519_key fi - if [[ ! -e "${BUILDROOT}/etc/ssh/sshd_config" ]]; then + if [[ ! -e "${zquickinit_root}/sshd_config" ]]; then echo "No sshd_config specified, generating sshd_config that allows root login on port 22" mkdir -p "${BUILDROOT}/etc/ssh" cat <<-EOF > "${BUILDROOT}/etc/ssh/sshd_config" diff --git a/zquickinit.sh b/zquickinit.sh index a4e4305..d755398 100755 --- a/zquickinit.sh +++ b/zquickinit.sh @@ -26,6 +26,9 @@ ZBM_ROOT=${SRC_ROOT}/../zfsbootmenu RECIPES_ROOT=${RECIPES_ROOT:-${SRC_ROOT}/recipes} RECIPE_BUILDER="ghcr.io/midzelis/zquickinit" ZQUICKEFI_URL="https://github.com/midzelis/zquickinit/releases/latest" +# if empty, use latest release tag +ZBM_TAG=v2.2.1 +KERNEL_BOOT= ENGINE= OBJCOPY= FIND= @@ -132,11 +135,13 @@ builder() { # shellcheck disable=SC2016 mapfile -t -O "${#packages[@]}" packages < <($YG eval-all '. as $item ireduce ({}; . *+ $item) | (... | select(type == "!!seq")) |= unique | .xbps-packages[] | .. style=""' "$RECIPES_ROOT"/*/recipe.yaml) - local latest_release_tag='' - latest_release_tag=$(curl --silent https://api.github.com/repos/zbm-dev/zfsbootmenu/releases/latest | $YG .tag_name) - ZBM_COMMIT_HASH=$(curl --silent "https://api.github.com/repos/zbm-dev/zfsbootmenu/git/ref/tags/${latest_release_tag}" | $YG .object.sha) - echo "ZQuickInit commit hash: $(git rev-parse HEAD)" - echo "ZBM latest release: $latest_release_tag" + if [[ -z "$ZBM_TAG" ]]; then + ZBM_TAG=$(curl --silent https://api.github.com/repos/zbm-dev/zfsbootmenu/releases/latest | $YG .tag_name) + fi + ZBM_COMMIT_HASH=$(curl --silent "https://api.github.com/repos/zbm-dev/zfsbootmenu/git/ref/tags/${ZBM_TAG}" | $YG .object.sha) + ZQUICKINIT_COMMIT_HASH=$(git rev-parse HEAD) + echo "ZQUICKINIT_COMMIT_HASH: $ZQUICKINIT_COMMIT_HASH" + echo "ZBM_TAG: $ZBM_TAG" echo "ZBM_COMMIT_HASH: $ZBM_COMMIT_HASH" echo echo "Building with Packages: ${packages[*]}" @@ -146,6 +151,7 @@ builder() { --build-arg KERNELS=linux6.2 --build-arg "PACKAGES=${packages[*]}" --build-arg ZBM_COMMIT_HASH="${ZBM_COMMIT_HASH}" + --build-arg ZQUICKINIT_COMMIT_HASH="${ZQUICKINIT_COMMIT_HASH}" --progress=plain) ((GITHUBACTION==1)) && cmd+=(--cache-from type=gha) ((GITHUBACTION==1)) && cmd+=(--cache-to type=gha,mode=max) @@ -172,7 +178,7 @@ make_zquick_initramfs() { echo "Downloading zquickinit" rm -rf /input git clone --quiet --depth 1 https://github.com/midzelis/zquickinit.git /input - hash=$(cat /etc/zquickinit-commit-hash) + hash=$(cat /etc/zquickinit-commit-hash || echo '') if [[ -n "${hash}" ]]; then (cd /input && git fetch --depth 1 origin "$hash" && git checkout FETCH_HEAD) fi @@ -218,6 +224,8 @@ make_zquick_initramfs() { sorted=("$(sort <<<"${recipes[*]}")") # shellcheck disable=SC2206 sorted=( ${sorted[@]/zquick_core} ) + # shellcheck disable=SC2206 + sorted=( ${sorted[@]/zquick_end} ) gum style --bold --border double --align center \ --width 50 --margin "1 2" --padding "0 2" "Welcome to zquickinit" "(interactive mode)" @@ -271,7 +279,7 @@ make_zquick_initramfs() { system_hooks=("${system_hooks[@]/autodetect}") ((RELEASE==1)) && recipes=("${recipes[@]/zquick_qemu}") ((RELEASE==1)) && strip_sel=1 || system_hooks=("${system_hooks[@]/strip}") - ((RELEASE==1)) && echo "zquickinit" > /etc/hostname + echo "zquickinit" > /etc/hostname fi # need to reorder strip/zfsbootmenu @@ -291,6 +299,8 @@ make_zquick_initramfs() { # strip goes last [[ -n $strip_sel ]] && hooks+=("strip") + hooks+=("zquick_end") + build_time=$(date -u +"%Y-%m-%d_%H%M%S"); mkdir -p /tmp @@ -403,7 +413,7 @@ initramfs() { ((ENTER==1)) && cmd+=(--entrypoint=/bin/bash -i) cmd+=("$RECIPE_BUILDER") ((ENTER==0)) && cmd+=(make_zquick_initramfs) - ((ENTER==0 && NOASK==1)) && cmd+=(--noask) + ((ENTER==0 && NOASK==1)) && cmd+=(--no-ask) ((DEBUG==1)) && cmd+=(--debug) ((RELEASE==1)) && cmd+=(--release) echo @@ -489,7 +499,7 @@ inject() { # shellcheck disable=SC2094 ${FIND} "${tmp}" -not -path "${initrd}" -not -path "${source}" -print | \ - pax -x sv4cpio -wd -s#"${tmp}"/tmp## | zstd >> "${initrd}" + pax -x sv4cpio -wd -s#"${tmp}"## | zstd >> "${initrd}" echo "Copying new initramfs ${source} to EFI ${target}..." cp "${source}" "${target}" @@ -594,12 +604,19 @@ playground() { fi if [[ ! -e /tmp/disk.raw ]]; then - echo "NOTE! Creating 5GiB file at /tmp/disk.raw as a disk image" - truncate -s 5GiB /tmp/disk.raw + echo "NOTE! Creating 8GiB file at /tmp/disk.raw as a disk image" + truncate -s 8GiB /tmp/disk.raw else echo "NOTE! Using file /tmp/disk.raw as a disk image" fi + # if [[ ! -e /tmp/disk2.raw ]]; then + # echo "NOTE! Creating 3GiB file at /tmp/disk2.raw as a disk image" + # truncate -s 3GiB /tmp/disk2.raw + # else + # echo "NOTE! Using file /tmp/disk2.raw as a disk image" + # fi + APPEND=("loglevel=6 zbm.show") SSH_PORT=2222 aoi='' @@ -610,16 +627,17 @@ playground() { fi # shellcheck disable=SC2054 args=(qemu-system-x86_64 - -m 8G + -m 16G -smp "$(nproc)" -object rng-random,id=rng0,filename=/dev/urandom -device virtio-rng-pci,rng=rng0 -object iothread,id=iothread0 - -object iothread,id=iothread1 - -netdev user,id=n1,hostfwd=tcp::"${SSH_PORT}"-:22 -device virtio-net-pci,netdev=n1 - -device virtio-scsi-pci,id=scsi0,iothread=iothread0 - -device virtio-blk-pci,drive=drive1,iothread=iothread1 + -netdev user,id=n1,hostfwd=tcp::"${SSH_PORT}"-:22,hostfwd=tcp::8006-:8006 -device virtio-net-pci,netdev=n1 + -device virtio-scsi-pci,id=scsi0,iothread=iothread0 + -device scsi-hd,drive=drive1,bus=scsi0.0,bootindex=1,rotation_rate=1 + # -device scsi-hd,drive=drive2,bus=scsi0.0,bootindex=2,rotation_rate=1 -fsdev local,id=f1,path=.,security_model=none -device virtio-9p-pci,fsdev=f1,mount_tag=qemuhost - -drive file=/tmp/disk.raw,format=raw,if=none,discard=unmap${aoi},cache=writeback,id=drive1 + -drive file=/tmp/disk.raw,format=raw,if=none,discard=unmap${aoi},cache=writeback,id=drive1,unit=0 + # -drive file=/tmp/disk2.raw,format=raw,if=none,discard=unmap${aoi},cache=writeback,id=drive2,unit=1 -serial "mon:stdio" ) if [[ "$OSTYPE" == "darwin"* ]]; then @@ -664,17 +682,12 @@ playground() { if [[ -n "$iso" ]]; then args+=(-drive "file=${tmpiso},media=cdrom") echo "ISO images automatically configure QEMU vnc server" - args+=(-display vnc=0.0.0.0:0) if [[ -z "${ovmf}" ]]; then echo "EFI firmware is required for ISO images!" exit 1; fi - echo "Connect to VNC server running on port :5900" else - args+=(-kernel "$kernel") - args+=(-initrd "$initrd") - fi - if [[ -z "${iso}" ]]; then + [[ -z "$KERNEL_BOOT" ]] && KERNEL_BOOT=1 if [[ -z "${SSHONLY}" ]]; then LINES="$(tput lines)" COLUMNS="$(tput cols)" @@ -682,9 +695,16 @@ playground() { [ -n "${COLUMNS}" ] && APPEND+=( "zbm.columns=${COLUMNS}" ) # shellcheck disable=SC2054 APPEND+=(console=ttyS0,115200n8); - args+=(-append "${APPEND[*]}") fi fi + if ((KERNEL_BOOT==1)); then + args+=(-kernel "$kernel") + args+=(-initrd "$initrd") + args+=(-append "${APPEND[*]}") + else + args+=(-display vnc=0.0.0.0:0) + echo "Not using serial console, VNC is running on 0.0.0.0:5900" + fi echo echo "Hint: to quit QEMU, press ctrl-a, x" [[ -n "${SSHONLY}" ]] && echo Running in SSH only mode, to enter SSH use command: ssh root@localhost -p 2222 @@ -700,7 +720,7 @@ if [[ $(type -t "$command") == function ]]; then while (($# > 0)); do key="$1" case $key in - --noask) + --no-ask) NOASK=1 ;; -e|--enter) @@ -713,12 +733,15 @@ if [[ $(type -t "$command") == function ]]; then --release) RELEASE=1 ;; - --sshonly) + --ssh-only) SSHONLY=1 ;; --githubaction) GITHUBACTION=1 ;; + --no-kernel) + KERNEL_BOOT=0 + ;; *) ARGS+=("$1") ;; @@ -772,10 +795,10 @@ else echo " ZQuickInit image functions!" echo echo " Usage" - echo " zquickinit.sh initramfs [--noask]" + echo " zquickinit.sh initramfs [--no-ask]" echo " zquickinit.sh inject [target_efi] [source_efi]" echo " zquickinit.sh iso [target_iso] [source_efi] " - echo " zquickinit.sh playground [--sshonly]" + echo " zquickinit.sh playground [--ssh-only] [--no-kernel]" echo echo " Advanced Usage" echo " zquickinit.sh builder" @@ -790,9 +813,9 @@ else echo " play around with zquickinit.efi" echo echo " Options" - echo " --noask Do not ask any questions for building image. " + echo " --no-ask Do not ask any questions for building image. " echo " Just use files in the current directory, if present." - echo " --release Do not add QEMU debug" + echo " --release Do not add QEMU debug, or any secrets" echo " -d,--debug Advanced: Turn on tracing" echo " -e,--enter Advanced: Do not build an image. Execute bash and" echo " enter the builder image." @@ -800,4 +823,8 @@ else echo " if not specified." echo " target_efi Where the results of the image after injection will" echo " be stored. Default is zquickinit.efi in the current folder" + echo " --ssh-only Will launch playground without console output on ttyS0, you" + echo " must connect to playground using ssh on localhost:2222" + echo " --no-kernel Do not launch playground with kerenel image, boot from" + echo " configured drives instead" fi