Skip to content

Instantly share code, notes, and snippets.

@anaxonda
Last active March 2, 2026 00:59
Show Gist options
  • Select an option

  • Save anaxonda/81d58573995ec57eab2afd632c600c1e to your computer and use it in GitHub Desktop.

Select an option

Save anaxonda/81d58573995ec57eab2afd632c600c1e to your computer and use it in GitHub Desktop.
Public redacted runbook: Debian mkinitcpio + ZFSBootMenu + Dropbear + Tailscale

ZFSBootMenu Runbook (Public Redacted)

Debian 13 (trixie) + ZFSBootMenu container build + mkinitcpio + Dropbear + Tailscale.

Last validated against live host <HOST_OS> on 2026-03-02.

1. Goal

This document is the reproducible build/operate runbook for the current <HOST_OS> setup:

  1. Build ZFSBootMenu on Debian via the upstream OCI builder (zbm-builder.sh + podman).
  2. Use mkinitcpio mode.
  3. Enable pre-boot remote shell with dropbear.
  4. Enable pre-boot tailnet access with mkinitcpio-tailscale.
  5. Keep upgrades low risk with staged EFI image + one-time test boot.

2. Upstream References

Primary docs used:

  1. https://docs.zfsbootmenu.org/en/v3.1.x/general/mkinitcpio.html
  2. https://docs.zfsbootmenu.org/en/v3.1.x/general/remote-access.html
  3. https://docs.zfsbootmenu.org/en/v3.1.x/general/tailscale.html
  4. https://docs.zfsbootmenu.org/en/v3.1.x/general/container-building.html
  5. https://docs.zfsbootmenu.org/en/v3.1.x/general/container-building/example.html

3. Design Decisions

  1. Containerized build path on Debian. Reason: cleanly avoids distro packaging mismatches; upstream supports this path directly.

  2. mkinitcpio hook snippets in /etc/zfsbootmenu/mkinitcpio.conf.d/*.conf. Reason: cleaner than mutating the base mkinitcpio config and works with the builder container include logic.

  3. rclocal networking hook instead of mkinitcpio net hook. Reason: Debian host path here did not have a practical mkinitcpio-nfs-utils net module flow for this build; rclocal + dhclient is deterministic.

  4. Dropbear listens on TCP 2222. Reason: avoids accidental conflict with normal host SSH assumptions and keeps pre-boot access explicit.

  5. ZBM timeout set to 120 seconds. Reason: gives a full two-minute window for remote attach during test boots.

  6. Upgrade workflow uses staging EFI and BootNext. Reason: avoids changing primary ZBM entry until test boot is validated.

4. Current Live State Snapshot (<HOST_OS>)

4.1 OS and key packages

PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
VERSION_ID="13"
DEBIAN_VERSION_FULL=13.3
ca-certificates 20250419
curl 8.14.1-2+deb13u2
efibootmgr 18-2
podman 5.4.2+ds1-2+b2
tailscale 1.94.2
dropbear-bin 2025.89-1~deb13u1

Builder image currently present:

ghcr.io/zbm-dev/zbm-builder:latest  (IMAGE ID 03d1950aa9bf)

4.2 Active config files

/etc/zfsbootmenu/config.yaml
/etc/zfsbootmenu/mkinitcpio.conf
/etc/zfsbootmenu/mkinitcpio.conf.d/10-network.conf
/etc/zfsbootmenu/mkinitcpio.conf.d/20-dropbear.conf
/etc/zfsbootmenu/mkinitcpio.conf.d/30-tailscale.conf
/etc/zfsbootmenu/initcpio/hooks/rclocal
/etc/zfsbootmenu/initcpio/install/rclocal
/etc/zfsbootmenu/initcpio/hooks/dropbear
/etc/zfsbootmenu/initcpio/install/dropbear
/etc/zfsbootmenu/initcpio/rc.local
/etc/zfsbootmenu/rc.d/dropbear
/etc/zfsbootmenu/rc.d/tailscale
/etc/zfsbootmenu/dropbear/dropbear.conf
/etc/zfsbootmenu/dropbear/root_key
/etc/zfsbootmenu/tailscale/tailscaled.state
/etc/zfsbootmenu/zbm-builder.sh
/etc/zfsbootmenu/zbm-builder.conf
/usr/local/sbin/zbm-upgrade-staged.sh

4.3 ZBM kernel command line (test/staging EFI)

zfsbootmenu ro quiet loglevel=0 nomodeset zbm.timeout=120

4.4 EFI paths and entries

EFI bundles:

/boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI
/boot/efi/EFI/zfsbootmenu-staging/zfsbootmenu.EFI
/boot/efi/EFI/ZBM/VMLINUZ.EFI

Current EFI entries observed:

Boot0001* ZFSBootMenu (Tailscale test)     \EFI\zfsbootmenu-ts\zfsbootmenu.EFI
Boot0002* ZFSBootMenu (Staging)            \EFI\zfsbootmenu-staging\zfsbootmenu.EFI
Boot0004* ZFSBootMenu                      \EFI\ZBM\VMLINUZ.EFI
Boot0003* ZFSBootMenu (Backup)             \EFI\ZBM\VMLINUZ-BACKUP.EFI

5. Reproduce the Setup From Scratch

All commands in this section are intended to run on <HOST_OS>.

5.1 Install host prerequisites

sudo apt update
sudo apt install -y podman efibootmgr curl ca-certificates tailscale dropbear-bin

Verify:

command -v podman efibootmgr curl tailscale dropbearkey

5.2 Base ZFSBootMenu build root

sudo mkdir -p /etc/zfsbootmenu

Create /etc/zfsbootmenu/config.yaml:

Global:
  ManageImages: true
  InitCPIO: true
  InitCPIOHookDirs:
    - /build/initcpio
    - /usr/lib/initcpio
Components:
  Enabled: false
EFI:
  Enabled: true
  Versions: false
Kernel:
  Prefix: zfsbootmenu
  CommandLine: zfsbootmenu ro quiet loglevel=0 nomodeset zbm.timeout=120

Install builder helper:

sudo curl -fsSL -o /etc/zfsbootmenu/zbm-builder.sh \
  https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/v3.1.0/zbm-builder.sh
sudo chmod 755 /etc/zfsbootmenu/zbm-builder.sh

Install container-specialized mkinitcpio config:

sudo curl -fsSL -o /etc/zfsbootmenu/mkinitcpio.conf \
  https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/v3.1.0/etc/zbm-builder/mkinitcpio.conf

Set builder package additions in /etc/zfsbootmenu/zbm-builder.conf:

BUILD_ARGS+=( -p dropbear -p psmisc )
BUILD_ARGS+=( -p tailscale -p mkinitcpio-tailscale )

5.3 Install custom mkinitcpio hooks (rclocal + dropbear)

sudo bash -eu <<'EOF'
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

mkdir -p /etc/zfsbootmenu/initcpio/{hooks,install}

curl -fsSL https://github.com/ahesford/mkinitcpio-rclocal/archive/master.tar.gz \
  | tar -zxf - -C "$tmp"
install -m 0644 "$tmp/mkinitcpio-rclocal-master/rclocal_hook" \
  /etc/zfsbootmenu/initcpio/hooks/rclocal
install -m 0644 "$tmp/mkinitcpio-rclocal-master/rclocal_install" \
  /etc/zfsbootmenu/initcpio/install/rclocal

curl -fsSL https://github.com/ahesford/mkinitcpio-dropbear/archive/master.tar.gz \
  | tar -zxf - -C "$tmp"
install -m 0644 "$tmp/mkinitcpio-dropbear-master/dropbear_hook" \
  /etc/zfsbootmenu/initcpio/hooks/dropbear
install -m 0644 "$tmp/mkinitcpio-dropbear-master/dropbear_install" \
  /etc/zfsbootmenu/initcpio/install/dropbear
EOF

5.4 Initramfs network bootstrap script (rclocal)

Create /etc/zfsbootmenu/initcpio/rc.local:

#!/bin/sh
set -eu

iface=""
for cand in enp1s0 eth0; do
  if ip link show dev "$cand" >/dev/null 2>&1; then
    iface="$cand"
    break
  fi
done
if [ -z "$iface" ]; then
  iface="$(ip -o link show | awk -F': ' '$2 != "lo" {print $2; exit}')"
fi
[ -n "$iface" ] || exit 0

ip link set dev "$iface" up
dhclient "$iface"
sudo chmod 0755 /etc/zfsbootmenu/initcpio/rc.local

5.5 mkinitcpio snippets

sudo mkdir -p /etc/zfsbootmenu/mkinitcpio.conf.d

/etc/zfsbootmenu/mkinitcpio.conf.d/10-network.conf:

BINARIES+=(ip dhclient dhclient-script)
HOOKS+=(rclocal)
rclocal_hook="/build/initcpio/rc.local"

/etc/zfsbootmenu/mkinitcpio.conf.d/20-dropbear.conf:

HOOKS+=(dropbear)

/etc/zfsbootmenu/mkinitcpio.conf.d/30-tailscale.conf:

HOOKS+=(tailscale)

5.6 Dropbear key material and config

Create dedicated dropbear host keys:

sudo mkdir -p /etc/zfsbootmenu/dropbear
for keytype in rsa ecdsa ed25519; do
  sudo dropbearkey -t "$keytype" \
    -f "/etc/zfsbootmenu/dropbear/dropbear_${keytype}_host_key"
done

Set authorized key for pre-boot root login (/etc/dropbear/root_key inside initramfs):

sudo install -m 600 ~/.ssh/id_ed25519.pub /etc/zfsbootmenu/dropbear/root_key

Set dropbear listen port:

echo 'dropbear_listen=2222' | sudo tee /etc/zfsbootmenu/dropbear/dropbear.conf >/dev/null

5.7 rc.d copy hooks for build container

sudo mkdir -p /etc/zfsbootmenu/rc.d

Create /etc/zfsbootmenu/rc.d/dropbear:

#!/bin/sh
[ -d /build/dropbear ] || exit 0
mkdir -p /etc/dropbear
cp -R /build/dropbear/* /etc/dropbear/

Create /etc/zfsbootmenu/rc.d/tailscale:

#!/bin/sh
[ -d /build/tailscale ] || exit 0
mkdir -p /etc/tailscale
cp -R /build/tailscale/* /etc/tailscale/
sudo chmod 0755 /etc/zfsbootmenu/rc.d/dropbear /etc/zfsbootmenu/rc.d/tailscale

5.8 Generate initramfs tailscale state

Do not print the auth key. Keep it in a file and pass by path only.

Place the fresh auth key file at a local path, example:

/etc/zfsbootmenu/tailscale/zbm-ts-authkey

Permissions:

sudo mkdir -p /etc/zfsbootmenu/tailscale
sudo chown root:root /etc/zfsbootmenu/tailscale/zbm-ts-authkey
sudo chmod 600 /etc/zfsbootmenu/tailscale/zbm-ts-authkey

Generate state with the builder image environment:

sudo podman run --rm \
  --entrypoint /bin/sh \
  -v /etc/zfsbootmenu/tailscale:/var/lib/tailscale \
  -v /etc/zfsbootmenu/tailscale/zbm-ts-authkey:/run/zbm-ts-authkey:ro \
  ghcr.io/zbm-dev/zbm-builder:latest \
  -lc '
    set -eu
    xbps-install -Suy xbps >/dev/null
    xbps-install -Sy tailscale mkinitcpio-tailscale >/dev/null
    mkinitcpio-tailscale-setup -f \
      -H <INITRAMFS_NODE_NAME> \
      -k /run/zbm-ts-authkey \
      -t "--advertise-tags=tag:zfsbootmenu"
  '

Validate expected output files:

sudo ls -l /etc/zfsbootmenu/tailscale
sudo test -s /etc/zfsbootmenu/tailscale/tailscaled.state

Optional cleanup:

sudo rm -f /etc/zfsbootmenu/tailscale/zbm-ts-authkey

5.9 Build test EFI image

sudo mkdir -p /boot/efi/EFI/zfsbootmenu-ts
cd /etc/zfsbootmenu
sudo ./zbm-builder.sh \
  -O "--volume=/boot/efi/EFI/zfsbootmenu-ts:/output" \
  -- -o /output \
  -e '.Global.InitCPIOFlags=["-v"]' \
  -- -d

Validate image:

sudo file /boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI
sudo zbm-kcl /boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI

Create EFI boot entry (idempotent pattern):

ESP_DEV="$(findmnt -no SOURCE /boot/efi)"
PKNAME="$(lsblk -no PKNAME "$ESP_DEV")"
PARTN="$(lsblk -no PARTN "$ESP_DEV")"

sudo efibootmgr -c \
  -d "/dev/$PKNAME" \
  -p "$PARTN" \
  -L "ZFSBootMenu (Tailscale test)" \
  -l '\EFI\zfsbootmenu-ts\zfsbootmenu.EFI'

6. Boot/Test Operations

6.1 One-time test boot

Find entry number:

sudo efibootmgr -v

Set BootNext to test entry (example 0001):

sudo efibootmgr -n 0001
sudo reboot

6.2 Connect to initramfs dropbear

LAN path:

ssh -p 2222 root@<HOST_LAN_IP>

Tailnet path (if MagicDNS is reachable):

ssh -p 2222 root@<INITRAMFS_TAILNODE_FQDN>

Tailnet fallback (no direct MagicDNS routing from client shell):

ssh -p 2222 \
  -o ProxyCommand='tailscale nc %h %p' \
  root@<INITRAMFS_TAILNODE_FQDN>

Observed peer metadata on 2026-03-02:

HostName: <INITRAMFS_NODE_NAME>
DNSName:  <INITRAMFS_TAILNODE_FQDN>.
Tag:      tag:zfsbootmenu

Note: DNS suffixes like -1 can change when node keys/state are regenerated.

6.3 Open ZFSBootMenu UI from remote shell

zfsbootmenu

7. Timeout Changes Without Rebuild

Check current KCL:

sudo zbm-kcl /boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI

Change timeout (example 30 -> 120) in-place:

sudo zbm-kcl \
  -r zbm.timeout=30 \
  -a zbm.timeout=120 \
  /boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI

Repeat for staging image if needed:

sudo zbm-kcl \
  -r zbm.timeout=30 \
  -a zbm.timeout=120 \
  /boot/efi/EFI/zfsbootmenu-staging/zfsbootmenu.EFI

Verify:

sudo zbm-kcl /boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI
sudo zbm-kcl /boot/efi/EFI/zfsbootmenu-staging/zfsbootmenu.EFI

8. Staged Upgrade Process

Installed utility:

/usr/local/sbin/zbm-upgrade-staged.sh

8.1 What the script does

  1. Builds new ZBM EFI into /boot/efi/EFI/zfsbootmenu-staging.
  2. Verifies EFI size and embedded KCL.
  3. Verifies build log evidence for rclocal, dropbear, and tailscale hooks.
  4. Ensures EFI boot entry ZFSBootMenu (Staging) exists.
  5. Optionally sets BootNext to staging entry (--arm-next).

8.2 Usage

Pin a specific upstream tag and arm next boot:

sudo /usr/local/sbin/zbm-upgrade-staged.sh -t v3.1.0 --arm-next

Build with builder-image default tag, do not arm boot:

sudo /usr/local/sbin/zbm-upgrade-staged.sh

8.3 Release/tag selection behavior

Important detail from builder internals:

  1. zbm-builder.sh defaults to image ghcr.io/zbm-dev/zbm-builder:latest.
  2. Inside that image, /build-init.sh uses /etc/zbm-commit-hash if present.
  3. On current image this file contains v3.1.0.
  4. Build source fetch URL is https://github.com/zbm-dev/zfsbootmenu/archive/${ZBMTAG}.tar.gz.

Practical implication:

  1. Without -t, you build whatever tag the builder image currently pins.
  2. With -t vX.Y.Z, you explicitly fetch that tag from GitHub.

Recommended upgrade cadence:

sudo podman pull ghcr.io/zbm-dev/zbm-builder:latest
sudo /usr/local/sbin/zbm-upgrade-staged.sh -t vX.Y.Z --arm-next
sudo reboot

After successful staged boot validation, promote by replacing primary EFI path/entry according to your site policy.

9. Validation Checklist

Before rebooting into a new image:

  1. sudo test -s /etc/zfsbootmenu/dropbear/root_key
  2. sudo test -s /etc/zfsbootmenu/tailscale/tailscaled.state
  3. sudo zbm-kcl /boot/efi/EFI/zfsbootmenu-ts/zfsbootmenu.EFI | grep -q "zbm.timeout=120"
  4. sudo efibootmgr -v | grep -E "zfsbootmenu-ts|zfsbootmenu-staging"

During initramfs test boot:

  1. LAN SSH works on :2222.
  2. Tailnet peer appears with tag:zfsbootmenu.
  3. zfsbootmenu command opens UI.

After reboot back to normal OS:

  1. efibootmgr no longer shows BootNext set.
  2. Host comes back as normal <HOST_OS>.

10. Troubleshooting

  1. No network in initramfs. Check rc.local interface detection and ensure dhclient binaries were included in 10-network.conf.

  2. Dropbear auth fails. Confirm /etc/zfsbootmenu/dropbear/root_key exists, mode 600, and contains the intended public key.

  3. Tailnet node never appears. Regenerate state with fresh auth key and verify ACL permits tag:zfsbootmenu.

  4. Builder succeeds but remote hooks missing. Run staged script and inspect its build log (/var/log/zbm-upgrade-staged-*.log) for hook evidence checks.

  5. Cannot resolve tailnet hostname from client. Use ProxyCommand='tailscale nc %h %p' or connect via LAN fallback.

11. Operational Notes

  1. Do not store reusable auth keys in shell history.
  2. Keep /etc/zfsbootmenu backed up before major edits.
  3. Test every upgrade with one-time BootNext before changing persistent boot order.
  4. Keep a known-good fallback entry (/EFI/ZBM/VMLINUZ-BACKUP.EFI) in boot order.

12. Appendix: zbm-upgrade-staged.sh

Current script as deployed on <HOST_OS> at /usr/local/sbin/zbm-upgrade-staged.sh:

#!/usr/bin/env bash
set -euo pipefail

CONFIG_DIR="/etc/zfsbootmenu"
STAGE_DIR="/boot/efi/EFI/zfsbootmenu-staging"
ENTRY_LABEL="ZFSBootMenu (Staging)"
TAG=""
ARM_NEXT=0

usage() {
  cat <<'EOF'
Usage: sudo zbm-upgrade-staged.sh [options]

Build and verify a staged ZFSBootMenu EFI image, ensure a staging EFI entry
exists, and optionally arm one-time BootNext for test boot.

Options:
  -t, --tag <zbm-tag>      Build specific ZFSBootMenu tag (e.g. v3.1.0)
  -a, --arm-next           Set BootNext to staging EFI entry
  -s, --stage-dir <dir>    Staging EFI directory (default: /boot/efi/EFI/zfsbootmenu-staging)
  -c, --config-dir <dir>   ZBM config/build dir (default: /etc/zfsbootmenu)
  -l, --label <label>      EFI boot entry label (default: ZFSBootMenu (Staging))
  -h, --help               Show this help

Examples:
  sudo zbm-upgrade-staged.sh -t v3.1.0
  sudo zbm-upgrade-staged.sh --tag v3.1.0 --arm-next
EOF
}

log() { printf '[INFO] %s\n' "$*"; }
pass() { printf '[PASS] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*"; }
fail() { printf '[FAIL] %s\n' "$*" >&2; exit 1; }

need_cmd() { command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"; }

while [ "$#" -gt 0 ]; do
  case "$1" in
    -t|--tag)
      [ "$#" -ge 2 ] || fail "missing value for $1"
      TAG="$2"
      shift 2
      ;;
    -a|--arm-next)
      ARM_NEXT=1
      shift
      ;;
    -s|--stage-dir)
      [ "$#" -ge 2 ] || fail "missing value for $1"
      STAGE_DIR="$2"
      shift 2
      ;;
    -c|--config-dir)
      [ "$#" -ge 2 ] || fail "missing value for $1"
      CONFIG_DIR="$2"
      shift 2
      ;;
    -l|--label)
      [ "$#" -ge 2 ] || fail "missing value for $1"
      ENTRY_LABEL="$2"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "unknown option: $1"
      ;;
  esac
done

[ "${EUID}" -eq 0 ] || fail "run as root (sudo)"

need_cmd efibootmgr
need_cmd findmnt
need_cmd lsblk
need_cmd file
need_cmd zbm-kcl

[ -x "${CONFIG_DIR}/zbm-builder.sh" ] || fail "missing ${CONFIG_DIR}/zbm-builder.sh"
[ -f "${CONFIG_DIR}/config.yaml" ] || fail "missing ${CONFIG_DIR}/config.yaml"

HAVE_DROPBEAR=0
HAVE_TAILSCALE=0

if [ -f "${CONFIG_DIR}/dropbear/root_key" ]; then
  pass "dropbear root_key present"
  HAVE_DROPBEAR=1
else
  warn "dropbear root_key missing (${CONFIG_DIR}/dropbear/root_key)"
fi

if [ -s "${CONFIG_DIR}/tailscale/tailscaled.state" ]; then
  pass "tailscale state present"
  HAVE_TAILSCALE=1
else
  warn "tailscale state missing (${CONFIG_DIR}/tailscale/tailscaled.state)"
fi

mkdir -p "${STAGE_DIR}"

LOG_FILE="/var/log/zbm-upgrade-staged-$(date +%F-%H%M%S).log"
log "build log: ${LOG_FILE}"

pushd "${CONFIG_DIR}" >/dev/null
if [ -n "${TAG}" ]; then
  log "building staged EFI from tag ${TAG}"
  if ! ./zbm-builder.sh -O "--volume=${STAGE_DIR}:/output" -- -t "${TAG}" -o /output -e '.Global.InitCPIOFlags=["-v"]' -- -d >"${LOG_FILE}" 2>&1; then
    tail -n 120 "${LOG_FILE}" >&2 || true
    fail "zbm-builder failed"
  fi
else
  log "building staged EFI from default tag"
  if ! ./zbm-builder.sh -O "--volume=${STAGE_DIR}:/output" -- -o /output -e '.Global.InitCPIOFlags=["-v"]' -- -d >"${LOG_FILE}" 2>&1; then
    tail -n 120 "${LOG_FILE}" >&2 || true
    fail "zbm-builder failed"
  fi
fi
popd >/dev/null

STAGE_EFI="${STAGE_DIR}/zfsbootmenu.EFI"
[ -s "${STAGE_EFI}" ] || fail "missing staged EFI: ${STAGE_EFI}"

SIZE_BYTES="$(stat -c '%s' "${STAGE_EFI}")"
if [ "${SIZE_BYTES}" -lt $((20 * 1024 * 1024)) ]; then
  fail "staged EFI too small (${SIZE_BYTES} bytes): ${STAGE_EFI}"
fi
pass "staged EFI exists (${SIZE_BYTES} bytes)"

file "${STAGE_EFI}" | sed 's/^/[INFO] /'
CMDLINE="$(zbm-kcl "${STAGE_EFI}")"
printf '[INFO] staged cmdline: %s\n' "${CMDLINE}"

# Evidence from verbose mkinitcpio + generate-zbm debug log
if grep -q '/build/initcpio/install/rclocal' "${LOG_FILE}"; then
  pass "log evidence: rclocal hook executed"
else
  warn "log evidence missing: rclocal hook"
fi

if [ "${HAVE_DROPBEAR}" -eq 1 ]; then
  grep -q '/build/initcpio/install/dropbear' "${LOG_FILE}" || fail "dropbear hook not executed"
  grep -q '/etc/dropbear/root_key' "${LOG_FILE}" || fail "dropbear root_key not embedded"
  pass "log evidence: dropbear hook + root_key embedded"
fi

if [ "${HAVE_TAILSCALE}" -eq 1 ]; then
  grep -q '/usr/lib/initcpio/install/tailscale' "${LOG_FILE}" || fail "tailscale hook not executed"
  grep -q '/etc/tailscale/tailscaled.state' "${LOG_FILE}" || fail "tailscaled.state not embedded"
  pass "log evidence: tailscale hook + state embedded"
fi

REL_PATH="${STAGE_DIR#/boot/efi/}"
EFI_PATH="\\${REL_PATH//\//\\}\\zfsbootmenu.EFI"
PATTERN="$(printf '%s' "${EFI_PATH}" | tr '[:upper:]' '[:lower:]')"

find_bootnum() {
  efibootmgr -v | awk -v pat="${PATTERN}" '
    {
      line=tolower($0)
      if (index(line, pat) > 0) {
        b=$1
        sub(/^Boot/, "", b)
        sub(/\*.*/, "", b)
        print b
        exit
      }
    }
  '
}

BOOTNUM="$(find_bootnum || true)"
if [ -z "${BOOTNUM}" ]; then
  ESP_DEV="$(findmnt -no SOURCE /boot/efi)"
  PKNAME="$(lsblk -no PKNAME "${ESP_DEV}")"
  PARTN="$(lsblk -no PARTN "${ESP_DEV}")"
  [ -n "${PKNAME}" ] || fail "unable to determine ESP parent disk"
  [ -n "${PARTN}" ] || fail "unable to determine ESP partition number"

  log "creating staging EFI entry: ${ENTRY_LABEL} -> ${EFI_PATH}"
  efibootmgr -c -d "/dev/${PKNAME}" -p "${PARTN}" -L "${ENTRY_LABEL}" -l "${EFI_PATH}" >/dev/null
  BOOTNUM="$(find_bootnum || true)"
  [ -n "${BOOTNUM}" ] || fail "failed to locate staging EFI entry after creation"
  pass "created staging EFI entry Boot${BOOTNUM}"
else
  pass "staging EFI entry already exists as Boot${BOOTNUM}"
fi

if [ "${ARM_NEXT}" -eq 1 ]; then
  efibootmgr -n "${BOOTNUM}" >/dev/null
  NEXT="$(efibootmgr | awk -F': ' '/^BootNext:/ {print $2}')"
  [ "${NEXT}" = "${BOOTNUM}" ] || fail "failed to arm BootNext=Boot${BOOTNUM}"
  pass "armed one-time BootNext=Boot${BOOTNUM}"
else
  log "BootNext not changed (use --arm-next to arm Boot${BOOTNUM})"
fi

printf '[INFO] staging EFI: %s\n' "${STAGE_EFI}"
printf '[INFO] staging entry: Boot%s (%s)\n' "${BOOTNUM}" "${ENTRY_LABEL}"
printf '[INFO] build log: %s\n' "${LOG_FILE}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment