diff --git a/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml
index 4b4e839ef5..701b615834 100644
--- a/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml
+++ b/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml
@@ -51,6 +51,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/rust/agama-lib/share/profile.schema.json b/rust/agama-lib/share/profile.schema.json
index d71f7576d0..e9643f43a0 100644
--- a/rust/agama-lib/share/profile.schema.json
+++ b/rust/agama-lib/share/profile.schema.json
@@ -39,6 +39,16 @@
}
}
},
+ "bootloader": {
+ "title": "Bootloader settings",
+ "type": "object",
+ "properties": {
+ "stopOnBootMenu": {
+ "title": "Specify if bootloader should stop on menu during boot.",
+ "type": "boolean"
+ }
+ }
+ },
"software": {
"title": "Software settings",
"type": "object",
diff --git a/rust/agama-lib/src/bootloader.rs b/rust/agama-lib/src/bootloader.rs
new file mode 100644
index 0000000000..dc6b83528e
--- /dev/null
+++ b/rust/agama-lib/src/bootloader.rs
@@ -0,0 +1,28 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+//! Implements support for handling the storage settings
+
+pub mod client;
+// pub mod http_client;
+pub mod http_client;
+pub mod model;
+pub mod proxies;
+pub mod store;
diff --git a/rust/agama-lib/src/bootloader/client.rs b/rust/agama-lib/src/bootloader/client.rs
new file mode 100644
index 0000000000..10287522e4
--- /dev/null
+++ b/rust/agama-lib/src/bootloader/client.rs
@@ -0,0 +1,57 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+//! Implements a client to access Agama's D-Bus API related to Bootloader management.
+
+use zbus::Connection;
+
+use crate::{
+ bootloader::{model::BootloaderSettings, proxies::BootloaderProxy},
+ error::ServiceError,
+};
+
+/// Client to connect to Agama's D-Bus API for Bootloader management.
+#[derive(Clone)]
+pub struct BootloaderClient<'a> {
+ bootloader_proxy: BootloaderProxy<'a>,
+}
+
+impl<'a> BootloaderClient<'a> {
+ pub async fn new(connection: Connection) -> Result, ServiceError> {
+ let bootloader_proxy = BootloaderProxy::new(&connection).await?;
+
+ Ok(Self { bootloader_proxy })
+ }
+
+ pub async fn get_config(&self) -> Result {
+ let serialized_string = self.bootloader_proxy.get_config().await?;
+ let settings = serde_json::from_str(serialized_string.as_str())?;
+ Ok(settings)
+ }
+
+ pub async fn set_config(&self, config: &BootloaderSettings) -> Result<(), ServiceError> {
+ // 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 ServiceError
+ self.bootloader_proxy
+ .set_config(serde_json::to_string(config)?.as_str())
+ .await?;
+ Ok(())
+ }
+}
diff --git a/rust/agama-lib/src/bootloader/http_client.rs b/rust/agama-lib/src/bootloader/http_client.rs
new file mode 100644
index 0000000000..9cde4444bc
--- /dev/null
+++ b/rust/agama-lib/src/bootloader/http_client.rs
@@ -0,0 +1,43 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+//! Implements a client to access Agama's HTTP API related to Bootloader management.
+
+use crate::base_http_client::BaseHTTPClient;
+use crate::bootloader::model::BootloaderSettings;
+use crate::ServiceError;
+
+pub struct BootloaderHTTPClient {
+ client: BaseHTTPClient,
+}
+
+impl BootloaderHTTPClient {
+ pub fn new(base: BaseHTTPClient) -> Self {
+ Self { client: base }
+ }
+
+ pub async fn get_config(&self) -> Result {
+ self.client.get("/bootloader/config").await
+ }
+
+ pub async fn set_config(&self, config: &BootloaderSettings) -> Result<(), ServiceError> {
+ self.client.put_void("/bootloader/config", config).await
+ }
+}
diff --git a/rust/agama-lib/src/bootloader/model.rs b/rust/agama-lib/src/bootloader/model.rs
new file mode 100644
index 0000000000..ac50f3dd16
--- /dev/null
+++ b/rust/agama-lib/src/bootloader/model.rs
@@ -0,0 +1,30 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+//! Implements a data model for Bootloader configuration.
+
+use serde::{Deserialize, Serialize};
+
+/// Represents a Bootloader
+#[derive(Clone, Debug, Serialize, Deserialize, Default, utoipa::ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct BootloaderSettings {
+ pub stop_on_boot_menu: bool,
+}
diff --git a/rust/agama-lib/src/bootloader/proxies.rs b/rust/agama-lib/src/bootloader/proxies.rs
new file mode 100644
index 0000000000..48e2543ed5
--- /dev/null
+++ b/rust/agama-lib/src/bootloader/proxies.rs
@@ -0,0 +1,35 @@
+//! # D-Bus interface proxy for: `org.opensuse.Agama.Storage1.Bootloader`
+//!
+//! This code was generated by `zbus-xmlgen` `5.0.1` from D-Bus introspection data.
+//! Source: `org.opensuse.Agama.Storage1.bus.xml`.
+//!
+//! You may prefer to adapt it, instead of using it verbatim.
+//!
+//! More information can be found in the [Writing a client proxy] section of the zbus
+//! documentation.
+//!
+//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the
+//! following zbus API can be used:
+//!
+//! * [`zbus::fdo::IntrospectableProxy`]
+//! * [`zbus::fdo::ObjectManagerProxy`]
+//! * [`zbus::fdo::PropertiesProxy`]
+//!
+//! Consequently `zbus-xmlgen` did not generate code for the above interfaces.
+//!
+//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
+//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
+use zbus::proxy;
+#[proxy(
+ default_service = "org.opensuse.Agama.Storage1",
+ default_path = "/org/opensuse/Agama/Storage1",
+ interface = "org.opensuse.Agama.Storage1.Bootloader",
+ assume_defaults = true
+)]
+pub trait Bootloader {
+ /// GetConfig method
+ fn get_config(&self) -> zbus::Result;
+
+ /// SetConfig method
+ fn set_config(&self, serialized_config: &str) -> zbus::Result;
+}
diff --git a/rust/agama-lib/src/bootloader/store.rs b/rust/agama-lib/src/bootloader/store.rs
new file mode 100644
index 0000000000..0ff742c787
--- /dev/null
+++ b/rust/agama-lib/src/bootloader/store.rs
@@ -0,0 +1,49 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+//! Implements the store for the storage settings.
+
+use crate::base_http_client::BaseHTTPClient;
+use crate::error::ServiceError;
+
+use super::http_client::BootloaderHTTPClient;
+use super::model::BootloaderSettings;
+
+/// Loads and stores the storage settings from/to the HTTP service.
+pub struct BootloaderStore {
+ bootloader_client: BootloaderHTTPClient,
+}
+
+impl BootloaderStore {
+ pub fn new(client: BaseHTTPClient) -> Result {
+ Ok(Self {
+ bootloader_client: BootloaderHTTPClient::new(client),
+ })
+ }
+
+ pub async fn load(&self) -> Result {
+ self.bootloader_client.get_config().await
+ }
+
+ pub async fn store(&self, settings: &BootloaderSettings) -> Result<(), ServiceError> {
+ self.bootloader_client.set_config(settings).await?;
+ Ok(())
+ }
+}
diff --git a/rust/agama-lib/src/install_settings.rs b/rust/agama-lib/src/install_settings.rs
index fe5113da0b..77ebdf9a59 100644
--- a/rust/agama-lib/src/install_settings.rs
+++ b/rust/agama-lib/src/install_settings.rs
@@ -21,6 +21,7 @@
//! Configuration settings handling
//!
//! This module implements the mechanisms to load and store the installation settings.
+use crate::bootloader::model::BootloaderSettings;
use crate::{
localization::LocalizationSettings, network::NetworkSettings, product::ProductSettings,
scripts::ScriptsConfig, software::SoftwareSettings, users::UserSettings,
@@ -39,6 +40,8 @@ use std::path::Path;
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstallSettings {
+ #[serde(default)]
+ pub bootloader: Option,
#[serde(default, flatten)]
pub user: Option,
#[serde(default)]
diff --git a/rust/agama-lib/src/lib.rs b/rust/agama-lib/src/lib.rs
index d7aab9c5c1..23c27abc0d 100644
--- a/rust/agama-lib/src/lib.rs
+++ b/rust/agama-lib/src/lib.rs
@@ -45,6 +45,7 @@
pub mod auth;
pub mod base_http_client;
+pub mod bootloader;
pub mod error;
pub mod install_settings;
pub mod jobs;
diff --git a/rust/agama-lib/src/store.rs b/rust/agama-lib/src/store.rs
index 14efddfea5..ff4779d1e0 100644
--- a/rust/agama-lib/src/store.rs
+++ b/rust/agama-lib/src/store.rs
@@ -22,6 +22,7 @@
// TODO: quickly explain difference between FooSettings and FooStore, with an example
use crate::base_http_client::BaseHTTPClient;
+use crate::bootloader::store::BootloaderStore;
use crate::error::ServiceError;
use crate::install_settings::InstallSettings;
use crate::manager::{InstallationPhase, ManagerHTTPClient};
@@ -38,6 +39,7 @@ use crate::{
///
/// This struct uses the default connection built by [connection function](super::connection).
pub struct Store {
+ bootloader: BootloaderStore,
users: UsersStore,
network: NetworkStore,
product: ProductStore,
@@ -52,6 +54,7 @@ pub struct Store {
impl Store {
pub async fn new(http_client: BaseHTTPClient) -> Result {
Ok(Self {
+ bootloader: BootloaderStore::new(http_client.clone())?,
localization: LocalizationStore::new(http_client.clone())?,
users: UsersStore::new(http_client.clone())?,
network: NetworkStore::new(http_client.clone()).await?,
@@ -67,6 +70,7 @@ impl Store {
/// Loads the installation settings from the HTTP interface.
pub async fn load(&self) -> Result {
let mut settings = InstallSettings {
+ bootloader: Some(self.bootloader.load().await?),
network: Some(self.network.load().await?),
software: Some(self.software.load().await?),
user: Some(self.users.load().await?),
@@ -121,6 +125,9 @@ impl Store {
if settings.storage.is_some() || settings.storage_autoyast.is_some() {
self.storage.store(&settings.into()).await?
}
+ if let Some(bootloader) = &settings.bootloader {
+ self.bootloader.store(bootloader).await?;
+ }
Ok(())
}
diff --git a/rust/agama-server/src/bootloader.rs b/rust/agama-server/src/bootloader.rs
new file mode 100644
index 0000000000..f0e99a215d
--- /dev/null
+++ b/rust/agama-server/src/bootloader.rs
@@ -0,0 +1,21 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+pub mod web;
diff --git a/rust/agama-server/src/bootloader/web.rs b/rust/agama-server/src/bootloader/web.rs
new file mode 100644
index 0000000000..bcce7944f6
--- /dev/null
+++ b/rust/agama-server/src/bootloader/web.rs
@@ -0,0 +1,93 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+//! This module implements the web API for the storage service.
+//!
+//! The module offers one public function:
+//!
+//! * `storage_service` which returns the Axum service.
+//!
+//! stream is not needed, as we do not need to emit signals (for NOW).
+
+use agama_lib::{
+ bootloader::{client::BootloaderClient, model::BootloaderSettings},
+ error::ServiceError,
+};
+use axum::{extract::State, routing::put, Json, Router};
+
+use crate::error;
+
+#[derive(Clone)]
+struct BootloaderState<'a> {
+ client: BootloaderClient<'a>,
+}
+
+/// Sets up and returns the axum service for the storage module.
+pub async fn bootloader_service(dbus: zbus::Connection) -> Result {
+ let client = BootloaderClient::new(dbus).await?;
+ let state = BootloaderState { client };
+ let router = Router::new()
+ .route("/config", put(set_config).get(get_config))
+ .with_state(state);
+ Ok(router)
+}
+
+/// Returns the bootloader configuration.
+///
+/// * `state` : service state.
+#[utoipa::path(
+ get,
+ path = "/config",
+ context_path = "/api/bootloader",
+ operation_id = "get_bootloader_config",
+ responses(
+ (status = 200, description = "bootloader configuration", body = BootloaderSettings),
+ (status = 400, description = "The D-Bus service could not perform the action")
+ )
+)]
+async fn get_config(
+ State(state): State>,
+) -> Result, error::Error> {
+ // StorageSettings is just a wrapper over serde_json::value::RawValue
+ let settings = state.client.get_config().await?;
+ Ok(Json(settings))
+}
+
+/// Sets the bootloader configuration.
+///
+/// * `state`: service state.
+/// * `config`: bootloader configuration.
+#[utoipa::path(
+ put,
+ path = "/config",
+ context_path = "/api/bootloader",
+ operation_id = "set_bootloader_config",
+ responses(
+ (status = 200, description = "Set the bootloader configuration"),
+ (status = 400, description = "The D-Bus service could not perform the action")
+ )
+)]
+async fn set_config(
+ State(state): State>,
+ Json(settings): Json,
+) -> Result, error::Error> {
+ state.client.set_config(&settings).await?;
+ Ok(Json(()))
+}
diff --git a/rust/agama-server/src/lib.rs b/rust/agama-server/src/lib.rs
index c26eaa774f..8ad2e6cb35 100644
--- a/rust/agama-server/src/lib.rs
+++ b/rust/agama-server/src/lib.rs
@@ -18,6 +18,7 @@
// To contact SUSE LLC about this file by physical or electronic mail, you may
// find current contact information at www.suse.com.
+pub mod bootloader;
pub mod cert;
pub mod dbus;
pub mod error;
diff --git a/rust/agama-server/src/web.rs b/rust/agama-server/src/web.rs
index 2a7ad06e75..8904565ff7 100644
--- a/rust/agama-server/src/web.rs
+++ b/rust/agama-server/src/web.rs
@@ -25,6 +25,7 @@
//! * Serve the code for the web user interface (not implemented yet).
use crate::{
+ bootloader::web::bootloader_service,
error::Error,
l10n::web::l10n_service,
manager::web::{manager_service, manager_stream},
@@ -79,6 +80,7 @@ where
.add_service("/manager", manager_service(dbus.clone()).await?)
.add_service("/software", software_service(dbus.clone()).await?)
.add_service("/storage", storage_service(dbus.clone()).await?)
+ .add_service("/bootloader", bootloader_service(dbus.clone()).await?)
.add_service("/network", network_service(network_adapter, events).await?)
.add_service("/questions", questions_service(dbus.clone()).await?)
.add_service("/users", users_service(dbus.clone()).await?)
diff --git a/rust/agama-server/src/web/docs.rs b/rust/agama-server/src/web/docs.rs
index 1592161228..848b0b327c 100644
--- a/rust/agama-server/src/web/docs.rs
+++ b/rust/agama-server/src/web/docs.rs
@@ -24,6 +24,8 @@ mod network;
pub use network::NetworkApiDocBuilder;
mod storage;
pub use storage::StorageApiDocBuilder;
+mod bootloader;
+pub use bootloader::BootloaderApiDocBuilder;
mod software;
pub use software::SoftwareApiDocBuilder;
mod l10n;
diff --git a/rust/agama-server/src/web/docs/bootloader.rs b/rust/agama-server/src/web/docs/bootloader.rs
new file mode 100644
index 0000000000..974efa9135
--- /dev/null
+++ b/rust/agama-server/src/web/docs/bootloader.rs
@@ -0,0 +1,43 @@
+// Copyright (c) [2024] SUSE LLC
+//
+// All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, contact SUSE LLC.
+//
+// To contact SUSE LLC about this file by physical or electronic mail, you may
+// find current contact information at www.suse.com.
+
+use utoipa::openapi::{Components, ComponentsBuilder, Paths, PathsBuilder};
+
+use super::ApiDocBuilder;
+pub struct BootloaderApiDocBuilder;
+
+impl ApiDocBuilder for BootloaderApiDocBuilder {
+ fn title(&self) -> String {
+ "Bootloader HTTP API".to_string()
+ }
+
+ fn paths(&self) -> Paths {
+ PathsBuilder::new()
+ .path_from::()
+ .path_from::()
+ .build()
+ }
+
+ fn components(&self) -> Components {
+ ComponentsBuilder::new()
+ .schema_from::()
+ .build()
+ }
+}
diff --git a/service/.rubocop.yml b/service/.rubocop.yml
index f5bea90ce8..2039f37050 100644
--- a/service/.rubocop.yml
+++ b/service/.rubocop.yml
@@ -31,3 +31,6 @@ Metrics/AbcSize:
Metrics/ParameterLists:
Max: 6
+
+Metrics/ClassLength:
+ Max: 300
diff --git a/service/lib/agama/dbus/storage/manager.rb b/service/lib/agama/dbus/storage/manager.rb
index 0514b159b5..665ece1828 100644
--- a/service/lib/agama/dbus/storage/manager.rb
+++ b/service/lib/agama/dbus/storage/manager.rb
@@ -144,6 +144,40 @@ def deprecated_system
dbus_reader(:deprecated_system, "b")
end
+ BOOTLOADER_INTERFACE = "org.opensuse.Agama.Storage1.Bootloader"
+ private_constant :BOOTLOADER_INTERFACE
+
+ # Applies the given serialized config according to the JSON schema.
+ #
+ #
+ # @raise If the config is not valid.
+ #
+ # @param serialized_config [String] Serialized storage config.
+ # @return [Integer] 0 success; 1 error
+ def load_bootloader_config_from_json(serialized_config)
+ logger.info("Setting bootloader config from D-Bus: #{serialized_config}")
+
+ backend.bootloader.config.load_json(serialized_config)
+
+ 0
+ end
+
+ # Gets and serializes the storage config used to calculate the current proposal.
+ #
+ # @return [String] Serialized config according to the JSON schema.
+ def bootloader_config_as_json
+ backend.bootloader.config.to_json
+ end
+
+ dbus_interface BOOTLOADER_INTERFACE do
+ dbus_method(:SetConfig, "in serialized_config:s, out result:u") do |serialized_config|
+ load_bootloader_config_from_json(serialized_config)
+ end
+ dbus_method(:GetConfig, "out serialized_config:s") do
+ bootloader_config_as_json
+ end
+ end
+
# @todo Move device related properties here, for example, the list of system and staging
# devices, dirty, etc.
STORAGE_DEVICES_INTERFACE = "org.opensuse.Agama.Storage1.Devices"
diff --git a/service/lib/agama/storage/bootloader.rb b/service/lib/agama/storage/bootloader.rb
new file mode 100644
index 0000000000..eb142fad6d
--- /dev/null
+++ b/service/lib/agama/storage/bootloader.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+# Copyright (c) [2024] SUSE LLC
+#
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
+
+require "yast"
+require "json"
+require "bootloader/bootloader_factory"
+
+module Agama
+ module Storage
+ # Represents bootloader specific functionality
+ class Bootloader
+ # Represents bootloader settings
+ class Config
+ # If bootloader should stop on boot menu
+ attr_accessor :stop_on_boot_menu
+
+ def initialize
+ @stop_on_boot_menu = false # false means use proposal, which has timeout
+ end
+
+ def to_json(*_args)
+ result = {}
+
+ # our json use camel case
+ result[:stopOnBootMenu] = stop_on_boot_menu
+ result.to_json
+ end
+
+ def load_json(serialized_config)
+ hsh = JSON.parse(serialized_config, symbolize_names: true)
+ self.stop_on_boot_menu = hsh[:stopOnBootMenu] if hsh.include?(:stopOnBootMenu)
+ end
+ end
+
+ attr_reader :config
+
+ def initialize(logger)
+ @config = Config.new
+ @logger = logger
+ end
+
+ def write_config
+ bootloader = ::Bootloader::BootloaderFactory.current
+ case @config.stop_on_boot_menu
+ when true
+ # grub2 based bootloaders
+ if bootloader.respond_to?(:grub_default)
+ bootloader.grub_default.timeout = -1
+ # systemd bootloader
+ elsif bootloader.respond_to?(:menu_timeout)
+ bootloader.menu_timeout = -1
+ else
+ @logger.info "bootloader #{bootloader.name} does not support forcing user input"
+ end
+ when false
+ # TODO: basically as we have single argument we repropose here. If more attributes comes
+ # we will need to do always propose first and then modify what is in config set
+ bootloader.propose
+ when nil
+ # not set, so do nothing and keep it as it is
+ else
+ @logger.error "unexpected value for stop_on_boot_menu #{@config.stop_on_boot_menu}"
+ end
+ end
+ end
+ end
+end
diff --git a/service/lib/agama/storage/manager.rb b/service/lib/agama/storage/manager.rb
index d7ea94436c..4e5fb937ed 100644
--- a/service/lib/agama/storage/manager.rb
+++ b/service/lib/agama/storage/manager.rb
@@ -24,6 +24,7 @@
require "y2storage/storage_manager"
require "y2storage/clients/inst_prepdisk"
require "agama/storage/actions_generator"
+require "agama/storage/bootloader"
require "agama/storage/proposal"
require "agama/storage/proposal_settings"
require "agama/storage/callbacks"
@@ -52,6 +53,9 @@ class Manager
# @return [Config]
attr_reader :config
+ # @return [Bootloader]
+ attr_reader :bootloader
+
# Constructor
#
# @param config [Config]
@@ -61,6 +65,7 @@ def initialize(config, logger)
@config = config
@logger = logger
+ @bootloader = Bootloader.new(logger)
register_proposal_callbacks
on_progress_change { logger.info progress.to_s }
end
@@ -126,6 +131,8 @@ def install
progress.step(_("Preparing bootloader proposal")) do
# first make bootloader proposal to be sure that required packages are installed
proposal = ::Bootloader::ProposalClient.new.make_proposal({})
+ # then also apply changes to that proposal
+ bootloader.write_config
logger.debug "Bootloader proposal #{proposal.inspect}"
end
progress.step(_("Adding storage-related packages")) { add_packages }
diff --git a/service/run_tests_in_container.sh b/service/run_tests_in_container.sh
index 429f2aa6ce..c298048703 100644
--- a/service/run_tests_in_container.sh
+++ b/service/run_tests_in_container.sh
@@ -4,7 +4,7 @@ set -ex
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
podman create -ti --rm --entrypoint '["sh", "-c"]' --name agama_ruby_tests -v $SCRIPT_DIR/..:/checkout registry.opensuse.org/yast/head/containers_tumbleweed/yast-ruby sh
podman start agama_ruby_tests
-podman exec agama_ruby_tests zypper --non-interactive install yast2-iscsi-client ruby3.2-rubygem-eventmachine
+podman exec agama_ruby_tests zypper --non-interactive install yast2-iscsi-client yast2-bootloader ruby3.3-rubygem-eventmachine
if podman exec --workdir /checkout/service agama_ruby_tests rake test:unit; then
if [ "$KEEP_RUNNING" != "1" ]; then
podman stop agama_ruby_tests
diff --git a/service/test/agama/storage/bootloader_test.rb b/service/test/agama/storage/bootloader_test.rb
new file mode 100644
index 0000000000..40a5d9c315
--- /dev/null
+++ b/service/test/agama/storage/bootloader_test.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+# Copyright (c) [2024] SUSE LLC
+#
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of version 2 of the GNU General Public License as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, contact SUSE LLC.
+#
+# To contact SUSE LLC about this file by physical or electronic mail, you may
+# find current contact information at www.suse.com.
+
+require_relative "../../test_helper"
+
+require "agama/storage/bootloader"
+
+describe Agama::Storage::Bootloader::Config do
+ subject(:config) { described_class.new }
+
+ describe "#to_json" do
+ it "serializes its content with keys as camelCase" do
+ config.stop_on_boot_menu = true
+ expect(config.to_json).to eq "{\"stopOnBootMenu\":true}"
+ end
+
+ it "can serialize in a way that #load_json can restore it" do
+ config.stop_on_boot_menu = false
+ json = config.to_json
+ config.stop_on_boot_menu = true
+ config.load_json(json)
+ expect(config.stop_on_boot_menu).to eq false
+ end
+ end
+
+ describe "#load_json" do
+ it "loads config from given json" do
+ content = "{\"stopOnBootMenu\":true}"
+ config.load_json(content)
+ expect(config.stop_on_boot_menu).to eq true
+ end
+ end
+end