Hello,
So, I’m trying to monitor some paths with systemd to trigger systemd services that do things, so I made a systemd template like so:
systemd.services."radicle-mirror@" = {
description = "Mirror Radicle repository %I to remote git host";
after = [ "radicle.service" ];
wants = [ "radicle.service" ];
script = ''
set -euo pipefail
RID="%I"
MIRROR_FILE="/var/lib/radicle/mirrors.json"
URL="$(jq -r '.["'"$RID"'"] // empty' "$MIRROR_FILE" 2>/dev/null || true)"
[ -z "$URL" ] && exit 0
cd "/var/lib/radicle/storage/$RID"
export GIT_SSH_COMMAND="ssh -i /var/lib/radicle/ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o IdentitiesOnly=yes"
echo "Mirroring $RID → $URL"
git push --force --prune "$URL" refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* 2>&1
'';
serviceConfig = {
Type = "oneshot";
User = "radicle";
Group = "radicle";
NoNewPrivileges = true;
};
};
systemd.paths."radicle-mirror@" = {
description = "Watch Radicle repository %I for changes";
after = [ "radicle.service" ];
wants = [ "radicle.service" ];
pathConfig = {
PathChanged = [
"/var/lib/radicle/storage/%I/packed-refs"
"/var/lib/radicle/storage/%I/HEAD"
];
Unit = "radicle-mirror@.service";
};
wantedBy = [ "multi-user.target" ];
};
however, if I attempt to enable the radicle-mirrors@.path, I’m greet with an error:
Enabling radicle-mirror@z39RJHSHs166S5kr8Qstj6kd1LFah.path...
Failed to enable unit: File /etc/systemd/system/radicle-mirror@z39RJHSHs166S5kr8Qstj6kd1LFah.path: Read-only file system
if my remote gets rebooted, I’m short of options to start those systemd path units.
is there a way to work around this without having to modify the NixOS configuration of my remote computer?
You’re not meant to use systemctl to enable units on NixOS, it has to be done declaratively.
What you should do is create a systemd service that sets overrideStrategy appropriately, since it’s a template unit.
1 Like
okay, but, what would be the effect of making that adjustment? would systemctl enable template-unit@someid.path work as expected?
overrideStrategy has nothing to do with it, and unfortunately your wantedBy isn’t doing what you want because all that does is set up a multi-user.target.wants/foo@.path -> ../foo@.path with no instancing.
What you probably should do is just systemd.targets.multi-user.wants = [ "radicle-mirror@instance-name.path" ];
I went with this solution: declaring a systemd unit service that will start all my .path units upon boot:
systemd.services."radicle-mirror-starter" = {
description = "Start radicle-mirror path units for all configured mirrors";
before = [ "radicle.service" ];
wantedBy = [ "multi-user.target" ];
script = ''
MIRROR_FILE="/var/lib/radicle/mirrors.json"
if [ ! -f "$MIRROR_FILE" ]; then
exit 0
fi
for RID in $(jq -r 'keys[]' "$MIRROR_FILE"); do
echo "Starting radicle-mirror@$RID.path"
systemctl start "radicle-mirror@$RID.path" || true
done
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "radicle";
Group = "radicle";
};
};
That is a lot more complicated than what I suggested and requires a whole extra state file 
the extra file (mirrors.json) is part of the design of what I’m trying to do, so it is not that extra!
If that file exists anyway for the purpose then you might just use a systemd generator.
systemd.generators.my-generator = (pkgs.writeShellApplication {
name = "my-generator";
runtimeInputs = [ jq ];
text = ''
MIRROR_FILE="/var/lib/radicle/mirrors.json"
if [ ! -f "$MIRROR_FILE" ]; then
exit 0
fi
mkdir -p $1/multi-user.target.wants
for RID in $(jq -r 'keys[]' "$MIRROR_FILE"); do
ln -s ../radicle-mirror@.path $1/multi-user.target.wants/radicle-mirror@$RID.path
done
'';
}) + "/bin/my-generator";
The reason I like this better is that it avoids having more runtime units in the graph cluttering things up. It just generates the dependencies that are needed on daemon-reload.