Last active
January 17, 2026 01:24
-
-
Save chaserhkj/ad666a558800abeae9bc51202ad0da78 to your computer and use it in GitHub Desktop.
Run garbage collection on a bootc composefs backend repo using a forked cfsctl. WARN: EXPERIMENTAL, DO NOT USE IN PRODUCTION
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 -s oci-config-sha256:${!digest_var}" | |
| verity_var=${type}_verity | |
| [ -n "${!verity_var}" ] && gc_root_flags="$gc_root_flags -i ${!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[@]}" | |
| force_flag= | |
| [ "$run_prefix" != "dry_run" ] && force_flag=--force | |
| gc_log=/tmp/bootc-cfsctl-cleanup.gc.log | |
| progress "Performing GC $force_flag on /sysroot/composefs and writing GC logs to $gc_log" | |
| cfsctl_cmdline="RUST_LOG=debug cfsctl --repo /sysroot/composefs gc $gc_root_flags $force_flag >$gc_log 2>&1" | |
| echo "(cfsctl) $cfsctl_cmdline" | |
| eval "$cfsctl_cmdline" | |
| echo "(cfsctl) Garbage collected objects: $(grep -E '(dry run: )?rm ' $gc_log | wc -l)" | |
| 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