Debian · Secure Boot · TPM · LUKS2

kernel-update-tpm
Installation, Testing
& Operations Manual

Complete guide for installing, verifying, and operating the automated kernel update TPM re-enrollment toolset. Covers manual use, APT hook integration, monitoring, and recovery procedures.

Contents
  1. Overview and file inventory
  2. Prerequisites
  3. Installation
  4. Verifying the installation
  5. Manual operation
  6. APT hook operation
  7. Monitoring a run in progress
  8. The sentinel file
  9. Recovery procedures
  10. Uninstalling
  11. Quick reference

1. Overview and file inventory

The toolset automates the three-phase kernel update process described in the Kernel Update: Temporary PCR Relaxation & Re-enrollment guide. It replaces the manual slot management steps with a script that persists across reboots via a sentinel file and a systemd oneshot unit.

Files

FileInstall pathPurpose
kernel-update-tpm.sh /usr/local/sbin/kernel-update-tpm.sh Main script. Runs Phase 1, 2, and 3. Called directly by user or by the APT hook.
kernel-update-tpm-apt-hook /usr/local/sbin/kernel-update-tpm-apt-hook APT Pre-Install-Pkgs hook. Detects boot-sensitive packages and triggers Phase 1 automatically.
90-kernel-update-tpm.conf /etc/apt/apt.conf.d/90-kernel-update-tpm.conf APT configuration that registers the hook with dpkg.

Runtime artifacts (created automatically, not installed manually)

PathPurposeLifetime
/var/lib/kernel-update-tpm/state Sentinel file tracking current phase, slot numbers, kernel version, timestamps. Phase 1 → Phase 3. Removed on successful completion.
/etc/systemd/system/kernel-update-tpm-resume.service Systemd oneshot unit that resumes the script after each reboot. Phase 1 → Phase 3. Removed on successful completion.
/etc/apt/apt.conf.d/99-kernel-update-tpm-reboot One-shot Post-Invoke drop-in that reboots after apt finishes (hook-mode only). Written at end of hook-mode Phase 1. Self-deletes on first execution.
/var/log/kernel-update-tpm-apt-hook.log APT hook log. Records every transaction the hook evaluated, triggered or not. Persistent. Rotate or inspect manually.

Flow summary

PhaseTriggerActionsEnds with
1 User runs script, or APT hook detects boot-sensitive package Preflight checks → bind loose PCR-7 slot → test → remove strict slot → [apt upgrade + initramfs/grub if manual] → install systemd unit Reboot
2 systemd unit fires on first post-update boot Confirm new kernel → verify temp slot → re-enroll strict slot → test strict slot Reboot
3 systemd unit fires on second post-update boot Verify strict slot via full TPM cycle → remove temp slot → final report → remove unit and sentinel Done — no reboot

2. Prerequisites

The following must be true before installing. The script checks most of these at runtime but confirming them in advance avoids surprises.

RequirementHow to confirm
Debian system with LUKS2 encrypted root, Secure Boot enabled sudo cryptsetup luksDump /dev/nvme0n1p3 | grep Version2
Exactly one Clevis TPM2 slot enrolled sudo clevis luks list -d /dev/nvme0n1p3 → one tpm2 entry
At least one human passphrase slot enrolled Confirmed by Phase 1 preflight automatically
TPM currently unseals (system auto-unlocked on last boot) Confirmed by Phase 1 preflight automatically
Required commands available clevis cryptsetup update-initramfs update-grub apt awk sort comm tpm2_pcrread systemctl lsblk
Perl-compatible regex support in grep grep -P '' /dev/null && echo ok
Do not install or run this script if the current Clevis TPM binding is already broken — for example if the system prompted for the LUKS passphrase on the last boot. Resolve the TPM binding first using the manual procedure in secureboot-guide.html, then install this toolset.

3. Installation

Option A — self-contained installer (recommended)

pcr-apt-update-install.sh embeds all four scripts and deploys them in a single command:

sudo bash pcr-apt-update-install.sh

The installer writes, permissions, and verifies each file. Run it once; it is not needed afterward and can be discarded.

Option B — manual copy

sudo cp kernel-update-tpm.sh /usr/local/sbin/
sudo cp kernel-update-tpm-apt-hook /usr/local/sbin/
sudo cp update-clevis-pcrs.sh /usr/local/sbin/
sudo chmod 755 /usr/local/sbin/kernel-update-tpm.sh \
               /usr/local/sbin/kernel-update-tpm-apt-hook \
               /usr/local/sbin/update-clevis-pcrs.sh
sudo chown root:root /usr/local/sbin/kernel-update-tpm.sh \
                     /usr/local/sbin/kernel-update-tpm-apt-hook \
                     /usr/local/sbin/update-clevis-pcrs.sh
sudo cp 90-kernel-update-tpm.conf /etc/apt/apt.conf.d/
sudo chown root:root /etc/apt/apt.conf.d/90-kernel-update-tpm.conf
sudo chmod 644 /etc/apt/apt.conf.d/90-kernel-update-tpm.conf
Scripts in /usr/local/sbin/ must be owned by root and not world-writable. The APT hook runs as root during every apt transaction. A writable hook script is a privilege escalation path.

Confirm file ownership and permissions

ls -l /usr/local/sbin/kernel-update-tpm.sh \
       /usr/local/sbin/kernel-update-tpm-apt-hook \
       /usr/local/sbin/update-clevis-pcrs.sh \
       /etc/apt/apt.conf.d/90-kernel-update-tpm.conf

Expected: all owned root:root. Scripts mode -rwxr-xr-x (755). Config mode -rw-r--r-- (644).

4. Verifying the installation

Run these checks after installation and before any kernel update occurs.

4a. Confirm the hook is registered

apt-config dump | grep -i "kernel-update-tpm"

Expected output:

DPkg::Pre-Install-Pkgs:: "/usr/local/sbin/kernel-update-tpm-apt-hook";
DPkg::Tools::Options::/usr/local/sbin/kernel-update-tpm-apt-hook::Version "3";

4b. Dry-run the hook against a known-trigger package

Use apt-get --simulate to exercise the hook without installing anything. The hook receives the package list on stdin and should log a trigger decision.

# This simulates an upgrade of linux-image — the hook fires but sees
# apt is in simulate mode and the script is not yet in a real transaction.
# Watch the log to confirm the hook ran and evaluated the package list.
sudo apt-get --simulate upgrade 2>&1 | grep -i "kernel-update-tpm" || true
sudo tail -20 /var/log/kernel-update-tpm-apt-hook.log
The hook will fire on --simulate runs but Phase 1 will abort at the preflight stage if a sentinel already exists, or will detect no real action is needed. In practice, the hook checks for the sentinel before doing anything irreversible — but be aware that apt-get --simulate is not a completely inert operation with this hook registered.

4c. Test auto-detection without running a real update

# Show usage and confirm the script is installed correctly (safe — reads nothing)
sudo /usr/local/sbin/kernel-update-tpm.sh --help

# Verify what LUKS block device will be detected
sudo blkid | grep crypto_LUKS
# Expected: the LUKS partition, e.g.  /dev/nvme0n1p3: UUID="..." TYPE="crypto_LUKS"

# Verify which Clevis slot will be used as the strict PCR source
sudo clevis luks list -d /dev/nvme0n1p3   # substitute your device from blkid output above
# Expected: one tpm2 slot listing the strict PCR policy

4d. Confirm no sentinel exists from a previous run

ls /var/lib/kernel-update-tpm/state 2>/dev/null \
  && echo "WARNING: sentinel exists — a previous run may be in progress" \
  || echo "No sentinel — clean state"

4e. Confirm the systemd resume unit is not already present

systemctl status kernel-update-tpm-resume.service 2>&1 | head -5 || echo "Unit not present — expected"

5. Manual operation

Run the script directly when you want to trigger a kernel update and TPM re-enrollment on your own schedule, without waiting for an apt transaction to trigger the hook. This is also the path to use when the hook is not installed.

Basic usage — auto-detect everything

sudo /usr/local/sbin/kernel-update-tpm.sh

Explicit device and PCR policy

sudo /usr/local/sbin/kernel-update-tpm.sh /dev/nvme0n1p3 0,1,2,4,5,7,8,9

What to expect

Phase 1 output looks like this (exact slot numbers will differ):

[kernel-update-tpm] Auto-detected LUKS device: /dev/nvme0n1p3
[kernel-update-tpm] Auto-detected strict PCRs from existing binding: 0,1,2,4,5,7,8,9
[kernel-update-tpm] === Phase 1: preflight and kernel update ===
[kernel-update-tpm] Human passphrase slot confirmed: slot 0
[kernel-update-tpm] Detecting existing Clevis slot...
[kernel-update-tpm] Found Clevis slot: 1
[kernel-update-tpm] Verifying TPM unseal on slot 1...
[kernel-update-tpm] TPM unseal OK
[kernel-update-tpm] Stable PCR values before update (0,1,2,4,5,7):
  sha256:
    0 : ...
    ...
[kernel-update-tpm] Binding temporary loose slot (PCR 7)...
[kernel-update-tpm] Temporary slot created: 2
[kernel-update-tpm] Testing temporary slot 2...
[kernel-update-tpm] Temporary slot OK
[kernel-update-tpm] Removing strict slot 1...
[kernel-update-tpm] Strict slot 1 removed
[kernel-update-tpm] Running apt update && apt upgrade...
[kernel-update-tpm] Rebuilding initramfs...
[kernel-update-tpm] Updating GRUB...
[kernel-update-tpm] Phase 1 complete.
[kernel-update-tpm] Kernel: 6.1.0-30-amd64 — will update on next boot.
[kernel-update-tpm] Rebooting in 5 seconds — Ctrl-C to cancel
The 5-second countdown before reboot is your only intervention window. Ctrl-C cancels the reboot but leaves the system in an intermediate state — see Recovery procedures.

If no kernel update is available

If apt upgrade finds nothing to install, Phase 1 will still complete: it will have swapped slots, rebuilt initramfs against the current kernel, and scheduled the re-enrollment reboot. This is harmless — Phase 2 will detect that the kernel version is unchanged and abort.

If you run the script manually when no kernel update is pending, Phase 2 will detect the unchanged kernel and offer an interactive restore: press r to re-enroll the strict slot against the current boot state and clean up immediately — no reboot required. See Recovery: Phase 2 kernel check.

6. APT hook operation

Once the hook is installed, kernel updates are handled automatically whenever apt, apt-get, or aptitude installs or upgrades a boot-sensitive package.

Trigger packages

The hook fires when any package name matches:

linux-image | linux-headers | linux-kbuild | linux-modules |
initramfs-tools | dracut | grub | shim | mokutil | fwupd |
firmware | microcode | amd64-microcode | intel-microcode |
clevis | cryptsetup | tpm2 | libtss2 | systemd

Typical automatic flow

# User runs a routine upgrade
sudo apt upgrade

# What happens behind the scenes:
# 1. apt evaluates the transaction
# 2. dpkg calls kernel-update-tpm-apt-hook with the package list on stdin
# 3. Hook detects linux-image-6.1.0-31-amd64 in the list
# 4. Hook calls kernel-update-tpm.sh --hook-mode
# 5. Phase 1 runs: preflight, slot swap, sentinel written, systemd unit installed
# 6. Hook exits 0; apt continues and installs packages normally
# 7. Package postinst scripts run update-initramfs and update-grub
# 8. After apt finishes, the Post-Invoke reboot drop-in fires
# 9. System reboots
# 10. Phase 2 runs automatically via systemd unit
# 11. System reboots again
# 12. Phase 3 runs automatically via systemd unit
# 13. Done — strict slot re-enrolled, temp slot removed

Terminal output during a hook-triggered run

Reading package lists... Done
...
kernel-update-tpm: boot-sensitive package(s) detected in this transaction:
  linux-image-6.1.0-31-amd64
Preparing TPM Clevis slot transition before package installation...
(See /var/log/kernel-update-tpm-apt-hook.log for details)

kernel-update-tpm: Clevis slot transition prepared successfully.
The system will reboot automatically after apt completes.

Setting up linux-image-6.1.0-31-amd64 ...
update-initramfs: Generating /boot/initrd.img-6.1.0-31-amd64
...
Broadcast message: The system will reboot now!

Hook log

# Review all hook decisions — triggered and skipped
sudo cat /var/log/kernel-update-tpm-apt-hook.log

# Watch in real time during an apt run
sudo tail -f /var/log/kernel-update-tpm-apt-hook.log

Transactions that do NOT trigger the hook

Any apt transaction containing only non-boot-sensitive packages passes through silently. The hook logs a SKIP: no boot-sensitive package detected entry and exits 0 immediately.

# Example: installing curl does not trigger the hook
sudo apt install curl
# Hook log will show: SKIP: no boot-sensitive package detected

7. Monitoring a run in progress

Check current phase

sudo cat /var/lib/kernel-update-tpm/state

If the file does not exist, no run is in progress. If it exists, the phase field shows where the process is: 2 means waiting for the first post-update reboot, 3 means waiting for the verification reboot.

Check systemd unit status

sudo systemctl status kernel-update-tpm-resume.service

The unit should show enabled when a run is in progress between reboots. After Phase 3 completes it will show disabled and then be removed entirely.

Follow Phase 2 or 3 in the journal

# Watch journal for the unit's output after a reboot
sudo journalctl -f -u kernel-update-tpm-resume.service

# Or review the completed run after the fact
sudo journalctl -u kernel-update-tpm-resume.service --no-pager

Confirm final Clevis state after Phase 3

export DEV=/dev/nvme0n1p3
sudo clevis luks list -d "$DEV"
# Expected: exactly one tpm2 entry with the strict PCR policy

sudo cryptsetup luksDump "$DEV" | sed -n '/Keyslots:/,/Tokens:/p'
# Expected: one passphrase slot (argon2id, no token) + one Clevis slot (pbkdf2, token 0)

8. The sentinel file

The sentinel at /var/lib/kernel-update-tpm/state is a plain key=value file. Understanding it helps with monitoring and recovery.

Example sentinel after Phase 1

phase=2
luksdev=/dev/nvme0n1p3
strict_pcr_ids=0,1,2,4,5,7,8,9
created_at=2026-06-03T14:22:10+00:00
phase_started_at=2026-06-03T14:22:10+00:00
kernel_at_phase1=6.1.0-30-amd64
temp_slot=2
strict_slot_removed=1

Example sentinel after Phase 2

phase=3
luksdev=/dev/nvme0n1p3
strict_pcr_ids=0,1,2,4,5,7,8,9
created_at=2026-06-03T14:22:10+00:00
phase_started_at=2026-06-03T14:28:45+00:00
kernel_at_phase1=6.1.0-30-amd64
temp_slot=2
strict_slot_removed=1
new_strict_slot=3

Key fields

FieldMeaning
phaseNext phase to run. 2 = waiting for first reboot. 3 = waiting for verification reboot.
temp_slotThe loose PCR-7 slot created in Phase 1. Still present until Phase 3 removes it.
strict_slot_removedThe original strict slot that was removed in Phase 1. Informational only.
new_strict_slotThe new strict slot created in Phase 2. Present after Phase 2 completes.
kernel_at_phase1Kernel version at Phase 1. Phase 2 checks that the running kernel differs.
phase_started_atTimestamp updated at the start of each phase. Used for the age check.
Do not manually edit the sentinel unless performing recovery. The script trusts the slot numbers in the sentinel to remove the correct slots. An incorrect temp_slot value could cause the wrong slot to be removed.

9. Recovery procedures

First step for any failure: assess the state

# What phase are we in?
sudo cat /var/lib/kernel-update-tpm/state

# What Clevis slots currently exist?
sudo clevis luks list -d /dev/nvme0n1p3

# Is the systemd unit enabled?
sudo systemctl is-enabled kernel-update-tpm-resume.service 2>/dev/null || echo "not present"

# What does the journal show for the last unit run?
sudo journalctl -u kernel-update-tpm-resume.service -n 50 --no-pager

Phase 1 preflight failed: TPM unseal failed (PCRs changed)

The existing Clevis slot cannot unseal because PCR values have changed since the slot was created — for example, due to a hardware change, chassis intrusion detection event, firmware update, or a stale temp slot left by an interrupted prior run.

If you ran the script interactively, it offers a recovery prompt:

[r]  Re-enroll from LUKS passphrase — bind a new slot to the current
     PCR state, then remove the broken slot (no reboot required)
[q]  Quit — inspect TPM and PCR state, then re-run

Press r. Clevis will prompt for your LUKS passphrase, bind a new slot sealed to the current PCR state, verify it unseals, and remove the broken slot. No reboot is required. If the script was triggered non-interactively (e.g. by the APT hook), it fails hard; run it manually to get the interactive prompt:

sudo /usr/local/sbin/kernel-update-tpm.sh /dev/nvme0n1p3

If you want to understand why PCRs changed, read the values with tpm2_pcrread sha256:0,1,2,4,5,7,8,9 and compare against the values logged by a prior Phase 1 run in the journal.

Phase 1 cancelled (Ctrl-C before reboot)

The slot swap has already happened: the strict slot is gone and the temp slot exists. The sentinel and systemd unit are written. The system has not rebooted — it is still running the old kernel. You can either reboot manually to continue, or abort cleanly.

Option A — Continue (reboot and let Phase 2 run)

sudo reboot

Option B — Abort and restore strict slot

The system has not rebooted, so the current boot state is unchanged and the strict PCR policy is still valid. This reads the slot numbers from the sentinel so you do not need to look them up manually.

export DEV=/dev/nvme0n1p3
TEMP_SLOT=$(sudo awk -F= '/^temp_slot/{print $2}' /var/lib/kernel-update-tpm/state)
STRICT_PCR_IDS=$(sudo awk -F= '/^strict_pcr_ids/{print $2}' /var/lib/kernel-update-tpm/state)
STRICT_POLICY="{\"pcr_bank\":\"sha256\",\"pcr_ids\":\"${STRICT_PCR_IDS}\"}"

# Re-enroll strict slot from the temp slot's passphrase
sudo clevis luks pass -d "$DEV" -s "$TEMP_SLOT" | \
  sudo clevis luks bind -y -k - -d "$DEV" tpm2 "$STRICT_POLICY"

# Verify the new strict slot, then remove the temp slot
NEW_STRICT_SLOT=$(sudo clevis luks list -d "$DEV" | awk -F: '/tpm2/ {gsub(/^[ \t]+/,"",$1); print $1}' | sort -n | tail -1)
sudo clevis luks pass -d "$DEV" -s "$NEW_STRICT_SLOT" >/dev/null \
  && sudo clevis luks unbind -f -d "$DEV" -s "$TEMP_SLOT"
# Remove sentinel and unit
sudo systemctl disable kernel-update-tpm-resume.service 2>/dev/null || true
sudo rm -f /etc/systemd/system/kernel-update-tpm-resume.service
sudo systemctl daemon-reload
sudo rm -f /var/lib/kernel-update-tpm/state

Phase 2 aborted: kernel version unchanged

Phase 2 detected that uname -r matches the kernel recorded in the sentinel. The kernel update did not take effect. The temp slot is still present. The systemd unit is now disabled (boot-loop guard fired).

Run the script manually — it will detect the unchanged kernel and offer a choice:

sudo /usr/local/sbin/kernel-update-tpm.sh

# You will see:
# [kernel-update-tpm] Kernel is still X.X.X — same as Phase 1.
# [kernel-update-tpm] The strict slot is gone; temp slot (PCR 7, slot N) is still active.
#
#   [r]  Restore strict slot from temp slot and clean up (recommended)
#   [q]  Quit — leave temp slot in place, fix /boot manually, then reboot
#
# Choice [r/q]:

Option r — Restore immediately (no kernel update needed)

Press r. The script re-enrolls the strict slot against the current (unchanged) boot state using the temp slot as authorization, verifies it, removes the temp slot, and cleans up the sentinel and unit. No reboot required.

Option q — Fix boot config and retry the update

Press q if a kernel update really should have installed but did not. Investigate, fix, then retry:

# Investigate why the new kernel did not boot
ls -lh /boot/vmlinuz-* /boot/initrd.img-*
sudo update-grub
sudo grep menuentry /boot/grub/grub.cfg | head -10

# If the new kernel is present but not the default, correct it, then re-enable the unit
sudo systemctl enable kernel-update-tpm-resume.service

# Reset the phase_started_at timestamp so the age check passes
sudo sed -i "s|^phase_started_at=.*|phase_started_at=$(date --iso-8601=seconds)|" \
  /var/lib/kernel-update-tpm/state

sudo reboot

Phase 2 aborted: temp slot failed to unseal

The PCR-7 binding did not unseal in the new boot state. This is unexpected — PCR 7 should not change during a kernel update. A simultaneous MOK enrollment or Secure Boot key change is the likely cause. The strict slot is already gone. Unlock using the human LUKS passphrase.

# Unlock manually if required, then investigate PCR 7
sudo tpm2_pcrread sha256:7

# Read back what PCR 7 was when the temp slot was bound
# (it was sealed against the value at Phase 1 time — if MOK changed, it shifted)
sudo journalctl -u kernel-update-tpm-resume.service --no-pager | grep -i "pcr\|fail\|slot"

# If you need to re-enroll everything from scratch using the human passphrase:
export DEV=/dev/nvme0n1p3
STRICT_PCR_IDS=$(sudo awk -F= '/^strict_pcr_ids/{print $2}' /var/lib/kernel-update-tpm/state)
STRICT_POLICY="{\"pcr_bank\":\"sha256\",\"pcr_ids\":\"${STRICT_PCR_IDS}\"}"
TEMP_SLOT=$(sudo awk -F= '/^temp_slot/{print $2}' /var/lib/kernel-update-tpm/state)

# Enroll new strict slot using passphrase
sudo clevis luks bind -y -d "$DEV" tpm2 "$STRICT_POLICY"

# Remove the failed temp slot using the new strict slot
NEW_SLOT=$(sudo clevis luks list -d "$DEV" | awk -F: '/tpm2/{gsub(/^[ \t]+/,"",$1); print $1}' | sort -n | tail -1)
sudo clevis luks pass -d "$DEV" -s "$NEW_SLOT" | \
  sudo clevis luks unbind -d "$DEV" -s "$TEMP_SLOT"

# Clean up sentinel and unit
sudo systemctl disable kernel-update-tpm-resume.service 2>/dev/null || true
sudo rm -f /etc/systemd/system/kernel-update-tpm-resume.service
sudo systemctl daemon-reload
sudo rm -f /var/lib/kernel-update-tpm/state

Phase 3 aborted: new strict slot failed verification

The strict slot created in Phase 2 did not unseal on the Phase 3 boot. The temp slot is still present. The systemd unit is disabled. The PCR state has shifted again between Phase 2 and Phase 3 — this should not happen on a normal boot with no changes, so investigate carefully.

# Read current PCR values
sudo tpm2_pcrread sha256:0,1,2,4,5,7,8,9

# Check what policy the new strict slot was bound to
sudo clevis luks list -d /dev/nvme0n1p3

# The temp slot (PCR 7) is still working. Use it to re-enroll strict.
export DEV=/dev/nvme0n1p3
TEMP_SLOT=$(sudo awk -F= '/^temp_slot/{print $2}' /var/lib/kernel-update-tpm/state)
NEW_STRICT_SLOT=$(sudo awk -F= '/^new_strict_slot/{print $2}' /var/lib/kernel-update-tpm/state)
STRICT_PCR_IDS=$(sudo awk -F= '/^strict_pcr_ids/{print $2}' /var/lib/kernel-update-tpm/state)
STRICT_POLICY="{\"pcr_bank\":\"sha256\",\"pcr_ids\":\"${STRICT_PCR_IDS}\"}"

# Remove the stale strict slot from Phase 2
sudo clevis luks pass -d "$DEV" -s "$TEMP_SLOT" | \
  sudo clevis luks unbind -d "$DEV" -s "$NEW_STRICT_SLOT"

# Re-enroll strict against the current (Phase 3) boot state
sudo clevis luks pass -d "$DEV" -s "$TEMP_SLOT" | \
  sudo clevis luks bind -y -k - -d "$DEV" tpm2 "$STRICT_POLICY"

# Verify the new enrollment
FINAL_SLOT=$(sudo clevis luks list -d "$DEV" | awk -F: '/tpm2/{gsub(/^[ \t]+/,"",$1); print $1}' | sort -n | tail -1)
sudo clevis luks pass -d "$DEV" -s "$FINAL_SLOT" >/dev/null \
  && echo "New strict slot OK"

# Remove temp slot and clean up
sudo clevis luks pass -d "$DEV" -s "$FINAL_SLOT" | \
  sudo clevis luks unbind -d "$DEV" -s "$TEMP_SLOT"
sudo rm -f /etc/systemd/system/kernel-update-tpm-resume.service
sudo systemctl daemon-reload
sudo rm -f /var/lib/kernel-update-tpm/state

Age check expired

If the system was shut down (not rebooted) between phases, or if the unit ran more than 30 minutes after boot, the age check fires. When running interactively, the script offers a prompt — press c to reset the timer and continue, or q to abort:

sudo /usr/local/sbin/kernel-update-tpm.sh

# [kernel-update-tpm] Sentinel is 37 minutes old (max 30) — Phase 2 did not run promptly after reboot.
#
#   [c]  Reset the age timer and continue Phase 2 now
#   [q]  Quit and inspect /var/lib/kernel-update-tpm/state manually
#
# Choice [c/q]: c

If the unit fired non-interactively (systemd) and hard-aborted, reset manually and re-enable:

sudo sed -i "s|^phase_started_at=.*|phase_started_at=$(date --iso-8601=seconds)|" \
  /var/lib/kernel-update-tpm/state
sudo systemctl enable kernel-update-tpm-resume.service
sudo reboot

10. Uninstalling

Only uninstall when no run is in progress. Check for a sentinel first: ls /var/lib/kernel-update-tpm/state. If one exists, complete or abort the current run before uninstalling.
# Remove APT hook configuration (disables automatic triggering)
sudo rm -f /etc/apt/apt.conf.d/90-kernel-update-tpm.conf

# Remove scripts
sudo rm -f /usr/local/sbin/kernel-update-tpm.sh
sudo rm -f /usr/local/sbin/kernel-update-tpm-apt-hook

# Remove any leftover runtime artifacts
sudo systemctl disable kernel-update-tpm-resume.service 2>/dev/null || true
sudo rm -f /etc/systemd/system/kernel-update-tpm-resume.service
sudo systemctl daemon-reload
sudo rm -rf /var/lib/kernel-update-tpm/
sudo rm -f /etc/apt/apt.conf.d/99-kernel-update-tpm-reboot

# Logs are left in place for audit purposes — remove manually if desired
# sudo rm -f /var/log/kernel-update-tpm-apt-hook.log

Uninstalling does not affect the current Clevis enrollment. Whatever slots exist at uninstall time remain.

11. Quick reference

Key paths

PathPurpose
/usr/local/sbin/kernel-update-tpm.shMain script (Phases 1–3)
/usr/local/sbin/kernel-update-tpm-apt-hookAPT hook
/usr/local/sbin/update-clevis-pcrs.shLower-level helper for manual slot management
/etc/apt/apt.conf.d/90-kernel-update-tpm.confHook registration
/var/lib/kernel-update-tpm/stateSentinel file
/etc/systemd/system/kernel-update-tpm-resume.serviceResume unit (runtime)
/var/log/kernel-update-tpm-apt-hook.logHook log

Common commands

TaskCommand
Show help / usagesudo /usr/local/sbin/kernel-update-tpm.sh --help
Run manuallysudo /usr/local/sbin/kernel-update-tpm.sh
Check current phasesudo cat /var/lib/kernel-update-tpm/state
Watch Phase 2/3 journalsudo journalctl -f -u kernel-update-tpm-resume.service
Check hook logsudo tail -f /var/log/kernel-update-tpm-apt-hook.log
Confirm hook registeredapt-config dump | grep kernel-update-tpm
Check Clevis slotssudo clevis luks list -d /dev/nvme0n1p3
Reset age checksudo sed -i "s|^phase_started_at=.*|phase_started_at=$(date --iso-8601=seconds)|" /var/lib/kernel-update-tpm/state

Downloadable Scripts