diff --git a/configure.ac b/configure.ac
index ed92982e1a42..54ea0e1faa74 100644
--- a/configure.ac
+++ b/configure.ac
@@ -180,6 +180,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 000000000000..68b0327496d2
--- /dev/null
+++ b/pyanaconda/core/kickstart/scripts.py
@@ -0,0 +1,79 @@
+#
+# Commond 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
+ # TODORV return
+ @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 e261ec2a01de..78a5eba94ab5 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/installation.py b/pyanaconda/installation.py
index 77dcfa49d7c1..9922701c6500 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
@@ -38,7 +38,8 @@
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__)
@@ -291,6 +292,10 @@ def run_generate_initramfs():
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()
@@ -380,6 +385,10 @@ def _prepare_installation(self, payload, ksdata):
"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/kickstart.py b/pyanaconda/kickstart.py
index 068487393355..e1cf678b13fe 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -30,6 +30,7 @@
from pyanaconda.anaconda_loggers import get_module_logger, get_stdout_logger
from pyanaconda.core import util
+from pyanaconda.core.kickstart.scripts import run_script
from pyanaconda.core.path import open_with_perm
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.kickstart import VERSION, commands as COMMANDS
@@ -72,63 +73,19 @@ 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())
-
- # Show error dialog even for non-interactive
- flags.ksprompt = True
-
- errorHandler.cb(ScriptError(self.lineno, err))
- util.ipmi_report(IPMI_ABORTED)
- sys.exit(0)
+ 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())
+
+ # Show error dialog even for non-interactive
+ flags.ksprompt = True
+
+ errorHandler.cb(ScriptError(self.lineno, err))
+ util.ipmi_report(IPMI_ABORTED)
+ sys.exit(0)
class AnacondaInternalScript(AnacondaKSScript):
diff --git a/pyanaconda/modules/common/constants/objects.py b/pyanaconda/modules/common/constants/objects.py
index 2deb5890dcbe..6eb81effe9a4 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/runtime/Makefile.am b/pyanaconda/modules/runtime/Makefile.am
index 5523d644dd39..5bbcfeec7c51 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 2f7627820ddf..ee02a52fea6e 100644
--- a/pyanaconda/modules/runtime/kickstart.py
+++ b/pyanaconda/modules/runtime/kickstart.py
@@ -17,6 +17,9 @@
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
+from pykickstart.parser import Script
+from pykickstart.sections import PreInstallScriptSection, PostScriptSection
+
from pyanaconda.core.kickstart import KickstartSpecification, commands as COMMANDS
@@ -41,3 +44,8 @@ class RuntimeKickstartSpecification(KickstartSpecification):
"DriverDiskData": COMMANDS.DriverDiskData,
"SshPwData": COMMANDS.SshPwData,
}
+
+ sections = {
+ "pre-install": lambda handler: PreInstallScriptSection(handler, dataObj=Script),
+ "post": lambda handler: PostScriptSection(handler, dataObj=Script),
+ }
diff --git a/pyanaconda/modules/runtime/runtime.py b/pyanaconda/modules/runtime/runtime.py
index ca1d59d19877..bcd9562d6a91 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 000000000000..c46bd8662fdf
--- /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
\ No newline at end of file
diff --git a/pyanaconda/modules/runtime/scripts/__init__.py b/pyanaconda/modules/runtime/scripts/__init__.py
new file mode 100644
index 000000000000..4f3b772bba62
--- /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/scripts.py b/pyanaconda/modules/runtime/scripts/scripts.py
new file mode 100644
index 000000000000..1d1e72c04005
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/scripts.py
@@ -0,0 +1,98 @@
+#
+# 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.
+import sys
+
+from pyanaconda.anaconda_loggers import get_module_logger
+from pyanaconda.core import util
+from pyanaconda.core.constants import IPMI_ABORTED
+from pyanaconda.core.dbus import DBus
+from pyanaconda.core.kickstart.scripts import run_script
+from pyanaconda.errors import errorHandler, ScriptError
+from pyanaconda.flags import flags
+from pyanaconda.modules.common.base import KickstartBaseModule
+from pyanaconda.modules.common.constants.objects import SCRIPTS
+from pyanaconda.modules.common.task import Task
+from pyanaconda.modules.runtime.scripts.scripts_interface import ScriptsInterface
+
+log = get_module_logger(__name__)
+
+__all__ = ["ScriptsModule"]
+
+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):
+ for script in self._scripts:
+ if script.type == self._script_type:
+ rc, log_file = run_script(script, "/")
+ if script.errorOnFail and rc != 0:
+ err = ""
+ with open(log_file, "r") as fp:
+ err = "".join(fp.readlines())
+
+ # Show error dialog even for non-interactive
+ flags.ksprompt = True
+
+ errorHandler.cb(ScriptError(script.lineno, err))
+ util.ipmi_report(IPMI_ABORTED)
+ sys.exit(0)
+
+
+
+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("DDDDD process_kickstart %s", data.scripts)
+ self._scripts = data.scripts
+
+ def setup_kickstart(self, data):
+ log.debug("DDDDD 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("DDDDD 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 000000000000..8761843fefa8
--- /dev/null
+++ b/pyanaconda/modules/runtime/scripts/scripts_interface.py
@@ -0,0 +1,48 @@
+#
+# 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)
+ )