~nabijaczleweli/klapki

klapki/man/klapki.md -rw-r--r-- 12.6 KiB
7126537fнаб . 6 months ago

#klapki(8) -- EFI boot manager; or, well, an EFI bootorder compiler.

#SYNOPSIS

klapki [-nvVEh]… [op [arg…]]…

#DESCRIPTION

klapki(8) generates and manages EFI boot entries on platforms compatible therewith.

This command-line interface is based on running a set of operations (see [OPS][]) which modify the state and context, then settling the new set-up, then committing it; this means that, barring I/O errors, {dump}ing after the last operation with -n is an accurate representation of what would be committed without it.

Care is taken to only write what is needed and only when it's needed – files and wanted entries are hashed with SHA1(3ssl) and only updated on mismatch; foreign entries are never touched, and klapki(8) prefers to abandon entries it doesn't understand than to accidentally mangle them.

Minimal state is stored, and it's only supplementary (see [UNINSTALLATION][]). This means that removing all instances of a kernel boot entry with tools such as efibootmgr(8), the EFI Shell, or the platform UI will make klapki(8) forget about the kernel entirely, after minor complaints.

klapki(8)'s entries can be moved across BootNNNN entries, however, so long as they are kept identical.

The entry description and kernel cmdline are controlled via small executable files, see [WISDOM][].

#OPTIONS

  • -n: Don't commit – nothing will be written to the filesystem or the firmware.

  • -v: Verbose operation.

  • -V: Very verbose – adds a {dump} op in-between each specified op.

  • -E: Increase libefivar verbosity level.

    At time of writing, libefivar supports LOG_VERBOSE and LOG_DEBUG, which require -E to be specified one and two times, respectively.

    See the [SEE ALSO][] sexion for details.

  • -h: Show a help message with these flags, recognised environment variables (see [ENVIRONMENT][]) and ops (see [OPS][]).

  • op [arg…]: Specify an operation to run and arguments to pass to it.

    See [OPS][] for more detail.

#ENVIRONMENT

  • KLAPKI_HOST=: By default, klapki(8) uses the value found in /etc/machine-id (or, failing that, the current hostname, as obtained with gethostname(2)) as the identifier for the host.

    If this environment variable is present, it will be used instead; note, that the host identifier is used verbatim as an EFI variable name under klapki's GUID (a8a9ad3a-f831-11ea-946d-674ccd7415cc).

  • KLAPKI_WISDOM=: To obtain the description and cmdline, klapki(8) invokes respectively-named files under the wisdom root via execl(3), which is /etc/klapki by default. This value overrides that path. If not empty, a '/' is additionally appended before the executable name.

    See also [WISDOM][] below.

  • KLAPKI_EFI_ROOT=: By default, klapki(8) puts newly-installed files in \klapki\{host}\{version} under the ESP.

    If present, this overrides the constant prefix. Par exemple, setting KLAPKI_EFI_ROOT= when adding a kernel will put it and the initrds directly under \{host}\{version} (note that by default this collides with kernel-install(8)).

#OPS

  • dump: Write some state (boot order, total boot entries, boot position, each wanted entry, boot variants) and context (our kernels, fresh kernels, deleted files) to the standard output.

  • bootpos <position>: Change the boot position to 0-based <position>.

    The cluster of entries for the current host can be placed at any point in the boot order; it's 0 (i.e. at the beginning) by default, but if you have another operating system or boot-loader and wish to have it be the default, you can simply move klapki(8) down the required amount of entries.

    See description of addvariant below for sorting inside the cluster.

  • addkernel <version> <image> [initrd…] <"">: Allocate entries for the kernel with version <version> whose image resides at <image> and initrds at [initrd]…. The list of initrds is terminated with an empty argument.

    This directive is ignored if a kernel with version <version> is already known. See delkernel below.

    The kernel image and initrds will be copied to the ESP (see KLAPKI_EFI_ROOT= in [ENVIRONMENT][]) during context commit.

  • delkernel <version>: Purge all entries for which the version is <version>.

    The kernel image, initrds and containing folder will, if not used, be removed from the ESP during context commit.

  • addvariant <variant>: Add an explicit variant <variant> to the end, if not already known. Accompanying boot entries will be allocated in the derivation phase as needed.

    Variants are a global property, and a boot entry is generated for each variant (that is: for the implicit variant, represented by the empty string, in addition to any configured explicit variants).

    The order of explicit variants is preserved within each version group, which are sorted highest-to-lowest. For example: a host with two kernels (5.8.0-[12]-amd64) and two explicit variants (debug, silent) will produce the following entries (assume $KLAPKI_WISDOM/description linked to /bin/echo); note how the highest kernel version is at the top:   5.8.0-2-amd64   5.8.0-2-amd64 debug   5.8.0-2-amd64 silent   5.8.0-1-amd64   5.8.0-1-amd64 debug   5.8.0-1-amd64 silent

    After running klapki(8) with "delvariant debug addvariant debug", the two explicit variants are now ordered differently (silent, debug), and this is reflected in the boot order; note also how the implicit variant always sorts earlier than any explicit ones:   5.8.0-2-amd64   5.8.0-2-amd64 silent   5.8.0-2-amd64 debug   5.8.0-1-amd64   5.8.0-1-amd64 silent   5.8.0-1-amd64 debug

  • delvariant <variant>: Remove explicit variant <variant>, if any; accompanying boot entries will purged in the derivation phase as needed.

#WISDOM

The entry description and kernel cmdline are acquired by executing description and cmdline in /etc/klapki (or KLAPKI_WISDOM=, see [ENVIRONMENT][]) with the following arguments: 0: "description" or "cmdline" 1: kernel version 2: boot variant

And the standard output tied to a memfd (see memfd_create(2)), which is then trimmed, and all newlines are replaced with a single space.

klapki(8) stops processing if the child exits with a non-zero status or is killed by a signal. The special exit value 0x6B (107, ASCII 'k') is used to signal an error in execl(3)ing the wisdom binary.

Additional initrd= statements should work (with warnings, since the should. Please report on the bug tracker/mailing list (see [REPORTING BUGS][]) if you use them successfully!) and will not be managed by klapki(8),

The simplest /etc/klapki/description would be a link to /bin/echo. A simple cmdline is a /bin/sh shebang + echo command. A cursed cmdline would be a /bin/sh shebang and an awk '{gsub(/initrd=[^ ]+ ?/, ""); print}' /proc/cmdline command.

#UNINSTALLATION

Remove the EFI variable corresponding to the host (see [ENVIRONMENT][]) under klapki's GUID (a8a9ad3a-f831-11ea-946d-674ccd7415cc). This will purge the state for the host and hence abandon any entries left over, which remain bootable; to remove all klapki entries run {delkernel} first (see [OPS][]), or remove them manually from the ESP and firmware afterward.

On Linux this involves running chattr -i, then rm on /sys/firmware/efi/efivars/{KLAPKI_HOST}-a8a9ad3a-f831-11ea-946d-674ccd7415cc.

#EXIT VALUES

1 - error reading configuration,
2 - error loading state,
3 - error resolving state,
4 - error running an op,
5 - error in propagation phase,
6 - error in aging phase,
7 - error in wisening phase,
8 - error in saving phase,
9 - error committing context,
10 - error committing state.

#EXAMPLES

A simple set-up:

root@zoot:~# ls -l description cmdline
-rwxr-xr-x 1 root root 79 Sep 21 03:30 cmdline
lrwxrwxrwx 1 root root  9 Sep 20 04:30 description -> /bin/echo
root@zoot:~# cat cmdline
#!/bin/sh
echo root=ZFS=zoot/root console=ttyS0

Add a kernel with a single initrd and a variant:

root@zoot:~# KLAPKI_HOST=zoot klapki addkernel 5.8.0-2-amd64 /boot/vmlinuz-5.8.0-2-amd64 /boot/initrd.img-5.8.0-2-amd64 "" addvariant debug
EFI load: no config for this host (zoot) found; going to the top
Entry 000B changed
Entry 000D changed
Entry 000B: copied vmlinuz-5.8.0-2-amd64 from /boot to \KLAPKI\ZOOT\5.8.0-2-AMD64\
Entry 000B: copied initrd.img-5.8.0-2-amd64 from /boot to \KLAPKI\ZOOT\5.8.0-2-AMD64\
Updating state config
Updating boot order
Writing entry 000B
Writing entry 000D

Success! But the new "debug" entry doesn't really do much:

root@zoot:~# efibootmgr
BootCurrent: 000D
Timeout: 0 seconds
BootOrder: 000B,000D,000C,000A,0000,0001,0002,0003,0004,0005,0006,0007,0008,0009
Boot0000 through Boot0008 omitted
Boot0009* EFI Internal Shell    FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(7c04a583-9e3e-4f1c-ad65-e05268d0b4d1)
Boot000A* Linux Boot Manager    HD(1,GPT,0655a4fb-e2e9-4fa6-b37b-a52633aed855,0x800,0x76800)/File(\EFI\systemd\systemd-bootx64.efi)
Boot000B* 5.8.0-2-amd64 HD(1,GPT,0655a4fb-e2e9-4fa6-b37b-a52633aed855,0x800,0x76800)/File(\KLAPKI\ZOOT\5.8.0-2-AMD64\VMLINUZ-5.8.0-2-AMD64)i.n.i.t.r.d.=.\.k.l.a.p.k.i.\.z.o.o.t.\.5...8...0.-.2.-.a.m.d.6.4.\.i.n.i.t.r.d...i.m.g.-.5...8...0.-.2.-.a.m.d.6.4. .r.o.o.t.=.Z.F.S.=.z.o.o.t./.r.o.o.t. .c.o.n.s.o.l.e.=.t.t.y.S.0.
Boot000C* zoot 5.8.0-1-amd64    HD(1,GPT,0655a4fb-e2e9-4fa6-b37b-a52633aed855,0x800,0x76800)/File(\62dd03a4928c412180b3024ac6c03a90\5.8.0-1-amd64\linux)i.n.i.t.r.d.=.\.6.2.d.d.0.3.a.4.9.2.8.c.4.1.2.1.8.0.b.3.0.2.4.a.c.6.c.0.3.a.9.0.\.5...8...0.-.1.-.a.m.d.6.4.\.i.n.i.t.r.d...i.m.g.-.5...8...0.-.1.-.a.m.d.6.4. .r.o.o.t.=.Z.F.S.=.z.o.o.t./.r.o.o.t. .c.o.n.s.o.l.e.=.t.t.y.S.0.
Boot000D* 5.8.0-2-amd64 debug   HD(1,GPT,0655a4fb-e2e9-4fa6-b37b-a52633aed855,0x800,0x76800)/File(\KLAPKI\ZOOT\5.8.0-2-AMD64\VMLINUZ-5.8.0-2-AMD64)i.n.i.t.r.d.=.\.k.l.a.p.k.i.\.z.o.o.t.\.5...8...0.-.2.-.a.m.d.6.4.\.i.n.i.t.r.d...i.m.g.-.5...8...0.-.2.-.a.m.d.6.4. .r.o.o.t.=.Z.F.S.=.z.o.o.t./.r.o.o.t. .c.o.n.s.o.l.e.=.t.t.y.S.0.

Uncontrivedly, adding a conditional with another argument to enable debugging in the Intel PRO/100 driver and re-running klapki(8) will work:

root@zoot:~# cat cmdline
#!/bin/sh
echo root=ZFS=zoot/root console=ttyS0
[ "$2" = "debug" ] && echo e100.debug=16 || :

root@zoot:~# KLAPKI_WISDOM=. KLAPKI_HOST=zoot klapki
Entry 000D changed
Updating state config
Updating entry 000D

Note, that, as expected, only the state configuration (which stores the hash of the wanted entry) and the debug entry itself was updated:

root@zoot:~# efibootmgr -v
BootCurrent: 000D
Timeout: 0 seconds
BootOrder: 000B,000D,000C,000A,0000,0001,0002,0003,0004,0005,0006,0007,0008,0009
Boot0000 through Boot000A and Boot000C omitted
Boot000B* 5.8.0-2-amd64 HD(1,GPT,0655a4fb-e2e9-4fa6-b37b-a52633aed855,0x800,0x76800)/File(\KLAPKI\ZOOT\5.8.0-2-AMD64\VMLINUZ-5.8.0-2-AMD64)i.n.i.t.r.d.=.\.k.l.a.p.k.i.\.z.o.o.t.\.5...8...0.-.2.-.a.m.d.6.4.\.i.n.i.t.r.d...i.m.g.-.5...8...0.-.2.-.a.m.d.6.4. .r.o.o.t.=.Z.F.S.=.z.o.o.t./.r.o.o.t. .c.o.n.s.o.l.e.=.t.t.y.S.0.
Boot000D* 5.8.0-2-amd64 debug   HD(1,GPT,0655a4fb-e2e9-4fa6-b37b-a52633aed855,0x800,0x76800)/File(\KLAPKI\ZOOT\5.8.0-2-AMD64\VMLINUZ-5.8.0-2-AMD64)i.n.i.t.r.d.=.\.k.l.a.p.k.i.\.z.o.o.t.\.5...8...0.-.2.-.a.m.d.6.4.\.i.n.i.t.r.d...i.m.g.-.5...8...0.-.2.-.a.m.d.6.4. .r.o.o.t.=.Z.F.S.=.z.o.o.t./.r.o.o.t. .c.o.n.s.o.l.e.=.t.t.y.S.0. .e.1.0.0...d.e.b.u.g.=.1.6.

#AUTHOR

Written by наб <nabijaczleweli@nabijaczleweli.xyz>

#SPECIAL THANKS

To all who support further development, in particular:

  • ThePhD
  • Embark Studios

#REPORTING BUGS

<https://todo.sr.ht/~nabijaczleweli/klapki>

<mailto:~nabijaczleweli/klapki@lists.sr.ht>, archived at <https://lists.sr.ht/~nabijaczleweli/klapki>

#SEE ALSO

<https://git.sr.ht/~nabijaczleweli/klapki>

UEFI specification, Sexion 3.1.3 Load Options and related: <https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf>

libefivar verbosity levels: <https://github.com/rhboot/efivar/blob/36297adcb266f07bb06e725a0da377bc6e6aedd0/src/util.h#L328>