SYSTEMD.GENERATOR(7)
๐ systemd.generator โ systemd unit generators ๐
okay bestie, sit down, hydrate, because systemd.generator(7) is literally the origin story of every "where did this unit file even come from??" moment of your life. let's get into it ๐ซถ
โจ SYNOPSIS โจ
/path/to/generator normal-dir [early-dir] [late-dir]
/run/systemd/system-generators/*
/etc/systemd/system-generators/*
/usr/local/lib/systemd/system-generators/*
/usr/lib/systemd/system-generators/*
/run/systemd/user-generators/*
/etc/systemd/user-generators/*
/usr/local/lib/systemd/user-generators/*
/usr/lib/systemd/user-generators/*
๐ธ DESCRIPTION ๐ธ
so. generators. they're these lil executables, right, and they live in /usr/lib/systemd/system-generators/ plus all those other directories we just listed up there. systemd(1) runs them very early at bootup and also at configuration reload time โ and the key thing, the whole thing, is that this happens before unit files are loaded. before. like, they show up to the party before the party even exists. ๐
their whole job? take configuration and execution-context parameters that are not native to the service manager and turn them into dynamically generated unit files, symlinks, or unit file drop-ins โ so that all of that can extend the unit file hierarchy that the service manager loads and operates on afterwards. it's giving "translator but for units". iykyk.
systemd calls every generator with three directory paths that are meant for generator output. inside those three dirs, generators are allowed to dynamically generate:
- unit files (regular ones, instances, and templates)
- unit file
.d/drop-ins - symbolic links to unit files โ to add extra dependencies, create aliases, or instantiate existing templates
those directories get folded into the unit load path, so all the generated config can extend or override existing definitions. iconic behaviour, no notes.
for tests though? a generator may get called with just one argument, and in that case it should assume all three paths are the same. don't be weird about it.
generators that the system manager runs are invoked inside a sandbox โ one with a private writable /tmp/ directory, where most of the file system is read-only except for the generator output directories. cosy. contained. safe. ๐ง
priority, bestie โ it's all about priority
the directory paths for generator output differ by priority, and here's the ranking:
.../generator.earlyhas priority higher than the admin configuration in/etc/.../generatorhas lower priority than/etc/but higher than the vendor configuration in/usr/.../generator.latehas priority lower than all other configuration
see the next section for the details, and also go read the discussion of unit load paths and unit overriding in systemd.unit(5). it's a whole thing, we love it.
where they load from
generators load from a set of paths that got determined during compilation โ that's the list from the synopsis, bestie. system and user generators come from directories whose names end in system-generators/ and user-generators/, respectively. ๐
generators found in directories listed earlier override the ones with the same name in directories lower in the list [1]. want to make a generator just... not run? a symlink to /dev/null or an empty file masks it. gone. not invited. ๐
โ ๏ธ okay important plot twist: the order of the two highest-priority directories is reversed with respect to the unit load path. so generators in/run/overwrite those in/etc/. yes really. write it on your hand.
after you install new generators or update the config, you may run systemctl daemon-reload. what does that do? it deletes the previous configuration created by generators, re-runs all the generators, and makes systemd reload units from disk. the full glow-up. โจ see systemctl(1) for more.
๐ฆ OUTPUT DIRECTORIES ๐ฆ
generators get invoked with three arguments: paths to directories where they can drop their generated unit files or symlinks. by default those paths are runtime directories that are already in systemd's search path โ but a generator may be called with different paths for debugging purposes. and if only one argument is provided? the generator should just use that same directory as all three output paths. keep it simple. ๐
normal-dir
in normal use this is /run/systemd/generator for the system generators, and $XDG_RUNTIME_DIR/systemd/generator for the user generators. unit files placed here take precedence over vendor unit configuration โ but not over native user/administrator unit configuration.
early-dir
in normal use this is /run/systemd/generator.early for the system generators, and $XDG_RUNTIME_DIR/systemd/generator.early for the user generators. unit files placed here override unit files in /usr/, /run/ and /etc/. which means โ say it with me โ unit files placed here take precedence over all normal configuration, both vendor and user/administrator. the girlboss directory.
late-dir
in normal use this is /run/systemd/generator.late for the system generators, and $XDG_RUNTIME_DIR/systemd/generator.late for the user generators. this one's for extending the unit file tree without overriding anything else. any native configuration files supplied by the vendor or by user/administrator take precedence. it's the polite one.
๐จ Note: generators must not write to other locations or otherwise make changes to system state. generator output is only supposed to last until the nextdaemon-reloadordaemon-reexec; if the generator gets replaced or masked, its effects should just vanish. no lingering. no haunting. periodt.
๐ ENVIRONMENT ๐
when it invokes generator executables, the service manager sets a bunch of environment variables. they carry info about the generator's execution context, so you can conditionally branch your generator for specific environments. here's the full roster, don't skip any, i would never do that to you:
$SYSTEMD_SCOPEโ if the generator is invoked from the system service manager this is set to"system"; if it's invoked from the per-user service manager it's set to"user". Added in version 251.$SYSTEMD_IN_INITRDโ if the generator runs as part of an initrd this is set to"1". if it runs from the regular host (i.e. after the transition from initrd to host) it's set to"0". this variable is only set for system generators. Added in version 251.$SYSTEMD_SOFT_REBOOTS_COUNTโ if the system has soft-rebooted, this is set to the count of soft-reboots. only set for system generators. Added in version 257.$SYSTEMD_FIRST_BOOTโ if this boot-up cycle counts as a "first boot" this is"1"; if it's a subsequent, regular boot it's"0". for the details go look atConditionFirstBoot=insystemd.unit(5). only set for system generators. Added in version 251.$SYSTEMD_VIRTUALIZATIONโ if the service manager runs in a virtualized environment, this gets set to a pair of strings separated by a colon. the first string is either"vm"or"container"โ that's the category of virtualization. the second string identifies the implementation of the virtualization technology. if no virtualization is detected, this variable just won't be set. this data is identical to whatsystemd-detect-virt(1)detects and reports, same vocabulary of virtualization implementation identifiers and all. Added in version 251.$SYSTEMD_ARCHITECTUREโ set to a short identifier of the reported architecture of the system. for details about the defined values, see the docs forConditionArchitecture=insystemd.unit(5). Added in version 251.$CREDENTIALS_DIRECTORY,$ENCRYPTED_CREDENTIALS_DIRECTORYโ if set, these refer to the directory system credentials have been placed in. credentials passed into the system in plaintext form land in$CREDENTIALS_DIRECTORY, and the ones passed in encrypted form land in$ENCRYPTED_CREDENTIALS_DIRECTORY. use thesystemd-creds(1)command to automatically decrypt/authenticate credentials passed in, if you need to. specifically, use thesystemd-creds --system catcommand. Added in version 254.$SYSTEMD_CONFIDENTIAL_VIRTUALIZATIONโ if the service manager runs in a confidential virtualized environment, this is set to a string identifying the confidential virtualization hardware technology. if no confidential virtualization is detected, the variable won't be set. this data is identical to whatsystemd-detect-virt(1)detects and reports, same vocabulary of confidential virtualization technology identifiers. Added in version 254.
more coming, bestie โ don't go anywhere ๐
๐ NOTES ABOUT WRITING GENERATORS ๐
Okay bestie, this is the part where we talk โจcraftโจ. Read it all, no skipping, iykyk.
- All generators are executed in parallel. That means all executables are started at the very same time and need to be able to cope with this parallelism. Like, everyone shows up to the party at 00:00 sharp. No queue, no waiting your turn. ๐
- Generators are run very early at boot and cannot rely on any external services. They may not talk to any other process. That includes simple things such as logging to
syslog(3),nss(5)calls such asgetpwnam(), or IPC calls to systemd itself (this means: nosystemctl(1))! Non-essential file systems like/var/and/home/are mounted after generators have run. Generators can however rely on the most basic kernel functionality to be available, as well as mounted/sys/,/proc/,/dev/,/usr/and/run/file systems. ๐ซถ - Units written by generators are removed when the configuration is reloaded. That means the lifetime of the generated units is closely bound to the reload cycles of systemd itself. Blink and they're gone, fr fr.
- Generators should only be used to generate unit files,
.d/*.confdrop-ins for them and symlinks to them, not any other kind of non-unit related configuration. Due to the lifecycle logic mentioned above, generators are not a good fit to generate dynamic configuration for other services. If you need to generate dynamic configuration for other services, do so in normal services you order before the service in question.
Note that using the StandardInputData=/StandardInputText= settings of service unit files (see systemd.exec(5)), it is possible to make arbitrary input data (including daemon-specific configuration) part of the unit definitions, which often might be sufficient to embed data or configuration for other programs into unit files in a native fashion. Slay, native embedding. โจ
- Since
syslog(3)is not available (see above ๐), log messages have to be written to/dev/kmsginstead. - The generator should always include its own name in a comment at the top of the generated file, so that the user can easily figure out which component created or amended a particular unit. Sign your work, bestie. ๐
The SourcePath= directive should be used in generated files to specify the source configuration file they are generated from. This makes things more easily understood by the user and also has the benefit that systemd can warn the user about configuration files that changed on disk but have not been read yet by systemd. The SourcePath= value does not have to be a file in a physical filesystem. For example, in the common case of the generator looking at the kernel command line, SourcePath=/proc/cmdline should be used. ๐
- Generators may write out dynamic unit files or just hook unit files into other units with the usual
.wants/or.requires/symlinks. Often, it is nicer to simply instantiate a template unit file from/usr/with a generator instead of writing out entirely dynamic unit files. Of course, this works only if a single parameter is to be used. One parameter, one vibe check. ๐ฆ - If you are careful, you can implement generators in shell scripts. We do recommend C code however, since generators are executed synchronously and hence delay the entire boot if they are slow. And nobody wants a slow boot, that's giving main character energy in the worst way. ๐
- Regarding overriding semantics: there are two rules we try to follow when thinking about the overriding semantics:
- User configuration should override vendor configuration. This (mostly) means that stuff from
/etc/should override stuff from/usr/. - Native configuration should override non-native configuration. This (mostly) means that stuff you generate should never override native unit files for the same purpose.
- User configuration should override vendor configuration. This (mostly) means that stuff from
Of these two rules the first rule is probably the more important one and breaks the second one sometimes. Hence, when deciding whether to use argv[1], argv[2], or argv[3], your default choice should probably be argv[1]. Lowkey just remember: argv[1]. ๐
- Instead of heading off now and writing all kind of generators for legacy configuration file formats, please think twice! It is often a better idea to just deprecate old stuff instead of keeping it artificially alive. Let it rest, bestie. ๐ง
๐ EXAMPLES ๐
Example 1. systemd-fstab-generator
systemd-fstab-generator(8) converts /etc/fstab into native mount units. It uses argv[1] as location to place the generated unit files in order to allow the user to override /etc/fstab with their own native unit files, but also to ensure that /etc/fstab overrides any vendor default from /usr/. Everybody gets their moment. ๐
After editing /etc/fstab, the user should invoke systemctl daemon-reload. This will re-run all generators and cause systemd to reload units from disk. To actually mount new directories added to fstab, systemctl start /path/to/mountpoint or systemctl start local-fs.target may be used.
Example 2. systemd-system-update-generator
systemd-system-update-generator(8) temporarily redirects default.target to system-update.target, if a system update is scheduled. Since this needs to override the default user configuration for default.target, it uses argv[2]. For details about this logic, see systemd.offline-updates(7). Look at it, using argv[2] because it genuinely has to. Respect. โจ
Example 3. Debugging a generator
dir=$(mktemp -d)
SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/system-generators/systemd-fstab-generator \
"$dir" "$dir" "$dir"
find $dir
๐ SEE ALSO ๐
systemd(1), systemd-cryptsetup-generator(8), systemd-debug-generator(8), systemd-fstab-generator(8), fstab(5), systemd-getty-generator(8), systemd-gpt-auto-generator(8), systemd-hibernate-resume-generator(8), systemd-system-update-generator(8), systemd-xdg-autostart-generator(8), systemd.unit(5), systemctl(1), systemd.environment-generator(7)
๐ฃ NOTES ๐ฃ
- ๐ฃ๐ฅ๐งจ๐ฅ๐ฅ๐ฃ Please note that those configuration files must be available at all times. If
/usr/local/is a separate partition, it may not be available during early boot, and must not be used for configuration. Don't do it, I'm begging. ๐
and that's a wrap on systemd.generator(7), bestie!! ๐ This whole thing came from systemd 262.1 โ the SYSTEMD.GENERATOR(7) page, your fave, no cap. Now go write a generator that signs its own name and logs to /dev/kmsg like a good little unit factory. Obsessed with you. ๐โจ๐ซถ๐
๐ฆ where this page comes from
- arch rolling: systemd 262.1-1 ยท LGPL-2.1-or-later AND CC0-1.0 AND GPL-2.0-or-later AND MIT-0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms