diff --git a/configure.ac b/configure.ac
index 7e09487129f..0019af31849 100644
--- a/configure.ac
+++ b/configure.ac
@@ -186,6 +186,7 @@ AC_CONFIG_FILES([Makefile
pyanaconda/modules/payloads/source/url/Makefile
pyanaconda/modules/runtime/Makefile
pyanaconda/modules/runtime/dracut_commands/Makefile
+ pyanaconda/modules/runtime/scripts/Makefile
pyanaconda/modules/runtime/user_interface/Makefile
pyanaconda/modules/storage/Makefile
pyanaconda/modules/storage/bootloader/Makefile
diff --git a/pyanaconda/core/kickstart/scripts.py b/pyanaconda/core/kickstart/scripts.py
new file mode 100644
index 00000000000..c39a70e22dd
--- /dev/null
+++ b/pyanaconda/core/kickstart/scripts.py
@@ -0,0 +1,80 @@
+#
+# Command utilities for working with scripts
+#
+# Copyright (C) 2024 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+import os
+import tempfile
+from pyanaconda.core import util
+from pyanaconda.core.path import open_with_perm
+
+from pyanaconda.anaconda_loggers import get_module_logger
+log = get_module_logger(__name__)
+
+script_log = log.getChild("script")
+
+
+__all__ = ["run_script"]
+
+
+def run_script(script, chroot):
+ """ Run the kickstart script
+
+ This will write the script to a file named /tmp/ks-script- before
+ execution.
+ Output is logged by the program logger, the path specified by --log
+ or to /tmp/ks-script-\\*.log
+ @param chroot directory path to chroot into before execution
+ """
+ if script.inChroot:
+ scriptRoot = chroot
+ else:
+ scriptRoot = "/"
+
+ (fd, path) = tempfile.mkstemp("", "ks-script-", scriptRoot + "/tmp")
+
+ os.write(fd, script.script.encode("utf-8"))
+ os.close(fd)
+ os.chmod(path, 0o700)
+
+ # Always log stdout/stderr from scripts. Using --log just lets you
+ # pick where it goes. The script will also be logged to program.log
+ # because of execWithRedirect.
+ if script.logfile:
+ if script.inChroot:
+ messages = "%s/%s" % (scriptRoot, script.logfile)
+ else:
+ messages = script.logfile
+
+ d = os.path.dirname(messages)
+ if not os.path.exists(d):
+ os.makedirs(d)
+ else:
+ # Always log outside the chroot, we copy those logs into the
+ # chroot later.
+ messages = "/tmp/%s.log" % os.path.basename(path)
+
+ with open_with_perm(messages, "w", 0o600) as fp:
+ rc = util.execWithRedirect(script.interp, ["/tmp/%s" % os.path.basename(path)],
+ stdout=fp,
+ root=scriptRoot)
+
+ if rc != 0:
+ script_log.error("Error code %s running the kickstart script at line %s",
+ rc, script.lineno)
+
+ return rc, messages
diff --git a/pyanaconda/core/kickstart/specification.py b/pyanaconda/core/kickstart/specification.py
index e261ec2a01d..78a5eba94ab 100644
--- a/pyanaconda/core/kickstart/specification.py
+++ b/pyanaconda/core/kickstart/specification.py
@@ -91,6 +91,8 @@ def __init__(self, specification):
for name, data in specification.addons.items():
self.registerAddonData(name, data)
+ self.scripts = []
+
def registerSectionData(self, name, data):
"""Register data used by a section."""
obj = data()
diff --git a/pyanaconda/errors.py b/pyanaconda/errors.py
index ded2804ab35..c8fda1e389a 100644
--- a/pyanaconda/errors.py
+++ b/pyanaconda/errors.py
@@ -15,7 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
+from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.i18n import _, C_
from pyanaconda.flags import flags
from pyanaconda.modules.common.errors.installation import BootloaderInstallationError, \
@@ -24,6 +24,7 @@
from pyanaconda.modules.common.errors.payload import SourceSetupError
from pyanaconda.modules.common.errors.storage import UnusableStorageError
+log = get_module_logger(__name__)
class ScriptError(Exception):
def __init__(self, lineno, details):
diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py
index 87a43fc8a3c..0d19fcf7424 100644
--- a/pyanaconda/exception.py
+++ b/pyanaconda/exception.py
@@ -33,7 +33,6 @@
from meh.dump import ReverseExceptionDump
from meh.handler import ExceptionHandler
-from pyanaconda import kickstart
from pyanaconda.core import util
from pyanaconda.core.async_utils import run_in_loop
from pyanaconda.core.configuration.anaconda import conf
@@ -41,12 +40,16 @@
from pyanaconda.core.product import get_product_is_final_release
from pyanaconda.errors import NonInteractiveError
from pyanaconda.core.i18n import _
+from pyanaconda.modules.common.constants.objects import SCRIPTS
+from pyanaconda.modules.common.constants.services import RUNTIME
from pyanaconda.modules.common.errors.storage import UnusableStorageError
from pyanaconda.core.threads import thread_manager
+from pyanaconda.modules.common.task import sync_run_task
from pyanaconda.ui.communication import hubQ
from simpleline import App
from simpleline.event_loop.signals import ExceptionSignal
+from pykickstart.constants import KS_SCRIPT_ONERROR, KS_SCRIPT_TRACEBACK
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
@@ -236,13 +239,20 @@ def postWriteHook(self, dump_info):
util.ipmi_report(IPMI_FAILED)
- def _run_kickstart_scripts(self, dump_info):
+ def _run_kickstart_scripts(self, _dump_info):
"""Run the %traceback and %onerror kickstart scripts."""
- anaconda = dump_info.object
+ scripts_proxy = RUNTIME.get_proxy(SCRIPTS)
+ # OnError script call
+ onerror_task_path = scripts_proxy.RunScriptsWithTask(KS_SCRIPT_ONERROR)
+ onerror_task_proxy = RUNTIME.get_proxy(onerror_task_path)
+
+ # Traceback script call
+ traceback_task_path = scripts_proxy.RunScriptsWithTask(KS_SCRIPT_TRACEBACK)
+ traceback_task_proxy = RUNTIME.get_proxy(traceback_task_path)
try:
- util.runOnErrorScripts(anaconda.ksdata.scripts)
- kickstart.runTracebackScripts(anaconda.ksdata.scripts)
+ sync_run_task(onerror_task_proxy)
+ sync_run_task(traceback_task_proxy)
# pylint: disable=bare-except
# ruff: noqa: E722
except:
diff --git a/pyanaconda/installation.py b/pyanaconda/installation.py
index 77dcfa49d7c..6b492c5ae01 100644
--- a/pyanaconda/installation.py
+++ b/pyanaconda/installation.py
@@ -23,9 +23,9 @@
CATEGORY_BOOTLOADER, CATEGORY_ENVIRONMENT, CATEGORY_STORAGE, CATEGORY_SOFTWARE
from pyanaconda.modules.boss.install_manager.installation_category_interface \
import CategoryReportTaskInterface
-from pyanaconda.modules.common.constants.objects import BOOTLOADER, SNAPSHOT, FIREWALL
+from pyanaconda.modules.common.constants.objects import BOOTLOADER, SNAPSHOT, FIREWALL, SCRIPTS
from pyanaconda.modules.common.constants.services import STORAGE, USERS, SERVICES, NETWORK, \
- SECURITY, LOCALIZATION, TIMEZONE, BOSS, SUBSCRIPTION
+ SECURITY, LOCALIZATION, TIMEZONE, BOSS, SUBSCRIPTION, RUNTIME
from pyanaconda.modules.common.task import sync_run_task, Task as InstallationTask
from pyanaconda.modules.common.util import is_module_available
from pyanaconda import flags
@@ -35,10 +35,10 @@
from pyanaconda import network
from pyanaconda.core.i18n import _
from pyanaconda.core.threads import thread_manager
-from pyanaconda.kickstart import runPostScripts, runPreInstallScripts
from pyanaconda.kexec import setup_kexec
from pyanaconda.installation_tasks import Task, TaskQueue, DBusTask
-from pykickstart.constants import SNAPSHOT_WHEN_POST_INSTALL
+from pykickstart.constants import (SNAPSHOT_WHEN_POST_INSTALL, KS_SCRIPT_PREINSTALL,
+ KS_SCRIPT_POST)
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
@@ -286,11 +286,10 @@ def run_generate_initramfs():
_("Running post-installation scripts"),
CATEGORY_SYSTEM
)
- post_scripts.append(Task(
- "Run post installation scripts",
- runPostScripts,
- (ksdata.scripts,)
- ))
+ scripts_proxy = RUNTIME.get_proxy(SCRIPTS)
+ post_scripts.append_dbus_tasks(RUNTIME, [
+ scripts_proxy.RunScriptsWithTask(KS_SCRIPT_POST)
+ ])
configuration_queue.append(post_scripts)
boss_proxy = BOSS.get_proxy()
@@ -376,10 +375,10 @@ def _prepare_installation(self, payload, ksdata):
_("Running pre-installation scripts"),
CATEGORY_ENVIRONMENT
)
- pre_install_scripts.append(Task(
- "Run %pre-install scripts",
- runPreInstallScripts, (ksdata.scripts,)
- ))
+ scripts_proxy = RUNTIME.get_proxy(SCRIPTS)
+ pre_install_scripts.append_dbus_tasks(RUNTIME, [
+ scripts_proxy.RunScriptsWithTask(KS_SCRIPT_PREINSTALL)
+ ])
installation_queue.append(pre_install_scripts)
# Do various pre-installation tasks
diff --git a/pyanaconda/installation_tasks.py b/pyanaconda/installation_tasks.py
index affb3e3ca94..e2fa71f01f8 100644
--- a/pyanaconda/installation_tasks.py
+++ b/pyanaconda/installation_tasks.py
@@ -17,11 +17,17 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
+import sys
import time
from dasbus.error import DBusError
+
+from pyanaconda.core import util
+from pyanaconda.core.constants import IPMI_ABORTED
from pyanaconda.core.signal import Signal
from pyanaconda.errors import errorHandler, ERROR_RAISE
+from pyanaconda.modules.common.errors.runtime import ScriptError
+from pyanaconda.flags import flags
from pyanaconda.modules.common.task import sync_run_task
from pyanaconda.anaconda_loggers import get_module_logger
@@ -295,8 +301,14 @@ def _run(self):
sync_run_task(self._task_proxy)
except DBusError as e:
# Handle a remote error.
- if errorHandler.cb(e) == ERROR_RAISE:
- raise
+ if isinstance(e, ScriptError):
+ flags.ksprompt = True
+ errorHandler.cb(e)
+ util.ipmi_report(IPMI_ABORTED)
+ sys.exit(0)
+ else:
+ if errorHandler.cb(e) == ERROR_RAISE:
+ raise
finally:
# Disconnect from the signal.
self._task_proxy.ProgressChanged.disconnect()
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index 06848739335..a39c1f63e95 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -19,10 +19,7 @@
#
import glob
-import os
-import os.path
import sys
-import tempfile
import time
import warnings
@@ -30,19 +27,18 @@
from pyanaconda.anaconda_loggers import get_module_logger, get_stdout_logger
from pyanaconda.core import util
-from pyanaconda.core.path import open_with_perm
-from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.kickstart.scripts import run_script
from pyanaconda.core.kickstart import VERSION, commands as COMMANDS
from pyanaconda.core.kickstart.specification import KickstartSpecification
from pyanaconda.core.constants import IPMI_ABORTED
-from pyanaconda.errors import ScriptError, errorHandler
-from pyanaconda.flags import flags
from pyanaconda.core.i18n import _
+from pyanaconda.errors import errorHandler, ScriptError
+from pyanaconda.flags import flags
from pyanaconda.modules.common.constants.services import BOSS
from pyanaconda.modules.common.structures.kickstart import KickstartReport
from pykickstart.base import KickstartCommand, RemovedCommand
-from pykickstart.constants import KS_SCRIPT_POST, KS_SCRIPT_PRE, KS_SCRIPT_TRACEBACK, KS_SCRIPT_PREINSTALL
+from pykickstart.constants import KS_SCRIPT_PRE
from pykickstart.errors import KickstartError, KickstartParseWarning
from pykickstart.ko import KickstartObject
from pykickstart.parser import KickstartParser
@@ -72,63 +68,22 @@ def check_kickstart_error():
class AnacondaKSScript(KSScript):
- """ Execute a kickstart script
-
- This will write the script to a file named /tmp/ks-script- before
- execution.
- Output is logged by the program logger, the path specified by --log
- or to /tmp/ks-script-\\*.log
- """
def run(self, chroot):
- """ Run the kickstart script
- @param chroot directory path to chroot into before execution
- """
- if self.inChroot:
- scriptRoot = chroot
- else:
- scriptRoot = "/"
-
- (fd, path) = tempfile.mkstemp("", "ks-script-", scriptRoot + "/tmp")
-
- os.write(fd, self.script.encode("utf-8"))
- os.close(fd)
- os.chmod(path, 0o700)
-
- # Always log stdout/stderr from scripts. Using --log just lets you
- # pick where it goes. The script will also be logged to program.log
- # because of execWithRedirect.
- if self.logfile:
- if self.inChroot:
- messages = "%s/%s" % (scriptRoot, self.logfile)
- else:
- messages = self.logfile
-
- d = os.path.dirname(messages)
- if not os.path.exists(d):
- os.makedirs(d)
- else:
- # Always log outside the chroot, we copy those logs into the
- # chroot later.
- messages = "/tmp/%s.log" % os.path.basename(path)
-
- with open_with_perm(messages, "w", 0o600) as fp:
- rc = util.execWithRedirect(self.interp, ["/tmp/%s" % os.path.basename(path)],
- stdout=fp,
- root=scriptRoot)
-
- if rc != 0:
- script_log.error("Error code %s running the kickstart script at line %s", rc, self.lineno)
- if self.errorOnFail:
- err = ""
- with open(messages, "r") as fp:
- err = "".join(fp.readlines())
+ rc, log_file = run_script(self, chroot)
+ if self.errorOnFail and rc != 0:
+ err = ""
+ with open(log_file, "r") as fp:
+ err = "".join(fp.readlines())
+ if self.type == KS_SCRIPT_PRE:
# Show error dialog even for non-interactive
flags.ksprompt = True
errorHandler.cb(ScriptError(self.lineno, err))
util.ipmi_report(IPMI_ABORTED)
sys.exit(0)
+ else:
+ return self.lineno, err
class AnacondaInternalScript(AnacondaKSScript):
@@ -386,19 +341,6 @@ def appendPostScripts(ksdata):
ksparser = AnacondaKSParser(ksdata, scriptClass=AnacondaInternalScript)
ksparser.readKickstartFromString(scripts, reset=False)
-
-def runPostScripts(scripts):
- postScripts = [s for s in scripts if s.type == KS_SCRIPT_POST]
-
- if len(postScripts) == 0:
- return
-
- script_log.info("Running kickstart %%post script(s)")
- for script in postScripts:
- script.run(conf.target.system_root)
- script_log.info("All kickstart %%post script(s) have been run")
-
-
def runPreScripts(scripts):
preScripts = [s for s in scripts if s.type == KS_SCRIPT_PRE]
@@ -412,24 +354,3 @@ def runPreScripts(scripts):
script.run("/")
script_log.info("All kickstart %%pre script(s) have been run")
-
-
-def runPreInstallScripts(scripts):
- preInstallScripts = [s for s in scripts if s.type == KS_SCRIPT_PREINSTALL]
-
- if len(preInstallScripts) == 0:
- return
-
- script_log.info("Running kickstart %%pre-install script(s)")
-
- for script in preInstallScripts:
- script.run("/")
-
- script_log.info("All kickstart %%pre-install script(s) have been run")
-
-
-def runTracebackScripts(scripts):
- script_log.info("Running kickstart %%traceback script(s)")
- for script in filter(lambda s: s.type == KS_SCRIPT_TRACEBACK, scripts):
- script.run("/")
- script_log.info("All kickstart %%traceback script(s) have been run")
diff --git a/pyanaconda/modules/common/constants/objects.py b/pyanaconda/modules/common/constants/objects.py
index 2deb5890dcb..6eb81effe9a 100644
--- a/pyanaconda/modules/common/constants/objects.py
+++ b/pyanaconda/modules/common/constants/objects.py
@@ -21,7 +21,12 @@
PARTITIONING_NAMESPACE, DEVICE_TREE_NAMESPACE, \
RHSM_NAMESPACE, RUNTIME_NAMESPACE
-# Boss objects.
+# Runtime objects
+
+SCRIPTS = DBusObjectIdentifier(
+ namespace=RUNTIME_NAMESPACE,
+ basename="Scripts"
+)
USER_INTERFACE = DBusObjectIdentifier(
namespace=RUNTIME_NAMESPACE,
diff --git a/pyanaconda/modules/common/errors/__init__.py b/pyanaconda/modules/common/errors/__init__.py
index 190371470aa..ab19025a8e2 100644
--- a/pyanaconda/modules/common/errors/__init__.py
+++ b/pyanaconda/modules/common/errors/__init__.py
@@ -27,6 +27,7 @@ def register_errors():
installation,
module,
payload,
+ runtime,
storage,
task
)
diff --git a/pyanaconda/modules/common/errors/runtime.py b/pyanaconda/modules/common/errors/runtime.py
new file mode 100644
index 00000000000..3c64c758aca
--- /dev/null
+++ b/pyanaconda/modules/common/errors/runtime.py
@@ -0,0 +1,31 @@
+#
+# DBus errors related to the runtime modules.
+#
+# Copyright (C) 2024 Red Hat, Inc. 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, see .
+#
+from pyanaconda.core.dbus import dbus_error
+from pyanaconda.modules.common.constants.namespaces import RUNTIME_NAMESPACE
+from pyanaconda.modules.common.errors.general import AnacondaError
+
+
+@dbus_error("ScriptError", namespace=RUNTIME_NAMESPACE)
+class ScriptError(AnacondaError):
+ """error raised during kickstart script processing."""
+ def __init__(self, message):
+ super().__init__(message)
+ lines = message.split("\n", 1)
+ self.lineno = lines[0]
+ self.details = lines[1].strip() if len(lines) > 1 else ""
diff --git a/pyanaconda/modules/runtime/Makefile.am b/pyanaconda/modules/runtime/Makefile.am
index 5523d644dd3..5bbcfeec7c5 100644
--- a/pyanaconda/modules/runtime/Makefile.am
+++ b/pyanaconda/modules/runtime/Makefile.am
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
-SUBDIRS = dracut_commands user_interface
+SUBDIRS = dracut_commands scripts user_interface
pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME)
runtimedir = $(pkgpyexecdir)/modules/runtime
diff --git a/pyanaconda/modules/runtime/kickstart.py b/pyanaconda/modules/runtime/kickstart.py
index 2f7627820dd..e18b0174768 100644
--- a/pyanaconda/modules/runtime/kickstart.py
+++ b/pyanaconda/modules/runtime/kickstart.py
@@ -17,7 +17,11 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
+from pykickstart.sections import (PreInstallScriptSection,
+ PostScriptSection, TracebackScriptSection, OnErrorScriptSection)
+
from pyanaconda.core.kickstart import KickstartSpecification, commands as COMMANDS
+from pyanaconda.kickstart import AnacondaKSScript
class RuntimeKickstartSpecification(KickstartSpecification):
@@ -41,3 +45,10 @@ class RuntimeKickstartSpecification(KickstartSpecification):
"DriverDiskData": COMMANDS.DriverDiskData,
"SshPwData": COMMANDS.SshPwData,
}
+
+ sections = {
+ "pre-install": lambda handler: PreInstallScriptSection(handler, dataObj=AnacondaKSScript),
+ "post": lambda handler: PostScriptSection(handler, dataObj=AnacondaKSScript),
+ "onerror": lambda handler: OnErrorScriptSection(handler, dataObj=AnacondaKSScript),
+ "traceback": lambda handler: TracebackScriptSection(handler, dataObj=AnacondaKSScript),
+ }
diff --git a/pyanaconda/modules/runtime/runtime.py b/pyanaconda/modules/runtime/runtime.py
index ca1d59d1987..bcd9562d6a9 100755
--- a/pyanaconda/modules/runtime/runtime.py
+++ b/pyanaconda/modules/runtime/runtime.py
@@ -25,6 +25,7 @@
from pyanaconda.modules.runtime.runtime_interface import RuntimeInterface
from pyanaconda.modules.runtime.kickstart import RuntimeKickstartSpecification
from pyanaconda.modules.runtime.dracut_commands import DracutCommandsModule
+from pyanaconda.modules.runtime.scripts import ScriptsModule
from pyanaconda.modules.runtime.user_interface import UIModule
from pyanaconda.modules.common.base import KickstartService
from pyanaconda.modules.common.constants.services import RUNTIME
@@ -51,6 +52,9 @@ def __init__(self):
self._dracut_module = DracutCommandsModule()
self._modules.add_module(self._dracut_module)
+ self._scripts_module = ScriptsModule()
+ self._modules.add_module(self._scripts_module)
+
self._ui_module = UIModule()
self._modules.add_module(self._ui_module)
diff --git a/pyanaconda/modules/runtime/scripts/Makefile.am b/pyanaconda/modules/runtime/scripts/Makefile.am
new file mode 100644
index 00000000000..e4ef93d357c
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/Makefile.am
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2024 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME)
+scriptsdir = $(pkgpyexecdir)/modules/runtime/scripts
+dist_scripts_DATA = $(wildcard $(srcdir)/*.py)
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/pyanaconda/modules/runtime/scripts/__init__.py b/pyanaconda/modules/runtime/scripts/__init__.py
new file mode 100644
index 00000000000..4f3b772bba6
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/__init__.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2024 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+from pyanaconda.modules.runtime.scripts.scripts import ScriptsModule
+
+__all__ = ["ScriptsModule"]
diff --git a/pyanaconda/modules/runtime/scripts/runtime.py b/pyanaconda/modules/runtime/scripts/runtime.py
new file mode 100644
index 00000000000..c0df36a5f47
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/runtime.py
@@ -0,0 +1,58 @@
+#
+# Copyright (C) 2024 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.modules.common.errors.runtime import ScriptError
+from pyanaconda.modules.common.task import Task
+from pyanaconda.anaconda_loggers import get_module_logger
+
+from pykickstart.constants import KS_SCRIPT_POST
+
+log = get_module_logger(__name__)
+
+
+class RunScriptsTask(Task):
+ """Task for running scripts."""
+
+ def __init__(self, script_type, scripts):
+ """Create a new task.
+
+ :param script_type: type of scripts to be run
+ :type script_type: int
+ :param scripts: list of scripts
+ :type scripts: list(Script)
+ """
+ super().__init__()
+ self._script_type = script_type
+ self._scripts = scripts
+
+ @property
+ def name(self):
+ return "Run scripts"
+
+ def run(self):
+ """Execute the task."""
+ for script in self._scripts:
+ if script.type == self._script_type:
+ if script.type == KS_SCRIPT_POST:
+ result = script.run(conf.target.system_root)
+ else:
+ result = script.run("/")
+ if result:
+ lineno, err = result
+ error_message = f"{lineno}\n\n{err.strip()}"
+ raise ScriptError(error_message)
diff --git a/pyanaconda/modules/runtime/scripts/scripts.py b/pyanaconda/modules/runtime/scripts/scripts.py
new file mode 100644
index 00000000000..03e13678493
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/scripts.py
@@ -0,0 +1,56 @@
+#
+# The user interface module
+#
+# Copyright (C) 2024 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Set up the modules logger.
+from pyanaconda.anaconda_loggers import get_module_logger
+from pyanaconda.core.dbus import DBus
+from pyanaconda.modules.common.base import KickstartBaseModule
+from pyanaconda.modules.common.constants.objects import SCRIPTS
+from pyanaconda.modules.runtime.scripts.runtime import RunScriptsTask
+from pyanaconda.modules.runtime.scripts.scripts_interface import ScriptsInterface
+
+log = get_module_logger(__name__)
+
+__all__ = ["ScriptsModule"]
+
+
+class ScriptsModule(KickstartBaseModule):
+ def __init__(self):
+ super().__init__()
+ self._scripts = []
+
+ def publish(self):
+ """Publish the module."""
+ DBus.publish_object(SCRIPTS.object_path, ScriptsInterface(self))
+
+ def process_kickstart(self, data):
+ log.debug("Process_kickstart %s", data.scripts)
+ self._scripts = data.scripts
+
+ def setup_kickstart(self, data):
+ log.debug("Setup_kickstart %s", data)
+ data.scripts = self._scripts
+
+ def run_scripts_with_task(self, script_type):
+ """Run all scripts of given type sequentially."""
+ log.debug("Running %s scripts with task", script_type)
+ return RunScriptsTask(
+ script_type=script_type,
+ scripts=self._scripts
+ )
diff --git a/pyanaconda/modules/runtime/scripts/scripts_interface.py b/pyanaconda/modules/runtime/scripts/scripts_interface.py
new file mode 100644
index 00000000000..87cc013eb26
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/scripts_interface.py
@@ -0,0 +1,49 @@
+#
+# DBus interface for the scripts module
+#
+# Copyright (C) 2021 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+from dasbus.server.interface import dbus_interface
+from dasbus.typing import * # pylint: disable=wildcard-import
+
+from pyanaconda.modules.common.base import KickstartModuleInterfaceTemplate
+from pyanaconda.modules.common.constants.objects import SCRIPTS
+from pyanaconda.modules.common.containers import TaskContainer
+
+__all__ = ["ScriptsInterface"]
+
+
+@dbus_interface(SCRIPTS.interface_name)
+class ScriptsInterface(KickstartModuleInterfaceTemplate):
+ """DBus interface for the scripts module."""
+
+ def RunScriptsWithTask(self, script_type: Int) -> ObjPath:
+ """Run all scripts of given type sequentially with task.
+
+ The types of scripts:
+ kickstart scripts:
+ KS_SCRIPT_PRE = 0
+ KS_SCRIPT_POST = 1
+ KS_SCRIPT_TRACEBACK = 2
+ KS_SCRIPT_PREINSTALL = 3
+ KS_SCRIPT_ONERROR = 4
+ :param script_type: Type of scripts to be run.
+ :return: a DBus path of the task
+ """
+ return TaskContainer.to_object_path(
+ self.implementation.run_scripts_with_task(script_type)
+ )
diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py
index a3db405d504..8beac72188b 100644
--- a/pyanaconda/rescue.py
+++ b/pyanaconda/rescue.py
@@ -16,10 +16,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+import sys
+
from pyanaconda.core import util
from pyanaconda.core.configuration.anaconda import conf
-from pyanaconda.core.constants import ANACONDA_CLEANUP, THREAD_STORAGE, QUIT_MESSAGE
-from pyanaconda.modules.common.constants.objects import DEVICE_TREE
+from pyanaconda.core.constants import ANACONDA_CLEANUP, THREAD_STORAGE, QUIT_MESSAGE, IPMI_ABORTED
+from pyanaconda.errors import errorHandler
+from pyanaconda.modules.common.errors.runtime import ScriptError
+from pyanaconda.modules.common.constants.objects import DEVICE_TREE, SCRIPTS
from pyanaconda.modules.common.constants.services import STORAGE, RUNTIME
from pyanaconda.modules.common.errors.storage import MountFilesystemError
from pyanaconda.modules.common.structures.rescue import RescueData
@@ -28,11 +32,11 @@
from pyanaconda.core.threads import thread_manager
from pyanaconda.flags import flags
from pyanaconda.core.i18n import _, N_
-from pyanaconda.kickstart import runPostScripts
from pyanaconda.ui.tui import tui_quit_callback
from pyanaconda.ui.tui.spokes import NormalTUISpoke
from pykickstart.constants import KS_REBOOT, KS_SHUTDOWN
+from pykickstart.constants import KS_SCRIPT_POST
from simpleline import App
from simpleline.render.adv_widgets import YesNoDialog, PasswordDialog
@@ -244,7 +248,17 @@ def mount_root(self, root):
# run %post if we've mounted everything
if not self.ro and self._scripts:
- runPostScripts(self._scripts)
+ scripts_proxy = RUNTIME.get_proxy(SCRIPTS)
+ post_task_path = scripts_proxy.RunScriptsWithTask(KS_SCRIPT_POST)
+ post_task_proxy = RUNTIME.get_proxy(post_task_path)
+ try:
+ sync_run_task(post_task_proxy)
+ except ScriptError as e:
+ flags.ksprompt = True
+ errorHandler.cb(e)
+ util.ipmi_report(IPMI_ABORTED)
+ sys.exit(0)
+ pass
self.status = RescueModeStatus.MOUNTED
return True