From 4ce0c55b26b4e5aa06aa532d0f95681c22ff2bb2 Mon Sep 17 00:00:00 2001 From: David Cantrell 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 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 Resolves: https://issues.redhat.com/browse/RHEL-49670 --- 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 0c4f4c6ad9..1fd0e96c3a 100644 --- a/dnf/cli/cli.py +++ b/dnf/cli/cli.py @@ -214,6 +214,15 @@ def do_transaction(self, display=()): 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 16c5bc89c1..9909f8fea5 100644 --- a/dnf/util.py +++ b/dnf/util.py @@ -33,11 +33,13 @@ 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 _tsi_or_pkg_nevra_cmp(item1, item2): def _name_unset_wrapper(input_name): # returns for everything that evaluates to False (None, empty..) return input_name if input_name else _("") + + +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 From 4d18a249a76f04aa7919f04a0716c391687e4876 Mon Sep 17 00:00:00 2001 From: Joseph Marrero Date: Tue, 16 Jul 2024 15:48:41 -0400 Subject: [PATCH 2/4] Update ostree/bootc host system check. 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 --- 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 1fd0e96c3a..8521dd3518 100644 --- a/dnf/cli/cli.py +++ b/dnf/cli/cli.py @@ -214,13 +214,12 @@ def do_transaction(self, display=()): 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 9909f8fea5..e8f587a030 100644 --- a/dnf/util.py +++ b/dnf/util.py @@ -33,13 +33,11 @@ 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 _("") -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) From 526a945c7140402ca41d5138a83f3cfcb8bff85a Mon Sep 17 00:00:00 2001 From: Joseph Marrero Date: Mon, 22 Jul 2024 15:33:32 -0400 Subject: [PATCH 3/4] Update bootc hosts message to point to bootc --help Upstream commit: e2535589ce16bc36b96b37369502a3c312f6056a Resolves: https://issues.redhat.com/browse/RHEL-49670 --- 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 8521dd3518..99af9069b9 100644 --- a/dnf/cli/cli.py +++ b/dnf/cli/cli.py @@ -217,7 +217,7 @@ def do_transaction(self, display=()): 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.")) From d4da674e23895ff5914dc7639859e70e628a58ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= Date: Thu, 15 Aug 2024 14:04:55 +0200 Subject: [PATCH 4/4] Allow --installroot on read-only bootc system 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 --- 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 99af9069b9..36cfa74b54 100644 --- a/dnf/cli/cli.py +++ b/dnf/cli/cli.py @@ -214,7 +214,8 @@ def do_transaction(self, display=()): 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`.