imapnotify: Add launchd agent (#3291)

* imapnotify: expose package (and exe) options

There are multiple packages that provide an imapnotify interface. Those
packages have differently named executables. This can now be customized.

This change also means test configurations can use stub packages.

* imapnotify: use/create config in configHome

Exposing the configuration file makes testing imapnotify configurations much
easier. It also allows for golden tests in home-manager.

* imapnotify: extend with launchd agent

Now that home-manager supports launchd agents, the imapnotify service
can be configured (and enabled) for darwin. The configuration matches
that of the linux/systemd version. In particular, by not setting a
`UserName`, this runs as the user whose configuration includes the
module.

Due to the launchd `Program` implementation (it must take an absolute
path) it is not possible to use that for the program and stub the path
in tests. Instead, this uses `ProgramArguments` for the program name.

The `ThrottleInterval` is equivalent to `RestartSec`. `KeepAlive` is
equivalent to `Restart`.

The `ExitTimeOut` default is 20 seconds, but goimapnotify should not
time out — this is achieved by setting the `ExitTimeout` to 0.

* imapnotify: add launchd plist test

This only tests the generated plist (which is new), not the original
systemd implementation, nor the json config file.

(Note the lack of a newline at the end of the plist file.)
This commit is contained in:
David Baynard 2023-07-07 10:39:12 +01:00 committed by GitHub
parent b23c7501f7
commit 719de878f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 7 deletions

View File

@ -8,6 +8,8 @@ let
safeName = lib.replaceStrings [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ];
configName = account: "imapnotify-${safeName account.name}-config.json";
imapnotifyAccounts =
filter (a: a.imapnotify.enable) (attrValues config.accounts.email.accounts);
@ -19,9 +21,10 @@ let
Unit = { Description = "imapnotify for ${name}"; };
Service = {
ExecStart = "${pkgs.goimapnotify}/bin/goimapnotify -conf ${
genAccountConfig account
}";
ExecStart =
"${getExe cfg.package} -conf '${config.xdg.configHome}/imapnotify/${
configName account
}'";
Restart = "always";
RestartSec = 30;
Type = "simple";
@ -34,8 +37,32 @@ let
};
};
genAccountAgent = account:
let name = safeName account.name;
in {
name = "imapnotify-${name}";
value = {
enable = true;
config = {
ProgramArguments = [
"${getExe cfg.package}"
"-conf"
"${config.xdg.configHome}/imapnotify/${configName account}"
];
KeepAlive = true;
ThrottleInterval = 30;
ExitTimeOut = 0;
ProcessType = "Background";
RunAtLoad = true;
} // optionalAttrs account.notmuch.enable {
EnvironmentVariables.NOTMUCH_CONFIG =
"${config.xdg.configHome}/notmuch/default/config";
};
};
};
genAccountConfig = account:
pkgs.writeText "imapnotify-${safeName account.name}-config.json" (let
pkgs.writeText (configName account) (let
port = if account.imap.port != null then
account.imap.port
else if account.imap.tls.enable then
@ -62,7 +89,17 @@ in {
meta.maintainers = [ maintainers.nickhu ];
options = {
services.imapnotify = { enable = mkEnableOption "imapnotify"; };
services.imapnotify = {
enable = mkEnableOption "imapnotify";
package = mkOption {
type = types.package;
default = pkgs.goimapnotify;
defaultText = literalExpression "pkgs.goimapnotify";
example = literalExpression "pkgs.imapnotify";
description = "The imapnotify package to use";
};
};
accounts.email.accounts = mkOption {
type = with types; attrsOf (submodule (import ./imapnotify-accounts.nix));
@ -79,8 +116,6 @@ in {
+ concatMapStringsSep ", " (a: a.name) badAccounts;
};
in [
(lib.hm.assertions.assertPlatform "services.imapnotify" pkgs
lib.platforms.linux)
(checkAccounts (a: a.maildir == null) "maildir configuration")
(checkAccounts (a: a.imap == null) "IMAP configuration")
(checkAccounts (a: a.passwordCommand == null) "password command")
@ -88,5 +123,12 @@ in {
];
systemd.user.services = listToAttrs (map genAccountUnit imapnotifyAccounts);
launchd.agents = listToAttrs (map genAccountAgent imapnotifyAccounts);
xdg.configFile = listToAttrs (map (account: {
name = "imapnotify/${configName account}";
value.source = genAccountConfig account;
}) imapnotifyAccounts);
};
}

View File

@ -145,6 +145,7 @@ import nmt {
] ++ lib.optionals isDarwin [
./modules/launchd
./modules/targets-darwin
./modules/programs/goimapnotify
] ++ lib.optionals isLinux [
./modules/config/i18n
./modules/i18n/input-method

View File

@ -0,0 +1 @@
{ goimapnotify-launchd = ./launchd.nix; }

View File

@ -0,0 +1,41 @@
{ config, lib, pkgs, ... }:
with lib;
{
imports = [ ../../accounts/email-test-accounts.nix ];
config = {
accounts.email.accounts = {
"hm@example.com" = {
notmuch.enable = true;
imap.port = 993;
imapnotify = {
enable = true;
boxes = [ "Inbox" ];
onNotify = ''
${pkgs.notmuch}/bin/notmuch new
'';
};
};
};
services.imapnotify = {
enable = true;
package = (config.lib.test.mkStubPackage {
name = "goimapnotify";
outPath = "@goimapnotify@";
});
};
nmt.script = let
serviceFileName =
"org.nix-community.home.imapnotify-hm-example.com.plist";
in ''
serviceFile=LaunchAgents/${serviceFileName}
assertFileExists $serviceFile
assertFileContent $serviceFile ${./launchd.plist}
'';
};
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>NOTMUCH_CONFIG</key>
<string>/home/hm-user/.config/notmuch/default/config</string>
</dict>
<key>ExitTimeOut</key>
<integer>0</integer>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>org.nix-community.home.imapnotify-hm-example.com</string>
<key>ProcessType</key>
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@goimapnotify@/bin/goimapnotify</string>
<string>-conf</string>
<string>/home/hm-user/.config/imapnotify/imapnotify-hm-example.com-config.json</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ThrottleInterval</key>
<integer>30</integer>
</dict>
</plist>