apt-ostree/.notes/apt-dnf/rpm-ostree.md
robojerk d295f9bb4d Major milestone: Complete apt-ostree bootc compatibility and OCI integration
-  Real package installation (replaced mock installation)
-  Real OSTree commit creation from installed packages
-  OCI image creation from both commits and rootfs
-  Full bootc compatibility with proper labels
-  Comprehensive test suite (test-bootc-apt-ostree.sh)
-  Container tool validation (skopeo, podman)
-  Updated compatibility reports for Ubuntu Questing
-  Fixed OCI schema version and field naming issues
-  Temporary directory lifecycle fixes
-  Serde rename attributes for OCI JSON compliance

Ready for Aurora-style workflow deployment!
2025-07-20 21:06:44 +00:00

812 lines
No EOL
26 KiB
Markdown

# rpm-ostree DNF/RPM Package Management
## Overview
rpm-ostree uses DNF (Dandified Yum) and RPM for package management, providing a sophisticated integration between traditional RPM package management and OSTree's atomic deployment model. This document explains how rpm-ostree implements DNF/RPM package management.
## Core DNF Integration
### libdnf Integration
rpm-ostree uses libdnf for advanced package management capabilities:
```c
// libdnf integration in rpmostree-core.cxx
#include <dnf/dnf-context.h>
#include <dnf/dnf-goal.h>
#include <dnf/dnf-package.h>
#include <dnf/dnf-repo.h>
#include <dnf/dnf-sack.h>
class RpmOstreeDnfManager {
private:
DnfContext *dnf_context;
DnfGoal *dnf_goal;
DnfSack *dnf_sack;
public:
// Initialize DNF context for OSTree operations
gboolean initialize_dnf_context(
RpmOstreeSysroot *sysroot,
const char *deployment_path,
GCancellable *cancellable,
GError **error) {
// Create DNF context
dnf_context = dnf_context_new();
if (!dnf_context) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create DNF context");
return FALSE;
}
// Configure for OSTree deployment
dnf_context_set_install_root(dnf_context, deployment_path);
dnf_context_set_release_ver(dnf_context, "39");
dnf_context_set_platform_module(dnf_context, "platform:39");
// Load repositories
if (!dnf_context_setup(dnf_context, cancellable, error)) {
return FALSE;
}
// Get DNF sack for package operations
dnf_sack = dnf_context_get_sack(dnf_context);
if (!dnf_sack) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get DNF sack");
return FALSE;
}
return TRUE;
}
// Resolve package dependencies
gboolean resolve_package_dependencies(
const char *package_name,
GPtrArray **resolved_packages,
GCancellable *cancellable,
GError **error) {
// Create DNF goal
dnf_goal = dnf_goal_new(dnf_context);
if (!dnf_goal) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create DNF goal");
return FALSE;
}
// Add package to goal
if (!dnf_goal_install(dnf_goal, package_name)) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to add package %s to goal", package_name);
return FALSE;
}
// Resolve dependencies
DnfGoalActions actions = dnf_goal_resolve(dnf_goal, error);
if (actions == DNF_GOAL_ACTION_ERROR) {
return FALSE;
}
// Get resolved packages
GPtrArray *packages = dnf_goal_get_packages(dnf_goal, DNF_PACKAGE_INFO_INSTALL);
if (!packages) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get resolved packages");
return FALSE;
}
*resolved_packages = packages;
return TRUE;
}
// Download packages
gboolean download_packages(
GPtrArray *packages,
const char *download_path,
GCancellable *cancellable,
GError **error) {
// Download packages to specified path
return dnf_context_download_packages(
dnf_context, packages, download_path, cancellable, error);
}
// Get package information
gboolean get_package_info(
const char *package_name,
GVariant **package_info,
GCancellable *cancellable,
GError **error) {
// Find package in sack
DnfPackage *package = dnf_sack_get_package_by_name(dnf_sack, package_name);
if (!package) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Package %s not found", package_name);
return FALSE;
}
// Create package info variant
*package_info = g_variant_new("(ssssss)",
dnf_package_get_name(package),
dnf_package_get_version(package),
dnf_package_get_release(package),
dnf_package_get_arch(package),
dnf_package_get_summary(package),
dnf_package_get_description(package));
dnf_package_free(package);
return TRUE;
}
};
```
### RPM Package Processing
rpm-ostree processes RPM packages for OSTree integration:
```c
// RPM processing in rpmostree-core.cxx
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
#include <rpm/rpmdb.h>
class RpmOstreeRpmProcessor {
public:
// Extract RPM package to filesystem
gboolean extract_rpm_package(
const char *rpm_path,
const char *extract_path,
GCancellable *cancellable,
GError **error) {
// Initialize RPM
if (rpmReadConfigFiles(NULL, NULL) != 0) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to read RPM configuration");
return FALSE;
}
// Open RPM transaction set
rpmts ts = rpmtsCreate();
if (!ts) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create RPM transaction set");
return FALSE;
}
// Set root directory for extraction
rpmtsSetRootDir(ts, extract_path);
// Add RPM to transaction
FD_t fd = Fopen(rpm_path, "r");
if (!fd) {
rpmtsFree(ts);
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to open RPM file %s", rpm_path);
return FALSE;
}
Header header;
if (rpmReadPackageFile(ts, fd, rpm_path, &header) != RPMRC_OK) {
Fclose(fd);
rpmtsFree(ts);
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to read RPM package %s", rpm_path);
return FALSE;
}
// Extract package files
rpmtsSetNotifyCallback(ts, rpm_ostree_extract_notify, NULL);
if (rpmtsRun(ts, NULL, 0) != 0) {
Fclose(fd);
rpmtsFree(ts);
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to extract RPM package %s", rpm_path);
return FALSE;
}
Fclose(fd);
rpmtsFree(ts);
return TRUE;
}
// Process RPM scripts
gboolean process_rpm_scripts(
const char *rpm_path,
const char *deployment_path,
const char *script_type,
GCancellable *cancellable,
GError **error) {
// Extract scripts from RPM
g_autofree char *script_path = g_strdup_printf(
"%s/var/lib/rpm-%s", deployment_path, script_type);
// Create script directory
g_mkdir_with_parents(script_path, 0755);
// Extract and execute scripts
return extract_and_execute_scripts(rpm_path, script_path, script_type, error);
}
private:
// RPM extraction notification callback
static int rpm_ostree_extract_notify(
const void *h,
const rpmCallbackType what,
const rpm_loff_t amount,
const rpm_loff_t total,
fnpyKey key,
rpmCallbackData data) {
// Handle extraction progress
switch (what) {
case RPMCALLBACK_INST_PROGRESS:
// Report progress
break;
case RPMCALLBACK_INST_START:
// File extraction started
break;
case RPMCALLBACK_INST_OPEN_FILE:
// File opened for extraction
break;
}
return 0;
}
// Extract and execute RPM scripts
gboolean extract_and_execute_scripts(
const char *rpm_path,
const char *script_path,
const char *script_type,
GError **error) {
// Extract scripts from RPM package
g_autofree char *extract_cmd = g_strdup_printf(
"rpm2cpio %s | cpio -idmv '*.%s'", rpm_path, script_type);
// Execute extraction
return rpmostree_sysroot_run_sync(
sysroot, extract_cmd, cancellable, error);
}
};
```
## Package Layering System
### Layer Management
rpm-ostree implements sophisticated package layering:
```c
// Layer management in rpmostree-core.cxx
class RpmOstreeLayerManager {
public:
// Create package layer
gboolean create_package_layer(
RpmOstreeSysroot *sysroot,
const char *base_commit,
const char *new_commit,
GPtrArray *packages,
GCancellable *cancellable,
GError **error) {
// 1. Extract base filesystem from OSTree commit
g_autoptr(OstreeRepo) repo = rpmostree_sysroot_get_repo(sysroot);
g_autoptr(GFile) base_tree = ostree_repo_read_commit(repo, base_commit, NULL, NULL, error);
// 2. Create temporary directory for layer
g_autofree char *layer_path = g_dir_make_tmp("rpm-ostree-layer-XXXXXX", error);
if (!layer_path) {
return FALSE;
}
// 3. Apply RPM packages to layer
for (guint i = 0; i < packages->len; i++) {
DnfPackage *package = g_ptr_array_index(packages, i);
const char *package_name = dnf_package_get_name(package);
// Download and extract package
if (!download_and_extract_package(package, layer_path, cancellable, error)) {
return FALSE;
}
// Process package scripts
if (!process_package_scripts(package, layer_path, cancellable, error)) {
return FALSE;
}
}
// 4. Merge layer with base tree
g_autoptr(GFile) layered_tree = merge_layer_with_base(base_tree, layer_path, error);
// 5. Create new OSTree commit
g_autofree char *new_commit_checksum = ostree_repo_write_commit(
repo, layered_tree, "Package layer update", NULL, NULL, error);
// 6. Update ref to point to new commit
ostree_repo_set_ref(repo, NULL, "fedora/39/x86_64/silverblue", new_commit_checksum, NULL, error);
return TRUE;
}
// Remove package layer
gboolean remove_package_layer(
RpmOstreeSysroot *sysroot,
const char *base_commit,
const char *new_commit,
GPtrArray *packages,
GCancellable *cancellable,
GError **error) {
// Create new deployment without specified packages
return create_deployment_without_packages(
sysroot, base_commit, new_commit, packages, cancellable, error);
}
private:
// Download and extract package
gboolean download_and_extract_package(
DnfPackage *package,
const char *layer_path,
GCancellable *cancellable,
GError **error) {
// Download package
const char *package_name = dnf_package_get_name(package);
g_autofree char *download_path = g_build_filename(layer_path, package_name, NULL);
if (!dnf_package_download(package, download_path, cancellable, error)) {
return FALSE;
}
// Extract package
return extract_rpm_package(download_path, layer_path, cancellable, error);
}
// Process package scripts
gboolean process_package_scripts(
DnfPackage *package,
const char *layer_path,
GCancellable *cancellable,
GError **error) {
const char *package_name = dnf_package_get_name(package);
// Process pre-installation scripts
g_autofree char *preinst_path = g_strdup_printf(
"%s/var/lib/rpm-preinst/%s", layer_path, package_name);
if (g_file_test(preinst_path, G_FILE_TEST_EXISTS)) {
if (!execute_package_script(preinst_path, layer_path, package_name, cancellable, error)) {
return FALSE;
}
}
// Process post-installation scripts
g_autofree char *postinst_path = g_strdup_printf(
"%s/var/lib/rpm-postinst/%s", layer_path, package_name);
if (g_file_test(postinst_path, G_FILE_TEST_EXISTS)) {
if (!execute_package_script(postinst_path, layer_path, package_name, cancellable, error)) {
return FALSE;
}
}
return TRUE;
}
// Merge layer with base tree
GFile* merge_layer_with_base(
GFile *base_tree,
const char *layer_path,
GError **error) {
// Create merged tree
g_autoptr(GFile) merged_tree = g_file_new_for_path("/tmp/merged-tree");
// Copy base tree
if (!copy_tree(base_tree, merged_tree, error)) {
return NULL;
}
// Overlay layer on top
if (!overlay_layer(merged_tree, layer_path, error)) {
return NULL;
}
return g_steal_pointer(&merged_tree);
}
};
```
## Dependency Resolution
### Advanced Dependency Resolution
rpm-ostree uses DNF's advanced dependency resolver:
```c
// Dependency resolution in rpmostree-core.cxx
class RpmOstreeDependencyResolver {
public:
// Resolve complex dependencies
gboolean resolve_complex_dependencies(
GPtrArray *requested_packages,
GPtrArray **resolved_packages,
GPtrArray **conflicts,
GCancellable *cancellable,
GError **error) {
// Create DNF goal for complex resolution
DnfGoal *goal = dnf_goal_new(dnf_context);
if (!goal) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create DNF goal");
return FALSE;
}
// Add requested packages to goal
for (guint i = 0; i < requested_packages->len; i++) {
const char *package = g_ptr_array_index(requested_packages, i);
if (!dnf_goal_install(goal, package)) {
dnf_goal_free(goal);
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to add package %s to goal", package);
return FALSE;
}
}
// Resolve dependencies
DnfGoalActions actions = dnf_goal_resolve(goal, error);
if (actions == DNF_GOAL_ACTION_ERROR) {
dnf_goal_free(goal);
return FALSE;
}
// Get resolved packages
GPtrArray *packages = dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_INSTALL);
if (!packages) {
dnf_goal_free(goal);
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to get resolved packages");
return FALSE;
}
// Get conflicts
GPtrArray *conflict_packages = dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_CONFLICT);
*resolved_packages = packages;
*conflicts = conflict_packages;
dnf_goal_free(goal);
return TRUE;
}
// Check for dependency conflicts
gboolean check_dependency_conflicts(
GPtrArray *packages,
GPtrArray **conflicts,
GCancellable *cancellable,
GError **error) {
// Create goal for conflict checking
DnfGoal *goal = dnf_goal_new(dnf_context);
if (!goal) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create DNF goal");
return FALSE;
}
// Add packages to goal
for (guint i = 0; i < packages->len; i++) {
const char *package = g_ptr_array_index(packages, i);
dnf_goal_install(goal, package);
}
// Check for conflicts
DnfGoalActions actions = dnf_goal_resolve(goal, error);
if (actions == DNF_GOAL_ACTION_ERROR) {
dnf_goal_free(goal);
return FALSE;
}
// Get conflicts
GPtrArray *conflict_packages = dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_CONFLICT);
*conflicts = conflict_packages;
dnf_goal_free(goal);
return TRUE;
}
// Resolve file conflicts
gboolean resolve_file_conflicts(
GPtrArray *packages,
GPtrArray **conflict_files,
GCancellable *cancellable,
GError **error) {
// Check for file conflicts between packages
g_autoptr(GHashTable) file_owners = g_hash_table_new_full(
g_str_hash, g_str_equal, g_free, g_free);
for (guint i = 0; i < packages->len; i++) {
DnfPackage *package = g_ptr_array_index(packages, i);
// Get package files
GPtrArray *files = dnf_package_get_files(package);
for (guint j = 0; j < files->len; j++) {
const char *file = g_ptr_array_index(files, j);
const char *package_name = dnf_package_get_name(package);
// Check if file is already owned by another package
const char *existing_owner = g_hash_table_lookup(file_owners, file);
if (existing_owner && strcmp(existing_owner, package_name) != 0) {
// File conflict detected
g_ptr_array_add(*conflict_files, g_strdup(file));
} else {
g_hash_table_insert(file_owners, g_strdup(file), g_strdup(package_name));
}
}
}
return TRUE;
}
};
```
## Package Override System
### Override Management
rpm-ostree implements package overrides for customization:
```c
// Override management in rpmostree-core.cxx
class RpmOstreeOverrideManager {
public:
// Add package override
gboolean add_package_override(
RpmOstreeSysroot *sysroot,
const char *package_name,
const char *override_type,
GCancellable *cancellable,
GError **error) {
// Create override configuration
g_autofree char *override_config = g_strdup_printf(
"[override]\n"
"package=%s\n"
"type=%s\n",
package_name, override_type);
// Write override configuration
g_autofree char *override_path = g_build_filename(
"/etc/rpm-ostree/overrides", package_name, NULL);
g_mkdir_with_parents(g_path_get_dirname(override_path), 0755);
if (!g_file_set_contents(override_path, override_config, -1, error)) {
return FALSE;
}
return TRUE;
}
// Remove package override
gboolean remove_package_override(
RpmOstreeSysroot *sysroot,
const char *package_name,
GCancellable *cancellable,
GError **error) {
// Remove override configuration
g_autofree char *override_path = g_build_filename(
"/etc/rpm-ostree/overrides", package_name, NULL);
if (g_file_test(override_path, G_FILE_TEST_EXISTS)) {
if (g_unlink(override_path) != 0) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to remove override for package %s", package_name);
return FALSE;
}
}
return TRUE;
}
// List package overrides
gboolean list_package_overrides(
RpmOstreeSysroot *sysroot,
GPtrArray **overrides,
GCancellable *cancellable,
GError **error) {
// Scan override directory
const char *override_dir = "/etc/rpm-ostree/overrides";
if (!g_file_test(override_dir, G_FILE_TEST_IS_DIR)) {
*overrides = g_ptr_array_new();
return TRUE;
}
GDir *dir = g_dir_open(override_dir, 0, error);
if (!dir) {
return FALSE;
}
*overrides = g_ptr_array_new();
const char *name;
while ((name = g_dir_read_name(dir))) {
g_autofree char *override_path = g_build_filename(override_dir, name, NULL);
if (g_file_test(override_path, G_FILE_TEST_IS_REGULAR)) {
// Read override configuration
g_autoptr(GKeyFile) key_file = g_key_file_new();
if (g_key_file_load_from_file(key_file, override_path, G_KEY_FILE_NONE, error)) {
g_autofree char *package = g_key_file_get_string(key_file, "override", "package", error);
g_autofree char *override_type = g_key_file_get_string(key_file, "override", "type", error);
if (package && override_type) {
g_autofree char *override_info = g_strdup_printf("%s (%s)", package, override_type);
g_ptr_array_add(*overrides, g_steal_pointer(&override_info));
}
}
}
}
g_dir_close(dir);
return TRUE;
}
};
```
## Performance Optimizations
### Package Caching
rpm-ostree implements sophisticated package caching:
```c
// Package caching in rpmostree-core.cxx
class RpmOstreePackageCache {
private:
GHashTable *package_cache;
const char *cache_dir;
public:
// Initialize package cache
gboolean initialize_package_cache(
const char *cache_directory,
GCancellable *cancellable,
GError **error) {
cache_dir = cache_directory;
package_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
// Create cache directory
g_mkdir_with_parents(cache_dir, 0755);
// Load existing cache
return load_existing_cache(error);
}
// Cache package
gboolean cache_package(
const char *package_name,
const char *package_path,
GCancellable *cancellable,
GError **error) {
// Check if package is already cached
if (g_hash_table_lookup(package_cache, package_name)) {
return TRUE; // Already cached
}
// Copy package to cache
g_autofree char *cached_path = g_build_filename(cache_dir, package_name, NULL);
if (!g_file_copy(package_path, cached_path, G_FILE_COPY_NONE, cancellable, NULL, NULL, error)) {
return FALSE;
}
// Add to cache table
g_hash_table_insert(package_cache, g_strdup(package_name), g_strdup(cached_path));
return TRUE;
}
// Get cached package
const char* get_cached_package(const char *package_name) {
return g_hash_table_lookup(package_cache, package_name);
}
// Clean old cache entries
gboolean clean_cache(
guint max_age_days,
GCancellable *cancellable,
GError **error) {
// Remove old cache entries
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, package_cache);
while (g_hash_table_iter_next(&iter, &key, &value)) {
const char *package_name = key;
const char *package_path = value;
// Check file age
GFile *file = g_file_new_for_path(package_path);
GFileInfo *info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
G_FILE_QUERY_INFO_NONE, cancellable, error);
if (info) {
GDateTime *mod_time = g_file_info_get_modification_date_time(info);
GDateTime *now = g_date_time_new_now_local();
GTimeSpan age = g_date_time_difference(now, mod_time);
guint age_days = age / (G_TIME_SPAN_DAY);
if (age_days > max_age_days) {
// Remove old cache entry
g_unlink(package_path);
g_hash_table_iter_remove(&iter);
}
g_date_time_unref(now);
g_date_time_unref(mod_time);
g_object_unref(info);
}
g_object_unref(file);
}
return TRUE;
}
private:
// Load existing cache
gboolean load_existing_cache(GError **error) {
GDir *dir = g_dir_open(cache_dir, 0, error);
if (!dir) {
return FALSE;
}
const char *name;
while ((name = g_dir_read_name(dir))) {
g_autofree char *package_path = g_build_filename(cache_dir, name, NULL);
if (g_file_test(package_path, G_FILE_TEST_IS_REGULAR)) {
g_hash_table_insert(package_cache, g_strdup(name), g_strdup(package_path));
}
}
g_dir_close(dir);
return TRUE;
}
};
```
## Future Enhancements
### Planned Features
1. **Enhanced Dependency Resolution**: Improved conflict detection and resolution
2. **Package Signing**: GPG signature verification for packages
3. **Delta Updates**: Efficient package updates using deltas
4. **Repository Management**: Advanced repository configuration and management
### Integration Roadmap
- **Phase 1**: Core DNF/RPM integration (✅ Complete)
- **Phase 2**: Advanced dependency resolution (✅ Complete)
- **Phase 3**: Package caching and optimization (✅ Complete)
- **Phase 4**: Enhanced security features (🔄 In Progress)
- **Phase 5**: Delta update support (📋 Planned)