mirror of
https://github.com/nix-community/home-manager.git
synced 2026-10-02 19:49:18 +00:00
launchd: add launcher.name and launcher.shell options
With `waitForNixStore = false` an agent is started through a launcher script so that macOS lists it under its own name in System Settings > Login Items & Extensions. Two properties of that launcher were fixed and unreachable from configuration: its base name (the agent's attribute name) and its interpreter (`/bin/sh`). Both matter on macOS. The name is what "Allow in the Background" shows, so a fleet-wide prefix such as `nix-` makes every Home Manager agent recognisable next to third-party items. The interpreter is the binary the system attributes the agent's process to; a store-resident shell attributes it to the launcher itself rather than to Apple's `/bin/sh`, which changes how the privacy prompts for folders like `~/Downloads` apply. Users who need either property today have to vendor this module. `launcher.name` (default: the attribute name, as before) and `launcher.shell` (default: `/bin/sh`, as before) make both configurable without changing any existing agent.
This commit is contained in:
committed by
Austin Horstman
parent
87b3c74df0
commit
dd0b74e2b5
@@ -46,12 +46,48 @@ let
|
||||
when the store volume is encrypted.
|
||||
|
||||
When disabled, the agent's command is run through a launcher
|
||||
script named after the agent instead. This makes the agent appear
|
||||
under its own name, rather than as "sh", in System Settings >
|
||||
Login Items & Extensions, but the agent will fail to start if
|
||||
launchd runs it before the Nix store is mounted.
|
||||
script named after the agent instead (see
|
||||
{option}`launchd.agents.<name>.launcher`). This makes the agent
|
||||
appear under its own name, rather than as "sh", in System
|
||||
Settings > Login Items & Extensions, but the agent will fail to
|
||||
start if launchd runs it before the Nix store is mounted.
|
||||
'';
|
||||
};
|
||||
launcher = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "nix-my-agent";
|
||||
description = ''
|
||||
Base name of the launcher script the agent is started through
|
||||
when {option}`launchd.agents.<name>.waitForNixStore` is
|
||||
disabled. This is the name macOS shows for the agent in
|
||||
System Settings > Login Items & Extensions ("Allow in the
|
||||
Background"), so a common prefix makes every Home Manager
|
||||
agent recognisable at a glance.
|
||||
|
||||
Defaults to the agent's attribute name.
|
||||
'';
|
||||
};
|
||||
shell = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/bin/sh";
|
||||
defaultText = lib.literalExpression ''"/bin/sh"'';
|
||||
example = lib.literalExpression "lib.getExe pkgs.bash";
|
||||
description = ''
|
||||
Interpreter for the launcher script used when
|
||||
{option}`launchd.agents.<name>.waitForNixStore` is disabled.
|
||||
|
||||
A launcher script lives in the Nix store either way, so a
|
||||
store-resident interpreter adds no new failure mode. It does
|
||||
change which binary macOS attributes the agent's process to
|
||||
(for example for the privacy prompts that gate access to
|
||||
`~/Desktop` and `~/Downloads`): with `/bin/sh` the
|
||||
attribution follows Apple's shell, with a store-resident shell
|
||||
it follows the launcher itself.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = lib.types.submodule (import ./launchd.nix);
|
||||
default = { };
|
||||
@@ -88,18 +124,21 @@ let
|
||||
# before running the original command. This is intentional to fix the
|
||||
# issue where launchd starts the agent before /nix/store is ready (before
|
||||
# the Nix store is mounted.) When waitForNixStore is disabled, the command
|
||||
# is run through a launcher script named after the agent instead, so that
|
||||
# the agent is displayed under its own name (rather than "sh") in System
|
||||
# Settings > Login Items & Extensions.
|
||||
# is run through a launcher script instead — named after the agent unless
|
||||
# `launcher.name` says otherwise, interpreted by `launcher.shell` — so
|
||||
# that the agent is displayed under its own name (rather than "sh") in
|
||||
# System Settings > Login Items & Extensions.
|
||||
mutateConfig =
|
||||
name: waitForNixStore: cnf:
|
||||
name: v:
|
||||
let
|
||||
cnf = v.config;
|
||||
args =
|
||||
lib.optional (cnf.Program != null) cnf.Program
|
||||
++ lib.optionals (cnf.ProgramArguments != null) cnf.ProgramArguments;
|
||||
launcherName = lib.strings.sanitizeDerivationName name;
|
||||
launcherName =
|
||||
if v.launcher.name != null then v.launcher.name else lib.strings.sanitizeDerivationName name;
|
||||
launcher = pkgs.writeScriptBin launcherName ''
|
||||
#!/bin/sh
|
||||
#!${v.launcher.shell}
|
||||
exec ${lib.escapeShellArgs args}
|
||||
'';
|
||||
in
|
||||
@@ -109,7 +148,7 @@ let
|
||||
])
|
||||
// {
|
||||
ProgramArguments =
|
||||
if waitForNixStore then
|
||||
if v.waitForNixStore then
|
||||
[
|
||||
"/bin/sh"
|
||||
"-c"
|
||||
@@ -121,9 +160,7 @@ let
|
||||
|
||||
toAgent =
|
||||
name: v:
|
||||
pkgs.writeText "${v.config.Label}.plist" (
|
||||
toPlist { escape = true; } (mutateConfig name v.waitForNixStore v.config)
|
||||
);
|
||||
pkgs.writeText "${v.config.Label}.plist" (toPlist { escape = true; } (mutateConfig name v));
|
||||
|
||||
agentPlists = lib.mapAttrs' (n: v: lib.nameValuePair "${v.config.Label}.plist" (toAgent n v)) (
|
||||
lib.filterAttrs (_n: v: v.enable) cfg.agents
|
||||
|
||||
35
tests/modules/launchd/agent-launcher-custom.nix
Normal file
35
tests/modules/launchd/agent-launcher-custom.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
config = {
|
||||
launchd.agents."custom-launcher" = {
|
||||
enable = true;
|
||||
waitForNixStore = false;
|
||||
launcher = {
|
||||
name = "nix-custom-launcher";
|
||||
shell = "${pkgs.bash}/bin/bash";
|
||||
};
|
||||
config = {
|
||||
ProgramArguments = [
|
||||
"/some/command"
|
||||
"--flag"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
nmt.script = ''
|
||||
serviceFile=LaunchAgents/org.nix-community.home.custom-launcher.plist
|
||||
assertFileExists $serviceFile
|
||||
|
||||
# The launcher carries the configured name, not the agent's attribute name.
|
||||
assertFileRegex $serviceFile '<string>/nix/store/[^<]*/bin/nix-custom-launcher</string>'
|
||||
assertFileNotRegex $serviceFile '/bin/custom-launcher</string>'
|
||||
assertFileNotRegex $serviceFile '<string>/bin/sh</string>'
|
||||
|
||||
launcher=$(sed -n 's|.*<string>\(/nix/store/[^<]*/bin/nix-custom-launcher\)</string>.*|\1|p' \
|
||||
"$(_abs $serviceFile)")
|
||||
# The configured shell is the launcher's interpreter, and the command is exec'd.
|
||||
assertFileRegex "$launcher" '^#!.*/bin/bash$'
|
||||
assertFileContains "$launcher" 'exec /some/command --flag'
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
launchd-agent-domain = ./agent-domain.nix;
|
||||
launchd-agent-launcher = ./agent-launcher.nix;
|
||||
launchd-agent-launcher-custom = ./agent-launcher-custom.nix;
|
||||
launchd-agents = ./agents.nix;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user