Restructure project layout for better CI/CD integration
- Flattened nested bootupd/bootupd/ structure to root level - Moved all core project files to root directory - Added proper Debian packaging structure (debian/ directory) - Created build scripts and CI configuration - Improved project organization for CI/CD tools - All Rust source, tests, and configuration now at root level - Added GitHub Actions workflow for automated testing - Maintained all original functionality while improving structure
This commit is contained in:
parent
5e8730df43
commit
aaf662d5b1
87 changed files with 1334 additions and 570 deletions
0
bootupd/.gitignore → .gitignore
vendored
0
bootupd/.gitignore → .gitignore
vendored
0
bootupd/Cargo.lock → Cargo.lock
generated
0
bootupd/Cargo.lock → Cargo.lock
generated
230
README.md
230
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.
|
||||
|
|
|
|||
250
bootupd.md
Normal file
250
bootupd.md
Normal file
|
|
@ -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<Vec<String>>) -> Result<Option<Adoptable>>;
|
||||
fn install(&self, src_root: &openat::Dir, dest_root: &str, device: &str, update_firmware: bool) -> Result<InstalledContent>;
|
||||
fn run_update(&self, rootcxt: &RootContext, current: &InstalledContent) -> Result<InstalledContent>;
|
||||
// ... 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<String, InstalledContent>,
|
||||
pub(crate) pending: Option<BTreeMap<String, ContentMetadata>>,
|
||||
pub(crate) static_configs: Option<ContentMetadata>,
|
||||
}
|
||||
```
|
||||
|
||||
- **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<Utc>,
|
||||
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<StateLockGuard>
|
||||
```
|
||||
|
||||
- **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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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: []
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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
|
||||
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||
|
||||
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 <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||
|
|
@ -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<Utc>,
|
||||
}
|
||||
|
||||
/// Path to the file, see above
|
||||
const ALEPH_PATH: &str = "sysroot/.coreos-aleph-version.json";
|
||||
|
||||
pub(crate) fn get_aleph_version(root: &Path) -> Result<Option<AlephWithTimestamp>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ContentMetadata> {
|
||||
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::<Result<BTreeMap<&str, DateTime<Utc>>>>()?;
|
||||
if pkgs.is_empty() {
|
||||
bail!("Failed to find any RPM packages matching files in source efidir");
|
||||
}
|
||||
let timestamps: BTreeSet<&DateTime<Utc>> = 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<T>(
|
||||
sysroot_path: &str,
|
||||
paths: impl IntoIterator<Item = T>,
|
||||
) -> Result<ContentMetadata>
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
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"
|
||||
);
|
||||
}
|
||||
382
deb-bootupd.md
Normal file
382
deb-bootupd.md
Normal file
|
|
@ -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 <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**:
|
||||
```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/`
|
||||
8
debian/changelog
vendored
Normal file
8
debian/changelog
vendored
Normal file
|
|
@ -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 <debian-bootupd@lists.debian.org> $(date -R)
|
||||
30
debian/control
vendored
Normal file
30
debian/control
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
Source: deb-bootupd
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Debian Bootupd Team <debian-bootupd@lists.debian.org>
|
||||
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
|
||||
15
debian/rules
vendored
Executable file
15
debian/rules
vendored
Executable file
|
|
@ -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
|
||||
20
scripts/build.sh
Executable file
20
scripts/build.sh
Executable file
|
|
@ -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"
|
||||
|
|
@ -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(
|
||||
|
|
@ -166,7 +166,7 @@ pub(crate) fn query_adopt_state() -> Result<Option<Adoptable>> {
|
|||
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 {
|
||||
187
src/coreos.rs
Executable file
187
src/coreos.rs
Executable file
|
|
@ -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<String>,
|
||||
#[serde(alias = "ostree-commit")]
|
||||
pub(crate) ostree_commit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SystemVersionWithTimestamp {
|
||||
pub(crate) version_info: SystemVersion,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) ts: chrono::DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 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<Option<SystemVersionWithTimestamp>> {
|
||||
// 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<Option<SystemVersionWithTimestamp>> {
|
||||
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<Option<SystemVersionWithTimestamp>> {
|
||||
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<Option<SystemVersionWithTimestamp>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Path>) -> Result<bool> {
|
||||
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<P: AsRef<Path>>(sysroot: P) -> Result<std::process::Command> {
|
||||
let mut c = std::process::Command::new("rpm");
|
||||
/// Create dpkg command for Debian systems
|
||||
pub(crate) fn dpkg_cmd<P: AsRef<Path>>(sysroot: P) -> Result<std::process::Command> {
|
||||
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<Option<String>> {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
||||
172
src/packagesystem.rs
Executable file
172
src/packagesystem.rs
Executable file
|
|
@ -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<String> {
|
||||
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<DateTime<Utc>> {
|
||||
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<String>) -> Result<ContentMetadata> {
|
||||
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<T>(
|
||||
sysroot_path: &str,
|
||||
paths: impl IntoIterator<Item = T>,
|
||||
) -> Result<ContentMetadata>
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
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));
|
||||
}
|
||||
0
bootupd/xtask/.gitignore → xtask/.gitignore
vendored
0
bootupd/xtask/.gitignore → xtask/.gitignore
vendored
Loading…
Add table
Add a link
Reference in a new issue