Compare commits

..

No commits in common. "main" and "v1.0" have entirely different histories.
main ... v1.0

13 changed files with 80 additions and 393 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Adrian Wannenmacher
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

113
README.md
View File

@ -1,108 +1,9 @@
# snap-pac-uki
Pacman hooks creating unified kernel images for snapshots created by snap-pac.
Need help? Talk to me at [#snap-pac-uki:tfld.dev][matrix-room].
## What is this?
This is a collection of scripts, pacman hooks and and systemd units for creating
and managing unified kernel images (UKIs) based on snapper. It originally
expected to be executed as a pacman hook after `snap-pac`, but this is no longer
required (though the name remains).
## Installation
### Prerequisites
While not depending on it directly, this tool expects that you already set up
snapper. It expects, that each snapshot contains its kernel
(`/boot/vmlinuz-linux`) and initramfs (`/boot/intitramfs-linux.img`), as well as
a kernel command line file (`/etc/snap-pac-uki/kernel-cmd`).
For generating the current and fallback UKIs, the kernel and initramfs files of
the currently running system are used. For a kernel cmd, it uses
`/etc/snap-pac-uki/kernel-cmd-current` and `/etc/snap-pac-uki/kernel-cmd-fallback`.
If you want to boot a UKI directly, use the `efibootmgr` command to create an
entry.
```bash
# template
efibootmgr -c \
-d <EFI DEVICE> \
-p <EFI PARTITION NUMBER> \
-l <EFI PATH> \
-L <LABEL>
# example
efibootmgr -c \
-d /dev/nvme0n1 \
-p 1 \
-l "\EFI\Linux\arch-linux.efi" \
-L "Arch Linux"
# example for fallback
efibootmgr -c \
-d /dev/nvme0n1 \
-p 1 \
-l "\EFI\Linux\arch-linux-fallback.efi" \
-L "Arch Linux (Fallback)"
```
### Using the install script
1. You can install the dependencies of this program with the following command:
```bash
pacman -S fish ripgrep sbsigntools sed
```
2. Download the latest release from [here][releases] and unpack it.
3. Then run the install script (you need to be in the same directory):
```bash
./install.fish
```
## Usage
### Creating snapshot UKIs
This creates a new UKI for all snapshots where none is found:
```bash
snap-pac-uki snapshots
```
### Create current UKIs
This creates a new UKI for the current system (including a fallback variant):
```bash
snap-pac-uki current
```
### Clean up UKIs
This removes all UKIs without corresponding snapshot (solely based on number):
```bash
snap-pac-uki clean
```
### Enable periodic cleanups using systemd timer
```
systemctl enable --now snap-pac-uki-cleanup.timer
```
## Uninstall
Simply run the following command in this directory:
```bash
./uninstall.fish
```
[matrix-room]: https://matrix.to/#/#snap-pac-uki:tfld.dev
[releases]: https://git.tfld.dev/tfld/snap-pac-uki/releases
## Limits
- only works on the "root" snap-pac config
- puts all UKIs into `/boot/EFI/Linux/`
- kernel parameters are extracted from a file called `/boot/loader/entries/00-arch.conf`
- expects that the default subvolume is mounted via kernel command line
- expects that the default subvolume is called `@`

View File

@ -8,5 +8,5 @@ Target = *
[Action]
Description = Performing pre UKI creation...
When = PreTransaction
Exec = /usr/local/bin/snap-pac-uki snapshots
Exec = /usr/share/libalpm/scripts/snap-pac-uki_create.fish
AbortOnFail

View File

@ -8,4 +8,4 @@ Target = *
[Action]
Description = Performing post UKI creation...
When = PostTransaction
Exec = /usr/local/bin/snap-pac-uki snapshots
Exec = /usr/share/libalpm/scripts/snap-pac-uki_create.fish

View File

@ -6,6 +6,6 @@ Type = Package
Target = *
[Action]
Description = Performing current UKI creation...
Description = Performing UKI cleanup...
When = PostTransaction
Exec = /usr/local/bin/snap-pac-uki current
Exec = /usr/share/libalpm/scripts/snap-pac-uki_cleanup.fish

View File

@ -1,23 +1,4 @@
#!/bin/fish
cp ./src/snap-pac-uki.fish /usr/local/bin/snap-pac-uki
echo "installed script."
if not test -d "/etc/snap-pac-uki"
mkdir -p "/etc/snap-pac-uki"
echo "created config dir."
end
if not test -f "/etc/snap-pac-uki/config.fish"
cp ./src/config.fish /etc/snap-pac-uki/config.fish
echo "installed config script."
else
cp ./src/config.fish /etc/snap-pac-uki/config-new.fish
echo "found existing config script -> installed as -new file."
end
cp ./pacman/* /usr/share/libalpm/hooks/
echo "installed pacman hooks."
cp ./systemd/* /etc/systemd/system/
echo "installed systemd unit files"
cp ./hooks/* /usr/share/libalpm/hooks/
cp ./scripts/* /usr/share/libalpm/scripts/

View File

@ -0,0 +1,17 @@
#!/bin/fish
# get snapshots
set -l config "root"
set -l snapshot_ids (snapper -c $config --jsonout ls | jq ".$config | .[] | .number")
# get current ukis
set -l ukis (ls /boot/EFI/Linux/ | grep '^snap-pac-uki-[1-9][0-9]*\.efi$' | grep -o "[1-9][0-9]*")
for x in $ukis
if not contains $x $snapshot_ids
set -l path "/boot/EFI/Linux/snap-pac-uki-$x.efi"
rm $path
echo "==> removed UKI: $path"
end
end

View File

@ -0,0 +1,46 @@
#!/bin/fish
# get id and type of last snapshot
set -l config "root"
set -l snapshot (snapper -c $config --jsonout ls | jq -c ".$config | last")
set -l id (echo $snapshot | jq ".number")
if not contains (echo $snapshot | jq -r ".type") "pre" "post"
echo "==> last snapshot type is neither 'pre' nor 'post'; no UKI creation"
exit
end
# check if UKI already exists
set -l ukis (ls /boot/EFI/Linux/ | grep '^snap-pac-uki-[1-9][0-9]*\.efi$' | grep -o "[1-9][0-9]*")
if contains $id $ukis
echo "==> UKI already exists; no UKI creation"
exit
end
# prepare uki creation
set -l buildpath "/tmp/snap-pac-uki/$id"
mkdir -p $buildpath
rm -rf "$buildpath/*"
cd $buildpath
sed "s/BUILD_ID=.*/BUILD_ID=\"Snapshot $id\"/" </usr/lib/os-release >./os-release
grep options /boot/loader/entries/00-arch.conf | \
string sub -s 9 | \
sed ':a;N;$!ba;s/\n/ /g' | \
sed "s/subvol=@/subvol=@snapshots\\/$id\\/snapshot/" >./kernel-parameters
# create uki
set -l ukipath "/boot/EFI/Linux/snap-pac-uki-$id.efi"
objcopy \
--add-section .osrel="./os-release" --change-section-vma .osrel=0x20000 \
--add-section .cmdline="./kernel-parameters" --change-section-vma .cmdline=0x30000 \
--add-section .splash="/usr/share/systemd/bootctl/splash-arch.bmp" --change-section-vma .splash=0x40000 \
--add-section .linux="/boot/vmlinuz-linux" --change-section-vma .linux=0x2000000 \
--add-section .initrd="/boot/initramfs-linux.img" --change-section-vma .initrd=0x3000000 \
"/usr/lib/systemd/boot/efi/linuxx64.efi.stub" $ukipath
echo "==> created UKI: $ukipath"

View File

@ -1,41 +0,0 @@
function config -d "sets some configuration variables"
# If this is not set to "yes", snap-pac-uki will not do anything when
# executed.
#
# This is to make sure that this program can be installed with pacman. If
# this wasn't done, pacman would install this program with an default
# config, which most likely wouldn't work correctly. Therefore this needs
# to be set to "yes" by the user, once they've configured snap-pac-uki.
set -g CFG_ENABLED "no"
# A directory where ukis can be built.
#
# It is recommended to keep this within /tmp, as the generated data will not
# be needed later.
# It is recommended to not set this to a path with data inside. This program
# will sometimes remove all content of specific subfolders.
set -g CFG_BUILD_DIR "/tmp/snap-pac-uki"
# The subvolume to use on the kernel command line when generating current
# images.
set -g CFG_DEFAULT_SUBVOL "@"
# The linux fs path containing all snapshot data
set -g CFG_SNAPSHOT_PATH "/.snapshots"
# The name of the subvolume containing all snapshots
set -g CFG_SNAPSHOT_SUBVOL "@snapshots"
# The directory to put uki files in
set -g CFG_UKI_DIR "/efi/EFI/Linux"
# Whether generated uki files should be signed
set -g CFG_SECUREBOOT "yes"
# The path to the .key file with which to sign uki files
set -g CFG_SECUREBOOT_KEY "/etc/secureboot/keys/db/db.key"
# The path to the .crt file with which to sign uki files
set -g CFG_SECUREBOOT_CERT "/etc/secureboot/keys/db/db.crt"
end

View File

@ -1,176 +0,0 @@
#!/bin/fish
function error -d "prints an error and exits" -a error
echo $error 1>&2
exit 1
end
function check_deps -d "checks if all dependencies are available"
set -l dependencies \
"rg" \
"sed" \
for dep in $dependencies
which $dep >/dev/null 2>/dev/null
if test $status != "0"
error "missing depenency: $dep"
end
end
which "sbsign" >/dev/null 2>/dev/null
if test $status != "0" -a $CFG_SECUREBOOT = "yes"
set -g CFG_SECUREBOOT "no"
echo "CFG_SECUREBOOT overwriten to \"no\": 'sbsign' not available"
end
end
function find_ukiless_snapshots -d "find"
set -ge TASK_UKI
for id in (ls $CFG_SNAPSHOT_PATH)
if test $id = "0"
continue
end
if test -f "$CFG_UKI_DIR/arch-linux-$id.efi"
continue
end
set -ga TASK_UKI $id
end
end
function create_uki -d "creates a new uki" -a variant id
switch $variant
case "rolling"
set bd "$CFG_BUILD_DIR/current"
set current "-current"
case "fallback"
set bd "$CFG_BUILD_DIR/current"
set fallback "-fallback"
case "snapshot"
set bd "$CFG_BUILD_DIR/$id"
set snid "-$id"
set prefix "$CFG_SNAPSHOT_PATH/$id/snapshot"
case "*"
error "unknown uki variant: $variant"
end
# create initrd
set initrd "$bd/initramfs-linux$fallback.img"
cat "$prefix/boot/initramfs-linux$fallback.img" >> $initrd
# create uki
objcopy \
--add-section .osrel="$bd/os-release$fallback" --change-section-vma .osrel=0x20000 \
--add-section .cmdline="$bd/kernel-cmd$current$fallback" --change-section-vma .cmdline=0x30000 \
--add-section .splash="$prefix/usr/share/systemd/bootctl/splash-arch.bmp" --change-section-vma .splash=0x40000 \
--add-section .linux="$prefix/boot/vmlinuz-linux" --change-section-vma .linux=0x2000000 \
--add-section .initrd="$initrd" --change-section-vma .initrd=0x3000000 \
"$prefix/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "$bd/arch-linux$fallback$snid.efi.unsigned"
# sign
if test "$CFG_SECUREBOOT" = "yes"
sbsign --key $CFG_SECUREBOOT_KEY \
--cert $CFG_SECUREBOOT_CERT \
--output "$bd/arch-linux$fallback$snid.efi" \
"$bd/arch-linux$fallback$snid.efi.unsigned"
else # otherwise move unsigned efi to location
mv "$bd/arch-linux$fallback$snid.efi.unsigned" "$bd/arch-linux$fallback$snid.efi"
end
end
function create_snapshot_uki -d "creates an uki for a snapshot" -a id
# create build directory
set bd "$CFG_BUILD_DIR/$id"
if test -f $bd
error "build dir for snapshot $id is a file"
else if test -d $bd
rm -rf "$bd/*"
else
mkdir -p $bd
end
# prepare files
sed "s/BUILD_ID=.*/BUILD_ID=\"snapshot $id\"/;s/PRETTY_NAME=\"\(.*\)\"/PRETTY_NAME=\"\1 (Snapshot $id)\"/" <"$CFG_SNAPSHOT_PATH/$id/snapshot/usr/lib/os-release" >"$bd/os-release"
sed "s/SNAPSHOT/$CFG_SNAPSHOT_SUBVOL\/$id\/snapshot/" <"$CFG_SNAPSHOT_PATH/$id/snapshot/etc/snap-pac-uki/kernel-cmd" >"$bd/kernel-cmd"
# create uki
create_uki snapshot $id
end
function create_current_uki -d "creates an uki for the current system"
# create build directory
set bd "$CFG_BUILD_DIR/current"
if test -f $bd
error "build dir for snapshot $id is a file"
else if test -d $bd
rm -rf "$bd/*"
else
mkdir -p $bd
end
# create default
cp "/usr/lib/os-release" "$bd/os-release"
sed "s/SNAPSHOT/$CFG_DEFAULT_SUBVOL/" <"/etc/snap-pac-uki/kernel-cmd-current" >"$bd/kernel-cmd-current"
create_uki rolling
# create fallbac
sed "s/BUILD_ID=.*/BUILD_ID=fallback/;s/PRETTY_NAME=\"\(.*\)\"/PRETTY_NAME=\"\1 (Fallback)\"/" <"/usr/lib/os-release" >"$bd/os-release-fallback"
sed "s/SNAPSHOT/$CFG_DEFAULT_SUBVOL/" <"/etc/snap-pac-uki/kernel-cmd-fallback" >"$bd/kernel-cmd-fallback"
create_uki fallback
end
function main
# prepare for execution
source "/etc/snap-pac-uki/config.fish"
config
if test "$CFG_ENABLED" != "yes"
echo "==> snap-pac-uki not enabled in config file" 1>&2
exit 0
end
check_deps
switch $argv[1]
case "snapshots"
find_ukiless_snapshots
for id in $TASK_UKI
echo "==> found snapshot without uki: $id"
create_snapshot_uki $id
echo " -> created uki for snapshot $id"
cp "$CFG_BUILD_DIR/$id/arch-linux-$id.efi" "$CFG_UKI_DIR"
echo " -> copied uki for snapshot $id into uki directory"
end
case "current"
echo "==> creating current ukis"
create_current_uki
echo " -> created ukis"
cp "$CFG_BUILD_DIR/current/arch-linux.efi" "$CFG_UKI_DIR"
cp "$CFG_BUILD_DIR/current/arch-linux-fallback.efi" "$CFG_UKI_DIR"
echo " -> copied ukis into uki directory"
case "clean"
echo "==> cleaning ukis"
set ids (ls $CFG_SNAPSHOT_PATH)
for uki in (ls "$CFG_UKI_DIR" | rg "[0-9]" | sed "s/arch-linux-\([0-9]*\).efi/\1/")
if not contains "$uki" $ids
echo " -> cleaning uki and boot entry for removed snapshot $uki"
rm "$CFG_UKI_DIR/arch-linux-$uki.efi"
end
end
case "*"
error "unknown command"
end
end
main $argv

View File

@ -1,6 +0,0 @@
[Unit]
Description=cleaning up ukis
[Service]
Type=oneshot
ExecStart=/usr/local/bin/snap-pac-uki clean

View File

@ -1,9 +0,0 @@
[Unit]
Description="timer for triggering uki cleanup
[Timer]
OnBootSec=15min
OnUnitActiveSec=1d
[Install]
WantedBy=timers.target

View File

@ -1,14 +1,9 @@
#!/bin/fish
rm /usr/local/bin/snap-pac-uki
echo "removed script."
for name in (ls ./pacman)
rm /usr/share/libalpm/hooks/$name
for x in (ls ./hooks)
rm "/usr/share/libalpm/hooks/$x"
end
echo "removed pacman hooks."
for name in (ls ./systemd)
rm /etc/systemd/system/$name
for x in (ls ./scripts)
rm "/usr/share/libalpm/scripts/$x"
end
echo "removed systemd unit files"