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-04-01 19:54:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/agama (Old)
 and      /work/SRC/openSUSE:Factory/.agama.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "agama"

Wed Apr  1 19:54:57 2026 rev:41 rq:1343948 version:0

Changes:
--------
--- /work/SRC/openSUSE:Factory/agama/agama.changes      2026-03-18 
16:49:54.643534669 +0100
+++ /work/SRC/openSUSE:Factory/.agama.new.21863/agama.changes   2026-04-01 
19:55:31.105308474 +0200
@@ -1,0 +2,52 @@
+Tue Mar 24 13:01:38 UTC 2026 - Ladislav Slezák <[email protected]>
+
+- Include more details when saving the journal log
+- Save separate libzypp.out.log file in the old y2log format
+  for libzypp debugging
+- Store the complete journal log in JSON format to include hidden
+  fields and more details
+- gh#agama-project/agama#3309
+
+-------------------------------------------------------------------
+Fri Mar 20 16:56:53 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Enable agama-scripts service if needed (bsc#1259899).
+
+-------------------------------------------------------------------
+Fri Mar 20 13:55:22 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Enable SELinux if needed in unattended installations (bsc#1259890).
+
+-------------------------------------------------------------------
+Fri Mar 20 08:46:19 UTC 2026 - Knut Anderssen <[email protected]>
+
+- Return error code when using "agama config generate",
+  "agama config load" or "agama config validate" does not validate
+  the given profile (bsc#1256951, gh#agama-project/agama#3304).
+
+-------------------------------------------------------------------
+Fri Mar 20 07:54:46 UTC 2026 - Knut Anderssen <[email protected]>
+
+- Fix incorrect openAPI parameters (gh#agama-project/agama#3299).
+  - Do not define params for POST '/api/v2/action'
+  - Do not define params for PUT and PATCH '/api/v2/config
+
+-------------------------------------------------------------------
+Thu Mar 19 21:11:29 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Continue loading the configuration even if there is a network setup
+  error (gh#agama-project/agama#3306).
+
+-------------------------------------------------------------------
+Wed Mar 18 22:29:10 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Report problems when writing files and scripts (gh#agama-project/agama#3301).
+
+-------------------------------------------------------------------
+Tue Mar 17 14:49:14 UTC 2026 - Michal Filka <[email protected]>
+
+- jsc#PED-15434
+  - fixed autoyast_compat.json to cover earlier patch which
+    extended capabilities of sshPublicKey / sshPublicKeys
+
+-------------------------------------------------------------------

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

Other differences:
------------------
++++++ agama.spec ++++++
--- /var/tmp/diff_new_pack.OZ1gw2/_old  2026-04-01 19:55:33.265397640 +0200
+++ /var/tmp/diff_new_pack.OZ1gw2/_new  2026-04-01 19:55:33.269397806 +0200
@@ -246,6 +246,8 @@
 %license LICENSE
 %{_bindir}/agama-web-server
 %{_bindir}/agama-web-server.sh
+%{_bindir}/agama-journal
+%{_bindir}/agama-zypp-journal
 %{_bindir}/agama-proxy-setup
 %{_pam_vendordir}/agama
 %{_unitdir}/agama-web-server.service

++++++ 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-03-17 15:08:44.000000000 +0100
+++ new/agama/Cargo.lock        2026-03-24 16:36:47.000000000 +0100
@@ -71,6 +71,7 @@
  "agama-software",
  "agama-utils",
  "async-trait",
+ "gettext-rs",
  "serde_json",
  "strum",
  "tempfile",
@@ -342,7 +343,6 @@
 name = "agama-software"
 version = "0.1.0"
 dependencies = [
- "agama-bootloader",
  "agama-l10n",
  "agama-security",
  "agama-utils",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-cli/src/config.rs 
new/agama/agama-cli/src/config.rs
--- old/agama/agama-cli/src/config.rs   2026-03-17 15:08:44.000000000 +0100
+++ new/agama/agama-cli/src/config.rs   2026-03-24 16:36:47.000000000 +0100
@@ -129,7 +129,7 @@
             let valid = validate(&http_client, 
CliInput::Full(contents.clone()), false).await?;
 
             if !matches!(valid, ValidationOutcome::Valid) {
-                return Ok(());
+                return Err(anyhow!("The profile is not valid"));
             }
 
             let model: api::Config = serde_json::from_str(&contents)?;
@@ -138,12 +138,16 @@
             monitor_progress(monitor).await?;
         }
         ConfigCommands::Validate { url_or_path, local } => {
-            let _ = if !local {
+            let validity = if !local {
                 let http_client = build_http_client(api_url, opts.insecure, 
true).await?;
-                validate(&http_client, url_or_path, false).await
+                validate(&http_client, url_or_path, false).await?
             } else {
-                validate_local(url_or_path, opts.insecure)
+                validate_local(url_or_path, opts.insecure)?
             };
+
+            if !matches!(validity, ValidationOutcome::Valid) {
+                return Err(anyhow!("The profile is not valid"));
+            }
         }
         ConfigCommands::Generate { url_or_path } => {
             let http_client = build_http_client(api_url, opts.insecure, 
true).await?;
@@ -293,7 +297,7 @@
         println!("{}", &profile_json);
         let _ = validation_msg(&validity);
 
-        return Ok(());
+        return Err(anyhow!("The profile is not valid"));
     }
 
     let config = api::Config::from_json(&profile_json, &context.source)?;
@@ -303,10 +307,9 @@
     let validity = validate(client, CliInput::Full(config_json.clone()), 
false).await?;
 
     if matches!(validity, ValidationOutcome::NotValid(_)) {
-        eprintln!(
-            "{} Internal error: the profile was made invalid by 
InstallSettings round trip",
-            style("\u{2717}").bold().red()
-        );
+        return Err(anyhow!(
+            "Internal error: the profile became invalid during Config round 
trip"
+        ));
     }
 
     Ok(())
@@ -364,10 +367,14 @@
     let status = command.status().context(format!("Running {:?}", command))?;
     // TODO: do nothing if the content of the file is unchanged
     if status.success() {
-        // FIXME: invalid profile still gets loaded
         let updated =
             std::fs::read_to_string(&path).context(format!("Reading from file 
{:?}", path))?;
-        validate(http_client, CliInput::Full(updated.clone()), false).await?;
+        let validity = validate(http_client, CliInput::Full(updated.clone()), 
false).await?;
+
+        if !matches!(validity, ValidationOutcome::Valid) {
+            return Err(anyhow!("The profile is not valid"));
+        }
+
         return Ok(serde_json::from_str(&updated)?);
     }
 
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-03-17 15:08:44.000000000 +0100
+++ new/agama/agama-files/Cargo.toml    2026-03-24 16:36:47.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"
+gettext-rs = { version = "0.7.7", features = ["gettext-system"] }
 strum = "0.27.2"
 tempfile = "3.23.0"
 thiserror = "2.0.17"
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-03-17 15:08:44.000000000 +0100
+++ new/agama/agama-files/src/lib.rs    2026-03-24 16:36:47.000000000 +0100
@@ -156,7 +156,7 @@
             .await
             .unwrap();
 
-        ctx.handler.call(message::WriteFiles).await.unwrap();
+        ctx.handler.call(message::Finish).await.unwrap();
 
         // Check that the file exists
         let expected_path = ctx.tmp_dir.path().join("etc/README.md");
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-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-files/src/message.rs        2026-03-24 16:36:47.000000000 
+0100
@@ -62,9 +62,9 @@
 }
 
 #[derive(Clone)]
-pub struct WriteFiles;
+pub struct Finish;
 
-impl Message for WriteFiles {
+impl Message for Finish {
     type Reply = ();
 }
 
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-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-files/src/service.rs        2026-03-24 16:36:47.000000000 
+0100
@@ -26,13 +26,19 @@
 use agama_software::{self as software, Resolvable, ResolvableType};
 use agama_utils::{
     actor::{self, Actor, Handler, MessageHandler},
-    api::files::{
-        scripts::{self, ScriptsGroup, ScriptsRepository},
-        user_file, ScriptsConfig, UserFile,
+    api::{
+        files::{
+            scripts::{self, ScriptsGroup, ScriptsRepository},
+            user_file, Script, ScriptsConfig, UserFile,
+        },
+        question::QuestionSpec,
     },
-    progress, question,
+    command::enable_service,
+    progress,
+    question::{self, ask_question, AskError},
 };
 use async_trait::async_trait;
+use gettextrs::gettext;
 use strum::IntoEnumIterator;
 use tokio::sync::Mutex;
 
@@ -48,6 +54,8 @@
     Software(#[from] software::service::Error),
     #[error(transparent)]
     Actor(#[from] actor::Error),
+    #[error(transparent)]
+    Questions(#[from] AskError),
 }
 
 const DEFAULT_SCRIPTS_DIR: &str = "run/agama/scripts";
@@ -142,29 +150,28 @@
     }
 
     pub async fn add_scripts(&mut self, config: ScriptsConfig) -> Result<(), 
Error> {
-        let mut repo = self.scripts.lock().await;
         if let Some(scripts) = config.pre {
             for pre in scripts {
-                repo.add(pre.into())?;
+                self.add_script(pre.into()).await?;
             }
         }
 
         if let Some(scripts) = config.post_partitioning {
             for post in scripts {
-                repo.add(post.into())?;
+                self.add_script(post.into()).await?;
             }
         }
 
         if let Some(scripts) = config.post {
             for post in scripts {
-                repo.add(post.into())?;
+                self.add_script(post.into()).await?;
             }
         }
 
         let mut packages = vec![];
         if let Some(scripts) = config.init {
             for init in scripts {
-                repo.add(init.into())?;
+                self.add_script(init.into()).await?;
             }
             packages.push(Resolvable::new("agama-scripts", 
ResolvableType::Package));
         }
@@ -176,6 +183,59 @@
             .await?;
         Ok(())
     }
+
+    async fn add_script(&self, script: Script) -> Result<(), Error> {
+        let result = {
+            let mut repo = self.scripts.lock().await;
+            repo.add(script.clone())
+        };
+
+        let mut attempt = 1;
+        while let Err(error) = &result {
+            tracing::error!("Failed to write the script {}: {error}.", 
script.name());
+
+            // TRANSLATORS: %s is replaced by the script name.
+            let text = &gettext("Failed to retrieve the script %s. Do you want 
to try again?")
+                .replace("%s", script.name());
+            let question = QuestionSpec::new(text, "write_script_failed")
+                .with_yes_no_actions()
+                .with_data(&[
+                    ("attempt", &attempt.to_string()),
+                    ("details", &error.to_string()),
+                ]);
+            let answer = ask_question(&self.questions, question).await?;
+            if answer.action == "No" {
+                return Ok(());
+            }
+            attempt += 1;
+        }
+
+        Ok(())
+    }
+
+    async fn write_file(&self, file: &UserFile) -> Result<(), Error> {
+        let mut attempt = 1;
+        while let Err(error) = file.write(&self.install_dir).await {
+            tracing::error!("Failed to write the file {}: {error}.", 
file.destination);
+
+            // TRANSLATORS: %s is replaced by the script name.
+            let text = &gettext("Failed to write the file %s. Do you want to 
try again?")
+                .replace("%s", &file.destination);
+            let question = QuestionSpec::new(text, "write_file_failed")
+                .with_yes_no_actions()
+                .with_data(&[
+                    ("attempt", &attempt.to_string()),
+                    ("details", &error.to_string()),
+                ]);
+            let answer = ask_question(&self.questions, question).await?;
+            if answer.action == "No" {
+                return Ok(());
+            }
+            attempt += 1;
+        }
+
+        Ok(())
+    }
 }
 
 impl Actor for Service {
@@ -234,13 +294,17 @@
 }
 
 #[async_trait]
-impl MessageHandler<message::WriteFiles> for Service {
-    async fn handle(&mut self, _message: message::WriteFiles) -> Result<(), 
Error> {
+impl MessageHandler<message::Finish> for Service {
+    async fn handle(&mut self, _message: message::Finish) -> Result<(), Error> 
{
         for file in &self.files {
-            if let Err(error) = file.write(&self.install_dir).await {
-                tracing::error!("Failed to write file {}: {error}", 
file.destination);
-            }
+            self.write_file(file).await?;
         }
+
+        let scripts = self.scripts.lock().await;
+        if !scripts.by_group(ScriptsGroup::Init).is_empty() {
+            enable_service(&self.install_dir, "agama-scripts");
+        }
+
         Ok(())
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-lib/src/logs.rs 
new/agama/agama-lib/src/logs.rs
--- old/agama/agama-lib/src/logs.rs     2026-03-17 15:08:44.000000000 +0100
+++ new/agama/agama-lib/src/logs.rs     2026-03-24 16:36:47.000000000 +0100
@@ -24,7 +24,6 @@
 use std::fs;
 use std::fs::File;
 use std::io;
-use std::io::Write;
 use std::os::unix::fs::PermissionsExt;
 use std::path::{Path, PathBuf};
 use std::process::Command;
@@ -32,9 +31,11 @@
 use utoipa::ToSchema;
 use zypp_agama::SOLVER_TESTCASE_DIR;
 
-const DEFAULT_COMMANDS: [(&str, &str); 2] = [
+const DEFAULT_COMMANDS: [(&str, &str); 4] = [
     // (<command to be executed>, <file name used for storing result of the 
command>)
-    ("journalctl", "journald"),
+    ("journalctl -o json", "journal_json"),
+    ("agama-journal", "journal"),
+    ("agama-zypp-journal", "libzypp"),
     ("rpm -qa", "rpm-qa"),
 ];
 
@@ -178,19 +179,34 @@
     fn store(&self) -> Result<(), io::Error> {
         let cmd_parts = self.cmd.split_whitespace().collect::<Vec<&str>>();
         let file_path = self.to();
-        let output = Command::new(cmd_parts[0])
-            .args(cmd_parts[1..].iter())
-            .output()?;
 
-        if !output.stdout.is_empty() {
-            let mut file_stdout = File::create(format!("{}.out.log", 
file_path.display()))?;
+        let mut stdout_name = file_path.clone();
+        stdout_name.set_extension("out.log");
 
-            file_stdout.write_all(&output.stdout)?;
-        }
-        if !output.stderr.is_empty() {
-            let mut file_stderr = File::create(format!("{}.err.log", 
file_path.display()))?;
+        let mut stderr_name = file_path.clone();
+        stderr_name.set_extension("err.log");
+
+        let file_stdout = File::create(&stdout_name)?;
+        let file_stderr = File::create(&stderr_name)?;
 
-            file_stderr.write_all(&output.stderr)?;
+        let status = Command::new(cmd_parts[0])
+            .args(&cmd_parts[1..])
+            .stdout(file_stdout)
+            .stderr(file_stderr)
+            .status()?;
+
+        tracing::info!(
+            "Command {} finished with status: {}",
+            self.cmd,
+            status.code().unwrap_or_default()
+        );
+
+        // delete the created files if they are empty
+        if fs::metadata(&stdout_name)?.len() == 0 {
+            let _ = fs::remove_file(&stdout_name);
+        }
+        if fs::metadata(&stderr_name)?.len() == 0 {
+            let _ = fs::remove_file(&stderr_name);
         }
 
         Ok(())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-manager/src/actions.rs 
new/agama/agama-manager/src/actions.rs
--- old/agama/agama-manager/src/actions.rs      2026-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-manager/src/actions.rs      2026-03-24 16:36:47.000000000 
+0100
@@ -113,7 +113,7 @@
             .await?;
         self.l10n.call(l10n::message::Install).await?;
         self.software.call(software::message::Finish).await?;
-        self.files.call(files::message::WriteFiles).await?;
+        self.files.call(files::message::Finish).await?;
         self.network.install().await?;
         self.proxy.call(proxy::message::Finish).await?;
         self.hostname.call(hostname::message::Install).await?;
@@ -294,12 +294,9 @@
                 .await?;
         }
 
-        if let Some(network) = config.network.clone() {
-            self.progress
-                .call(progress::message::Next::new(Scope::Manager))
-                .await?;
-            self.network.update_config(network).await?;
-            self.network.apply().await?;
+        // FIXME: report the error in a proper way.
+        if let Err(error) = self.set_network(&config).await {
+            tracing::error!("Failed to set up the network: {error}");
         }
 
         match &product {
@@ -307,6 +304,18 @@
                 self.progress
                     .call(progress::message::Next::new(Scope::Manager))
                     .await?;
+                self.software
+                    .call(software::message::SetConfig::new(
+                        Arc::clone(product),
+                        config.software.clone(),
+                    ))
+                    .await?;
+
+                self.set_selinux().await?;
+
+                self.progress
+                    .call(progress::message::Next::new(Scope::Manager))
+                    .await?;
                 let future = self
                     .storage
                     .call(storage::message::SetConfig::new(
@@ -325,16 +334,6 @@
                         config.bootloader.clone(),
                     ))
                     .await?;
-
-                self.progress
-                    .call(progress::message::Next::new(Scope::Manager))
-                    .await?;
-                self.software
-                    .call(software::message::SetConfig::new(
-                        Arc::clone(product),
-                        config.software.clone(),
-                    ))
-                    .await?;
             }
 
             None => {
@@ -344,6 +343,52 @@
         }
 
         Ok(())
+    }
+
+    async fn set_network(&self, config: &Config) -> Result<(), service::Error> 
{
+        let Some(network) = config.network.clone() else {
+            return Ok(());
+        };
+
+        self.progress
+            .call(progress::message::Next::new(Scope::Manager))
+            .await?;
+        self.network.update_config(network).await?;
+        self.network.apply().await?;
+
+        Ok(())
+    }
+
+    // Enables/Disables SELinux in the installed system.
+    //
+    // If the "selinux" pattern is selected, set the "security=selinux" boot
+    // kernel parameter.
+    //
+    // NOTE: this logic should live in another place, like "agama-security".
+    // It is temporarily here to fix bsc#1259890.
+    async fn set_selinux(&self) -> Result<(), service::Error> {
+        let selinux_selected = self
+            .software
+            .call(software::message::IsPatternSelected::new(
+                "selinux".to_string(),
+            ))
+            .await?;
+
+        let value = if selinux_selected {
+            "security=selinux"
+        } else {
+            "security="
+        };
+        let message = agama_bootloader::message::SetKernelArg {
+            id: "selinux".to_string(),
+            value: value.to_string(),
+        };
+
+        if let Err(error) = self.bootloader.cast(message) {
+            tracing::warn!("Failed to send to bootloader new selinux state: 
{error:?}");
+        }
+
+        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-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-manager/src/service.rs      2026-03-24 16:36:47.000000000 
+0100
@@ -281,7 +281,6 @@
                     progress.clone(),
                     self.questions.clone(),
                     security.clone(),
-                    bootloader.clone(),
                 )
                 .start()
                 .await?
@@ -730,7 +729,6 @@
     /// Sets the user configuration with the given values.
     async fn handle(&mut self, message: message::SetConfig) -> Result<(), 
Error> {
         checks::check_stage(&self.progress, Stage::Configuring).await?;
-        tracing::debug!("DEBUG: SetConfig handler (calling set_config)");
         self.set_config(message.config).await
     }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-proxy/src/service.rs 
new/agama/agama-proxy/src/service.rs
--- old/agama/agama-proxy/src/service.rs        2026-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-proxy/src/service.rs        2026-03-24 16:36:47.000000000 
+0100
@@ -19,10 +19,7 @@
 // find current contact information at www.suse.com.
 
 use crate::model;
-use std::{
-    path::{Path, PathBuf},
-    process,
-};
+use std::path::{Path, PathBuf};
 
 use agama_utils::{
     actor::{self, Actor, Handler, MessageHandler},
@@ -30,6 +27,7 @@
         self,
         event::{self},
     },
+    command::enable_service,
 };
 use async_trait::async_trait;
 
@@ -203,31 +201,9 @@
     pub fn config_path(&self) -> PathBuf {
         self.install_dir.join(PROXY_PATH)
     }
-    pub fn enable_services(&self) -> Result<(), Error> {
-        self.enable_service("setup-systemd-proxy-env.service")?;
-        self.enable_service("setup-systemd-proxy-env.path")?;
-        Ok(())
-    }
-
-    pub fn enable_service(&self, name: &str) -> Result<(), Error> {
-        let mut command = process::Command::new("chroot");
-        let path = self.install_dir.to_str().unwrap();
-        command.args([path, "systemctl", "enable", name]);
-
-        match command.output() {
-            Ok(output) => {
-                if !output.status.success() {
-                    tracing::error!("Failed to enable the {name} service: 
{output:?}")
-                }
-            }
-            Err(error) => {
-                tracing::error!(
-                    "Failed to run the command to enable the {name} service 
command: {error}"
-                );
-            }
-        }
-
-        Ok(())
+    pub fn enable_services(&self) {
+        enable_service(&self.install_dir, "setup-systemd-proxy-env.service");
+        enable_service(&self.install_dir, "setup-systemd-proxy-env.path");
     }
 }
 
@@ -277,7 +253,7 @@
                 std::fs::create_dir_all(parent)?;
             }
             config.write_to(&path)?;
-            self.enable_services()?;
+            self.enable_services();
         }
         Ok(())
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-server/src/server/web.rs 
new/agama/agama-server/src/server/web.rs
--- old/agama/agama-server/src/server/web.rs    2026-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-server/src/server/web.rs    2026-03-24 16:36:47.000000000 
+0100
@@ -210,12 +210,10 @@
     put,
     path = "/config",
     context_path = "/api/v2",
+    request_body(content = Value, description = "Configuration to apply."),
     responses(
         (status = 200, description = "The configuration was replaced. Other 
operations can be running in background."),
         (status = 400, description = "Not possible to replace the 
configuration.")
-    ),
-    params(
-        ("config" = Value, description = "Configuration to apply.")
     )
 )]
 async fn put_config(State(state): State<ServerState>, Json(json): Json<Value>) 
-> ServerResult<()> {
@@ -232,12 +230,10 @@
     patch,
     path = "/config",
     context_path = "/api/v2",
+    request_body(content = Patch, description = "Changes in the 
configuration."),
     responses(
         (status = 200, description = "The configuration was patched. Other 
operations can be running in background."),
         (status = 400, description = "Not possible to patch the 
configuration.")
-    ),
-    params(
-        ("patch" = Patch, description = "Changes in the configuration.")
     )
 )]
 async fn patch_config(
@@ -410,13 +406,11 @@
     post,
     path = "/action",
     context_path = "/api/v2",
+    request_body(content = Action, description = "Description of the action to 
run."),
     responses(
         (status = 200, description = "Action successfully ran."),
         (status = 400, description = "Not possible to run the action.", body = 
Object),
         (status = 422, description = "Action blocked by backend state", body = 
Object)
-    ),
-    params(
-        ("action" = Action, description = "Description of the action to run."),
     )
 )]
 async fn run_action(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-software/Cargo.toml 
new/agama/agama-software/Cargo.toml
--- old/agama/agama-software/Cargo.toml 2026-03-17 15:08:44.000000000 +0100
+++ new/agama/agama-software/Cargo.toml 2026-03-24 16:36:47.000000000 +0100
@@ -5,7 +5,6 @@
 edition.workspace = true
 
 [dependencies]
-agama-bootloader = { path = "../agama-bootloader" }
 agama-l10n = { path = "../agama-l10n" }
 agama-utils = { path = "../agama-utils" }
 agama-security = { path = "../agama-security" }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-software/src/message.rs 
new/agama/agama-software/src/message.rs
--- old/agama/agama-software/src/message.rs     2026-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-software/src/message.rs     2026-03-24 16:36:47.000000000 
+0100
@@ -133,3 +133,18 @@
 impl Message for SetLocale {
     type Reply = ();
 }
+
+#[derive(Clone)]
+pub struct IsPatternSelected {
+    pub name: String,
+}
+
+impl Message for IsPatternSelected {
+    type Reply = bool;
+}
+
+impl IsPatternSelected {
+    pub fn new(name: String) -> Self {
+        Self { name }
+    }
+}
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-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-software/src/service.rs     2026-03-24 16:36:47.000000000 
+0100
@@ -26,7 +26,6 @@
     zypp_server::{self, SoftwareAction, ZyppServer},
     Model, ResolvableType,
 };
-use agama_bootloader;
 use agama_security as security;
 use agama_utils::{
     actor::{self, Actor, Handler, MessageHandler},
@@ -92,7 +91,6 @@
     progress: Handler<progress::Service>,
     questions: Handler<question::Service>,
     security: Handler<security::Service>,
-    bootloader: Handler<agama_bootloader::Service>,
 }
 
 impl Starter {
@@ -102,7 +100,6 @@
         progress: Handler<progress::Service>,
         questions: Handler<question::Service>,
         security: Handler<security::Service>,
-        bootloader: Handler<agama_bootloader::Service>,
     ) -> Self {
         Self {
             model: None,
@@ -111,7 +108,6 @@
             progress,
             questions,
             security,
-            bootloader,
         }
     }
 
@@ -158,7 +154,6 @@
             progress: self.progress,
             product: None,
             kernel_cmdline,
-            bootloader: self.bootloader,
         };
         service.setup().await?;
         Ok(actor::spawn(service))
@@ -181,7 +176,6 @@
     product: Option<Arc<RwLock<ProductSpec>>>,
     selection: SoftwareSelection,
     kernel_cmdline: KernelCmdline,
-    bootloader: Handler<agama_bootloader::Service>,
 }
 
 #[derive(Default)]
@@ -199,9 +193,8 @@
         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, bootloader)
+        Starter::new(events, issues, progress, questions, security)
     }
 
     pub async fn setup(&mut self) -> Result<(), Error> {
@@ -217,26 +210,6 @@
         Ok(())
     }
 
-    fn update_selinux(&self, state: &SoftwareState) {
-        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:
@@ -258,8 +231,6 @@
             SoftwareState::build_from(&product, &state.config, &state.system, 
&self.selection)
         };
 
-        self.update_selinux(&new_state);
-
         tracing::info!("Wanted software state: {new_state:?}");
         {
             let mut state = self.state.write().await;
@@ -513,6 +484,26 @@
     }
 }
 
+#[async_trait]
+impl MessageHandler<message::IsPatternSelected> for Service {
+    async fn handle(&mut self, message: message::IsPatternSelected) -> 
Result<bool, Error> {
+        let state = self.state.read().await;
+        let Some(software_state) = &state.state else {
+            return Ok(false);
+        };
+
+        let selected = software_state
+            .resolvables
+            .to_vec()
+            .iter()
+            .any(|(name, typ, state)| {
+                typ == &ResolvableType::Pattern && name == &message.name && 
state.selected()
+            });
+
+        Ok(selected)
+    }
+}
+
 const LIVE_REPO_DIR: &str = "run/initramfs/live/install";
 const DUD_REPO_DIR: &str = "var/lib/agama/dud/repo";
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-software/src/test_utils.rs 
new/agama/agama-software/src/test_utils.rs
--- old/agama/agama-software/src/test_utils.rs  2026-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-software/src/test_utils.rs  2026-03-24 16:36:47.000000000 
+0100
@@ -27,7 +27,7 @@
     },
     issue,
     products::ProductSpec,
-    progress, question, test,
+    progress, question,
 };
 use async_trait::async_trait;
 
@@ -90,9 +90,7 @@
     questions: Handler<question::Service>,
 ) -> Handler<Service> {
     let security = start_security_service(questions.clone()).await;
-    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)
+    Service::starter(events, issues, progress, questions, security)
         .with_model(TestModel {})
         .start()
         .await
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/agama-utils/src/command.rs 
new/agama/agama-utils/src/command.rs
--- old/agama/agama-utils/src/command.rs        2026-03-17 15:08:44.000000000 
+0100
+++ new/agama/agama-utils/src/command.rs        2026-03-24 16:36:47.000000000 
+0100
@@ -77,3 +77,26 @@
         .open(path)?;
     Ok(file)
 }
+
+/// Convenience function to enable a service
+pub fn enable_service<P: AsRef<Path>>(root_dir: P, name: &str) {
+    let mut command = std::process::Command::new("chroot");
+    command
+        .arg(root_dir.as_ref())
+        .args(["systemctl", "enable", name]);
+
+    match command.output() {
+        Ok(output) => {
+            if output.status.success() {
+                tracing::info!("Enabled the {name} service");
+            } else {
+                tracing::error!("Failed to enable the {name} service: 
{output:?}")
+            }
+        }
+        Err(error) => {
+            tracing::error!(
+                "Failed to run the command to enable the {name} service 
command: {error}"
+            );
+        }
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/install.sh new/agama/install.sh
--- old/agama/install.sh        2026-03-17 15:08:44.000000000 +0100
+++ new/agama/install.sh        2026-03-24 16:36:47.000000000 +0100
@@ -39,6 +39,8 @@
 install -D -t "${DESTDIR}${bindir}" 
"${SRCDIR}/target/${RUST_TARGET}/agama-proxy-setup"
 install -D -t "${DESTDIR}${bindir}" 
"${SRCDIR}/target/${RUST_TARGET}/agama-web-server"
 install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-web-server.sh"
+install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-journal"
+install -D -t "${DESTDIR}${bindir}" "${SRCDIR}/share/agama-zypp-journal"
 
 install6 -D -p "${SRCDIR}"/share/agama.pam "${DESTDIR}${pamvendordir}"/agama
 
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-03-17 15:08:44.000000000 +0100
+++ new/agama/package/agama.changes     2026-03-24 16:36:47.000000000 +0100
@@ -1,4 +1,56 @@
 -------------------------------------------------------------------
+Tue Mar 24 13:01:38 UTC 2026 - Ladislav Slezák <[email protected]>
+
+- Include more details when saving the journal log
+- Save separate libzypp.out.log file in the old y2log format
+  for libzypp debugging
+- Store the complete journal log in JSON format to include hidden
+  fields and more details
+- gh#agama-project/agama#3309
+
+-------------------------------------------------------------------
+Fri Mar 20 16:56:53 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Enable agama-scripts service if needed (bsc#1259899).
+
+-------------------------------------------------------------------
+Fri Mar 20 13:55:22 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Enable SELinux if needed in unattended installations (bsc#1259890).
+
+-------------------------------------------------------------------
+Fri Mar 20 08:46:19 UTC 2026 - Knut Anderssen <[email protected]>
+
+- Return error code when using "agama config generate",
+  "agama config load" or "agama config validate" does not validate
+  the given profile (bsc#1256951, gh#agama-project/agama#3304).
+
+-------------------------------------------------------------------
+Fri Mar 20 07:54:46 UTC 2026 - Knut Anderssen <[email protected]>
+
+- Fix incorrect openAPI parameters (gh#agama-project/agama#3299).
+  - Do not define params for POST '/api/v2/action'
+  - Do not define params for PUT and PATCH '/api/v2/config
+
+-------------------------------------------------------------------
+Thu Mar 19 21:11:29 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Continue loading the configuration even if there is a network setup
+  error (gh#agama-project/agama#3306).
+
+-------------------------------------------------------------------
+Wed Mar 18 22:29:10 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
+
+- Report problems when writing files and scripts (gh#agama-project/agama#3301).
+
+-------------------------------------------------------------------
+Tue Mar 17 14:49:14 UTC 2026 - Michal Filka <[email protected]>
+
+- jsc#PED-15434
+  - fixed autoyast_compat.json to cover earlier patch which
+    extended capabilities of sshPublicKey / sshPublicKeys
+
+-------------------------------------------------------------------
 Tue Mar 17 12:15:22 UTC 2026 - Imobach Gonzalez Sosa <[email protected]>
 
 - Version 19
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/package/agama.spec new/agama/package/agama.spec
--- old/agama/package/agama.spec        2026-03-17 15:08:44.000000000 +0100
+++ new/agama/package/agama.spec        2026-03-24 16:36:47.000000000 +0100
@@ -246,6 +246,8 @@
 %license LICENSE
 %{_bindir}/agama-web-server
 %{_bindir}/agama-web-server.sh
+%{_bindir}/agama-journal
+%{_bindir}/agama-zypp-journal
 %{_bindir}/agama-proxy-setup
 %{_pam_vendordir}/agama
 %{_unitdir}/agama-web-server.service
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/share/agama-journal 
new/agama/share/agama-journal
--- old/agama/share/agama-journal       1970-01-01 01:00:00.000000000 +0100
+++ new/agama/share/agama-journal       2026-03-24 16:36:47.000000000 +0100
@@ -0,0 +1,131 @@
+#!/bin/bash
+#
+# Copyright (c) [2026] 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.
+
+# Helper script which prints the messages from the systemd journal with
+# additional details like code location.
+
+# All script arguments are passed to journalctl, you can use it to filter the
+# displayed messages, e.g. filter by unit with '-u sshd.service', filter by 
time
+# with '--since "5 minutes ago"' or show only errors and higher priority with
+# '-p err'..
+
+# The timestamps are printed in the local time, to print the UTC time
+# run with TZ=UTC. Hint: You can set any time zone, e.g. TZ=America/New_York.
+
+# It accepts special "--input-file <file>" parameter to load the data from
+# previously saved journal in JSON format.
+
+
+# Use colors if the output goes to a terminal
+if [ -t 1 ]; then
+  USE_COLORS="true"
+else
+  USE_COLORS="false"
+fi
+
+SHOW_MICROSECONDS="false"
+INPUT_FILE=""
+JOURNAL_ARGS=()
+
+while [ $# -gt 0 ]; do
+  case "$1" in
+    # Allow using a JSON file as input
+    --input-file)
+      INPUT_FILE="$2"
+      shift 2
+      ;;
+    --microseconds)
+      SHOW_MICROSECONDS="true"
+      shift
+      ;;
+    *)
+      JOURNAL_ARGS+=("$1")
+      shift
+      ;;
+  esac
+done
+
+process_logs() {
+  if [ -n "$INPUT_FILE" ]; then
+    cat "$INPUT_FILE"
+  else
+    journalctl -o json "$@"
+  fi | jq --unbuffered --arg use_colors "$USE_COLORS" --arg show_microseconds 
"$SHOW_MICROSECONDS" -r '
+  # Helper function to get color based on priority
+  def get_color:
+    {
+      "0": "\u001b[1;91m", # Emergency - Bold Bright Red
+      "1": "\u001b[1;91m", # Alert - Bold Bright Red
+      "2": "\u001b[1;91m", # Critical - Bold Bright Red
+      "3": "\u001b[1;31m", # Error - Bold Red
+      "4": "\u001b[93m", # Warning - Bright Yellow
+      "5": "\u001b[1m",  # Notice - Bold white
+      "6": "",           # Informational - Default
+      "7": "\u001b[90m"  # Debug - Grey
+    }[tostring] // "";
+
+  # Helper function to format timestamp
+  def format_timestamp:
+    # The journal time is in microseconds
+    (tonumber / 1000000 | strflocaltime("%b %e %H:%M:%S"))
+      + (if $show_microseconds == "true" then "." + (tostring | .[-6:]) else 
"" end);
+
+  # Helper function to build the source code location string
+  def format_location:
+    [
+      (.CODE_FILE | select(. != null and . != "") | tostring),
+      (.CODE_FUNC | select(. != null and . != "") | "(" + tostring + ")"),
+      (.CODE_LINE | select(. != null and . != "") | ":" + tostring + ":")
+    ]
+    | join("")
+    | select(. != "");
+
+  # Helper function to combine syslog identifier and PID
+  def format_syslog_pid:
+    [
+      (.SYSLOG_IDENTIFIER | select(. != null and . != "")),
+      (._PID | select(. != null and . != "") | "[" + tostring + "]:")
+    ]
+    | join("")
+    | select(. != "");
+
+  # Join the requested fields into a single line
+  (.PRIORITY | get_color) as $color |
+  [
+    (.__REALTIME_TIMESTAMP | select(. != null and . != "") | format_timestamp),
+    (.PRIORITY | select(. != null and . != "") | "<" + tostring + ">"),
+    format_syslog_pid,
+    format_location,
+    (.ZYPP_GROUP | select(. != null and . != "") | "[" + tostring + "]"),
+    (.MESSAGE | select(. != null and . != "") | tostring | if $use_colors == 
"true" and $color != "" then $color + . + "\u001b[0m" else . end)
+  ]
+  # Remove any missing/empty values and join them with a space
+  | map(select(. != null and . != ""))
+  | join(" ")
+'
+}
+
+if [ "$USE_COLORS" = "true" ] && [ -n "$PAGER" ]; then
+  process_logs "${JOURNAL_ARGS[@]}" | $PAGER
+else
+  process_logs "${JOURNAL_ARGS[@]}"
+fi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/share/agama-zypp-journal 
new/agama/share/agama-zypp-journal
--- old/agama/share/agama-zypp-journal  1970-01-01 01:00:00.000000000 +0100
+++ new/agama/share/agama-zypp-journal  2026-03-24 16:36:47.000000000 +0100
@@ -0,0 +1,119 @@
+#!/bin/bash
+#
+# Copyright (c) [2026] 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.
+
+# Helper script which extracts the libzypp messages from the systemd journal
+# and prints them in the YaST y2log compatible format.
+
+# All script arguments are passed to journalctl, you can use it to filter the
+# displayed messages, e.g. by date with '--since "5 minutes ago"' or show only
+# errors and higher priority with '-p err'.
+
+# It accepts special "--input-file <file>" parameter to load the data from
+# previously saved journal in JSON format.
+
+
+# Use colors if the output goes to a terminal
+if [ -t 1 ]; then
+  USE_COLORS="true"
+else
+  USE_COLORS="false"
+fi
+
+INPUT_FILE=""
+JOURNAL_ARGS=()
+
+while [ $# -gt 0 ]; do
+  case "$1" in
+    # Allow using a JSON file as input
+    --input-file)
+      INPUT_FILE="$2"
+      shift 2
+      ;;
+    *)
+      JOURNAL_ARGS+=("$1")
+      shift
+      ;;
+  esac
+done
+
+process_logs() {
+  if [ -n "$INPUT_FILE" ]; then
+    cat "$INPUT_FILE"
+  else
+    journalctl -u agama-web-server.service -o json "$@"
+  fi | jq --unbuffered --arg use_colors "$USE_COLORS" -r '
+  # Helper function to get color based on priority
+  def get_color:
+    {
+      "5": "\u001b[1;91m", # Internal error - Bold Bright Red
+      "4": "\u001b[1;95m", # Security error - Bold Bright Magenta
+      "3": "\u001b[1;31m", # Error - Bold Red
+      "2": "\u001b[93m",   # Warning - Bright Yellow
+      "1": "",             # Milestone - Default
+      "0": "\u001b[90m"    # Debug - Grey
+    }[tostring] // "\u001b[35m"; # Missing or unknown - Magenta
+
+  # Helper function to format timestamp (uses UTC)
+  def format_timestamp:
+    # The journal time is in microseconds
+    (tonumber / 1000000 | strflocaltime("%Y-%m-%d %H:%M:%S"));
+
+  # Helper function to build the source code location string
+  def format_location:
+    [
+      (.CODE_FILE | select(. != null and . != "") | tostring),
+      (.CODE_FUNC | select(. != null and . != "") | "(" + tostring + ")"),
+      (.CODE_LINE | select(. != null and . != "") | ":" + tostring)
+    ]
+    | join("");
+
+  # Helper function to combine hostname and PID
+  def format_syslog_pid:
+    [
+      ._HOSTNAME,
+      (._PID | select(. != null and . != "") | "(" + tostring + ")")
+    ]
+    | join("")
+    | select(. != "");
+
+  select(.COMPONENT == "libzypp" or .COMPONENT == "zypp-agama-sys") |
+  # Join the requested fields into a single line
+  (.ZYPP_LEVEL | get_color) as $color |
+  [
+    (.__REALTIME_TIMESTAMP | select(. != null and . != "") | format_timestamp),
+    (((.ZYPP_LEVEL | select(. != null and . != "") | tostring) // "1") | "<" + 
tostring + ">"),
+    format_syslog_pid,
+    "[" + ((.ZYPP_GROUP | select(. != null and . != "") | tostring) // 
.COMPONENT) + "]",
+    format_location,
+    (.MESSAGE | select(. != null and . != "") | tostring | if $use_colors == 
"true" and $color != "" then $color + . + "\u001b[0m" else . end)
+  ]
+  # Remove any missing/empty values and join them with a space
+  | map(select(. != null and . != ""))
+  | join(" ")
+'
+}
+
+if [ "$USE_COLORS" = "true" ] && [ -n "$PAGER" ]; then
+  process_logs "${JOURNAL_ARGS[@]}" | $PAGER
+else
+  process_logs "${JOURNAL_ARGS[@]}"
+fi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/agama/zypp-agama/zypp-agama-sys/c-layer/lib.cxx 
new/agama/zypp-agama/zypp-agama-sys/c-layer/lib.cxx
--- old/agama/zypp-agama/zypp-agama-sys/c-layer/lib.cxx 2026-03-17 
15:08:44.000000000 +0100
+++ new/agama/zypp-agama/zypp-agama-sys/c-layer/lib.cxx 2026-03-24 
16:36:47.000000000 +0100
@@ -33,6 +33,30 @@
 
 #include <systemd/sd-journal.h>
 
+// helper macro for logging the code location from where libzypp is called
+#define LOG_LOCATION(message)                                                  
\
+  do {                                                                         
\
+    std::string line("CODE_LINE=");                                            
\
+    line.append(std::to_string(__LINE__));                                     
\
+    sd_journal_send_with_location("CODE_FILE=" __FILE__, line.c_str(),         
\
+                                  __func__, "PRIORITY=%i", LOG_NOTICE,         
\
+                                  "MESSAGE=%s", (message),                     
\
+                                  "COMPONENT=zypp-agama-sys", NULL);           
\
+  } while (0)
+
+// helper function to hide the password in URL
+std::string filter_url(const char *url) {
+  std::string result(url);
+  try {
+    zypp::Url z_url(url);
+    // asString() by default does not include the password
+    result = z_url.asString();
+  } catch (const zypp::Exception &excpt) {
+    // if the URL is invalid, just return the original string
+  }
+  return result;
+}
+
 struct Zypp {
   zypp::ZYpp::Ptr zypp_pointer;
   zypp::RepoManager *repo_manager;
@@ -100,7 +124,7 @@
     // see "man sd_journal_send_with_location"
     sd_journal_send_with_location(
         file.c_str(), line.c_str(), zypp_func, "PRIORITY=%i", level,
-        "MESSAGE=[%s] %s", zypp_group.c_str(), zypp_message.c_str(),
+        "MESSAGE=%s", zypp_message.c_str(),
         // some custom data to allow easy filtering of the libzypp messages
         "COMPONENT=libzypp", "ZYPP_GROUP=%s", zypp_group.c_str(),
         "ZYPP_LEVEL=%i", zypp_level, NULL);
@@ -119,6 +143,7 @@
 };
 
 void free_zypp(struct Zypp *zypp) noexcept {
+  LOG_LOCATION("Dropping libzypp");
   // ensure that target is unloaded otherwise nasty things can happen if new
   // zypp is created in different thread
   zypp->zypp_pointer->getTarget()->unload();
@@ -129,6 +154,7 @@
 }
 
 static zypp::ZYpp::Ptr zypp_ptr() {
+  LOG_LOCATION("Initializing libzypp");
   sd_journal_print(LOG_NOTICE, "Redirecting libzypp logs to systemd journal");
 
   // log to systemd journal using our specific formatter
@@ -146,6 +172,10 @@
 
 void switch_target(struct Zypp *zypp, const char *root,
                    struct Status *status) noexcept {
+  std::string message("Switching target to: ");
+  message.append(root);
+  LOG_LOCATION(message.c_str());
+
   const std::string root_str(root);
   try {
     zypp->zypp_pointer->initializeTarget(root_str,
@@ -179,6 +209,8 @@
             struct DownloadResolvableCallbacks *download_callbacks,
             struct SecurityCallbacks *security_callbacks,
             struct InstallCallbacks *install_callbacks) noexcept {
+  LOG_LOCATION("Starting package installation");
+
   try {
     set_zypp_resolvable_download_callbacks(download_callbacks);
     set_zypp_security_callbacks(security_callbacks);
@@ -205,6 +237,10 @@
 // target and merge it in rust
 struct Zypp *init_target(const char *root, struct Status *status,
                          ProgressCallback progress, void *user_data) noexcept {
+  std::string message("Initializing target: ");
+  message.append(root);
+  LOG_LOCATION(message.c_str());
+
   if (the_zypp.zypp_pointer != NULL) {
     STATUS_ERROR(status, "Cannot have two init_target concurrently, "
                          "libzypp not ready for this. Call free_zypp first.");
@@ -314,6 +350,10 @@
 void resolvable_select(struct Zypp *_zypp, const char *name,
                        enum RESOLVABLE_KIND kind, enum RESOLVABLE_SELECTED who,
                        struct Status *status) noexcept {
+  std::string message("Selecting resolvable: ");
+  message.append(name);
+  LOG_LOCATION(message.c_str());
+
   if (who == RESOLVABLE_SELECTED::NOT_SELECTED) {
     STATUS_OK(status);
     return;
@@ -336,6 +376,10 @@
                          enum RESOLVABLE_KIND kind,
                          enum RESOLVABLE_SELECTED who,
                          struct Status *status) noexcept {
+  std::string message("Unselecting resolvable: ");
+  message.append(name);
+  LOG_LOCATION(message.c_str());
+
   STATUS_OK(status);
   if (who == RESOLVABLE_SELECTED::NOT_SELECTED) {
     return;
@@ -354,7 +398,8 @@
 }
 
 void resolvable_reset_all(struct Zypp *_zypp) noexcept {
-  MIL << "Resetting status of all resolvables" << std::endl;
+  LOG_LOCATION("Resetting status of all resolvables");
+
   for (auto &item : zypp::ResPool::instance())
     item.statusReset();
 }
@@ -384,6 +429,8 @@
 
 struct Patterns get_patterns(struct Zypp *zypp,
                              struct Status *status) noexcept {
+  LOG_LOCATION("Getting patterns");
+
   auto iterator =
       zypp->zypp_pointer->pool().proxy().byKind(zypp::ResKind::pattern);
 
@@ -426,6 +473,7 @@
 
 struct Products get_products(struct Zypp *zypp,
                              struct Status *status) noexcept {
+  LOG_LOCATION("Getting products");
   auto iterator =
       zypp->zypp_pointer->pool().proxy().byKind(zypp::ResKind::product);
 
@@ -460,6 +508,7 @@
 
 bool run_solver(struct Zypp *zypp, bool only_required,
                 struct Status *status) noexcept {
+  LOG_LOCATION("Running solver");
   try {
     STATUS_OK(status);
     auto resolver = zypp->zypp_pointer->resolver();
@@ -482,6 +531,13 @@
 
 void add_service(struct Zypp *zypp, const char *alias, const char *url,
                  struct Status *status) noexcept {
+  std::string message("Adding service: ");
+  message.append(alias);
+  message.append(" (");
+  message.append(filter_url(url));
+  message.append(")");
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -498,11 +554,19 @@
 }
 
 bool create_solver_testcase(struct Zypp *zypp, const char *dir) noexcept {
+  std::string message("Creating solver testcase in directory ");
+  message.append(dir);
+  LOG_LOCATION(message.c_str());
+
   return zypp->zypp_pointer->resolver()->createSolverTestcase(dir);
 }
 
 void refresh_service(struct Zypp *zypp, const char *alias,
                      struct Status *status) noexcept {
+  std::string message("Refreshing service: ");
+  message.append(alias);
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -526,6 +590,10 @@
                         struct Status *status,
                         struct DownloadProgressCallbacks *callbacks,
                         struct SecurityCallbacks *security_callbacks) noexcept 
{
+  std::string message("Refreshing repository: ");
+  message.append(alias);
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -566,6 +634,7 @@
 }
 
 unsigned packages_to_install(struct Zypp *zypp) noexcept {
+  LOG_LOCATION("Getting number of packages to install");
   return zypp::ResPool::instance()
       .byStatus(&zypp::ResStatus::isToBeInstalled)
       .size();
@@ -573,6 +642,14 @@
 
 static bool package_check(Zypp *zypp, const char *tag, bool selected,
                           Status *status) noexcept {
+  std::string message("Checking package: ");
+  message.append(tag);
+  if (selected)
+    message.append(" (selected)");
+  else
+    message.append(" (available)");
+  LOG_LOCATION(message.c_str());
+
   try {
     std::string s_tag(tag);
     if (s_tag.empty()) {
@@ -615,6 +692,13 @@
 void add_repository(struct Zypp *zypp, const char *alias, const char *url,
                     struct Status *status, ZyppProgressCallback callback,
                     void *user_data) noexcept {
+  std::string message("Adding repository: ");
+  message.append(alias);
+  message.append(" (");
+  message.append(filter_url(url));
+  message.append(")");
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -634,6 +718,10 @@
 
 void disable_repository(struct Zypp *zypp, const char *alias,
                         struct Status *status) noexcept {
+  std::string message("Disabling repository: ");
+  message.append(alias);
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -650,6 +738,14 @@
 
 void set_repository_url(struct Zypp *zypp, const char *alias, const char *url,
                         struct Status *status) noexcept {
+
+  std::string message("Setting repository url: ");
+  message.append(alias);
+  message.append(" (");
+  message.append(filter_url(url));
+  message.append(")");
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -668,6 +764,10 @@
 void remove_repository(struct Zypp *zypp, const char *alias,
                        struct Status *status, ZyppProgressCallback callback,
                        void *user_data) noexcept {
+  std::string message("Removing repository: ");
+  message.append(alias);
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -687,6 +787,8 @@
 
 struct RepositoryList list_repositories(struct Zypp *zypp,
                                         struct Status *status) noexcept {
+  LOG_LOCATION("Listing repositories");
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return {0, NULL};
@@ -715,6 +817,10 @@
 
 void load_repository_cache(struct Zypp *zypp, const char *alias,
                            struct Status *status) noexcept {
+  std::string message("Loading repository cache: ");
+  message.append(alias);
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
   }
@@ -739,6 +845,10 @@
                             struct Status *status,
                             ZyppProgressCallback callback,
                             void *user_data) noexcept {
+  std::string message("Building repository cache: ");
+  message.append(alias);
+  LOG_LOCATION(message.c_str());
+
   if (zypp->repo_manager == NULL) {
     STATUS_ERROR(status, "Internal Error: Repo manager is not initialized.");
     return;
@@ -762,6 +872,10 @@
 
 void import_gpg_key(struct Zypp *zypp, const char *const pathname,
                     struct Status *status) noexcept {
+  std::string message("Importing GPG key: ");
+  message.append(pathname);
+  LOG_LOCATION(message.c_str());
+
   try {
     zypp::filesystem::Pathname path(pathname);
     zypp::PublicKey key(path);
@@ -779,6 +893,8 @@
 void get_space_usage(struct Zypp *zypp, struct Status *status,
                      struct MountPoint *mount_points,
                      unsigned mount_points_size) noexcept {
+  LOG_LOCATION("Getting disk space usage");
+
   try {
     zypp::DiskUsageCounter::MountPointSet mount_points_set;
     for (unsigned i = 0; i < mount_points_size; ++i) {

++++++ agama.obsinfo ++++++
--- /var/tmp/diff_new_pack.OZ1gw2/_old  2026-04-01 19:55:34.421445361 +0200
+++ /var/tmp/diff_new_pack.OZ1gw2/_new  2026-04-01 19:55:34.437446021 +0200
@@ -1,5 +1,5 @@
 name: agama
-version: 19+0.e4e1132bc
-mtime: 1773756524
-commit: e4e1132bc3d19d48ebf6e423d089cce0e78ecf3e
+version: 19+70.3f126ff03
+mtime: 1774366607
+commit: 3f126ff037d4587f7c4a1aff0042f0ac2f0c1f21
 

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

Reply via email to