deb-bootupd/deb-bootupd.md
robojerk 95c23891b6
Some checks failed
Build deb-bootupd Artifacts / Build deb-bootupd Artifacts (push) Failing after 1m51s
Simple Build & Upload / build (push) Failing after 1m20s
Add Forgejo Actions workflows for automated builds and artifact uploads
- Add comprehensive build-artifacts.yml workflow with Forgejo Package Registry upload
- Add simple-build.yml workflow for basic artifact management
- Update README.md with workflow documentation and setup instructions
- Fix debian/rules to correctly create bootupctl symlink to /usr/libexec/bootupd
- Improve error handling and validation throughout the codebase
- Remove unused functions and imports
- Update documentation to clarify bootupd is not a daemon
- Fix binary layout to match RPM packaging pattern
2025-08-09 23:38:01 -07:00

23 KiB

Debian Bootupd Fork Plan

Project Overview

Goal: Create a Debian-compatible version of bootupd to make particle-os (Debian-based ublue-os) bootable.

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

  1. Reproducible Builds: Same binary every time, no compilation variations
  2. System Integration: Automatic service files, paths, and dependencies
  3. Security: Signed packages with proper dependency resolution
  4. Performance: No compilation time during image building
  5. 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:

  1. Single binary: bootupd is compiled as one Rust binary
  2. Symlink creation: bootupctl is created as a symlink to bootupd
  3. Runtime detection: The binary detects which name it was called as (argv[0])
  4. 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:

  1. Install main binary: /usr/libexec/bootupd (like RPM does)
  2. Create symlink: /usr/bin/bootupctl/usr/libexec/bootupd
  3. 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: bootupd is a command-line tool that runs and exits
  • systemd integration: Uses systemd-run for 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.rs and src/bios.rs kernel detection logic
  • Example difference: vmlinuz-6.1.0-13-amd64 vs vmlinuz-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 -S and dpkg -s queries

2.2 OSTree Utilities (src/ostreeutil.rs)

Current Issue: RPM command construction Solution: Adapt for Debian package database structure

Changes Required:

  • Replace rpm_cmd() with dpkg_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.json with 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 in model.rs)
  • /boot state directory (hardcoded in statefile.rs)
  • /run/bootupd-lock lock file path

Solution: Make paths configurable or follow Debian conventions

  • Consider using /var/lib/bootupd/updates (FHS compliant)
  • Keep /boot for state (standard across distributions)
  • Keep /run for 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.rs and src/bios.rs kernel 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:

  1. Build phase: Install deb-bootupd .deb package using debian-bootc-image-builder
  2. Installation: Package manager automatically places binary in /usr/libexec/bootupd and creates symlinks
  3. Configuration: Set up initial bootloader configuration
  4. 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-cargo for Rust build system integration
  • Runtime dependencies: Ensure efibootmgr, grub-common are available
  • Package naming: Consider deb-bootupd vs bootupd package 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

  1. Package system adaptation (RPM → DPKG) - Essential for Debian compatibility
  2. Debian packaging creation - Create .deb package for debian-bootc-image-builder integration
  3. Fix binary layout - Correct file locations to match RPM pattern (/usr/libexec/bootupd, symlink for bootupctl)
  4. Core path and dependency fixes - Make it compile and run
  5. Basic Debian compatibility - Get it working on Debian system

Medium Priority (Phase 3-4) - Basic Functionality

  1. Enhanced OS detection - Proper Debian identification
  2. Debian-specific features - Basic bootloader management
  3. Testing infrastructure - Verify it works

Low Priority (Phase 5-7) - Polish & Production

  1. Advanced features - Only if proof-of-concept succeeds
  2. Documentation - Only if proof-of-concept succeeds
  3. 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

  1. Package format differences: RPM vs DPKG complexity
  2. Path conventions: Red Hat vs Debian filesystem standards
  3. Tool availability: Ensuring required tools exist in Debian
  4. OSTree integration complexity: Coordinating with Debian OSTree deployment system
  5. Hybrid system challenges: Debian packages within immutable OSTree structure

Mitigation Strategies

  1. Incremental development: Test each component individually
  2. Fallback mechanisms: Graceful degradation when tools unavailable
  3. Comprehensive testing: Multiple Debian versions and environments
  4. Upstream independence: Build Debian-specific features without upstream dependencies
  5. Maintenance planning: Regular subtree pulls to stay current with upstream fixes

Next Steps

  1. Immediate: Set up deb-bootupd directory structure
  2. Week 1: Adapt package system integration (RPM → DPKG)
  3. Week 2: Create Debian packaging files (.deb package)
  4. Week 3: Fix hardcoded paths and dependencies
  5. Week 4: Basic testing and validation
  6. 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