Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 33 additions & 2 deletions rust/agama-bootloader/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@

//! Implements a client to access Agama's D-Bus API related to Bootloader management.

use std::collections::HashMap;

use agama_utils::api::bootloader::Config;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use zbus::Connection;

use crate::dbus::BootloaderProxy;
Expand All @@ -47,6 +50,21 @@ pub trait BootloaderClient {
async fn get_config(&self) -> ClientResult<Config>;
/// Sets the bootloader configuration.
async fn set_config(&self, config: &Config) -> ClientResult<()>;
/// Sets the extra kernel args for given scope.
///
/// * `id`: identifier of the kernel argument so it can be later changed.
/// * `value`: plaintext value that will be appended to kernel commandline.
async fn set_kernel_arg(&mut self, id: String, value: String);
}

// full config used on dbus which beside public config passes
// also additional internal settings
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct FullConfig {
#[serde(flatten)]
config: Config,
kernel_args: HashMap<String, String>,
}

pub type ClientResult<T> = Result<T, Error>;
Expand All @@ -55,13 +73,17 @@ pub type ClientResult<T> = Result<T, Error>;
#[derive(Clone)]
pub struct Client<'a> {
bootloader_proxy: BootloaderProxy<'a>,
kernel_args: HashMap<String, String>,
}

impl<'a> Client<'a> {
pub async fn new(connection: Connection) -> ClientResult<Client<'a>> {
let bootloader_proxy = BootloaderProxy::new(&connection).await?;

Ok(Self { bootloader_proxy })
Ok(Self {
bootloader_proxy,
kernel_args: HashMap::new(),
})
}
}

Expand All @@ -74,11 +96,20 @@ impl<'a> BootloaderClient for Client<'a> {
}

async fn set_config(&self, config: &Config) -> ClientResult<()> {
let full_config = FullConfig {
config: config.clone(),
kernel_args: self.kernel_args.clone(),
};
tracing::info!("sending bootloader config {:?}", full_config);
// ignore return value as currently it does not fail and who knows what future brings
// but it should not be part of result and instead transformed to Issue
self.bootloader_proxy
.set_config(serde_json::to_string(config)?.as_str())
.set_config(serde_json::to_string(&full_config)?.as_str())
.await?;
Ok(())
}

async fn set_kernel_arg(&mut self, id: String, value: String) {
self.kernel_args.insert(id, value);
}
}
15 changes: 15 additions & 0 deletions rust/agama-bootloader/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,18 @@ impl<T> SetConfig<T> {
}
}
}

pub struct SetKernelArg {
pub id: String,
pub value: String,
}

impl Message for SetKernelArg {
type Reply = ();
}

impl SetKernelArg {
pub fn new(id: String, value: String) -> Self {
Self { id, value }
}
}
8 changes: 8 additions & 0 deletions rust/agama-bootloader/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,11 @@ impl MessageHandler<message::SetConfig<Config>> for Service {
Ok(())
}
}

#[async_trait]
impl MessageHandler<message::SetKernelArg> for Service {
async fn handle(&mut self, message: message::SetKernelArg) -> Result<(), Error> {
self.client.set_kernel_arg(message.id, message.value).await;
Ok(())
}
}
2 changes: 2 additions & 0 deletions rust/agama-bootloader/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ impl BootloaderClient for TestClient {
state.config = config.clone();
Ok(())
}

async fn set_kernel_arg(&mut self, _id: String, _value: String) {}
}

/// Starts a testing storage service.
Expand Down
1 change: 1 addition & 0 deletions rust/agama-manager/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ impl Starter {
progress.clone(),
self.questions.clone(),
security.clone(),
bootloader.clone(),
)
.start()
.await?
Expand Down
1 change: 1 addition & 0 deletions rust/agama-software/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ rust-version.workspace = true
edition.workspace = true

[dependencies]
agama-bootloader = { path = "../agama-bootloader" }
agama-l10n = { path = "../agama-l10n" }
agama-locale-data = { path = "../agama-locale-data" }
agama-utils = { path = "../agama-utils" }
Expand Down
8 changes: 8 additions & 0 deletions rust/agama-software/src/model/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,14 @@ impl ResolvableSelection {

false
}

pub fn selected(&self) -> bool {
match self {
ResolvableSelection::Selected => true,
ResolvableSelection::AutoSelected { optional: _ } => true,
_ => false,
}
}
}

impl From<ResolvableSelection> for zypp_agama::ResolvableSelected {
Expand Down
33 changes: 31 additions & 2 deletions rust/agama-software/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ use crate::{
message,
model::{software_selection::SoftwareSelection, state::SoftwareState, ModelAdapter},
zypp_server::{self, SoftwareAction, ZyppServer},
Model,
Model, ResolvableType,
};
use agama_bootloader;
use agama_security as security;
use agama_utils::{
actor::{self, Actor, Handler, MessageHandler},
Expand Down Expand Up @@ -74,6 +75,7 @@ pub struct Starter {
progress: Handler<progress::Service>,
questions: Handler<question::Service>,
security: Handler<security::Service>,
bootloader: Handler<agama_bootloader::Service>,
}

impl Starter {
Expand All @@ -83,6 +85,7 @@ impl Starter {
progress: Handler<progress::Service>,
questions: Handler<question::Service>,
security: Handler<security::Service>,
bootloader: Handler<agama_bootloader::Service>,
) -> Self {
Self {
model: None,
Expand All @@ -91,6 +94,7 @@ impl Starter {
progress,
questions,
security,
bootloader,
}
}

Expand Down Expand Up @@ -135,6 +139,7 @@ impl Starter {
progress: self.progress,
product: None,
kernel_cmdline: KernelCmdline::parse().unwrap_or_default(),
bootloader: self.bootloader,
};
service.setup().await?;
Ok(actor::spawn(service))
Expand All @@ -157,6 +162,7 @@ pub struct Service {
product: Option<Arc<RwLock<ProductSpec>>>,
selection: SoftwareSelection,
kernel_cmdline: KernelCmdline,
bootloader: Handler<agama_bootloader::Service>,
}

#[derive(Default)]
Expand All @@ -173,8 +179,9 @@ impl Service {
progress: Handler<progress::Service>,
questions: Handler<question::Service>,
security: Handler<security::Service>,
bootloader: Handler<agama_bootloader::Service>,
) -> Starter {
Starter::new(events, issues, progress, questions, security)
Starter::new(events, issues, progress, questions, security, bootloader)
}

pub async fn setup(&mut self) -> Result<(), Error> {
Expand All @@ -190,6 +197,26 @@ impl Service {
Ok(())
}

fn update_selinux(&self, state: &SoftwareState) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would be cleaner to extend SoftwareState with a method to search for a selected pattern (or a selected resolvable).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I usually try to make shared method when there are at least two users of it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the problem is that the caller needs to know too many details about SoftwareState. But it is not super important.

let selinux_selected = state.resolvables.to_vec().iter().any(|(name, typ, state)| {
typ == &ResolvableType::Pattern && name == "selinux" && state.selected()
});

let value = if selinux_selected {
"security=selinux"
} else {
"security="
};
let message = agama_bootloader::message::SetKernelArg {
id: "selinux".to_string(),
value: value.to_string(),
};
let res = self.bootloader.cast(message);
if res.is_err() {
tracing::warn!("Failed to send to bootloader new selinux state: {:?}", res);
}
}

/// Updates the proposal and the service state.
///
/// This function performs the following actions:
Expand All @@ -214,6 +241,8 @@ impl Service {
};

tracing::info!("Wanted software state: {new_state:?}");
self.update_selinux(&new_state);

let model = self.model.clone();
let progress = self.progress.clone();
let issues = self.issues.clone();
Expand Down
6 changes: 4 additions & 2 deletions rust/agama-software/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use agama_utils::{
},
issue,
products::ProductSpec,
progress, question,
progress, question, test,
};
use async_trait::async_trait;

Expand Down Expand Up @@ -91,7 +91,9 @@ pub async fn start_service(
questions: Handler<question::Service>,
) -> Handler<Service> {
let security = start_security_service(questions.clone()).await;
Service::starter(events, issues, progress, questions, security)
let dbus = test::dbus::connection().await.unwrap();
let bootloader = agama_bootloader::test_utils::start_service(issues.clone(), dbus).await;
Service::starter(events, issues, progress, questions, security, bootloader)
.with_model(TestModel {})
.start()
.await
Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Mon Feb 2 08:13:10 UTC 2026 - Josef Reidinger <jreidinger@suse.com>

- Implement modification of kernel parameters when SELinux pattern
is selected (gh#agama-project/agama#3103)

-------------------------------------------------------------------
Fri Jan 30 16:27:35 UTC 2026 - Ancor Gonzalez Sosa <ancor@suse.com>

Expand Down
18 changes: 15 additions & 3 deletions service/lib/agama/storage/bootloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class Config
# @return [String]
attr_accessor :extra_kernel_params

# Bootloader extra kernel parameters needed for other parts of agama
#
# @return [Hash<String, String>]
attr_accessor :scoped_kernel_params

# Keys to export to JSON.
#
# As both previous keys are conflicting, remember which one to set or none. It can be empty
Expand Down Expand Up @@ -109,6 +114,8 @@ def load_json(serialized_config)
end
end

self.scoped_kernel_params = hsh[:kernelArgs]

self
end
end
Expand Down Expand Up @@ -169,19 +176,24 @@ def write_config
bootloader = ::Bootloader::BootloaderFactory.current
write_stop_on_boot(bootloader) if @config.keys_to_export.include?(:stop_on_boot_menu)
write_timeout(bootloader) if @config.keys_to_export.include?(:timeout)
kernel_params = @config.scoped_kernel_params.values.join(" ")
@logger.info "scoped kernel params: #{kernel_params}"

if @config.keys_to_export.include?(:extra_kernel_params)
write_extra_kernel_params(bootloader)
kernel_params += " " + @config.extra_kernel_params
end
@logger.info "full kernel params: #{kernel_params}"
write_extra_kernel_params(bootloader, kernel_params)

bootloader
end

def write_extra_kernel_params(bootloader)
def write_extra_kernel_params(bootloader, kernel_params)
# no systemd boot support for now
return unless bootloader.respond_to?(:grub_default)

new_bl = bootloader.class.new
new_bl.grub_default.kernel_params.replace(@config.extra_kernel_params)
new_bl.grub_default.kernel_params.replace(kernel_params)
# and now just merge extra kernel params with all merge logic
bootloader.merge(new_bl)
end
Expand Down
6 changes: 6 additions & 0 deletions service/package/rubygem-agama-yast.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Mon Feb 2 08:13:48 UTC 2026 - Josef Reidinger <jreidinger@suse.com>

- Allow other parts to define in bootloader its proposed kernel
parameters (gh#agama-project/agama#3103)

-------------------------------------------------------------------
Fri Jan 30 16:21:23 UTC 2026 - Ancor Gonzalez Sosa <ancor@suse.com>

Expand Down
6 changes: 6 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Mon Feb 2 08:08:26 UTC 2026 - Josef Reidinger <jreidinger@suse.com>

- Fix unselecting product preselected patterns
(gh#agama-project/agama#3103)

-------------------------------------------------------------------
Fri Jan 30 16:28:39 UTC 2026 - Ancor Gonzalez Sosa <ancor@suse.com>

Expand Down
5 changes: 4 additions & 1 deletion web/src/components/software/SoftwarePatternsSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ function SoftwarePatternsSelection(): React.ReactNode {
} else {
// add the pattern to the "remove" list only if it was autoselected by dependencies, otherwise
// it was selected by user and it is enough to remove it from the "add" list above
if (selection[name] === SelectedBy.AUTO) {
// with exception of product preselected pattern which also needs to be added
const preselected = patterns.find((p) => p.name === name && p.preselected);

if (selection[name] === SelectedBy.AUTO || preselected) {
remove.push(name);
}
}
Expand Down
Loading