tree-wide: remove redundant platform checks

In the code base, there are lots of configurations locally guarded by
`stdenv.hostPlatform.is(Darwin|Linux)` despite the targeted options
already being guarded. Examples for these targeted options are:

- `systemd.user.*`: globally guarded by `systemd.user.enable`.
- `launchd.*`: globally guarded by `launchd.enable`.
- `lib.hm.darwin.assertInterval`: only effective on Darwin.

These local guards are an antipattern since they weaken the global
guards. Furthermore, they hamper readability. This series of commits
remove instances of these local guards.
This commit is contained in:
Robert Helgesson
2026-01-06 13:13:38 +01:00
17 changed files with 902 additions and 997 deletions

View File

@@ -136,7 +136,7 @@ in
xdg.stateHome = mkOptionDefault defaultStateHome; xdg.stateHome = mkOptionDefault defaultStateHome;
home.sessionVariables = variables; home.sessionVariables = variables;
systemd.user.sessionVariables = mkIf pkgs.stdenv.hostPlatform.isLinux variables; systemd.user.sessionVariables = variables;
} }
) )

View File

@@ -11,7 +11,6 @@ let
tomlFormat = pkgs.formats.toml { }; tomlFormat = pkgs.formats.toml { };
inherit (lib) mkIf mkOption types; inherit (lib) mkIf mkOption types;
inherit (pkgs.stdenv) isLinux isDarwin;
in in
{ {
meta.maintainers = with lib.maintainers; [ meta.maintainers = with lib.maintainers; [
@@ -221,98 +220,82 @@ in
}; };
} }
(mkIf daemonCfg.enable ( (mkIf daemonCfg.enable {
lib.mkMerge [ assertions = [
{ {
assertions = [ assertion = lib.versionAtLeast cfg.package.version "18.2.0";
{ message = ''
assertion = lib.versionAtLeast cfg.package.version "18.2.0"; The Atuin daemon requires at least version 18.2.0 or later.
message = '' '';
The Atuin daemon requires at least version 18.2.0 or later.
'';
}
{
assertion = isLinux || isDarwin;
message = "The Atuin daemon can only be configured on either Linux or macOS.";
}
];
programs.atuin.settings = {
daemon = {
enabled = true;
};
};
} }
(mkIf isLinux { {
programs.atuin.settings = { assertion = config.systemd.user.enable || config.launchd.enable;
daemon = { message = "The Atuin daemon can only be configured on systems with systemd or launchd.";
systemd_socket = true; }
}; ];
};
systemd.user.services.atuin-daemon = { programs.atuin.settings.daemon = {
Unit = { enabled = true;
Description = "Atuin daemon"; systemd_socket = config.systemd.user.enable;
Requires = [ "atuin-daemon.socket" ]; socket_path = lib.mkIf (!config.systemd.user.enable) (
}; lib.mkDefault "${config.xdg.dataHome}/atuin/daemon.sock"
Install = { );
Also = [ "atuin-daemon.socket" ]; };
WantedBy = [ "default.target" ];
};
Service = {
ExecStart = "${lib.getExe cfg.package} daemon";
Environment = lib.optionals (daemonCfg.logLevel != null) [ "ATUIN_LOG=${daemonCfg.logLevel}" ];
Restart = "on-failure";
RestartSteps = 3;
RestartMaxDelaySec = 6;
};
};
systemd.user.sockets.atuin-daemon = systemd.user.services.atuin-daemon = {
let Unit = {
socket_dir = if lib.versionAtLeast cfg.package.version "18.4.0" then "%t" else "%D/atuin"; Description = "Atuin daemon";
in Requires = [ "atuin-daemon.socket" ];
{ };
Unit = { Install = {
Description = "Atuin daemon socket"; Also = [ "atuin-daemon.socket" ];
}; WantedBy = [ "default.target" ];
Install = { };
WantedBy = [ "sockets.target" ]; Service = {
}; ExecStart = "${lib.getExe cfg.package} daemon";
Socket = { Environment = lib.optionals (daemonCfg.logLevel != null) [ "ATUIN_LOG=${daemonCfg.logLevel}" ];
ListenStream = "${socket_dir}/atuin.sock"; Restart = "on-failure";
SocketMode = "0600"; RestartSteps = 3;
RemoveOnStop = true; RestartMaxDelaySec = 6;
}; };
}; };
})
(mkIf isDarwin {
programs.atuin.settings = {
daemon = {
socket_path = lib.mkDefault "${config.xdg.dataHome}/atuin/daemon.sock";
};
};
launchd.agents.atuin-daemon = { systemd.user.sockets.atuin-daemon =
enable = true; let
config = { socket_dir = if lib.versionAtLeast cfg.package.version "18.4.0" then "%t" else "%D/atuin";
ProgramArguments = [ in
"${lib.getExe cfg.package}" {
"daemon" Unit = {
]; Description = "Atuin daemon socket";
EnvironmentVariables = lib.optionalAttrs (daemonCfg.logLevel != null) {
ATUIN_LOG = daemonCfg.logLevel;
};
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
};
}; };
}) Install = {
] WantedBy = [ "sockets.target" ];
)) };
Socket = {
ListenStream = "${socket_dir}/atuin.sock";
SocketMode = "0600";
RemoveOnStop = true;
};
};
launchd.agents.atuin-daemon = {
enable = true;
config = {
ProgramArguments = [
"${lib.getExe cfg.package}"
"daemon"
];
EnvironmentVariables = lib.optionalAttrs (daemonCfg.logLevel != null) {
ATUIN_LOG = daemonCfg.logLevel;
};
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
};
};
})
] ]
); );
} }

View File

@@ -100,7 +100,7 @@ in
lib.optional (cfg.clean.enable && config.nix.gc.automatic) lib.optional (cfg.clean.enable && config.nix.gc.automatic)
"programs.nh.clean.enable and nix.gc.automatic (Home-Manager) are both enabled. Please use one or the other to avoid conflict."; "programs.nh.clean.enable and nix.gc.automatic (Home-Manager) are both enabled. Please use one or the other to avoid conflict.";
assertions = lib.optionals pkgs.stdenv.isDarwin [ assertions = [
(lib.hm.darwin.assertInterval "programs.nh.clean.dates" cfg.clean.dates pkgs) (lib.hm.darwin.assertInterval "programs.nh.clean.dates" cfg.clean.dates pkgs)
]; ];
@@ -131,30 +131,25 @@ in
]; ];
}; };
systemd.user = lib.mkIf (cfg.clean.enable && pkgs.stdenv.isLinux) { systemd.user = lib.mkIf cfg.clean.enable {
services.nh-clean = { services.nh-clean = {
Unit.Description = "Nh clean (user)"; Unit.Description = "Nh clean (user)";
Service = { Service = {
Type = "oneshot"; Type = "oneshot";
ExecStart = "${lib.getExe cfg.package} clean user ${cfg.clean.extraArgs}"; ExecStart = "${lib.getExe cfg.package} clean user ${cfg.clean.extraArgs}";
}; };
}; };
timers.nh-clean = { timers.nh-clean = {
Unit.Description = "Run nh clean"; Unit.Description = "Run nh clean";
Timer = { Timer = {
OnCalendar = cfg.clean.dates; OnCalendar = cfg.clean.dates;
Persistent = true; Persistent = true;
}; };
Install.WantedBy = [ "timers.target" ]; Install.WantedBy = [ "timers.target" ];
}; };
}; };
launchd.agents.nh-clean = lib.mkIf (cfg.clean.enable && pkgs.stdenv.isDarwin) { launchd.agents.nh-clean = lib.mkIf cfg.clean.enable {
enable = true; enable = true;
config = { config = {
ProgramArguments = [ ProgramArguments = [
@@ -163,9 +158,7 @@ in
"user" "user"
] ]
++ lib.optional (cfg.clean.extraArgs != "") cfg.clean.extraArgs; ++ lib.optional (cfg.clean.extraArgs != "") cfg.clean.extraArgs;
StartCalendarInterval = lib.hm.darwin.mkCalendarInterval cfg.clean.dates; StartCalendarInterval = lib.hm.darwin.mkCalendarInterval cfg.clean.dates;
}; };
}; };
}; };

View File

@@ -32,78 +32,72 @@ in
}; };
}; };
config = lib.mkIf serviceConfig.enable ( config = lib.mkIf serviceConfig.enable {
lib.mkMerge [ systemd.user = {
(lib.mkIf pkgs.stdenv.isLinux { services.borgmatic = {
systemd.user = { Unit = {
services.borgmatic = { Description = "borgmatic backup";
Unit = { # Prevent borgmatic from running unless the machine is
Description = "borgmatic backup"; # plugged into power:
# Prevent borgmatic from running unless the machine is ConditionACPower = true;
# plugged into power:
ConditionACPower = true;
};
Service = {
Type = "oneshot";
# Lower CPU and I/O priority:
Nice = 19;
IOSchedulingClass = "best-effort";
IOSchedulingPriority = 7;
IOWeight = 100;
Restart = "no";
LogRateLimitIntervalSec = 0;
# Delay start to prevent backups running during boot:
ExecStartPre = "${pkgs.coreutils}/bin/sleep 3m";
ExecStart = ''
${pkgs.systemd}/bin/systemd-inhibit \
--who="borgmatic" \
--what="sleep:shutdown" \
--why="Prevent interrupting scheduled backup" \
${programConfig.package}/bin/borgmatic \
--stats \
--verbosity -1 \
--list \
--syslog-verbosity 1
'';
};
};
timers.borgmatic = {
Unit.Description = "Run borgmatic backup";
Timer = {
OnCalendar = serviceConfig.frequency;
Persistent = true;
RandomizedDelaySec = "10m";
};
Install.WantedBy = [ "timers.target" ];
};
}; };
}) Service = {
Type = "oneshot";
(lib.mkIf pkgs.stdenv.isDarwin { # Lower CPU and I/O priority:
assertions = [ Nice = 19;
(lib.hm.darwin.assertInterval "services.borgmatic.frequency" serviceConfig.frequency pkgs) IOSchedulingClass = "best-effort";
IOSchedulingPriority = 7;
IOWeight = 100;
Restart = "no";
LogRateLimitIntervalSec = 0;
# Delay start to prevent backups running during boot:
ExecStartPre = "${pkgs.coreutils}/bin/sleep 3m";
ExecStart = ''
${pkgs.systemd}/bin/systemd-inhibit \
--who="borgmatic" \
--what="sleep:shutdown" \
--why="Prevent interrupting scheduled backup" \
${programConfig.package}/bin/borgmatic \
--stats \
--verbosity -1 \
--list \
--syslog-verbosity 1
'';
};
};
timers.borgmatic = {
Unit.Description = "Run borgmatic backup";
Timer = {
OnCalendar = serviceConfig.frequency;
Persistent = true;
RandomizedDelaySec = "10m";
};
Install.WantedBy = [ "timers.target" ];
};
};
assertions = [
(lib.hm.darwin.assertInterval "services.borgmatic.frequency" serviceConfig.frequency pkgs)
];
launchd.agents.borgmatic = {
enable = true;
config = {
ProgramArguments = [
(lib.getExe programConfig.package)
"--stats"
"--list"
]; ];
ProcessType = "Background";
launchd.agents.borgmatic = { StartCalendarInterval = lib.hm.darwin.mkCalendarInterval serviceConfig.frequency;
enable = true; StandardOutPath = "${config.home.homeDirectory}/Library/Logs/borgmatic/launchd-stdout.log";
config = { StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/borgmatic/launchd-stderr.log";
ProgramArguments = [ };
(lib.getExe programConfig.package) };
"--stats" };
"--list"
];
ProcessType = "Background";
StartCalendarInterval = lib.hm.darwin.mkCalendarInterval serviceConfig.frequency;
StandardOutPath = "${config.home.homeDirectory}/Library/Logs/borgmatic/launchd-stdout.log";
StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/borgmatic/launchd-stderr.log";
};
};
})
]
);
} }

View File

@@ -156,7 +156,7 @@ in
xdg.configFile."clipse/custom_theme.json".source = jsonFormat.generate "theme" cfg.theme; xdg.configFile."clipse/custom_theme.json".source = jsonFormat.generate "theme" cfg.theme;
systemd.user.services.clipse = lib.mkIf (pkgs.stdenv.isLinux && (cfg.package != null)) { systemd.user.services.clipse = lib.mkIf (cfg.package != null) {
Unit = { Unit = {
Description = "Clipse listener"; Description = "Clipse listener";
PartOf = [ "graphical-session.target" ]; PartOf = [ "graphical-session.target" ];

View File

@@ -163,7 +163,7 @@ in
}; };
}; };
config = lib.mkIf cfg.enable ({ config = lib.mkIf cfg.enable {
assertions = [ assertions = [
{ {
assertion = (lib.count (p: p.isActive) (lib.attrValues cfg.profiles)) <= 1; assertion = (lib.count (p: p.isActive) (lib.attrValues cfg.profiles)) <= 1;
@@ -189,78 +189,74 @@ in
if activeProfile.name != "default" then "colima-${activeProfile.name}" else "colima" if activeProfile.name != "default" then "colima-${activeProfile.name}" else "colima"
); );
launchd.agents = lib.mkIf pkgs.stdenv.isDarwin ( launchd.agents = lib.mapAttrs' (
lib.mapAttrs' ( name: profile:
name: profile: lib.nameValuePair "colima-${name}" {
lib.nameValuePair "colima-${name}" { enable = true;
enable = true; config = {
config = { ProgramArguments = [
ProgramArguments = [ "${lib.getExe cfg.package}"
"${lib.getExe cfg.package}" "start"
"start" name
name "-f"
"-f" "--activate=${if profile.isActive then "true" else "false"}"
"--activate=${if profile.isActive then "true" else "false"}" "--save-config=false"
"--save-config=false" ];
]; KeepAlive = true;
KeepAlive = true; RunAtLoad = true;
RunAtLoad = true; EnvironmentVariables.PATH = lib.makeBinPath [
EnvironmentVariables.PATH = lib.makeBinPath [ cfg.package
cfg.package cfg.perlPackage
cfg.perlPackage cfg.dockerPackage
cfg.dockerPackage cfg.sshPackage
cfg.sshPackage cfg.coreutilsPackage
cfg.coreutilsPackage cfg.curlPackage
cfg.curlPackage cfg.bashPackage
cfg.bashPackage pkgs.darwin.DarwinTools
pkgs.darwin.DarwinTools ];
]; StandardOutPath = profile.logFile;
StandardOutPath = profile.logFile; StandardErrorPath = profile.logFile;
StandardErrorPath = profile.logFile; };
}; }
} ) (lib.filterAttrs (_: p: p.isService) cfg.profiles);
) (lib.filterAttrs (_: p: p.isService) cfg.profiles)
);
systemd.user.services = lib.mkIf pkgs.stdenv.isLinux ( systemd.user.services = lib.mapAttrs' (
lib.mapAttrs' ( name: profile:
name: profile: lib.nameValuePair "colima-${name}" {
lib.nameValuePair "colima-${name}" { Unit = {
Unit = { Description = "Colima container runtime (${name} profile)";
Description = "Colima container runtime (${name} profile)"; After = [ "network-online.target" ];
After = [ "network-online.target" ]; Wants = [ "network-online.target" ];
Wants = [ "network-online.target" ]; };
}; Service = {
Service = { ExecStart = ''
ExecStart = '' ${lib.getExe cfg.package} start ${name} \
${lib.getExe cfg.package} start ${name} \ -f \
-f \ --activate=${if profile.isActive then "true" else "false"} \
--activate=${if profile.isActive then "true" else "false"} \ --save-config=false
--save-config=false '';
''; Restart = "always";
Restart = "always"; RestartSec = 2;
RestartSec = 2; Environment = [
Environment = [ "PATH=${
"PATH=${ lib.makeBinPath [
lib.makeBinPath [ cfg.package
cfg.package cfg.perlPackage
cfg.perlPackage cfg.dockerPackage
cfg.dockerPackage cfg.sshPackage
cfg.sshPackage cfg.coreutilsPackage
cfg.coreutilsPackage cfg.curlPackage
cfg.curlPackage cfg.bashPackage
cfg.bashPackage ]
] }"
}" ];
]; StandardOutput = "append:${profile.logFile}";
StandardOutput = "append:${profile.logFile}"; StandardError = "append:${profile.logFile}";
StandardError = "append:${profile.logFile}"; };
}; Install = {
Install = { WantedBy = [ "default.target" ];
WantedBy = [ "default.target" ]; };
}; }
} ) (lib.filterAttrs (_: p: p.isService) cfg.profiles);
) (lib.filterAttrs (_: p: p.isService) cfg.profiles) };
);
});
} }

View File

@@ -119,127 +119,117 @@ in
}; };
}; };
config = mkIf cfg.enable ( config = mkIf cfg.enable {
lib.mkMerge [ home.sessionVariables =
{ let
home.sessionVariables = editorBin = lib.getBin (
let pkgs.writeShellScript "editor" ''exec ${lib.getBin cfg.package}/bin/emacsclient "''${@:---create-frame}"''
editorBin = lib.getBin ( );
pkgs.writeShellScript "editor" ''exec ${lib.getBin cfg.package}/bin/emacsclient "''${@:---create-frame}"'' in
); mkIf cfg.defaultEditor {
in EDITOR = editorBin;
mkIf cfg.defaultEditor { VISUAL = editorBin;
EDITOR = editorBin; };
VISUAL = editorBin;
}; home.packages = optional (cfg.client.enable && pkgs.stdenv.isLinux) (lib.hiPrio clientDesktopItem);
systemd.user.services.emacs = {
Unit = {
Description = "Emacs text editor";
Documentation = "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
After = optional (cfg.startWithUserSession == "graphical") "graphical-session.target";
PartOf = optional (cfg.startWithUserSession == "graphical") "graphical-session.target";
# Avoid killing the Emacs session, which may be full of
# unsaved buffers.
X-RestartIfChanged = false;
} }
// optionalAttrs needsSocketWorkaround {
# Emacs deletes its socket when shutting down, which systemd doesn't
# handle, resulting in a server without a socket.
# See https://github.com/nix-community/home-manager/issues/2018
RefuseManualStart = true;
};
(mkIf pkgs.stdenv.isLinux { Service = {
systemd.user.services.emacs = { Type = "notify";
Unit = {
Description = "Emacs text editor";
Documentation = "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
After = optional (cfg.startWithUserSession == "graphical") "graphical-session.target"; # We wrap ExecStart in a login shell so Emacs starts with the user's
PartOf = optional (cfg.startWithUserSession == "graphical") "graphical-session.target"; # environment, most importantly $PATH and $NIX_PROFILES. It may be
# worth investigating a more targeted approach for user services to
# import the user environment.
ExecStart = ''${pkgs.runtimeShell} -l -c "${emacsBinPath}/emacs --fg-daemon${
# In case the user sets 'server-directory' or 'server-name' in
# their Emacs config, we want to specify the socket path explicitly
# so launching 'emacs.service' manually doesn't break emacsclient
# when using socket activation.
lib.optionalString cfg.socketActivation.enable "=${lib.escapeShellArg socketPath}"
} ${lib.escapeShellArgs cfg.extraOptions}"'';
# Avoid killing the Emacs session, which may be full of # Emacs will exit with status 15 after having received SIGTERM, which
# unsaved buffers. # is the default "KillSignal" value systemd uses to stop services.
X-RestartIfChanged = false; SuccessExitStatus = 15;
}
// optionalAttrs needsSocketWorkaround {
# Emacs deletes its socket when shutting down, which systemd doesn't
# handle, resulting in a server without a socket.
# See https://github.com/nix-community/home-manager/issues/2018
RefuseManualStart = true;
};
Service = { Restart = "on-failure";
Type = "notify"; }
// optionalAttrs needsSocketWorkaround {
# Use read-only directory permissions to prevent emacs from
# deleting systemd's socket file before exiting.
ExecStartPost = "${pkgs.coreutils}/bin/chmod --changes -w ${socketDir}";
ExecStopPost = "${pkgs.coreutils}/bin/chmod --changes +w ${socketDir}";
};
}
// optionalAttrs (cfg.startWithUserSession != false) {
Install = {
WantedBy = [
(if cfg.startWithUserSession == true then "default.target" else "graphical-session.target")
];
};
};
# We wrap ExecStart in a login shell so Emacs starts with the user's systemd.user.sockets.emacs = mkIf cfg.socketActivation.enable {
# environment, most importantly $PATH and $NIX_PROFILES. It may be Unit = {
# worth investigating a more targeted approach for user services to Description = "Emacs text editor";
# import the user environment. Documentation = "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
ExecStart = ''${pkgs.runtimeShell} -l -c "${emacsBinPath}/emacs --fg-daemon${ };
# In case the user sets 'server-directory' or 'server-name' in
# their Emacs config, we want to specify the socket path explicitly
# so launching 'emacs.service' manually doesn't break emacsclient
# when using socket activation.
lib.optionalString cfg.socketActivation.enable "=${lib.escapeShellArg socketPath}"
} ${lib.escapeShellArgs cfg.extraOptions}"'';
# Emacs will exit with status 15 after having received SIGTERM, which Socket = {
# is the default "KillSignal" value systemd uses to stop services. ListenStream = socketPath;
SuccessExitStatus = 15; FileDescriptorName = "server";
SocketMode = "0600";
DirectoryMode = "0700";
# This prevents the service from immediately starting again
# after being stopped, due to the function
# `server-force-stop' present in `kill-emacs-hook', which
# calls `server-running-p', which opens the socket file.
FlushPending = true;
};
Restart = "on-failure"; Install = {
} WantedBy = [ "sockets.target" ];
// optionalAttrs needsSocketWorkaround { # Adding this Requires= dependency ensures that systemd
# Use read-only directory permissions to prevent emacs from # manages the socket file, in the case where the service is
# deleting systemd's socket file before exiting. # started when the socket is stopped.
ExecStartPost = "${pkgs.coreutils}/bin/chmod --changes -w ${socketDir}"; # The socket unit is implicitly ordered before the service.
ExecStopPost = "${pkgs.coreutils}/bin/chmod --changes +w ${socketDir}"; RequiredBy = [ "emacs.service" ];
}; };
} };
// optionalAttrs (cfg.startWithUserSession != false) {
Install = { launchd.agents.emacs = {
WantedBy = [ enable = true;
(if cfg.startWithUserSession == true then "default.target" else "graphical-session.target") config = {
]; ProgramArguments = [
}; "${cfg.package}/bin/emacs"
"--fg-daemon"
]
++ cfg.extraOptions;
RunAtLoad = true;
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
}; };
};
home.packages = optional cfg.client.enable (lib.hiPrio clientDesktopItem); };
}) };
(mkIf (cfg.socketActivation.enable && pkgs.stdenv.isLinux) {
systemd.user.sockets.emacs = {
Unit = {
Description = "Emacs text editor";
Documentation = "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
};
Socket = {
ListenStream = socketPath;
FileDescriptorName = "server";
SocketMode = "0600";
DirectoryMode = "0700";
# This prevents the service from immediately starting again
# after being stopped, due to the function
# `server-force-stop' present in `kill-emacs-hook', which
# calls `server-running-p', which opens the socket file.
FlushPending = true;
};
Install = {
WantedBy = [ "sockets.target" ];
# Adding this Requires= dependency ensures that systemd
# manages the socket file, in the case where the service is
# started when the socket is stopped.
# The socket unit is implicitly ordered before the service.
RequiredBy = [ "emacs.service" ];
};
};
})
(mkIf pkgs.stdenv.isDarwin {
launchd.agents.emacs = {
enable = true;
config = {
ProgramArguments = [
"${cfg.package}/bin/emacs"
"--fg-daemon"
]
++ cfg.extraOptions;
RunAtLoad = true;
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
};
};
})
]
);
} }

View File

@@ -15,49 +15,12 @@ let
cfg = config.services.git-sync; cfg = config.services.git-sync;
mkUnit = name: repo: { services =
Unit.Description = "Git Sync ${name}"; mkService:
lib.mapAttrs' (name: repo: {
Install.WantedBy = [ "default.target" ]; name = "git-sync-${name}";
value = mkService name repo;
Service = { }) cfg.repositories;
Environment = [
"PATH=${
lib.makeBinPath (
with pkgs;
[
openssh
git
]
++ repo.extraPackages
)
}"
"GIT_SYNC_DIRECTORY=${lib.strings.escapeShellArg repo.path}"
"GIT_SYNC_COMMAND=${cfg.package}/bin/git-sync"
"GIT_SYNC_REPOSITORY=${lib.strings.escapeShellArg repo.uri}"
"GIT_SYNC_INTERVAL=${toString repo.interval}"
];
ExecStart = "${cfg.package}/bin/git-sync-on-inotify";
Restart = "on-abort";
};
};
mkAgent = name: repo: {
enable = true;
config = {
StartInterval = repo.interval;
ProcessType = "Background";
WorkingDirectory = "${repo.path}";
WatchPaths = [ "${repo.path}" ];
ProgramArguments = [ "${cfg.package}/bin/git-sync" ];
};
};
mkService = if pkgs.stdenv.isLinux then mkUnit else mkAgent;
services = lib.mapAttrs' (name: repo: {
name = "git-sync-${name}";
value = mkService name repo;
}) cfg.repositories;
repositoryType = types.submodule ( repositoryType = types.submodule (
{ name, ... }: { name, ... }:
@@ -141,11 +104,48 @@ in
}; };
}; };
config = mkIf cfg.enable ( config = mkIf cfg.enable {
lib.mkMerge [ launchd.agents = services (
(mkIf pkgs.stdenv.isLinux { systemd.user.services = services; }) name: repo: {
(mkIf pkgs.stdenv.isDarwin { launchd.agents = services; }) enable = true;
] config = {
); StartInterval = repo.interval;
ProcessType = "Background";
WorkingDirectory = "${repo.path}";
WatchPaths = [ "${repo.path}" ];
ProgramArguments = [ "${cfg.package}/bin/git-sync" ];
};
}
);
systemd.user.services = services (
name: repo: {
Unit.Description = "Git Sync ${name}";
Install.WantedBy = [ "default.target" ];
Service = {
Environment = [
"PATH=${
lib.makeBinPath (
with pkgs;
[
openssh
git
]
++ repo.extraPackages
)
}"
"GIT_SYNC_DIRECTORY=${lib.strings.escapeShellArg repo.path}"
"GIT_SYNC_COMMAND=${cfg.package}/bin/git-sync"
"GIT_SYNC_REPOSITORY=${lib.strings.escapeShellArg repo.uri}"
"GIT_SYNC_INTERVAL=${toString repo.interval}"
];
ExecStart = "${cfg.package}/bin/git-sync-on-inotify";
Restart = "on-abort";
};
}
);
};
} }

View File

@@ -344,125 +344,115 @@ in
}; };
}; };
config = mkIf cfg.enable ( config = mkIf cfg.enable {
lib.mkMerge [ # Grab the default binary name and fallback to expected value if `meta.mainProgram` not set
{ services.gpg-agent.pinentry.program = lib.mkOptionDefault (
# Grab the default binary name and fallback to expected value if `meta.mainProgram` not set cfg.pinentry.package.meta.mainProgram or "pinentry"
services.gpg-agent.pinentry.program = lib.mkOptionDefault ( );
cfg.pinentry.package.meta.mainProgram or "pinentry"
);
home.file."${homedir}/gpg-agent.conf".text = lib.concatStringsSep "\n" ( home.file."${homedir}/gpg-agent.conf".text = lib.concatStringsSep "\n" (
optional cfg.enableSshSupport "enable-ssh-support" optional cfg.enableSshSupport "enable-ssh-support"
++ optional cfg.grabKeyboardAndMouse "grab" ++ optional cfg.grabKeyboardAndMouse "grab"
++ optional (!cfg.enableScDaemon) "disable-scdaemon" ++ optional (!cfg.enableScDaemon) "disable-scdaemon"
++ optional cfg.noAllowExternalCache "no-allow-external-cache" ++ optional cfg.noAllowExternalCache "no-allow-external-cache"
++ optional (cfg.defaultCacheTtl != null) "default-cache-ttl ${toString cfg.defaultCacheTtl}" ++ optional (cfg.defaultCacheTtl != null) "default-cache-ttl ${toString cfg.defaultCacheTtl}"
++ optional ( ++ optional (
cfg.defaultCacheTtlSsh != null cfg.defaultCacheTtlSsh != null
) "default-cache-ttl-ssh ${toString cfg.defaultCacheTtlSsh}" ) "default-cache-ttl-ssh ${toString cfg.defaultCacheTtlSsh}"
++ optional (cfg.maxCacheTtl != null) "max-cache-ttl ${toString cfg.maxCacheTtl}" ++ optional (cfg.maxCacheTtl != null) "max-cache-ttl ${toString cfg.maxCacheTtl}"
++ optional (cfg.maxCacheTtlSsh != null) "max-cache-ttl-ssh ${toString cfg.maxCacheTtlSsh}" ++ optional (cfg.maxCacheTtlSsh != null) "max-cache-ttl-ssh ${toString cfg.maxCacheTtlSsh}"
++ optional ( ++ optional (
cfg.pinentry.package != null cfg.pinentry.package != null
) "pinentry-program ${lib.getExe' cfg.pinentry.package cfg.pinentry.program}" ) "pinentry-program ${lib.getExe' cfg.pinentry.package cfg.pinentry.program}"
++ [ cfg.extraConfig ] ++ [ cfg.extraConfig ]
); );
home.sessionVariablesExtra = optionalString cfg.enableSshSupport '' home.sessionVariablesExtra = optionalString cfg.enableSshSupport ''
unset SSH_AGENT_PID unset SSH_AGENT_PID
if [ -z "$SSH_CONNECTION" -o -z "$SSH_AUTH_SOCK" ] && [ "''${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then if [ -z "$SSH_CONNECTION" -o -z "$SSH_AUTH_SOCK" ] && [ "''${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
export SSH_AUTH_SOCK="$(${gpgPkg}/bin/gpgconf --list-dirs agent-ssh-socket)" export SSH_AUTH_SOCK="$(${gpgPkg}/bin/gpgconf --list-dirs agent-ssh-socket)"
fi fi
''; '';
programs = { programs = {
bash.initExtra = mkIf cfg.enableBashIntegration gpgBashInitStr; bash.initExtra = mkIf cfg.enableBashIntegration gpgBashInitStr;
zsh.initContent = mkIf cfg.enableZshIntegration gpgZshInitStr; zsh.initContent = mkIf cfg.enableZshIntegration gpgZshInitStr;
fish.interactiveShellInit = mkIf cfg.enableFishIntegration gpgFishInitStr; fish.interactiveShellInit = mkIf cfg.enableFishIntegration gpgFishInitStr;
nushell.extraConfig = mkIf cfg.enableNushellIntegration gpgNushellInitStr; nushell.extraConfig = mkIf cfg.enableNushellIntegration gpgNushellInitStr;
};
# Trailing newlines are important
home.file."${homedir}/sshcontrol" = mkIf (cfg.sshKeys != null) {
text = lib.concatMapStrings (s: ''
${s}
'') cfg.sshKeys;
};
systemd.user = {
services.gpg-agent = {
Unit = {
Description = "GnuPG cryptographic agent and passphrase cache";
Documentation = "man:gpg-agent(1)";
Requires = "gpg-agent.socket";
After = "gpg-agent.socket";
# This is a socket-activated service:
RefuseManualStart = true;
}; };
}
(mkIf (cfg.sshKeys != null) { Service = {
# Trailing newlines are important ExecStart = "${gpgPkg}/bin/gpg-agent --supervised" + optionalString cfg.verbose " --verbose";
home.file."${homedir}/sshcontrol".text = lib.concatMapStrings (s: '' ExecReload = "${gpgPkg}/bin/gpgconf --reload gpg-agent";
${s} Environment = [ "GNUPGHOME=${homedir}" ];
'') cfg.sshKeys; };
}) };
(lib.mkMerge [ sockets = {
(mkIf pkgs.stdenv.isLinux { gpg-agent = mkSocket {
systemd.user = { desc = "GnuPG cryptographic agent and passphrase cache";
services.gpg-agent = { docs = "man:gpg-agent(1)";
Unit = { stream = "S.gpg-agent";
Description = "GnuPG cryptographic agent and passphrase cache"; fdName = "std";
Documentation = "man:gpg-agent(1)"; };
Requires = "gpg-agent.socket";
After = "gpg-agent.socket";
# This is a socket-activated service:
RefuseManualStart = true;
};
Service = { gpg-agent-ssh = mkIf cfg.enableSshSupport (mkSocket {
ExecStart = "${gpgPkg}/bin/gpg-agent --supervised" + optionalString cfg.verbose " --verbose"; desc = "GnuPG cryptographic agent (ssh-agent emulation)";
ExecReload = "${gpgPkg}/bin/gpgconf --reload gpg-agent"; docs = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)";
Environment = [ "GNUPGHOME=${homedir}" ]; stream = "S.gpg-agent.ssh";
}; fdName = "ssh";
}; });
sockets = { gpg-agent-extra = mkIf cfg.enableExtraSocket (mkSocket {
gpg-agent = mkSocket { desc = "GnuPG cryptographic agent and passphrase cache (restricted)";
desc = "GnuPG cryptographic agent and passphrase cache"; docs = "man:gpg-agent(1) man:ssh(1)";
docs = "man:gpg-agent(1)"; stream = "S.gpg-agent.extra";
stream = "S.gpg-agent"; fdName = "extra";
fdName = "std"; });
}; };
};
gpg-agent-ssh = mkIf cfg.enableSshSupport (mkSocket { launchd.agents.gpg-agent = {
desc = "GnuPG cryptographic agent (ssh-agent emulation)"; enable = true;
docs = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)"; config = {
stream = "S.gpg-agent.ssh"; ProgramArguments = [
fdName = "ssh"; "${gpgPkg}/bin/gpg-agent"
}); "--supervised"
]
gpg-agent-extra = mkIf cfg.enableExtraSocket (mkSocket { ++ optional cfg.verbose "--verbose";
desc = "GnuPG cryptographic agent and passphrase cache (restricted)"; EnvironmentVariables = {
docs = "man:gpg-agent(1) man:ssh(1)"; GNUPGHOME = homedir;
stream = "S.gpg-agent.extra"; };
fdName = "extra"; KeepAlive = {
}); Crashed = true;
}; SuccessfulExit = false;
}; };
}) ProcessType = "Background";
RunAtLoad = cfg.enableSshSupport;
(mkIf pkgs.stdenv.isDarwin { Sockets = {
launchd.agents.gpg-agent = { Agent = mkAgentSock "S.gpg-agent";
enable = true; Ssh = mkIf cfg.enableSshSupport (mkAgentSock "S.gpg-agent.ssh");
config = { Extra = mkIf cfg.enableExtraSocket (mkAgentSock "S.gpg-agent.extra");
ProgramArguments = [ };
"${gpgPkg}/bin/gpg-agent" };
"--supervised" };
] };
++ optional cfg.verbose "--verbose";
EnvironmentVariables = {
GNUPGHOME = homedir;
};
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
RunAtLoad = cfg.enableSshSupport;
Sockets = {
Agent = mkAgentSock "S.gpg-agent";
Ssh = mkIf cfg.enableSshSupport (mkAgentSock "S.gpg-agent.ssh");
Extra = mkIf cfg.enableExtraSocket (mkAgentSock "S.gpg-agent.extra");
};
};
};
})
])
]
);
} }

View File

@@ -81,46 +81,38 @@ in
}; };
}; };
config = lib.mkIf cfg.enable ( config = lib.mkIf cfg.enable {
lib.mkMerge [
(lib.mkIf pkgs.stdenv.isLinux {
systemd.user = {
timers.home-manager-auto-expire = {
Unit.Description = "Home Manager expire generations timer";
Install.WantedBy = [ "timers.target" ]; systemd.user = {
timers.home-manager-auto-expire = {
Timer = { Unit.Description = "Home Manager expire generations timer";
OnCalendar = cfg.frequency; Install.WantedBy = [ "timers.target" ];
Unit = "home-manager-auto-expire.service"; Timer = {
Persistent = true; OnCalendar = cfg.frequency;
}; Unit = "home-manager-auto-expire.service";
}; Persistent = true;
services.home-manager-auto-expire = {
Unit.Description = "Home Manager expire generations";
Service.ExecStart = toString script;
};
}; };
}) };
services.home-manager-auto-expire = {
Unit.Description = "Home Manager expire generations";
Service.ExecStart = toString script;
};
};
(lib.mkIf pkgs.stdenv.isDarwin { assertions = [
assertions = [ (lib.hm.darwin.assertInterval "services.home-manager.autoExpire.frequency" cfg.frequency pkgs)
(lib.hm.darwin.assertInterval "services.home-manager.autoExpire.frequency" cfg.frequency pkgs) ];
];
launchd.agents.home-manager-auto-expire = { launchd.agents.home-manager-auto-expire = {
enable = true; enable = true;
config = { config = {
ProgramArguments = [ (toString script) ]; ProgramArguments = [ (toString script) ];
ProcessType = "Background"; ProcessType = "Background";
StartCalendarInterval = lib.hm.darwin.mkCalendarInterval cfg.frequency; StartCalendarInterval = lib.hm.darwin.mkCalendarInterval cfg.frequency;
StandardOutPath = "${config.home.homeDirectory}/Library/Logs/home-manager-auto-expire/launchd-stdout.log"; StandardOutPath = "${config.home.homeDirectory}/Library/Logs/home-manager-auto-expire/launchd-stdout.log";
StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/home-manager-auto-expire/launchd-stderr.log"; StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/home-manager-auto-expire/launchd-stderr.log";
}; };
}; };
})
] };
);
} }

View File

@@ -182,7 +182,7 @@ in
}) })
]; ];
systemd.user = lib.mkIf pkgs.stdenv.hostPlatform.isLinux { systemd.user = {
services.mpd = { services.mpd = {
Unit = lib.mkMerge [ Unit = lib.mkMerge [
{ {
@@ -236,7 +236,7 @@ in
}; };
}; };
launchd.agents.mpd = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin { launchd.agents.mpd = {
enable = true; enable = true;
config = { config = {
ProgramArguments = [ ProgramArguments = [

View File

@@ -83,56 +83,51 @@ in
}; };
}; };
config = lib.mkIf cfg.automatic ( config = lib.mkIf cfg.automatic {
lib.mkMerge [ systemd.user.services.nix-gc = {
(lib.mkIf pkgs.stdenv.isLinux { Unit = {
systemd.user.services.nix-gc = { Description = "Nix Garbage Collector";
Unit = { };
Description = "Nix Garbage Collector"; Service = {
}; Type = "oneshot";
Service = { ExecStart = pkgs.writeShellScript "nix-gc" "exec ${nixPackage}/bin/nix-collect-garbage ${
Type = "oneshot"; lib.optionalString (cfg.options != null) cfg.options
ExecStart = pkgs.writeShellScript "nix-gc" "exec ${nixPackage}/bin/nix-collect-garbage ${ }";
lib.optionalString (cfg.options != null) cfg.options };
}"; };
};
};
systemd.user.timers.nix-gc = {
Unit = {
Description = "Nix Garbage Collector";
};
Timer = {
OnCalendar = cfg.dates;
RandomizedDelaySec = cfg.randomizedDelaySec;
Persistent = cfg.persistent;
Unit = "nix-gc.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
})
(lib.mkIf pkgs.stdenv.isDarwin { systemd.user.timers.nix-gc = {
assertions = [ Unit = {
{ Description = "Nix Garbage Collector";
assertion = (lib.length cfg.dates) == 1; };
message = "On Darwin, `nix.gc.dates` must contain a single element."; Timer = {
} OnCalendar = cfg.dates;
(lib.hm.darwin.assertInterval "nix.gc.dates.*" (lib.elemAt cfg.dates 0) pkgs) RandomizedDelaySec = cfg.randomizedDelaySec;
]; Persistent = cfg.persistent;
Unit = "nix-gc.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
launchd.agents.nix-gc = { assertions = [
enable = true; {
config = { assertion = pkgs.stdenv.isDarwin -> (lib.length cfg.dates == 1);
ProgramArguments = [ message = "On Darwin, `nix.gc.dates` must contain a single element.";
"${nixPackage}/bin/nix-collect-garbage" }
] (lib.hm.darwin.assertInterval "nix.gc.dates.*" (lib.elemAt cfg.dates 0) pkgs)
++ lib.optional (cfg.options != null) cfg.options; ];
StartCalendarInterval = lib.hm.darwin.mkCalendarInterval (lib.elemAt cfg.dates 0);
}; launchd.agents.nix-gc = {
}; enable = true;
}) config = {
] ProgramArguments = [
); "${nixPackage}/bin/nix-collect-garbage"
]
++ lib.optional (cfg.options != null) cfg.options;
StartCalendarInterval = lib.hm.darwin.mkCalendarInterval (lib.elemAt cfg.dates 0);
};
};
};
} }

View File

@@ -87,7 +87,7 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
systemd.user.services.ollama = mkIf pkgs.stdenv.isLinux { systemd.user.services.ollama = {
Unit = { Unit = {
Description = "Server for local large language models"; Description = "Server for local large language models";
After = [ "network.target" ]; After = [ "network.target" ];
@@ -105,7 +105,7 @@ in
}; };
}; };
launchd.agents.ollama = mkIf pkgs.stdenv.isDarwin { launchd.agents.ollama = {
enable = true; enable = true;
config = { config = {
ProgramArguments = [ ProgramArguments = [

View File

@@ -35,51 +35,49 @@ in
}; };
}; };
config = lib.mkIf cfg.enable ( config = lib.mkIf cfg.enable {
lib.mkMerge [ home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
{
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
}
(lib.mkIf pkgs.stdenv.isLinux {
xdg.configFile."pueue/pueue.yml".source = configFile;
systemd.user = lib.mkIf (cfg.package != null) {
services.pueued = {
Unit = {
Description = "Pueue Daemon - CLI process scheduler and manager";
};
Service = { xdg.configFile."pueue/pueue.yml" = lib.mkIf pkgs.stdenv.isLinux { source = configFile; };
Restart = "on-failure";
ExecStart = "${pueuedBin} -v -c ${configFile}";
};
Install.WantedBy = [ "default.target" ]; systemd.user = lib.mkIf (cfg.package != null) {
}; services.pueued = {
Unit = {
Description = "Pueue Daemon - CLI process scheduler and manager";
}; };
})
(lib.mkIf pkgs.stdenv.isDarwin {
# This is the default configuration file location for pueue on
# darwin (https://github.com/Nukesor/pueue/wiki/Configuration)
home.file."Library/Application Support/pueue/pueue.yml".source = configFile;
launchd.agents.pueued = lib.mkIf (cfg.package != null) {
enable = true;
config = { Service = {
ProgramArguments = [ Restart = "on-failure";
pueuedBin ExecStart = "${pueuedBin} -v -c ${configFile}";
"-v"
"-c"
"${configFile}"
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
RunAtLoad = true;
};
}; };
})
] Install.WantedBy = [ "default.target" ];
); };
};
# This is the default configuration file location for pueue on
# darwin (https://github.com/Nukesor/pueue/wiki/Configuration)
home.file."Library/Application Support/pueue/pueue.yml" = lib.mkIf pkgs.stdenv.isDarwin {
source = configFile;
};
launchd.agents.pueued = lib.mkIf (cfg.package != null) {
enable = true;
config = {
ProgramArguments = [
pueuedBin
"-v"
"-c"
"${configFile}"
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
RunAtLoad = true;
};
};
};
} }

View File

@@ -62,15 +62,13 @@ let
)) ))
]; ];
inherit (pkgs.stdenv.hostPlatform) isLinux;
# Until we have launchd support (#7924), mark the options # Until we have launchd support (#7924), mark the options
# not used in the helper script as "linux exclusive" # not used in the helper script as "linux exclusive"
linuxExclusive = linuxExclusive =
option: option:
option option
// { // {
readOnly = pkgs.stdenv.hostPlatform.isDarwin; readOnly = !pkgs.stdenv.hostPlatform.isLinux;
description = option.description + '' description = option.description + ''
@@ -393,219 +391,209 @@ in
}; };
}; };
config = lib.mkIf cfg.enable ( config = lib.mkIf cfg.enable {
lib.mkMerge [ assertions = lib.mapAttrsToList (n: v: {
{ assertion = lib.xor (v.repository == null) (v.repositoryFile == null);
assertions = lib.mapAttrsToList (n: v: { message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
assertion = lib.xor (v.repository == null) (v.repositoryFile == null); }) cfg.backups;
message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
}) cfg.backups;
}
(lib.mkIf isLinux { systemd.user.services = lib.mapAttrs' (
systemd.user.services = lib.mapAttrs' ( name: backup:
name: backup: let
let doBackup = backup.dynamicFilesFrom != null || backup.paths != [ ];
doBackup = backup.dynamicFilesFrom != null || backup.paths != [ ]; doPrune = backup.pruneOpts != [ ];
doPrune = backup.pruneOpts != [ ]; doCheck = backup.runCheck;
doCheck = backup.runCheck; serviceName = "restic-backups-${name}";
serviceName = "restic-backups-${name}";
extraOptions = lib.concatMap (arg: [ extraOptions = lib.concatMap (arg: [
"-o" "-o"
arg arg
]) backup.extraOptions; ]) backup.extraOptions;
excludeFile = pkgs.writeText "exclude-patterns" (lib.concatLines backup.exclude); excludeFile = pkgs.writeText "exclude-patterns" (lib.concatLines backup.exclude);
excludeFileFlag = "--exclude-file=${excludeFile}"; excludeFileFlag = "--exclude-file=${excludeFile}";
filesFromTmpFile = "/run/user/$UID/${serviceName}/includes"; filesFromTmpFile = "/run/user/$UID/${serviceName}/includes";
filesFromFlag = "--files-from=${filesFromTmpFile}"; filesFromFlag = "--files-from=${filesFromTmpFile}";
inhibitCmd = lib.optionals backup.inhibitsSleep [ inhibitCmd = lib.optionals backup.inhibitsSleep [
"${pkgs.systemd}/bin/systemd-inhibit" "${pkgs.systemd}/bin/systemd-inhibit"
"--mode='block'" "--mode='block'"
"--who='restic'" "--who='restic'"
"--what='idle'" "--what='idle'"
"--why=${lib.escapeShellArg "Scheduled backup ${name}"}" "--why=${lib.escapeShellArg "Scheduled backup ${name}"}"
]; ];
mkResticCmd' = mkResticCmd' =
pre: args: pre: args:
lib.concatStringsSep " " ( lib.concatStringsSep " " (
pre ++ lib.singleton (lib.getExe backup.package) ++ extraOptions ++ lib.flatten args pre ++ lib.singleton (lib.getExe backup.package) ++ extraOptions ++ lib.flatten args
); );
mkResticCmd = mkResticCmd' [ ]; mkResticCmd = mkResticCmd' [ ];
backupCmd = backupCmd =
"${lib.getExe pkgs.bash} -c " "${lib.getExe pkgs.bash} -c "
+ lib.escapeShellArg ( + lib.escapeShellArg (
mkResticCmd' inhibitCmd [ mkResticCmd' inhibitCmd [
"backup" "backup"
backup.extraBackupArgs backup.extraBackupArgs
excludeFileFlag excludeFileFlag
filesFromFlag filesFromFlag
] ]
); );
forgetCmd = mkResticCmd [ forgetCmd = mkResticCmd [
"forget" "forget"
"--prune" "--prune"
backup.pruneOpts backup.pruneOpts
]; ];
checkCmd = mkResticCmd [ checkCmd = mkResticCmd [
"check" "check"
backup.checkOpts backup.checkOpts
]; ];
unlockCmd = mkResticCmd "unlock"; unlockCmd = mkResticCmd "unlock";
in in
lib.nameValuePair serviceName { lib.nameValuePair serviceName {
Unit = { Unit = {
Description = "Restic backup service"; Description = "Restic backup service";
Wants = [ "network-online.target" ]; Wants = [ "network-online.target" ];
After = [ "network-online.target" ]; After = [ "network-online.target" ];
}; };
Service = { Service = {
Type = "oneshot"; Type = "oneshot";
X-RestartIfChanged = true; X-RestartIfChanged = true;
RuntimeDirectory = serviceName; RuntimeDirectory = serviceName;
CacheDirectory = serviceName; CacheDirectory = serviceName;
CacheDirectoryMode = "0700"; CacheDirectoryMode = "0700";
PrivateTmp = true; PrivateTmp = true;
Environment = mkEnvironment backup ++ [ "RESTIC_CACHE_DIR=%C/${serviceName}" ]; Environment = mkEnvironment backup ++ [ "RESTIC_CACHE_DIR=%C/${serviceName}" ];
ExecStart = ExecStart =
lib.optional doBackup backupCmd lib.optional doBackup backupCmd
++ lib.optionals doPrune [ ++ lib.optionals doPrune [
unlockCmd unlockCmd
forgetCmd forgetCmd
] ]
++ lib.optional doCheck checkCmd; ++ lib.optional doCheck checkCmd;
ExecStartPre = lib.getExe ( ExecStartPre = lib.getExe (
pkgs.writeShellApplication { pkgs.writeShellApplication {
name = "${serviceName}-exec-start-pre"; name = "${serviceName}-exec-start-pre";
inherit runtimeInputs; inherit runtimeInputs;
text = '' text = ''
set -x set -x
${lib.optionalString (backup.backupPrepareCommand != null) '' ${lib.optionalString (backup.backupPrepareCommand != null) ''
${pkgs.writeShellScript "backupPrepareCommand" backup.backupPrepareCommand} ${pkgs.writeShellScript "backupPrepareCommand" backup.backupPrepareCommand}
''} ''}
${lib.optionalString (backup.initialize) '' ${lib.optionalString (backup.initialize) ''
${ ${
mkResticCmd [ mkResticCmd [
"cat" "cat"
"config" "config"
] ]
} 2>/dev/null || ${mkResticCmd "init"} } 2>/dev/null || ${mkResticCmd "init"}
''} ''}
${lib.optionalString (backup.paths != null && backup.paths != [ ]) '' ${lib.optionalString (backup.paths != null && backup.paths != [ ]) ''
cat ${pkgs.writeText "staticPaths" (lib.concatLines backup.paths)} >> ${filesFromTmpFile} cat ${pkgs.writeText "staticPaths" (lib.concatLines backup.paths)} >> ${filesFromTmpFile}
''} ''}
${lib.optionalString (backup.dynamicFilesFrom != null) '' ${lib.optionalString (backup.dynamicFilesFrom != null) ''
${pkgs.writeShellScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile} ${pkgs.writeShellScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
''} ''}
''; '';
}
);
ExecStopPost = lib.getExe (
pkgs.writeShellApplication {
name = "${serviceName}-exec-stop-post";
inherit runtimeInputs;
text = ''
set -x
${lib.optionalString (backup.backupCleanupCommand != null) ''
${pkgs.writeShellScript "backupCleanupCommand" backup.backupCleanupCommand}
''}
'';
}
);
} }
// lib.optionalAttrs (backup.environmentFile != null) { );
EnvironmentFile = backup.environmentFile;
};
}
) cfg.backups;
})
(lib.mkIf isLinux { ExecStopPost = lib.getExe (
systemd.user.timers = lib.mapAttrs' ( pkgs.writeShellApplication {
name: backup: name = "${serviceName}-exec-stop-post";
lib.nameValuePair "restic-backups-${name}" { inherit runtimeInputs;
Unit.Description = "Restic backup service"; text = ''
Install.WantedBy = [ "timers.target" ]; set -x
Timer = backup.timerConfig; ${lib.optionalString (backup.backupCleanupCommand != null) ''
} ${pkgs.writeShellScript "backupCleanupCommand" backup.backupCleanupCommand}
) (lib.filterAttrs (_: v: v.timerConfig != null) cfg.backups); ''}
}) '';
}
{ );
home.packages = lib.mapAttrsToList ( }
name: backup: // lib.optionalAttrs (backup.environmentFile != null) {
let EnvironmentFile = backup.environmentFile;
serviceName = "restic-backups-${name}"; };
environment = mkEnvironment backup;
notPathVar = x: !(lib.hasPrefix "PATH" x);
extraOptions = lib.concatMap (arg: [
"-o"
arg
]) backup.extraOptions;
restic = lib.concatStringsSep " " (
lib.flatten [
(lib.getExe backup.package)
extraOptions
]
);
in
pkgs.writeShellApplication {
name = "restic-${name}";
excludeShellChecks = [
# https://github.com/koalaman/shellcheck/issues/1986
"SC2034"
# Allow sourcing environmentFile
"SC1091"
];
bashOptions = [
"errexit"
"nounset"
"allexport"
];
text = ''
${lib.optionalString (backup.environmentFile != null) ''
source ${backup.environmentFile}
''}
# Set same environment variables as the systemd service
${lib.pipe environment [
(lib.filter notPathVar)
lib.concatLines
]}
RESTIC_CACHE_DIR=${config.xdg.cacheHome}/${serviceName}
PATH=${
lib.pipe environment [
(lib.filter (lib.hasPrefix "PATH="))
lib.head
(lib.removePrefix "PATH=")
]
}:$PATH
exec ${restic} "$@"
'';
}
) (lib.filterAttrs (_: v: v.createWrapper) cfg.backups);
} }
] ) cfg.backups;
);
systemd.user.timers = lib.mapAttrs' (
name: backup:
lib.nameValuePair "restic-backups-${name}" {
Unit.Description = "Restic backup service";
Install.WantedBy = [ "timers.target" ];
Timer = backup.timerConfig;
}
) (lib.filterAttrs (_: v: v.timerConfig != null) cfg.backups);
home.packages = lib.mapAttrsToList (
name: backup:
let
serviceName = "restic-backups-${name}";
environment = mkEnvironment backup;
notPathVar = x: !(lib.hasPrefix "PATH" x);
extraOptions = lib.concatMap (arg: [
"-o"
arg
]) backup.extraOptions;
restic = lib.concatStringsSep " " (
lib.flatten [
(lib.getExe backup.package)
extraOptions
]
);
in
pkgs.writeShellApplication {
name = "restic-${name}";
excludeShellChecks = [
# https://github.com/koalaman/shellcheck/issues/1986
"SC2034"
# Allow sourcing environmentFile
"SC1091"
];
bashOptions = [
"errexit"
"nounset"
"allexport"
];
text = ''
${lib.optionalString (backup.environmentFile != null) ''
source ${backup.environmentFile}
''}
# Set same environment variables as the systemd service
${lib.pipe environment [
(lib.filter notPathVar)
lib.concatLines
]}
RESTIC_CACHE_DIR=${config.xdg.cacheHome}/${serviceName}
PATH=${
lib.pipe environment [
(lib.filter (lib.hasPrefix "PATH="))
lib.head
(lib.removePrefix "PATH=")
]
}:$PATH
exec ${restic} "$@"
'';
}
) (lib.filterAttrs (_: v: v.createWrapper) cfg.backups);
};
} }

View File

@@ -46,91 +46,85 @@ in
enableNushellIntegration = lib.hm.shell.mkNushellIntegrationOption { inherit config; }; enableNushellIntegration = lib.hm.shell.mkNushellIntegrationOption { inherit config; };
}; };
config = lib.mkIf cfg.enable ( config = lib.mkIf cfg.enable {
lib.mkMerge [
{ programs =
programs = let
socketPath =
if pkgs.stdenv.isDarwin then
"$(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}"
else
"$XDG_RUNTIME_DIR/${cfg.socket}";
# Preserve $SSH_AUTH_SOCK only if it stems from a forwarded agent which
# is the case if both $SSH_AUTH_SOCK and $SSH_CONNECTION are set.
bashIntegration = ''
if [ -z "$SSH_AUTH_SOCK" -o -z "$SSH_CONNECTION" ]; then
export SSH_AUTH_SOCK=${socketPath}
fi
'';
fishIntegration = ''
if test -z "$SSH_AUTH_SOCK"; or test -z "$SSH_CONNECTION"
set -x SSH_AUTH_SOCK ${socketPath}
end
'';
nushellIntegration =
let let
unsetOrEmpty = var: ''("${var}" not-in $env) or ($env.${var} | is-empty)'';
socketPath = socketPath =
if pkgs.stdenv.isDarwin then if pkgs.stdenv.isDarwin then
"$(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}" ''$"(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}"''
else else
"$XDG_RUNTIME_DIR/${cfg.socket}"; ''$"($env.XDG_RUNTIME_DIR)/${cfg.socket}"'';
# Preserve $SSH_AUTH_SOCK only if it stems from a forwarded agent,
# which is the case if both $SSH_AUTH_SOCK and $SSH_CONNECTION are
# set.
bashIntegration = ''
if [ -z "$SSH_AUTH_SOCK" -o -z "$SSH_CONNECTION" ]; then
export SSH_AUTH_SOCK=${socketPath}
fi
'';
fishIntegration = ''
if test -z "$SSH_AUTH_SOCK"; or test -z "$SSH_CONNECTION"
set -x SSH_AUTH_SOCK ${socketPath}
end
'';
nushellIntegration =
let
unsetOrEmpty = var: ''("${var}" not-in $env) or ($env.${var} | is-empty)'';
socketPath =
if pkgs.stdenv.isDarwin then
''$"(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}"''
else
''$"($env.XDG_RUNTIME_DIR)/${cfg.socket}"'';
in
''
if ${unsetOrEmpty "SSH_AUTH_SOCK"} or ${unsetOrEmpty "SSH_CONNECTION"} {
$env.SSH_AUTH_SOCK = ${socketPath}
}
'';
in in
{ ''
# $SSH_AUTH_SOCK has to be set early since other tools rely on it if ${unsetOrEmpty "SSH_AUTH_SOCK"} or ${unsetOrEmpty "SSH_CONNECTION"} {
bash.profileExtra = lib.mkIf cfg.enableBashIntegration (lib.mkOrder 900 bashIntegration); $env.SSH_AUTH_SOCK = ${socketPath}
fish.shellInit = lib.mkIf cfg.enableFishIntegration (lib.mkOrder 900 fishIntegration); }
nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration (lib.mkOrder 900 nushellIntegration); '';
zsh.envExtra = lib.mkIf cfg.enableZshIntegration (lib.mkOrder 900 bashIntegration); in
}; {
} # $SSH_AUTH_SOCK has to be set early since other tools rely on it
bash.profileExtra = lib.mkIf cfg.enableBashIntegration (lib.mkOrder 900 bashIntegration);
fish.shellInit = lib.mkIf cfg.enableFishIntegration (lib.mkOrder 900 fishIntegration);
nushell.extraConfig = lib.mkIf cfg.enableNushellIntegration (lib.mkOrder 900 nushellIntegration);
zsh.envExtra = lib.mkIf cfg.enableZshIntegration (lib.mkOrder 900 bashIntegration);
};
(lib.mkIf pkgs.stdenv.isLinux { systemd.user.services.ssh-agent = {
systemd.user.services.ssh-agent = { Install.WantedBy = [ "default.target" ];
Install.WantedBy = [ "default.target" ]; Unit = {
Unit = { Description = "SSH authentication agent";
Description = "SSH authentication agent"; Documentation = "man:ssh-agent(1)";
Documentation = "man:ssh-agent(1)"; };
}; Service.ExecStart = "${lib.getExe' cfg.package "ssh-agent"} -D -a %t/${cfg.socket}${
Service.ExecStart = "${lib.getExe' cfg.package "ssh-agent"} -D -a %t/${cfg.socket}${ lib.optionalString (
cfg.defaultMaximumIdentityLifetime != null
) " -t ${toString cfg.defaultMaximumIdentityLifetime}"
}";
};
launchd.agents.ssh-agent = {
enable = true;
config = {
ProgramArguments = [
(lib.getExe pkgs.bash)
"-c"
''${lib.getExe' cfg.package "ssh-agent"} -D -a "$(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}"${
lib.optionalString ( lib.optionalString (
cfg.defaultMaximumIdentityLifetime != null cfg.defaultMaximumIdentityLifetime != null
) " -t ${toString cfg.defaultMaximumIdentityLifetime}" ) " -t ${toString cfg.defaultMaximumIdentityLifetime}"
}"; }''
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
}; };
}) ProcessType = "Background";
RunAtLoad = true;
};
};
};
(lib.mkIf pkgs.stdenv.isDarwin {
launchd.agents.ssh-agent = {
enable = true;
config = {
ProgramArguments = [
(lib.getExe pkgs.bash)
"-c"
''${lib.getExe' cfg.package "ssh-agent"} -D -a "$(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}"${
lib.optionalString (
cfg.defaultMaximumIdentityLifetime != null
) " -t ${toString cfg.defaultMaximumIdentityLifetime}"
}''
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
RunAtLoad = true;
};
};
})
]
);
} }

View File

@@ -19,79 +19,71 @@ in
package = lib.mkPackageOption pkgs "yubikey-agent" { }; package = lib.mkPackageOption pkgs "yubikey-agent" { };
}; };
config = mkIf cfg.enable ( config = mkIf cfg.enable {
lib.mkMerge [ home.packages = [ cfg.package ];
{ home.packages = [ cfg.package ]; }
(mkIf pkgs.stdenv.isLinux { home.sessionVariables.SSH_AUTH_SOCK =
systemd.user.services.yubikey-agent = { if pkgs.stdenv.isDarwin then
Unit = { "/tmp/yubikey-agent.sock"
Description = "Seamless ssh-agent for YubiKeys"; else
Documentation = "https://github.com/FiloSottile/yubikey-agent"; "\${XDG_RUNTIME_DIR:-/run/user/$UID}/yubikey-agent/yubikey-agent.sock";
Requires = "yubikey-agent.socket";
After = "yubikey-agent.socket";
RefuseManualStart = true;
};
Service = { systemd.user.services.yubikey-agent = {
ExecStart = "${cfg.package}/bin/yubikey-agent -l %t/yubikey-agent/yubikey-agent.sock"; Unit = {
Type = "simple"; Description = "Seamless ssh-agent for YubiKeys";
# /run/user/$UID for the socket Documentation = "https://github.com/FiloSottile/yubikey-agent";
ReadWritePaths = [ "%t" ]; Requires = "yubikey-agent.socket";
After = "yubikey-agent.socket";
RefuseManualStart = true;
};
Service = {
ExecStart = "${cfg.package}/bin/yubikey-agent -l %t/yubikey-agent/yubikey-agent.sock";
Type = "simple";
# /run/user/$UID for the socket
ReadWritePaths = [ "%t" ];
};
};
systemd.user.sockets.yubikey-agent = {
Unit = {
Description = "Unix domain socket for Yubikey SSH agent";
Documentation = "https://github.com/FiloSottile/yubikey-agent";
};
Socket = {
ListenStream = "%t/yubikey-agent/yubikey-agent.sock";
RuntimeDirectory = "yubikey-agent";
SocketMode = "0600";
DirectoryMode = "0700";
};
Install = {
WantedBy = [ "sockets.target" ];
};
};
launchd.agents.yubikey-agent = {
enable = true;
config = {
ProgramArguments = [
"${cfg.package}/bin/yubikey-agent"
"-l"
"/tmp/yubikey-agent.sock"
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
Sockets = {
Listener = {
SockPathName = "/tmp/yubikey-agent.sock";
SockPathMode = 384; # 0600 in decimal
}; };
}; };
};
systemd.user.sockets.yubikey-agent = { };
Unit = { };
Description = "Unix domain socket for Yubikey SSH agent";
Documentation = "https://github.com/FiloSottile/yubikey-agent";
};
Socket = {
ListenStream = "%t/yubikey-agent/yubikey-agent.sock";
RuntimeDirectory = "yubikey-agent";
SocketMode = "0600";
DirectoryMode = "0700";
};
Install = {
WantedBy = [ "sockets.target" ];
};
};
home.sessionVariables = {
SSH_AUTH_SOCK = "\${XDG_RUNTIME_DIR:-/run/user/$UID}/yubikey-agent/yubikey-agent.sock";
};
})
(mkIf pkgs.stdenv.isDarwin {
launchd.agents.yubikey-agent = {
enable = true;
config = {
ProgramArguments = [
"${cfg.package}/bin/yubikey-agent"
"-l"
"/tmp/yubikey-agent.sock"
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
Sockets = {
Listener = {
SockPathName = "/tmp/yubikey-agent.sock";
SockPathMode = 384; # 0600 in decimal
};
};
};
};
home.sessionVariables = {
SSH_AUTH_SOCK = "/tmp/yubikey-agent.sock";
};
})
]
);
} }