- Added 20-daemon-integration.sh scriptlet for D-Bus and daemon lifecycle management - Updated 99-main.sh with new daemon subcommands (start, stop, status, install, uninstall, test, layer, deploy, upgrade, rollback) - Enhanced help and usage text for daemon integration - Fixed bash syntax errors in daemon integration scriptlet - Updated compile.sh to include daemon integration in build process - Updated .gitignore to exclude src/rpm-ostree/ reference source - Updated CHANGELOG.md and TODO.md to document daemon integration milestone - Removed src/rpm-ostree/ from git tracking (reference only, not committed)
10 KiB
rpm-ostree Daemon Architecture
Overview
rpm-ostree uses a client/daemon architecture to ensure safe, serialized system operations. This model provides transaction safety, enables unprivileged operations via polkit, and allows integration with other system management tools.
Basic Architecture Recap
rpm-ostree operates in two main contexts:
- Compose servers: Generate OSTree commits from RPMs
- Client systems: Consume OSTree commits for transactional upgrades
Both contexts use the same core processes for converting RPMs to OSTree commits, but client systems add the daemon layer for safe operations.
The rpm-ostree Daemon
Purpose and Benefits
The daemon architecture provides:
- Serialization/locking: Only one system mutation at a time
- Multi-client support: Other tools can manage rpm-ostree systems
- Unprivileged operations: Users can make system changes via polkit
- Transaction safety: Atomic operations with rollback capability
Service Configuration
The daemon runs as a systemd service:
# /etc/systemd/system/rpm-ostreed.service
[Unit]
Description=rpm-ostree daemon
After=network.target
[Service]
Type=dbus
BusName=org.projectatomic.rpmostree1
ExecStart=/usr/bin/rpm-ostreed
Restart=on-failure
[Install]
WantedBy=multi-user.target
D-Bus Integration
The daemon owns the org.projectatomic.rpmostree1 D-Bus name:
<!-- D-Bus interface definition -->
<interface name="org.projectatomic.rpmostree1">
<method name="Upgrade">
<arg name="options" type="a{sv}" direction="in"/>
<arg name="transaction_address" type="s" direction="out"/>
</method>
<method name="Install">
<arg name="packages" type="as" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
<arg name="transaction_address" type="s" direction="out"/>
</method>
<!-- Additional methods... -->
</interface>
Interacting with the Daemon
Client-Side Flow
When a user runs rpm-ostree upgrade:
// Client-side code (rpmostree-builtin-upgrade.cxx)
rpmostree_os_call_upgrade_sync(
os_proxy,
options,
&transaction_address,
&error
);
Daemon-Side Flow
The daemon handles the request:
// Daemon-side code (rpmostreed-os.cxx)
static void
os_handle_upgrade(OsOstree *self, GDBusMethodInvocation *invocation, GVariant *options)
{
auto transaction = rpmostreed_transaction_new_deploy(self, options);
auto address = rpmostreed_transaction_get_address(transaction);
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(s)", address));
}
Transaction Model
All operations use a transaction model:
- Transaction Creation: Daemon creates transaction object
- Address Return: Client receives transaction socket address
- Peer Connection: Client connects to transaction as D-Bus peer
- Operation Execution: Transaction executes the requested operation
- Progress Monitoring: Client receives progress signals
- Completion: Transaction emits Finished signal
Transaction Architecture
Transaction Types
Different operations use different transaction types:
// Transaction type mapping
typedef enum {
RPMOSTREED_TRANSACTION_TYPE_DEPLOY,
RPMOSTREED_TRANSACTION_TYPE_KERNEL_ARGS,
RPMOSTREED_TRANSACTION_TYPE_OVERRIDE_REPLACE,
RPMOSTREED_TRANSACTION_TYPE_OVERRIDE_REMOVE,
// ...
} RpmOstreedTransactionType;
Transaction Execution
Each transaction type has its own execution logic:
// Deploy transaction execution
static void
deploy_transaction_execute(RpmOstreedTransaction *transaction)
{
auto self = RPMOSTREED_TRANSACTION_DEPLOY(transaction);
// 1. Download packages
download_packages(self->packages);
// 2. Create new deployment
create_deployment(self->base_commit, self->packages);
// 3. Update bootloader
update_bootloader();
// 4. Emit completion signal
emit_finished_signal(transaction);
}
Progress Reporting
Transactions report progress via D-Bus signals:
// Progress signal emission
g_dbus_connection_emit_signal(
connection,
NULL,
object_path,
"org.projectatomic.rpmostree1.Transaction",
"Message",
g_variant_new("(us)", level, message),
NULL
);
Polkit Integration
Authorization Framework
rpm-ostree integrates with polkit for authorization:
// Method authorization (os_authorize_method)
static gboolean
os_authorize_method(OsOstree *self, GDBusMethodInvocation *invocation,
const char *action_id)
{
auto subject = polkit_system_bus_name_new(
g_dbus_method_invocation_get_sender(invocation));
auto authority = polkit_authority_get_sync(NULL, NULL);
auto result = polkit_authority_check_authorization_sync(
authority, subject, action_id, NULL,
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL);
return polkit_authorization_result_get_is_authorized(result);
}
Action Mapping
D-Bus methods map to polkit actions:
// Method to action mapping
static const char*
get_polkit_action_for_method(const char *method_name)
{
if (g_str_equal(method_name, "Upgrade"))
return "org.projectatomic.rpmostree1.upgrade";
if (g_str_equal(method_name, "Install"))
return "org.projectatomic.rpmostree1.install";
if (g_str_equal(method_name, "Uninstall"))
return "org.projectatomic.rpmostree1.uninstall";
// ...
return NULL;
}
Policy Configuration
Base policy file defines allowed actions:
<!-- /usr/share/polkit-1/actions/org.projectatomic.rpmostree1.policy -->
<policyconfig>
<action id="org.projectatomic.rpmostree1.upgrade">
<description>Upgrade system</description>
<message>Authentication is required to upgrade the system</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
</action>
<!-- Additional actions... -->
</policyconfig>
D-Bus API Design
Method Signatures
D-Bus methods use consistent patterns:
<!-- Standard method pattern -->
<method name="MethodName">
<arg name="required_param" type="s" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>
<arg name="transaction_address" type="s" direction="out"/>
</method>
Options Parameter
The options parameter allows API extension:
// Options handling
static void
parse_options(GVariant *options, MethodOptions *parsed)
{
if (g_variant_lookup(options, "reboot", "b"))
parsed->reboot = g_variant_get_boolean(value);
if (g_variant_lookup(options, "allow-downgrade", "b"))
parsed->allow_downgrade = g_variant_get_boolean(value);
// Additional options...
}
Signal Definitions
Transactions emit various signals:
<!-- Transaction signals -->
<signal name="Message">
<arg name="level" type="u"/>
<arg name="message" type="s"/>
</signal>
<signal name="Progress">
<arg name="percentage" type="u"/>
</signal>
<signal name="Finished">
<arg name="success" type="b"/>
<arg name="error_message" type="s"/>
</signal>
Error Handling
Transaction Error Handling
// Error handling in transactions
static void
handle_transaction_error(RpmOstreedTransaction *transaction, GError *error)
{
// Log error
g_warning("Transaction failed: %s", error->message);
// Emit error signal
g_dbus_connection_emit_signal(
connection,
NULL,
object_path,
"org.projectatomic.rpmostree1.Transaction",
"Finished",
g_variant_new("(bs)", FALSE, error->message),
NULL
);
// Cleanup
cleanup_transaction(transaction);
}
Client Error Handling
// Client-side error handling
static void
on_transaction_finished(GDBusConnection *connection, const char *sender,
const char *object_path, const char *interface,
const char *signal_name, GVariant *parameters,
void *user_data)
{
gboolean success;
const char *error_message;
g_variant_get(parameters, "(bs)", &success, &error_message);
if (!success) {
g_error("Transaction failed: %s", error_message);
}
}
Integration Examples
Cockpit Integration
Cockpit can manage rpm-ostree systems:
// Cockpit JavaScript integration
cockpit.spawn(["rpm-ostree", "status"])
.done(function(data) {
// Parse status and update UI
})
.fail(function(error) {
// Handle error
});
Ansible Integration
Ansible modules can use rpm-ostree:
# Ansible module example
def upgrade_system(module):
cmd = ["rpm-ostree", "upgrade"]
if module.params['reboot']:
cmd.append("--reboot")
rc, stdout, stderr = module.run_command(cmd)
return rc == 0
Performance Considerations
Daemon Startup
The daemon starts on-demand:
# D-Bus service file
[D-BUS Service]
Name=org.projectatomic.rpmostree1
Exec=/usr/bin/rpm-ostreed
User=root
Connection Management
- Persistent connections: Clients maintain connections during operations
- Connection pooling: Multiple clients can connect simultaneously
- Resource cleanup: Proper cleanup on connection close
Transaction Lifecycle
// Transaction lifecycle management
typedef struct {
GDBusConnection *connection;
char *object_path;
RpmOstreedTransactionType type;
gpointer user_data;
GDestroyNotify destroy_func;
} TransactionInfo;
static void
transaction_cleanup(TransactionInfo *info)
{
if (info->destroy_func)
info->destroy_func(info->user_data);
g_free(info->object_path);
g_free(info);
}
Security Considerations
D-Bus Security
- Message filtering: Validate all incoming messages
- Sender verification: Verify message sender identity
- Resource limits: Limit daemon resource usage
Polkit Integration
- Action granularity: Fine-grained action definitions
- User interaction: Support for authentication prompts
- Policy flexibility: Distribution-specific policy rules
This daemon architecture provides the foundation for safe, multi-client system management in rpm-ostree and serves as a model for apt-layer's daemon implementation.