From 5eca556fe78989d17caddc58a58a944bf08558fa Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Mon, 6 Aug 2018 19:04:56 +0900 Subject: [PATCH] offlineimap: add module OfflineIMAP is a Mail Retrieval Agent (MRA) like mbsync but written in Python. --- modules/accounts/email.nix | 1 + modules/misc/news.nix | 7 + modules/modules.nix | 1 + modules/programs/offlineimap-accounts.nix | 46 +++++ modules/programs/offlineimap.nix | 206 ++++++++++++++++++++++ 5 files changed, 261 insertions(+) create mode 100644 modules/programs/offlineimap-accounts.nix create mode 100644 modules/programs/offlineimap.nix diff --git a/modules/accounts/email.nix b/modules/accounts/email.nix index 74bb84ac..660f6ad8 100644 --- a/modules/accounts/email.nix +++ b/modules/accounts/email.nix @@ -320,6 +320,7 @@ in (import ../programs/mbsync-accounts.nix) (import ../programs/msmtp-accounts.nix) (import ../programs/notmuch-accounts.nix) + (import ../programs/offlineimap-accounts.nix) ]); default = {}; description = "List of email accounts."; diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 8e893658..ed16f962 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -772,6 +772,13 @@ in A new module is available: 'services.pasystray'. ''; } + + { + time = "2018-08-29T20:27:04+00:00"; + message = '' + A new module is available: 'programs.offlineimap'. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index 914d275c..39db0cc8 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -49,6 +49,7 @@ let ./programs/neovim.nix ./programs/newsboat.nix ./programs/notmuch.nix + ./programs/offlineimap.nix ./programs/pidgin.nix ./programs/rofi.nix ./programs/ssh.nix diff --git a/modules/programs/offlineimap-accounts.nix b/modules/programs/offlineimap-accounts.nix new file mode 100644 index 00000000..1900617c --- /dev/null +++ b/modules/programs/offlineimap-accounts.nix @@ -0,0 +1,46 @@ +{ config, lib, ... }: + +with lib; + +let + + extraConfigType = with types; attrsOf (either (either str int) bool); + +in + +{ + options.offlineimap = { + enable = mkEnableOption "OfflineIMAP"; + + extraConfig.local = mkOption { + type = extraConfigType; + default = {}; + example = { + sync_deletes = true; + }; + description = '' + Extra configuration options to add to the local account + section. + ''; + }; + + extraConfig.remote = mkOption { + type = extraConfigType; + default = {}; + example = { + maxconnections = 2; + expunge = false; + }; + description = '' + Extra configuration options to add to the remote account + section. + ''; + }; + + postSyncHookCommand = mkOption { + type = types.lines; + default = ""; + description = "Command to run after fetching new mails."; + }; + }; +} diff --git a/modules/programs/offlineimap.nix b/modules/programs/offlineimap.nix new file mode 100644 index 00000000..7a1b5734 --- /dev/null +++ b/modules/programs/offlineimap.nix @@ -0,0 +1,206 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.offlineimap; + + accounts = filter (a: a.offlineimap.enable) + (attrValues config.accounts.email.accounts); + + toIni = generators.toINI { + mkKeyValue = key: value: + let + value' = + if isBool value then (if value then "yes" else "no") + else toString value; + in + "${key} = ${value'}"; + }; + + # Generates a script to fetch only a specific account. + # + # Note, these scripts are not actually created and installed at the + # moment. It will need some thinking on whether this is a good idea + # and whether other modules should have some similar functionality. + # + # Perhaps have a single tool `email` that wraps the command? + # Something like + # + # $ email + genOfflineImapScript = account: with account; + pkgs.writeShellScriptBin "offlineimap-${name}" '' + exec ${pkgs.offlineimap}/bin/offlineimap -a${account.name} "$@" + ''; + + accountStr = account: with account; + let + postSyncHook = optionalAttrs (offlineimap.postSyncHookCommand != "") { + postsynchook = + pkgs.writeShellScriptBin + "postsynchook" + offlineimap.postSyncHookCommand + + "/bin/postsynchook"; + }; + + localType = + if account.flavor == "gmail.com" + then "GmailMaildir" + else "Maildir"; + + remoteType = + if account.flavor == "gmail.com" + then "Gmail" + else "IMAP"; + + remoteHost = optionalAttrs (imap.host != null) { + remotehost = imap.host; + }; + + remotePort = optionalAttrs ((imap.port or null) != null) { + remoteport = imap.port; + }; + + ssl = + if imap.tls.enable + then + { + ssl = true; + sslcacertfile = imap.tls.certificatesFile; + starttls = imap.tls.useStartTls; + } + else + { + ssl = false; + }; + + remotePassEval = + let + arglist = concatMapStringsSep "," (x: "'${x}'") passwordCommand; + in + optionalAttrs (passwordCommand != null) { + remotepasseval = ''get_pass("${name}", [${arglist}])''; + }; + in + toIni { + "Account ${name}" = { + localrepository = "${name}-local"; + remoterepository = "${name}-remote"; + } + // postSyncHook; + + "Repository ${name}-local" = { + type = localType; + localfolders = maildir.absPath; + } + // offlineimap.extraConfig.local; + + "Repository ${name}-remote" = { + type = remoteType; + remoteuser = userName; + } + // remoteHost + // remotePort + // remotePassEval + // ssl + // offlineimap.extraConfig.remote; + }; + + extraConfigType = with types; attrsOf (either (either str int) bool); + +in + +{ + options = { + programs.offlineimap = { + enable = mkEnableOption "OfflineIMAP"; + + pythonFile = mkOption { + type = types.lines; + default = '' + import subprocess + + def get_pass(service, cmd): + return subprocess.check_output(cmd, ) + ''; + description = '' + Python code that can then be used in other parts of the + configuration. + ''; + }; + + extraConfig.general = mkOption { + type = extraConfigType; + default = {}; + example = { + maxage = 30; + ui = "blinkenlights"; + }; + description = '' + Extra configuration options added to the + section. + ''; + }; + + extraConfig.default = mkOption { + type = extraConfigType; + default = {}; + example = { + gmailtrashfolder = "[Gmail]/Papierkorb"; + }; + description = '' + Extra configuration options added to the + section. + ''; + }; + + extraConfig.mbnames = mkOption { + type = extraConfigType; + default = {}; + example = literalExample '' + { + filename = "~/.config/mutt/mailboxes"; + header = "'mailboxes '"; + peritem = "'+%(accountname)s/%(foldername)s'"; + sep = "' '"; + footer = "'\\n'"; + } + ''; + description = '' + Extra configuration options added to the + mbnames section. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.offlineimap ]; + + xdg.configFile."offlineimap/get_settings.py".text = cfg.pythonFile; + + xdg.configFile."offlineimap/config".text = + '' + # Generated by Home Manager. + # See https://github.com/OfflineIMAP/offlineimap/blob/master/offlineimap.conf + # for an exhaustive list of options. + '' + + toIni ({ + general = { + accounts = concatMapStringsSep "," (a: a.name) accounts; + pythonfile = "${config.xdg.configHome}/offlineimap/get_settings.py"; + metadata = "${config.xdg.dataHome}/offlineimap"; + } + // cfg.extraConfig.general; + } + // optionalAttrs (cfg.extraConfig.mbnames != {}) { + mbnames = { enabled = true; } // cfg.extraConfig.mbnames; + } + // optionalAttrs (cfg.extraConfig.default != {}) { + DEFAULT = cfg.extraConfig.default; + }) + + "\n" + + concatStringsSep "\n" (map accountStr accounts); + }; +}