Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package agama for openSUSE:Factory checked 
in at 2026-02-07 15:33:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/agama (Old)
 and      /work/SRC/openSUSE:Factory/.agama.new.1670 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "agama"

Sat Feb  7 15:33:27 2026 rev:36 rq:1331727 version:0

Changes:
--------
--- /work/SRC/openSUSE:Factory/agama/agama.changes      2026-02-03 
21:25:57.842680796 +0100
+++ /work/SRC/openSUSE:Factory/.agama.new.1670/agama.changes    2026-02-07 
15:33:54.474573139 +0100
@@ -1,0 +2,29 @@
+Fri Feb  6 13:59:11 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Fix parsing of the answers file (bsc#1257400).
+
+-------------------------------------------------------------------
+Fri Feb  6 07:38:38 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Allow loading a profile with no product (bsc#1257067 and bsc#1257082).
+- Make the bootloader proposal after the storage one is ready (bsc#1257530).
+- Run pre-scripts only once to prevent potential infinite loops.
+
+-------------------------------------------------------------------
+Thu Feb  5 06:46:46 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Sort the list of languages, keymaps and timezones (bsc#1257497).
+
+-------------------------------------------------------------------
+Wed Feb  4 11:37:18 UTC 2026 - Josef Reidinger <[email protected]>
+
+- Fix usage of local repositories on full medium
+  (bsc#1257475)
+
+-------------------------------------------------------------------
+Tue Feb  3 07:23:18 UTC 2026 - Knut Anderssen <[email protected]>
+
+- Correct the /etc/resolv.conf link in the chroot to
+  /run/NetworkManager/resolv.conf (bsc#257468).
+
+-------------------------------------------------------------------
@@ -31 +60 @@
-  they are None (related to bsc#1257400).
+  they are None (related to bsc#1257230).

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ agama.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/Cargo.lock new/agama/Cargo.lock
--- old/agama/Cargo.lock        2026-02-02 14:01:52.000000000 +0100
+++ new/agama/Cargo.lock        2026-02-06 17:21:45.000000000 +0100
@@ -61,6 +61,7 @@
  "inquire",
  "regex",
  "reqwest",
+ "serde",
  "serde_json",
  "tempfile",
  "thiserror 2.0.18",
@@ -76,6 +77,7 @@
  "agama-utils",
  "async-trait",
  "serde_json",
+ "strum",
  "tempfile",
  "test-context",
  "thiserror 2.0.18",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-cli/Cargo.toml 
new/agama/agama-cli/Cargo.toml
--- old/agama/agama-cli/Cargo.toml      2026-02-02 14:01:52.000000000 +0100
+++ new/agama/agama-cli/Cargo.toml      2026-02-06 17:21:45.000000000 +0100
@@ -28,6 +28,7 @@
 regex = "1.11.1"
 home = "0.5.11"
 fluent-uri = "0.3.2"
+serde = { version = "1.0.228", features = ["derive"] }
 
 [[bin]]
 name = "agama"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-cli/src/questions.rs 
new/agama/agama-cli/src/questions.rs
--- old/agama/agama-cli/src/questions.rs        2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-cli/src/questions.rs        2026-02-06 17:21:45.000000000 
+0100
@@ -24,6 +24,7 @@
 use agama_utils::api::question::{AnswerRule, Policy, QuestionSpec};
 use anyhow::anyhow;
 use clap::{Args, Subcommand, ValueEnum};
+use serde::Deserialize;
 
 // TODO: use for answers also JSON to be consistent
 #[derive(Subcommand, Debug)]
@@ -72,11 +73,17 @@
     Ok(())
 }
 
+#[derive(Deserialize)]
+struct AnswersWrapper {
+    #[serde(default)]
+    answers: Vec<AnswerRule>,
+}
+
 async fn set_answers(client: HTTPClient, path: &str) -> anyhow::Result<()> {
     let file = File::open(&path)?;
     let reader = BufReader::new(file);
-    let rules: Vec<AnswerRule> = serde_json::from_reader(reader)?;
-    client.set_answers(rules).await?;
+    let wrapper: AnswersWrapper = serde_json::from_reader(reader)?;
+    client.set_answers(wrapper.answers).await?;
     Ok(())
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-files/Cargo.toml 
new/agama/agama-files/Cargo.toml
--- old/agama/agama-files/Cargo.toml    2026-02-02 14:01:52.000000000 +0100
+++ new/agama/agama-files/Cargo.toml    2026-02-06 17:21:45.000000000 +0100
@@ -8,6 +8,7 @@
 agama-software = { version = "0.1.0", path = "../agama-software" }
 agama-utils = { path = "../agama-utils" }
 async-trait = "0.1.89"
+strum = "0.27.2"
 tempfile = "3.23.0"
 thiserror = "2.0.17"
 tokio = { version = "1.48.0", features = ["sync"] }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-files/src/lib.rs 
new/agama/agama-files/src/lib.rs
--- old/agama/agama-files/src/lib.rs    2026-02-02 14:01:52.000000000 +0100
+++ new/agama/agama-files/src/lib.rs    2026-02-06 17:21:45.000000000 +0100
@@ -78,7 +78,7 @@
             )
             .await;
             let handler = Service::starter(progress, questions, software)
-                .with_scripts_workdir(tmp_dir.path())
+                .with_workdir(tmp_dir.path())
                 .with_install_dir(tmp_dir.path())
                 .start()
                 .await
@@ -94,6 +94,13 @@
     #[test_context(Context)]
     #[tokio::test]
     async fn test_add_and_run_scripts(ctx: &mut Context) -> Result<(), Error> {
+        let ran = ctx
+            .handler
+            .call(message::RunScripts::new(ScriptsGroup::Pre))
+            .await
+            .unwrap();
+        assert_eq!(ran, false);
+
         let test_file_1 = ctx.tmp_dir.path().join("file-1.txt");
         let test_file_2 = ctx.tmp_dir.path().join("file-2.txt");
 
@@ -118,10 +125,12 @@
             .await
             .unwrap();
 
-        ctx.handler
+        let ran = ctx
+            .handler
             .call(message::RunScripts::new(ScriptsGroup::Pre))
             .await
             .unwrap();
+        assert_eq!(ran, true);
 
         // Wait until the scripts are executed.
         while let Ok(event) = ctx.events_rx.recv().await {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-files/src/message.rs 
new/agama/agama-files/src/message.rs
--- old/agama/agama-files/src/message.rs        2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-files/src/message.rs        2026-02-06 17:21:45.000000000 
+0100
@@ -44,6 +44,7 @@
     }
 }
 
+/// Run scripts of the given group.
 #[derive(Clone)]
 pub struct RunScripts {
     pub group: ScriptsGroup,
@@ -55,8 +56,9 @@
     }
 }
 
+/// It returns true if any script ran; false otherwise.
 impl Message for RunScripts {
-    type Reply = ();
+    type Reply = bool;
 }
 
 #[derive(Clone)]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-files/src/runner.rs 
new/agama/agama-files/src/runner.rs
--- old/agama/agama-files/src/runner.rs 2026-02-02 14:01:52.000000000 +0100
+++ new/agama/agama-files/src/runner.rs 2026-02-06 17:21:45.000000000 +0100
@@ -52,11 +52,12 @@
 
 /// Implements the logic to run a script.
 ///
-/// It takes care of running the script, reporting errors (and asking whether 
to retry) and write
+/// It takes care of running the script, reporting errors (and asking whether 
to retry) and writing
 /// the logs.
 pub struct ScriptsRunner {
     progress: Handler<progress::Service>,
     questions: Handler<question::Service>,
+    root_dir: PathBuf,
     install_dir: PathBuf,
     workdir: PathBuf,
 }
@@ -64,12 +65,14 @@
 impl ScriptsRunner {
     /// Creates a new runner.
     ///
+    /// * `root_dir`: root directory of the system.
     /// * `install_dir`: directory where the system is being installed. It is 
relevant for
     ///   chrooted scripts.
     /// * `workdir`: scripts work directory.
     /// * `progress`: handler to report the progress.
     /// * `questions`: handler to interact with the user.
     pub fn new<P: AsRef<Path>>(
+        root_dir: P,
         install_dir: P,
         workdir: P,
         progress: Handler<progress::Service>,
@@ -78,6 +81,7 @@
         Self {
             progress,
             questions,
+            root_dir: root_dir.as_ref().to_path_buf(),
             install_dir: install_dir.as_ref().to_path_buf(),
             workdir: workdir.as_ref().to_path_buf(),
         }
@@ -90,7 +94,8 @@
     ///
     /// * `scripts`: scripts to run.
     pub async fn run(&self, scripts: &[&Script]) -> Result<(), Error> {
-        self.start_progress(scripts);
+        let scripts: Vec<_> = self.find_scripts_to_run(&scripts);
+        self.start_progress(&scripts);
 
         let mut resolv_linked = false;
         if scripts.iter().any(|s| s.chroot()) {
@@ -118,12 +123,9 @@
     ///
     /// If the script fails, it asks the user whether it should try again.
     async fn run_script(&self, script: &Script) -> Result<(), Error> {
-        loop {
-            let path = self
-                .workdir
-                .join(script.group().to_string())
-                .join(script.name());
+        let path = self.workdir.join(script.relative_script_path());
 
+        loop {
             let Err(error) = self.run_command(&path, script.chroot()).await 
else {
                 return Ok(());
             };
@@ -211,6 +213,23 @@
         _ = self.progress.cast(progress_action);
     }
 
+    /// Returns the scripts to run from the given collection
+    ///
+    /// It exclues any script that already ran.
+    fn find_scripts_to_run<'a>(&self, scripts: &[&'a Script]) -> Vec<&'a 
Script> {
+        scripts
+            .into_iter()
+            .filter(|s| {
+                let stdout_file = self
+                    .workdir
+                    .join(s.relative_script_path())
+                    .with_extension("stdout");
+                !std::fs::exists(stdout_file).unwrap_or(false)
+            })
+            .cloned()
+            .collect()
+    }
+
     /// Reads the last n bytes of the file and returns them as a string.
     fn read_n_last_bytes(path: &Path, n_bytes: u64) -> io::Result<String> {
         let mut file = File::open(path)?;
@@ -228,7 +247,7 @@
     ///
     /// It returns false if the resolv.conf was already linked and no action 
was required.
     fn link_resolv(&self) -> Result<bool, std::io::Error> {
-        let original = self.install_dir.join(NM_RESOLV_CONF_PATH);
+        let original = self.root_dir.join(NM_RESOLV_CONF_PATH);
         let link = self.resolv_link_path();
 
         if fs::exists(&link)? || !fs::exists(&original)? {
@@ -291,7 +310,7 @@
 
             let (events_tx, events_rx) = broadcast::channel::<Event>(16);
             let install_dir = tmp_dir.path().join("mnt");
-            let workdir = tmp_dir.path().join("scripts");
+            let workdir = tmp_dir.path().to_path_buf();
             let questions = question::start(events_tx.clone()).await.unwrap();
             let progress = 
progress::Service::starter(events_tx.clone()).start();
 
@@ -310,13 +329,18 @@
     impl Context {
         pub fn runner(&self) -> ScriptsRunner {
             ScriptsRunner::new(
-                self.install_dir.clone(),
                 self.workdir.clone(),
+                self.install_dir.clone(),
+                self.scripts_dir(),
                 self.progress.clone(),
                 self.questions.clone(),
             )
         }
 
+        pub fn scripts_dir(&self) -> PathBuf {
+            self.workdir.join("run/agama/scripts")
+        }
+
         pub fn setup_script(&self, content: &str, chroot: bool) -> Script {
             let base = BaseScript {
                 name: "test.sh".to_string(),
@@ -329,14 +353,14 @@
                 chroot: Some(chroot),
             });
             script
-                .write(&self.workdir)
+                .write(&self.scripts_dir())
                 .expect("Could not write the script");
             script
         }
 
         // Set up a fake chroot.
         pub fn setup_chroot(&self) -> std::io::Result<()> {
-            let nm_dir = self.install_dir.join("run/NetworkManager");
+            let nm_dir = self.workdir.join("run/NetworkManager");
             fs::create_dir_all(&nm_dir)?;
             fs::create_dir_all(self.install_dir.join("etc"))?;
 
@@ -348,7 +372,7 @@
 
         // Return the content of a script result file.
         pub fn result_content(&self, script_type: &str, name: &str) -> String {
-            let path = &self.workdir.join(script_type).join(name);
+            let path = &self.scripts_dir().join(script_type).join(name);
             let body: Vec<u8> = std::fs::read(path).unwrap();
             String::from_utf8(body).unwrap()
         }
@@ -448,7 +472,7 @@
         });
 
         // Check the generated files
-        let path = &ctx.workdir.join("post").join("test.stderr");
+        let path = &ctx.scripts_dir().join("post").join("test.stderr");
         let body: Vec<u8> = std::fs::read(path).unwrap();
         let body = String::from_utf8(body).unwrap();
         assert!(body.contains("agama-unknown"));
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-files/src/service.rs 
new/agama/agama-files/src/service.rs
--- old/agama/agama-files/src/service.rs        2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-files/src/service.rs        2026-02-06 17:21:45.000000000 
+0100
@@ -27,12 +27,13 @@
 use agama_utils::{
     actor::{self, Actor, Handler, MessageHandler},
     api::files::{
-        scripts::{self, ScriptsRepository},
+        scripts::{self, ScriptsGroup, ScriptsRepository},
         user_file, ScriptsConfig, UserFile,
     },
     progress, question,
 };
 use async_trait::async_trait;
+use strum::IntoEnumIterator;
 use tokio::sync::Mutex;
 
 use crate::{message, ScriptsRunner};
@@ -49,14 +50,15 @@
     Actor(#[from] actor::Error),
 }
 
-const DEFAULT_SCRIPTS_DIR: &str = "/run/agama/scripts";
+const DEFAULT_SCRIPTS_DIR: &str = "run/agama/scripts";
+const DEFAULT_WORK_DIR: &str = "/";
 const DEFAULT_INSTALL_DIR: &str = "/mnt";
 
 /// Builds and spawns the files service.
 ///
 /// This structs allows to build a files service.
 pub struct Starter {
-    scripts_workdir: PathBuf,
+    workdir: PathBuf,
     install_dir: PathBuf,
     software: Handler<software::Service>,
     progress: Handler<progress::Service>,
@@ -76,14 +78,14 @@
             software,
             progress,
             questions,
-            scripts_workdir: PathBuf::from(DEFAULT_SCRIPTS_DIR),
+            workdir: PathBuf::from(DEFAULT_WORK_DIR),
             install_dir: PathBuf::from(DEFAULT_INSTALL_DIR),
         }
     }
 
     /// Starts the service and returns the handler to communicate with it.
     pub async fn start(self) -> Result<Handler<Service>, Error> {
-        let scripts = ScriptsRepository::new(self.scripts_workdir);
+        let scripts = 
ScriptsRepository::new(self.workdir.join(DEFAULT_SCRIPTS_DIR));
         let service = Service {
             progress: self.progress,
             questions: self.questions,
@@ -91,13 +93,14 @@
             scripts: Arc::new(Mutex::new(scripts)),
             files: vec![],
             install_dir: self.install_dir,
+            root_dir: self.workdir,
         };
         let handler = actor::spawn(service);
         Ok(handler)
     }
 
-    pub fn with_scripts_workdir<P: AsRef<Path>>(mut self, workdir: P) -> Self {
-        self.scripts_workdir = PathBuf::from(workdir.as_ref());
+    pub fn with_workdir<P: AsRef<Path>>(mut self, workdir: P) -> Self {
+        self.workdir = PathBuf::from(workdir.as_ref());
         self
     }
 
@@ -114,6 +117,7 @@
     scripts: Arc<Mutex<ScriptsRepository>>,
     files: Vec<UserFile>,
     install_dir: PathBuf,
+    root_dir: PathBuf,
 }
 
 impl Service {
@@ -125,9 +129,15 @@
         Starter::new(progress, questions, software)
     }
 
+    /// Clear the scripts.
+    ///
+    /// Keep the pre-scripts because they are expected to run as soon as they 
are imported.
     pub async fn clear_scripts(&mut self) -> Result<(), Error> {
         let mut repo = self.scripts.lock().await;
-        repo.clear()?;
+        let groups: Vec<_> = ScriptsGroup::iter()
+            .filter(|g| g != &ScriptsGroup::Pre)
+            .collect();
+        repo.clear(groups.as_slice())?;
         Ok(())
     }
 
@@ -195,20 +205,26 @@
 
 #[async_trait]
 impl MessageHandler<message::RunScripts> for Service {
-    async fn handle(&mut self, message: message::RunScripts) -> Result<(), 
Error> {
-        let scripts = self.scripts.clone();
-        let install_dir = self.install_dir.clone();
-        let progress = self.progress.clone();
-        let questions = self.questions.clone();
-
-        tokio::task::spawn(async move {
-            let scripts = scripts.lock().await;
-            let workdir = scripts.workdir.clone();
-            let to_run = scripts.by_group(message.group).clone();
-            let runner = ScriptsRunner::new(install_dir, workdir, progress, 
questions);
-            runner.run(&to_run).await.unwrap();
-        });
-        Ok(())
+    async fn handle(&mut self, message: message::RunScripts) -> Result<bool, 
Error> {
+        let scripts = self.scripts.lock().await;
+        let workdir = scripts.workdir.clone();
+        let to_run = scripts.by_group(message.group).clone();
+
+        if to_run.is_empty() {
+            return Ok(false);
+        } else {
+            let runner = ScriptsRunner::new(
+                &self.root_dir,
+                &self.install_dir,
+                &workdir,
+                self.progress.clone(),
+                self.questions.clone(),
+            );
+            if let Err(error) = runner.run(&to_run).await {
+                tracing::error!("Error running scripts: {error}");
+            }
+            Ok(true)
+        }
     }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-l10n/src/model/keyboard.rs 
new/agama/agama-l10n/src/model/keyboard.rs
--- old/agama/agama-l10n/src/model/keyboard.rs  2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-l10n/src/model/keyboard.rs  2026-02-06 17:21:45.000000000 
+0100
@@ -40,14 +40,14 @@
     }
 
     pub fn with_entries(data: &[Keymap]) -> Self {
-        Self {
-            keymaps: data.to_vec(),
-        }
+        let mut database = Self::new();
+        database.set_entries(data.to_vec());
+        database
     }
 
     /// Reads the list of keymaps.
     pub fn read(&mut self) -> anyhow::Result<()> {
-        self.keymaps = get_keymaps()?;
+        self.set_entries(get_keymaps()?);
         Ok(())
     }
 
@@ -59,6 +59,33 @@
     pub fn entries(&self) -> &Vec<Keymap> {
         &self.keymaps
     }
+
+    // Set the locales entries.
+    fn set_entries(&mut self, keymaps: Vec<Keymap>) {
+        self.keymaps = keymaps;
+        self.keymaps.sort();
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_sorting_keymaps() {
+        let entries = vec![
+            Keymap::new("es".parse().unwrap(), "Spanish"),
+            Keymap::new("us".parse().unwrap(), "English (US)"),
+            Keymap::new("de".parse().unwrap(), "German"),
+        ];
+
+        let db = KeymapsDatabase::with_entries(&entries);
+        let keymaps = db.entries();
+
+        assert_eq!(keymaps[0].description.to_string(), "English (US)");
+        assert_eq!(keymaps[1].description.to_string(), "German");
+        assert_eq!(keymaps[2].description.to_string(), "Spanish");
+    }
 }
 
 /// Returns the list of keymaps to offer.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-l10n/src/model/locale.rs 
new/agama/agama-l10n/src/model/locale.rs
--- old/agama/agama-l10n/src/model/locale.rs    2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-l10n/src/model/locale.rs    2026-02-06 17:21:45.000000000 
+0100
@@ -41,10 +41,10 @@
     }
 
     pub fn with_entries(data: &[LocaleEntry]) -> Self {
-        Self {
-            known_locales: data.iter().map(|l| l.id.clone()).collect(),
-            locales: data.to_vec(),
-        }
+        let mut database = Self::new();
+        database.set_entries(data.to_vec());
+        database.known_locales = database.locales.iter().map(|l| 
l.id.clone()).collect();
+        database
     }
 
     /// Loads the list of locales.
@@ -55,7 +55,7 @@
     /// * `ui_language`: language to translate the descriptions (e.g., "en").
     pub fn read(&mut self, ui_language: &str) -> anyhow::Result<()> {
         self.known_locales = Self::get_locales_list()?;
-        self.locales = self.get_locales(ui_language)?;
+        self.set_entries(self.get_locales(ui_language)?);
         Ok(())
     }
 
@@ -125,6 +125,12 @@
         Ok(result)
     }
 
+    // Set the locales entries.
+    fn set_entries(&mut self, locales: Vec<LocaleEntry>) {
+        self.locales = locales;
+        self.locales.sort();
+    }
+
     fn get_locales_list() -> anyhow::Result<Vec<LocaleId>> {
         const LOCALES_LIST_PATH: &str = "/etc/agama.d/locales";
 
@@ -198,4 +204,45 @@
         assert!(db.exists(&en_us));
         assert!(!db.exists(&unknown));
     }
+
+    #[test]
+    fn test_sorting_locales() {
+        use agama_utils::api::l10n::LocaleEntry;
+        let entries = vec![
+            LocaleEntry {
+                id: "es_ES".parse().unwrap(),
+                language: "Spanish".to_string(),
+                territory: "Spain".to_string(),
+                consolefont: None,
+            },
+            LocaleEntry {
+                id: "en_GB".parse().unwrap(),
+                language: "English".to_string(),
+                territory: "United Kingdom".to_string(),
+                consolefont: None,
+            },
+            LocaleEntry {
+                id: "en_US".parse().unwrap(),
+                language: "English".to_string(),
+                territory: "United States".to_string(),
+                consolefont: None,
+            },
+            LocaleEntry {
+                id: "de_DE".parse().unwrap(),
+                language: "German".to_string(),
+                territory: "Germany".to_string(),
+                consolefont: None,
+            },
+        ];
+
+        let db = LocalesDatabase::with_entries(&entries);
+        let locales = db.entries();
+
+        assert_eq!(locales[0].language, "English");
+        assert_eq!(locales[0].territory, "United Kingdom");
+        assert_eq!(locales[1].language, "English");
+        assert_eq!(locales[1].territory, "United States");
+        assert_eq!(locales[2].language, "German");
+        assert_eq!(locales[3].language, "Spanish");
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-l10n/src/model/timezone.rs 
new/agama/agama-l10n/src/model/timezone.rs
--- old/agama/agama-l10n/src/model/timezone.rs  2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-l10n/src/model/timezone.rs  2026-02-06 17:21:45.000000000 
+0100
@@ -35,16 +35,16 @@
     }
 
     pub fn with_entries(data: &[TimezoneEntry]) -> Self {
-        Self {
-            timezones: data.to_vec(),
-        }
+        let mut database = Self::new();
+        database.set_entries(data.to_vec());
+        database
     }
 
     /// Initializes the list of known timezones.
     ///
     /// * `ui_language`: language to translate the descriptions (e.g., "en").
     pub fn read(&mut self, ui_language: &str) -> anyhow::Result<()> {
-        self.timezones = self.get_timezones(ui_language)?;
+        self.set_entries(self.get_timezones(ui_language)?);
         Ok(())
     }
 
@@ -91,6 +91,12 @@
 
         Ok(ret)
     }
+
+    // Set the locales entries.
+    fn set_entries(&mut self, timezones: Vec<TimezoneEntry>) {
+        self.timezones = timezones;
+        self.timezones.sort();
+    }
 }
 
 fn translate_parts(timezone: &str, ui_language: &str, tz_parts: 
&TimezoneIdParts) -> Vec<String> {
@@ -124,6 +130,7 @@
 #[cfg(test)]
 mod tests {
     use super::TimezonesDatabase;
+    use agama_utils::api::l10n::TimezoneEntry;
 
     #[test]
     fn test_read_timezones() {
@@ -175,4 +182,32 @@
         assert!(db.exists(&canary));
         assert!(!db.exists(&unknown));
     }
+
+    #[test]
+    fn test_sorting_timezones() {
+        let entries = vec![
+            TimezoneEntry {
+                id: "Europe/Madrid".parse().unwrap(),
+                parts: vec!["Europe".to_string(), "Madrid".to_string()],
+                country: Some("Spain".to_string()),
+            },
+            TimezoneEntry {
+                id: "Atlantic/Canary".parse().unwrap(),
+                parts: vec!["Atlantic".to_string(), "Canary".to_string()],
+                country: Some("Spain".to_string()),
+            },
+            TimezoneEntry {
+                id: "Europe/Berlin".parse().unwrap(),
+                parts: vec!["Europe".to_string(), "Berlin".to_string()],
+                country: Some("Germany".to_string()),
+            },
+        ];
+
+        let db = TimezonesDatabase::with_entries(&entries);
+        let timezones = db.entries();
+
+        assert_eq!(timezones[0].id.as_str(), "Atlantic/Canary");
+        assert_eq!(timezones[1].id.as_str(), "Europe/Berlin");
+        assert_eq!(timezones[2].id.as_str(), "Europe/Madrid");
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-locale-data/src/locale.rs 
new/agama/agama-locale-data/src/locale.rs
--- old/agama/agama-locale-data/src/locale.rs   2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-locale-data/src/locale.rs   2026-02-06 17:21:45.000000000 
+0100
@@ -26,7 +26,7 @@
 use std::{fmt::Display, str::FromStr};
 use thiserror::Error;
 
-#[derive(Debug, Clone, Serialize, PartialEq, utoipa::ToSchema)]
+#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, 
utoipa::ToSchema)]
 pub struct TimezoneId(String);
 
 impl Default for TimezoneId {
@@ -60,7 +60,9 @@
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, utoipa::ToSchema)]
+#[derive(
+    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, 
utoipa::ToSchema,
+)]
 pub struct LocaleId {
     // ISO-639
     pub language: String,
@@ -134,7 +136,9 @@
 /// let id_with_dashes: KeymapId = "es-ast".parse().unwrap();
 /// assert_eq!(id, id_with_dashes);
 /// ```
-#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, utoipa::ToSchema)]
+#[derive(
+    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, 
utoipa::ToSchema,
+)]
 pub struct KeymapId {
     /// Keyboard layout (e.g., "es" in "es(ast)")
     pub layout: String,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-manager/src/lib.rs 
new/agama/agama-manager/src/lib.rs
--- old/agama/agama-manager/src/lib.rs  2026-02-02 14:01:52.000000000 +0100
+++ new/agama/agama-manager/src/lib.rs  2026-02-06 17:21:45.000000000 +0100
@@ -137,25 +137,6 @@
 
     #[test_context(Context)]
     #[tokio::test]
-    async fn test_update_config_without_product(ctx: &mut Context) {
-        let input_config = Config {
-            l10n: Some(l10n::Config {
-                locale: Some("es_ES.UTF-8".to_string()),
-                keymap: Some("es".to_string()),
-                timezone: Some("Atlantic/Canary".to_string()),
-            }),
-            ..Default::default()
-        };
-
-        let error = ctx
-            .handler
-            .call(message::SetConfig::new(input_config.clone()))
-            .await;
-        assert!(matches!(error, Err(crate::service::Error::MissingProduct)));
-    }
-
-    #[test_context(Context)]
-    #[tokio::test]
     async fn test_patch_config(ctx: &mut Context) -> Result<(), Error> {
         select_product(&ctx.handler).await?;
 
@@ -184,28 +165,4 @@
 
         Ok(())
     }
-
-    #[test_context(Context)]
-    #[tokio::test]
-    async fn test_patch_config_without_product(ctx: &mut Context) -> 
Result<(), Error> {
-        let input_config = Config {
-            l10n: Some(l10n::Config {
-                keymap: Some("es".to_string()),
-                ..Default::default()
-            }),
-            ..Default::default()
-        };
-
-        let result = ctx
-            .handler
-            .call(message::UpdateConfig::new(input_config.clone()))
-            .await;
-        assert!(matches!(result, Err(crate::service::Error::MissingProduct)));
-
-        let extended_config = 
ctx.handler.call(message::GetExtendedConfig).await?;
-        let l10n_config = extended_config.l10n.unwrap();
-        assert_eq!(l10n_config.keymap, Some("us".to_string()));
-
-        Ok(())
-    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-manager/src/service.rs 
new/agama/agama-manager/src/service.rs
--- old/agama/agama-manager/src/service.rs      2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-manager/src/service.rs      2026-02-06 17:21:45.000000000 
+0100
@@ -45,8 +45,6 @@
 
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
-    #[error("Missing product")]
-    MissingProduct,
     #[error(transparent)]
     Event(#[from] broadcast::error::SendError<Event>),
     #[error(transparent)]
@@ -434,73 +432,27 @@
     }
 
     async fn set_config(&mut self, config: Config) -> Result<(), Error> {
+        tracing::debug!("SetConfig: {config:?}");
         self.set_product(&config)?;
+        self.config = config;
 
-        let Some(product) = &self.product else {
-            return Err(Error::MissingProduct);
+        let action = SetConfigAction {
+            bootloader: self.bootloader.clone(),
+            files: self.files.clone(),
+            hostname: self.hostname.clone(),
+            iscsi: self.iscsi.clone(),
+            l10n: self.l10n.clone(),
+            network: self.network.clone(),
+            proxy: self.proxy.clone(),
+            questions: self.questions.clone(),
+            security: self.security.clone(),
+            software: self.software.clone(),
+            storage: self.storage.clone(),
+            users: self.users.clone(),
         };
 
-        self.security
-            .call(security::message::SetConfig::new(config.security.clone()))
-            .await?;
-
-        self.hostname
-            .call(hostname::message::SetConfig::new(config.hostname.clone()))
-            .await?;
-
-        self.proxy
-            .call(proxy::message::SetConfig::new(config.proxy.clone()))
-            .await?;
+        action.run(self.product.clone(), &self.config).await;
 
-        self.files
-            .call(files::message::SetConfig::new(config.files.clone()))
-            .await?;
-
-        self.files
-            .call(files::message::RunScripts::new(ScriptsGroup::Pre))
-            .await?;
-
-        self.questions
-            .call(question::message::SetConfig::new(config.questions.clone()))
-            .await?;
-
-        self.software
-            .call(software::message::SetConfig::new(
-                Arc::clone(product),
-                config.software.clone(),
-            ))
-            .await?;
-
-        self.l10n
-            .call(l10n::message::SetConfig::new(config.l10n.clone()))
-            .await?;
-
-        self.users
-            .call(users::message::SetConfig::new(config.users.clone()))
-            .await?;
-
-        self.iscsi
-            .call(iscsi::message::SetConfig::new(config.iscsi.clone()))
-            .await?;
-
-        self.storage.cast(storage::message::SetConfig::new(
-            Arc::clone(product),
-            config.storage.clone(),
-        ))?;
-
-        // call bootloader always after storage to ensure that bootloader 
reflect new storage settings
-        self.bootloader
-            .call(bootloader::message::SetConfig::new(
-                config.bootloader.clone(),
-            ))
-            .await?;
-
-        if let Some(network) = config.network.clone() {
-            self.network.update_config(network).await?;
-            self.network.apply().await?;
-        }
-
-        self.config = config;
         Ok(())
     }
 
@@ -1037,3 +989,113 @@
         }
     }
 }
+
+/// Implements the set config logic.
+///
+/// This action runs on a separate Tokio task to prevent the manager from 
blocking.
+struct SetConfigAction {
+    bootloader: Handler<bootloader::Service>,
+    files: Handler<files::Service>,
+    hostname: Handler<hostname::Service>,
+    iscsi: Handler<iscsi::Service>,
+    l10n: Handler<l10n::Service>,
+    network: NetworkSystemClient,
+    proxy: Handler<proxy::Service>,
+    questions: Handler<question::Service>,
+    security: Handler<security::Service>,
+    software: Handler<software::Service>,
+    storage: Handler<storage::Service>,
+    users: Handler<users::Service>,
+}
+
+impl SetConfigAction {
+    pub async fn run(self, product: Option<Arc<RwLock<ProductSpec>>>, config: 
&Config) {
+        let config = config.clone();
+        tokio::spawn(async move {
+            tracing::info!("Updating the configuration");
+            match self.set_config(product, config).await {
+                Ok(_) => tracing::info!("Configuration updated successfully"),
+                Err(error) => tracing::error!("Failed to update the 
configuration: {error}"),
+            }
+        });
+    }
+
+    async fn set_config(
+        self,
+        product: Option<Arc<RwLock<ProductSpec>>>,
+        config: Config,
+    ) -> Result<(), Error> {
+        self.security
+            .call(security::message::SetConfig::new(config.security.clone()))
+            .await?;
+
+        self.hostname
+            .call(hostname::message::SetConfig::new(config.hostname.clone()))
+            .await?;
+
+        self.proxy
+            .call(proxy::message::SetConfig::new(config.proxy.clone()))
+            .await?;
+
+        self.files
+            .call(files::message::SetConfig::new(config.files.clone()))
+            .await?;
+
+        self.files
+            .call(files::message::RunScripts::new(ScriptsGroup::Pre))
+            .await?;
+
+        self.questions
+            .call(question::message::SetConfig::new(config.questions.clone()))
+            .await?;
+
+        self.l10n
+            .call(l10n::message::SetConfig::new(config.l10n.clone()))
+            .await?;
+
+        self.users
+            .call(users::message::SetConfig::new(config.users.clone()))
+            .await?;
+
+        self.iscsi
+            .call(iscsi::message::SetConfig::new(config.iscsi.clone()))
+            .await?;
+
+        if let Some(network) = config.network.clone() {
+            self.network.update_config(network).await?;
+            self.network.apply().await?;
+        }
+
+        match &product {
+            Some(product) => {
+                self.software
+                    .call(software::message::SetConfig::new(
+                        Arc::clone(product),
+                        config.software.clone(),
+                    ))
+                    .await?;
+
+                self.storage
+                    .call(storage::message::SetConfig::new(
+                        Arc::clone(product),
+                        config.storage.clone(),
+                    ))
+                    .await?;
+
+                // call bootloader always after storage to ensure that 
bootloader reflect new storage settings
+                self.bootloader
+                    .call(bootloader::message::SetConfig::new(
+                        config.bootloader.clone(),
+                    ))
+                    .await?;
+            }
+
+            None => {
+                // TODO: reset software and storage proposals.
+                tracing::info!("No product is selected.");
+            }
+        }
+
+        Ok(())
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-server/tests/server_service.rs 
new/agama/agama-server/tests/server_service.rs
--- old/agama/agama-server/tests/server_service.rs      2026-02-02 
14:01:52.000000000 +0100
+++ new/agama/agama-server/tests/server_service.rs      2026-02-06 
17:21:45.000000000 +0100
@@ -152,30 +152,6 @@
 
 #[test_context(Context)]
 #[test]
-async fn test_put_config_without_product(ctx: &mut Context) -> Result<(), 
Box<dyn Error>> {
-    let json = r#"
-        {
-          "l10n": {
-            "locale": "es_ES.UTF-8", "keymap": "es", "timezone": 
"Atlantic/Canary"
-          }
-        }
-    "#;
-
-    let request = Request::builder()
-        .uri("/config")
-        .header("Content-Type", "application/json")
-        .method(Method::PUT)
-        .body(json.to_string())
-        .unwrap();
-
-    let response = ctx.client.send_request(request).await;
-    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
-
-    Ok(())
-}
-
-#[test_context(Context)]
-#[test]
 async fn test_put_config_without_mode(ctx: &mut Context) -> Result<(), Box<dyn 
Error>> {
     let json = r#"
         {
@@ -274,28 +250,6 @@
 
     Ok(())
 }
-
-#[test_context(Context)]
-#[test]
-async fn test_patch_config_without_selected_product(
-    ctx: &mut Context,
-) -> Result<(), Box<dyn Error>> {
-    let json = r#"{ "update": { "l10n": { "keymap": "en" } } }"#;
-    let request = Request::builder()
-        .uri("/config")
-        .header("Content-Type", "application/json")
-        .method(Method::PATCH)
-        .body(json.to_string())
-        .unwrap();
-
-    let response = ctx.client.send_request(request).await;
-    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
-
-    let body = body_to_string(response.into_body()).await;
-    assert_eq!(body, r#"{"error":"Missing product"}"#);
-
-    Ok(())
-}
 
 #[test_context(Context)]
 #[test]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-software/src/model.rs 
new/agama/agama-software/src/model.rs
--- old/agama/agama-software/src/model.rs       2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-software/src/model.rs       2026-02-06 17:21:45.000000000 
+0100
@@ -149,10 +149,21 @@
             .iter()
             .map(|r| r.url.as_str())
             .collect();
+        let mut missing_predefined_repos = 
self.predefined_repositories.clone();
+
         for repo in system_info.repositories.iter_mut() {
             repo.predefined = predefined_urls.contains(&repo.url.as_str());
+            if repo.predefined {
+                missing_predefined_repos.retain(|r| r.url != repo.url);
+            }
         }
 
+        // Add all predefined repositories that are missing in libzypp.
+        // As a result, they will be added to libzypp when writing the new
+        // software state.
+        system_info.repositories.extend(missing_predefined_repos);
+        tracing::info!("System info: {:?}", system_info);
+
         Ok(system_info)
     }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-software/src/service.rs 
new/agama/agama-software/src/service.rs
--- old/agama/agama-software/src/service.rs     2026-02-02 14:01:52.000000000 
+0100
+++ new/agama/agama-software/src/service.rs     2026-02-06 17:21:45.000000000 
+0100
@@ -459,6 +459,8 @@
         repos.push(dud)
     }
 
+    tracing::info!("Using mandatory repositories: {:?}", repos);
+
     repos
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-utils/src/api/files/scripts.rs 
new/agama/agama-utils/src/api/files/scripts.rs
--- old/agama/agama-utils/src/api/files/scripts.rs      2026-02-02 
14:01:52.000000000 +0100
+++ new/agama/agama-utils/src/api/files/scripts.rs      2026-02-06 
17:21:45.000000000 +0100
@@ -132,6 +132,14 @@
         self.base().write(&path)
     }
 
+    /// Returns the relative script path.
+    ///
+    /// The full script path depends on the workdir. This method returns
+    /// the relative path (e.g., "pre/my-script.sh").
+    pub fn relative_script_path(&self) -> PathBuf {
+        PathBuf::from(self.group().to_string()).join(&self.base().name)
+    }
+
     /// Script's group.
     ///
     /// It determines whether the script runs.
@@ -289,8 +297,10 @@
     }
 
     /// Removes all the scripts from the repository.
-    pub fn clear(&mut self) -> Result<(), Error> {
-        for group in ScriptsGroup::iter() {
+    ///
+    /// * `groups`: groups of scripts to clear.
+    pub fn clear(&mut self, groups: &[ScriptsGroup]) -> Result<(), Error> {
+        for group in ScriptsGroup::iter().filter(|g| groups.contains(&g)) {
             let path = self.workdir.join(group.to_string());
             if path.exists() {
                 std::fs::remove_dir_all(path)?;
@@ -319,14 +329,18 @@
 
 #[cfg(test)]
 mod test {
+    use std::path::PathBuf;
+
     use tempfile::TempDir;
-    use tokio::test;
 
-    use crate::api::files::{BaseScript, FileSource, PreScript, Script};
+    use crate::api::files::{
+        scripts::ScriptsGroup, BaseScript, FileSource, InitScript, 
PostPartitioningScript,
+        PostScript, PreScript, Script,
+    };
 
     use super::ScriptsRepository;
 
-    #[test]
+    #[tokio::test]
     async fn test_add_script() {
         let tmp_dir = TempDir::with_prefix("scripts-").expect("a temporary 
directory");
         let mut repo = ScriptsRepository::new(&tmp_dir);
@@ -347,7 +361,7 @@
         assert!(script_path.exists());
     }
 
-    #[test]
+    #[tokio::test]
     async fn test_clear_scripts() {
         let tmp_dir = TempDir::with_prefix("scripts-").expect("a temporary 
directory");
         let mut repo = ScriptsRepository::new(&tmp_dir);
@@ -365,10 +379,34 @@
 
         let script_path = tmp_dir.path().join("pre").join("test");
         assert!(script_path.exists());
-        _ = repo.clear();
+        _ = repo.clear(&[ScriptsGroup::Pre]);
         assert!(!script_path.exists());
 
         // the directory for AutoYaST scripts is not removed
         assert!(autoyast_path.exists())
     }
+
+    #[test]
+    fn test_relative_script_path() {
+        let base = BaseScript {
+            name: "test".to_string(),
+            source: FileSource::Text {
+                content: "".to_string(),
+            },
+        };
+        let script = Script::Pre(PreScript { base: base.clone() });
+        assert_eq!(script.relative_script_path(), PathBuf::from("pre/test"));
+        let script = Script::PostPartitioning(PostPartitioningScript { base: 
base.clone() });
+        assert_eq!(
+            script.relative_script_path(),
+            PathBuf::from("postPartitioning/test")
+        );
+        let script = Script::Post(PostScript {
+            base: base.clone(),
+            chroot: Some(false),
+        });
+        assert_eq!(script.relative_script_path(), PathBuf::from("post/test"));
+        let script = Script::Init(InitScript { base: base.clone() });
+        assert_eq!(script.relative_script_path(), PathBuf::from("init/test"));
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-utils/src/api/l10n/system_info.rs 
new/agama/agama-utils/src/api/l10n/system_info.rs
--- old/agama/agama-utils/src/api/l10n/system_info.rs   2026-02-02 
14:01:52.000000000 +0100
+++ new/agama/agama-utils/src/api/l10n/system_info.rs   2026-02-06 
17:21:45.000000000 +0100
@@ -47,7 +47,7 @@
 
 /// Represents a locale, including the localized language and territory.
 #[serde_as]
-#[derive(Debug, Serialize, Clone, utoipa::ToSchema)]
+#[derive(Debug, Serialize, Clone, utoipa::ToSchema, PartialEq, Eq)]
 pub struct LocaleEntry {
     /// The locale code (e.g., "es_ES.UTF-8").
     #[serde_as(as = "DisplayFromStr")]
@@ -60,8 +60,24 @@
     pub consolefont: Option<String>,
 }
 
+impl Ord for LocaleEntry {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.language
+            .cmp(&other.language)
+            .then_with(|| self.territory.cmp(&other.territory))
+            .then_with(|| self.id.cmp(&other.id))
+            .then_with(|| self.consolefont.cmp(&other.consolefont))
+    }
+}
+
+impl PartialOrd for LocaleEntry {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
 /// Represents a timezone, including each part as localized.
-#[derive(Clone, Debug, Serialize, utoipa::ToSchema)]
+#[derive(Clone, Debug, Serialize, utoipa::ToSchema, PartialEq, Eq)]
 pub struct TimezoneEntry {
     /// Timezone identifier (e.g. "Atlantic/Canary").
     pub id: TimezoneId,
@@ -71,13 +87,42 @@
     pub country: Option<String>,
 }
 
+impl Ord for TimezoneEntry {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.parts
+            .cmp(&other.parts)
+            .then_with(|| self.country.cmp(&other.country))
+            .then_with(|| self.id.cmp(&other.id))
+    }
+}
+
+impl PartialOrd for TimezoneEntry {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
 // Minimal representation of a keymap
-#[derive(Clone, Debug, utoipa::ToSchema)]
+#[derive(Clone, Debug, utoipa::ToSchema, PartialEq, Eq)]
 pub struct Keymap {
     /// Keymap identifier (e.g., "us")
     pub id: KeymapId,
     /// Keymap description
-    description: String,
+    pub description: String,
+}
+
+impl Ord for Keymap {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.localized_description()
+            .cmp(&other.localized_description())
+            .then_with(|| self.id.cmp(&other.id))
+    }
+}
+
+impl PartialOrd for Keymap {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
 }
 
 impl Keymap {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/package/agama.changes 
new/agama/package/agama.changes
--- old/agama/package/agama.changes     2026-02-02 14:01:52.000000000 +0100
+++ new/agama/package/agama.changes     2026-02-06 17:21:45.000000000 +0100
@@ -1,4 +1,33 @@
 -------------------------------------------------------------------
+Fri Feb  6 13:59:11 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Fix parsing of the answers file (bsc#1257400).
+
+-------------------------------------------------------------------
+Fri Feb  6 07:38:38 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Allow loading a profile with no product (bsc#1257067 and bsc#1257082).
+- Make the bootloader proposal after the storage one is ready (bsc#1257530).
+- Run pre-scripts only once to prevent potential infinite loops.
+
+-------------------------------------------------------------------
+Thu Feb  5 06:46:46 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Sort the list of languages, keymaps and timezones (bsc#1257497).
+
+-------------------------------------------------------------------
+Wed Feb  4 11:37:18 UTC 2026 - Josef Reidinger <[email protected]>
+
+- Fix usage of local repositories on full medium
+  (bsc#1257475)
+
+-------------------------------------------------------------------
+Tue Feb  3 07:23:18 UTC 2026 - Knut Anderssen <[email protected]>
+
+- Correct the /etc/resolv.conf link in the chroot to
+  /run/NetworkManager/resolv.conf (bsc#257468).
+
+-------------------------------------------------------------------
 Mon Feb  2 11:56:24 UTC 2026 - Ladislav Slezák <[email protected]>
 
 - Fixed opening the SSH firewall port when the firewall is not
@@ -28,7 +57,7 @@
 Thu Jan 29 22:30:05 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
 
 - Do not export neither network/connections nor network/state when
-  they are None (related to bsc#1257400).
+  they are None (related to bsc#1257230).
 
 -------------------------------------------------------------------
 Thu Jan 29 21:10:18 UTC 2026 - Josef Reidinger <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/share/system.dasd.schema.json 
new/agama/share/system.dasd.schema.json
--- old/agama/share/system.dasd.schema.json     1970-01-01 01:00:00.000000000 
+0100
+++ new/agama/share/system.dasd.schema.json     2026-02-06 17:21:45.000000000 
+0100
@@ -0,0 +1,44 @@
+{
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "$id": 
"https://github.com/openSUSE/agama/blob/master/rust/share/system.storage.schema.json";,
+  "title": "System",
+  "description": "API description of the DASD system",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["devices"],
+  "properties": {
+    "devices": {
+      "description": "DASD devices",
+      "type": "array",
+      "items": { "$ref": "#/$defs/device" }
+    }
+  },
+  "$defs": {
+    "device": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "channel",
+        "deviceName",
+        "type",
+        "diag",
+        "accessType",
+        "partitionInfo",
+        "status",
+        "active",
+        "formatted"
+      ],
+      "properties": {
+        "channel": { "type": "string" },
+        "deviceName": { "type": "string" },
+        "type": { "type": "string" },
+        "diag": { "type": "boolean" },
+        "accessType": { "type": "string" },
+        "partitionInfo": { "type": "string" },
+        "status": { "type": "string" },
+        "active": { "type": "boolean" },
+        "formatted": { "type": "boolean" }
+      }
+    }
+  }
+}

++++++ agama.obsinfo ++++++
--- /var/tmp/diff_new_pack.fjXUH2/_old  2026-02-07 15:33:56.358651414 +0100
+++ /var/tmp/diff_new_pack.fjXUH2/_new  2026-02-07 15:33:56.374652079 +0100
@@ -1,5 +1,5 @@
 name: agama
-version: 19.pre+1359.77cf8dc51
-mtime: 1770037312
-commit: 77cf8dc51086f7e25c669ae83399e40e5c1e27e5
+version: 19.pre+1415.80da57854
+mtime: 1770394905
+commit: 80da578541862b51b08a290145b25597faed02f3
 

++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/agama/vendor.tar.zst 
/work/SRC/openSUSE:Factory/.agama.new.1670/vendor.tar.zst differ: char 7, line 1

Reply via email to