{ config, lib, pkgs, ... }: let inherit (lib) concatStringsSep mkIf mkMerge mkOption optionalAttrs types ; cfg = config.services.mbsync; isDarwin = pkgs.stdenv.isDarwin; isLinux = pkgs.stdenv.isLinux; mbsyncOptions = [ "--all" ] ++ lib.optional cfg.verbose "--verbose" ++ lib.optional (cfg.configFile != null) "--config ${cfg.configFile}"; mbsyncCommand = "${cfg.package}/bin/mbsync ${concatStringsSep " " mbsyncOptions}"; # Convert systemd calendar format to launchd interval (approximate) # Format like "*:0/5" means every 5 minutes # We'll parse simple cases, default to 5 minutes parseFrequencyToSeconds = freq: let # Try to extract minute interval from patterns like "*:0/5" or "*:*:0/30" parts = builtins.match ".*\\*/([0-9]+).*" freq; in if parts != null then (lib.toInt (builtins.head parts)) * 60 else 300; in { meta.maintainers = [ lib.maintainers.pjones ]; options.services.mbsync = { enable = lib.mkEnableOption "mbsync"; package = lib.mkPackageOption pkgs "isync" { }; frequency = mkOption { type = types.str; default = "*:0/5"; description = '' How often to run mbsync. On Linux, this value is passed to the systemd timer configuration as the onCalendar option. See {manpage}`systemd.time(7)` for more information about the format. On Darwin, this is converted to an approximate interval in seconds. ''; }; verbose = mkOption { type = types.bool; default = true; description = '' Whether mbsync should produce verbose output. ''; }; configFile = mkOption { type = types.nullOr types.path; default = null; description = '' Optional configuration file to link to use instead of the default file ({file}`~/.mbsyncrc`). ''; }; preExec = mkOption { type = types.nullOr types.str; default = null; example = "mkdir -p %h/mail"; description = '' An optional command to run before mbsync executes. This is useful for creating the directories mbsync is going to use. ''; }; postExec = mkOption { type = types.nullOr types.str; default = null; example = "\${pkgs.mu}/bin/mu index"; description = '' An optional command to run after mbsync executes successfully. This is useful for running mailbox indexing tools. ''; }; }; config = mkIf cfg.enable (mkMerge [ # Linux-specific: systemd user service and timer (mkIf isLinux { systemd.user.services.mbsync = { Unit = { Description = "mbsync mailbox synchronization"; }; Service = { Type = "oneshot"; ExecStart = mbsyncCommand; } // (optionalAttrs (cfg.postExec != null) { ExecStartPost = cfg.postExec; }) // (optionalAttrs (cfg.preExec != null) { ExecStartPre = cfg.preExec; }); }; systemd.user.timers.mbsync = { Unit = { Description = "mbsync mailbox synchronization"; }; Timer = { OnCalendar = cfg.frequency; Unit = "mbsync.service"; }; Install = { WantedBy = [ "timers.target" ]; }; }; }) # Darwin-specific: launchd agent (mkIf isDarwin { launchd.agents.mbsync = { enable = true; config = let # Build a script that handles pre/post exec mbsyncScript = pkgs.writeShellScript "mbsync-wrapper" '' set -e ${lib.optionalString (cfg.preExec != null) cfg.preExec} ${mbsyncCommand} ${lib.optionalString (cfg.postExec != null) cfg.postExec} ''; in { ProgramArguments = [ "${mbsyncScript}" ]; StartInterval = parseFrequencyToSeconds cfg.frequency; RunAtLoad = true; StandardErrorPath = "/tmp/mbsync.err.log"; StandardOutPath = "/tmp/mbsync.out.log"; }; }; }) ]); }