#!/usr/bin/env bash # ============================================================================= # build-recovery-squashfs.sh # # Builds the minimal live environment for BastionGuard Recovery. # # Secure Boot modes: # auto -> detect firmware keys and choose shim or custom # shim -> force Microsoft/Debian shim chain # custom -> force custom firmware-trusted EFI signing # off -> no Secure Boot signing requirements # ============================================================================= set -Eeuo pipefail # ----------------------------------------------------------------------------- # Global configuration # ----------------------------------------------------------------------------- CHROOT_DIR="/tmp/bg-recovery-chroot" OUTPUT_DIR="/boot/bastionguard-recovery" LIVE_DIR="$OUTPUT_DIR/live" SQUASHFS="$LIVE_DIR/filesystem.squashfs" FILESYSTEM_SIZE="$LIVE_DIR/filesystem.size" LOG_FILE="/tmp/bastionguard-recovery-build.log" DISTRO="trixie" ARCH="amd64" MIRROR="https://deb.debian.org/debian" SOURCE_ZIP="/usr/share/bastionguard-backup/grub/bastionguard-backup.zip" SOURCE_ZIP_BASENAME="bastionguard-backup.zip" MESON_PREFIX="/usr" MESON_SYSCONFDIR="/etc" MESON_LOCALSTATEDIR="/var" MESON_LOCALDIR="/usr/share/locale" SECUREBOOT_MODE="${SECUREBOOT_MODE:-auto}" # auto | shim | custom | off CUSTOM_SIGN_BACKEND="${CUSTOM_SIGN_BACKEND:-auto}" # auto | sbctl | sbsign ENROLL_MOK_ON_BUILD="${ENROLL_MOK_ON_BUILD:-yes}" # yes | no MOK_PASSWORD="${MOK_PASSWORD:-bastionguard}" SECUREBOOT_DIR="/etc/bastionguard-backup/secureboot" MOK_KEY="$SECUREBOOT_DIR/MOK.key" MOK_CRT="$SECUREBOOT_DIR/MOK.crt" MOK_DER="$SECUREBOOT_DIR/MOK.der" CUSTOM_SB_DIR="/etc/bastionguard-backup/custom-secureboot" CUSTOM_EFI_KEY="$CUSTOM_SB_DIR/DB.key" CUSTOM_EFI_CRT="$CUSTOM_SB_DIR/DB.crt" CUSTOM_EFI_DER="$CUSTOM_SB_DIR/DB.der" SB_EFFECTIVE_MODE="" SB_FIRMWARE_MODE="bios" SB_ENABLED="no" SB_HAS_MS_KEYS="no" SB_HAS_CUSTOM_KEYS="no" CUSTOM_SIGN_BACKEND_EFFECTIVE="sbsign" # Asset installati da Meson PLYMOUTH_THEME_NAME="bastionguard" PLYMOUTH_THEME_DIR="/usr/share/plymouth/themes/bastionguard" PLYMOUTH_THEME_FILE="/usr/share/plymouth/themes/bastionguard/bastionguard.plymouth" WALLPAPER_INSTALLED="/usr/share/bastionguard-backup/data/wallpapers/bastionguard-wallpapers.png" WALLPAPER_DST="/usr/share/backgrounds/bastionguard-wallpapers.png" # Pacchetti runtime BASE_PACKAGES=( "linux-image-amd64" "live-boot" "live-boot-initramfs-tools" "systemd-sysv" "udev" "ca-certificates" "curl" "bash" "passwd" "coreutils" "util-linux" "e2fsprogs" "parted" "rsync" "debianutils" "cronie" "psmisc" "findutils" "grep" "sed" "gawk" "gzip" "xz-utils" "shim-signed" "grub-efi-amd64-signed" "grub-efi-amd64" "grub-common" "grub2-common" "efibootmgr" "os-prober" "mokutil" "sbsigntool" "plymouth" "plymouth-themes" "initramfs-tools" ) GUI_PACKAGES=( "xinit" "xserver-xorg-core" "xserver-xorg-video-fbdev" "xserver-xorg-input-libinput" "openbox" "dbus-x11" "xterm" "polkitd" "pkexec" "libgtk-4-1" "libvte-2.91-gtk4-0" "libgee-0.8-2" "libjson-glib-1.0-0" "fonts-dejavu-core" "network-manager" "network-manager-gnome" "network-manager-applet" "nm-connection-editor" "libnm0" "libsecret-1-0" "gnome-keyring" "conky-all" "feh" ) BUILD_PACKAGES=( "build-essential" "meson" "ninja-build" "pkg-config" "valac" "unzip" "gettext" "help2man" "desktop-file-utils" "appstream" "itstool" "xsltproc" "libgtk-4-dev" "libgee-0.8-dev" "libjson-glib-dev" "libvte-2.91-gtk4-dev" "libglib2.0-dev" "libcairo2-dev" "libgdk-pixbuf-2.0-dev" ) # ----------------------------------------------------------------------------- # Logging / errors # ----------------------------------------------------------------------------- exec > >(tee -a "$LOG_FILE") 2>&1 CURRENT_STEP="startup" log() { echo "[INFO] $*" } warn() { echo "[WARN] $*" >&2 } die() { echo echo "[ERROR] Step: $CURRENT_STEP" >&2 echo "[ERROR] $*" >&2 echo "[ERROR] Full log: $LOG_FILE" >&2 exit 1 } on_error() { local exit_code=$? local line_no=$1 echo echo "[FATAL] Build failed." >&2 echo "[FATAL] Step: $CURRENT_STEP" >&2 echo "[FATAL] Exit code: $exit_code" >&2 echo "[FATAL] Line: $line_no" >&2 echo "[FATAL] See full log: $LOG_FILE" >&2 exit "$exit_code" } trap 'on_error $LINENO' ERR # ----------------------------------------------------------------------------- # Helpers # ----------------------------------------------------------------------------- cleanup() { set +e umount -lf "$CHROOT_DIR/dev/pts" 2>/dev/null || true umount -lf "$CHROOT_DIR/dev" 2>/dev/null || true umount -lf "$CHROOT_DIR/proc" 2>/dev/null || true umount -lf "$CHROOT_DIR/sys" 2>/dev/null || true umount -lf "$CHROOT_DIR/run" 2>/dev/null || true rm -f "$CHROOT_DIR/usr/sbin/policy-rc.d" 2>/dev/null || true } trap cleanup EXIT require_cmd() { local cmd="$1" local pkg_hint="${2:-}" command -v "$cmd" >/dev/null 2>&1 || { [ -n "$pkg_hint" ] \ && die "Required command not found: $cmd (install package: $pkg_hint)" \ || die "Required command not found: $cmd" } } retry_cmd() { local max_attempts="$1" shift local attempt=1 while true; do if "$@"; then return 0 fi if [ "$attempt" -ge "$max_attempts" ]; then die "Command failed after $attempt attempts: $*" fi warn "Attempt $attempt failed, retrying in 5 seconds: $*" attempt=$((attempt + 1)) sleep 5 done } assert_file() { local path="$1" [ -f "$path" ] || die "Expected file not found: $path" } find_first_existing() { local path for path in "$@"; do if [ -f "$path" ]; then echo "$path" return 0 fi done return 1 } # ----------------------------------------------------------------------------- # Secure Boot detection / signing # ----------------------------------------------------------------------------- detect_firmware_mode() { if [ -d /sys/firmware/efi ]; then SB_FIRMWARE_MODE="uefi" else SB_FIRMWARE_MODE="bios" fi } detect_secure_boot_state() { SB_ENABLED="no" [ "$SB_FIRMWARE_MODE" = "uefi" ] || return 0 if command -v mokutil >/dev/null 2>&1; then if mokutil --sb-state 2>/dev/null | grep -qi "SecureBoot enabled"; then SB_ENABLED="yes" return 0 fi if mokutil --sb-state 2>/dev/null | grep -qi "SecureBoot disabled"; then SB_ENABLED="no" return 0 fi fi local sb_var sb_var="$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' 2>/dev/null | head -n1 || true)" if [ -n "$sb_var" ] && [ -r "$sb_var" ]; then local value value="$(od -An -t u1 -j4 -N1 "$sb_var" 2>/dev/null | tr -d ' ')" if [ "${value:-0}" = "1" ]; then SB_ENABLED="yes" fi fi } detect_microsoft_keys() { SB_HAS_MS_KEYS="no" [ "$SB_FIRMWARE_MODE" = "uefi" ] || return 0 if command -v efi-readvar >/dev/null 2>&1; then local tmp_db tmp_kek tmp_db="$(mktemp)" tmp_kek="$(mktemp)" efi-readvar -v db >"$tmp_db" 2>/dev/null || true efi-readvar -v KEK >"$tmp_kek" 2>/dev/null || true if grep -Eqi 'Microsoft|Windows UEFI CA|Microsoft Corporation UEFI' "$tmp_db" "$tmp_kek"; then SB_HAS_MS_KEYS="yes" fi rm -f "$tmp_db" "$tmp_kek" else warn "efi-readvar not found on host; Microsoft key detection skipped." fi } detect_custom_signing_material() { if [ -f "$CUSTOM_EFI_KEY" ] && [ -f "$CUSTOM_EFI_CRT" ]; then SB_HAS_CUSTOM_KEYS="yes" else SB_HAS_CUSTOM_KEYS="no" fi } detect_custom_sign_backend() { case "$CUSTOM_SIGN_BACKEND" in sbctl) command -v sbctl >/dev/null 2>&1 || die "CUSTOM_SIGN_BACKEND=sbctl but sbctl not found" CUSTOM_SIGN_BACKEND_EFFECTIVE="sbctl" ;; sbsign) CUSTOM_SIGN_BACKEND_EFFECTIVE="sbsign" ;; auto) if command -v sbctl >/dev/null 2>&1; then CUSTOM_SIGN_BACKEND_EFFECTIVE="sbctl" else CUSTOM_SIGN_BACKEND_EFFECTIVE="sbsign" fi ;; *) die "Invalid CUSTOM_SIGN_BACKEND: $CUSTOM_SIGN_BACKEND" ;; esac } ensure_mok_keys() { CURRENT_STEP="ensure_mok_keys" mkdir -p "$SECUREBOOT_DIR" chmod 700 "$SECUREBOOT_DIR" if [ ! -f "$MOK_KEY" ] || [ ! -f "$MOK_CRT" ]; then log "Generating new BastionGuard MOK key..." openssl req \ -new -x509 -newkey rsa:2048 \ -keyout "$MOK_KEY" \ -out "$MOK_CRT" \ -nodes \ -days 3650 \ -subj "/CN=BastionGuard Secure Boot/" chmod 600 "$MOK_KEY" chmod 644 "$MOK_CRT" else log "Using existing BastionGuard MOK key." fi openssl x509 -outform DER -in "$MOK_CRT" -out "$MOK_DER" chmod 644 "$MOK_DER" assert_file "$MOK_DER" } ensure_custom_keys() { CURRENT_STEP="ensure_custom_keys" assert_file "$CUSTOM_EFI_KEY" assert_file "$CUSTOM_EFI_CRT" mkdir -p "$CUSTOM_SB_DIR" chmod 700 "$CUSTOM_SB_DIR" openssl x509 -outform DER -in "$CUSTOM_EFI_CRT" -out "$CUSTOM_EFI_DER" chmod 644 "$CUSTOM_EFI_DER" assert_file "$CUSTOM_EFI_DER" } decide_secureboot_mode() { CURRENT_STEP="decide_secureboot_mode" detect_firmware_mode detect_secure_boot_state detect_microsoft_keys detect_custom_signing_material detect_custom_sign_backend log "Secure Boot detection:" log " Firmware mode : $SB_FIRMWARE_MODE" log " Secure Boot : $SB_ENABLED" log " Microsoft keys : $SB_HAS_MS_KEYS" log " Custom keypair : $SB_HAS_CUSTOM_KEYS" log " Requested mode : $SECUREBOOT_MODE" log " Sign backend : $CUSTOM_SIGN_BACKEND_EFFECTIVE" if [ "$SB_FIRMWARE_MODE" != "uefi" ]; then SB_EFFECTIVE_MODE="off" log " Effective mode : off (legacy BIOS)" return 0 fi case "$SECUREBOOT_MODE" in off) SB_EFFECTIVE_MODE="off" ;; shim) if [ "$SB_ENABLED" = "yes" ] && [ "$SB_HAS_MS_KEYS" != "yes" ]; then die "shim mode requested, but Microsoft keys were not detected in firmware." fi SB_EFFECTIVE_MODE="shim" ;; custom) ensure_custom_keys SB_EFFECTIVE_MODE="custom" ;; auto) if [ "$SB_ENABLED" != "yes" ]; then SB_EFFECTIVE_MODE="off" elif [ "$SB_HAS_MS_KEYS" = "yes" ]; then SB_EFFECTIVE_MODE="shim" elif [ "$SB_HAS_CUSTOM_KEYS" = "yes" ]; then ensure_custom_keys SB_EFFECTIVE_MODE="custom" else die "Secure Boot enabled, Microsoft keys not detected, and no custom signing keys available." fi ;; *) die "Invalid SECUREBOOT_MODE: $SECUREBOOT_MODE" ;; esac log " Effective mode : $SB_EFFECTIVE_MODE" } sign_file_with_sbsign() { local input="$1" local key="$2" local crt="$3" local output="${input}.signed" assert_file "$input" assert_file "$key" assert_file "$crt" sbsign --key "$key" --cert "$crt" --output "$output" "$input" assert_file "$output" mv -f "$output" "$input" } sign_file_custom() { local input="$1" assert_file "$input" case "$CUSTOM_SIGN_BACKEND_EFFECTIVE" in sbctl) sbctl sign -s "$input" ;; sbsign) sign_file_with_sbsign "$input" "$CUSTOM_EFI_KEY" "$CUSTOM_EFI_CRT" ;; *) die "Unsupported custom sign backend: $CUSTOM_SIGN_BACKEND_EFFECTIVE" ;; esac } sign_recovery_kernel_for_mode() { CURRENT_STEP="sign_recovery_kernel_for_mode" local kernel_path="$1" assert_file "$kernel_path" case "$SB_EFFECTIVE_MODE" in off) log "Skipping kernel signing (mode=off)." ;; shim) ensure_mok_keys log "Signing kernel with BastionGuard MOK..." sign_file_with_sbsign "$kernel_path" "$MOK_KEY" "$MOK_CRT" ;; custom) ensure_custom_keys log "Signing kernel with custom firmware-trusted certificate..." sign_file_custom "$kernel_path" ;; *) die "Unsupported Secure Boot mode for kernel signing: $SB_EFFECTIVE_MODE" ;; esac } write_main_grub_cfg() { mkdir -p "$OUTPUT_DIR/grub" cat > "$OUTPUT_DIR/grub/grub.cfg" << 'ENDGRUBCFG' set default=0 set timeout=5 menuentry "BastionGuard Recovery" { search --no-floppy --label --set=root bastionguard-recovery linux /bastionguard-recovery/vmlinuz boot=live components quiet splash bastionguard.recovery=1 live-media-path=/bastionguard-recovery/live initrd /bastionguard-recovery/initrd.img } menuentry "BastionGuard Recovery (debug)" { search --no-floppy --label --set=root bastionguard-recovery linux /bastionguard-recovery/vmlinuz boot=live components bastionguard.recovery=1 live-media-path=/bastionguard-recovery/live initrd /bastionguard-recovery/initrd.img } ENDGRUBCFG } write_grub_stub_cfg() { local stub_path="$1" mkdir -p "$(dirname "$stub_path")" cat > "$stub_path" << 'ENDSTUB' search --no-floppy --label --set=root bastionguard-recovery set prefix=($root)/bastionguard-recovery/grub configfile ($root)/bastionguard-recovery/grub/grub.cfg ENDSTUB } build_efi_layout_shim() { CURRENT_STEP="build_efi_layout_shim" log "Building EFI layout in shim mode..." local efi_dir="$OUTPUT_DIR/EFI/BOOT" mkdir -p "$efi_dir" local shim_src local mok_src local grub_signed_src shim_src="$(find_first_existing \ "$CHROOT_DIR/usr/lib/shim/shimx64.efi.signed" \ "$CHROOT_DIR/usr/lib/shim/shimx64.efi" \ "$(find "$CHROOT_DIR/usr/lib" -type f -name 'shimx64.efi.signed' 2>/dev/null | head -n1)" \ "$(find "$CHROOT_DIR" -type f -name 'shimx64.efi.signed' 2>/dev/null | head -n1)" )" || true [ -n "${shim_src:-}" ] || die "shim EFI binary not found in chroot." mok_src="$(find_first_existing \ "$CHROOT_DIR/usr/lib/shim/mmx64.efi" \ "$CHROOT_DIR/usr/lib/shim/mmx64.efi.signed" \ "$(find "$CHROOT_DIR/usr/lib" -type f -name 'mmx64.efi*' 2>/dev/null | head -n1)" )" || true grub_signed_src="$(find_first_existing \ "$CHROOT_DIR/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed" \ "$CHROOT_DIR/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed" \ "$(find "$CHROOT_DIR/usr/lib/grub" -type f -name 'grubx64.efi.signed' 2>/dev/null | head -n1)" )" || true [ -n "${grub_signed_src:-}" ] || die "Signed GRUB EFI binary not found in chroot." cp "$shim_src" "$efi_dir/BOOTx64.EFI" [ -n "${mok_src:-}" ] && cp "$mok_src" "$efi_dir/mmx64.efi" cp "$grub_signed_src" "$efi_dir/grubx64.efi" assert_file "$efi_dir/BOOTx64.EFI" assert_file "$efi_dir/grubx64.efi" write_main_grub_cfg write_grub_stub_cfg "$efi_dir/grub.cfg" write_grub_stub_cfg "$OUTPUT_DIR/EFI/debian/grub.cfg" write_grub_stub_cfg "$OUTPUT_DIR/grub.cfg" } build_efi_layout_custom() { CURRENT_STEP="build_efi_layout_custom" log "Building EFI layout in custom-key mode..." local efi_dir="$OUTPUT_DIR/EFI/BOOT" mkdir -p "$efi_dir" local grub_unsigned_src grub_unsigned_src="$(find_first_existing \ "$CHROOT_DIR/usr/lib/grub/x86_64-efi/monolithic/grubx64.efi" \ "$CHROOT_DIR/usr/lib/grub/x86_64-efi/grub.efi" \ "$(find "$CHROOT_DIR/usr/lib/grub" -type f \( -name 'grubx64.efi' -o -name 'grub.efi' \) 2>/dev/null | head -n1)" )" || true [ -n "${grub_unsigned_src:-}" ] || die "Custom-signable GRUB EFI binary not found in chroot." cp "$grub_unsigned_src" "$efi_dir/BOOTx64.EFI" assert_file "$efi_dir/BOOTx64.EFI" ensure_custom_keys sign_file_custom "$efi_dir/BOOTx64.EFI" cp "$efi_dir/BOOTx64.EFI" "$efi_dir/grubx64.efi" assert_file "$efi_dir/grubx64.efi" write_main_grub_cfg write_grub_stub_cfg "$efi_dir/grub.cfg" write_grub_stub_cfg "$OUTPUT_DIR/EFI/debian/grub.cfg" write_grub_stub_cfg "$OUTPUT_DIR/grub.cfg" } build_efi_layout_for_mode() { CURRENT_STEP="build_efi_layout_for_mode" case "$SB_EFFECTIVE_MODE" in off) log "Building EFI layout without Secure Boot chain..." mkdir -p "$OUTPUT_DIR/EFI/BOOT" write_main_grub_cfg write_grub_stub_cfg "$OUTPUT_DIR/EFI/BOOT/grub.cfg" write_grub_stub_cfg "$OUTPUT_DIR/EFI/debian/grub.cfg" write_grub_stub_cfg "$OUTPUT_DIR/grub.cfg" ;; shim) build_efi_layout_shim ;; custom) build_efi_layout_custom ;; *) die "Invalid SB_EFFECTIVE_MODE: $SB_EFFECTIVE_MODE" ;; esac } # ----------------------------------------------------------------------------- # UI / live environment helpers # ----------------------------------------------------------------------------- prepare_live_root() { CURRENT_STEP="prepare_live_root" log "Preparing live root..." umount -lf "$CHROOT_DIR/dev/pts" 2>/dev/null || true umount -lf "$CHROOT_DIR/dev" 2>/dev/null || true umount -lf "$CHROOT_DIR/proc" 2>/dev/null || true umount -lf "$CHROOT_DIR/sys" 2>/dev/null || true umount -lf "$CHROOT_DIR/run" 2>/dev/null || true mkdir -p \ "$CHROOT_DIR/dev" \ "$CHROOT_DIR/proc" \ "$CHROOT_DIR/sys" \ "$CHROOT_DIR/run" \ "$CHROOT_DIR/tmp" \ "$CHROOT_DIR/etc/network" \ "$CHROOT_DIR/var/log" \ "$CHROOT_DIR/var/cache/apt" chmod 1777 "$CHROOT_DIR/tmp" : > "$CHROOT_DIR/etc/network/interfaces" } configure_locale() { CURRENT_STEP="configure_locale" log "Setting locale inside chroot..." mkdir -p "$CHROOT_DIR/etc" cat > "$CHROOT_DIR/etc/default/locale" < "$CHROOT_DIR/root/.conkyrc" << 'ENDCONKY' conky.config = { background = true, double_buffer = true, own_window = true, own_window_type = 'desktop', own_window_transparent = true, own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager', alignment = 'top_left', gap_x = 24, gap_y = 24, minimum_width = 760, maximum_width = 980, draw_shades = false, draw_outline = false, draw_borders = false, use_xft = true, font = 'DejaVu Sans Mono:size=11', xftalpha = 1.0, update_interval = 60, cpu_avg_samples = 1, net_avg_samples = 1, default_color = 'white', }; conky.text = [[ ${font DejaVu Sans:bold:size=16}BastionGuard Recovery${font} ${font DejaVu Sans:bold:size=13}Recommended procedure after restore${font} 1. Complete the restore with BastionGuard Backup 2. Check disks and partitions with: lsblk -f 3. Reinstall GRUB on the installed system 4. Reboot and verify normal startup Automatic script: bastionguard-reinstall-grub ]]; ENDCONKY } create_grub_restore_helper() { CURRENT_STEP="create_grub_restore_helper" mkdir -p "$CHROOT_DIR/usr/local/bin" cat > "$CHROOT_DIR/usr/local/bin/bastionguard-reinstall-grub" << 'ENDGRUBHELP' #!/bin/bash set -euo pipefail cleanup_mounts() { umount -lf /mnt/dev 2>/dev/null || true umount -lf /mnt/proc 2>/dev/null || true umount -lf /mnt/sys 2>/dev/null || true umount -lf /mnt/run 2>/dev/null || true umount -lf /mnt/boot/efi 2>/dev/null || true umount -lf /mnt/boot 2>/dev/null || true umount -lf /mnt 2>/dev/null || true } die() { echo "ERROR: $*" cleanup_mounts exit 1 } trap cleanup_mounts EXIT echo "Available partitions:" lsblk -f ENDGRUBHELP chmod +x "$CHROOT_DIR/usr/local/bin/bastionguard-reinstall-grub" } install_plymouth_and_wallpaper() { CURRENT_STEP="install_plymouth_and_wallpaper" log "Setting Plymouth theme and wallpaper inside chroot..." chroot "$CHROOT_DIR" env LANG=C.UTF-8 LC_ALL=C.UTF-8 bash -lc " set -Eeuo pipefail THEME_NAME='$PLYMOUTH_THEME_NAME' THEME_FILE='$PLYMOUTH_THEME_FILE' WALLPAPER_SRC='$WALLPAPER_INSTALLED' WALLPAPER_DST='$WALLPAPER_DST' # --- Wallpaper: indipendente dal tema Plymouth --- if [ -f \"\$WALLPAPER_SRC\" ]; then mkdir -p /usr/share/backgrounds cp -f \"\$WALLPAPER_SRC\" \"\$WALLPAPER_DST\" echo '[INFO] Wallpaper installed:' \"\$WALLPAPER_DST\" else echo '[WARN] Wallpaper not found:' \"\$WALLPAPER_SRC\" >&2 fi # --- Plymouth theme --- if [ ! -f \"\$THEME_FILE\" ]; then echo '[WARN] Plymouth theme file not found:' \"\$THEME_FILE\" >&2 exit 0 fi if command -v plymouth-set-default-theme >/dev/null 2>&1; then plymouth-set-default-theme \"\$THEME_NAME\" else mkdir -p /etc/plymouth printf '[Daemon]\nTheme=%s\n' \"\$THEME_NAME\" > /etc/plymouth/plymouthd.conf fi update-initramfs -u # --- Verifica: tema attivo? --- if command -v plymouth-set-default-theme >/dev/null 2>&1; then current=\"\$(plymouth-set-default-theme 2>/dev/null || true)\" if [ \"\$current\" != \"\$THEME_NAME\" ]; then echo \"[WARN] Plymouth default theme is '\$current', expected '\$THEME_NAME'\" >&2 fi fi # --- Verifica: tema dentro l'initrd appena rigenerato? --- initrd=\"\$(ls /boot/initrd.img-* 2>/dev/null | sort -V | tail -1 || true)\" if [ -n \"\$initrd\" ] && command -v lsinitramfs >/dev/null 2>&1; then if lsinitramfs \"\$initrd\" | grep -q \"plymouth/themes/\$THEME_NAME/\"; then echo \"[INFO] Plymouth theme '\$THEME_NAME' present in \$initrd\" else echo \"[WARN] Plymouth theme '\$THEME_NAME' NOT found in \$initrd — splash will fall back to default\" >&2 fi fi " } # ----------------------------------------------------------------------------- # Main build # ----------------------------------------------------------------------------- main() { CURRENT_STEP="prerequisites" [ "$EUID" -eq 0 ] || die "Run as root (sudo or pkexec)." echo "========================================================" echo " BastionGuard Recovery — Squashfs environment build" echo "========================================================" echo " Log file: $LOG_FILE" echo require_cmd debootstrap debootstrap require_cmd mksquashfs squashfs-tools require_cmd chroot coreutils require_cmd mount util-linux require_cmd umount util-linux require_cmd awk gawk require_cmd openssl openssl require_cmd sbsign sbsigntool require_cmd unzip unzip require_cmd cp coreutils require_cmd ls coreutils require_cmd find findutils require_cmd tee coreutils require_cmd du coreutils if [ "$ENROLL_MOK_ON_BUILD" = "yes" ]; then require_cmd mokutil mokutil fi assert_file "$SOURCE_ZIP" CURRENT_STEP="cleanup_previous" log "Cleaning previous build directories..." cleanup rm -rf "$CHROOT_DIR" "$OUTPUT_DIR" mkdir -p "$CHROOT_DIR" "$OUTPUT_DIR" "$LIVE_DIR" CURRENT_STEP="debootstrap" log "Running debootstrap..." retry_cmd 3 debootstrap \ --arch="$ARCH" \ --variant=minbase \ --include="$(IFS=,; echo "${BASE_PACKAGES[*]}")" \ "$DISTRO" \ "$CHROOT_DIR" \ "$MIRROR" assert_file "$CHROOT_DIR/usr/bin/apt-get" CURRENT_STEP="apt_config" log "Configuring apt..." mkdir -p "$CHROOT_DIR/etc/apt/apt.conf.d" cat > "$CHROOT_DIR/etc/apt/apt.conf.d/80retries" << 'EOF' Acquire::Retries "5"; Acquire::http::Timeout "30"; Acquire::https::Timeout "30"; APT::Get::Assume-Yes "true"; EOF CURRENT_STEP="mount_pseudofs" log "Mounting pseudo-filesystems..." mkdir -p "$CHROOT_DIR/dev" "$CHROOT_DIR/dev/pts" "$CHROOT_DIR/proc" "$CHROOT_DIR/sys" "$CHROOT_DIR/run" mount --bind /dev "$CHROOT_DIR/dev" mount --bind /dev/pts "$CHROOT_DIR/dev/pts" mount -t proc /proc "$CHROOT_DIR/proc" mount -t sysfs /sys "$CHROOT_DIR/sys" mount --bind /run "$CHROOT_DIR/run" if [ -f /etc/resolv.conf ]; then cp /etc/resolv.conf "$CHROOT_DIR/etc/resolv.conf" fi mkdir -p "$CHROOT_DIR/usr/sbin" cat > "$CHROOT_DIR/usr/sbin/policy-rc.d" << 'EOF' #!/bin/sh exit 101 EOF chmod +x "$CHROOT_DIR/usr/sbin/policy-rc.d" CURRENT_STEP="install_packages" log "Installing runtime and build packages in chroot..." retry_cmd 5 chroot "$CHROOT_DIR" apt-get update -o Acquire::Retries=5 retry_cmd 5 env DEBIAN_FRONTEND=noninteractive \ chroot "$CHROOT_DIR" apt-get install -y --fix-missing \ "${GUI_PACKAGES[@]}" \ "${BUILD_PACKAGES[@]}" # Plugin label per i temi Plymouth basati su script che renderizzano testo. # Installazione non bloccante: il nome del pacchetto varia tra le release. env DEBIAN_FRONTEND=noninteractive \ chroot "$CHROOT_DIR" apt-get install -y plymouth-label-ft 2>/dev/null \ || env DEBIAN_FRONTEND=noninteractive \ chroot "$CHROOT_DIR" apt-get install -y plymouth-label 2>/dev/null \ || warn "Plymouth label plugin not available (theme text rendering may not work)." configure_locale CURRENT_STEP="copy_source" log "Copying source archive into chroot..." mkdir -p "$CHROOT_DIR/root/src" cp -f "$SOURCE_ZIP" "$CHROOT_DIR/root/src/$SOURCE_ZIP_BASENAME" assert_file "$CHROOT_DIR/root/src/$SOURCE_ZIP_BASENAME" CURRENT_STEP="extract_source" log "Extracting source archive in chroot..." chroot "$CHROOT_DIR" env LANG=C.UTF-8 LC_ALL=C.UTF-8 bash -lc " set -Eeuo pipefail cd /root/src rm -rf bastionguard-backup extracted-src mkdir -p extracted-src cd extracted-src unzip -q ../$SOURCE_ZIP_BASENAME topdir=\$(find . -mindepth 1 -maxdepth 1 -type d | head -n1) [ -n \"\$topdir\" ] || { echo 'ERROR: no extracted top-level directory found'; exit 1; } mv \"\$topdir\" ../bastionguard-backup # Fix timestamps imported from ZIP archive. find /root/src/bastionguard-backup -exec touch -h {} + [ -f /root/src/bastionguard-backup/meson.build ] || { echo 'ERROR: meson.build not found after extraction'; exit 1; } " CURRENT_STEP="build_project" log "Building bastionguard-backup in chroot..." chroot "$CHROOT_DIR" env LANG=C.UTF-8 LC_ALL=C.UTF-8 bash -lc " set -Eeuo pipefail cd /root/src/bastionguard-backup rm -rf build meson setup build \ --prefix=$MESON_PREFIX \ --sysconfdir=$MESON_SYSCONFDIR \ --localstatedir=$MESON_LOCALSTATEDIR \ --localedir=$MESON_LOCALDIR ninja -C build ninja -C build install command -v bastionguard-backup-gtk >/dev/null 2>&1 || test -x /usr/bin/bastionguard-backup-gtk " CURRENT_STEP="install_launcher" log "Configuring recovery launcher..." mkdir -p "$CHROOT_DIR/usr/bin" cat > "$CHROOT_DIR/usr/bin/bastionguard-launcher" << 'ENDLAUNCHER' #!/bin/bash exec bastionguard-backup-gtk --recovery ENDLAUNCHER chmod +x "$CHROOT_DIR/usr/bin/bastionguard-launcher" mkdir -p "$CHROOT_DIR/root/.config/openbox" cat > "$CHROOT_DIR/root/.config/openbox/menu.xml" << 'ENDMENU' /usr/bin/bastionguard-launcher xterm -fa "Monospace" -fs 12 -e /usr/local/bin/bastionguard-reinstall-grub xterm -fa "Monospace" -fs 12 nm-connection-editor reboot poweroff ENDMENU cat > "$CHROOT_DIR/root/.config/openbox/autostart" << ENDAUTO if [ -f "$WALLPAPER_DST" ]; then feh --no-fehbg --bg-fill "$WALLPAPER_DST" else xsetroot -solid "#1a1a2e" fi nm-applet --indicator & conky & /usr/bin/bastionguard-launcher & ENDAUTO cat > "$CHROOT_DIR/root/.xinitrc" << 'ENDXINIT' #!/bin/bash export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/0}" mkdir -p "$XDG_RUNTIME_DIR" chmod 700 "$XDG_RUNTIME_DIR" eval "$(dbus-launch --sh-syntax)" export DBUS_SESSION_BUS_ADDRESS eval "$(gnome-keyring-daemon --start --components=secrets)" export GNOME_KEYRING_CONTROL export GNOME_KEYRING_PID exec openbox-session ENDXINIT chmod +x "$CHROOT_DIR/root/.xinitrc" create_desktop_notes create_grub_restore_helper CURRENT_STEP="live_config" log "Configuring live system..." mkdir -p "$CHROOT_DIR/etc/live" cat > "$CHROOT_DIR/etc/live/config" << 'ENDLIVE' LIVE_HOSTNAME="bastionguard-recovery" LIVE_USERNAME="root" LIVE_USER_FULLNAME="BastionGuard Recovery" LIVE_NOCONFIGS="True" ENDLIVE mkdir -p "$CHROOT_DIR/etc/systemd/system/getty@tty1.service.d" cat > "$CHROOT_DIR/etc/systemd/system/getty@tty1.service.d/autologin.conf" << 'ENDGETTY' [Service] ExecStart= ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM ENDGETTY cat > "$CHROOT_DIR/root/.bash_profile" << 'ENDBASH' if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then export XDG_RUNTIME_DIR="/run/user/0" mkdir -p "$XDG_RUNTIME_DIR" chmod 700 "$XDG_RUNTIME_DIR" exec startx /root/.xinitrc -- :0 vt1 -nolisten tcp 2>/tmp/xorg.log fi ENDBASH echo "bastionguard-recovery" > "$CHROOT_DIR/etc/hostname" if [ ! -x "$CHROOT_DIR/usr/sbin/chpasswd" ]; then die "chpasswd missing in chroot (install package: passwd)" fi printf 'root:root\n' | chroot "$CHROOT_DIR" /usr/sbin/chpasswd chroot "$CHROOT_DIR" systemctl disable \ apt-daily.timer \ apt-daily-upgrade.timer \ man-db.timer \ 2>/dev/null || true chroot "$CHROOT_DIR" systemctl enable NetworkManager 2>/dev/null || true mkdir -p "$CHROOT_DIR/etc/NetworkManager/conf.d" cat > "$CHROOT_DIR/etc/NetworkManager/conf.d/10-recovery.conf" << 'ENDNM' [main] plugins=keyfile [keyfile] unmanaged-devices=none ENDNM mkdir -p "$CHROOT_DIR/etc/polkit-1/rules.d" cat > "$CHROOT_DIR/etc/polkit-1/rules.d/50-nm-recovery.rules" << 'ENDPOLKIT' polkit.addRule(function(action, subject) { if (action.id.indexOf("org.freedesktop.NetworkManager.") === 0 && subject.user === "root") { return polkit.Result.YES; } }); ENDPOLKIT CURRENT_STEP="cleanup_build_artifacts" log "Removing source tree and build-only packages from chroot..." chroot "$CHROOT_DIR" env LANG=C.UTF-8 LC_ALL=C.UTF-8 bash -lc " set -Eeuo pipefail rm -rf /root/src/bastionguard-backup rm -rf /root/src/extracted-src rm -f /root/src/$SOURCE_ZIP_BASENAME apt-get purge -y ${BUILD_PACKAGES[*]} apt-get autoremove -y --purge apt-get clean rm -rf /var/lib/apt/lists/* rm -rf /var/cache/apt/* rm -rf /tmp/* rm -rf /var/tmp/* find /var/log -type f -delete 2>/dev/null || true " install_plymouth_and_wallpaper CURRENT_STEP="extract_kernel_initrd" log "Extracting kernel and initrd..." local kernel_file initrd_file kernel_file="$(ls "$CHROOT_DIR/boot"/vmlinuz-* 2>/dev/null | sort -V | tail -1 || true)" initrd_file="$(ls "$CHROOT_DIR/boot"/initrd.img-* 2>/dev/null | sort -V | tail -1 || true)" if [ -z "${kernel_file:-}" ] || [ -z "${initrd_file:-}" ]; then warn "Kernel/initrd not found in chroot; using host fallback." kernel_file="$(ls /boot/vmlinuz-* 2>/dev/null | sort -V | tail -1 || true)" initrd_file="$(ls /boot/initrd.img-* 2>/dev/null | sort -V | tail -1 || true)" [ -n "${kernel_file:-}" ] || die "No kernel found in chroot or host." [ -n "${initrd_file:-}" ] || die "No initrd found in chroot or host." fi cp "$kernel_file" "$OUTPUT_DIR/vmlinuz" cp "$initrd_file" "$OUTPUT_DIR/initrd.img" assert_file "$OUTPUT_DIR/vmlinuz" assert_file "$OUTPUT_DIR/initrd.img" CURRENT_STEP="secureboot_layout" decide_secureboot_mode sign_recovery_kernel_for_mode "$OUTPUT_DIR/vmlinuz" build_efi_layout_for_mode prepare_live_root CURRENT_STEP="filesystem_size" log "Calculating filesystem.size..." du -sx --block-size=1 "$CHROOT_DIR" | awk '{print $1}' > "$FILESYSTEM_SIZE" assert_file "$FILESYSTEM_SIZE" CURRENT_STEP="create_squashfs" log "Creating squashfs..." mksquashfs \ "$CHROOT_DIR" \ "$SQUASHFS" \ -comp xz \ -b 1M \ -Xdict-size 100% \ -e boot \ -e var/cache/apt/* \ -e var/log/* \ -wildcards \ -progress assert_file "$SQUASHFS" CURRENT_STEP="enroll_mok" if [ "$ENROLL_MOK_ON_BUILD" = "yes" ] && [ "$SB_EFFECTIVE_MODE" = "shim" ] && [ -f "$MOK_DER" ]; then log "Staging MOK enrollment request with mokutil..." printf '%s\n%s\n' "$MOK_PASSWORD" "$MOK_PASSWORD" | mokutil --import "$MOK_DER" || true log "MOK import request staged. Enrollment still requires reboot + MokManager confirmation." fi CURRENT_STEP="final_cleanup" log "Cleaning temporary chroot..." cleanup rm -rf "$CHROOT_DIR" echo echo "========================================================" echo " Build completed successfully" echo "========================================================" echo " Output directory: $OUTPUT_DIR" echo " Live directory : $LIVE_DIR" echo " Squashfs : $SQUASHFS" echo " Log file : $LOG_FILE" echo " SecureBoot mode : $SB_EFFECTIVE_MODE" echo [ -f "$MOK_DER" ] && echo " MOK certificate : $MOK_DER" [ -f "$CUSTOM_EFI_DER" ] && echo " Custom cert DER : $CUSTOM_EFI_DER" echo if [ "$ENROLL_MOK_ON_BUILD" = "yes" ] && [ "$SB_EFFECTIVE_MODE" = "shim" ]; then echo " MOK password staged for MokManager: $MOK_PASSWORD" echo " Reboot required to complete enrollment in MokManager." echo fi echo " Next step:" echo " bash $(dirname "$0")/install-grub-entry.sh" echo "========================================================" } main "$@"