Compare commits

...
8 Commits
Author SHA1 Message Date
gandalf ff806523b7 fix(live-usb): use baked-in UUIDs for ESP/LIVE search (label lookups fail on real HW)
Real UEFI hardware v2.12.7/v2.12.8 still landed in the grub> rescue
shell, and the user reported the same happening in legacy BIOS mode
too — both code paths share the /boot/grub/grub.cfg which begins with
`search --no-floppy --label LIVE --set=live`. If that label lookup
fails (some firmware doesn't surface FAT/ext4 labels, or the firmware
exposes the disk through a path GRUB's label table doesn't recognise)
\$live stays unset, every menuentry's `linux (\$live)/live/vmlinuz`
becomes `linux ()/live/vmlinuz`, the kernel doesn't load, GRUB
drops the operator at the rescue prompt. Same root cause as the
EFI embed-cfg's `search --label ESP` failure.

Capture the ESP + LIVE UUIDs with blkid right after mkfs.* and
substitute them into both configs:

  * grub.cfg uses `search --fs-uuid <LIVE_UUID> --set=live` as
    primary; falls back to `search --label LIVE` for firmware that
    DOES prefer labels (no-op if UUID already resolved).
  * embed-cfg uses `\$cmdpath/grub.cfg` first, then falls back to
    `search --fs-uuid <ESP_UUID>` + configfile from that root.

UUIDs are deterministic at build time, can't be shadowed by similarly
labelled partitions, and don't depend on the firmware's label
indexing — should work on any firmware that surfaces the disk to
GRUB's block IO at all.
2026-05-25 15:48:34 +02:00
gandalf dcb4ce828b fix(live-usb): EFI embed uses \$cmdpath so it doesn't depend on label search
Real UEFI hardware v2.12.7 reported landing at the grub> rescue
prompt even with the simple pre-e1a53297 embed-cfg restored. Cause:
`search --no-floppy --label ESP --set=root` failed silently on that
firmware (FAT label not recognised, predictable rename, etc.), so
\$root stayed unset, prefix was empty, configfile fell through to
the rescue shell.

Replace the embed with a single-line `configfile \$cmdpath/grub.cfg`.
\$cmdpath is set by the EFI firmware itself to the directory it
loaded BOOTX64.EFI from — for us that's (hdX,gptY)/EFI/BOOT, where
the build copies grub.cfg already. The full menu at /EFI/BOOT/grub.cfg
still does its own `search --label LIVE --set=live` to locate the
squashfs partition (unchanged), so this only changes the very first
lookup hop.

Legacy BIOS grub-install --target=i386-pc untouched — it never had
the issue.
2026-05-25 11:00:44 +02:00
gandalf a88c177372 fix(live-usb): simplify bootstrap netplan — DHCP on e* only
Real-hardware report: only the phantom `br-lan` (interfaces: [] +
static 192.168.1.1/24) showed an IP, the physical ethernet stayed
sec. The old netplan declared three separate interface groups
(en*, eth*, all-wifi with empty SSID) plus a member-less bridge with
a static address — that was enough to confuse networkd into
honouring the bridge while dropping the real DHCP request on the
floor.

Cut to the bone: one `eth-all` block matching `e*` (which covers
enpXsY, enoX, ensX, enxAABBCC and ethX), DHCP only, no static, no
bridge, no wifi-with-empty-SSID. Operators wire router-mode br-lan
or wifi later via the secubox-net-* tooling once they've decided
this is an endpoint vs a router.

For the no-DHCP fallback case, secubox-net-fallback.service is
already shipped and runs after networkd to ARP-probe common gateways
and assign a free .250 IP — that remains unchanged.

This means v2.12.6 has both a clean EFI build AND a netplan that
actually requests DHCP on the physical NIC instead of squatting on
the bridge.
2026-05-25 09:40:57 +02:00
gandalf 36612f622d revert(live-usb): restore simple GRUB EFI build (pre-e1a53297)
Real UEFI hardware boot was broken by my e1a53297 "hardening" — the
gold-plated module list (disk/usb/usbms/ahci/ata/...) and multi-stage
embed-cfg search fallback that I added to chase a VBox EFI Shell drop
turned out to break legitimate UEFI firmware too. None of those
modules exist in x86_64-efi (block IO comes from the EFI firmware
itself), and the if/else cmdpath fallback landed users in a `grub>`
rescue shell on hardware that worked fine with the original config.

Revert the EFI section to the v2.10.x layout that v2.10/v2.11 booted
cleanly on real amd64 boxes: 14 modules, single-line search, plain
grub-mkimage with no compress/no Secure Boot shim. Also drop the
shim-signed + grub-efi-amd64-signed from the host apt install — they
were only there to feed the SB swap that's now removed.

VBox EFI drops to the GRUB rescue shell with this config; that's
acceptable per operator direction ("je m'en fous de booter en legacy
sur vbox, ca marche sur real hw") — VBox testing has always used the
BIOS firmware path which still works perfectly.
2026-05-25 09:39:15 +02:00
gandalf 930a885f04 fix(kiosk): --no-block start so firstboot.sh's enable doesn't deadlock
Live USB v2.12.4 boot showed `secubox-kiosk-setup enable --x11` running
for 5+ minutes while the kiosk.service stayed `inactive (dead)`. The
synchronous `systemctl start secubox-kiosk.service` at the end of
enable_kiosk was hanging.

Root cause: secubox-kiosk.service declares
  Conflicts=getty@tty7.service
  TTYPath=/dev/tty7

systemd waits for tty7 to be released and for the conflicting unit's
JobsToStart to clear before kiosk's ExecStart can fire. When the call
comes from a process running inside the tty1 autologin shell (which
is what firstboot.sh's `secubox-kiosk-setup enable --x11` safety net
does), systemd deadlocks itself waiting on a tty operation the caller
can't unblock. The kiosk service appears to start fine when triggered
manually from SSH (proven during debug — once the stuck setup PID was
killed, `systemctl start secubox-kiosk.service` returned in <1s and
the service activated cleanly).

Fix: pass --no-block so systemctl returns as soon as the job is
queued, regardless of the tty wait. systemd then runs the start in
the background after the parent autologin shell exits, breaking the
loop. The exit-code branch is preserved for legitimate failures.
2026-05-25 08:59:28 +02:00
gandalf 24d6f28dab fix(live-usb): IP shown live in MOTD + modesetting fallback for VBox kiosk
Three regressions surfaced on the v2.12.3 live USB amd64 boot console:

1. /etc/issue + /etc/motd had `<IP>` as literal text — nothing was
   substituting at runtime, so operators saw `https://<IP>:9443`
   instead of the actual address. The bashrc splash showed real IP
   via `hostname -I` but was masked by the static MOTD on top.

   Fixes:
   * /etc/issue: switch to getty's `\4` escape (resolves to first
     IPv4 address at TTY render time).
   * /etc/motd: blank out the static file + ship the banner as
     /etc/update-motd.d/10-secubox, which pam_motd regenerates on
     every interactive login. The script substitutes the live
     `hostname -I` first address (falls back to `no-ip` while DHCP
     hasn't completed).

2. Kiosk Xorg failed under VirtualBox VMSVGA. The auto-detect picked
   the `vmware` Xorg driver, which loads vmwgfx kernel module — vmwgfx
   tries to talk to a VMware Workstation host channel that doesn't
   exist on VBox, prints "Failed to send host log message" then
   bails. The kiosk launcher then logs Xorg failure, retries 3 times,
   self-disables. Operator sees no kiosk even after running
   `secubox-kiosk-setup enable` (because the launcher disabled the
   sentinel again).

   Fix: secubox-x11-setup unconditionally picks `modesetting` on
   oracle (VirtualBox), regardless of VMSVGA vs VBoxVGA. modesetting
   uses DRM/KMS through vmwgfx but skips the broken host channel.
   Real VMware Workstation users (VM_TYPE=vmware) still get `vmware`
   via the dedicated case branch — only the VBox path changed.

Note: didn't tag yet — wait for user retest after CI rebuilds. If the
boot console comes up with the right IP + the kiosk paints pixels, we
tag v2.12.4 next.
2026-05-25 08:10:28 +02:00
gandalf faa5f5d84a fix(live-usb): drop netplan static-in-DHCP (regressed all IPs) + mass-mask LXC modules
VBox + bare-metal v2.12.2 amd64 tests showed the live USB had no IP at
all (DHCP nor static fallback worked) and ~10 [FAILED] secubox-*
services on the boot console.

Two independent fixes layered together:

1. Inline netplan: my v2.12.1 commit (27645f0a) baked
   `addresses: [192.168.1.55/24]` + gw 192.168.1.254 into both
   eth-dhcp (match en*) and eth-legacy (match eth*). On a single-NIC
   box both interface defs match the same kernel interface and try to
   claim 192.168.1.55 simultaneously → systemd-networkd refuses the
   apply and the link gets NO IP at all. Even DHCP was broken.

   Drop the static block entirely; let secubox-net-fallback.service
   (already shipped, runs after networkd) handle the no-DHCP case via
   ARP-probe gateway discovery + free .250 IP assignment.

2. INCOMPLETE_MODULES list: extended with the host-side control planes
   that proxy to an LXC container — without `install-lxc.sh` having
   run, the container at 10.100.0.X doesn't exist, the health probe
   fails immediately, and Restart=on-failure spins the service into a
   FAILED loop that fills the console and blocks multi-user.target.

   Added: grafana, yacy, rustdesk, lyrion, authelia (host), mail,
   gitea, matrix, horde, mitmproxy, nextcloud, rbs-sensor.

   Operator workflow stays unchanged on real installs: run
   `bash install-lxc.sh` to provision the container, then
   `systemctl unmask secubox-<name>` + enable + start.
2026-05-25 07:00:20 +02:00
gandalf da09fa6987 fix(live-usb): mask sentinelle-gsm + fmrelay; cap their restart storm
Both modules need physical hardware (RTL-SDR, EP06 modem) and crash
on `import gr_osmosdr` when those aren't there. On the live USB
amd64 build neither is present, so the service died at startup,
systemd restarted in 5s under `Restart=on-failure`, and the cycle
repeated until /var/log filled with hundreds of FAILED lines —
visible on the boot console and blocking the kiosk target from
ever finishing.

Two fixes layered:

1. systemd unit (sentinelle-gsm.service): add StartLimitIntervalSec=30
   + StartLimitBurst=3 + StartLimitAction=none. If start fails 3
   times in 30 s, give up instead of looping forever. The kiosk's
   multi-user.target settles even when the SDR is missing.

2. build-live-usb.sh: mask secubox-sentinelle-gsm + secubox-fmrelay
   in the INCOMPLETE_MODULES list alongside other hardware-gated
   modules. Operators enable + unmask by hand once they wire the
   physical RTL-SDR / GSM modem.

Operator quick fix on an already-flashed live USB:

  sudo systemctl stop secubox-sentinelle-gsm
  sudo systemctl mask secubox-sentinelle-gsm
  sudo systemctl mask secubox-fmrelay
  sudo systemctl daemon-reload

Then `secubox-kiosk-setup enable --x11` should complete normally.
2026-05-25 06:33:45 +02:00
3 changed files with 139 additions and 184 deletions
+121 -182
View File
@@ -141,8 +141,7 @@ fi
# Required tools
log "Checking dependencies..."
apt-get install -y -qq debootstrap squashfs-tools \
grub-efi-amd64-bin grub-efi-amd64-signed grub-pc-bin shim-signed \
apt-get install -y -qq debootstrap squashfs-tools grub-efi-amd64-bin grub-pc-bin \
xorriso mtools dosfstools parted e2fsprogs live-boot 2>/dev/null || true
for cmd in debootstrap parted mkfs.fat mkfs.ext4 mksquashfs grub-mkimage; do
@@ -781,103 +780,37 @@ else
# Uses two separate match patterns to avoid conflicts when multiple interfaces match
# secubox-net-detect.service will generate proper config at first boot
cat > "${ROOTFS}/etc/netplan/00-secubox.yaml" <<'NETPLAN'
# /etc/netplan/00-secubox.yaml — SecuBox Live USB (Bare Metal) — Bootstrap Config
# This is a minimal bootstrap configuration for real AMD64 hardware.
# secubox-net-detect.service will generate the proper config at first boot.
# /etc/netplan/00-secubox.yaml — SecuBox Live USB bootstrap.
#
# Strategy: Enable DHCP on ALL detected Ethernet interfaces initially.
# secubox-net-detect will refine this to router mode (WAN + br-lan).
# DHCP on every ethernet interface. That's it. Operators wire WiFi
# and router-mode br-lan later via secubox-net-* tools.
#
# Earlier versions baked an empty `br-lan` bridge with a static
# 192.168.1.1/24 address into the bootstrap. On bare-metal real
# hardware the physical NIC went silent and only the phantom br-lan
# showed an IP — networkd was honouring the static bridge but
# something (predictable rename? secubox-net-detect leftover?)
# stopped the DHCP request reaching the real cable. Stripping the
# bridge + the wifi-with-empty-SSID block restores classic DHCP.
# secubox-net-detect.service (disabled by default, no .wants/ link)
# can still be run by hand once the operator has decided whether
# this box is a router vs an endpoint.
network:
version: 2
renderer: networkd
ethernets:
# Bootstrap: Enable DHCP on all ethernet interfaces for initial connectivity
# This ensures we get an IP regardless of interface naming (eno1, enp2s0, etc.)
# After first boot, secubox-net-detect rewrites this with proper WAN/LAN split.
# Match modern interface patterns (enp*, eno*, ens*, enx*).
# WAN fallback (closes #370): static 192.168.1.55/24 + gw
# 192.168.1.254 carried alongside DHCP. systemd-networkd accepts
# both — DHCP routes get the low metric (100), the static gets
# metric 1000, so DHCP wins when available and the static keeps
# the appliance reachable when DHCP fails. Caveat: if multiple
# interfaces match, both will try to claim 192.168.1.55 and one
# will fail with address-in-use; bare-metal boxes typically have
# one primary NIC, and multi-NIC operators run net-detect to
# refine the layout anyway.
eth-dhcp:
# Match everything that *looks* like ethernet — covers enpXsY /
# enoX / ensX / enxAABBCC (USB ethernet) / ethX. All get DHCP.
eth-all:
match:
name: "en*"
name: "e*"
dhcp4: true
dhcp6: false
dhcp4-overrides:
use-dns: true
use-routes: true
route-metric: 100
optional: true
addresses: [192.168.1.55/24]
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
routes:
- to: default
via: 192.168.1.254
metric: 1000
# Match legacy interface patterns (eth0, eth1, etc.) — same static
# WAN fallback rationale.
eth-legacy:
match:
name: "eth*"
dhcp4: true
dhcp6: false
dhcp4-overrides:
use-dns: true
use-routes: true
route-metric: 200
optional: true
addresses: [192.168.1.55/24]
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
routes:
- to: default
via: 192.168.1.254
metric: 1000
wifis:
# All WiFi interfaces (wlp*, wlan*, wl*)
all-wifi:
match:
name: "wl*"
dhcp4: true
dhcp4-overrides:
route-metric: 300
use-dns: true
use-routes: true
optional: true
access-points:
# Open networks (fallback)
"": {}
bridges:
# br-lan: Pre-defined but empty - secubox-net-detect populates interfaces
br-lan:
interfaces: []
addresses:
- 192.168.1.1/24
dhcp4: false
optional: true
parameters:
stp: false
forward-delay: 0
# Note: At first boot, secubox-net-detect.service will:
# 1. Detect board type (x64-baremetal) and available interfaces
# 2. Determine WAN (first interface with link/DHCP response)
# 3. Assign remaining interfaces to br-lan
# 4. Rewrite this file with explicit interface names
#
# To force re-detection: rm /var/lib/secubox/.net-configured && reboot
NETPLAN
chmod 600 "${ROOTFS}/etc/netplan/00-secubox.yaml"
fi # end static-IP branch
@@ -2241,17 +2174,17 @@ DRIVER="modesetting" # Safe default
case "$VM_TYPE" in
oracle)
# VirtualBox - check graphics controller type
if echo "$GPU_INFO" | grep -qi "SVGA\|VMware"; then
# VMSVGA controller (default in VBox 6+) - use vmware driver
DRIVER="vmware"
log "VirtualBox VMSVGA detected → vmware driver"
else
# VBoxVGA or VBoxSVGA - modesetting works
DRIVER="modesetting"
log "VirtualBox VBoxVGA detected → modesetting driver"
fi
# Load VirtualBox kernel modules
# VirtualBox: use modesetting unconditionally. The previous
# "vmware" branch for VMSVGA controllers triggered vmwgfx kernel
# ERRORs ("Failed to send host log message") because vmwgfx
# expects a real VMware Workstation host — on VBox it half-loads
# then fails on the Xorg side, kiosk launcher gives up after 3
# retries and self-disables. modesetting talks DRM/KMS through
# the same vmwgfx kmod but ignores the broken host channel and
# actually paints pixels.
DRIVER="modesetting"
log "VirtualBox detected → modesetting driver (works for both VMSVGA + VBoxVGA)"
# Load VirtualBox kernel modules so Guest Additions integration works
modprobe vboxguest 2>/dev/null || true
modprobe vboxvideo 2>/dev/null || true
;;
@@ -2803,6 +2736,31 @@ INCOMPLETE_MODULES=(
secubox-newsbin
secubox-ui-manager
secubox-ui-health
# Hardware-gated modules — restart-storm on live USB without the
# physical RTL-SDR + GSM modem present. Operators install + enable
# them by hand once they wire the SDR / EP06.
secubox-sentinelle-gsm
secubox-fmrelay
# LXC-backed modules — their host control plane immediately tries to
# probe the container at 10.100.0.X over the br-lxc bridge. On live
# USB the LXC stack isn't initialised + the container doesn't exist,
# so the service fails the health probe and goes into restart-fail
# loop. Filled the boot console with [FAILED] lines and held up the
# multi-user target. Operators run install-lxc.sh once on a real
# install to provision the container, then `secubox-lxc-modules
# enable <name>` to lift the mask.
secubox-grafana
secubox-yacy
secubox-rustdesk
secubox-lyrion
secubox-authelia
secubox-mail
secubox-gitea
secubox-matrix
secubox-horde
secubox-mitmproxy
secubox-nextcloud
secubox-rbs-sensor
)
for svc in "${INCOMPLETE_MODULES[@]}"; do
chroot "${ROOTFS}" systemctl disable ${svc}.service 2>/dev/null || true
@@ -2994,14 +2952,25 @@ printf '%b' "\e[38;5;29m
\e[38;5;242m Build: ${BUILD_TIMESTAMP}\e[0m
\e[38;5;250m 🔐 Default: \e[38;5;214mroot\e[38;5;250m / \e[38;5;214msecubox\e[0m
\e[38;5;250m 🌐 Web UI: \e[38;5;45mhttps://<IP>:9443\e[0m
\e[38;5;250m 🌐 Web UI: \e[38;5;45mhttps://\\4:9443\e[0m
\e[38;5;250m 📡 SSH: \e[38;5;45mport 22\e[0m
\e[38;5;242m─────────────────────────────────────────────────────────────\e[0m
" > "${ROOTFS}/etc/issue"
# Post-login MOTD - use printf %b for real escape sequences
# Post-login MOTD via update-motd.d so the IP can be substituted live
# at login time (pam_motd runs /etc/update-motd.d/* on every interactive
# session). Static /etc/motd kept blank — without it, pam_motd printed
# the literal `<IP>:9443` because nothing was substituting the placeholder.
# Operators see the box's actual IPv4 (or `no-ip` when DHCP hasn't fired).
: > "${ROOTFS}/etc/motd"
mkdir -p "${ROOTFS}/etc/update-motd.d"
cat > "${ROOTFS}/etc/update-motd.d/10-secubox" <<MOTD_DYN
#!/bin/sh
# Generated by build-live-usb.sh — dynamic MOTD with live IP.
ip=\$(hostname -I 2>/dev/null | awk '{print \$1}')
[ -z "\$ip" ] && ip="no-ip"
printf '%b' "\e[38;5;214m
╔═══════════════════════════════════════════════════════════════╗
║\e[38;5;45m ███████╗███████╗ ██████╗██╗ ██╗██████╗ ██████╗ ██╗ ██╗ \e[38;5;214m║
@@ -3015,13 +2984,15 @@ printf '%b' "\e[38;5;214m
\e[38;5;242m Build: ${BUILD_TIMESTAMP}\e[0m
\e[38;5;250m 🌐 Web UI: \e[38;5;45mhttps://<IP>:9443\e[0m
\e[38;5;250m 🌐 Web UI: \e[38;5;45mhttps://\${ip}:9443\e[0m
\e[38;5;250m 🔐 Credentials: \e[38;5;214mroot\e[38;5;250m / \e[38;5;214msecubox\e[0m
\e[38;5;250m 📖 Docs: \e[38;5;45mhttps://secubox.in/docs\e[0m
\e[38;5;242m Type \e[38;5;82msecubox-status\e[38;5;242m for system overview\e[0m
" > "${ROOTFS}/etc/motd"
"
MOTD_DYN
chmod +x "${ROOTFS}/etc/update-motd.d/10-secubox"
# Dynamic status script for interactive use
cat > "${ROOTFS}/usr/bin/secubox-status" <<'STATUS_SCRIPT'
@@ -3550,6 +3521,17 @@ else
fi
fi
# Capture UUIDs — labels failed to resolve on at least one real UEFI
# box (v2.12.x grub> shell symptom). Bake UUIDs into the embed-cfg
# and grub.cfg so `search --fs-uuid` is used everywhere instead of
# the fragile `search --label`. blkid is read AFTER mkfs.* so the
# values are guaranteed fresh.
udevadm settle 2>/dev/null || sleep 1
ESP_UUID=$(blkid -s UUID -o value "${LOOP}p2" 2>/dev/null || echo "")
LIVE_UUID=$(blkid -s UUID -o value "${LOOP}p3" 2>/dev/null || echo "")
[[ -z "$ESP_UUID" || -z "$LIVE_UUID" ]] && err "Failed to read ESP/LIVE UUIDs from ${LOOP} after mkfs"
log "ESP UUID=${ESP_UUID} LIVE UUID=${LIVE_UUID}"
# Mount for file copy
MNT="${WORK_DIR}/mnt"
mkdir -p "${MNT}/esp" "${MNT}/live"
@@ -3600,7 +3582,15 @@ insmod all_video
insmod echo
insmod gfxterm
search --no-floppy --label LIVE --set=live
# Resolve LIVE partition by UUID — `search --label` failed silently on
# real UEFI hardware (v2.12.x grub> shell). UUIDs are baked at build
# time from blkid after mkfs.ext4, so this lookup is deterministic.
search --no-floppy --fs-uuid ${LIVE_UUID} --set=live
# Fallback: keep the label search as a second chance for firmware
# that prefers labels (no-op if already set above).
if [ -z "\$live" ]; then
search --no-floppy --label LIVE --set=live
fi
# CRT-style menu colors (cyan on black, gold highlights)
set menu_color_normal=cyan/black
@@ -3717,103 +3707,52 @@ fi
cp "${MNT}/esp/boot/grub/grub.cfg" "${MNT}/esp/EFI/BOOT/grub.cfg"
# ── Build GRUB EFI ────────────────────────────────────────────────────────
# Module set tuned for booting from a USB stick / VM disk under both
# OVMF (VirtualBox + qemu) and real UEFI firmware. The list MUST include
# `disk`, `usb`, `usbms` so the firmware can hand off block IO; without
# them OVMF drops to the EFI Shell PXE prompt instead of loading GRUB
# (this was the v2.12.0/v2.12.1 regression — see issue #382 followup).
GRUB_MODS="part_gpt part_msdos fat ext2 ntfs iso9660 normal linux boot \
configfile loopback chain efi_gop efi_uga ls cat echo test help \
search search_label search_fs_uuid search_fs_file \
gfxterm gfxterm_background all_video gzio png jpeg font \
fat exfat read sleep reboot halt true \
disk usb usbms ahci ata loadenv minicmd terminal"
# Build GRUB EFI — use $cmdpath, the variable EFI firmware sets to the
# directory it loaded BOOTX64.EFI from. /EFI/BOOT/grub.cfg sits right
# next to BOOTX64.EFI on the ESP (script copies it there above), so
# `configfile $cmdpath/grub.cfg` is a single-step lookup that doesn't
# depend on filesystem labels or partition search — both of which
# have failed on different real-hardware firmware over the v2.12.x
# series. The full grub.cfg itself still does `search --label LIVE
# --set=live` to find the squashfs partition, so that part is
# unchanged.
GRUB_MODS="part_gpt part_msdos fat ext2 normal linux boot configfile loopback chain efi_gop efi_uga ls search search_label gfxterm all_video"
cat > "${WORK_DIR}/grub-embed.cfg" <<'EMBEDCFG'
# Fallback chain — label first (set by mkfs.fat -n ESP), then UUID,
# then EFI's $cmdpath (the directory the .EFI was loaded from). Last
# resort: hardcode the relative prefix so configfile can still find the
# menu even if no var resolution worked.
search --no-floppy --label ESP --set=root --no-floppy
if [ -z "$root" ]; then
search --no-floppy --label LIVE --set=root --no-floppy
cat > "${WORK_DIR}/grub-embed.cfg" <<EMBEDCFG
# Try \$cmdpath first (set by firmware to dir of loaded .EFI). Falls
# back to UUID-based search if \$cmdpath isn't usable.
if [ -n "\$cmdpath" ]; then
configfile \$cmdpath/grub.cfg
fi
if [ -n "$root" ]; then
set prefix=($root)/boot/grub
else
set prefix=($cmdpath)/../../boot/grub
search --no-floppy --fs-uuid ${ESP_UUID} --set=root
if [ -n "\$root" ]; then
configfile (\$root)/EFI/BOOT/grub.cfg
fi
configfile $prefix/grub.cfg
echo "GRUB embed-cfg fallback failed: cmdpath=\$cmdpath root=\$root"
EMBEDCFG
# Build the EFI binary. Bail loudly if grub-mkimage fails — silent
# failure leaves the ESP without BOOTX64.EFI and the box drops to PXE
# at first boot, which is exactly the bug we just hit.
if ! grub-mkimage -o "${MNT}/esp/EFI/BOOT/BOOTX64.EFI" \
-O x86_64-efi \
-c "${WORK_DIR}/grub-embed.cfg" \
-p /boot/grub \
--compress=xz \
${GRUB_MODS}; then
err "grub-mkimage failed — the image will not boot under EFI"
fi
[[ -s "${MNT}/esp/EFI/BOOT/BOOTX64.EFI" ]] || err "BOOTX64.EFI missing or empty after grub-mkimage"
EFI_SIZE=$(stat -c%s "${MNT}/esp/EFI/BOOT/BOOTX64.EFI")
ok "BOOTX64.EFI built (${EFI_SIZE} bytes)"
grub-mkimage -o "${MNT}/esp/EFI/BOOT/BOOTX64.EFI" \
-O x86_64-efi \
-c "${WORK_DIR}/grub-embed.cfg" \
-p /boot/grub \
${GRUB_MODS}
# Mirror to /EFI/BOOT/grubx64.efi (some firmware looks for this name)
# and to /EFI/secubox/grubx64.efi (lets `efibootmgr` register a named
# entry once the live system is running, without clobbering /BOOT).
cp "${MNT}/esp/EFI/BOOT/BOOTX64.EFI" "${MNT}/esp/EFI/BOOT/grubx64.efi"
mkdir -p "${MNT}/esp/EFI/secubox"
cp "${MNT}/esp/EFI/BOOT/BOOTX64.EFI" "${MNT}/esp/EFI/secubox/grubx64.efi"
# Secure Boot: ship the Debian-signed shim + grub if they're available
# on the builder. shim is what Microsoft trusts; it then verifies the
# signed grubx64.efi we copy alongside. Firmware looks for
# /EFI/BOOT/BOOTX64.EFI first, so when shim is present, point that at
# shim and let it chainload grub.
SHIM_SRC="/usr/lib/shim/shimx64.efi.signed"
GRUB_SIGNED_SRC="/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed"
if [[ -f "$SHIM_SRC" && -f "$GRUB_SIGNED_SRC" ]]; then
log "Secure Boot assets present — installing shim as BOOTX64.EFI"
cp "$SHIM_SRC" "${MNT}/esp/EFI/BOOT/BOOTX64.EFI"
cp "$GRUB_SIGNED_SRC" "${MNT}/esp/EFI/BOOT/grubx64.efi"
cp "$GRUB_SIGNED_SRC" "${MNT}/esp/EFI/secubox/grubx64.efi"
ok "Secure Boot shim wired (shimx64 -> grubx64.efi.signed)"
else
warn "Secure Boot shim not found — image won't boot with SB on"
warn " missing: $SHIM_SRC OR $GRUB_SIGNED_SRC"
warn " (install: apt-get install shim-signed grub-efi-amd64-signed)"
fi
# EFI shell fallback — some OVMF builds drop into the shell on first
# boot if no BootOrder is set. startup.nsh auto-runs the bootloader.
# Add startup.nsh for EFI shell auto-boot (VirtualBox/OVMF compatibility)
cat > "${MNT}/esp/startup.nsh" <<'STARTUPNSH'
@echo -off
echo "SecuBox Live — handing off to BOOTX64.EFI..."
\EFI\BOOT\BOOTX64.EFI
STARTUPNSH
# Copy GRUB modules — only the bits BOOTX64.EFI's embedded prefix
# might want to load on demand. Failure is non-fatal: we statically
# linked everything we need above, this is belt-and-suspenders.
# Copy GRUB modules
cp /usr/lib/grub/x86_64-efi/*.mod "${MNT}/esp/boot/grub/x86_64-efi/" 2>/dev/null || true
# ── BIOS GRUB ──────────────────────────────────────────────────────────────
# Fail loudly here too — BIOS-only systems (older laptops, VBox legacy
# default) silently won't boot if i386-pc grub isn't installed in the
# BIOS-boot partition (p1, set bios_grub on).
if ! grub-install --target=i386-pc \
--boot-directory="${MNT}/esp/boot" \
--recheck "${LOOP}" 2>&1 | tee "${WORK_DIR}/grub-install-bios.log"; then
cat "${WORK_DIR}/grub-install-bios.log"
err "BIOS grub-install failed — image will not boot on legacy systems"
fi
# Install BIOS GRUB
grub-install --target=i386-pc --boot-directory="${MNT}/esp/boot" --recheck "${LOOP}" 2>/dev/null || warn "BIOS GRUB failed"
cp /usr/lib/grub/i386-pc/*.mod "${MNT}/esp/boot/grub/i386-pc/" 2>/dev/null || true
ok "GRUB installed (UEFI BOOTX64.EFI + BIOS i386-pc, dual-boot ready)"
ok "GRUB installed (UEFI + BIOS)"
# Persistence
if [[ $INCLUDE_PERSISTENCE -eq 1 ]] && [[ -b "${LOOP}p4" ]]; then
+9 -2
View File
@@ -377,8 +377,15 @@ enable_kiosk() {
# Enable kiosk service
systemctl enable secubox-kiosk.service
# Start kiosk
systemctl start secubox-kiosk.service || log "Kiosk will start on next boot"
# Start kiosk in the background so this caller doesn't block. The
# service has Conflicts=getty@tty7.service + TTYPath=/dev/tty7 and
# systemd will sometimes wait for /dev/tty7 to release / sync up,
# which deadlocks a synchronous `systemctl start` invoked from
# firstboot.sh (firstboot runs from inside the autologin shell on
# tty1 itself — observed live USB hang where kiosk-setup ran for
# 5+ minutes and the service stayed `inactive (dead)`).
systemctl --no-block start secubox-kiosk.service \
|| log "Kiosk will start on next boot"
ok "Kiosk mode enabled (${mode})"
log "SecuBox WebUI will display on the connected screen"
@@ -3,6 +3,15 @@ Description=SecuBox SENTINELLE-GSM — passive rogue-BTS analyzer (MIND layer)
Documentation=file:///usr/share/doc/secubox-sentinelle-gsm/README
After=network.target secubox-core.service
Wants=secubox-core.service
# Cap the restart storm: if start fails 3 times in 30s, give up
# instead of looping forever. Observed live-USB regression — when the
# RTL-SDR or its userspace stack is missing, Restart=on-failure +
# RestartSec=5 spammed [FAILED] hundreds of times and blocked the
# kiosk target from settling. StartLimitAction=none means we just
# stop trying, not reboot the system.
StartLimitIntervalSec=30
StartLimitBurst=3
StartLimitAction=none
[Service]
UMask=0007