From 04d5e2944e1b1a85a945910995cb8df00d983a7c Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Mon, 6 Oct 2025 16:54:46 +0200 Subject: [PATCH] feat(dracut): introduce additional hooks locations Dracut init supports running custom hooks which are placed to /var/lib/dracut/hooks/. The location was previously changed to /var as the place needs to be writeable as some hooks are created and removed in runtime. The current location, however, may come inconvenient in some scenarios when the user wants to extend initramfs with custom scripts. In particular, systemd allows extending initrd with 'sysext' and 'confext' mechanism. This comes handy for extending e.g. UKI's initramfs without the need to rebuild (and re-sign) the UKI. The problem is that 'sysext' can only be used to extend /usr and /opt and 'confext/ can only extend /etc. Both services make the location read-only and thus can't be used for the main dracut hooks location even if we move it somewhere. Add additional locations where users can put dracut hooks: - /lib/dracut/hooks -- this location is supposed to be used for distro-specific static hooks. - /etc/dracut/hooks -- this location can be used by users for locally created hooks. - /var/lib/dracut/hooks -- the default location which is supposed to be used by dracut modules. This location is always writeable so modules can place and remove hooks from there in runtime. The existing '$hookdir' variable keeps pointing at this place. Dracut also has support for /var/lib/dracut/hooks/initqueue/work flag and in theory, it does not have to be in the hooks directory as it is not a hook. The location, however, is documented and it is not entirely clear if it would make sense to add support for /lib/dracut/hooks/initqueue/work and /etc/dracut/hooks/initqueue/work as well: these locations can (and probably should) be read-only so creating/removing flag there is hard. Keep the status quo and only support '$hookdir/initqueue/work' for now. Closes: https://github.com/dracut-ng/dracut-ng/issues/1618 Signed-off-by: Vitaly Kuznetsov --- .../modules/ROOT/pages/developer/modules.adoc | 27 ++++++++++++------- dracut.sh | 5 ++-- man/dracut.modules.7.adoc | 11 ++++++++ .../77dracut-systemd/dracut-cmdline.service | 2 ++ .../77dracut-systemd/dracut-mount.service | 2 ++ .../77dracut-systemd/dracut-pre-mount.service | 2 ++ .../77dracut-systemd/dracut-pre-pivot.service | 4 +++ .../dracut-pre-trigger.service | 2 ++ .../77dracut-systemd/dracut-pre-udev.service | 2 ++ modules.d/80base/dracut-lib.sh | 15 +++++++++-- modules.d/80base/module-setup.sh | 3 --- modules.d/86shutdown/module-setup.sh | 3 --- 12 files changed, 57 insertions(+), 21 deletions(-) diff --git a/doc_site/modules/ROOT/pages/developer/modules.adoc b/doc_site/modules/ROOT/pages/developer/modules.adoc index 54e21967a..f5637c336 100644 --- a/doc_site/modules/ROOT/pages/developer/modules.adoc +++ b/doc_site/modules/ROOT/pages/developer/modules.adoc @@ -123,18 +123,25 @@ You are encouraged to provide a `README` that describes what the module is for. == Hooks -init has the following hook points to inject scripts: - -`/var/lib/dracut/hooks/cmdline/*.sh`:: +Dracut init looks for custom hook scripts in the following locations +(`HOOKDIR` below): `/var/lib/dracut/hooks`, `/etc/dracut/hooks`, and +`/lib/dracut/hooks`. The intended use of these locations is: standard, +distribution shipped scripts are put to `/lib/dracut/hooks`; +`/etc/dracut/hooks` is used as a local override; dracut modules create +(and remove) scripts in runtime in `/var/lib/dracut/hooks` (and `$hookdir` +variable is provided). The following hook points to inject scripts are +currently supported: + +`HOOKDIR/cmdline/*.sh`:: scripts for command line parsing -`/var/lib/dracut/hooks/pre-udev/*.sh`:: +`HOOKDIR/pre-udev/*.sh`:: scripts to run before udev is started -`/var/lib/dracut/hooks/pre-trigger/*.sh`:: +`HOOKDIR/pre-trigger/*.sh`:: scripts to run before the main udev trigger is pulled -`/var/lib/dracut/hooks/initqueue/*.sh`:: +`HOOKDIR/initqueue/*.sh`:: runs in parallel to the udev trigger + Udev events can add scripts here with `/sbin/initqueue`. @@ -151,23 +158,23 @@ a timeout. + Scripts can remove themselves from the initqueue by `rm $job`. -`/var/lib/dracut/hooks/pre-mount/*.sh`:: +`HOOKDIR/pre-mount/*.sh`:: scripts to run before the root filesystem is mounted + Network filesystems like NFS that do not use device files are an exception. Root can be mounted already at this point. -`/var/lib/dracut/hooks/mount/*.sh`:: +`HOOKDIR/mount/*.sh`:: scripts to mount the root filesystem + If the udev queue is empty and no root device is found or no root filesystem was mounted, the user will be dropped to a shell after a timeout. -`/var/lib/dracut/hooks/pre-pivot/*.sh`:: +`HOOKDIR/pre-pivot/*.sh`:: scripts to run before latter initramfs cleanups -`/var/lib/dracut/hooks/cleanup/*.sh`:: +`HOOKDIR/cleanup/*.sh`:: scripts to run before the real init is executed and the initramfs disappears + diff --git a/dracut.sh b/dracut.sh index 6a1963b9d..e1aafac7b 100755 --- a/dracut.sh +++ b/dracut.sh @@ -1994,11 +1994,13 @@ dracut_kernel_post() { } if [[ "$(ln --help)" == *--relative* ]]; then + # shellcheck disable=SC2329 ln_r() { local dstdir="${dstdir:-"$initdir"}" ln -sfnr "${dstdir}/$1" "${dstdir}/$2" } else + # shellcheck disable=SC2329 ln_r() { local dstdir="${dstdir:-"$initdir"}" local _source=$1 @@ -2701,9 +2703,6 @@ if [[ $kernel_only != yes ]]; then # shellcheck disable=SC2068 ((${#install_optional_items[@]} > 0)) && inst_multiple -o ${install_optional_items[@]} - # symlink to old hooks location for compatibility - ln_r /var/lib/dracut/hooks /lib/dracut/hooks - for _d in $hookdirs; do # shellcheck disable=SC2174 mkdir -m 0755 -p "${initdir}/var/lib/dracut/hooks/$_d" diff --git a/man/dracut.modules.7.adoc b/man/dracut.modules.7.adoc index 62aaca5c6..af5fc0393 100644 --- a/man/dracut.modules.7.adoc +++ b/man/dracut.modules.7.adoc @@ -47,6 +47,17 @@ These hooks are plain directories containing shell scripts ending with ".sh", which are sourced by init. Common used functions are in _dracut-lib.sh_, which can be sourced by any script. +dracut looks for custom hook scripts in subdirectories (cmdline, pre-udev, +pre-trigger, initqueue, pre-mount, mount, pre-pivot, cleanup) of the following +locations: _/var/lib/dracut/hooks_, _/etc/dracut/hooks_, and +_/lib/dracut/hooks_. The intended use of these locations is: standard, +distribution shipped scripts are put to _/lib/dracut/hooks_; _/etc/dracut/hooks_ +is used as a local override; dracut modules create (and remove) scripts in +runtime in _/var/lib/dracut/hooks_. If a hook with the same name exists in +multiple locations simultaneously, the most privileged location +(_/var/lib/dracut/hooks_, then _/etc/dracut/hooks_, and then +_/lib/dracut/hooks_) wins and only one instance of the hook is executed. + === Hook: cmdline The _cmdline_ hook is a place to insert scripts to parse the kernel command line diff --git a/modules.d/77dracut-systemd/dracut-cmdline.service b/modules.d/77dracut-systemd/dracut-cmdline.service index d4fbc695a..d991619e8 100644 --- a/modules.d/77dracut-systemd/dracut-cmdline.service +++ b/modules.d/77dracut-systemd/dracut-cmdline.service @@ -10,6 +10,8 @@ Wants=systemd-journald.socket ConditionPathExists=/usr/lib/initrd-release ConditionPathExistsGlob=|/etc/cmdline.d/*.conf ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/cmdline +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/cmdline +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/cmdline ConditionKernelCommandLine=|rd.break=cmdline Conflicts=shutdown.target emergency.target diff --git a/modules.d/77dracut-systemd/dracut-mount.service b/modules.d/77dracut-systemd/dracut-mount.service index 884c699bf..dd5e2cb21 100644 --- a/modules.d/77dracut-systemd/dracut-mount.service +++ b/modules.d/77dracut-systemd/dracut-mount.service @@ -7,6 +7,8 @@ After=initrd-root-fs.target initrd-parse-etc.service After=dracut-initqueue.service dracut-pre-mount.service ConditionPathExists=/usr/lib/initrd-release ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/mount +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/mount +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/mount ConditionKernelCommandLine=|rd.break=mount DefaultDependencies=no Conflicts=shutdown.target emergency.target diff --git a/modules.d/77dracut-systemd/dracut-pre-mount.service b/modules.d/77dracut-systemd/dracut-pre-mount.service index 0e19cac03..3c76ff5e6 100644 --- a/modules.d/77dracut-systemd/dracut-pre-mount.service +++ b/modules.d/77dracut-systemd/dracut-pre-mount.service @@ -8,6 +8,8 @@ Before=initrd-root-fs.target sysroot.mount systemd-fsck-root.service After=dracut-initqueue.service cryptsetup.target ConditionPathExists=/usr/lib/initrd-release ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/pre-mount +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/pre-mount +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-mount ConditionKernelCommandLine=|rd.break=pre-mount Conflicts=shutdown.target emergency.target diff --git a/modules.d/77dracut-systemd/dracut-pre-pivot.service b/modules.d/77dracut-systemd/dracut-pre-pivot.service index 6c786341e..8194ee254 100644 --- a/modules.d/77dracut-systemd/dracut-pre-pivot.service +++ b/modules.d/77dracut-systemd/dracut-pre-pivot.service @@ -11,7 +11,11 @@ Wants=remote-fs.target After=remote-fs.target ConditionPathExists=/usr/lib/initrd-release ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/pre-pivot +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/pre-pivot +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-pivot ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/cleanup +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/cleanup +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/cleanup ConditionKernelCommandLine=|rd.break=pre-pivot ConditionKernelCommandLine=|rd.break=cleanup ConditionKernelCommandLine=|rd.break diff --git a/modules.d/77dracut-systemd/dracut-pre-trigger.service b/modules.d/77dracut-systemd/dracut-pre-trigger.service index 555fcefb3..b99837b31 100644 --- a/modules.d/77dracut-systemd/dracut-pre-trigger.service +++ b/modules.d/77dracut-systemd/dracut-pre-trigger.service @@ -9,6 +9,8 @@ After=dracut-pre-udev.service systemd-udevd.service systemd-tmpfiles-setup-dev.s Wants=dracut-pre-udev.service systemd-udevd.service ConditionPathExists=/usr/lib/initrd-release ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/pre-trigger +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/pre-trigger +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-trigger ConditionKernelCommandLine=|rd.break=pre-trigger Conflicts=shutdown.target emergency.target diff --git a/modules.d/77dracut-systemd/dracut-pre-udev.service b/modules.d/77dracut-systemd/dracut-pre-udev.service index f3c10214a..8a803697c 100644 --- a/modules.d/77dracut-systemd/dracut-pre-udev.service +++ b/modules.d/77dracut-systemd/dracut-pre-udev.service @@ -9,6 +9,8 @@ After=dracut-cmdline.service Wants=dracut-cmdline.service ConditionPathExists=/usr/lib/initrd-release ConditionDirectoryNotEmpty=|/var/lib/dracut/hooks/pre-udev +ConditionDirectoryNotEmpty=|/etc/dracut/hooks/pre-udev +ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-udev ConditionKernelCommandLine=|rd.break=pre-udev ConditionKernelCommandLine=|rd.driver.blacklist ConditionKernelCommandLine=|rd.driver.pre diff --git a/modules.d/80base/dracut-lib.sh b/modules.d/80base/dracut-lib.sh index 851090a48..2710347bf 100755 --- a/modules.d/80base/dracut-lib.sh +++ b/modules.d/80base/dracut-lib.sh @@ -373,8 +373,19 @@ list_hooks() { [ -z "$pattern" ] && pattern="*.sh" local hook - for hook in "$hookdir/$dir/"$pattern; do - [ -f "$hook" ] && echo "$hook" + # It is allowed to override hooks by creating a file with the same name + # in a directory which has higher priority. '/var/lib/dracut/hooks' gets top + # priority, '/etc/dracut/hooks' comes after and '/lib/dracut/hooks' is the + # least priviliged location. + for hook in "/var/lib/dracut/hooks/$dir/"$pattern; do + [ -f "$hook" ] && echo "$hook" + done + for hook in "/etc/dracut/hooks/$dir/"$pattern; do + [ -f "$hook" ] && [ ! -f "/var/lib/dracut/hooks/$dir/${hook##*/}" ] && echo "$hook" + done + for hook in "/lib/dracut/hooks/$dir/"$pattern; do + [ -f "$hook" ] && [ ! -f "/var/lib/dracut/hooks/$dir/${hook##*/}" ] \ + && [ ! -f "/etc/dracut/hooks/$dir/${hook##*/}" ] && echo "$hook" done } diff --git a/modules.d/80base/module-setup.sh b/modules.d/80base/module-setup.sh index 91797d858..dfe81ec3a 100755 --- a/modules.d/80base/module-setup.sh +++ b/modules.d/80base/module-setup.sh @@ -88,9 +88,6 @@ install() { mkdir -m 0755 -p "${initdir}"/lib/dracut mkdir -m 0755 -p "${initdir}"/var/lib/dracut/hooks - # symlink to old hooks location for compatibility - ln_r /var/lib/dracut/hooks /lib/dracut/hooks - mkdir -p "${initdir}"/tmp inst_simple "$moddir/dracut-lib.sh" "/lib/dracut-lib.sh" diff --git a/modules.d/86shutdown/module-setup.sh b/modules.d/86shutdown/module-setup.sh index d9ce47328..846cf7e7c 100755 --- a/modules.d/86shutdown/module-setup.sh +++ b/modules.d/86shutdown/module-setup.sh @@ -14,9 +14,6 @@ install() { inst "$moddir/shutdown.sh" "$prefix/shutdown" mkdir -m 0755 -p "${initdir}"/var/lib/dracut/hooks - # symlink to old hooks location for compatibility - ln_r /var/lib/dracut/hooks /lib/dracut/hooks - for _d in $hookdirs shutdown shutdown-emergency; do mkdir -m 0755 -p "${initdir}"/var/lib/dracut/hooks/"$_d" done -- 2.47.3