23 KiB
Debian Bootupd Fork Plan
Project Overview
Goal: Create a Debian-compatible version of bootupd to Debian Atomic.
Context:
- Proof-of-concept: Test if we can create an immutable Debian using ublue-os tools
- Original bootupd is Red Hat/Fedora-centric with RPM dependencies
- Need to adapt for Debian OSTree immutable systems (not traditional Debian)
- Target: Debian Trixie slim container image converted to bootc format
- Critical Insight: This is a hybrid system - Debian packages within immutable OSTree structure
- Success metric: Can we boot a Debian-based immutable system?
Key Understanding: OSTree vs Traditional Debian
Traditional Debian (Mutable)
- Package-based updates via
apt/dpkg - Mutable filesystem with writable
/usr - Kernel in
/boot/vmlinuz-$kver - GRUB configured via
grub-mkconfig/update-grub
Particle-OS (Immutable OSTree)
- Image-based atomic updates via bootc
- Immutable
/usr(read-only core OS) - Kernel embedded in
/usr/lib/modules/$kver/vmlinuz - GRUB configured to point to OSTree deployment paths
- Configuration in
/etc(writable, three-way merge) - State in
/var(shared across deployments) - OSTree object store in
/ostree
Critical Context: How bootc-image-builder Uses bootupd
bootc-image-builder Uses bootupd as a Rust Crate Packaged into an RPM
This is a fundamental insight that shapes our entire implementation strategy:
1. Build-Time Integration (Not Runtime)
# During image build, bootc-image-builder:
1. Installs the rust-bootupd RPM package
2. RPM package contains the compiled Rust binary
3. Binary gets placed in /usr/libexec/bootupd
4. bootupctl symlink created in /usr/bin/ (multicall binary pattern)
5. Systemd services and GRUB configs installed
6. Final bootc image contains the compiled bootupd binary
2. Package-Based Distribution
From the Fedora RPM spec file, bootupd is distributed as:
rust-bootupd- The RPM package containing the compiled Rust binary- Source: The RPM build process compiles the Rust crate into a binary
- Result: A pre-compiled binary package, not source code
The bootc-image-builder installs the pre-built RPM package, which contains the compiled Rust binary.
3. Why This Matters for deb-bootupd
This is exactly what we need to replicate for Debian:
# debian-bootc-image-builder workflow:
1. Install deb-bootupd .deb package during image build
2. Package manager automatically places bootupd binary in /usr/libexec/bootupd and creates bootupctl symlink in /usr/bin/
3. Systemd services and GRUB configs installed
4. Final Debian bootc image contains deb-bootupd
4. Key Differences from Rust Crate Usage
| Aspect | Rust Crate | Fedora Package | Debian Package |
|---|---|---|---|
| Build Process | cargo install |
dnf install rust-bootupd |
apt install deb-bootupd |
| Binary Location | ~/.cargo/bin/ |
/usr/libexec/bootupd |
/usr/libexec/bootupd |
| System Integration | Manual setup | Automatic via RPM | Automatic via .deb |
| Dependencies | Rust deps only | System packages | System packages |
| Image Builder | Source compilation | Package installation | Package installation |
5. Why Package-Based Approach is Critical
- Reproducible Builds: Same binary every time, no compilation variations
- System Integration: Automatic service files, paths, and dependencies
- Security: Signed packages with proper dependency resolution
- Performance: No compilation time during image building
- Maintenance: Updates come through package management, not cargo
Conclusion
bootc-image-builder uses bootupd as a Rust crate packaged into an RPM. This means:
- deb-bootupd must be packaged as a Debian .deb package (using dh-cargo)
- debian-bootc-image-builder must install the .deb package during image build
- The .deb package contains the compiled Rust binary
- No Rust compilation happens during image building - binary is pre-compiled
This is why we're creating the Debian packaging files (debian/control, debian/rules) with dh-cargo - so that debian-bootc-image-builder can install deb-bootupd as a package, just like Fedora systems do with the rust-bootupd RPM package.
Critical Analysis: How bootupd is Actually Packaged
Based on the official bootupd repository, here's exactly how they package it:
1. RPM Packaging Structure (contrib/packaging/bootupd.spec)
Name: rust-%{crate} # Package name: rust-bootupd
Version: 0.2.9
BuildRequires: cargo-rpm-macros >= 25 # Uses Fedora's Rust packaging macros
Key Files Installed:
%{_bindir}/bootupctl→/usr/bin/bootupctl(symlink)%{_libexecdir}/bootupd→/usr/libexec/bootupd(main binary)%{_prefix}/lib/bootupd/grub2-static/→ GRUB configuration files%{_unitdir}/bootloader-update.service→ systemd service
2. Makefile Installation Pattern
install:
mkdir -p "${DESTDIR}$(PREFIX)/bin" "${DESTDIR}$(LIBEXECDIR)"
install -D -t "${DESTDIR}$(LIBEXECDIR)" target/${PROFILE}/bootupd
ln -f ${DESTDIR}$(LIBEXECDIR)/bootupd ${DESTDIR}$(PREFIX)/bin/bootupctl
Critical Insight: bootupctl is a symlink to the main bootupd binary, not a separate binary!
3. Multicall Binary Architecture
From the Makefile:
all:
cargo build ${CARGO_ARGS}
ln -f target/${PROFILE}/bootupd target/${PROFILE}/bootupctl
How it works:
- Single binary:
bootupdis compiled as one Rust binary - Symlink creation:
bootupctlis created as a symlink tobootupd - Runtime detection: The binary detects which name it was called as (
argv[0]) - Behavior switching: Different behavior based on the name (daemon vs client)
4. Why This Matters for deb-bootupd
Our current debian/rules is INCORRECT:
# Current (wrong):
ln -sf /usr/bin/bootupd debian/deb-bootupd/usr/bin/bootupctl
# Should be (correct):
ln -sf /usr/libexec/bootupd debian/deb-bootupd/usr/bin/bootupctl
Correct Debian packaging should:
- Install main binary:
/usr/libexec/bootupd(like RPM does) - Create symlink:
/usr/bin/bootupctl→/usr/libexec/bootupd - Follow RPM pattern: Mirror the exact file layout from the RPM spec
5. File Layout Comparison
| Component | RPM Location | Debian Location | Type |
|---|---|---|---|
| Main binary | /usr/libexec/bootupd |
/usr/libexec/bootupd |
Executable |
| CLI interface | /usr/bin/bootupctl |
/usr/bin/bootupctl |
Symlink |
| GRUB configs | /usr/lib/bootupd/grub2-static/ |
/usr/lib/bootupd/grub2-static/ |
Static files |
| Systemd service | /usr/lib/systemd/system/bootloader-update.service |
/etc/systemd/system/bootloader-update.service |
Service file |
Key insight: The binary goes in /usr/libexec/ (not /usr/bin/), and bootupctl is a symlink to it.
6. Critical Correction: bootupd is NOT a Daemon
From the official bootupd repository:
Is bootupd a daemon?
It was never a daemon. The name was intended to be "bootloader-upDater" not "bootloader-updater-Daemon". The choice of a "d" suffix is in retrospect probably too confusing.
What this means for deb-bootupd:
- No daemon process:
bootupdis a command-line tool that runs and exits - systemd integration: Uses
systemd-runfor locking and sandboxing, not as a persistent service - Event-driven: Triggered by systemd services or bootc hooks, not running continuously
- Multicall binary: Single executable that behaves differently based on how it's called
Phase 1: Project Setup & Structure
1.1 Create Debian Bootupd Directory Structure
deb-bootupd/
├── src/ # Source code (adapted from .Red_Hat_Version/bootupd/src/)
├── systemd/ # Systemd service files
├── tests/ # Test suite
├── Cargo.toml # Dependencies adapted for Debian
├── README.md # Debian-specific documentation
├── debian/ # Debian packaging files
└── scripts/ # Build and deployment scripts
1.2 Initialize Git Repository
- Fork from original bootupd repository
- Create debian branch
- Set up proper attribution and licensing
1.3 Git Strategy: Fork with Upstream Remote
Approach: Proper Git fork with upstream remote for sustainable development
Benefits:
- Clean start: No complex git history or upstream sync complexity
- Maintainable: Can easily pull upstream changes, security patches, and bug fixes
- Focus on core: Concentrate on making Debian immutable system bootable
- Proof-of-concept: Perfect for testing the concept while maintaining upstream compatibility
- Future-proof: Simplifies long-term maintenance and reduces technical debt
Implementation:
# Proper fork approach
git clone https://github.com/coreos/bootupd.git deb-bootupd
cd deb-bootupd
git remote rename origin upstream
git remote add origin <your-fork-url>
git checkout -b debian-adaptation
Alternative (if proper fork is too complex initially):
# Simple copy approach (higher maintenance risk)
cp -r .Red_Hat_Version/bootupd deb-bootupd/
cd deb-bootupd
git init
git add .
git commit -m "Initial Debian fork of bootupd for immutable Debian proof-of-concept"
Recommendation: Use the proper Git fork approach. While slightly more complex initially, it significantly reduces the long-term maintenance burden and makes it easier to incorporate upstream security patches and improvements.
Phase 2: Core Code Adaptation
2.1 Package System Integration (src/packagesystem.rs)
Current Issue: Hard dependency on RPM commands Solution: Rewrite for DPKG/APT
OSTree-Specific Considerations:
- Not traditional package management: Particle-OS uses image-based updates, not
apt/dpkg - Package queries: Still need DPKG integration for querying existing package metadata
- Update mechanism: Updates come from new bootc images, not package repositories
- Version tracking: Need to track Debian package versions within the immutable OS tree
Kernel Detection Challenge:
- File location:
/usr/lib/modules/*/vmlinuz(same as Red Hat) - Filename parsing: Must handle Debian kernel naming conventions
- Critical files:
src/efi.rsandsrc/bios.rskernel detection logic - Example difference:
vmlinuz-6.1.0-13-amd64vsvmlinuz-6.1.0-1.fc38.x86_64
Changes Required:
// Replace RPM-specific code:
// OLD: rpm -q --queryformat "%{nevra},%{buildtime}" -f <file>
// NEW: dpkg -S <file> && dpkg -s <package> | grep "Installed-Time"
// Replace RPM database paths:
// OLD: usr/lib/sysimage/rpm, usr/share/rpm
// NEW: var/lib/dpkg, usr/share/doc
Implementation:
- Create
dpkg_parse_metadata()function - Parse DPKG output format instead of RPM
- Handle Debian package versioning conventions
- Support both
dpkg -Sanddpkg -squeries
2.2 OSTree Utilities (src/ostreeutil.rs)
Current Issue: RPM command construction Solution: Adapt for Debian package database structure
Changes Required:
- Replace
rpm_cmd()withdpkg_cmd() - Update database path detection logic
- Maintain OSTree integration compatibility
2.3 CoreOS Integration (src/coreos.rs)
Current Issue: Fedora CoreOS-specific Aleph version parsing Solution: Adapt for Debian/particle-os versioning
Changes Required:
- Replace
.coreos-aleph-version.jsonwith Debian-specific version file - Update version parsing logic for Debian conventions
- Remove Fedora-specific test data references
2.4 Hardcoded Paths
Current Issues:
/usr/lib/bootupd/updates(hardcoded inmodel.rs)/bootstate directory (hardcoded instatefile.rs)/run/bootupd-locklock file path
Solution: Make paths configurable or follow Debian conventions
- Consider using
/var/lib/bootupd/updates(FHS compliant) - Keep
/bootfor state (standard across distributions) - Keep
/runfor locks (standard across distributions)
2.5 OSTree-Specific Adaptations
Critical Changes for Debian OSTree:
Kernel Path Adaptation:
// Current (Red Hat): /usr/lib/modules/*/vmlinuz
// Debian OSTree: /ostree/deploy/debian/deploy/$checksum.0/usr/lib/modules/*/vmlinuz
// Need to ensure this works with Debian kernel naming conventions
// Critical: bootupd must correctly resolve the nested OSTree deployment path
Kernel Filename Parsing (Key Challenge):
- Red Hat convention:
vmlinuz-6.1.0-1.fc38.x86_64 - Debian convention:
vmlinuz-6.1.0-13-amd64 - Critical: Filename parsing logic must handle Debian version format
- Location: This affects
src/efi.rsandsrc/bios.rskernel detection
Bootloader Configuration:
- Current: GRUB configs from coreos-assembler
- Debian OSTree: GRUB configs pointing to OSTree deployment paths
- Path format:
/ostree/deploy/debian/deploy/$checksum.0/vmlinuz
State Management:
- Current: State in
/boot/bootupd-state.json - Debian OSTree: State must persist across OSTree deployments
- Location: Consider
/var/lib/bootupd/for persistent state
Phase 3: System Integration
3.1 System Commands
Current Dependencies:
efibootmgr(EFI boot management)mount/umount(filesystem operations)grub-common(GRUB configuration tools)
Debian Compatibility:
- ✅
efibootmgr: Available in Debian repositories - ✅
mount/umount: Standard Linux tools - ✅
grub-common: Available in Debian repositories
Action: Ensure these packages are available in particle-os base image
Note: bootupd's primary role is updating bootloader configuration files and entries, not running a full grub-install. In an immutable OSTree system, the core GRUB files are part of the image itself. bootupd uses tools like efibootmgr and GRUB configuration manipulation to point to the correct deployment.
3.2 Systemd Integration
Current: Hard dependency on libsystemd
Debian: ✅ Fully supports systemd
Action: No changes needed, maintain compatibility
3.3 OS Detection
Current: Red Hat-specific logic Solution: Enhance OS detection for Debian
Changes Required:
// Current logic in efi.rs:
// 1. Try /etc/system-release (Red Hat specific)
// 2. Fall back to /etc/os-release
// Enhanced logic:
// 1. Try /etc/os-release first (standard)
// 2. Parse for Debian family distributions
// 3. Fall back to /etc/system-release for Red Hat
// 4. Handle Debian-specific version formats
Phase 4: Debian-Specific Features
4.1 Package Manager Integration
New Features:
- APT package discovery integration
- Debian package version comparison
- Support for Debian backports and testing repositories
OSTree-Specific Features:
- Image-based update detection: Detect new bootc images vs package updates
- Deployment coordination: Work with OSTree deployment system
- Rollback support: Leverage OSTree's built-in rollback capabilities
- State persistence: Ensure bootupd state survives across deployments
Trigger Mechanism: bootupd is not a standalone daemon that runs on a schedule. It's triggered by systemd services (like bootupd-post-upgrade.service) or bootc hooks that run after a new OSTree deployment is committed. This event-driven approach ensures bootloader updates happen at the right time in the deployment lifecycle.
4.2 Debian OSTree Integration
Unique Challenges:
- Hybrid approach: Debian packages within immutable OSTree system
- Update workflow: New images contain updated Debian packages
- Version compatibility: Ensure Debian package versions work with OSTree structure
- Bootloader updates: Coordinate with OSTree deployment changes
4.3 Distribution-Specific Bootloader Configs
Current: Static GRUB configs from coreos-assembler Solution: Create Debian-specific GRUB configurations
- Support for Debian kernel naming conventions
- Debian-specific boot parameters
- Integration with Debian initramfs tools
- OSTree path integration: GRUB configs pointing to OSTree deployment paths
4.4 Debian Package Dependencies
Update Cargo.toml:
- Keep core dependencies (anyhow, clap, serde, etc.)
- Maintain systemd integration
- Add Debian-specific optional features
Phase 5: Testing & Validation
5.1 Test Suite Adaptation
Current: Fedora/RPM-centric tests Solution: Create Debian-compatible test suite
Test Categories:
- Unit tests for DPKG parsing functions
- Integration tests with Debian package databases
- End-to-end tests in Debian container environments
- Bootloader update simulation tests
5.2 Particle-OS Integration Testing
Test Scenarios:
- Fresh particle-os installation
- Bootloader adoption from existing Debian systems
- Update process validation
- Rollback functionality testing
OSTree-Specific Testing:
- Deployment switching: Test bootloader updates across OSTree deployments
- State persistence: Verify bootupd state survives deployment switches
- Image updates: Test with new bootc images containing updated Debian packages
- Rollback scenarios: Test bootloader rollback with OSTree deployment rollback
- Hybrid updates: Test scenarios where Debian packages and OSTree images are updated
Phase 6: Packaging & Deployment
6.1 Debian Package Creation
Files Required:
debian/control(package metadata)debian/rules(build instructions)debian/changelog(version history)debian/postinst(post-installation scripts)
Build System Integration:
- dh-cargo: Use Debian's Rust packaging helper
- Benefits: Automates cargo build, cargo install, and dependency handling
- debian/rules: Simplified with dh-cargo integration
- Cargo.lock: Include for reproducible builds
- Vendor directory: Consider including for offline builds
Important: The Rust compilation happens when the .deb package is built (using dh_cargo build --release), not during the debian-bootc-image-builder process. The image builder simply installs the resulting pre-compiled binary via the .deb package.
6.2 Container Integration
Integration Points:
- particle-os base image requirements
- Bootc image builder integration
- Container runtime compatibility
6.3 Bootc Image Builder Integration
Critical Integration Points:
Build Process Integration:
- Include deb-bootupd: Binary must be installed as a .deb package during image build
- Build timing: deb-bootupd package installed during image build, not compiled from source
- Dependencies: Ensure all required tools (
efibootmgr,grub-install) are in base image
Image Builder Workflow:
- Build phase: Install deb-bootupd .deb package using debian-bootc-image-builder
- Installation: Package manager automatically places binary in
/usr/libexec/bootupdand creates symlinks - Configuration: Set up initial bootloader configuration
- First boot: deb-bootupd runs to adopt existing bootloader or install new one
Key Insight: This mirrors exactly how Fedora bootc-image-builder works - it installs the rust-bootupd RPM package, which contains the compiled Rust binary. We must replicate this pattern with Debian packaging using dh-cargo.
debian/control Integration:
- Build dependencies: Use
dh-cargofor Rust build system integration - Runtime dependencies: Ensure
efibootmgr,grub-commonare available - Package naming: Consider
deb-bootupdvsbootupdpackage name
Phase 7: Documentation & Maintenance
7.1 Documentation Updates
Required Documents:
- Debian-specific README
- Installation guide for particle-os
- Troubleshooting guide
- Migration guide from Red Hat systems
7.2 Maintenance Strategy
Ongoing Tasks:
- Track upstream bootupd changes
- Adapt new features for Debian
- Maintain Debian package compatibility
- Community support and bug fixes
Implementation Priority
High Priority (Phase 1-2) - Proof-of-Concept Core
- Package system adaptation (RPM → DPKG) - Essential for Debian compatibility
- Debian packaging creation - Create .deb package for debian-bootc-image-builder integration
- Fix binary layout - Correct file locations to match RPM pattern (
/usr/libexec/bootupd, symlink forbootupctl) - Core path and dependency fixes - Make it compile and run
- Basic Debian compatibility - Get it working on Debian system
Medium Priority (Phase 3-4) - Basic Functionality
- Enhanced OS detection - Proper Debian identification
- Debian-specific features - Basic bootloader management
- Testing infrastructure - Verify it works
Low Priority (Phase 5-7) - Polish & Production
- Advanced features - Only if proof-of-concept succeeds
- Documentation - Only if proof-of-concept succeeds
- Long-term maintenance - Only if proof-of-concept succeeds
Success Criteria
Proof-of-Concept Success (Phases 1-2)
- Debian bootupd compiles successfully - Basic Rust compilation
- Package system queries work with DPKG - Can read Debian package info
- Basic bootloader detection works - Can identify EFI/BIOS systems
Core Functionality Success (Phase 3-4)
- Bootloader management works on Debian - Can install/update GRUB
- OSTree integration functions - Works with Debian OSTree deployment
- State management works - Can track bootloader state
Full Success (Phase 5+)
- Particle-OS image boots successfully - Proof-of-concept achieved!
- Bootloader updates work end-to-end - Can update from new images
- Rollback functionality works - Can rollback to previous state
Ultimate Goal: Boot a Debian-based immutable system using ublue-os tools
Risk Mitigation
Technical Risks
- Package format differences: RPM vs DPKG complexity
- Path conventions: Red Hat vs Debian filesystem standards
- Tool availability: Ensuring required tools exist in Debian
- OSTree integration complexity: Coordinating with Debian OSTree deployment system
- Hybrid system challenges: Debian packages within immutable OSTree structure
Mitigation Strategies
- Incremental development: Test each component individually
- Fallback mechanisms: Graceful degradation when tools unavailable
- Comprehensive testing: Multiple Debian versions and environments
- Upstream independence: Build Debian-specific features without upstream dependencies
- Maintenance planning: Regular subtree pulls to stay current with upstream fixes
Next Steps
- Immediate: Set up deb-bootupd directory structure
- Week 1: Adapt package system integration (RPM → DPKG)
- Week 2: Create Debian packaging files (.deb package)
- Week 3: Fix hardcoded paths and dependencies
- Week 4: Basic testing and validation
- Week 5: Particle-OS integration testing with debian-bootc-image-builder
Critical: Focus on creating a working .deb package first, as this is how debian-bootc-image-builder will integrate with deb-bootupd (mirroring the Fedora RPM approach).
Resources & References
- Original bootupd:
.Red_Hat_Version/bootupd/ - Debian packaging: https://www.debian.org/doc/manuals/debmake-doc/
- FHS standards: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/
- Debian bootc tools:
debian-bootc-image-builder/