switch-to-configuration-ng: Better handling of socket-activated units

Previously, if any unit had a socket associated with it, stc-ng
counted it as "socket-activated", meaning that the unit would get
stopped and the socket get restarted. That can wreak havoc on units
like systemd-udevd and systemd-networkd.

Instead, let units set the new flag notSocketActivated, which sets a
boolean on the unit indicating to stc-ng that the unit wants to be
treated like any other non-socket-activated unit instead. That will
stop/start or restart these units on upgrades, without unnecessarily
tearing down any machinery that the system needs to run.

Changed files
+28 -2
nixos
lib
modules
services
hardware
system
pkgs
by-name
sw
switch-to-configuration-ng
src
src
+2
nixos/lib/systemd-lib.nix
···
'' else "")
+ optionalString (def ? stopIfChanged && !def.stopIfChanged) ''
X-StopIfChanged=false
+
'' + optionalString (def ? notSocketActivated && def.notSocketActivated) ''
+
X-NotSocketActivated=true
'' + attrsToSection def.serviceConfig);
};
+12
nixos/lib/systemd-unit-options.nix
···
'';
};
+
notSocketActivated = mkOption {
+
type = types.bool;
+
default = false;
+
description = ''
+
If set, a changed unit is never assumed to be
+
socket-activated on configuration activation, even if
+
it might have associated socket units. Instead, the unit
+
will be restarted (or stopped/started) as if it had no
+
associated sockets.
+
'';
+
};
+
startAt = mkOption {
type = with types; either str (listOf str);
default = [];
+3 -2
nixos/modules/services/hardware/udev.nix
···
fi
'';
-
systemd.services.systemd-udevd =
-
{ restartTriggers = [ config.environment.etc."udev/rules.d".source ];
+
systemd.services.systemd-udevd = {
+
restartTriggers = [ config.environment.etc."udev/rules.d".source ];
+
notSocketActivated = true;
};
};
+1
nixos/modules/system/boot/networkd.nix
···
config.environment.etc."systemd/networkd.conf".source
];
aliases = [ "dbus-org.freedesktop.network1.service" ];
+
notSocketActivated = true;
};
networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) {
+10
pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs
···
// `stopIfChanged = true` is ignored, switch-to-configuration will handle `restartIfChanged =
// false` and `reloadIfChanged = true`. This is the same as specifying a restart trigger in the
// NixOS module.
+
// In addition, switch-to-configuration will handle notSocketActivated=true to disable treatment
+
// of units as "socket-activated" even though they might have any associated sockets.
//
// The reload file asks this program to reload a unit. This is the same as specifying a reload
// trigger in the NixOS module and can be ignored if the unit is restarted in this activation.
···
} else {
// If this unit is socket-activated, then stop the socket unit(s) as well, and
// restart the socket(s) instead of the service.
+
// We count as "socket-activated" any unit that doesn't declare itself not so
+
// via X-NotSocketActivated, that has any associated .socket units.
let mut socket_activated = false;
if unit.ends_with(".service") {
let mut sockets = if let Some(Some(Some(sockets))) = new_unit_info.map(|info| {
···
}
}
}
+
}
+
if parse_systemd_bool(new_unit_info, "Service", "X-NotSocketActivated", false) {
+
// If the unit explicitly opts out of socket
+
// activation, restart it as if it weren't (but do
+
// restart its sockets, that's fine):
+
socket_activated = false;
}
// If the unit is not socket-activated, record that this unit needs to be started