A minimal, security-focused initramfs generator for Linux systems with full disk encryption. Written in Go, Vanguard creates lightweight boot images optimized for LUKS + LVM + TPM2 setups with PCRLock boot integrity enforcement.
- Full Disk Encryption — LUKS2 with TPM2 automatic unlock via systemd-cryptenroll
- Native Go TPM2 — Zero external dependencies; uses
google/go-tpmfor sealed key unseal with traditional PCR policy and PolicyAuthorizeNV (pcrlock) - Native Go LUKS — LUKS v1/v2 header parsing, Argon2/PBKDF2 key derivation, and dm-crypt mapper setup via ioctl — no libcryptsetup required at runtime
- TPM2 Integration — Automatic token detection, PIN support, PCRLock policy binding with multi-branch PCR prediction (PolicyOR)
- LVM Support — Full LVM2 volume group and logical volume activation with persistent symlinks across switch_root
- Non-Root Filesystem Mounting — Mounts
/homeand other non-root filesystems from fstab before switch_root, bypassing udev database corruption issues - Boot TUI — Bubble Tea-based terminal UI with spinner, stage progress, and password/PIN prompts during boot
- GPT Autodiscovery — Automatic root partition detection using Discoverable Partitions Specification
- Hibernate/Resume — Support for resuming from encrypted swap (inside LUKS+LVM)
- Filesystem Check — Optional fsck before mounting root
- Vconsole Support — Keyboard layout and console font configuration for password prompts
- Minimal Footprint — Only includes binaries and modules needed for your configuration
- Fast Boot — zstd compression, parallel device scanning
- Self-contained — CLI embeds pre-built init binaries; no CGo in init
# Build from source
git clone https://github.com/zaolin/vanguard
cd vanguard
make
# Check system protection status
sudo ./vanguard status
# Generate initramfs
sudo ./vanguard generate -o /boot/initramfs-linux.img
# With debug output enabled
sudo ./vanguard generate -o /boot/initramfs-linux.img --debug
# Strict mode — token-only unlock, no passphrase fallback
sudo ./vanguard generate -o /boot/initramfs-linux.img -s| Document | Description |
|---|---|
| Boot Flow | Detailed initramfs boot sequence |
| Configuration | CLI options and config file format |
| TPM2 Setup | TPM2 enrollment and PCRLock guide |
| Kernel Parameters | Supported kernel command line options |
Generate an initramfs image:
vanguard generate -o /boot/initramfs-linux.img [options]| Option | Description |
|---|---|
-o, --output |
Output path (required) |
-f, --firmware |
Comma-separated firmware files |
-m, --modules |
Comma-separated kernel modules |
-c, --compression |
zstd, gzip, or none (default: zstd) |
-d, --debug |
Enable verbose boot output |
-s, --strict |
Strict mode: token-only unlock, no passphrase fallback |
--config |
Path to TOML config file |
Update TPM2 PCRLock policy for secure boot:
vanguard update -u /boot/EFI/Gentoo/kernel.efi [options]| Option | Description |
|---|---|
-u, --uki-path |
Path to UKI file (required) |
-p, --policy-output |
Output path for policy JSON (default: <uki-path>.pcrlock.json) |
-l, --luks-device |
LUKS device for token verification (enables GPT binding on PCR 5) |
--no-gpt |
Disable GPT partition table binding (PCR 5) |
--no-verify |
Skip policy verification |
-v, --verbose |
Show verbose output from pcrlock tools |
-c, --cleanup |
Remove old unused pcrlock NV indices from TPM |
What it does:
- Configure masks — Masks noisy/unpredictable PCR components (firmware code/config, OS separator, NV-PCR separator, machine ID, root filesystem, shutdown, final). These systemd-specific components expect userspace PCR measurements that vanguard's custom init does not produce; unmasked they cause
systemd-pcrlock make-policyto drop all PCRs from the protection mask. - Regenerate firmware components — Runs
systemd-pcrlock lock-firmware-codeandlock-firmware-config(or the VarlinkLockmethod on systemd 262+) to refresh stale/var/lib/pcrlock.d/250-firmware-*-early.pcrlock.d/generated.pcrlockfiles from the current boot's event log. Without this, stale firmware components cause a cascade that drops all PCRs including PCR 7. - Lock Secure Boot — Runs
lock-secureboot-policy+lock-secureboot-authority(or VarlinkLock) to generate PCR 7 component files from current Secure Boot state. - Lock GPT (with
-l) — Binds the policy to the disk's GPT partition layout (PCR 5). - Lock UKI — Creates multi-branch PCR 4 predictions for the UKI (lock-pe + lock-uki + event log extraction).
- Make policy — Runs
systemd-pcrlock make-policyto generate the final policy JSON with a recovery PIN. - Verify — Checks that required PCRs (7, and optionally 5) are present in the generated policy.
Verify TPM2 pcrlock setup (PCRs, NV Index, LUKS token):
vanguard verify -p /boot/pcrlock.json [options]| Option | Description |
|---|---|
-p, --policy-path |
Path to pcrlock.json policy file (required) |
-l, --luks-device |
LUKS device to verify (optional) |
This checks:
- NV Index synchronization (TPM matches policy file)
- Current PCR values against policy expectations
- LUKS token validation (when
-lspecified)
Show system protection status with security layering:
vanguard status [options]| Option | Description |
|---|---|
--json |
Machine-readable JSON output |
-v, --verbose |
Show full PCR hash values |
Shows protection tier (HIGH/MEDIUM/LOW/WARNING), LUKS encryption status, TPM 2.0 presence and lockout state, PCRLock boot integrity with per-PCR classification (enforced/unbound), and Secure Boot (PCR 7) detection.
Vanguard has a dual-binary design:
cmd/vanguard/— CLI tool running on the build host, generates initramfs imagesinit/— Init binary running inside the initramfs at boot (19-step sequence)internal/— Shared libraries: native Go TPM2 client, native Go LUKS implementation, CPIO archive writer, compression, pcrlock integration
Build produces 4 init variants via Go build tags from a single source tree:
| Tag(s) | Binary | Behavior |
|---|---|---|
| (none) | init |
Release: minimal output, passphrase fallback |
debug |
init-debug |
Verbose output for troubleshooting |
strict |
init-strict |
Token-only unlock, no passphrase fallback |
debug,strict |
init-debug-strict |
Verbose + strict mode |
The generator outputs a chained CPIO: an uncompressed early archive for firmware (available to built-in kernel drivers) followed by a zstd/gzip-compressed main archive. All init binaries are statically linked (CGO_ENABLED=0) with zero runtime dependencies.
No external binaries needed at boot — LUKS unlock and TPM2 operations use native Go implementations. The only external binaries included in the initramfs are lvm (for LVM activation), systemd-udevd/udevadm (device management), and dmsetup (DM rules).
Create /etc/vanguard.toml:
output = "/boot/initramfs-linux.img"
compression = "zstd"
debug = false
firmware = [
"amd/amd_sev.fw",
"amdgpu/vangogh_sos.bin",
]
modules = [
"nvme",
"xhci_pci",
]/dev/nvme0n1
├── /dev/nvme0n1p1 ESP (FAT32, ~512MB) — EFI System Partition
└── /dev/nvme0n1p2 LUKS encrypted — Contains LVM
└── LVM PV
└── VG: gentoo
├── LV: root (ext4/xfs)
├── LV: home (ext4/xfs)
└── LV: swap
# Generate PCRLock policy (Secure Boot + UKI + GPT binding)
sudo vanguard update -u /boot/EFI/Gentoo/kernel.efi -l /dev/nvme0n1p2
# Enroll TPM2 token with pcrlock binding
sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto \
--tpm2-with-pin=yes \
--tpm2-pcrlock=/boot/EFI/Gentoo/kernel.pcrlock.json \
/dev/nvme0n1p2sudo vanguard generate -o /boot/initramfs-linux.imgroot=/dev/gentoo/root resume=/dev/gentoo/swap
See docs/kernel-parameters.md for all supported parameters.
sudo vanguard status┌──────────────────────────────────────────────────────────────────┐
│ 1. Console Setup Set up early console I/O │
│ 2. Mount Filesystems /proc, /sys, /dev, /run │
│ 3. Vconsole Config Load keyboard layout/font │
│ 4. Mount /boot Mount boot partition for policy │
│ 5. Init Boot Log Start logging to /boot │
│ 6. Start udevd Device discovery daemon │
│ 7. Load Modules Kernel modules from image │
│ 8. Trigger udev Events Firmware loading │
│ 9. Load TPM Modules tpm_crb, tpm_tis, tpm_tis_core │
│ 10. Setup PCRLock Copy pcrlock.json for TPM2 │
│ 11. Unlock LUKS TPM2 → PIN → Passphrase │
│ 12. Activate LVM Scan and activate volumes │
│ 13. Try Resume Hibernate resume from swap │
│ 14. Find Root Device cmdline → fstab → GPT autodiscovery │
│ 15. fsck Check root filesystem │
│ 16. Mount Root Mount to /sysroot │
│ 16a. Mount Non-Root FS Mount /home etc. from fstab │
│ 16b. LVM Symlinks Create persistent symlinks │
│ 17. Cleanup udev Settle, trigger graphics, stop │
│ 18. Close Boot Log Close log, unmount /boot │
│ 19. Switch Root Hand off to real init │
└──────────────────────────────────────────────────────────────────┘
See docs/boot-flow.md for detailed documentation.
Build Requirements:
- Go 1.25+
- make
Runtime Dependencies (included in initramfs):
- lvm2
- systemd-udevd
- systemd-pcrlock (for policy generation only, not runtime)
Optional (for fwupd coexistence on systemd 262+):
- systemd 262+ — enables the
io.systemd.PCRLockVarlink interface forLock/MakePolicycalls. On older systemd, vanguard falls back to thesystemd-pcrlockCLI automatically. - fwupd 2.1.7+ — the
systemd-pcrlockfwupd plugin loosens systemd's pcrlock policy before firmware updates. Vanguard's policy is separate (different NV index) and must be regenerated manually before rebooting after a firmware update.
No external TPM or LUKS dependencies — Vanguard's init binary uses native Go implementations for all crypto and TPM2 operations.
# Run QEMU test with software TPM
./scripts/qemu-test.sh all-tpm
# Individual steps
./scripts/qemu-test.sh build # Build initramfs
./scripts/qemu-test.sh disk # Create test disk
./scripts/qemu-test.sh enroll-tpm # Enroll TPM2 token
./scripts/qemu-test.sh tpm # Boot with TPM
# Unit tests (no TPM hardware required)
go test ./internal/pcrlock/... -v # Varlink client + pcrlock integration
go test ./internal/tpm/... -v # TPM2 policy computation + auth
go test ./internal/luks/... -v # LUKS header parsing
go test ./init/... -v # Init token parsing| Layer | Mechanism |
|---|---|
| Disk Encryption | LUKS2 (aes-xts-plain64 or aes-xts-essiv:sha256) with Argon2id/PBKDF2 key derivation |
| TPM Binding | Sealed keys bound to TPM with PCR policy enforcement |
| PIN Protection | PBKDF2-derived authentication value for TPM2 tokens |
| Boot Integrity | PCRLock with PolicyAuthorizeNV — enforces expected PCR values before unlock |
| Strict Mode | Disables passphrase fallback when TPM2 token is present |
| Process Isolation | Static Go binary init (CGO_ENABLED=0) — no dynamic linking vulnerabilities |
| Minimal Surface | Only essential binaries and modules included; no interpreters or package managers |
| PCR | Name | Enforcement |
|---|---|---|
| 2 | external-code | ✓ Enforced |
| 3 | external-config | ✓ Enforced |
| 4 | boot-loader-code | ✓ Enforced (multi-branch PolicyOR) |
| 5 | GPT partition table | Optional (-l flag) |
| 7 | secure-boot-policy | ✓ Enforced |
| 13 | sysexts | — Unbound (all-zeros) |
| 14 | shim-policy | — Unbound (all-zeros) |
- TPM dictionary attack lockout detection prevents brute-force PIN attacks
- Kernel message suppression during password entry
- Passphrase fallback with 3 attempts before halt (unless strict mode)
- Boot logging to
/boot/.vanguard.logfor audit trail
MIT License — see LICENSE for details.
Contributions welcome! Please open an issue or pull request on GitHub.