import CS dnf-4.14.0-25.el9

This commit is contained in:
eabdullin 2025-03-11 07:09:46 +00:00
parent cc2d726add
commit 82265e91f1
19 changed files with 1667 additions and 2 deletions

View File

@ -0,0 +1,99 @@
From 6af6633b3e1e92405ec63ef4d3d697891213f8cb Mon Sep 17 00:00:00 2001
From: David Cantrell <dcantrell@redhat.com>
Date: Thu, 15 Feb 2024 14:03:32 -0500
Subject: [PATCH 1/4] Add detection for ostree-based systems and warn users
about losing changes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 5c050ba2324c5fb95bf0e0501c7925f38f6a09dc
On ostree-based systems, users can use dnf to customize the
environment but those changes will be lost at the next ostree-based
image update. If you want to retain changes between ostree-updates
you need to make use of rpm-ostree right now.
Signed-off-by: David Cantrell <dcantrell@redhat.com>
Resolves: https://issues.redhat.com/browse/RHEL-49670
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 9 +++++++++
dnf/util.py | 31 +++++++++++++++++++++++++++++++
2 files changed, 40 insertions(+)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 0c4f4c6ad..1fd0e96c3 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -214,6 +214,15 @@ class BaseCli(dnf.Base):
elif 'test' in self.conf.tsflags:
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
+ if dnf.util.is_container():
+ _container_msg = _("""
+*** This system is managed with ostree. Changes to the system
+*** made with dnf will be lost with the next ostree-based update.
+*** If you do not want to lose these changes, use 'rpm-ostree'.
+""")
+ logger.info(_container_msg)
+ raise CliError(_("Operation aborted."))
+
if self._promptWanted():
if self.conf.assumeno or not self.output.userconfirm():
raise CliError(_("Operation aborted."))
diff --git a/dnf/util.py b/dnf/util.py
index 16c5bc89c..9909f8fea 100644
--- a/dnf/util.py
+++ b/dnf/util.py
@@ -33,11 +33,13 @@ import errno
import functools
import hawkey
import itertools
+import json
import locale
import logging
import os
import pwd
import shutil
+import subprocess
import sys
import tempfile
import time
@@ -631,3 +633,32 @@ def _post_transaction_output(base, transaction, action_callback):
def _name_unset_wrapper(input_name):
# returns <name-unset> for everything that evaluates to False (None, empty..)
return input_name if input_name else _("<name-unset>")
+
+
+def is_container():
+ """Returns true is the system is managed as an immutable container,
+ false otherwise. If msg is True, a warning message is displayed
+ for the user.
+ """
+
+ bootc = '/usr/bin/bootc'
+ ostree = '/sysroot/ostree'
+
+ if os.path.isfile(bootc) and os.access(bootc, os.X_OK):
+ p = subprocess.Popen([bootc, "status", "--json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (out, err) = p.communicate()
+
+ if p.returncode == 0:
+ # check the output of 'bootc status'
+ j = json.loads(out)
+
+ # XXX: the API from bootc status is evolving
+ status = j.get("status", "")
+ kind = j.get("kind", "")
+
+ if kind.lower() == "bootchost" and bool(status.get("isContainer", None)):
+ return True
+ elif os.path.isdir(ostree):
+ return True
+
+ return False
\ No newline at end of file
--
2.46.2

View File

@ -0,0 +1,107 @@
From 6157248a5035a7752115143d8d29773384c1db3f Mon Sep 17 00:00:00 2001
From: Joseph Marrero <jmarrero@redhat.com>
Date: Tue, 16 Jul 2024 15:48:41 -0400
Subject: [PATCH 2/4] Update ostree/bootc host system check.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 6120fe52511775b60b6031d4169988c025610ab5
This changes the is_container() func for _is_bootc_host()
and updates the logic and message. This should detect on
all ostree and bootc hosts to date that are not using
bootc usroverlay or ostree admin unlock for development
purposes.
Resolves: https://issues.redhat.com/browse/RHEL-49670
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 11 +++++------
dnf/util.py | 33 ++++++++-------------------------
2 files changed, 13 insertions(+), 31 deletions(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 1fd0e96c3..8521dd351 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -214,13 +214,12 @@ class BaseCli(dnf.Base):
elif 'test' in self.conf.tsflags:
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
- if dnf.util.is_container():
- _container_msg = _("""
-*** This system is managed with ostree. Changes to the system
-*** made with dnf will be lost with the next ostree-based update.
-*** If you do not want to lose these changes, use 'rpm-ostree'.
+ if dnf.util._is_bootc_host():
+ _bootc_host_msg = _("""
+*** Error: system is configured to be read-only; for more
+*** information run `bootc status` or `ostree admin status`.
""")
- logger.info(_container_msg)
+ logger.info(_bootc_host_msg)
raise CliError(_("Operation aborted."))
if self._promptWanted():
diff --git a/dnf/util.py b/dnf/util.py
index 9909f8fea..e8f587a03 100644
--- a/dnf/util.py
+++ b/dnf/util.py
@@ -33,13 +33,11 @@ import errno
import functools
import hawkey
import itertools
-import json
import locale
import logging
import os
import pwd
import shutil
-import subprocess
import sys
import tempfile
import time
@@ -635,30 +633,15 @@ def _name_unset_wrapper(input_name):
return input_name if input_name else _("<name-unset>")
-def is_container():
+def _is_bootc_host():
"""Returns true is the system is managed as an immutable container,
false otherwise. If msg is True, a warning message is displayed
for the user.
"""
-
- bootc = '/usr/bin/bootc'
- ostree = '/sysroot/ostree'
-
- if os.path.isfile(bootc) and os.access(bootc, os.X_OK):
- p = subprocess.Popen([bootc, "status", "--json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (out, err) = p.communicate()
-
- if p.returncode == 0:
- # check the output of 'bootc status'
- j = json.loads(out)
-
- # XXX: the API from bootc status is evolving
- status = j.get("status", "")
- kind = j.get("kind", "")
-
- if kind.lower() == "bootchost" and bool(status.get("isContainer", None)):
- return True
- elif os.path.isdir(ostree):
- return True
-
- return False
\ No newline at end of file
+ ostree_booted = '/run/ostree-booted'
+ usr = '/usr/'
+ # Check if usr is writtable and we are in a running ostree system.
+ # We want this code to return true only when the system is in locked state. If someone ran
+ # bootc overlay or ostree admin unlock we would want normal DNF path to be ran as it will be
+ # temporary changes (until reboot).
+ return os.path.isfile(ostree_booted) and not os.access(usr, os.W_OK)
--
2.46.2

View File

@ -0,0 +1,32 @@
From 0bab6b9620f9872271c9458212538eb03b8b085c Mon Sep 17 00:00:00 2001
From: Joseph Marrero <jmarrero@redhat.com>
Date: Mon, 22 Jul 2024 15:33:32 -0400
Subject: [PATCH 3/4] Update bootc hosts message to point to bootc --help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: e2535589ce16bc36b96b37369502a3c312f6056a
Resolves: https://issues.redhat.com/browse/RHEL-49670
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 8521dd351..99af9069b 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -217,7 +217,7 @@ class BaseCli(dnf.Base):
if dnf.util._is_bootc_host():
_bootc_host_msg = _("""
*** Error: system is configured to be read-only; for more
-*** information run `bootc status` or `ostree admin status`.
+*** information run `bootc --help`.
""")
logger.info(_bootc_host_msg)
raise CliError(_("Operation aborted."))
--
2.46.2

View File

@ -0,0 +1,47 @@
From ca4c52214b16fe298c44939cd7aaccd9b7549869 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Thu, 15 Aug 2024 14:04:55 +0200
Subject: [PATCH 4/4] Allow --installroot on read-only bootc system
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: a1aa8d0e048751859a2bec1b2fb12fcca93c6e83
Some people use --installroot on a read-only bootc system to install
a system into a chroot subtree. However, current bootc check did not
take into account --installroot and rejected the operation.
This patch augments the check for the installroot being different
from /.
It's pointless to check for installroot writability here because
installroot is written before this check when updating the
repositories and computing a transaction. Moving this check sooner
would not help because some directories (/opt, /) are kept read-only
even on writable bootc.
Resolves: #2108
Resolves: https://issues.redhat.com/browse/RHEL-49670
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 99af9069b..36cfa74b5 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -214,7 +214,8 @@ class BaseCli(dnf.Base):
elif 'test' in self.conf.tsflags:
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
- if dnf.util._is_bootc_host():
+ if dnf.util._is_bootc_host() and \
+ os.path.realpath(self.conf.installroot) == "/":
_bootc_host_msg = _("""
*** Error: system is configured to be read-only; for more
*** information run `bootc --help`.
--
2.46.2

View File

@ -0,0 +1,33 @@
From ea2d17cc484c7c49686145f4b2e98e4b73b9c967 Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Mon, 13 Mar 2023 14:50:41 -0400
Subject: [PATCH] smtplib: catch OSError, not SMTPException
Some, but not all, types of connection error are caught by smtplib and
reraised as an smtplib.SMTPException. Notably, TimeoutError,
socket.gaierror (name resolution failure), and ConnectionRefusedError
and are not caught.
The more generic OSError should be caught here instead.
Resolves #1905
---
dnf/automatic/emitter.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dnf/automatic/emitter.py b/dnf/automatic/emitter.py
index 4aea4b02..648f1a1d 100644
--- a/dnf/automatic/emitter.py
+++ b/dnf/automatic/emitter.py
@@ -106,7 +106,7 @@ class EmailEmitter(Emitter):
smtp = smtplib.SMTP(self._conf.email_host, timeout=300)
smtp.sendmail(email_from, email_to, message.as_string())
smtp.close()
- except smtplib.SMTPException as exc:
+ except OSError as exc:
msg = _("Failed to send an email via '%s': %s") % (
self._conf.email_host, exc)
logger.error(msg)
--
2.46.1

View File

@ -0,0 +1,40 @@
From fdeb208b7b6522bd458142867683b1bf68cd355d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Thu, 10 Oct 2024 10:57:48 +0200
Subject: [PATCH] Allow --downloadonly on read-only bootc system
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 8d888d26e6da27ba37243d7504eb42472f389bde
"dnf install --downloadonly" failed on read-only bootc system despite
not running the transaction. The downloaded packages are stored under
writable /var or to a directory explicitly choosen by a user.
This patch suppresses the bootc read-only bailout if --downloadonly
option is used.
https://issues.redhat.com/browse/RHEL-61745
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 36cfa74b5..3dc08d616 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -215,7 +215,8 @@ class BaseCli(dnf.Base):
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
if dnf.util._is_bootc_host() and \
- os.path.realpath(self.conf.installroot) == "/":
+ os.path.realpath(self.conf.installroot) == "/" and \
+ not self.conf.downloadonly:
_bootc_host_msg = _("""
*** Error: system is configured to be read-only; for more
*** information run `bootc --help`.
--
2.47.0

View File

@ -0,0 +1,81 @@
From d8bd8174426a2d053b20acc7c2bcd83a572fc1d5 Mon Sep 17 00:00:00 2001
From: Marek Blaha <mblaha@redhat.com>
Date: Thu, 17 Oct 2024 13:30:21 +0200
Subject: [PATCH] automatic: Check availability of config file
Upstream commit: 13ecc3921fb1566c2af7b80d5cb515d8fc4e5ec9
RHEL issue: https://issues.redhat.com/browse/RHEL-49743
If a configuration file is explicitly specified on the command line,
ensure that it exists and is readable. If the file is not found, notify
the user immediately and terminate the process.
This resolves issues where users may run dnf-automatic with unrecognized
positional arguments, such as `dnf-automatic install`.
The most natural approach to handle a non-existing config file would be
by catching the exception thrown by the `read()` method of the
`libdnf.conf.ConfigParser` class. Unfortunately, the Python bindings
override the `read()` method at the SWIG level, causing it to suppress any
potentially raised IOError.
For details see this section of the commit
https://github.com/rpm-software-management/libdnf/commit/8f1fedf8551b72d6bc24018f5980714b3a103aeb
def ConfigParser__newRead(self, filenames):
parsedFNames = []
try:
if isinstance(filenames, str) or isinstance(filenames, unicode):
filenames = [filenames]
except NameError:
pass
for fname in filenames:
try:
self.readFileName(fname)
parsedFNames.append(fname)
except IOError:
pass
except Exception as e:
raise RuntimeError("Parsing file '%s' failed: %s" % (fname, str(e)))
return parsedFNames
ConfigParser.read = ConfigParser__newRead
Resolves: https://issues.redhat.com/browse/RHEL-46030
---
dnf/automatic/main.py | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/dnf/automatic/main.py b/dnf/automatic/main.py
index caef627f..bb0bd493 100644
--- a/dnf/automatic/main.py
+++ b/dnf/automatic/main.py
@@ -74,7 +74,7 @@ def build_emitters(conf):
def parse_arguments(args):
parser = argparse.ArgumentParser()
- parser.add_argument('conf_path', nargs='?', default=dnf.const.CONF_AUTOMATIC_FILENAME)
+ parser.add_argument('conf_path', nargs='?')
parser.add_argument('--timer', action='store_true')
parser.add_argument('--installupdates', dest='installupdates', action='store_true')
parser.add_argument('--downloadupdates', dest='downloadupdates', action='store_true')
@@ -89,7 +89,17 @@ def parse_arguments(args):
class AutomaticConfig(object):
def __init__(self, filename=None, downloadupdates=None,
installupdates=None):
- if not filename:
+ if filename:
+ # Specific config file was explicitely requested. Check that it exists
+ # and is readable.
+ if os.access(filename, os.F_OK):
+ if not os.access(filename, os.R_OK):
+ raise dnf.exceptions.Error(
+ "Configuration file \"{}\" is not readable.".format(filename))
+ else:
+ raise dnf.exceptions.Error(
+ "Configuration file \"{}\" not found.".format(filename))
+ else:
filename = dnf.const.CONF_AUTOMATIC_FILENAME
self.commands = CommandsConfig()
self.email = EmailConfig()
--
2.47.0

View File

@ -0,0 +1,247 @@
From 130e44556d49cf9fa4a0468bb73b56182691ab86 Mon Sep 17 00:00:00 2001
From: derickdiaz <derickdiaz123@outlook.com>
Date: Sun, 15 Oct 2023 10:58:03 -0500
Subject: [PATCH 1/4] Added feature to allow emitters to invoke on dnf error
---
AUTHORS | 1 +
dnf/automatic/emitter.py | 22 ++++++++++++++++++----
dnf/automatic/main.py | 5 +++++
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/AUTHORS b/AUTHORS
index 699a92c4..2e16c8ae 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -68,6 +68,7 @@ DNF CONTRIBUTORS
Christopher Meng <cickumqt@gmail.com>
Daniel Mach <dmach@redhat.com>
Dave Johansen <davejohansen@gmail.com>
+ Derick Diaz <derickdiaz123@outlook.com>
Dylan Pindur <dylanpindur@gmail.com>
Eduard Cuba <ecuba@redhat.com>
Evan Goode <egoode@redhat.com>
diff --git a/dnf/automatic/emitter.py b/dnf/automatic/emitter.py
index 648f1a1d..673da082 100644
--- a/dnf/automatic/emitter.py
+++ b/dnf/automatic/emitter.py
@@ -33,6 +33,7 @@ APPLIED = _("The following updates have been applied on '%s':")
APPLIED_TIMESTAMP = _("Updates completed at %s")
AVAILABLE = _("The following updates are available on '%s':")
DOWNLOADED = _("The following updates were downloaded on '%s':")
+ERROR = _("An error has occured on: '%s'")
logger = logging.getLogger('dnf')
@@ -44,10 +45,15 @@ class Emitter(object):
self._downloaded = False
self._system_name = system_name
self._trans_msg = None
+ self._error = False
+ self._error_msg = None
def _prepare_msg(self):
msg = []
- if self._applied:
+ if self._error:
+ msg.append(ERROR % self._system_name)
+ msg.append(self._error_msg)
+ elif self._applied:
msg.append(APPLIED % self._system_name)
msg.append(self._available_msg)
msg.append(APPLIED_TIMESTAMP % time.strftime("%c"))
@@ -72,6 +78,10 @@ class Emitter(object):
assert self._available_msg
self._downloaded = True
+ def notify_error(self, msg):
+ self._error = True
+ self._error_msg = msg
+
class EmailEmitter(Emitter):
def __init__(self, system_name, conf):
@@ -79,7 +89,9 @@ class EmailEmitter(Emitter):
self._conf = conf
def _prepare_msg(self):
- if self._applied:
+ if self._error:
+ subj = _("An error has occured on '%s'.") % self._system_name
+ elif self._applied:
subj = _("Updates applied on '%s'.") % self._system_name
elif self._downloaded:
subj = _("Updates downloaded on '%s'.") % self._system_name
@@ -95,6 +107,8 @@ class EmailEmitter(Emitter):
message.set_charset('utf-8')
email_from = self._conf.email_from
email_to = self._conf.email_to
+ email_host = self._conf.email_host
+ email_port = self._conf.email_port
message['Date'] = email.utils.formatdate()
message['From'] = email_from
message['Subject'] = subj
@@ -103,12 +117,12 @@ class EmailEmitter(Emitter):
# Send the email
try:
- smtp = smtplib.SMTP(self._conf.email_host, timeout=300)
+ smtp = smtplib.SMTP(email_host, email_port, timeout=300)
smtp.sendmail(email_from, email_to, message.as_string())
smtp.close()
except OSError as exc:
msg = _("Failed to send an email via '%s': %s") % (
- self._conf.email_host, exc)
+ email_host, exc)
logger.error(msg)
diff --git a/dnf/automatic/main.py b/dnf/automatic/main.py
index bb0bd493..8aca210a 100644
--- a/dnf/automatic/main.py
+++ b/dnf/automatic/main.py
@@ -314,6 +314,7 @@ def main(args):
try:
conf = AutomaticConfig(opts.conf_path, opts.downloadupdates,
opts.installupdates)
+ emitters = None
with dnf.Base() as base:
cli = dnf.cli.Cli(base)
cli._read_conf_file()
@@ -376,9 +377,13 @@ def main(args):
exit_code = os.waitstatus_to_exitcode(os.system(conf.commands.reboot_command))
if exit_code != 0:
logger.error('Error: reboot command returned nonzero exit code: %d', exit_code)
+ emitters.notify_error('Error: reboot command returned nonzero exit code: %d', exit_code)
+ emitters.commit()
return 1
except dnf.exceptions.Error as exc:
logger.error(_('Error: %s'), ucd(exc))
+ emitters.notify_error(_('Error: %s') % str(exc))
+ emitters.commit()
return 1
return 0
--
2.47.1
From 1d18e7c6c03d6de084a6845db3a2dc50f02c8e4c Mon Sep 17 00:00:00 2001
From: derickdiaz <derickdiaz123@outlook.com>
Date: Thu, 19 Oct 2023 04:58:45 -0500
Subject: [PATCH 2/4] Checks if emitter is null incase build_emitters throws a
ConfigError
---
dnf/automatic/main.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dnf/automatic/main.py b/dnf/automatic/main.py
index 8aca210a..04926346 100644
--- a/dnf/automatic/main.py
+++ b/dnf/automatic/main.py
@@ -382,8 +382,9 @@ def main(args):
return 1
except dnf.exceptions.Error as exc:
logger.error(_('Error: %s'), ucd(exc))
- emitters.notify_error(_('Error: %s') % str(exc))
- emitters.commit()
+ if conf.emitters != None:
+ emitters.notify_error(_('Error: %s') % str(exc))
+ emitters.commit()
return 1
return 0
--
2.47.1
From 3e45752f0b74d2c4297da429a57b3e5148374195 Mon Sep 17 00:00:00 2001
From: derickdiaz <derickdiaz123@outlook.com>
Date: Thu, 19 Oct 2023 05:21:23 -0500
Subject: [PATCH 3/4] Added 'send_error_messages' Boolean Option and updated
man docs
Added option 'send_error_messages'
Fixed Option String List
Changed option to Boolean
---
dnf/automatic/main.py | 8 +++-----
doc/automatic.rst | 5 +++++
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/dnf/automatic/main.py b/dnf/automatic/main.py
index 04926346..0a9d5041 100644
--- a/dnf/automatic/main.py
+++ b/dnf/automatic/main.py
@@ -239,6 +239,7 @@ class EmittersConfig(Config):
libdnf.conf.VectorString(['email', 'stdio'])))
self.add_option('output_width', libdnf.conf.OptionNumberInt32(80))
self.add_option('system_name', libdnf.conf.OptionString(socket.gethostname()))
+ self.add_option('send_error_messages', libdnf.conf.OptionBool(False))
def gpgsigcheck(base, pkgs):
@@ -376,13 +377,10 @@ def main(args):
(conf.commands.reboot == 'when-needed' and base.reboot_needed())):
exit_code = os.waitstatus_to_exitcode(os.system(conf.commands.reboot_command))
if exit_code != 0:
- logger.error('Error: reboot command returned nonzero exit code: %d', exit_code)
- emitters.notify_error('Error: reboot command returned nonzero exit code: %d', exit_code)
- emitters.commit()
- return 1
+ raise dnf.exceptions.Error('reboot command returned nonzero exit code: %d', exit_code)
except dnf.exceptions.Error as exc:
logger.error(_('Error: %s'), ucd(exc))
- if conf.emitters != None:
+ if conf.emitters.send_error_messages and emitters != None:
emitters.notify_error(_('Error: %s') % str(exc))
emitters.commit()
return 1
diff --git a/doc/automatic.rst b/doc/automatic.rst
index 329c2f46..2d514b78 100644
--- a/doc/automatic.rst
+++ b/doc/automatic.rst
@@ -120,6 +120,11 @@ Choosing how the results should be reported.
How the system is called in the reports.
+``send_error_messages``
+ boolean, default: False
+
+ Invokes emitters when an errors occurs
+
---------------------
``[command]`` section
---------------------
--
2.47.1
From 33d52a8072c47b74603cf38ec807c26657799578 Mon Sep 17 00:00:00 2001
From: derickdiaz <derickdiaz123@outlook.com>
Date: Fri, 27 Oct 2023 12:00:21 -0500
Subject: [PATCH 4/4] Fixed Typo in docs
---
doc/automatic.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/automatic.rst b/doc/automatic.rst
index 2d514b78..4f8eec68 100644
--- a/doc/automatic.rst
+++ b/doc/automatic.rst
@@ -123,7 +123,7 @@ Choosing how the results should be reported.
``send_error_messages``
boolean, default: False
- Invokes emitters when an errors occurs
+ Invokes emitters when an error occurs.
---------------------
``[command]`` section
--
2.47.1

View File

@ -0,0 +1,60 @@
From 2a1046f4dbf855902056e463a9d35612e93f786e Mon Sep 17 00:00:00 2001
From: Marek Blaha <mblaha@redhat.com>
Date: Mon, 9 Dec 2024 13:42:44 +0100
Subject: [PATCH] automatic: Enhance errors reporting
Emitters must be initialized early to ensure they can report errors that
might occur in earlier stages of execution, before transaction
resolution. Also a broader range of exceptions must be caught to
ensure they are communicated to the user through the configured
emitters.
For example "No space left on the device" error can be raised during the
fill_sack() call. This patch should report it via configured emitters.
Resolves: https://issues.redhat.com/browse/RHEL-61882
---
dnf/automatic/main.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/dnf/automatic/main.py b/dnf/automatic/main.py
index 0a9d5041..5ca4ad39 100644
--- a/dnf/automatic/main.py
+++ b/dnf/automatic/main.py
@@ -311,11 +311,13 @@ def wait_for_network(repos, timeout):
def main(args):
(opts, parser) = parse_arguments(args)
+ conf = None
+ emitters = None
try:
conf = AutomaticConfig(opts.conf_path, opts.downloadupdates,
opts.installupdates)
- emitters = None
+ emitters = build_emitters(conf)
with dnf.Base() as base:
cli = dnf.cli.Cli(base)
cli._read_conf_file()
@@ -349,7 +351,6 @@ def main(args):
return 0
lst = output.list_transaction(trans, total_width=80)
- emitters = build_emitters(conf)
emitters.notify_available(lst)
if not conf.commands.download_updates:
emitters.commit()
@@ -378,9 +379,9 @@ def main(args):
exit_code = os.waitstatus_to_exitcode(os.system(conf.commands.reboot_command))
if exit_code != 0:
raise dnf.exceptions.Error('reboot command returned nonzero exit code: %d', exit_code)
- except dnf.exceptions.Error as exc:
+ except Exception as exc:
logger.error(_('Error: %s'), ucd(exc))
- if conf.emitters.send_error_messages and emitters != None:
+ if conf is not None and conf.emitters.send_error_messages and emitters is not None:
emitters.notify_error(_('Error: %s') % str(exc))
emitters.commit()
return 1
--
2.47.1

View File

@ -0,0 +1,29 @@
From ed14b8c8425c6fb6dbade3028ac0118086052b1b Mon Sep 17 00:00:00 2001
From: Klaas Demter <Klaas-@users.noreply.github.com>
Date: Tue, 15 Oct 2024 11:58:15 +0200
Subject: [PATCH] Update need_reboot for dnf-automatic
The need_reboot from dnf-automatic did not match NEED_REBOOT from
needs-restarting.
---
dnf/base.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dnf/base.py b/dnf/base.py
index 13222407..168207b5 100644
--- a/dnf/base.py
+++ b/dnf/base.py
@@ -2832,8 +2832,8 @@ class Base(object):
# List taken from DNF needs-restarting
need_reboot = frozenset(('kernel', 'kernel-rt', 'glibc',
- 'linux-firmware', 'systemd', 'dbus',
- 'dbus-broker', 'dbus-daemon'))
+ 'linux-firmware', 'systemd', 'dbus',
+ 'dbus-broker', 'dbus-daemon', 'microcode_ctl'))
changed_pkgs = self.transaction.install_set | self.transaction.remove_set
return any(pkg.name in need_reboot for pkg in changed_pkgs)
--
2.47.1

View File

@ -0,0 +1,27 @@
From c57c26e0cf8a3bba061f5ddf619c40f36a94aab9 Mon Sep 17 00:00:00 2001
From: Marek Blaha <mblaha@redhat.com>
Date: Thu, 12 Dec 2024 08:09:48 +0100
Subject: [PATCH] automatic: Fix incorrect Error class instantiation
dnf.exceptions.Error class constructor accepts only one argument - error
message.
---
dnf/automatic/main.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dnf/automatic/main.py b/dnf/automatic/main.py
index 5ca4ad39..51057dfb 100644
--- a/dnf/automatic/main.py
+++ b/dnf/automatic/main.py
@@ -378,7 +378,7 @@ def main(args):
(conf.commands.reboot == 'when-needed' and base.reboot_needed())):
exit_code = os.waitstatus_to_exitcode(os.system(conf.commands.reboot_command))
if exit_code != 0:
- raise dnf.exceptions.Error('reboot command returned nonzero exit code: %d', exit_code)
+ raise dnf.exceptions.Error('reboot command returned nonzero exit code: %d' % exit_code)
except Exception as exc:
logger.error(_('Error: %s'), ucd(exc))
if conf is not None and conf.emitters.send_error_messages and emitters is not None:
--
2.47.1

View File

@ -0,0 +1,39 @@
From be3f218b6964116bc2948a9b7b93247322dc979f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
Date: Thu, 12 Dec 2024 12:59:03 +0100
Subject: [PATCH] doc: `--disableexcludepkgs=all` doesn't affect just file
configuration
The option `--disableexcludepkgs=all` disables all configuration
includes and excludes including packages specified on the commandline
via `--exclude=`, `-x=`, the deprecated `--excludepkgs=` and file
configuration via `--excludepkgs=`, `--includepkgs=`.
---
doc/command_ref.rst | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/doc/command_ref.rst b/doc/command_ref.rst
index 36817c00..75ded68c 100644
--- a/doc/command_ref.rst
+++ b/doc/command_ref.rst
@@ -165,12 +165,13 @@ Options
.. _disableexcludes-label:
``--disableexcludes=[all|main|<repoid>], --disableexcludepkgs=[all|main|<repoid>]``
+ Disable ``excludepkgs`` and ``includepkgs`` configuration options. Takes one of the following three options:
- Disable the configuration file excludes. Takes one of the following three options:
+ * ``all``, disables all ``excludepkgs`` and ``includepkgs`` configurations
+ * ``main``, disables ``excludepkgs`` and ``includepkgs`` defined in the ``[main]`` section
+ * ``repoid``, disables ``excludepkgs`` and ``includepkgs`` defined for the given repository
- * ``all``, disables all configuration file excludes
- * ``main``, disables excludes defined in the ``[main]`` section
- * ``repoid``, disables excludes defined for the given repository
+ Note that the \-\ :ref:`-exclude <exclude_option-label>` option appends to the ``[main]`` ``excludepkgs`` configuration and therefore is disabled when ``main`` or ``all`` is specified.
``--disable, --set-disabled``
Disable specified repositories (automatically saves). The option has to be used together with the
--
2.46.2

View File

@ -0,0 +1,181 @@
From e236290f4aec12ad9b2e5cdfa48ec8e3172bb89c Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Thu, 7 Nov 2024 02:31:25 +0000
Subject: [PATCH 39/44] Add support for --transient
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 6091f3fccea988208ca417a504569947e4c99263
Adds support for the --transient option on all transactions. Passing
--transient on a bootc system will call `bootc usr-overlay` to create a
transient writeable /usr and continue the transaction.
Specifying --transient on a non-bootc system will throw an error; we
don't want to mislead users to thinking this feature works on non-bootc
systems.
If --transient is not specified and the bootc system is in a locked
state, the operation will be aborted and a message will be printed
suggesting to try again with --transient.
Resolves: https://issues.redhat.com/browse/RHEL-70917
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 40 ++++++++++++++++++++++++++++++---------
dnf/cli/option_parser.py | 3 +++
dnf/conf/config.py | 2 +-
dnf/util.py | 41 +++++++++++++++++++++++++++++-----------
4 files changed, 65 insertions(+), 21 deletions(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 3dc08d616..33fe20aab 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -205,28 +205,50 @@ class BaseCli(dnf.Base):
else:
self.output.reportDownloadSize(install_pkgs, install_only)
+ bootc_unlock_requested = False
+
if trans or self._moduleContainer.isChanged() or \
(self._history and (self._history.group or self._history.env)):
# confirm with user
if self.conf.downloadonly:
logger.info(_("{prog} will only download packages for the transaction.").format(
prog=dnf.util.MAIN_PROG_UPPER))
+
elif 'test' in self.conf.tsflags:
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
- if dnf.util._is_bootc_host() and \
- os.path.realpath(self.conf.installroot) == "/" and \
- not self.conf.downloadonly:
- _bootc_host_msg = _("""
-*** Error: system is configured to be read-only; for more
-*** information run `bootc --help`.
-""")
- logger.info(_bootc_host_msg)
- raise CliError(_("Operation aborted."))
+
+ is_bootc_transaction = dnf.util._is_bootc_host() and \
+ os.path.realpath(self.conf.installroot) == "/" and \
+ not self.conf.downloadonly
+
+ # Handle bootc transactions. `--transient` must be specified if
+ # /usr is not already writeable.
+ if is_bootc_transaction:
+ if self.conf.persistence == "persist":
+ logger.info(_("Persistent transactions aren't supported on bootc systems."))
+ raise CliError(_("Operation aborted."))
+ assert self.conf.persistence in ("auto", "transient")
+ if not dnf.util._is_bootc_unlocked():
+ if self.conf.persistence == "auto":
+ logger.info(_("This bootc system is configured to be read-only. Pass --transient to "
+ "perform this and subsequent transactions in a transient overlay which "
+ "will reset when the system reboots."))
+ raise CliError(_("Operation aborted."))
+ assert self.conf.persistence == "transient"
+ logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. "
+ "Keep in mind that changes to /etc and /var will still persist, and packages "
+ "commonly modify these directories."))
+ bootc_unlock_requested = True
+ elif self.conf.persistence == "transient":
+ raise CliError(_("Transient transactions are only supported on bootc systems."))
if self._promptWanted():
if self.conf.assumeno or not self.output.userconfirm():
raise CliError(_("Operation aborted."))
+
+ if bootc_unlock_requested:
+ dnf.util._bootc_unlock()
else:
logger.info(_('Nothing to do.'))
return
diff --git a/dnf/cli/option_parser.py b/dnf/cli/option_parser.py
index 41ff16451..66e69cc98 100644
--- a/dnf/cli/option_parser.py
+++ b/dnf/cli/option_parser.py
@@ -320,6 +320,9 @@ class OptionParser(argparse.ArgumentParser):
general_grp.add_argument("--downloadonly", dest="downloadonly",
action="store_true", default=False,
help=_("only download packages"))
+ general_grp.add_argument("--transient", dest="persistence",
+ action="store_const", const="transient", default=None,
+ help=_("Use a transient overlay which will reset on reboot"))
general_grp.add_argument("--comment", dest="comment", default=None,
help=_("add a comment to transaction"))
# Updateinfo options...
diff --git a/dnf/conf/config.py b/dnf/conf/config.py
index 32516d1a8..ed6daeb2d 100644
--- a/dnf/conf/config.py
+++ b/dnf/conf/config.py
@@ -342,7 +342,7 @@ class MainConf(BaseConfig):
'best', 'assumeyes', 'assumeno', 'clean_requirements_on_remove', 'gpgcheck',
'showdupesfromrepos', 'plugins', 'ip_resolve',
'rpmverbosity', 'disable_excludes', 'color',
- 'downloadonly', 'exclude', 'excludepkgs', 'skip_broken',
+ 'downloadonly', 'persistence', 'exclude', 'excludepkgs', 'skip_broken',
'tsflags', 'arch', 'basearch', 'ignorearch', 'cacheonly', 'comment']
for name in config_args:
diff --git a/dnf/util.py b/dnf/util.py
index e8f587a03..f22e3901b 100644
--- a/dnf/util.py
+++ b/dnf/util.py
@@ -38,6 +38,7 @@ import logging
import os
import pwd
import shutil
+import subprocess
import sys
import tempfile
import time
@@ -634,14 +635,32 @@ def _name_unset_wrapper(input_name):
def _is_bootc_host():
- """Returns true is the system is managed as an immutable container,
- false otherwise. If msg is True, a warning message is displayed
- for the user.
- """
- ostree_booted = '/run/ostree-booted'
- usr = '/usr/'
- # Check if usr is writtable and we are in a running ostree system.
- # We want this code to return true only when the system is in locked state. If someone ran
- # bootc overlay or ostree admin unlock we would want normal DNF path to be ran as it will be
- # temporary changes (until reboot).
- return os.path.isfile(ostree_booted) and not os.access(usr, os.W_OK)
+ """Returns true is the system is managed as an immutable container, false
+ otherwise."""
+ ostree_booted = "/run/ostree-booted"
+ return os.path.isfile(ostree_booted)
+
+
+def _is_bootc_unlocked():
+ """Check whether /usr is writeable, e.g. if we are in a normal mutable
+ system or if we are in a bootc after `bootc usr-overlay` or `ostree admin
+ unlock` was run."""
+ usr = "/usr"
+ return os.access(usr, os.W_OK)
+
+
+def _bootc_unlock():
+ """Set up a writeable overlay on bootc systems."""
+
+ if _is_bootc_unlocked():
+ return
+
+ unlock_command = ["bootc", "usr-overlay"]
+
+ try:
+ completed_process = subprocess.run(unlock_command, text=True)
+ completed_process.check_returncode()
+ except FileNotFoundError:
+ raise dnf.exceptions.Error(_("bootc command not found. Is this a bootc system?"))
+ except subprocess.CalledProcessError:
+ raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
--
2.48.1

View File

@ -0,0 +1,74 @@
From ec5cbd19adc6f384923e95d42357f23696b3c950 Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Tue, 17 Dec 2024 18:58:32 +0000
Subject: [PATCH 40/44] bootc: Document `--transient` and `persistence`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 80a62d89ba3c00f4e0bd3fea995d04ccf9ba8098
Documents the new `--transient` command-line argument and `persistence`
configuration option. I tried to use a table for listing the valid
options for `persistence`, but RST does not automatically wrap table
cells containing long lines, so a list was much easier.
Resolves: https://issues.redhat.com/browse/RHEL-70917
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
doc/command_ref.rst | 9 +++++++++
doc/conf_ref.rst | 11 +++++++++++
2 files changed, 20 insertions(+)
diff --git a/doc/command_ref.rst b/doc/command_ref.rst
index 75ded68cd..8b55d5a76 100644
--- a/doc/command_ref.rst
+++ b/doc/command_ref.rst
@@ -389,6 +389,11 @@ Options
``--showduplicates``
Show duplicate packages in repositories. Applicable for the list and search commands.
+.. _transient_option-label:
+
+``--transient``
+ Applicable only on bootc (bootable containers) systems. Perform transactions using a transient overlay which will be lost on the next reboot. See also the :ref:`persistence <persistence-label>` configuration option.
+
.. _verbose_options-label:
``-v, --verbose``
@@ -708,6 +713,10 @@ transactions and act according to this information (assuming the
which specifies a transaction by a package which it manipulated. When no
transaction is specified, list all known transactions.
+ Note that transient transactions (see :ref:`--transient
+ <transient_option-label>`) will be listed even though they do not make
+ persistent changes to files under ``/usr`` or to the RPM database.
+
The "Action(s)" column lists each type of action taken in the transaction. The possible values are:
* Install (I): a new package was installed on the system
diff --git a/doc/conf_ref.rst b/doc/conf_ref.rst
index 42a9a37e5..a34e355b6 100644
--- a/doc/conf_ref.rst
+++ b/doc/conf_ref.rst
@@ -430,6 +430,17 @@ configuration file by your distribution to override the DNF defaults.
Directory where DNF stores its persistent data between runs. Default is ``"/var/lib/dnf"``.
+.. _persistence-label:
+
+``persistence``
+ :ref:`string <string-label>`
+
+ Whether changes should persist across system reboots. Default is ``auto``. Passing :ref:`--transient <transient_option-label>` will override this setting to ``transient``. Valid values are:
+
+ * ``auto``: Changes will persist across reboots, unless the target is a running bootc system and the system is already in an unlocked state (i.e. ``/usr`` is writable).
+ * ``transient``: Changes will be lost on the next reboot. Only applicable on bootc systems. Beware that changes to ``/etc`` and ``/var`` will persist, depending on the configuration of your bootc system. See also https://containers.github.io/bootc/man/bootc-usr-overlay.html.
+ * ``persist``: Changes will persist across reboots.
+
.. _pluginconfpath-label:
``pluginconfpath``
--
2.48.1

View File

@ -0,0 +1,166 @@
From 5a5572b8adc335075cdae321ad1447e1b8bd8760 Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Wed, 15 Jan 2025 21:43:58 +0000
Subject: [PATCH 41/44] bootc: Use ostree GObject API to get deployment status
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: f3abee56452e40ce475f714666c3a16426759c96
Using libostree gives us more detail about the current state of the
deployment than only checking whether /usr is writable.
Resolves: https://issues.redhat.com/browse/RHEL-70917
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 13 ++++----
dnf/util.py | 80 +++++++++++++++++++++++++++++++++++---------------
2 files changed, 65 insertions(+), 28 deletions(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index 33fe20aab..e7ca86ba8 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -218,18 +218,22 @@ class BaseCli(dnf.Base):
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
- is_bootc_transaction = dnf.util._is_bootc_host() and \
+ is_bootc_transaction = dnf.util._Bootc.is_bootc_host() and \
os.path.realpath(self.conf.installroot) == "/" and \
not self.conf.downloadonly
# Handle bootc transactions. `--transient` must be specified if
# /usr is not already writeable.
+ bootc = None
if is_bootc_transaction:
if self.conf.persistence == "persist":
logger.info(_("Persistent transactions aren't supported on bootc systems."))
raise CliError(_("Operation aborted."))
assert self.conf.persistence in ("auto", "transient")
- if not dnf.util._is_bootc_unlocked():
+
+ bootc = dnf.util._Bootc()
+
+ if not bootc.is_unlocked():
if self.conf.persistence == "auto":
logger.info(_("This bootc system is configured to be read-only. Pass --transient to "
"perform this and subsequent transactions in a transient overlay which "
@@ -239,7 +243,6 @@ class BaseCli(dnf.Base):
logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. "
"Keep in mind that changes to /etc and /var will still persist, and packages "
"commonly modify these directories."))
- bootc_unlock_requested = True
elif self.conf.persistence == "transient":
raise CliError(_("Transient transactions are only supported on bootc systems."))
@@ -247,8 +250,8 @@ class BaseCli(dnf.Base):
if self.conf.assumeno or not self.output.userconfirm():
raise CliError(_("Operation aborted."))
- if bootc_unlock_requested:
- dnf.util._bootc_unlock()
+ if bootc:
+ bootc.unlock_and_prepare()
else:
logger.info(_('Nothing to do.'))
return
diff --git a/dnf/util.py b/dnf/util.py
index f22e3901b..994fddafc 100644
--- a/dnf/util.py
+++ b/dnf/util.py
@@ -634,33 +634,67 @@ def _name_unset_wrapper(input_name):
return input_name if input_name else _("<name-unset>")
-def _is_bootc_host():
- """Returns true is the system is managed as an immutable container, false
- otherwise."""
- ostree_booted = "/run/ostree-booted"
- return os.path.isfile(ostree_booted)
+class _Bootc:
+ usr = "/usr"
+ def __init__(self):
+ if not self.is_bootc_host():
+ raise RuntimeError(_("Not running on a bootc system."))
-def _is_bootc_unlocked():
- """Check whether /usr is writeable, e.g. if we are in a normal mutable
- system or if we are in a bootc after `bootc usr-overlay` or `ostree admin
- unlock` was run."""
- usr = "/usr"
- return os.access(usr, os.W_OK)
+ import gi
+ self._gi = gi
+ gi.require_version("OSTree", "1.0")
+ from gi.repository import OSTree
-def _bootc_unlock():
- """Set up a writeable overlay on bootc systems."""
+ self._OSTree = OSTree
- if _is_bootc_unlocked():
- return
+ self._sysroot = self._OSTree.Sysroot.new_default()
+ assert self._sysroot.load(None)
- unlock_command = ["bootc", "usr-overlay"]
+ self._booted_deployment = self._sysroot.require_booted_deployment()
+ assert self._booted_deployment is not None
- try:
- completed_process = subprocess.run(unlock_command, text=True)
- completed_process.check_returncode()
- except FileNotFoundError:
- raise dnf.exceptions.Error(_("bootc command not found. Is this a bootc system?"))
- except subprocess.CalledProcessError:
- raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
+ @staticmethod
+ def is_bootc_host():
+ """Returns true is the system is managed as an immutable container, false
+ otherwise."""
+ ostree_booted = "/run/ostree-booted"
+ return os.path.isfile(ostree_booted)
+
+ def _get_unlocked_state(self):
+ return self._booted_deployment.get_unlocked()
+
+ def is_unlocked(self):
+ return self._get_unlocked_state() != self._OSTree.DeploymentUnlockedState.NONE
+
+ def unlock_and_prepare(self):
+ """Set up a writeable overlay on bootc systems."""
+
+ bootc_unlocked_state = self._get_unlocked_state()
+
+ valid_bootc_unlocked_states = (
+ self._OSTree.DeploymentUnlockedState.NONE,
+ self._OSTree.DeploymentUnlockedState.DEVELOPMENT,
+ # self._OSTree.DeploymentUnlockedState.TRANSIENT,
+ # self._OSTree.DeploymentUnlockedState.HOTFIX,
+ )
+
+ if bootc_unlocked_state not in valid_bootc_unlocked_states:
+ raise ValueError(_("Unhandled bootc unlocked state: %s") % bootc_unlocked_state.value_nick)
+
+ if bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.DEVELOPMENT:
+ # System is already unlocked.
+ pass
+ elif bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.NONE:
+ unlock_command = ["ostree", "admin", "unlock"]
+
+ try:
+ completed_process = subprocess.run(unlock_command, text=True)
+ completed_process.check_returncode()
+ except FileNotFoundError:
+ raise dnf.exceptions.Error(_("ostree command not found. Is this a bootc system?"))
+ except subprocess.CalledProcessError:
+ raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
+
+ assert os.access(self.usr, os.W_OK)
--
2.48.1

View File

@ -0,0 +1,237 @@
From 7e0180ad97a677e6701031f13069c20beec1d8ff Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Thu, 16 Jan 2025 14:06:26 -0500
Subject: [PATCH 42/44] bootc: "Re-locking": use ostree admin unlock
--transient
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: fa47a256ae7add2ce1c99ae8bedce7216001f396
To keep /usr read-only after DNF is finished with a transient
transaction, we call `ostree admin unlock --transient` to mount the /usr
overlay as read-only by default. Then, we create a private mount
namespace for DNF and its child processes and remount the /usr overlayfs
as read/write in the private mountns.
os.unshare is unfortunately only available in Python >= 3.12, so we have
to call libc.unshare via Python ctypes here and hardcode the CLONE_NEWNS
flag that we need to pass.
Resolves: https://issues.redhat.com/browse/RHEL-70917
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf/cli/cli.py | 33 ++++++++++---------
dnf/util.py | 86 ++++++++++++++++++++++++++++++++++++++------------
2 files changed, 83 insertions(+), 36 deletions(-)
diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py
index e7ca86ba8..23170a82b 100644
--- a/dnf/cli/cli.py
+++ b/dnf/cli/cli.py
@@ -205,8 +205,6 @@ class BaseCli(dnf.Base):
else:
self.output.reportDownloadSize(install_pkgs, install_only)
- bootc_unlock_requested = False
-
if trans or self._moduleContainer.isChanged() or \
(self._history and (self._history.group or self._history.env)):
# confirm with user
@@ -218,40 +216,45 @@ class BaseCli(dnf.Base):
logger.info(_("{prog} will only download packages, install gpg keys, and check the "
"transaction.").format(prog=dnf.util.MAIN_PROG_UPPER))
- is_bootc_transaction = dnf.util._Bootc.is_bootc_host() and \
+ is_bootc_transaction = dnf.util._BootcSystem.is_bootc_system() and \
os.path.realpath(self.conf.installroot) == "/" and \
not self.conf.downloadonly
# Handle bootc transactions. `--transient` must be specified if
# /usr is not already writeable.
- bootc = None
+ bootc_system = None
if is_bootc_transaction:
if self.conf.persistence == "persist":
logger.info(_("Persistent transactions aren't supported on bootc systems."))
raise CliError(_("Operation aborted."))
assert self.conf.persistence in ("auto", "transient")
- bootc = dnf.util._Bootc()
+ bootc_system = dnf.util._BootcSystem()
- if not bootc.is_unlocked():
+ if not bootc_system.is_writable():
if self.conf.persistence == "auto":
logger.info(_("This bootc system is configured to be read-only. Pass --transient to "
- "perform this and subsequent transactions in a transient overlay which "
- "will reset when the system reboots."))
+ "perform this transaction in a transient overlay which will reset when "
+ "the system reboots."))
raise CliError(_("Operation aborted."))
assert self.conf.persistence == "transient"
- logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. "
- "Keep in mind that changes to /etc and /var will still persist, and packages "
- "commonly modify these directories."))
- elif self.conf.persistence == "transient":
- raise CliError(_("Transient transactions are only supported on bootc systems."))
+ if not bootc_system.is_unlocked_transient():
+ # Only tell the user about the transient overlay if
+ # it's not already in place
+ logger.info(_("A transient overlay will be created on /usr that will be discarded on reboot. "
+ "Keep in mind that changes to /etc and /var will still persist, and packages "
+ "commonly modify these directories."))
+ else:
+ # Not a bootc transaction.
+ if self.conf.persistence == "transient":
+ raise CliError(_("Transient transactions are only supported on bootc systems."))
if self._promptWanted():
if self.conf.assumeno or not self.output.userconfirm():
raise CliError(_("Operation aborted."))
- if bootc:
- bootc.unlock_and_prepare()
+ if bootc_system:
+ bootc_system.make_writable()
else:
logger.info(_('Nothing to do.'))
return
diff --git a/dnf/util.py b/dnf/util.py
index 994fddafc..0161f80d8 100644
--- a/dnf/util.py
+++ b/dnf/util.py
@@ -25,6 +25,7 @@ from __future__ import unicode_literals
from .pycomp import PY3, basestring
from dnf.i18n import _, ucd
import argparse
+import ctypes
import dnf
import dnf.callback
import dnf.const
@@ -634,11 +635,12 @@ def _name_unset_wrapper(input_name):
return input_name if input_name else _("<name-unset>")
-class _Bootc:
+class _BootcSystem:
usr = "/usr"
+ CLONE_NEWNS = 0x00020000 # defined in linux/include/uapi/linux/sched.h
def __init__(self):
- if not self.is_bootc_host():
+ if not self.is_bootc_system():
raise RuntimeError(_("Not running on a bootc system."))
import gi
@@ -656,45 +658,87 @@ class _Bootc:
assert self._booted_deployment is not None
@staticmethod
- def is_bootc_host():
+ def is_bootc_system():
"""Returns true is the system is managed as an immutable container, false
otherwise."""
ostree_booted = "/run/ostree-booted"
return os.path.isfile(ostree_booted)
+ @classmethod
+ def is_writable(cls):
+ """Returns true if and only if /usr is writable."""
+ return os.access(cls.usr, os.W_OK)
+
def _get_unlocked_state(self):
return self._booted_deployment.get_unlocked()
- def is_unlocked(self):
- return self._get_unlocked_state() != self._OSTree.DeploymentUnlockedState.NONE
+ def is_unlocked_transient(self):
+ """Returns true if and only if the bootc system is unlocked in a
+ transient state, i.e. a overlayfs is mounted as read-only on /usr.
+ Changes can be made to the overlayfs by remounting /usr as
+ read/write in a private mount namespace."""
+ return self._get_unlocked_state() == self._OSTree.DeploymentUnlockedState.TRANSIENT
+
+ @classmethod
+ def _set_up_mountns(cls):
+ # os.unshare is only available in Python >= 3.12.
+
+ # Access symbols in libraries loaded by the Python interpreter,
+ # which will include libc. See https://bugs.python.org/issue34592.
+ libc = ctypes.CDLL(None)
+ if libc.unshare(cls.CLONE_NEWNS) != 0:
+ raise OSError("Failed to unshare mount namespace")
+
+ mount_command = ["mount", "--options-source=disable", "-o", "remount,rw", cls.usr]
+ try:
+ completed_process = subprocess.run(mount_command, text=True)
+ completed_process.check_returncode()
+ except FileNotFoundError:
+ raise dnf.exceptions.Error(_("%s: command not found.") % mount_command[0])
+ except subprocess.CalledProcessError:
+ raise dnf.exceptions.Error(_("Failed to mount %s as read/write: %s", cls.usr, completed_process.stderr))
- def unlock_and_prepare(self):
- """Set up a writeable overlay on bootc systems."""
+ @staticmethod
+ def _unlock():
+ unlock_command = ["ostree", "admin", "unlock", "--transient"]
+ try:
+ completed_process = subprocess.run(unlock_command, text=True)
+ completed_process.check_returncode()
+ except FileNotFoundError:
+ raise dnf.exceptions.Error(_("%s: command not found. Is this a bootc system?") % unlock_command[0])
+ except subprocess.CalledProcessError:
+ raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
+
+ def make_writable(self):
+ """Set up a writable overlay on bootc systems."""
bootc_unlocked_state = self._get_unlocked_state()
valid_bootc_unlocked_states = (
self._OSTree.DeploymentUnlockedState.NONE,
self._OSTree.DeploymentUnlockedState.DEVELOPMENT,
- # self._OSTree.DeploymentUnlockedState.TRANSIENT,
- # self._OSTree.DeploymentUnlockedState.HOTFIX,
+ self._OSTree.DeploymentUnlockedState.TRANSIENT,
+ self._OSTree.DeploymentUnlockedState.HOTFIX,
)
-
if bootc_unlocked_state not in valid_bootc_unlocked_states:
raise ValueError(_("Unhandled bootc unlocked state: %s") % bootc_unlocked_state.value_nick)
- if bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.DEVELOPMENT:
- # System is already unlocked.
+ writable_unlocked_states = (
+ self._OSTree.DeploymentUnlockedState.DEVELOPMENT,
+ self._OSTree.DeploymentUnlockedState.HOTFIX,
+ )
+ if bootc_unlocked_state in writable_unlocked_states:
+ # System is already unlocked in development mode, and usr is
+ # already mounted read/write.
pass
elif bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.NONE:
- unlock_command = ["ostree", "admin", "unlock"]
-
- try:
- completed_process = subprocess.run(unlock_command, text=True)
- completed_process.check_returncode()
- except FileNotFoundError:
- raise dnf.exceptions.Error(_("ostree command not found. Is this a bootc system?"))
- except subprocess.CalledProcessError:
- raise dnf.exceptions.Error(_("Failed to unlock system: %s", completed_process.stderr))
+ # System is not unlocked. Unlock it in transient mode, then set up
+ # a mount namespace for DNF.
+ self._unlock()
+ self._set_up_mountns()
+ elif bootc_unlocked_state == self._OSTree.DeploymentUnlockedState.TRANSIENT:
+ # System is unlocked in transient mode, so usr is mounted
+ # read-only. Set up a mount namespace for DNF.
+ self._set_up_mountns()
assert os.access(self.usr, os.W_OK)
--
2.48.1

View File

@ -0,0 +1,56 @@
From 9c2a1a6a36d748d059140bf6ee9113d5e9641477 Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Tue, 28 Jan 2025 11:27:00 -0500
Subject: [PATCH 43/44] spec: Add dnf-bootc subpackage
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 76a0c339eb172b1b2a4b1aa8b4db8d6a5145916b
dnf-bootc's only job is to Require python3-gobject-base, ostree,
ostree-libs, and util-linux-core, which are needed to interact with
bootc systems. We don't want to add these dependencies on `python3-dnf`
because we don't want them on non-bootc systems, so we use a subpackage.
Resolves: https://issues.redhat.com/browse/RHEL-70917
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf.spec | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/dnf.spec b/dnf.spec
index e9abd9041..b60f2692b 100644
--- a/dnf.spec
+++ b/dnf.spec
@@ -180,6 +180,17 @@ Requires: %{name} = %{version}-%{release}
%description automatic
Systemd units that can periodically download package upgrades and apply them.
+%package bootc
+Summary: %{pkg_summary} - additional bootc dependencies
+Requires: python3-%{name} = %{version}-%{release}
+Requires: ostree
+Requires: ostree-libs
+Requires: python3-gobject-base
+Requires: util-linux-core
+
+%description bootc
+Additional dependencies needed to perform transactions on booted bootc (bootable containers) systems.
+
%prep
%autosetup
@@ -358,6 +369,9 @@ popd
%{_unitdir}/%{name}-automatic-install.timer
%{python3_sitelib}/%{name}/automatic/
+%files bootc
+# bootc subpackage does not include any files
+
%changelog
* Fri Sep 09 2022 Jaroslav Rohel <jrohel@redhat.com> - 4.14.0-1
- doc: Describe how gpg keys are stored for `repo_ggpcheck` (RhBug:2020678)
--
2.48.1

View File

@ -0,0 +1,45 @@
From 19754a7c80e1c2232c370f394ad0d36f5713bf1e Mon Sep 17 00:00:00 2001
From: Evan Goode <mail@evangoo.de>
Date: Wed, 5 Feb 2025 10:35:08 -0500
Subject: [PATCH 44/44] Require libdnf >= 0.74.0 with `persistence` option
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 5a4f6c42e61ed764ff85eea69125947a280d665d
This backport actually uses RHEL-9 libdnf version.
Resolves: https://issues.redhat.com/browse/RHEL-70917
Signed-off-by: Petr Písař <ppisar@redhat.com>
---
dnf.spec | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/dnf.spec b/dnf.spec
index b60f2692b..313a3cb2a 100644
--- a/dnf.spec
+++ b/dnf.spec
@@ -2,7 +2,7 @@
%define __cmake_in_source_build 1
# default dependencies
-%global hawkey_version 0.66.0
+%global hawkey_version 0.74.0
%global libcomps_version 0.1.8
%global libmodulemd_version 2.9.3
%global rpm_version 4.14.0
@@ -21,6 +21,10 @@
%global rpm_version 4.11.3-25.el7.centos.1
%endif
+%if 0%{?rhel} == 9
+ %global hawkey_version 0.69.0-13
+%endif
+
# override dependencies for fedora 26
%if 0%{?fedora} == 26
%global rpm_version 4.13.0.1-7
--
2.48.1

View File

@ -2,7 +2,7 @@
%define __cmake_in_source_build 1 %define __cmake_in_source_build 1
# default dependencies # default dependencies
%global hawkey_version 0.66.0 %global hawkey_version 0.74.0
%global libcomps_version 0.1.8 %global libcomps_version 0.1.8
%global libmodulemd_version 2.9.3 %global libmodulemd_version 2.9.3
%global rpm_version 4.14.0 %global rpm_version 4.14.0
@ -21,6 +21,10 @@
%global rpm_version 4.11.3-25.el7.centos.1 %global rpm_version 4.11.3-25.el7.centos.1
%endif %endif
%if 0%{?rhel} == 9
%global hawkey_version 0.69.0-13
%endif
# override dependencies for fedora 26 # override dependencies for fedora 26
%if 0%{?fedora} == 26 %if 0%{?fedora} == 26
%global rpm_version 4.13.0.1-7 %global rpm_version 4.13.0.1-7
@ -69,7 +73,7 @@ It supports RPMs, modules and comps groups & environments.
Name: dnf Name: dnf
Version: 4.14.0 Version: 4.14.0
Release: 17%{?dist} Release: 25%{?dist}
Summary: %{pkg_summary} Summary: %{pkg_summary}
# For a breakdown of the licensing, see PACKAGE-LICENSING # For a breakdown of the licensing, see PACKAGE-LICENSING
License: GPLv2+ License: GPLv2+
@ -101,6 +105,24 @@ Patch23: 0023-Limit-queries-to-nevra-forms-when-provided-by-comman.patch
Patch24: 0024-doc-Remove-provide-of-spec-definition-for-repoquery-.patch Patch24: 0024-doc-Remove-provide-of-spec-definition-for-repoquery-.patch
Patch25: 0025-man-Improve-upgrade-minimal-command-docs-RHEL-6417.patch Patch25: 0025-man-Improve-upgrade-minimal-command-docs-RHEL-6417.patch
Patch26: 0026-doc-Makecache-with-timer-tries-only-one-mirror.patch Patch26: 0026-doc-Makecache-with-timer-tries-only-one-mirror.patch
Patch27: 0027-Add-detection-for-ostree-based-systems-and-warn-user.patch
Patch28: 0028-Update-ostree-bootc-host-system-check.patch
Patch29: 0029-Update-bootc-hosts-message-to-point-to-bootc-help.patch
Patch30: 0030-Allow-installroot-on-read-only-bootc-system.patch
Patch31: 0031-smtplib-catch-OSError-not-SMTPException.patch
Patch32: 0032-Allow-downloadonly-on-read-only-bootc-system.patch
Patch33: 0033-automatic-Check-availability-of-config-file.patch
Patch34: 0034-automatic-emitters-send-error-messages.patch
Patch35: 0035-automatic-Enhance-errors-reporting.patch
Patch36: 0036-Update-need_reboot-for-dnf-automatic.patch
Patch37: 0037-automatic-Fix-incorrect-Error-class-instantiation.patch
PAtch38: 0038-doc-disableexcludepkgs-all-doesn-t-affect-just-file.patch
Patch39: 0039-Add-support-for-transient.patch
Patch40: 0040-bootc-Document-transient-and-persistence.patch
Patch41: 0041-bootc-Use-ostree-GObject-API-to-get-deployment-statu.patch
Patch42: 0042-bootc-Re-locking-use-ostree-admin-unlock-transient.patch
Patch43: 0043-spec-Add-dnf-bootc-subpackage.patch
Patch44: 0044-Require-libdnf-0.74.0-with-persistence-option.patch
BuildArch: noarch BuildArch: noarch
BuildRequires: cmake BuildRequires: cmake
@ -210,6 +232,17 @@ Requires: %{name} = %{version}-%{release}
%description automatic %description automatic
Systemd units that can periodically download package upgrades and apply them. Systemd units that can periodically download package upgrades and apply them.
%package bootc
Summary: %{pkg_summary} - additional bootc dependencies
Requires: python3-%{name} = %{version}-%{release}
Requires: ostree
Requires: ostree-libs
Requires: python3-gobject-base
Requires: util-linux-core
%description bootc
Additional dependencies needed to perform transactions on booted bootc (bootable containers) systems.
%prep %prep
%autosetup -p1 %autosetup -p1
@ -388,7 +421,39 @@ popd
%{_unitdir}/%{name}-automatic-install.timer %{_unitdir}/%{name}-automatic-install.timer
%{python3_sitelib}/%{name}/automatic/ %{python3_sitelib}/%{name}/automatic/
%files bootc
# bootc subpackage does not include any files
%changelog %changelog
* Tue Feb 04 2025 Petr Pisar <ppisar@redhat.com> - 4.14.0-25
- Add support for transient transactions (RHEL-70917)
* Mon Jan 13 2025 Ales Matej <amatej@redhat.com> - 4.14.0-24
- doc: `--disableexcludepkgs=all` doesn't affect just file configuration
(RHEL-28779)
* Thu Dec 12 2024 Marek Blaha <mblaha@redhat.com> - 4.14.0-23
- automatic: Update need_reboot to match needs-restarting (RHEL-62830)
* Wed Dec 11 2024 Marek Blaha <mblaha@redhat.com> - 4.14.0-22
- automatic: Added feature to allow emitters to invoke on dnf error
(RHEL-45505, RHEL-61882)
* Mon Oct 21 2024 Marek Blaha <mblaha@redhat.com> - 4.14.0-21
- automatic: Check availability of config file (RHEL-49743)
* Thu Oct 10 2024 Petr Pisar <ppisar@redhat.com> - 4.14.0-20
- Allow "dnf install --downloadonly" on locked OSTree and bootc systems
(RHEL-61745)
* Mon Oct 07 2024 Marek Blaha <mblaha@redhat.com> - 4.14.0-19
- Catch more specific OSError instead of SMTPException in dnf-automatic email
emitter (RHEL-49743)
* Tue Oct 01 2024 Petr Pisar <ppisar@redhat.com> - 4.14.0-18
- More specific error message on a locked OSTree system or a bootc system
without a usr-overlay (RHEL-49670)
* Tue Aug 06 2024 Petr Pisar <ppisar@redhat.com> - 4.14.0-17 * Tue Aug 06 2024 Petr Pisar <ppisar@redhat.com> - 4.14.0-17
- Revert more specific error message on a locked OSTree system or a bootc system - Revert more specific error message on a locked OSTree system or a bootc system
without a usr-overlay (RHEL-49670) without a usr-overlay (RHEL-49670)