Two integration patterns, four example units for the dummy service.
| file | how the program gets its secrets |
|---|---|
dummy.before.service |
plaintext file on disk (the thing we're replacing) |
dummy.wrap.service |
wrapped; {config} → materialised path + [[env]] vars |
dummy.env.service |
wrapped; config path via $APP_CONFIG (no CLI flag) |
ss-fetcher run execs your program as a child, injects everything, and
shreds the temp files on exit. Simple, but systemd supervises
ss-fetcher, not your program.
| file | how the program gets its secrets |
|---|---|
dummy.prepare.service |
ExecStartPre=prepare + EnvironmentFile= + ExecStopPost=cleanup |
Here systemd runs your program directly (it stays the parent — MainPID,
cgroup, signals, sd_notify all point at your process). ss-fetcher prepare writes the config file(s) and an EnvironmentFile into
%t/ss-fetcher/<id>/ before ExecStart; cleanup removes them after.
Reach for this when wrapping causes problems.
%t is the unit runtime dir ($XDG_RUNTIME_DIR for user units) — the same
location prepare writes to. Print the exact paths with:
ss-fetcher paths
ss-fetcher paths --config prod.yaml # just one path (scripting)
ss-fetcher paths --env-filecd /opt/dummy
ss-fetcher save prod.yaml # store the config
ss-fetcher set-env O365_CLIENT_ID --value ... # store env secrets
rm prod.yaml # remove plaintextcp dummy.prepare.service ~/.config/systemd/user/dummy.service # or dummy.wrap.service
systemctl --user daemon-reload
systemctl --user start dummy.service
journalctl --user -u dummy.service -eThe tool and the unit must agree on one directory. The tool writes to
<base>/ss-fetcher/<id>; the unit references %t/ss-fetcher/<id>. They line up
when <base> equals %t, and the clean way to guarantee that is to base
everything on $XDG_RUNTIME_DIR:
- The tool's
runtime_dirdefaults to$XDG_RUNTIME_DIR— so leaveruntime_dirunset in.secretrc(the dummy example does). Don't hardcode/run/user/{uid}: it only matches by luck for user units and is plain wrong for system units (and may not even exist for a daemonUser=). - User services (
systemctl --user): systemd always setsXDG_RUNTIME_DIR=/run/user/<uid>, which is%t. Nothing else to do — this is the recommended setup and it works at boot too (loginctl enable-linger). - Let systemd own the directory with
RuntimeDirectory=ss-fetcher/<id>: it creates%t/ss-fetcher/<id>at0700, owned by the unit's user, beforeExecStartPre, and removes it on stop — soExecStopPost=… cleanupbecomes optional (kept in the example as an explicit shred). systemd also exports$RUNTIME_DIRECTORYwith that path, and ss-fetcher auto-detects it:prepare/cleanup/pathsthen write to exactly that directory, so alignment is automatic — theRuntimeDirectory=name need not matchid, and you don't have to reason aboutruntime_dir/%tat all. (Manual runs, where$RUNTIME_DIRECTORYis unset, fall back to the computed path below.) - System services (not
--user):XDG_RUNTIME_DIRis unset and%tis/run, so addEnvironment=XDG_RUNTIME_DIR=%tto pin the tool's base to/run(the units carry this as a commented line).
In short: unset runtime_dir + RuntimeDirectory=ss-fetcher/<id> (+
Environment=XDG_RUNTIME_DIR=%t for system units). Use ss-fetcher paths to
print the exact resolved paths.
EnvironmentFileordering: systemd reads it when spawningExecStart, which happens afterExecStartPre, sopreparegenerating it first works. Keep the leading-(EnvironmentFile=-...) so a missing file (no[[env]]vars) isn't fatal.EnvironmentFilevalues should be single-line.- PATH: systemd user units have a minimal
PATH. Ifss-fetcherisn't found, install it onto the userPATH(e.g.pipx→~/.local/bin) or use the absolute path to the console script. - Backend must be reachable when the unit runs:
- secret-service: a Secret Service provider must be on the session D-Bus and
its store unlocked (graphical session /
gnome-keyring, or KeePassXC with its DB open). - vault:
$VAULT_TOKEN(and$VAULT_ADDR) must be in the unit's environment — supply via a privateEnvironmentFile=or systemd credentials. Never put the token in the committed.secretrc.
- secret-service: a Secret Service provider must be on the session D-Bus and
its store unlocked (graphical session /
%t/%hexpand to the runtime dir / home inside units — handy so paths aren't user-specific.