systemd-environment-d-generator gets one extra variable to the services your user manager starts, without touching a login shell or a system file.
This guide sets a variable, verifies it over D-Bus, and removes it cleanly if it turns out wrong. Give it ten minutes. You need a Linux system on systemd 255 or nearby, with an active user manager. None of this needs root, because it only touches your own ~/.config directory.
The generator is not something you run day to day; systemd runs it for you. On this machine, the installed package is systemd 255.4-1ubuntu8.17 and the user generator lives at /usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator. Confirm both the executable and the user manager before changing anything:
$ systemd --version
systemd 255 (255.4-1ubuntu8.17)
$ command -v systemctl
/usr/bin/systemctl
$ systemctl --user show-environment | sed -n '1,12p'
HOME=/home/alice
LANG=en_US.UTF-8
LOGNAME=alice
Your version, home directory and existing variables will all differ from this example. If systemctl --user says it cannot connect to a bus, fix the user session first. Do not reach for sudo systemctl --user: root has its own, entirely separate user manager.
Checkpoint: you have a live user manager, and you know this feature affects services it starts, not every process in every login shell you have open.
Make the user configuration directory and a numbered file. This example adds a directory to PATH and defines a value a user service can read:
$ mkdir -p "$HOME/.config/environment.d"
$ umask 077
$ printf '%s\n' \
'PROJECT_ROOT=/home/alice/projects/example' \
'PATH=/home/alice/.local/bin:$PATH' \
> "$HOME/.config/environment.d/60-project.conf"
$ sed -n '1,10p' "$HOME/.config/environment.d/60-project.conf"
PROJECT_ROOT=/home/alice/projects/example
PATH=/home/alice/.local/bin:$PATH
Replace /home/alice and /home/alice/projects/example with real paths of your own. The file is plain KEY=VALUE assignments; blank lines and lines starting with # are ignored. The right-hand side can reference an earlier variable with $NAME or ${NAME}, but this is not shell syntax: no command substitution, no shell quoting rules, no arbitrary operators.
The two-digit prefix is just an ordering aid, since files are sorted by name across all the configuration directories. A later filename wins if it sets the same variable again. Your own overrides live in ~/.config/environment.d; vendor files belong under /usr/lib/environment.d, and administrator overrides under /etc/environment.d.
Ask the user manager to reload its configuration:
$ systemctl --user daemon-reload
This reruns the environment generators. It is not a service restart, but any service started after this point gets the new environment; anything already running keeps whatever it started with. Only restart a particular service once you have checked that doing so is actually safe:
$ systemctl --user restart example.service
That restart is service-disrupting. Use a real unit name, and skip this command entirely if you only need the value for services that have not started yet. Do not restart something important purely to make a shell show you a variable.
Checkpoint: the reload finished without an error. If it failed, look for a misspelled variable name, a bad assignment, or a path copied literally from this example.
Read back what the user manager is actually exporting:
$ systemctl --user show-environment | grep -E '^(PATH|PROJECT_ROOT)='
PATH=/home/alice/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PROJECT_ROOT=/home/alice/projects/example
Order is not significant, and your original PATH will differ. What matters is that PROJECT_ROOT has the value you meant and the new directory appears ahead of the old ones. A unit started by this manager inherits these; a terminal you already had open, usually does not.
For a more direct check, run the generator by hand:
$ /usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator | sed -n '1,8p'
QT_ACCESSIBILITY=1
PATH=/home/alice/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PROJECT_ROOT=/home/alice/projects/example
That output is a diagnostic view, not a substitute for daemon-reload. The program takes no arguments, and some values, accessibility settings among them, are host-specific, so do not expect an identical list on your machine.
Keep the file until you have finished verifying. To remove it, delete only the file you created and reload:
$ rm "$HOME/.config/environment.d/60-project.conf"
$ systemctl --user daemon-reload
$ systemctl --user show-environment | grep -E '^(PATH|PROJECT_ROOT)='
Recovery: do not run that rm if the file already existed before you started. Restore its previous contents from backup or version control instead, then reload. Removing a vendor file is a different operation: mask its exact filename with a symlink to /dev/null under /etc/environment.d, and remove the mask to bring the vendor file back. Plan and record that one; it is an administrator-level change.
These settings reach services started by the systemd user instance, including anything that launches a user shell inside some graphical sessions. They do not retroactively touch the manager itself, your current shell, unrelated system services, or every SSH login you have open. Anything started outside the user manager gets its environment from somewhere else entirely.
A later file can override an earlier one, and a same-named file in a higher-priority directory replaces a lower one completely. When debugging, list ~/.config/environment.d, /etc/environment.d, /run/environment.d, /usr/local/lib/environment.d and /usr/lib/environment.d, and compare filenames across all five. Keep secrets out of these files: the whole point is that the values get exported to multiple user services.
environment.d file contains only the intended assignments.systemctl --user daemon-reload completed successfully.systemctl --user show-environment shows the expected values.