Last active
November 12, 2025 16:04
-
-
Save dzogrim/b177bb0b9b746653f691ff01c9391a31 to your computer and use it in GitHub Desktop.
Kickstart Fedora 42 x86_64 minimal TTY Live hybrid ISO for firmware on/offline updates (fwupd)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: MIT | |
| #version=DEVEL | |
| # Fedora 42 x86_64 minimal TTY Live hybrid ISO for firmware on/offline updates (fwupd) | |
| # | |
| # Purpose (what this ISO is for) | |
| # - Text-only (TTY) Fedora 42 Live ISO to run firmware updates with fwupd. | |
| # - Works online (LVFS over HTTPS) or offline using a restored cache. | |
| # - Boots on UEFI and BIOS/legacy. | |
| # - No GUI (nmcli only). Wi-Fi supported. | |
| # - Persistence supported via an ext4 partition labeled 'overlay' on the USB stick. | |
| # - Includes bolt for Thunderbolt security/updates. | |
| # Released: 2025-11-12 (6.14.0-63.fc42) | |
| # | |
| # Build prerequisites: | |
| # sudo dnf install -y \ | |
| # lorax lorax-lmc-novirt lorax-templates-generic pykickstart shim-x64 \ | |
| # anaconda-core anaconda-tui anaconda-dracut gdisk \ | |
| # xorriso isomd5sum dosfstools policycoreutils-python-utils \ | |
| # grub2-tools grub2-tools-minimal grub2-tools-extra \ | |
| # grub2-efi-x64 grub2-efi-x64-modules grub2-efi-x64-cdboot \ | |
| # grub2-pc grub2-pc-modules | |
| # | |
| # Create ISO: | |
| # curl -L https://gist.github.com/dzogrim/dd025b82664c91e8aaf38355f3afcfe7/raw > fedora42-live-fwupd-tty.ks | |
| # sudo setenforce 0 | |
| # sudo livemedia-creator --make-iso --no-virt --releasever 42 \ | |
| # --resultdir ./out --ks ./fedora42-live-fwupd-tty.ks --keep-image \ | |
| # --lorax-templates /usr/share/lorax/templates.d/99-generic \ | |
| # --project Fedora-Live-fwupd-tty --volid FWUPDLIVE --iso-name fwupd-live-42-x86_64.iso | |
| # === Installation source (Everything base tree) === | |
| url --url=https://download.fedoraproject.org/pub/fedora/linux/releases/42/Everything/$basearch/os/ | |
| # === System configuration === | |
| lang en_US.UTF-8 # System language | |
| keyboard fr # French Keyboard layout | |
| timezone Europe/Paris # Time zone | |
| rootpw --plaintext liveuser # Root password (live only) | |
| selinux --permissive # Keep host enforcing; live image runs permissive | |
| # === Default user (for live use) === | |
| user --name=liveuser --groups=wheel --plaintext --password liveuser | |
| # === Services to enable (TTY only) === | |
| services --enabled=NetworkManager,fwupd,chronyd,ModemManager | |
| # === Bootloader (Live stick with persistence overlay) === | |
| bootloader --timeout=5 --append="rd.live.image rd.live.overlay=LABEL=overlay rd.live.overlay.overlayfs=1 rd.efi.writevars=1" | |
| # === Networking === | |
| network --device=link --bootproto=dhcp --activate --hostname=fwupd-live | |
| # === Target disk layout for the Live image (info) === | |
| # 1) BIOS boot partition (for BIOS on GPT) | |
| part biosboot --fstype=biosboot --size=1 | |
| # 2) "/boot/efi": EFI System Partition | |
| # * UEFI firmware requires FAT | |
| # * 2 GiB = enough for multiple kernel versions + UEFI firmware capsules | |
| # * Label "BOOT" makes manual mount/debug easier if needed | |
| part /boot/efi --size=2048 --fstype=vfat --label=BOOT | |
| # 3) "/": Root filesystem | |
| # * Contains the live OS image and runtime environment | |
| # * ext4 chosen for robustness and OverlayFS compatibility | |
| # * Label "ROOT" | |
| # * Size can be adjusted depending on the package set | |
| part / --size=10240 --fstype=ext4 --label=ROOT | |
| # 4) "/overlay": persistence partition | |
| # * ext4, labeled "overlay" | |
| # * Mountpoint is found automatically via rd.live.overlay=LABEL=overlay | |
| # * "--grow" consumes all remaining space | |
| part /overlay --fstype=ext4 --label=overlay --size=1 --grow | |
| # === Package selection (no GUI) === | |
| %packages --excludedocs | |
| # System base | |
| @core | |
| @hardware-support | |
| # Kernel and extra drivers | |
| kernel | |
| kernel-modules-extra | |
| # Firmware update via UEFI capsule | |
| fwupd | |
| fwupd-efi | |
| fwupd-plugin-flashrom | |
| fwupd-plugin-modem-manager | |
| fwupd-plugin-uefi-capsule-data | |
| ModemManager | |
| tpm2-tss | |
| bolt | |
| # Device support + tooling (QoL picks) | |
| bash-completion | |
| iw | |
| iwl*firmware | |
| less | |
| linux-firmware | |
| nano | |
| sudo | |
| usbutils | |
| zstd | |
| # Networking (nmcli is included in NetworkManager) & time | |
| NetworkManager | |
| NetworkManager-wifi | |
| wpa_supplicant | |
| wireless-regdb | |
| ca-certificates | |
| chrony | |
| # Live essentials | |
| dracut-live | |
| livesys-scripts | |
| # Boot (BIOS/UEFI) | |
| efibootmgr | |
| grub2-efi-x64 | |
| grub2-efi-x64-cdboot | |
| grub2-efi-x64-modules | |
| grub2-pc | |
| grub2-pc-modules | |
| grub2-tools-extra | |
| grub2-tools-minimal | |
| shim-x64 | |
| # Avoid glibc-all-langpacks | |
| glibc-langpack-en | |
| # Aggressive exclusions enforcement | |
| # Keep this lean: explicitly avoid heavy apps | |
| -*debuginfo | |
| -cheese | |
| -gnome-* | |
| -gstreamer* | |
| -gtk3 | |
| -gtk4 | |
| -libreoffice* | |
| -man-db | |
| -man-pages | |
| -mediawriter | |
| -nano-langpack | |
| -plymouth-theme-* | |
| %end | |
| ######## | |
| # POST # | |
| ######## | |
| # === Post-install === | |
| %post | |
| set -euo pipefail | |
| # Ensure overlay is mounted on next boots (idempotent) | |
| if ! grep -q '^LABEL=overlay /overlay ' /etc/fstab; then | |
| mkdir -p /overlay | |
| echo "LABEL=overlay /overlay ext4 noauto,x-systemd.automount,nofail 0 0" >> /etc/fstab | |
| fi | |
| mkdir -p /overlay | |
| # Ensure efivars is writable in live (for UEFI/dbx/capsules) | |
| cat >/etc/systemd/system/efivars-rw.service <<'EOF' | |
| [Unit] | |
| Description=Ensure efivarfs is mounted read-write for firmware updates | |
| After=local-fs.target | |
| ConditionPathExists=/sys/firmware/efi/efivars | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/usr/bin/sh -c '\ | |
| /usr/bin/mkdir -p /sys/firmware/efi/efivars ; \ | |
| /usr/bin/mountpoint -q /sys/firmware/efi/efivars || /usr/bin/mount -t efivarfs efivarfs /sys/firmware/efi/efivars || true ; \ | |
| /usr/bin/mount -o remount,rw /sys/firmware/efi/efivars || true \ | |
| ' | |
| RemainAfterExit=yes | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| install -d /etc/systemd/system/multi-user.target.wants | |
| ln -sf ../efivars-rw.service /etc/systemd/system/multi-user.target.wants/efivars-rw.service | |
| # 'sudo': allow the batch script without password; nothing else | |
| install -d -m 0750 /etc/sudoers.d | |
| cat >/etc/sudoers.d/90-fwupd-batch <<'EOF' | |
| liveuser ALL=(root) NOPASSWD: /usr/local/sbin/fwupd-batch.sh, /usr/local/sbin/fwupd-restore.sh | |
| Defaults!/usr/local/sbin/fwupd-batch.sh !requiretty | |
| Defaults!/usr/local/sbin/fwupd-restore.sh !requiretty | |
| EOF | |
| chmod 0440 /etc/sudoers.d/90-fwupd-batch | |
| # ====================== | |
| # = First-boot Service = | |
| # ====================== | |
| # First-boot LVFS refresh with robust connectivity wait (One-shot service) | |
| ## Starting with "fwupd-refresh-once.service" ... | |
| cat >/etc/systemd/system/fwupd-refresh-once.service <<'EOF' | |
| [Unit] | |
| Description=Refresh LVFS metadata on first boot (robust online wait) | |
| Wants=network-online.target | |
| After=network-online.target | |
| ConditionPathExists=!/var/lib/fwupd/.refreshed | |
| RequiresMountsFor=/overlay | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/usr/bin/sh -c '\ | |
| /usr/bin/mkdir -p /var/lib/fwupd; \ | |
| [ -f /var/lib/fwupd/.refreshed ] && exit 0; \ | |
| /usr/bin/nm-online -s -q || true; \ | |
| /usr/bin/fwupdmgr refresh --force && \ | |
| /usr/bin/touch /var/lib/fwupd/.refreshed \ | |
| ' | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| ## Done with "fwupd-refresh-once.service". | |
| # Enable par symlink (pas de reload necessaire lors du build) | |
| install -d /etc/systemd/system/multi-user.target.wants | |
| ln -sf ../fwupd-refresh-once.service \ | |
| /etc/systemd/system/multi-user.target.wants/fwupd-refresh-once.service | |
| # Activer aussi le timer fwupd (fourni par le paquet fwupd) | |
| install -d /etc/systemd/system/timers.target.wants | |
| ln -sf /usr/lib/systemd/system/fwupd-refresh.timer \ | |
| /etc/systemd/system/timers.target.wants/fwupd-refresh.timer | |
| # =============== | |
| # = Main Script = | |
| # =============== | |
| # Batch script: updates + cache archiving by product model/P-N, logging, retries, optional auto-reboot | |
| install -d -m 0755 /usr/local/sbin | |
| install -m 0755 /dev/stdin /usr/local/sbin/fwupd-batch.sh <<'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| umask 022 | |
| LOG="/var/log/fwupd-batch.log" | |
| exec > >(tee -a "$LOG") 2>&1 | |
| USE_TESTING="${USE_TESTING:-0}" # 1 = enable lvfs-testing | |
| AUTO_REBOOT="${AUTO_REBOOT:-0}" # 1 = reboot automatically if required | |
| MAX_RETRY="${MAX_RETRY:-2}" # retries for refresh/update | |
| CACHE_DIR="/var/cache/fwupd" | |
| OVERLAY="/overlay" | |
| pick_model() { | |
| for f in /sys/class/dmi/id/product_sku /sys/class/dmi/id/product_name /sys/class/dmi/id/board_name; do | |
| [[ -s "$f" ]] && { cat "$f"; return 0; } | |
| done | |
| echo "unknown-model" | |
| } | |
| model_raw="$(pick_model)" | |
| model_norm="$(echo -n "$model_raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g;s/^-+|-+$//g')" | |
| STAMP="$(date -u +%Y%m%d-%H%M%S)" | |
| ARCHIVE="${OVERLAY}/fwupd-cache-${model_norm}-${STAMP}.tar.zst" | |
| MANIFEST="${ARCHIVE}.sha256" | |
| echo "== fwupd-batch start ==" | |
| echo "Model: $model_raw -> $model_norm" | |
| echo "Log: $LOG" | |
| ############################### | |
| # Detect ONLINE / OFFLINE | |
| ############################### | |
| HAS_NET=0 | |
| nm-online -q 2>/dev/null && HAS_NET=1 | |
| if [[ "$HAS_NET" -eq 1 ]]; then | |
| echo "-- network detected: ONLINE mode" | |
| if [[ "$USE_TESTING" == "1" ]]; then | |
| echo "-- enabling lvfs-testing" | |
| fwupdmgr enable-remote lvfs-testing || true | |
| fi | |
| try() { local n=0; until "$@"; do n=$((n+1)); [[ $n -ge $MAX_RETRY ]] && return 1; echo "retry $n/$MAX_RETRY: $*"; sleep 3; done; } | |
| echo "-- refresh metadata (online)" | |
| try fwupdmgr refresh --force | |
| else | |
| echo "-- no network found: OFFLINE mode" | |
| # Find the most recent matching cache | |
| LATEST_CACHE="$( | |
| find "$OVERLAY" -maxdepth 1 -type f -name "fwupd-cache-${model_norm}-*.tar.zst" -print0 2>/dev/null \ | |
| | xargs -0 -r stat -c '%Y %n' 2>/dev/null \ | |
| | sort -nr | head -n1 | cut -d' ' -f2- | |
| )" | |
| if [[ -n "$LATEST_CACHE" ]]; then | |
| # Optional safety: ensure archive name contains the model | |
| case "$LATEST_CACHE" in | |
| *"$model_norm"*) : ;; | |
| *) | |
| echo "!! Cache archive exists but does not match this model: $model_norm" | |
| echo "Aborting to avoid flashing wrong firmware" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "-- found previous cache: $LATEST_CACHE" | |
| [[ -f "${LATEST_CACHE}.sha256" ]] && sha256sum -c "${LATEST_CACHE}.sha256" || true | |
| tar -C / -I zstd -xf "$LATEST_CACHE" | |
| echo "-- cache restored" | |
| else | |
| echo "!! no cache archive found for this model: $model_norm" | |
| echo "Offline mode cannot continue without preloaded firmware." | |
| exit 1 | |
| fi | |
| fi | |
| ############################### | |
| # Firmware update logic | |
| ############################### | |
| echo "-- device inventory" | |
| fwupdmgr get-devices || true | |
| echo "-- check updates" | |
| fwupdmgr get-updates || true | |
| echo "-- apply updates" | |
| if ! fwupdmgr update -y; then | |
| rc=$? | |
| echo "fwupd batch/update exited $rc; some updates may require reboot or second pass." | |
| fi | |
| ############################### | |
| # ONLINE ONLY: Save new cache | |
| ############################### | |
| if [[ "$HAS_NET" -eq 1 ]]; then | |
| if [[ -d "$CACHE_DIR" ]]; then | |
| echo "-- archiving new cache -> $ARCHIVE" | |
| tar -C / -I zstd -cf "$ARCHIVE" \ | |
| "var/cache/fwupd" \ | |
| "var/lib/fwupd" | |
| sha256sum "$ARCHIVE" > "$MANIFEST" | |
| echo "Archive: $ARCHIVE" | |
| echo "SHA256: $(cut -d' ' -f1 "$MANIFEST")" | |
| else | |
| echo "WARNING: cache directory not found, nothing to archive" | |
| fi | |
| fi | |
| ############################### | |
| # Reboot logic | |
| ############################### | |
| NEEDS_REBOOT=0 | |
| if fwupdmgr get-history 2>/dev/null | grep -qiE 'restart required|restart.*pending|pending.*device'; then | |
| NEEDS_REBOOT=1 | |
| fi | |
| echo "== fwupd-batch done ==" | |
| if [[ "$NEEDS_REBOOT" -eq 1 ]]; then | |
| echo "Reboot required to complete firmware installation." | |
| if [[ "$AUTO_REBOOT" -eq 1 ]]; then | |
| echo "AUTO_REBOOT=1 -> rebooting in 5 seconds..." | |
| sleep 5 | |
| /usr/bin/systemctl reboot | |
| fi | |
| fi | |
| EOF | |
| # NetworkManager / Wi-Fi helper | |
| mkdir -p /usr/local/sbin | |
| install -m 0755 /dev/stdin /usr/local/sbin/wifi.sh <<'EOS' | |
| #!/usr/bin/env bash | |
| ssid="$1" | |
| nmcli radio wifi on | |
| nmcli dev wifi connect "$ssid" --ask | |
| EOS | |
| # Restore helper: quickly restore a saved cache archive | |
| install -m 0755 /dev/stdin /usr/local/sbin/fwupd-restore.sh <<'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| ARCHIVE="${1:-}" | |
| [[ -z "$ARCHIVE" ]] && { echo "Usage: fwupd-restore.sh /path/to/fwupd-cache-*.tar.zst"; exit 1; } | |
| if [[ -f "${ARCHIVE}.sha256" ]]; then | |
| sha256sum -c "${ARCHIVE}.sha256" | |
| fi | |
| tar -C / -I zstd -xf "$ARCHIVE" | |
| fwupdmgr refresh --force | |
| fwupdmgr get-updates || true | |
| EOF | |
| # Convenience alias for the live user (uses NOPASSWD sudo for scripts only) | |
| mkdir -p /etc/profile.d | |
| cat >/etc/profile.d/fwupd.sh <<'EOF' | |
| alias fwupd-batch='sudo /usr/local/sbin/fwupd-batch.sh' | |
| alias fwupd-restore='sudo /usr/local/sbin/fwupd-restore.sh' | |
| EOF | |
| chmod 0644 /etc/profile.d/fwupd.sh | |
| # Also copy same aliases into skel so liveuser inherits them explicitly | |
| mkdir -p /etc/skel | |
| cat >>/etc/skel/.bashrc <<'EOF' | |
| # Firmware update helper aliases (auto-added) | |
| alias fwupd-batch='sudo /usr/local/sbin/fwupd-batch.sh' | |
| alias fwupd-restore='sudo /usr/local/sbin/fwupd-restore.sh' | |
| EOF | |
| # ========= | |
| # = Usage = | |
| # ========= | |
| # Create a usage file in the live user home | |
| mkdir -p /etc/skel | |
| cat >/etc/skel/USAGE.md <<'EOF' | |
| # Firmware Update Live environment (TTY only) | |
| # Network | |
| - Ethernet = auto | |
| - Wi-Fi with "wifi.sh" or via nmcli: | |
| nmcli radio wifi on | |
| nmcli dev wifi list | |
| nmcli dev wifi connect "SSID" password "pass" | |
| # Run firmware updates | |
| - Firmware batch (on stable LVFS): | |
| fwupd-batch | |
| - Firmware batch (with lvfs-testing): | |
| USE_TESTING=1 fwupd-batch | |
| - Firmware batch (with auto-reboot): | |
| AUTO_REBOOT=1 fwupd-batch | |
| # Restore a previous cache | |
| - Restore a previous cache: | |
| fwupd-restore /overlay/fwupd-cache-<model>-<stamp>.tar.zst | |
| # Offline update (no network required if cache exists) | |
| - Just locally run: | |
| fwupd-batch | |
| EOF | |
| chmod 0644 /etc/skel/USAGE.md | |
| install -d -m 0750 /home/liveuser | |
| cp -n /etc/skel/USAGE.md /home/liveuser/USAGE.md | |
| grep -q "fwupd-batch" /home/liveuser/.bashrc || cat /etc/skel/.bashrc >> /home/liveuser/.bashrc | |
| chown -R liveuser:liveuser /home/liveuser | |
| # Field teams reminder and usage banner | |
| mkdir -p /etc/motd.d/ | |
| install -d -m 0755 /etc/motd.d | |
| cat >/etc/motd.d/10-oem-requirements.motd <<'EOF' | |
| ------------------------------------------------------------ | |
| Fedora Firmware Live Environment (SELinux: permissive) | |
| ------------------------------------------------------------ | |
| FIELD TEAM REMINDERS | |
| - Some firmware updates require AC power. Plug in now. | |
| - For Thunderbolt devices not yet authorized: | |
| sudo boltctl list | |
| sudo boltctl enroll <UUID> | |
| TO BEGIN | |
| - Read usage instructions: | |
| cat USAGE.md | |
| ------------------------------------------------------------ | |
| EOF | |
| # === Dynamic HW Part Number in MOTD === | |
| install -d -m 0755 /usr/local/sbin | |
| cat >/usr/local/sbin/gen-motd-hw.sh <<'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| sku="" | |
| for f in /sys/class/dmi/id/product_sku /sys/class/dmi/id/board_asset_tag; do | |
| [[ -s "$f" ]] && { sku="$(cat "$f")"; break; } | |
| done | |
| model="" | |
| for f in /sys/class/dmi/id/product_name /sys/class/dmi/id/board_name; do | |
| [[ -s "$f" ]] && { model="$(cat "$f")"; break; } | |
| done | |
| sku=${sku:-unknown} | |
| model=${model:-unknown} | |
| cat >/etc/motd.d/90-hw.motd <<EOM | |
| ------------------------------------------------------------ | |
| Hardware: Part Number (SKU): ${sku} | |
| Model: ${model} | |
| ------------------------------------------------------------ | |
| EOM | |
| EOF | |
| chmod 0755 /usr/local/sbin/gen-motd-hw.sh | |
| cat >/etc/systemd/system/motd-hw.service <<'EOF' | |
| [Unit] | |
| Description=Generate MOTD with HW Part Number (SKU) | |
| After=local-fs.target | |
| ConditionPathExists=/sys/class/dmi/id | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/usr/local/sbin/gen-motd-hw.sh | |
| RemainAfterExit=yes | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| install -d /etc/systemd/system/multi-user.target.wants | |
| ln -sf ../motd-hw.service /etc/systemd/system/multi-user.target.wants/motd-hw.service | |
| # Active pam_motd pour TTY et SSH (idempotent) | |
| #grep -q 'pam_motd.so' /etc/pam.d/login || { | |
| # printf '%s\n' 'session optional pam_motd.so motd_dir=/etc/motd.d' >> /etc/pam.d/login | |
| #} | |
| #rm -f /home/liveuser/.hushlogin || true | |
| # Prepopulate bash history for convenience | |
| HISTFILE_SKEL="/etc/skel/.bash_history" | |
| cat >"$HISTFILE_SKEL" <<'EOF' | |
| cat USAGE.md | |
| fwupd-batch | |
| EOF | |
| chmod 0600 "$HISTFILE_SKEL" | |
| # If liveuser home already exists, also seed it now | |
| if [ -d /home/liveuser ]; then | |
| install -m 0600 "$HISTFILE_SKEL" /home/liveuser/.bash_history | |
| chown liveuser:liveuser /home/liveuser/.bash_history | |
| fi | |
| %end | |
| # Shutdown when the build completes | |
| shutdown |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| shopt -s failglob | |
| DEVICE=${DEVICE:-/dev/sdb} | |
| BUILTSRC="/home/liveuser/Downloads/out" | |
| ESP_SIZE="1024MiB" | |
| if [[ ! -b "$DEVICE" ]]; then echo "Device $DEVICE not found"; exit 1; fi | |
| if [[ ! -d "$BUILTSRC" ]]; then echo "Live project $BUILTSRC not found"; exit 1; fi | |
| for f in "$BUILTSRC/images/pxeboot/vmlinuz" \ | |
| "$BUILTSRC/images/pxeboot/initrd.img" \ | |
| "$BUILTSRC/LiveOS/squashfs.img" \ | |
| "$BUILTSRC/EFI/BOOT/BOOTX64.EFI"; do | |
| [[ -f "$f" ]] || { echo "Missing $f"; exit 1; } | |
| done | |
| part() { | |
| case "$DEVICE" in | |
| *[0-9]) printf "%sp%s" "$DEVICE" "$1" ;; | |
| *) printf "%s%s" "$DEVICE" "$1" ;; | |
| esac | |
| } | |
| # ensure cleanup if script stops (Ctrl-C, error, etc.) | |
| trap 'set +e; sync; sudo umount /mnt/efi /mnt/usb 2>/dev/null || true' EXIT | |
| printf "\nCurrently set device %s will be destroyed!\n" "$DEVICE" | |
| udevadm info --query=property --name "${DEVICE}" | grep -E 'ID_BUS=|ID_MODEL=|ID_SERIAL=|ID_SERIAL_SHORT=' || true | |
| BUS="$(udevadm info --query=property --name "${DEVICE}" | sed -n 's/^ID_BUS=//p')" | |
| TRAN="$(lsblk -dno TRAN "${DEVICE}" || true)" | |
| if [[ "${BUS:-}" != "usb" && "${TRAN:-}" != "usb" ]]; then | |
| echo "ERROR: ${DEVICE} does not look like a USB drive (BUS='${BUS:-}', TRAN='${TRAN:-}'). Abort." >&2 | |
| exit 1 | |
| fi | |
| printf "\nTHIS WILL WIPE %s. Type YES to continue: " "$DEVICE"; read -r ANSWER | |
| [[ "$ANSWER" == "YES" ]] || { echo "Aborted."; exit 1; } | |
| while IFS= read -r line; do | |
| dev=${line%% *}; mp=${line#* } | |
| [[ -n "$mp" && "$mp" != "$dev" ]] && sudo umount -- "$mp" | |
| done < <(lsblk -nrpo NAME,MOUNTPOINTS "$DEVICE" | awk 'NF>1') | |
| sync | |
| EFI_DEV="$(part 1)" | |
| LIVE_DEV="$(part 2)" | |
| OVLY_DEV="$(part 3)" | |
| sudo wipefs -a "$DEVICE" | |
| sudo sgdisk --zap-all "$DEVICE" | |
| sudo parted -s "$DEVICE" mklabel gpt | |
| sudo parted -s -a optimal "$DEVICE" mkpart ESP fat32 1MiB "$ESP_SIZE" | |
| sudo parted -s "$DEVICE" set 1 esp on | |
| sudo parted -s -a optimal "$DEVICE" mkpart LIVE ext4 "$ESP_SIZE" 95% | |
| sudo parted -s -a optimal "$DEVICE" mkpart OVERLAY ext4 95% 100% | |
| # after partitioning force kernel/udev to see them | |
| sudo partprobe "$DEVICE" || true | |
| sudo udevadm settle | |
| sudo mkfs.vfat -F32 -n EFI "$EFI_DEV" | |
| sudo mkfs.ext4 -F -L live "$LIVE_DEV" | |
| sudo mkfs.ext4 -F -L overlay "$OVLY_DEV" | |
| sudo mkdir -p /mnt/efi /mnt/usb | |
| sudo mount "$EFI_DEV" /mnt/efi | |
| sudo mount "$LIVE_DEV" /mnt/usb | |
| # Copy payload to the live partition (root of /mnt/usb) | |
| sudo install -D "$BUILTSRC/images/pxeboot/vmlinuz" /mnt/usb/vmlinuz | |
| sudo install -D "$BUILTSRC/images/pxeboot/initrd.img" /mnt/usb/initrd.img | |
| sudo mkdir -p /mnt/usb/LiveOS | |
| sudo install -D "$BUILTSRC/LiveOS/squashfs.img" /mnt/usb/LiveOS/squashfs.img | |
| # Copy EFI payload to the ESP | |
| sudo mkdir -p /mnt/efi/EFI/BOOT | |
| sudo rsync -aH --delete "$BUILTSRC/EFI/BOOT/" /mnt/efi/EFI/BOOT/ | |
| # Refresh cache once, then non-root reads are fine: | |
| sudo blkid -g | |
| # read UUIDs (quotes are important) | |
| EFI_UUID="$(sudo blkid -s UUID -o value "$EFI_DEV")" | |
| LIVE_UUID="$(sudo blkid -s UUID -o value "$LIVE_DEV")" | |
| OVLY_UUID="$(sudo blkid -s UUID -o value "$OVLY_DEV")" | |
| # Sanity print | |
| echo "EFI_DEV=$EFI_DEV EFI_UUID=$EFI_UUID" | |
| echo "LIVE_DEV=$LIVE_DEV LIVE_UUID=$LIVE_UUID" | |
| echo "OVLY_DEV=$OVLY_DEV OVLY_UUID=$OVLY_UUID" | |
| lsblk -o NAME,FSTYPE,UUID,MOUNTPOINT "${DEVICE}" | |
| # if any is empty, stop here | |
| for v in EFI_UUID LIVE_UUID OVLY_UUID; do | |
| [[ -n "${!v}" ]] || { echo "ERROR: $v is empty"; exit 1; } | |
| done | |
| EFI_CFG="/mnt/efi/EFI/BOOT/grub.cfg" | |
| sudo tee "$EFI_CFG" >/dev/null <<EOF | |
| set default=0 | |
| set timeout=10 | |
| insmod part_gpt | |
| insmod fat | |
| insmod ext2 | |
| insmod gzio | |
| insmod efi_gop | |
| insmod all_video | |
| # Set prefix on the ESP (FAT) so GRUB can load its modules | |
| search --no-floppy --fs-uuid --set=efi ${EFI_UUID} | |
| set prefix=(\$efi)/EFI/BOOT | |
| # Set root to the live (ext4) partition that holds vmlinuz/initrd/LiveOS | |
| search --no-floppy --set=root --fs-uuid ${LIVE_UUID} | |
| menuentry 'Start LiveFwupd42' { | |
| linuxefi /vmlinuz root=live:UUID=${LIVE_UUID} rd.live.overlay=UUID=${OVLY_UUID} rd.live.image quiet rhgb | |
| initrdefi /initrd.img | |
| } | |
| menuentry 'Test this media & start LiveFwupd42' { | |
| linuxefi /vmlinuz root=live:UUID=${LIVE_UUID} rd.live.overlay=UUID=${OVLY_UUID} rd.live.image rd.live.check quiet rhgb | |
| initrdefi /initrd.img | |
| } | |
| EOF | |
| echo "== GRUB (EFI) ==" | |
| grep -E '^(set (default|timeout)|search )|linuxefi|initrdefi|rd.live.overlay' "$EFI_CFG" || true | |
| echo "LIVE_UUID=${LIVE_UUID} (vmlinuz/initrd/squashfs)" | |
| echo "OVLY_UUID=${OVLY_UUID} (overlay)" | |
| sync | |
| sudo umount /mnt/efi || true | |
| sudo umount /mnt/usb || true | |
| sync | |
| lsblk -f "$DEVICE" | |
| #sudo parted ${DEVICE} resizepart 3 100% | |
| #sudo e2fsck -f ${DEVICE}3 | |
| #sudo resize2fs ${DEVICE}3 | |
| echo "Done." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment