Overview
The nbc update command implements an A/B (dual root) update system that enables safe, atomic system updates with automatic rollback capability.
How It Works
Partition Layout
/dev/sdX1 - EFI (2GB) - Boot files
/dev/sdX2 - Boot (1GB) - Kernels and initramfs
/dev/sdX3 - Root1 (12GB) - Primary root filesystem
/dev/sdX4 - Root2 (12GB) - Secondary root filesystem
/dev/sdX5 - Var (remaining) - Shared /var data
Update Process
-
Detect Active Partition
- Reads
/proc/cmdlineto determine which root partition is currently booted - Identifies the inactive partition as the update target
- Reads
-
Check If Update Needed
- Compares installed image digest with remote image digest
- Skips update if digests match (system is up-to-date)
- Use
--forceto reinstall even if up-to-date
-
Pull New Image
- Downloads the latest container image using go-containerregistry
- Alternatively, uses a pre-staged image from local cache (see
nbc download --for-update)
-
Extract to Inactive Partition
- Mounts the inactive root partition
- Clears existing content
- Extracts new container filesystem
-
Update Bootloader
- Updates GRUB configuration to boot from the new partition
- Sets new partition as default boot option
- Keeps old partition as fallback in boot menu
-
Reboot to Activate
- Next boot uses the updated partition
- Previous partition remains available for rollback
Usage
Check for Updates
# Check if an update is available without installing
sudo nbc update --check
Basic Update
sudo nbc update \
--image quay.io/example/myimage:latest
The device is automatically detected from the running system. Override with --device if needed.
Update with Custom Kernel Arguments
sudo nbc update \
--image localhost/custom-image \
--karg console=ttyS0 \
--karg quiet
Skip Image Pull (Use Cached)
sudo nbc update \
--image localhost/myimage \
--skip-pull
Staged Updates (Download Now, Apply Later)
Download an update image without applying it:
# Download update using system's configured image
sudo nbc download --for-update
# Or specify a different image
sudo nbc download --for-update --image quay.io/example/myimage:v2.0
Apply the staged update later:
# Apply the previously downloaded update
sudo nbc update --local-image
Check staged update status:
nbc status --json
The staged update cache is automatically cleared after successful application.
Dry Run Test
nbc update \
--image test/image \
--dry-run
Boot Menu Options
After an update, the GRUB boot menu provides two options:
1. Linux (Updated) - New system (default)
2. Linux (Previous) - Previous system (rollback)
Rollback Procedure
If the new system has issues, you can rollback by:
- At Boot Time: Select "Linux (Previous)" from GRUB menu
- After Booting: Run another update to switch back
File Organization
Implementation Files
-
pkg/update.go - Update logic and A/B switching
GetActiveRootPartition()- Detects current rootGetInactiveRootPartition()- Finds update targetSystemUpdater- Main update orchestratorUpdateBootloader()- GRUB configuration updates
-
cmd/update.go - CLI command interface
Key Functions
Active Partition Detection
func GetActiveRootPartition() (string, error)
Reads /proc/cmdline to determine which root partition the system booted from.
Inactive Partition Selection
func GetInactiveRootPartition(scheme *PartitionScheme) (string, bool, error)
Returns the partition that should receive the update.
Update Execution
func (u *SystemUpdater) Update() error
Performs the complete update:
- Mount target partition
- Clear old content
- Extract new filesystem
- Update bootloader configuration
Advantages
- Atomic Updates: Either fully succeeds or fully fails - no partial states
- Zero Downtime: System remains operational during update
- Instant Rollback: Switch back to previous version at boot time
- Safe Testing: New system can be tested before commitment
- Shared Data: /var partition is shared, preserving application data
Safety Features
- Confirmation Prompt: Requires explicit "yes" to proceed
- Dry Run Mode: Test without making changes
- Separate Partitions: Update failure doesn't affect running system
- Boot Menu: Always provides fallback option
- Data Preservation: /var remains untouched during updates
Update Workflow Example
# Initial installation (creates both root partitions)
sudo nbc install \
--image myimage:v1.0 \
--device /dev/sda
# Reboot into root1 (partition 3)
# First update (writes to root2 - partition 4)
sudo nbc update \
--image myimage:v1.1 \
--device /dev/sda
# Reboot into root2 (partition 4)
# root1 still has v1.0 for rollback
# Second update (writes back to root1 - partition 3)
sudo nbc update \
--image myimage:v1.2 \
--device /dev/sda
# Reboot into root1 (partition 3)
# root2 still has v1.1 for rollback
Technical Details
Partition State Detection
The system determines the active partition by parsing kernel command line:
$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.5.0 root=UUID=xxx-xxx-xxx ro console=tty0
The UUID is matched against partition UUIDs to identify which root is active.
GRUB Configuration
After update, GRUB config provides both options:
set timeout=5
set default=0
menuentry 'Linux (Updated)' {
linux /vmlinuz-6.5.0 root=UUID=new-uuid ro console=tty0
initrd /initramfs-6.5.0.img
}
menuentry 'Linux (Previous)' {
linux /vmlinuz-6.5.0 root=UUID=old-uuid ro console=tty0
initrd /initramfs-6.5.0.img
}
Shared /var Partition
Both root filesystems mount the same /var partition, ensuring:
- Application data persists across updates
- Logs are maintained
- Databases remain accessible
- Configuration in /var is preserved
Encryption Support
A/B updates fully support LUKS-encrypted systems. The encryption configuration is stored in /var/lib/nbc/state/config.json during installation and automatically loaded during updates.
For encrypted systems, the update process:
- Reads encryption config from the system config file
- Generates correct LUKS kernel arguments for each root partition:
rd.luks.uuid- LUKS container UUIDrd.luks.name- Mapper device name (root1, root2, or var)rd.luks.options- TPM2 unlock options (if enabled)
- Creates separate boot entries with the appropriate LUKS UUIDs for:
- Target partition (the one being updated)
- Active partition (for rollback)
The system config stores LUKS UUIDs for all partitions:
{
"encryption": {
"enabled": true,
"tpm2": true,
"root1_luks_uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"root2_luks_uuid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
"var_luks_uuid": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"
}
}
This ensures that after an update, both boot entries (new and previous) have the correct LUKS kernel arguments for their respective root partitions.
Comparison to Other Systems
vs Traditional Package Updates
- Atomic (all-or-nothing)
- Instant rollback
- No package conflicts
vs OSTree/rpm-ostree
- Simpler implementation
- Standard ext4 filesystems
- Larger disk space requirement
vs Docker/Kubernetes Updates
- Full system updates (not just containers)
- Includes kernel and bootloader
- Bare metal support
Future Enhancements
Potential improvements:
- Automatic health checks before switching
- Scheduled updates
- Multi-version retention (keep more than 2 versions)
- Delta updates (only transfer changes)
- Automatic rollback on boot failure
- Status command to show active partition
- Update history tracking