diff --git a/nixos/doc/manual/development/modular-services.md b/nixos/doc/manual/development/modular-services.md index 62b1d9f2fa4fa..63a24666ce26c 100644 --- a/nixos/doc/manual/development/modular-services.md +++ b/nixos/doc/manual/development/modular-services.md @@ -48,7 +48,7 @@ It is possible to write service modules that are portable. This is done by eithe { config, options, lib, ... }: { _class = "service"; config = { - process.executable = "${lib.getExe config.foo.program}"; + process.argv = [ (lib.getExe config.foo.program) ]; } // lib.optionalAttrs (options?systemd) { # ... systemd-specific definitions ... }; diff --git a/nixos/modules/system/service/portable/service.nix b/nixos/modules/system/service/portable/service.nix index d000398b8a6ac..b62b23e09a67b 100644 --- a/nixos/modules/system/service/portable/service.nix +++ b/nixos/modules/system/service/portable/service.nix @@ -7,18 +7,6 @@ let inherit (lib) mkOption types; pathOrStr = types.coercedTo types.path (x: "${x}") types.str; - program = - types.coercedTo ( - types.package - // { - # require mainProgram for this conversion - check = v: v.type or null == "derivation" && v ? meta.mainProgram; - } - ) lib.getExe pathOrStr - // { - description = "main program, path or command"; - descriptionClass = "conjunction"; - }; in { # https://nixos.org/manual/nixos/unstable/#modular-services @@ -45,18 +33,15 @@ in visible = "shallow"; }; process = { - executable = mkOption { - type = program; - description = '' - The path to the executable that will be run when the service is started. - ''; - }; - args = lib.mkOption { + argv = lib.mkOption { type = types.listOf pathOrStr; + example = lib.literalExpression ''[ (lib.getExe config.package) "--nobackground" ]''; description = '' - Arguments to pass to the `executable`. + Command filename and arguments for starting this service. + This is a raw command-line that should not contain any shell escaping. + If expansion of environmental variables is required then use + a shell script or `importas` from `pkgs.execline`. ''; - default = [ ]; }; }; }; diff --git a/nixos/modules/system/service/portable/test.nix b/nixos/modules/system/service/portable/test.nix index 7901ea6965e98..5f78208dc4eb2 100644 --- a/nixos/modules/system/service/portable/test.nix +++ b/nixos/modules/system/service/portable/test.nix @@ -20,8 +20,10 @@ let services = { service1 = { process = { - executable = "/usr/bin/echo"; # *giggles* - args = [ "hello" ]; + argv = [ + "/usr/bin/echo" # *giggles* + "hello" + ]; }; assertions = [ { @@ -37,21 +39,27 @@ let process = { # No meta.mainProgram, because it's supposedly an executable script _file_, # not a directory with a bin directory containing the main program. - executable = dummyPkg "cowsay.sh"; - args = [ "world" ]; + argv = [ + (dummyPkg "cowsay.sh") + "world" + ]; }; }; service3 = { process = { - executable = "/bin/false"; - args = [ ]; + argv = [ "/bin/false" ]; }; services.exclacow = { process = { - executable = dummyPkg "cowsay-ng" // { - meta.mainProgram = "cowsay"; - }; - args = [ "!" ]; + argv = [ + (lib.getExe ( + dummyPkg "cowsay-ng" + // { + meta.mainProgram = "cowsay"; + } + )) + "!" + ]; }; assertions = [ { @@ -91,8 +99,10 @@ let services = { service1 = { process = { - executable = "/usr/bin/echo"; - args = [ "hello" ]; + argv = [ + "/usr/bin/echo" + "hello" + ]; }; services = { }; assertions = [ @@ -107,8 +117,10 @@ let }; service2 = { process = { - executable = "${dummyPkg "cowsay.sh"}"; - args = [ "world" ]; + argv = [ + "${dummyPkg "cowsay.sh"}" + "world" + ]; }; services = { }; assertions = [ ]; @@ -116,13 +128,14 @@ let }; service3 = { process = { - executable = "/bin/false"; - args = [ ]; + argv = [ "/bin/false" ]; }; services.exclacow = { process = { - executable = "${dummyPkg "cowsay-ng"}/bin/cowsay"; - args = [ "!" ]; + argv = [ + "${dummyPkg "cowsay-ng"}/bin/cowsay" + "!" + ]; }; services = { }; assertions = [ diff --git a/nixos/modules/system/service/systemd/service.nix b/nixos/modules/system/service/systemd/service.nix index 1ee6a4ff26924..53c317b631cb0 100644 --- a/nixos/modules/system/service/systemd/service.nix +++ b/nixos/modules/system/service/systemd/service.nix @@ -64,7 +64,7 @@ in Restart = lib.mkDefault "always"; RestartSec = lib.mkDefault "5"; ExecStart = [ - (systemdPackage.functions.escapeSystemdExecArgs ([ config.process.executable ] ++ config.process.args)) + (systemdPackage.functions.escapeSystemdExecArgs config.process.argv) ]; }; }; diff --git a/nixos/modules/system/service/systemd/test.nix b/nixos/modules/system/service/systemd/test.nix index b393763edbe53..70d1b17d1a6df 100644 --- a/nixos/modules/system/service/systemd/test.nix +++ b/nixos/modules/system/service/systemd/test.nix @@ -11,14 +11,17 @@ let machine = evalSystem ( { lib, ... }: + let + hello' = lib.getExe hello; + in { # Test input system.services.foo = { process = { - executable = hello; - args = [ + argv = [ + hello' "--greeting" "hoi" ]; @@ -26,8 +29,8 @@ let }; system.services.bar = { process = { - executable = hello; - args = [ + argv = [ + hello' "--greeting" "hoi" ]; @@ -37,8 +40,8 @@ let }; services.db = { process = { - executable = hello; - args = [ + argv = [ + hello' "--greeting" "Hi, I'm a database, would you believe it" ]; @@ -72,12 +75,12 @@ runCommand "test-modular-service-systemd-units" cat -n ${toplevel}/etc/systemd/system/foo.service ( set -x - grep -F 'ExecStart=${hello}/bin/hello --greeting hoi' ${toplevel}/etc/systemd/system/foo.service >/dev/null + grep -F 'ExecStart="${hello}/bin/hello" "--greeting" "hoi"' ${toplevel}/etc/systemd/system/foo.service >/dev/null - grep -F 'ExecStart=${hello}/bin/hello --greeting hoi' ${toplevel}/etc/systemd/system/bar.service >/dev/null + grep -F 'ExecStart="${hello}/bin/hello" "--greeting" "hoi"' ${toplevel}/etc/systemd/system/bar.service >/dev/null grep -F 'X-Bar=lol crossbar whatever' ${toplevel}/etc/systemd/system/bar.service >/dev/null - grep 'ExecStart=${hello}/bin/hello --greeting .*database.*' ${toplevel}/etc/systemd/system/bar-db.service >/dev/null + grep 'ExecStart="${hello}/bin/hello" "--greeting" ".*database.*"' ${toplevel}/etc/systemd/system/bar-db.service >/dev/null grep -F 'RestartSec=42' ${toplevel}/etc/systemd/system/bar-db.service >/dev/null [[ ! -e ${toplevel}/etc/systemd/system/foo.socket ]] diff --git a/pkgs/by-name/gh/ghostunnel/service.nix b/pkgs/by-name/gh/ghostunnel/service.nix index dbc49478a6cb8..0d200335d5260 100644 --- a/pkgs/by-name/gh/ghostunnel/service.nix +++ b/pkgs/by-name/gh/ghostunnel/service.nix @@ -9,6 +9,7 @@ let inherit (lib) concatStringsSep escapeShellArg + getExe mkDefault mkIf mkOption @@ -130,9 +131,9 @@ in }; extraArguments = mkOption { - description = "Extra arguments to pass to `ghostunnel server` (shell syntax)"; - type = types.separatedString " "; - default = ""; + description = "Extra arguments to pass to `ghostunnel server`"; + type = types.listOf types.str; + default = [ ]; }; unsafeTarget = mkOption { @@ -181,32 +182,44 @@ in # TODO assertions process = { - executable = pkgs.writeScriptBin "run-ghostunnel" '' - #!${pkgs.runtimeShell} - exec ${lib.getExe cfg.package} ${ - concatStringsSep " " ( - optional (cfg.keystore != null) "--keystore=$CREDENTIALS_DIRECTORY/keystore" - ++ optional (cfg.cert != null) "--cert=$CREDENTIALS_DIRECTORY/cert" - ++ optional (cfg.key != null) "--key=$CREDENTIALS_DIRECTORY/key" - ++ optional (cfg.cacert != null) "--cacert=$CREDENTIALS_DIRECTORY/cacert" - ++ [ - "server" - "--listen" - cfg.listen - "--target" - cfg.target - ] - ++ optional cfg.allowAll "--allow-all" - ++ map (v: "--allow-cn=${escapeShellArg v}") cfg.allowCN - ++ map (v: "--allow-ou=${escapeShellArg v}") cfg.allowOU - ++ map (v: "--allow-dns=${escapeShellArg v}") cfg.allowDNS - ++ map (v: "--allow-uri=${escapeShellArg v}") cfg.allowURI - ++ optional cfg.disableAuthentication "--disable-authentication" - ++ optional cfg.unsafeTarget "--unsafe-target" - ++ [ cfg.extraArguments ] + argv = + # Use a shell if credentials need to be pulled from the environment. + optional + (builtins.any (v: v != null) [ + cfg.keystore + cfg.cert + cfg.key + cfg.cacert + ]) + ( + pkgs.writeScript "load-credentials" '' + #!${pkgs.runtimeShell} + exec $@ ${ + concatStringsSep " " ( + optional (cfg.keystore != null) "--keystore=$CREDENTIALS_DIRECTORY/keystore" + ++ optional (cfg.cert != null) "--cert=$CREDENTIALS_DIRECTORY/cert" + ++ optional (cfg.key != null) "--key=$CREDENTIALS_DIRECTORY/key" + ++ optional (cfg.cacert != null) "--cacert=$CREDENTIALS_DIRECTORY/cacert" + ) + } + '' ) - } - ''; + ++ [ + (getExe cfg.package) + "server" + "--listen" + cfg.listen + "--target" + cfg.target + ] + ++ optional cfg.allowAll "--allow-all" + ++ map (v: "--allow-cn=${v}") cfg.allowCN + ++ map (v: "--allow-ou=${v}") cfg.allowOU + ++ map (v: "--allow-dns=${v}") cfg.allowDNS + ++ map (v: "--allow-uri=${v}") cfg.allowURI + ++ optional cfg.disableAuthentication "--disable-authentication" + ++ optional cfg.unsafeTarget "--unsafe-target" + ++ cfg.extraArguments; }; # refine the service