Skip to content

Instantly share code, notes, and snippets.

@chaserhkj
Last active June 4, 2026 22:52
Show Gist options
  • Select an option

  • Save chaserhkj/ad666a558800abeae9bc51202ad0da78 to your computer and use it in GitHub Desktop.

Select an option

Save chaserhkj/ad666a558800abeae9bc51202ad0da78 to your computer and use it in GitHub Desktop.
DEPRECATED, SEE NOTE

Deprecated

GC code this script uses has been merged upstream.

Since bootc 1.14.0, GC is automatically performed at the end of an update. The use of this script is no longer neccessary.

Below is the original script kept for reference reasons.

#!/bin/bash
# binary dependencies outside bootc, coreutils and util-linux: cfsctl jq
# use together with cfsctl from https://github.com/chaserhkj/composefs-rs (PR underway)
#
# This script cleans up bootc composefs repository, leaving only booted, stage and rollback deployments,
# pinning their images and streams running garbage collection on everything else, and finally removes
# unused BLS entires
set -e
shopt -s extglob nullglob

program=$(basename $0)

progress() {
    echo "($program) $@"
}

log_and_run() {
    echo "(running) $@"
    "$@"
}

dry_run() {
    echo "(dry run) $@"
}

[ "$(readlink /proc/self/ns/mnt)" = "$(readlink /proc/1/ns/mnt)" ] && { progress "Entering private mntns" ; exec unshare -m "$0" "$@"; }

run_prefix=dry_run
while [ $# -gt 0 ]; do
    if [ $1 = -f ] || [ $1 = --force ]; then
        run_prefix=log_and_run
    fi
    shift
done

bootc_status="$(bootc status --format json)"

for type in booted rollback staged; do
    printf -v "${type}_digest" "%s" $(echo "$bootc_status" | jq -r ".status.$type.image.imageDigest // \"\" " | cut -d: -f2)
    printf -v "${type}_verity" "%s" $(echo "$bootc_status" | jq -r ".status.$type.composefs.verity // \"\" ")
    printf -v "${type}_boot_digest" "%s" $(echo "$bootc_status" | jq -r ".status.$type.composefs.bootDigest // \"\" ")
    printf -v "${type}_boot_type"  "%s" $(echo "$bootc_status" | jq -r ".status.$type.composefs.bootType // \"\" ")
    printf -v "${type}_bootloader" "%s" $(echo "$bootc_status" | jq -r ".status.$type.composefs.bootloader // \"\" ")
done

progress "Resolved GC roots:"

for type in booted rollback staged; do
    for item in digest verity boot_digest boot_type bootloader; do
        var=${type}_${item}
        echo "  $var: ${!var}"
    done
    echo
done


efi_device=$(lsblk -o PATH -nr -Q 'PARTTYPE == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"')
gc_root_flags=

for type in booted rollback staged; do
    digest_var=${type}_digest
    [ -n "${!digest_var}" ] && gc_root_flags="$gc_root_flags -r oci-config-sha256:${!digest_var}"
    verity_var=${type}_verity
    [ -n "${!verity_var}" ] && gc_root_flags="$gc_root_flags -r ${!verity_var}"
done

progress "Remounting /sysroot RW"

$run_prefix mount --make-private /sysroot
$run_prefix mount -o remount,rw /sysroot

progress "Pruning /sysroot/state/deploy"

stale_deployments=( /sysroot/state/deploy/!($booted_verity|$rollback_verity|$staged_verity) )
$run_prefix rm -rf "${stale_deployments[@]}"

dry_run_flag=
[ "$run_prefix" = "dry_run" ] && dry_run_flag=--dry-run

progress "Performing GC $dry_run_flag on /sysroot/composefs"

cfsctl_cmdline="cfsctl --repo /sysroot/composefs gc $gc_root_flags $dry_run_flag"
echo "(cfsctl) $cfsctl_cmdline"
$cfsctl_cmdline

clean_esp=yes
for type in booted rollback staged; do
    var=${type}_boot_type
    # when boot_type is empty string, assume the current entry is empty and skip to next entry
    [ -z "${!var}" ] && continue
    [ "${!var}" != "Bls" ] && clean_esp=no
    var=${type}_bootloader
    [ "${!var}" != "systemd" ] && clean_esp=no
done

if [ "$clean_esp" != "yes" ]; then
    progress "Unsupported bootloader/boot type is used, skip cleaning ESP"
    exit 0
fi

progress "Mounting ESP"

log_and_run mount --make-private $efi_device /sysroot/boot
boot_contents=( /sysroot/boot/EFI/Linux/* )

progress "Cleaning unused kernel files"

# /sysroot/boot/EFI/Linux entires may still refer to stale deployment ids
# Try to rename them to current deployment ids
# This is necessary to make "bootc upgrade" and "bootc switch" function correctly
stale_deployment_ids=()
for path in "${stale_deployments[@]}"; do
    stale_deployment_ids+=( "$(basename $path)" )
done

declare -A rename_map

for boot_content_dir in "${boot_contents[@]}"; do
    boot_content_id=$(basename $boot_content_dir)
    used=no
    boot_digest=$(cat $boot_content_dir/vmlinuz $boot_content_dir/initrd | sha256sum | cut -d" " -f1)
    echo "(ESP GC) Boot binary entry: $boot_content_id"
    echo "(ESP GC) Boot binary digest: $boot_digest"
    # This order decides which digest to use for rename when multiple entries share same boot binaries
    # Always prioritize staged > booted > rollback
    for entry_type in rollback booted staged; do

        digest_var_name=${entry_type}_boot_digest
        match_digest=${!digest_var_name}
        

        if [[ "$boot_digest" == "$match_digest" ]]; then
            used=yes
        else
            continue
        fi

        verity_var_name=${entry_type}_verity
        match_verity=${!verity_var_name}

        # Check if used boot binary is referring to stale deployment id
        for stale_id in "${stale_deployment_ids[@]}"; do
            [[ "$stale_id" != "$boot_content_id" ]] && continue
            rename_map[$stale_id]=$match_verity
            echo "  mark rename, from $stale_id"
            echo "  to $match_verity"
        done
    done
    [ "$used" != "yes" ] && $run_prefix rm -rf $boot_content_dir
done

progress "Renaming ESP references to stale entries"

for from in "${!rename_map[@]}"; do
    to=${rename_map[$from]}
    $run_prefix mv /sysroot/boot/EFI/Linux/$from /sysroot/boot/EFI/Linux/$to
    $run_prefix sed -i "s/\\/$from\\//\\/$to\\//g" /sysroot/boot/loader/entries/*.conf
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment