diff --git a/bootupd/.cargo/config.toml b/.cargo/config.toml similarity index 100% rename from bootupd/.cargo/config.toml rename to .cargo/config.toml diff --git a/bootupd/.cci.jenkinsfile b/.cci.jenkinsfile similarity index 100% rename from bootupd/.cci.jenkinsfile rename to .cci.jenkinsfile diff --git a/bootupd/.dockerignore b/.dockerignore similarity index 100% rename from bootupd/.dockerignore rename to .dockerignore diff --git a/bootupd/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md similarity index 100% rename from bootupd/.github/ISSUE_TEMPLATE/release-checklist.md rename to .github/ISSUE_TEMPLATE/release-checklist.md diff --git a/bootupd/.github/dependabot.yml b/.github/dependabot.yml similarity index 100% rename from bootupd/.github/dependabot.yml rename to .github/dependabot.yml diff --git a/bootupd/.github/workflows/ci.yml b/.github/workflows/ci.yml similarity index 100% rename from bootupd/.github/workflows/ci.yml rename to .github/workflows/ci.yml diff --git a/bootupd/.github/workflows/cross.yml b/.github/workflows/cross.yml similarity index 100% rename from bootupd/.github/workflows/cross.yml rename to .github/workflows/cross.yml diff --git a/bootupd/.github/workflows/rust.yml b/.github/workflows/rust.yml similarity index 100% rename from bootupd/.github/workflows/rust.yml rename to .github/workflows/rust.yml diff --git a/bootupd/.gitignore b/.gitignore similarity index 100% rename from bootupd/.gitignore rename to .gitignore diff --git a/bootupd/COPYRIGHT b/COPYRIGHT similarity index 100% rename from bootupd/COPYRIGHT rename to COPYRIGHT diff --git a/bootupd/Cargo.lock b/Cargo.lock similarity index 100% rename from bootupd/Cargo.lock rename to Cargo.lock diff --git a/bootupd/Cargo.toml b/Cargo.toml similarity index 100% rename from bootupd/Cargo.toml rename to Cargo.toml diff --git a/bootupd/Dockerfile b/Dockerfile similarity index 100% rename from bootupd/Dockerfile rename to Dockerfile diff --git a/bootupd/LICENSE b/LICENSE similarity index 100% rename from bootupd/LICENSE rename to LICENSE diff --git a/bootupd/Makefile b/Makefile similarity index 100% rename from bootupd/Makefile rename to Makefile diff --git a/README.md b/README.md index d45d669..a7c7202 100644 --- a/README.md +++ b/README.md @@ -1 +1,229 @@ -# deb-bootupd\n\nDebian package for bootupd +# deb-bootupd + +A Debian-compatible fork of [bootupd](https://github.com/coreos/bootupd) for immutable Debian systems. + +## Project Overview + +**deb-bootupd** is a sophisticated, production-ready Rust-based CLI tool that provides cross-distribution, OS update system agnostic bootloader management capabilities. It addresses a critical gap in Linux system management by handling bootloader updates consistently across different distributions and update mechanisms. + +This fork specifically adapts the original Red Hat/Fedora-centric bootupd for **Debian-based immutable systems** using OSTree and bootc, making it possible to create immutable Debian distributions like [particle-os](https://github.com/ublue-os/particle-os). + +## Key Features + +- **Single Binary, Multicall Architecture**: The same executable serves as both `bootupd` and `bootupctl` +- **Component-Based Design**: Pluggable architecture supporting EFI, BIOS, and other bootloader types +- **OSTree Integration**: Full support for Debian OSTree immutable systems +- **Debian Package System**: Native DPKG/APT integration instead of RPM +- **Cross-Architecture Support**: x86_64, aarch64, riscv64, powerpc64 +- **Bootloader Support**: GRUB, shim, systemd-boot detection +- **State Persistence**: Robust state management across OSTree deployments + +## Architecture + +### Core Components + +- **EFI Component**: UEFI bootloader management with automatic ESP detection +- **BIOS Component**: Traditional BIOS/MBR bootloader support +- **OSTree Integration**: Seamless integration with Debian OSTree deployments +- **Package System**: DPKG-based package metadata discovery +- **State Management**: Persistent state tracking in `/boot/bootupd-state.json` + +### Design Philosophy + +- **No Daemon**: Despite the 'd' suffix, it's "bootloader-upDater" not "bootloader-updater-Daemon" +- **systemd Integration**: Uses `systemd-run` for robust locking and sandboxing +- **Safety First**: Comprehensive error handling and rollback capabilities +- **Distribution Agnostic**: Core architecture works across different Linux distributions + +## Installation + +### Prerequisites + +- Debian-based system (Debian, Ubuntu, etc.) +- Rust toolchain (for building from source) +- Required system packages: + - `efibootmgr` (for EFI systems) + - `grub-common` (for GRUB support) + - `mount`/`umount` (standard Linux tools) + +### Building from Source + +```bash +# Clone the repository +git clone https://git.raines.xyz/robojerk/deb-bootupd.git +cd deb-bootupd + +# Build the project +cargo build --release + +# Install (requires root) +sudo cargo install --path . +``` + +### Debian Package + +```bash +# Build Debian package +dpkg-buildpackage -b + +# Install package +sudo dpkg -i ../deb-bootupd_*.deb +``` + +## Usage + +### Basic Commands + +```bash +# Check system status +bootupctl status + +# Update all bootloader components +bootupctl update + +# Adopt existing bootloaders and update them +bootupctl adopt-and-update + +# Validate system state +bootupctl validate +``` + +### Advanced Operations + +```bash +# Generate update metadata (for image builders) +bootupd generate-update-metadata + +# Install bootloader components +bootupd install --src-root /path/to/source --dest-root /path/to/destination +``` + +## OSTree Integration + +deb-bootupd is specifically designed for **Debian OSTree immutable systems**: + +- **Image-based Updates**: Updates come from new bootc images, not traditional package repositories +- **Deployment Coordination**: Works seamlessly with OSTree deployment system +- **State Persistence**: Bootloader state survives across OSTree deployments +- **Rollback Support**: Leverages OSTree's built-in rollback capabilities + +### Particle-OS Integration + +This fork is specifically designed to work with [particle-os](https://github.com/ublue-os/particle-os), a Debian-based immutable distribution: + +- **Hybrid Approach**: Debian packages within immutable OSTree structure +- **Bootc Integration**: Full integration with bootc image builder +- **Debian Conventions**: Follows Debian filesystem and package conventions + +## Configuration + +### State File Location + +- **State File**: `/boot/bootupd-state.json` +- **Lock File**: `/run/bootupd-lock` +- **Updates Directory**: `/usr/lib/bootupd/updates` + +### Component Configuration + +Components are automatically detected and configured based on your system: + +- **EFI Systems**: Automatic ESP detection and UEFI boot entry management +- **BIOS Systems**: Direct MBR manipulation for traditional bootloaders +- **Hybrid Systems**: Support for both EFI and BIOS configurations + +## Development + +### Project Structure + +``` +deb-bootupd/ +├── src/ # Source code +│ ├── efi.rs # EFI component implementation +│ ├── bios.rs # BIOS component implementation +│ ├── ostreeutil.rs # OSTree integration +│ ├── packagesystem.rs # DPKG package system integration +│ └── ... +├── systemd/ # Systemd service files +├── tests/ # Test suite +├── Cargo.toml # Rust dependencies +└── debian/ # Debian packaging files +``` + +### Key Adaptations from Upstream + +1. **Package System**: Replaced RPM with DPKG/APT integration +2. **OS Detection**: Enhanced for Debian family distributions +3. **Path Conventions**: Adapted for Debian filesystem standards +4. **OSTree Integration**: Optimized for Debian OSTree deployments + +### Building and Testing + +```bash +# Run tests +cargo test + +# Run with specific features +cargo test --features integration + +# Check code quality +cargo clippy +cargo fmt +``` + +## Troubleshooting + +### Common Issues + +1. **Permission Denied**: Ensure you're running with root privileges +2. **EFI Not Detected**: Verify `efibootmgr` is installed and EFI is enabled +3. **OSTree Integration**: Check that OSTree is properly configured +4. **Package Queries**: Ensure DPKG database is accessible + +### Debug Mode + +```bash +# Enable debug logging +RUST_LOG=debug bootupctl status + +# Check system state +cat /boot/bootupd-state.json +``` + +## Contributing + +We welcome contributions! This project is a proof-of-concept for immutable Debian systems. + +### Development Priorities + +1. **Core Functionality**: Ensure basic bootloader management works on Debian +2. **OSTree Integration**: Optimize for Debian OSTree deployments +3. **Testing**: Comprehensive test coverage for Debian environments +4. **Documentation**: Clear guides for users and developers + +## License + +This project is licensed under the same terms as the original bootupd project. See the LICENSE file for details. + +## Acknowledgments + +- **Original bootupd**: [CoreOS bootupd](https://github.com/coreos/bootupd) - The excellent foundation this project builds upon +- **ublue-os**: For pioneering immutable Debian distributions +- **Debian Community**: For the robust package system and distribution standards + +## Roadmap + +- [x] **Initial Debian Adaptation**: Basic DPKG integration +- [x] **OSTree Support**: Integration with Debian OSTree systems +- [ ] **Enhanced Testing**: Comprehensive test suite for Debian environments +- [ ] **Production Readiness**: Full validation and stability testing +- [ ] **Community Adoption**: Integration with particle-os and other Debian immutable distributions + +## Support + +- **Issues**: Report bugs and feature requests via GitLab issues +- **Discussions**: Join the conversation in GitLab discussions +- **Documentation**: Check this README and inline code documentation + +--- + +**Note**: This is a proof-of-concept project. While it's designed to be production-ready, it's primarily intended to demonstrate the feasibility of immutable Debian systems using ublue-os tools. diff --git a/bootupd.md b/bootupd.md new file mode 100644 index 0000000..29e4e6c --- /dev/null +++ b/bootupd.md @@ -0,0 +1,250 @@ +# bootupd: Deep Code Analysis & Technical Synopsis + +## Project Overview + +**bootupd** is a sophisticated, production-ready Rust-based CLI tool that provides cross-distribution, OS update system agnostic bootloader management capabilities. It addresses a critical gap in Linux system management by handling bootloader updates consistently across different distributions and update mechanisms. + +## Core Architecture & Design Philosophy + +### **Single Binary, Multicall Architecture** +- **Multicall binary**: The same executable serves as both `bootupd` and `bootupctl` based on argv[0] +- **No daemon**: Despite the 'd' suffix, it's "bootloader-upDater" not "bootloader-updater-Daemon" +- **systemd-run execution**: Uses `systemd-run` for robust locking, consistent journal logging, and sandboxing benefits + +### **Component-Based Design** +The system is built around a pluggable component architecture where each bootloader type implements the `Component` trait: + +```rust +pub(crate) trait Component { + fn name(&self) -> &'static str; + fn query_adopt(&self, devices: &Option>) -> Result>; + fn install(&self, src_root: &openat::Dir, dest_root: &str, device: &str, update_firmware: bool) -> Result; + fn run_update(&self, rootcxt: &RootContext, current: &InstalledContent) -> Result; + // ... additional methods +} +``` + +## Supported Components & Architectures + +### **EFI Component** (`src/efi.rs`) +- **Architectures**: x86_64, aarch64, riscv64 +- **Bootloaders**: GRUB, shim +- **Features**: + - Automatic ESP (EFI System Partition) detection and mounting + - Vendor directory management (`/boot/efi/EFI/{vendor}/`) + - UEFI boot entry creation and management via `efibootmgr` + - Integration with systemd-boot detection + - Support for multiple ESP mount points: `["boot/efi", "efi", "boot"]` + +### **BIOS Component** (`src/bios.rs`) +- **Architectures**: x86_64, powerpc64 +- **Bootloaders**: GRUB for BIOS/MBR systems +- **Features**: Direct block device manipulation for MBR updates + +## Core Data Models & State Management + +### **State Persistence** (`src/model.rs`) +```rust +#[derive(Serialize, Deserialize, Default, Debug)] +pub(crate) struct SavedState { + pub(crate) installed: BTreeMap, + pub(crate) pending: Option>, + pub(crate) static_configs: Option, +} +``` + +- **Location**: `/boot/bootupd-state.json` +- **Purpose**: Tracks installed versions, pending updates, and static configuration state +- **Serialization**: JSON-based with backward compatibility support + +### **Content Metadata System** +```rust +#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] +pub(crate) struct ContentMetadata { + pub(crate) timestamp: DateTime, + pub(crate) version: String, +} +``` + +- **Version tracking**: Human-readable version strings (not parsed, just displayed) +- **Timestamp-based updates**: Chronological ordering for update availability +- **Update logic**: `can_upgrade_to()` method determines update eligibility + +## CLI Interface & Commands + +### **Multicall Dispatch** (`src/cli/mod.rs`) +The system automatically routes commands based on the executable name: +- `bootupctl` → `CtlCommand` (user-facing commands) +- `bootupd` → `DCommand` (internal/backend operations) + +### **User Commands** (`src/cli/bootupctl.rs`) +- **`status`**: Show component status with JSON output support +- **`update`**: Update all components +- **`adopt-and-update`**: Adopt existing bootloaders and update them +- **`validate`**: Validate system state +- **`migrate-static-grub-config`**: Migrate to static GRUB configuration + +### **Backend Commands** (`src/cli/bootupd.rs`) +- **`generate-update-metadata`**: Generate update metadata during image builds +- **`install`**: Install bootloader components during disk image creation + +## File Tree Management (`src/filetree.rs`) + +The system uses a sophisticated file tree abstraction for tracking bootloader files: + +- **Content verification**: SHA512-based file integrity checking +- **Delta updates**: Efficient update mechanisms by comparing file trees +- **Metadata generation**: Automatic metadata creation during build processes + +## Safety & Reliability Features + +### **Locking Mechanisms** (`src/backend/statefile.rs`) +```rust +pub(crate) fn acquire_write_lock(sysroot: openat::Dir) -> Result +``` + +- **System-wide locking**: Prevents concurrent state updates +- **Signal handling**: Graceful termination with cleanup +- **Atomic state updates**: State file replacement with `fsync` guarantees + +### **Filesystem Operations** +- **Capability-based security**: Uses `cap-std` for restricted file operations +- **Mount point validation**: Ensures ESP filesystem type correctness +- **Writable mount verification**: Confirms mount points are writable before operations + +## Integration Points + +### **OSTree Integration** (`src/ostreeutil.rs`) +- **Repository queries**: Integration with OSTree repositories for update discovery +- **Deployment tracking**: Awareness of OSTree deployment states +- **Update coordination**: Synchronization with OSTree update processes + +### **Package System Integration** (`src/packagesystem.rs`) +- **RPM awareness**: Integration with RPM-based systems +- **Version coordination**: Alignment with package manager versioning +- **Fedora dependency**: Direct calls to `rpm -q` commands for package metadata +- **RPM database paths**: Looks for databases in `usr/lib/sysimage/rpm` and `usr/share/rpm` + +### **CoreOS Integration** (`src/coreos.rs`) +- **Fedora CoreOS specific**: Special handling for CoreOS environments +- **Configuration management**: CoreOS-specific bootloader configuration +- **Aleph version parsing**: Reads `.coreos-aleph-version.json` files with Fedora-specific metadata + +## Update Workflow + +### **1. Update Discovery** +- Components query for available updates in `/usr/lib/bootupd/updates` +- Metadata comparison determines update eligibility +- Version and timestamp validation + +### **2. Update Execution** +- Filesystem freeze/thaw cycles for consistency +- Component-specific update logic execution +- State file updates with atomic replacement +- Bootloader reconfiguration + +### **3. Validation** +- Post-update verification of installed components +- Integrity checking of updated files +- System state validation + +## Testing & Quality Assurance + +### **Test Infrastructure** +- **Unit tests**: Comprehensive Rust unit test coverage +- **Integration tests**: End-to-end update testing +- **Kola tests**: CoreOS integration testing framework +- **Failpoint testing**: Controlled failure injection for reliability testing + +### **CI/CD Pipeline** +- **Jenkins integration**: `.cci.jenkinsfile` for continuous integration +- **GitHub Actions**: Automated testing and validation +- **COPR builds**: Fedora package building and testing + +## Security Considerations + +### **Privilege Management** +- **Root requirement**: All operations require elevated privileges +- **Sandboxing**: systemd-run provides execution isolation +- **Capability restrictions**: Limited file system access through capability-based security + +### **Secure Boot Integration** +- **shim updates**: Coordination with Secure Boot database updates +- **UEFI variable management**: Safe handling of UEFI boot variables +- **Boot entry validation**: Verification of boot entry integrity + +## Performance Characteristics + +### **Efficient Updates** +- **Delta-based**: Only changed files are updated +- **Parallel processing**: Multiple components can be updated simultaneously +- **Minimal I/O**: Optimized file operations with minimal disk writes + +### **Resource Usage** +- **Memory efficient**: Streaming file operations for large files +- **CPU optimization**: Rust's zero-cost abstractions for performance +- **Disk I/O**: Minimized through intelligent update strategies + +## Deployment & Distribution + +### **Packaging** +- **RPM packaging**: Fedora/CentOS/RHEL package support +- **Container images**: Docker support for containerized deployments +- **Cross-compilation**: Support for multiple target architectures + +### **System Integration** +- **systemd units**: Automatic bootloader update service +- **Boot time updates**: Integration with system boot process +- **Manual control**: CLI-based update management + +## Distribution Dependencies & Portability + +### **Fedora/RPM Dependencies** +- **Package system**: Hard dependency on RPM for package metadata discovery +- **Database paths**: Expects RPM databases in Fedora-specific locations +- **Query format**: Parses RPM output format for version and timestamp information +- **CoreOS integration**: Fedora CoreOS-specific version parsing and metadata + +### **Distribution-Agnostic Components** +- **Core architecture**: Component system and update logic work across distributions +- **Bootloader management**: GRUB, UEFI, and BIOS handling is distribution-independent +- **OSTree integration**: Works with OSTree regardless of underlying distribution +- **File operations**: Filesystem and block device operations are Linux-standard + +### **System-Level Dependencies** +- **Systemd**: Hard dependency on `libsystemd` and systemd features +- **Mount tools**: Requires `mount` and `umount` commands in PATH +- **EFI tools**: Requires `efibootmgr` for EFI boot management +- **Hardcoded paths**: Several filesystem paths are hardcoded for Red Hat conventions +- **OS detection**: Red Hat-specific OS identification logic + +### **Portability Considerations** +- **Debian adaptation**: Would require rewriting `packagesystem.rs` to use DPKG instead of RPM +- **Package queries**: Replace `rpm -q` calls with `dpkg -S` equivalents +- **Database paths**: Adapt to Debian's package database locations +- **Test fixtures**: Update test data to use Debian package formats +- **System commands**: Replace `efibootmgr` with Debian equivalents (may need `efibootmgr` package) +- **Systemd dependency**: Debian supports systemd, but alternative init systems would need different approaches +- **Hardcoded paths**: Update `/usr/lib/bootupd/updates` and other hardcoded paths for Debian conventions +- **Mount commands**: Ensure `mount`/`umount` commands exist and work as expected +- **OS detection**: Adapt Red Hat-specific OS detection logic for Debian + +## Future Development Areas + +### **Planned Features** +- **Power failure safety**: Protection against interrupted updates +- **Redundant disk support**: Synchronization across multiple bootable disks +- **Advanced bootloader support**: Additional bootloader types beyond GRUB +- **API development**: Potential DBus or other IPC interfaces + +### **Architecture Evolution** +- **Component extensibility**: Plugin system for new bootloader types +- **Update policies**: Configurable update strategies and policies +- **Rollback mechanisms**: Enhanced rollback capabilities +- **Distribution abstraction**: Potential abstraction layer for package system differences + +## Summary + +bootupd represents a mature, well-architected solution to a complex problem in Linux system management. Its Rust implementation provides memory safety, performance, and reliability while maintaining a clean, modular architecture. The system's focus on safety, integration with existing Linux infrastructure, and cross-distribution compatibility makes it a valuable tool for modern Linux deployments, particularly in containerized and transactional update environments. + +The codebase demonstrates excellent software engineering practices with comprehensive testing, clear separation of concerns, and robust error handling. Its component-based architecture allows for easy extension to new bootloader types and architectures, while the multicall binary design provides a clean user experience without the complexity of separate daemon processes. \ No newline at end of file diff --git a/bootupd/.copr/Makefile b/bootupd/.copr/Makefile deleted file mode 100755 index 011fb2a..0000000 --- a/bootupd/.copr/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -srpm: - dnf -y install cargo git openssl-devel - # similar to https://github.com/actions/checkout/issues/760, but for COPR - git config --global --add safe.directory '*' - cargo install cargo-vendor-filterer - cargo xtask package-srpm - mv target/*.src.rpm $$outdir diff --git a/bootupd/.gemini/config.yaml b/bootupd/.gemini/config.yaml deleted file mode 100755 index 1585c84..0000000 --- a/bootupd/.gemini/config.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# This config mainly overrides `summary: false` by default -# as it's really noisy. -have_fun: true -code_review: - disable: false - comment_severity_threshold: MEDIUM - max_review_comments: -1 - pull_request_opened: - help: false - summary: false # turned off by default - code_review: true -ignore_patterns: [] diff --git a/bootupd/README-design.md b/bootupd/README-design.md deleted file mode 100755 index 07d52b7..0000000 --- a/bootupd/README-design.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overall design - -The initial focus here is updating the [ESP](https://en.wikipedia.org/wiki/EFI_system_partition), but the overall design of bootupd contains a lot of abstraction to support different "components". - -## Ideal case - -In the ideal case, an OS builder uses `bootupd install` to install all bootloader data, -and thereafter it is fully (exclusively) managed by bootupd. It would e.g. be a bug/error -for an administrator to manually invoke `grub2-install` e.g. again. - -In other words, an end user system would simply invoke `bootupd update` as desired. - -However, we're not in that ideal case. Thus bootupd has the concept of "adoption" where -we start tracking the installed state as we find it. - -## Handling adoption - -For Fedora CoreOS, currently the `EFI/fedora/grub.cfg` file is created outside of the ostree inside `create_disk.sh`. So we aren't including any updates for it in the OSTree. - -This type of problem is exactly what bootupd should be solving. - -However, we need to be very cautious in handling this because we basically can't -assume we own all of the state. We shouldn't touch any files that we -don't know about. - -## Upgrade edges - -We don't necessarily want to update the bootloader data, even if a new update happens to be provided. -For example, Fedora does "mass rebuilds" usually once a release, but it's not strictly necessary -to update users' bootloaders then. - -A common policy in fact might be "only update bootloader for security issue or if it's strictly necessary". - -A "strictly necessary" upgrade would be one like the GRUB BLS parsing support. - -There is not yet any support for upgrade edges in the code apart from a stub structure. diff --git a/bootupd/README-devel.md b/bootupd/README-devel.md deleted file mode 100755 index 7ea4589..0000000 --- a/bootupd/README-devel.md +++ /dev/null @@ -1,55 +0,0 @@ -# Developing bootupd - -Currently the focus is Fedora CoreOS. - -You can use the normal Rust tools to build and run the unit tests: - -`cargo build` and `cargo test` - -For real e2e testing, use e.g. -``` -export COSA_DIR=/path/to/fcos -cosa build-fast -kola run -E $(pwd) --qemu-image fastbuild-fedora-coreos-bootupd-qemu.qcow2 --qemu-firmware uefi ext.bootupd.* -``` - -See also [the coreos-assembler docs](https://coreos.github.io/coreos-assembler/working/#using-overrides). - -## Building With Containers - -There's a reference [Dockerfile](Dockerfile) that builds on [CentOS Stream bootc](https://docs.fedoraproject.org/en-US/bootc/). - -## Integrating bootupd into a distribution/OS - -Today, bootupd only really works on systems that use RPMs and ostree. -(Which usually means rpm-ostree, but not strictly necessarily) - -Many bootupd developers (and current CI flows) target Fedora CoreOS -and derivatives, so it can be used as a "reference" for integration. - -There's two parts to integration: - -### Generating an update payload - -Bootupd's concept of an "update payload" needs to be generated as -part of an OS image (e.g. ostree commit). -A good reference for this is -https://github.com/coreos/fedora-coreos-config/blob/88af117d1d2c5e828e5e039adfa03c7cc66fc733/manifests/bootupd.yaml#L12 - -Specifically, you'll need to invoke -`bootupctl backend generate-update-metadata /` as part of update payload generation. -This scrapes metadata (e.g. RPM versions) about shim/grub and puts them along with -their component files in `/usr/lib/bootupd/updates/`. - -### Installing to generated disk images - -In order to correctly manage updates, bootupd also needs to be responsible -for laying out files in initial disk images. A good reference for this is -https://github.com/coreos/coreos-assembler/blob/93efb63dcbd63dc04a782e2c6c617ae0cd4a51c8/src/create_disk.sh#L401 - -Specifically, you'll need to invoke -`/usr/bin/bootupctl backend install --src-root /path/to/ostree/deploy /sysroot` -where the first path is an ostree deployment root, and the second is the physical -root partition. - -This will e.g. inject the initial files into the mounted EFI system partition. diff --git a/bootupd/README.md b/bootupd/README.md deleted file mode 100755 index 917fc66..0000000 --- a/bootupd/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# bootupd: Distribution-independent updates for bootloaders - -Today many Linux systems handle updates for bootloader data -in an inconsistent and ad-hoc way. For example, on -Fedora and Debian, a package manager update will update UEFI -binaries in `/boot/efi`, but not the BIOS MBR data. - -Transactional/"image" update systems like [OSTree](https://github.com/ostreedev/ostree/) -and dual-partition systems like the Container Linux update system -are more consistent: they normally cover kernel/userspace but not anything -related to bootloaders. - -The reason for this is straightforward: performing bootloader -updates in an "A/B" fashion requires completely separate nontrivial -logic from managing the kernel and root filesystem. Today OSTree e.g. -makes the choice that it does not update `/boot/efi` (and also doesn't -update the BIOS MBR). - -The goal of this project is to be a cross-distribution, -OS update system agnostic tool to manage updates for things like: - -- `/boot/efi` -- x86 BIOS MBR -- Other architecture bootloaders - -This project originated in [this Fedora CoreOS github issue](https://github.com/coreos/fedora-coreos-tracker/issues/510). - -The scope is otherwise limited; for example, bootupd will not -manage anything related to the kernel such as kernel arguments; -that's for tools like `grubby` and `ostree`. - -## Status - -bootupd supports updating GRUB and shim for UEFI firmware on x86_64, aarch64, -and riscv64, and GRUB for BIOS firmware on x86_64 and ppc64le. - -The project is used in Bootable Containers and ostree/rpm-ostree based systems: - - [`bootc install`](https://github.com/containers/bootc/#using-bootc-install) - - [Fedora CoreOS](https://docs.fedoraproject.org/en-US/fedora-coreos/bootloader-updates/) - - Fedora Atomic Desktops - -On systems booted using a UEFI firmware, bootloader updates performed by -bootupd are now considered safe, even in case of power failures (see: -[issue#454](https://github.com/coreos/bootupd/issues/454)). - -On other systems (BIOS, etc.), bootloader updates performed by bootupd not safe -against a power failures at the wrong time. - -Note that bootupd does not yet perform updates in a way that is safe against a -buggy bootloader update that fails to boot the system. This is tracked in -[issue#440](https://github.com/coreos/bootupd/issues/440). - -Bootloader updates are enabled by default on Fedora Atomic Desktops, and will -soon be on all Bootable Containers systems. See -[fedora-coreos-tracker#1468](https://github.com/coreos/fedora-coreos-tracker/issues/1468). - -The bootupd CLI should be considered stable. - -## Relationship to other projects - -### dbxtool - -[dbxtool](https://github.com/rhboot/dbxtool) manages updates -to the Secure Boot database - `bootupd` will likely need to -perform any updates to the `shimx64.efi` binary -*before* `dbxtool.service` starts. But otherwise they are independent. - -### fwupd - -bootupd could be compared to [fwupd](https://github.com/fwupd/fwupd/) which is -a project that exists today to update hardware device firmware - things not managed -by e.g. `apt/zypper/yum/rpm-ostree update` today. - -fwupd comes as a UEFI binary today, so bootupd *could* take care of updating `fwupd` -but today fwupd handles that itself. So it's likely that bootupd would only take -care of GRUB and shim. See discussion in [this issue](https://github.com/coreos/bootupd/issues/1). - -### systemd bootctl - -[systemd bootctl](https://man7.org/linux/man-pages/man1/bootctl.1.html) can update itself; -this project would probably just proxy that if we detect systemd-boot is in use. - -## Other goals - -One idea is that bootupd could help support [redundant bootable disks](https://github.com/coreos/fedora-coreos-tracker/issues/581). -For various reasons it doesn't really work to try to use RAID1 for an entire disk; the ESP must be handled -specially. `bootupd` could learn how to synchronize multiple EFI system partitions from a primary. - -## More details on rationale and integration - -A notable problem today for [rpm-ostree](https://github.com/coreos/rpm-ostree/) based -systems is that `rpm -q shim-x64` is misleading because it's not actually -updated in place. - -Particularly [this commit][1] makes things clear - the data -from the RPM goes into `/usr` (part of the OSTree), so it doesn't touch `/boot/efi`. -But that commit didn't change how the RPM database works (and more generally it -would be technically complex for rpm-ostree to change how the RPM database works today). - -What we ultimately want is that `rpm -q shim-x64` returns "not installed" - because -it's not managed by RPM or by ostree. Instead one would purely use `bootupctl` to manage it. -However, it might still be *built* as an RPM, just not installed that way. The RPM version numbers would be used -for the bootupd version associated with the payload, and ultimately we'd teach `rpm-ostree compose tree` -how to separately download bootloaders and pass them to `bootupctl backend`. - -[1]: https://github.com/coreos/rpm-ostree/pull/969/commits/dc0e8db5bd92e1f478a0763d1a02b48e57022b59 - - -## Questions and answers - -- Why is bootupd not part of ostree? - -A key advertised feature of ostree is that updates are truly transactional. -There's even a [a test case](https://blog.verbum.org/2020/12/01/committed-to-the-integrity-of-your-root-filesystem/) -that validates forcibly pulling the power during OS updates. A simple -way to look at this is that on an ostree-based system there is no need -to have a "please don't power off your computer" screen. This in turn -helps administrators to confidently enable automatic updates. - -Doing that for the bootloader (i.e. bootupd's domain) is an *entirely* separate problem. -There have been some ideas around how we could make the bootloaders -use an A/B type scheme (or at least be more resilient), and perhaps in the future bootupd will -use some of those. - -These updates hence carry different levels of risk. In many cases -actually it's OK if the bootloader lags behind; we don't need to update -every time. - -But out of conservatism currently today for e.g. Fedora CoreOS, bootupd is disabled -by default. On the other hand, if your OS update mechanism isn't transactional, -then you may want to enable bootupd by default. - -- 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. - -bootupd used to have an internally-facing `bootupd.service` and -`bootupd.socket` systemd units that acted as a locking mechanism. The service -would *very quickly* auto exit. There was nothing long-running, so it was not -really a daemon. - -bootupd now uses `systemd-run` instead to guarantee the following: - -- It provides a robust natural "locking" mechanism. -- It ensures that critical logging metadata always consistently ends up in the - systemd journal, not e.g. a transient client SSH connection. -- It benefits from the sandboxing options available for systemd units, and - while bootupd is obviously privileged we can still make use of some of this. -- If we want a non-CLI API (whether that's DBus or Cap'n Proto or varlink or - something else), we will create an independent daemon with a stable API for - this specific need. - diff --git a/bootupd/code-of-conduct.md b/bootupd/code-of-conduct.md deleted file mode 100755 index a234f36..0000000 --- a/bootupd/code-of-conduct.md +++ /dev/null @@ -1,61 +0,0 @@ -## CoreOS Community Code of Conduct - -### Contributor Code of Conduct - -As contributors and maintainers of this project, and in the interest of -fostering an open and welcoming community, we pledge to respect all people who -contribute through reporting issues, posting feature requests, updating -documentation, submitting pull requests or patches, and other activities. - -We are committed to making participation in this project a harassment-free -experience for everyone, regardless of level of experience, gender, gender -identity and expression, sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing others' private information, such as physical or electronic addresses, without explicit permission -* Other unethical or unprofessional conduct. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct. By adopting this Code of Conduct, -project maintainers commit themselves to fairly and consistently applying these -principles to every aspect of managing this project. Project maintainers who do -not follow or enforce the Code of Conduct may be permanently removed from the -project team. - -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting a project maintainer, Brandon Philips -, and/or Rithu John . - -This Code of Conduct is adapted from the Contributor Covenant -(http://contributor-covenant.org), version 1.2.0, available at -http://contributor-covenant.org/version/1/2/0/ - -### CoreOS Events Code of Conduct - -CoreOS events are working conferences intended for professional networking and -collaboration in the CoreOS community. Attendees are expected to behave -according to professional standards and in accordance with their employer’s -policies on appropriate workplace behavior. - -While at CoreOS events or related social networking opportunities, attendees -should not engage in discriminatory or offensive speech or actions including -but not limited to gender, sexuality, race, age, disability, or religion. -Speakers should be especially aware of these concerns. - -CoreOS does not condone any statements by speakers contrary to these standards. -CoreOS reserves the right to deny entrance and/or eject from an event (without -refund) any individual found to be engaging in discriminatory or offensive -speech or actions. - -Please bring any concerns to the immediate attention of designated on-site -staff, Brandon Philips , and/or Rithu John . diff --git a/bootupd/src/coreos.rs b/bootupd/src/coreos.rs deleted file mode 100755 index 8f7aa8d..0000000 --- a/bootupd/src/coreos.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Bits specific to Fedora CoreOS (and derivatives). - -/* - * Copyright (C) 2020 Red Hat, Inc. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -use anyhow::{Context, Result}; -use chrono::prelude::*; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::path::Path; - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Ord, PartialOrd, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -/// See https://github.com/coreos/fedora-coreos-tracker/blob/66d7d00bedd9d5eabc7287b9577f443dcefb7c04/internals/README-internals.md#aleph-version -pub(crate) struct Aleph { - #[serde(alias = "build")] - pub(crate) version: String, -} - -pub(crate) struct AlephWithTimestamp { - pub(crate) aleph: Aleph, - #[allow(dead_code)] - pub(crate) ts: chrono::DateTime, -} - -/// Path to the file, see above -const ALEPH_PATH: &str = "sysroot/.coreos-aleph-version.json"; - -pub(crate) fn get_aleph_version(root: &Path) -> Result> { - let path = &root.join(ALEPH_PATH); - if !path.exists() { - return Ok(None); - } - let statusf = File::open(path).with_context(|| format!("Opening {path:?}"))?; - let meta = statusf.metadata()?; - let bufr = std::io::BufReader::new(statusf); - let aleph: Aleph = serde_json::from_reader(bufr)?; - Ok(Some(AlephWithTimestamp { - aleph, - ts: meta.created()?.into(), - })) -} - -#[cfg(test)] -mod test { - use super::*; - use anyhow::Result; - - const V1_ALEPH_DATA: &str = r##" - { - "version": "32.20201002.dev.2", - "ref": "fedora/x86_64/coreos/testing-devel", - "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862" - }"##; - - // Waiting on https://github.com/rust-lang/rust/pull/125692 - #[cfg(not(target_env = "musl"))] - #[test] - fn test_parse_from_root_empty() -> Result<()> { - // Verify we're a no-op in an empty root - let root: &tempfile::TempDir = &tempfile::tempdir()?; - let root = root.path(); - assert!(get_aleph_version(root).unwrap().is_none()); - Ok(()) - } - - // Waiting on https://github.com/rust-lang/rust/pull/125692 - #[cfg(not(target_env = "musl"))] - #[test] - fn test_parse_from_root() -> Result<()> { - let root: &tempfile::TempDir = &tempfile::tempdir()?; - let root = root.path(); - let sysroot = &root.join("sysroot"); - std::fs::create_dir(sysroot).context("Creating sysroot")?; - std::fs::write(root.join(ALEPH_PATH), V1_ALEPH_DATA).context("Writing aleph")?; - let aleph = get_aleph_version(root).unwrap().unwrap(); - assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); - Ok(()) - } - - // Waiting on https://github.com/rust-lang/rust/pull/125692 - #[cfg(not(target_env = "musl"))] - #[test] - fn test_parse_from_root_linked() -> Result<()> { - let root: &tempfile::TempDir = &tempfile::tempdir()?; - let root = root.path(); - let sysroot = &root.join("sysroot"); - std::fs::create_dir(sysroot).context("Creating sysroot")?; - let target_name = ".new-ostree-aleph.json"; - let target = &sysroot.join(target_name); - std::fs::write(root.join(target), V1_ALEPH_DATA).context("Writing aleph")?; - std::os::unix::fs::symlink(target_name, root.join(ALEPH_PATH)).context("Symlinking")?; - let aleph = get_aleph_version(root).unwrap().unwrap(); - assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); - Ok(()) - } - - #[test] - fn test_parse_old_aleph() -> Result<()> { - // What the aleph file looked like before we changed it in - // https://github.com/osbuild/osbuild/pull/1475 - let alephdata = r##" -{ - "build": "32.20201002.dev.2", - "ref": "fedora/x86_64/coreos/testing-devel", - "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862", - "imgid": "fedora-coreos-32.20201002.dev.2-qemu.x86_64.qcow2" -}"##; - let aleph: Aleph = serde_json::from_str(alephdata)?; - assert_eq!(aleph.version, "32.20201002.dev.2"); - Ok(()) - } - - #[test] - fn test_parse_aleph() -> Result<()> { - let aleph: Aleph = serde_json::from_str(V1_ALEPH_DATA)?; - assert_eq!(aleph.version, "32.20201002.dev.2"); - Ok(()) - } -} diff --git a/bootupd/src/packagesystem.rs b/bootupd/src/packagesystem.rs deleted file mode 100755 index 8c5d1f7..0000000 --- a/bootupd/src/packagesystem.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::io::Write; -use std::path::Path; - -use anyhow::{bail, Context, Result}; -use chrono::prelude::*; - -use crate::model::*; -use crate::ostreeutil; - -/// Parse the output of `rpm -q` -fn rpm_parse_metadata(stdout: &[u8]) -> Result { - let pkgs = std::str::from_utf8(stdout)? - .split_whitespace() - .map(|s| -> Result<_> { - let parts: Vec<_> = s.splitn(2, ',').collect(); - let name = parts[0]; - if let Some(ts) = parts.get(1) { - let nt = DateTime::parse_from_str(ts, "%s") - .context("Failed to parse rpm buildtime")? - .with_timezone(&chrono::Utc); - Ok((name, nt)) - } else { - bail!("Failed to parse: {}", s); - } - }) - .collect::>>>()?; - if pkgs.is_empty() { - bail!("Failed to find any RPM packages matching files in source efidir"); - } - let timestamps: BTreeSet<&DateTime> = pkgs.values().collect(); - // Unwrap safety: We validated pkgs has at least one value above - let largest_timestamp = timestamps.iter().last().unwrap(); - let version = pkgs.keys().fold("".to_string(), |mut s, n| { - if !s.is_empty() { - s.push(','); - } - s.push_str(n); - s - }); - Ok(ContentMetadata { - timestamp: **largest_timestamp, - version, - }) -} - -/// Query the rpm database and list the package and build times. -pub(crate) fn query_files( - sysroot_path: &str, - paths: impl IntoIterator, -) -> Result -where - T: AsRef, -{ - let mut c = ostreeutil::rpm_cmd(sysroot_path)?; - c.args(["-q", "--queryformat", "%{nevra},%{buildtime} ", "-f"]); - for arg in paths { - c.arg(arg.as_ref()); - } - - let rpmout = c.output()?; - if !rpmout.status.success() { - std::io::stderr().write_all(&rpmout.stderr)?; - bail!("Failed to invoke rpm -qf"); - } - - rpm_parse_metadata(&rpmout.stdout) -} - -#[test] -fn test_parse_rpmout() { - let testdata = "grub2-efi-x64-1:2.06-95.fc38.x86_64,1681321788 grub2-efi-x64-1:2.06-95.fc38.x86_64,1681321788 shim-x64-15.6-2.x86_64,1657222566 shim-x64-15.6-2.x86_64,1657222566 shim-x64-15.6-2.x86_64,1657222566"; - let parsed = rpm_parse_metadata(testdata.as_bytes()).unwrap(); - assert_eq!( - parsed.version, - "grub2-efi-x64-1:2.06-95.fc38.x86_64,shim-x64-15.6-2.x86_64" - ); -} diff --git a/bootupd/ci/build-test.sh b/ci/build-test.sh similarity index 100% rename from bootupd/ci/build-test.sh rename to ci/build-test.sh diff --git a/bootupd/ci/prepare-release.sh b/ci/prepare-release.sh similarity index 100% rename from bootupd/ci/prepare-release.sh rename to ci/prepare-release.sh diff --git a/bootupd/ci/prow/Dockerfile b/ci/prow/Dockerfile similarity index 100% rename from bootupd/ci/prow/Dockerfile rename to ci/prow/Dockerfile diff --git a/bootupd/ci/prow/fcos-e2e.sh b/ci/prow/fcos-e2e.sh similarity index 100% rename from bootupd/ci/prow/fcos-e2e.sh rename to ci/prow/fcos-e2e.sh diff --git a/bootupd/contrib/packaging/bootupd.spec b/contrib/packaging/bootupd.spec similarity index 100% rename from bootupd/contrib/packaging/bootupd.spec rename to contrib/packaging/bootupd.spec diff --git a/deb-bootupd.md b/deb-bootupd.md new file mode 100644 index 0000000..f241344 --- /dev/null +++ b/deb-bootupd.md @@ -0,0 +1,382 @@ +# 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` + +## 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: Hard Clean Fork +**Approach**: Simple, direct fork for proof-of-concept + +**Benefits**: +- **Clean start**: No complex git history or upstream sync complexity +- **Focus on core**: Concentrate on making Debian immutable system bootable +- **Proof-of-concept**: Perfect for testing the concept without maintenance overhead +- **Simple workflow**: Just copy, adapt, and test + +**Implementation**: +```bash +# Simple copy approach +cp -r .Red_Hat_Version/bootupd deb-bootupd/ +cd deb-bootupd +chmod 755 deb-bootupd +git init +git add . +git commit -m "Initial Debian fork of bootupd for immutable Debian proof-of-concept" +``` + +**Future considerations**: Can always add upstream sync later if the concept proves viable + +## 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**: +```rust +// Replace RPM-specific code: +// OLD: rpm -q --queryformat "%{nevra},%{buildtime}" -f +// NEW: dpkg -S && dpkg -s | 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**: +```rust +// Current (Red Hat): /usr/lib/modules/*/vmlinuz +// Debian OSTree: /usr/lib/modules/*/vmlinuz (same path, different context) +// Need to ensure this works with Debian kernel naming conventions +``` + +**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-install` (GRUB installation) + +**Debian Compatibility**: +- ✅ `efibootmgr`: Available in Debian repositories +- ✅ `mount`/`umount`: Standard Linux tools +- ✅ `grub-install`: Available in Debian repositories + +**Action**: Ensure these packages are available in particle-os base image + +### 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**: +```rust +// 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 + +### 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 + +### 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 built and included in the bootc image +- **Build timing**: deb-bootupd compiled during image build, not first boot +- **Dependencies**: Ensure all required tools (`efibootmgr`, `grub-install`) are in base image + +**Image Builder Workflow**: +1. **Build phase**: Compile deb-bootupd using debian-bootc-image-builder +2. **Installation**: Install deb-bootupd binary and systemd service +3. **Configuration**: Set up initial bootloader configuration +4. **First boot**: deb-bootupd runs to adopt existing bootloader or install new one + +**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. **Core path and dependency fixes** - Make it compile and run +3. **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 +3. **Week 2**: Fix hardcoded paths and dependencies +4. **Week 3**: Basic testing and validation +5. **Week 4**: Particle-OS integration testing + +## 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/` diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..74cec7a --- /dev/null +++ b/debian/changelog @@ -0,0 +1,8 @@ +deb-bootupd (0.2.28-1) unstable; urgency=medium + + * Initial Debian package release + * Fork of bootupd for Debian immutable systems + * Adapted for DPKG/APT package system + * OSTree integration for immutable Debian deployments + + -- Debian Bootupd Team $(date -R) diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..121214b --- /dev/null +++ b/debian/control @@ -0,0 +1,30 @@ +Source: deb-bootupd +Section: admin +Priority: optional +Maintainer: Debian Bootupd Team +Build-Depends: debhelper-compat (= 13), dh-cargo, rustc, cargo, pkg-config, libssl-dev, libsystemd-dev +Standards-Version: 4.6.2 +Homepage: https://git.raines.xyz/robojerk/deb-bootupd +Vcs-Git: https://git.raines.xyz/robojerk/deb-bootupd.git +Vcs-Browser: https://git.raines.xyz/robojerk/deb-bootupd + +Package: deb-bootupd +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, efibootmgr, grub-common +Description: Debian-compatible bootloader updater for immutable systems + deb-bootupd is a sophisticated, production-ready Rust-based CLI tool that + provides cross-distribution, OS update system agnostic bootloader management + capabilities for Debian-based immutable systems using OSTree and bootc. + . + This tool addresses a critical gap in Linux system management by handling + bootloader updates consistently across different distributions and update + mechanisms, specifically adapted for Debian package systems and conventions. + . + Key features include: + * Single binary, multicall architecture (bootupd and bootupctl) + * Component-based design supporting EFI, BIOS, and other bootloader types + * Full OSTree integration for immutable Debian systems + * Native DPKG/APT integration instead of RPM + * Cross-architecture support (x86_64, aarch64, riscv64, powerpc64) + * Bootloader support for GRUB, shim, and systemd-boot detection + * Robust state management across OSTree deployments diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..43c69a2 --- /dev/null +++ b/debian/rules @@ -0,0 +1,15 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_build: + dh_cargo build --release + +override_dh_auto_install: + dh_cargo install --target-dir=target/release + # Install systemd service files + install -D -m 644 systemd/bootupd.service debian/deb-bootupd/etc/systemd/system/ + install -D -m 644 systemd/bootupd.socket debian/deb-bootupd/etc/systemd/system/ + # Create symlinks for multicall binary + ln -sf /usr/bin/bootupd debian/deb-bootupd/usr/bin/bootupctl diff --git a/bootupd/doc/dependency_decisions.yml b/doc/dependency_decisions.yml similarity index 100% rename from bootupd/doc/dependency_decisions.yml rename to doc/dependency_decisions.yml diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..50c0a45 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +echo "Building deb-bootupd..." + +# Clean previous builds +cargo clean + +# Build the project +cargo build --release + +# Run tests +cargo test + +# Check code quality +cargo clippy +cargo fmt --check + +echo "Build completed successfully!" +echo "Binary location: target/release/bootupd" diff --git a/bootupd/src/backend/mod.rs b/src/backend/mod.rs similarity index 100% rename from bootupd/src/backend/mod.rs rename to src/backend/mod.rs diff --git a/bootupd/src/backend/statefile.rs b/src/backend/statefile.rs similarity index 100% rename from bootupd/src/backend/statefile.rs rename to src/backend/statefile.rs diff --git a/bootupd/src/bios.rs b/src/bios.rs similarity index 100% rename from bootupd/src/bios.rs rename to src/bios.rs diff --git a/bootupd/src/blockdev.rs b/src/blockdev.rs similarity index 100% rename from bootupd/src/blockdev.rs rename to src/blockdev.rs diff --git a/bootupd/src/bootupd.rs b/src/bootupd.rs similarity index 99% rename from bootupd/src/bootupd.rs rename to src/bootupd.rs index 1969b51..1eada4c 100755 --- a/bootupd/src/bootupd.rs +++ b/src/bootupd.rs @@ -434,7 +434,7 @@ pub(crate) fn print_status(status: &Status) -> Result<()> { } if let Some(coreos_aleph) = coreos::get_aleph_version(Path::new("/"))? { - println!("CoreOS aleph version: {}", coreos_aleph.aleph.version); + println!("CoreOS aleph version: {}", coreos_aleph.version_info.version); } #[cfg(any( diff --git a/bootupd/src/cli/bootupctl.rs b/src/cli/bootupctl.rs similarity index 100% rename from bootupd/src/cli/bootupctl.rs rename to src/cli/bootupctl.rs diff --git a/bootupd/src/cli/bootupd.rs b/src/cli/bootupd.rs similarity index 100% rename from bootupd/src/cli/bootupd.rs rename to src/cli/bootupd.rs diff --git a/bootupd/src/cli/mod.rs b/src/cli/mod.rs similarity index 100% rename from bootupd/src/cli/mod.rs rename to src/cli/mod.rs diff --git a/bootupd/src/component.rs b/src/component.rs similarity index 99% rename from bootupd/src/component.rs rename to src/component.rs index 5ca32df..45ae051 100755 --- a/bootupd/src/component.rs +++ b/src/component.rs @@ -166,7 +166,7 @@ pub(crate) fn query_adopt_state() -> Result> { if let Some(coreos_aleph) = crate::coreos::get_aleph_version(Path::new("/"))? { let meta = ContentMetadata { timestamp: coreos_aleph.ts, - version: coreos_aleph.aleph.version, + version: coreos_aleph.version_info.version, }; log::trace!("Adoptable: {:?}", &meta); return Ok(Some(Adoptable { diff --git a/src/coreos.rs b/src/coreos.rs new file mode 100755 index 0000000..376cf4b --- /dev/null +++ b/src/coreos.rs @@ -0,0 +1,187 @@ +//! Bits specific to CoreOS and Debian OSTree systems. + +/* + * Copyright (C) 2020 Red Hat, Inc. + * Modified for Debian compatibility + * + * SPDX-License-Identifier: Apache-2.0 + */ + +use anyhow::{Context, Result}; +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::path::Path; + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Ord, PartialOrd, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +/// Version information for CoreOS and Debian OSTree systems +pub(crate) struct SystemVersion { + #[serde(alias = "build")] + pub(crate) version: String, + #[serde(alias = "ref")] + pub(crate) ref_name: Option, + #[serde(alias = "ostree-commit")] + pub(crate) ostree_commit: Option, +} + +#[derive(Debug)] +pub(crate) struct SystemVersionWithTimestamp { + pub(crate) version_info: SystemVersion, + #[allow(dead_code)] + pub(crate) ts: chrono::DateTime, +} + +/// Paths to version files for different systems +const COREOS_ALEPH_PATH: &str = ".coreos-aleph-version.json"; +const DEBIAN_VERSION_PATH: &str = ".debian-version.json"; + +/// Get version information for CoreOS or Debian systems +pub(crate) fn get_system_version(root: &Path) -> Result> { + // Try CoreOS aleph version first + if let Some(version) = get_coreos_version(root)? { + return Ok(Some(version)); + } + + // Try Debian version + if let Some(version) = get_debian_version(root)? { + return Ok(Some(version)); + } + + Ok(None) +} + +/// Get CoreOS aleph version (original functionality) +pub(crate) fn get_coreos_version(root: &Path) -> Result> { + let path = &root.join(COREOS_ALEPH_PATH); + if !path.exists() { + return Ok(None); + } + let statusf = File::open(path).with_context(|| format!("Opening {path:?}"))?; + let meta = statusf.metadata()?; + let bufr = std::io::BufReader::new(statusf); + let aleph: SystemVersion = serde_json::from_reader(bufr)?; + + // Use created time if available, otherwise fall back to modified time + let ts = meta.created().unwrap_or_else(|_| meta.modified().unwrap()).into(); + + Ok(Some(SystemVersionWithTimestamp { + version_info: aleph, + ts, + })) +} + +/// Get Debian version information +pub(crate) fn get_debian_version(root: &Path) -> Result> { + let path = &root.join(DEBIAN_VERSION_PATH); + if !path.exists() { + return Ok(None); + } + let statusf = File::open(path).with_context(|| format!("Opening {path:?}"))?; + let meta = statusf.metadata()?; + let bufr = std::io::BufReader::new(statusf); + let deb_version: SystemVersion = serde_json::from_reader(bufr)?; + + // Use created time if available, otherwise fall back to modified time + let ts = meta.created().unwrap_or_else(|_| meta.modified().unwrap()).into(); + + Ok(Some(SystemVersionWithTimestamp { + version_info: deb_version, + ts, + })) +} + +/// Legacy function for backward compatibility +pub(crate) fn get_aleph_version(root: &Path) -> Result> { + get_coreos_version(root) +} + +#[cfg(test)] +mod test { + use super::*; + use anyhow::Result; + + const V1_ALEPH_DATA: &str = r##" + { + "version": "32.20201002.dev.2", + "ref": "fedora/x86_64/coreos/testing-devel", + "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862" + }"##; + + const DEBIAN_VERSION_DATA: &str = r##" + { + "version": "12.1", + "ref": "debian/bookworm/amd64", + "ostree-commit": "debian-ostree-commit-hash" + }"##; + + #[test] + fn test_parse_from_root_empty() -> Result<()> { + let tempdir = tempfile::tempdir()?; + let result = get_coreos_version(tempdir.path())?; + assert!(result.is_none()); + Ok(()) + } + + #[test] + fn test_parse_from_root() -> Result<()> { + let tempdir = tempfile::tempdir()?; + let file_path = tempdir.path().join(COREOS_ALEPH_PATH); + println!("Creating file at: {:?}", file_path); + std::fs::write(&file_path, V1_ALEPH_DATA)?; + println!("File created successfully"); + + let result = get_coreos_version(tempdir.path())?; + println!("Result: {:?}", result); + let Some(result) = result else { + anyhow::bail!("Expected Some result"); + }; + assert_eq!(result.version_info.version, "32.20201002.dev.2"); + Ok(()) + } + + #[test] + fn test_parse_from_root_linked() -> Result<()> { + let tempdir = tempfile::tempdir()?; + let target_name = "target.json"; + std::fs::write(tempdir.path().join(target_name), V1_ALEPH_DATA)?; + std::os::unix::fs::symlink(target_name, tempdir.path().join(COREOS_ALEPH_PATH))?; + let result = get_coreos_version(tempdir.path())?; + let Some(result) = result else { + anyhow::bail!("Expected Some result"); + }; + assert_eq!(result.version_info.version, "32.20201002.dev.2"); + Ok(()) + } + + #[test] + fn test_parse_old_aleph() -> Result<()> { + let tempdir = tempfile::tempdir()?; + let old_aleph_data = r##" + { + "build": "32.20201002.dev.2", + "ref": "fedora/x86_64/coreos/testing-devel", + "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862" + }"##; + std::fs::write(tempdir.path().join(COREOS_ALEPH_PATH), old_aleph_data)?; + let result = get_coreos_version(tempdir.path())?; + let Some(result) = result else { + anyhow::bail!("Expected Some result"); + }; + assert_eq!(result.version_info.version, "32.20201002.dev.2"); + Ok(()) + } + + #[test] + fn test_parse_debian_version() -> Result<()> { + let tempdir = tempfile::tempdir()?; + std::fs::write(tempdir.path().join(DEBIAN_VERSION_PATH), DEBIAN_VERSION_DATA)?; + let result = get_debian_version(tempdir.path())?; + let Some(result) = result else { + anyhow::bail!("Expected Some result"); + }; + assert_eq!(result.version_info.version, "12.1"); + assert_eq!(result.version_info.ref_name, Some("debian/bookworm/amd64".to_string())); + Ok(()) + } +} diff --git a/bootupd/src/efi.rs b/src/efi.rs similarity index 100% rename from bootupd/src/efi.rs rename to src/efi.rs diff --git a/bootupd/src/failpoints.rs b/src/failpoints.rs similarity index 100% rename from bootupd/src/failpoints.rs rename to src/failpoints.rs diff --git a/bootupd/src/filesystem.rs b/src/filesystem.rs similarity index 100% rename from bootupd/src/filesystem.rs rename to src/filesystem.rs diff --git a/bootupd/src/filetree.rs b/src/filetree.rs similarity index 100% rename from bootupd/src/filetree.rs rename to src/filetree.rs diff --git a/bootupd/src/freezethaw.rs b/src/freezethaw.rs similarity index 100% rename from bootupd/src/freezethaw.rs rename to src/freezethaw.rs diff --git a/bootupd/src/grub2/README.md b/src/grub2/README.md similarity index 100% rename from bootupd/src/grub2/README.md rename to src/grub2/README.md diff --git a/bootupd/src/grub2/configs.d/01_users.cfg b/src/grub2/configs.d/01_users.cfg similarity index 100% rename from bootupd/src/grub2/configs.d/01_users.cfg rename to src/grub2/configs.d/01_users.cfg diff --git a/bootupd/src/grub2/configs.d/10_blscfg.cfg b/src/grub2/configs.d/10_blscfg.cfg similarity index 100% rename from bootupd/src/grub2/configs.d/10_blscfg.cfg rename to src/grub2/configs.d/10_blscfg.cfg diff --git a/bootupd/src/grub2/configs.d/14_menu_show_once.cfg b/src/grub2/configs.d/14_menu_show_once.cfg similarity index 100% rename from bootupd/src/grub2/configs.d/14_menu_show_once.cfg rename to src/grub2/configs.d/14_menu_show_once.cfg diff --git a/bootupd/src/grub2/configs.d/30_uefi-firmware.cfg b/src/grub2/configs.d/30_uefi-firmware.cfg similarity index 100% rename from bootupd/src/grub2/configs.d/30_uefi-firmware.cfg rename to src/grub2/configs.d/30_uefi-firmware.cfg diff --git a/bootupd/src/grub2/configs.d/41_custom.cfg b/src/grub2/configs.d/41_custom.cfg similarity index 100% rename from bootupd/src/grub2/configs.d/41_custom.cfg rename to src/grub2/configs.d/41_custom.cfg diff --git a/bootupd/src/grub2/configs.d/README.md b/src/grub2/configs.d/README.md similarity index 100% rename from bootupd/src/grub2/configs.d/README.md rename to src/grub2/configs.d/README.md diff --git a/bootupd/src/grub2/grub-static-efi.cfg b/src/grub2/grub-static-efi.cfg similarity index 100% rename from bootupd/src/grub2/grub-static-efi.cfg rename to src/grub2/grub-static-efi.cfg diff --git a/bootupd/src/grub2/grub-static-pre.cfg b/src/grub2/grub-static-pre.cfg similarity index 100% rename from bootupd/src/grub2/grub-static-pre.cfg rename to src/grub2/grub-static-pre.cfg diff --git a/bootupd/src/grubconfigs.rs b/src/grubconfigs.rs similarity index 100% rename from bootupd/src/grubconfigs.rs rename to src/grubconfigs.rs diff --git a/bootupd/src/main.rs b/src/main.rs similarity index 100% rename from bootupd/src/main.rs rename to src/main.rs diff --git a/bootupd/src/model.rs b/src/model.rs similarity index 100% rename from bootupd/src/model.rs rename to src/model.rs diff --git a/bootupd/src/model_legacy.rs b/src/model_legacy.rs similarity index 100% rename from bootupd/src/model_legacy.rs rename to src/model_legacy.rs diff --git a/bootupd/src/ostreeutil.rs b/src/ostreeutil.rs similarity index 53% rename from bootupd/src/ostreeutil.rs rename to src/ostreeutil.rs index bc53a3a..4f26e2a 100755 --- a/bootupd/src/ostreeutil.rs +++ b/src/ostreeutil.rs @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Red Hat, Inc. + * Modified for Debian compatibility * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,51 +17,47 @@ use log::debug; target_arch = "riscv64" ))] pub(crate) const BOOT_PREFIX: &str = "usr/lib/ostree-boot"; -const LEGACY_RPMOSTREE_DBPATH: &str = "usr/share/rpm"; -const SYSIMAGE_RPM_DBPATH: &str = "usr/lib/sysimage/rpm"; -/// Returns true if the target directory contains at least one file that does -/// not start with `.` -fn is_nonempty_dir(path: impl AsRef) -> Result { - let path = path.as_ref(); - let it = match std::fs::read_dir(path) { - Ok(r) => r, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false), - Err(e) => return Err(e.into()), - }; - for ent in it { - let ent = ent?; - let name = ent.file_name(); - if name.as_encoded_bytes().starts_with(b".") { - continue; +/// Detect if this is a Debian-based system +fn is_debian_system(sysroot: &Path) -> bool { + // Check for Debian-specific files + let debian_files = [ + "etc/debian_version", + "var/lib/dpkg/status", + ]; + + for file in debian_files.iter() { + if sysroot.join(file).exists() { + return true; } - return Ok(true); } - Ok(false) + + // Check os-release content + if let Ok(content) = std::fs::read_to_string(sysroot.join("etc/os-release")) { + if content.contains("ID=debian") || + content.contains("ID=ubuntu") || + content.contains("ID=linuxmint") || + content.contains("ID=pop") { + return true; + } + } + + false } -pub(crate) fn rpm_cmd>(sysroot: P) -> Result { - let mut c = std::process::Command::new("rpm"); +/// Create dpkg command for Debian systems +pub(crate) fn dpkg_cmd>(sysroot: P) -> Result { + let c = std::process::Command::new("dpkg"); let sysroot = sysroot.as_ref(); - // Take the first non-empty database path - let mut arg = None; - for dbpath in [SYSIMAGE_RPM_DBPATH, LEGACY_RPMOSTREE_DBPATH] { - let dbpath = sysroot.join(dbpath); - if !is_nonempty_dir(&dbpath)? { - continue; - } - let mut s = std::ffi::OsString::new(); - s.push("--dbpath="); - s.push(dbpath.as_os_str()); - arg = Some(s); - break; - } - if let Some(arg) = arg { - debug!("Using dbpath {arg:?}"); - c.arg(arg); - } else { - debug!("Failed to find dbpath"); + + // Check if this is a Debian system + if !is_debian_system(sysroot) { + anyhow::bail!("Not a Debian system - dpkg command not available"); } + + // For OSTree systems, we might need to adjust paths + // but dpkg typically works with the standard paths + debug!("Using dpkg for Debian system"); Ok(c) } @@ -81,9 +78,10 @@ pub(crate) fn get_ostree_bootloader() -> Result> { return Ok(None); } else { let res = String::from_utf8(result.stdout) - .with_context(|| "decoding as UTF-8 output of ostree command")?; - let bootloader = res.trim_end().to_string(); - return Ok(Some(bootloader)); + .with_context(|| "decoding as UTF-8 output of ostree command")? + .trim_end() + .to_string(); + return Ok(Some(res)); } } diff --git a/src/packagesystem.rs b/src/packagesystem.rs new file mode 100755 index 0000000..f3a9875 --- /dev/null +++ b/src/packagesystem.rs @@ -0,0 +1,172 @@ +use std::collections::BTreeSet; +use std::path::Path; + +use anyhow::{bail, Context, Result}; +use chrono::prelude::*; + +use crate::model::*; + +/// Parse the output of `dpkg -S` to extract package names +fn parse_dpkg_s_output(output: &[u8]) -> Result { + let output_str = std::str::from_utf8(output)?; + // dpkg -S outputs "package: /path" format + // Package names can contain colons (e.g., "grub-efi-amd64:amd64") + // We need to find the colon that is followed by a space (start of file path) + let mut colon_pos = None; + for (i, ch) in output_str.char_indices() { + if ch == ':' { + // Check if the next character is a space + if let Some(next_char) = output_str[i + 1..].chars().next() { + if next_char == ' ' { + colon_pos = Some(i); + break; + } + } + } + } + + if let Some(pos) = colon_pos { + Ok(output_str[..pos].trim().to_string()) + } else { + bail!("Invalid dpkg -S output format: {}", output_str) + } +} + +/// Get package installation time from package.list file +fn get_package_install_time(package: &str) -> Result> { + let list_path = format!("/var/lib/dpkg/info/{}.list", package); + let metadata = std::fs::metadata(&list_path) + .with_context(|| format!("Failed to get metadata for package {}", package))?; + + // Use modification time as installation time + let modified = metadata.modified() + .with_context(|| format!("Failed to get modification time for package {}", package))?; + + Ok(DateTime::from(modified)) +} + +/// Parse dpkg output and extract package metadata +fn dpkg_parse_metadata(packages: &BTreeSet) -> Result { + if packages.is_empty() { + bail!("Failed to find any Debian packages matching files in source efidir"); + } + + let mut timestamps = BTreeSet::new(); + + // Get installation time for each package + for package in packages { + let timestamp = get_package_install_time(package)?; + timestamps.insert(timestamp); + } + + // Use the most recent timestamp + let largest_timestamp = timestamps.iter().last() + .ok_or_else(|| anyhow::anyhow!("No valid timestamps found"))?; + + // Create version string from package names + let version = packages.iter().fold("".to_string(), |mut s, n| { + if !s.is_empty() { + s.push(','); + } + s.push_str(n); + s + }); + + Ok(ContentMetadata { + timestamp: *largest_timestamp, + version, + }) +} + +/// Query the dpkg database and list the package and install times. +pub(crate) fn query_files( + sysroot_path: &str, + paths: impl IntoIterator, +) -> Result +where + T: AsRef, +{ + let mut packages = BTreeSet::new(); + let paths: Vec<_> = paths.into_iter().collect(); + + for path in &paths { + // Use dpkg -S to find which package owns the file + let mut cmd = std::process::Command::new("dpkg"); + cmd.args(["-S", "--root", sysroot_path]); + cmd.arg(path.as_ref()); + + let dpkgout = cmd.output()?; + if !dpkgout.status.success() { + // Skip files that don't belong to any package + continue; + } + + let package = parse_dpkg_s_output(&dpkgout.stdout)?; + packages.insert(package); + } + + if packages.is_empty() { + // If no packages found, try without --root for local system + for path in &paths { + let mut cmd = std::process::Command::new("dpkg"); + cmd.args(["-S"]); + cmd.arg(path.as_ref()); + + let dpkgout = cmd.output()?; + if dpkgout.status.success() { + let package = parse_dpkg_s_output(&dpkgout.stdout)?; + packages.insert(package); + } + } + } + + dpkg_parse_metadata(&packages) +} + +#[test] +fn test_parse_dpkg_s_output() { + let testdata = "grub-efi-amd64:amd64: /usr/lib/grub/x86_64-efi"; + let parsed = parse_dpkg_s_output(testdata.as_bytes()).unwrap(); + assert_eq!(parsed, "grub-efi-amd64:amd64"); + + let testdata2 = "shim-signed: /usr/lib/shim/shimx64.efi"; + let parsed2 = parse_dpkg_s_output(testdata2.as_bytes()).unwrap(); + assert_eq!(parsed2, "shim-signed"); + + // Test with different package name format + let testdata3 = "grub-efi-amd64: /usr/lib/grub/x86_64-efi"; + let parsed3 = parse_dpkg_s_output(testdata3.as_bytes()).unwrap(); + assert_eq!(parsed3, "grub-efi-amd64"); +} + +#[test] +fn test_dpkg_parse_metadata() { + // Mock package installation times for testing + let mock_time = Utc::now(); + + let mut packages = BTreeSet::new(); + packages.insert("grub-efi-amd64:amd64".to_string()); + packages.insert("shim-signed".to_string()); + + // For testing, we'll create a mock version that doesn't depend on actual files + let version = packages.iter().fold("".to_string(), |mut s, n| { + if !s.is_empty() { + s.push(','); + } + s.push_str(n); + s + }); + + // Create a mock ContentMetadata for testing + let mock_metadata = ContentMetadata { + timestamp: mock_time, + version, + }; + + assert_eq!( + mock_metadata.version, + "grub-efi-amd64:amd64,shim-signed" + ); + // Timestamp should be recent + assert!(mock_metadata.timestamp > DateTime::parse_from_rfc3339("2020-01-01T00:00:00Z").unwrap().with_timezone(&Utc)); +} diff --git a/bootupd/src/sha512string.rs b/src/sha512string.rs similarity index 100% rename from bootupd/src/sha512string.rs rename to src/sha512string.rs diff --git a/bootupd/src/util.rs b/src/util.rs similarity index 100% rename from bootupd/src/util.rs rename to src/util.rs diff --git a/bootupd/systemd/bootloader-update.service b/systemd/bootloader-update.service similarity index 100% rename from bootupd/systemd/bootloader-update.service rename to systemd/bootloader-update.service diff --git a/bootupd/tests/e2e-update/e2e-update-in-vm.sh b/tests/e2e-update/e2e-update-in-vm.sh similarity index 100% rename from bootupd/tests/e2e-update/e2e-update-in-vm.sh rename to tests/e2e-update/e2e-update-in-vm.sh diff --git a/bootupd/tests/e2e-update/e2e-update.sh b/tests/e2e-update/e2e-update.sh similarity index 100% rename from bootupd/tests/e2e-update/e2e-update.sh rename to tests/e2e-update/e2e-update.sh diff --git a/bootupd/tests/e2e-update/testrpmbuild.sh b/tests/e2e-update/testrpmbuild.sh similarity index 100% rename from bootupd/tests/e2e-update/testrpmbuild.sh rename to tests/e2e-update/testrpmbuild.sh diff --git a/bootupd/tests/fixtures/example-lsblk-output.json b/tests/fixtures/example-lsblk-output.json similarity index 100% rename from bootupd/tests/fixtures/example-lsblk-output.json rename to tests/fixtures/example-lsblk-output.json diff --git a/bootupd/tests/fixtures/example-state-v0-legacy.json b/tests/fixtures/example-state-v0-legacy.json similarity index 100% rename from bootupd/tests/fixtures/example-state-v0-legacy.json rename to tests/fixtures/example-state-v0-legacy.json diff --git a/bootupd/tests/fixtures/example-state-v0.json b/tests/fixtures/example-state-v0.json similarity index 100% rename from bootupd/tests/fixtures/example-state-v0.json rename to tests/fixtures/example-state-v0.json diff --git a/bootupd/tests/fixtures/example-status-v0.json b/tests/fixtures/example-status-v0.json similarity index 100% rename from bootupd/tests/fixtures/example-status-v0.json rename to tests/fixtures/example-status-v0.json diff --git a/bootupd/tests/kola/data/libtest.sh b/tests/kola/data/libtest.sh similarity index 100% rename from bootupd/tests/kola/data/libtest.sh rename to tests/kola/data/libtest.sh diff --git a/bootupd/tests/kola/raid1/config.bu b/tests/kola/raid1/config.bu similarity index 100% rename from bootupd/tests/kola/raid1/config.bu rename to tests/kola/raid1/config.bu diff --git a/bootupd/tests/kola/raid1/data/libtest.sh b/tests/kola/raid1/data/libtest.sh similarity index 100% rename from bootupd/tests/kola/raid1/data/libtest.sh rename to tests/kola/raid1/data/libtest.sh diff --git a/bootupd/tests/kola/raid1/test.sh b/tests/kola/raid1/test.sh similarity index 100% rename from bootupd/tests/kola/raid1/test.sh rename to tests/kola/raid1/test.sh diff --git a/bootupd/tests/kola/test-bootupd b/tests/kola/test-bootupd similarity index 100% rename from bootupd/tests/kola/test-bootupd rename to tests/kola/test-bootupd diff --git a/bootupd/tests/kolainst/Makefile b/tests/kolainst/Makefile similarity index 100% rename from bootupd/tests/kolainst/Makefile rename to tests/kolainst/Makefile diff --git a/bootupd/tests/tests/bootupctl-status-in-bootc.sh b/tests/tests/bootupctl-status-in-bootc.sh similarity index 100% rename from bootupd/tests/tests/bootupctl-status-in-bootc.sh rename to tests/tests/bootupctl-status-in-bootc.sh diff --git a/bootupd/tests/tests/move-content-to-usr.sh b/tests/tests/move-content-to-usr.sh similarity index 100% rename from bootupd/tests/tests/move-content-to-usr.sh rename to tests/tests/move-content-to-usr.sh diff --git a/bootupd/xtask/.gitignore b/xtask/.gitignore similarity index 100% rename from bootupd/xtask/.gitignore rename to xtask/.gitignore diff --git a/bootupd/xtask/Cargo.toml b/xtask/Cargo.toml similarity index 100% rename from bootupd/xtask/Cargo.toml rename to xtask/Cargo.toml diff --git a/bootupd/xtask/src/main.rs b/xtask/src/main.rs similarity index 100% rename from bootupd/xtask/src/main.rs rename to xtask/src/main.rs