nixos/systemd: allow using writeShellApplication for systemd unit scripts

This commit is contained in:
r-vdp 2023-11-30 10:54:09 +01:00
parent 509b099fd4
commit 2b224f0e3c
No known key found for this signature in database
4 changed files with 72 additions and 13 deletions

View File

@ -62,6 +62,15 @@
- The OCaml-based Xen Store can now be configured using [`virtualisation.xen.store.settings`](#opt-virtualisation.xen.store.settings). - The OCaml-based Xen Store can now be configured using [`virtualisation.xen.store.settings`](#opt-virtualisation.xen.store.settings).
- The `virtualisation.xen.bridge` options have been deprecated in this release cycle. Users who need network bridges are encouraged to set up their own networking configurations. - The `virtualisation.xen.bridge` options have been deprecated in this release cycle. Users who need network bridges are encouraged to set up their own networking configurations.
- A new option [`systemd.enableStrictShellChecks`](#opt-systemd.enableStrictShellChecks) has been added. When enabled, all systemd scripts generated by NixOS will
be checked with [shellcheck](https://www.shellcheck.net) and any errors or warnings will cause the build to fail.
This affects all scripts that have been created through the `script`, `reload`, `preStart`, `postStart`, `preStop` and `postStop` options for systemd services.
This does not affect commandlines passed directly to `ExecStart`, `ExecReload`, `ExecStartPre`, `ExecStartPost`, `ExecStop` or `ExecStopPost`.
It therefore also does not affect systemd units that are coming from packages and that are not defined through the NixOS config.
This option is disabled by default, and although some services have already been fixed, it is still likely that you will encounter build failures when enabling this.
We encourage people to enable this option when they are willing and able to submit fixes for potential build failures to nixpkgs.
The option can also be enabled or disabled for individual services using the `enableStrictShellChecks` option on the service itself, which will take precedence over the global setting.
## New Modules {#sec-release-24.11-new-modules} ## New Modules {#sec-release-24.11-new-modules}
- [TaskChampion Sync-Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server), a [Taskwarrior 3](https://taskwarrior.org/docs/upgrade-3/) sync server, replacing Taskwarrior 2's sync server named [`taskserver`](https://github.com/GothenburgBitFactory/taskserver). - [TaskChampion Sync-Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server), a [Taskwarrior 3](https://taskwarrior.org/docs/upgrade-3/) sync server, replacing Taskwarrior 2's sync server named [`taskserver`](https://github.com/GothenburgBitFactory/taskserver).

View File

@ -386,18 +386,27 @@ in rec {
''} ''}
''; # */ ''; # */
makeJobScript = name: text: makeJobScript = { name, text, enableStrictShellChecks }:
let let
scriptName = replaceStrings [ "\\" "@" ] [ "-" "_" ] (shellEscape name); scriptName = replaceStrings [ "\\" "@" ] [ "-" "_" ] (shellEscape name);
out = (pkgs.writeShellScriptBin scriptName '' out = (
if ! enableStrictShellChecks then
pkgs.writeShellScriptBin scriptName ''
set -e set -e
${text} ${text}
'').overrideAttrs (_: { ''
else
pkgs.writeShellApplication {
name = scriptName;
inherit text;
}
).overrideAttrs (_: {
# The derivation name is different from the script file name # The derivation name is different from the script file name
# to keep the script file name short to avoid cluttering logs. # to keep the script file name short to avoid cluttering logs.
name = "unit-script-${scriptName}"; name = "unit-script-${scriptName}";
}); });
in "${out}/bin/${scriptName}"; in lib.getExe out;
unitConfig = { config, name, options, ... }: { unitConfig = { config, name, options, ... }: {
config = { config = {
@ -448,10 +457,16 @@ in rec {
}; };
}; };
serviceConfig = { name, config, ... }: { serviceConfig =
let
nixosConfig = config;
in
{ name, lib, config, ... }: {
config = { config = {
name = "${name}.service"; name = "${name}.service";
environment.PATH = mkIf (config.path != []) "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}"; environment.PATH = mkIf (config.path != []) "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
enableStrictShellChecks = lib.mkOptionDefault nixosConfig.systemd.enableStrictShellChecks;
}; };
}; };

View File

@ -17,6 +17,7 @@ let
concatMap concatMap
filterOverrides filterOverrides
isList isList
literalExpression
mergeEqualOption mergeEqualOption
mkIf mkIf
mkMerge mkMerge
@ -357,6 +358,14 @@ in rec {
''; '';
}; };
enableStrictShellChecks = mkOption {
type = types.bool;
description = "Enable running shellcheck on the generated scripts for this unit.";
# The default gets set in systemd-lib.nix because we don't have access to
# the full NixOS config here.
defaultText = literalExpression "config.systemd.enableStrictShellChecks";
};
script = mkOption { script = mkOption {
type = types.lines; type = types.lines;
default = ""; default = "";
@ -428,27 +437,51 @@ in rec {
config = mkMerge [ config = mkMerge [
(mkIf (config.preStart != "") rec { (mkIf (config.preStart != "") rec {
jobScripts = makeJobScript "${name}-pre-start" config.preStart; jobScripts = makeJobScript {
name = "${name}-pre-start";
text = config.preStart;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStartPre = [ jobScripts ]; serviceConfig.ExecStartPre = [ jobScripts ];
}) })
(mkIf (config.script != "") rec { (mkIf (config.script != "") rec {
jobScripts = makeJobScript "${name}-start" config.script; jobScripts = makeJobScript {
name = "${name}-start";
text = config.script;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs; serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs;
}) })
(mkIf (config.postStart != "") rec { (mkIf (config.postStart != "") rec {
jobScripts = (makeJobScript "${name}-post-start" config.postStart); jobScripts = makeJobScript {
name = "${name}-post-start";
text = config.postStart;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStartPost = [ jobScripts ]; serviceConfig.ExecStartPost = [ jobScripts ];
}) })
(mkIf (config.reload != "") rec { (mkIf (config.reload != "") rec {
jobScripts = makeJobScript "${name}-reload" config.reload; jobScripts = makeJobScript {
name = "${name}-reload";
text = config.reload;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecReload = jobScripts; serviceConfig.ExecReload = jobScripts;
}) })
(mkIf (config.preStop != "") rec { (mkIf (config.preStop != "") rec {
jobScripts = makeJobScript "${name}-pre-stop" config.preStop; jobScripts = makeJobScript {
name = "${name}-pre-stop";
text = config.preStop;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStop = jobScripts; serviceConfig.ExecStop = jobScripts;
}) })
(mkIf (config.postStop != "") rec { (mkIf (config.postStop != "") rec {
jobScripts = makeJobScript "${name}-post-stop" config.postStop; jobScripts = makeJobScript {
name = "${name}-post-stop";
text = config.postStop;
inherit (config) enableStrictShellChecks;
};
serviceConfig.ExecStopPost = jobScripts; serviceConfig.ExecStopPost = jobScripts;
}) })
]; ];

View File

@ -197,6 +197,8 @@ in
package = mkPackageOption pkgs "systemd" {}; package = mkPackageOption pkgs "systemd" {};
enableStrictShellChecks = mkEnableOption "running shellcheck on the generated scripts for systemd units.";
units = mkOption { units = mkOption {
description = "Definition of systemd units; see {manpage}`systemd.unit(5)`."; description = "Definition of systemd units; see {manpage}`systemd.unit(5)`.";
default = {}; default = {};