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("/"))? {
|
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(
|
#[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("/"))? {
|
if let Some(coreos_aleph) = crate::coreos::get_aleph_version(Path::new("/"))? {
|
||||||
let meta = ContentMetadata {
|
let meta = ContentMetadata {
|
||||||
timestamp: coreos_aleph.ts,
|
timestamp: coreos_aleph.ts,
|
||||||
version: coreos_aleph.aleph.version,
|
version: coreos_aleph.version_info.version,
|
||||||
};
|
};
|
||||||
log::trace!("Adoptable: {:?}", &meta);
|
log::trace!("Adoptable: {:?}", &meta);
|
||||||
return Ok(Some(Adoptable {
|
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.
|
* Copyright (C) 2020 Red Hat, Inc.
|
||||||
|
* Modified for Debian compatibility
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -16,51 +17,47 @@ use log::debug;
|
||||||
target_arch = "riscv64"
|
target_arch = "riscv64"
|
||||||
))]
|
))]
|
||||||
pub(crate) const BOOT_PREFIX: &str = "usr/lib/ostree-boot";
|
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
|
/// Detect if this is a Debian-based system
|
||||||
/// not start with `.`
|
fn is_debian_system(sysroot: &Path) -> bool {
|
||||||
fn is_nonempty_dir(path: impl AsRef<Path>) -> Result<bool> {
|
// Check for Debian-specific files
|
||||||
let path = path.as_ref();
|
let debian_files = [
|
||||||
let it = match std::fs::read_dir(path) {
|
"etc/debian_version",
|
||||||
Ok(r) => r,
|
"var/lib/dpkg/status",
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),
|
];
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
for file in debian_files.iter() {
|
||||||
for ent in it {
|
if sysroot.join(file).exists() {
|
||||||
let ent = ent?;
|
return true;
|
||||||
let name = ent.file_name();
|
|
||||||
if name.as_encoded_bytes().starts_with(b".") {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rpm_cmd<P: AsRef<Path>>(sysroot: P) -> Result<std::process::Command> {
|
// Check os-release content
|
||||||
let mut c = std::process::Command::new("rpm");
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
let sysroot = sysroot.as_ref();
|
||||||
// Take the first non-empty database path
|
|
||||||
let mut arg = None;
|
// Check if this is a Debian system
|
||||||
for dbpath in [SYSIMAGE_RPM_DBPATH, LEGACY_RPMOSTREE_DBPATH] {
|
if !is_debian_system(sysroot) {
|
||||||
let dbpath = sysroot.join(dbpath);
|
anyhow::bail!("Not a Debian system - dpkg command not available");
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
Ok(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,9 +78,10 @@ pub(crate) fn get_ostree_bootloader() -> Result<Option<String>> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
} else {
|
} else {
|
||||||
let res = String::from_utf8(result.stdout)
|
let res = String::from_utf8(result.stdout)
|
||||||
.with_context(|| "decoding as UTF-8 output of ostree command")?;
|
.with_context(|| "decoding as UTF-8 output of ostree command")?
|
||||||
let bootloader = res.trim_end().to_string();
|
.trim_end()
|
||||||
return Ok(Some(bootloader));
|
.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