BastionGuard/thirdparty/bastionguard-recovery-data/scripts/build-recovery-squashfs.sh
specialworld83 f0f913a209 Release 2.0
2026-07-15 10:52:22 +02:00

1185 lines
35 KiB
Bash
Executable file

#!/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" <<EOF
LANG=C.UTF-8
LC_ALL=C.UTF-8
EOF
}
create_desktop_notes() {
CURRENT_STEP="create_desktop_notes"
mkdir -p "$CHROOT_DIR/root"
cat > "$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'
<?xml version="1.0" encoding="UTF-8"?>
<openbox_menu xmlns="http://openbox.org/3.4/menu">
<menu id="root-menu" label="BastionGuard Recovery">
<item label="BastionGuard Backup">
<action name="Execute">
<command>/usr/bin/bastionguard-launcher</command>
</action>
</item>
<item label="Restore GRUB">
<action name="Execute">
<command>xterm -fa "Monospace" -fs 12 -e /usr/local/bin/bastionguard-reinstall-grub</command>
</action>
</item>
<separator/>
<item label="Terminal">
<action name="Execute">
<command>xterm -fa "Monospace" -fs 12</command>
</action>
</item>
<item label="Network (NetworkManager)">
<action name="Execute">
<command>nm-connection-editor</command>
</action>
</item>
<separator/>
<item label="Reboot">
<action name="Execute">
<command>reboot</command>
</action>
</item>
<item label="Power off">
<action name="Execute">
<command>poweroff</command>
</action>
</item>
</menu>
</openbox_menu>
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 "$@"