service.proton-pass-agent: init module

This commit is contained in:
Thierry Delafontaine 2026-01-10 19:33:24 +01:00 committed by Austin Horstman
parent 7213f7ee3e
commit 85e3ee7e59
12 changed files with 358 additions and 0 deletions

View file

@ -0,0 +1,143 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.proton-pass-agent;
in
{
meta.maintainers = [ lib.maintainers.delafthi ];
options.services.proton-pass-agent = {
enable = lib.mkEnableOption "Proton Pass as a SSH agent";
package = lib.mkPackageOption pkgs "proton-pass-cli" { };
socket = lib.mkOption {
type = lib.types.str;
default = "proton-pass-agent";
example = "proton-pass-agent/socket";
description = ''
The agent's socket; interpreted as a suffix to {env}`$XDG_RUNTIME_DIR`
on Linux and `$(getconf DARWIN_USER_TEMP_DIR)` on macOS. This option
adds the `--socket-path` argument to the command.
'';
};
extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [
"--share-id"
"--vault-name"
"MySshKeyVault"
"--refresh-interval"
"7200"
];
description = ''
Options given to `pass-cli ssh-agent shart` when the service is run.
See <https://protonpass.github.io/pass-cli/commands/ssh-agent/#passphrase-protected-ssh-keys>
for more information.
'';
};
enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { inherit config; };
enableZshIntegration = lib.hm.shell.mkZshIntegrationOption { inherit config; };
enableFishIntegration = lib.hm.shell.mkFishIntegrationOption { inherit config; };
enableNushellIntegration = lib.hm.shell.mkNushellIntegrationOption { inherit config; };
};
config =
let
socketPath =
if pkgs.stdenv.isDarwin then
"$(${lib.getExe pkgs.getconf} DARWIN_USER_TEMP_DIR)/${cfg.socket}"
else
"$XDG_RUNTIME_DIR/${cfg.socket}";
cmd = [
"${lib.getExe' cfg.package "pass-cli"}"
"ssh-agent"
"start"
"--socket-path"
"${if pkgs.stdenv.isDarwin then socketPath else "%t/${cfg.socket}"}"
]
++ cfg.extraArgs;
in
lib.mkIf cfg.enable {
home.packages = [ cfg.package ];
programs =
let
# 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
{
# $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);
};
systemd.user.services.proton-pass-agent = {
Install.WantedBy = [ "default.target" ];
Unit = {
Description = "Proton Pass SSH agent";
Documentation = "https://protonpass.github.io/pass-cli/commands/ssh-agent/#ssh-agent-integration";
};
Service = {
ExecStart = lib.concatStringsSep " " cmd;
KeyringMode = "shared";
};
};
launchd.agents.proton-pass-agent = {
enable = true;
config = {
ProgramArguments = [
(lib.getExe pkgs.bash)
"-c"
(lib.concatStringsSep " " cmd)
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
RunAtLoad = true;
StandardOutPath = "${config.home.homeDirectory}/Library/Logs/Proton Pass CLI/ssh-agent-stdout.log";
StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/Proton Pass CLI/ssh-agent-stderr.log";
};
};
};
}

View file

@ -140,6 +140,7 @@ let
"pls"
"poetry"
"powerline-go"
"proton-pass-cli"
"pubs"
"pyenv"
"qcal"

View file

@ -0,0 +1,23 @@
{ pkgs, ... }:
{
services.proton-pass-agent = {
enable = true;
enableBashIntegration = true;
};
programs.bash.enable = true;
nmt.script = ''
bash_profile=home-files/.profile
assertFileContains $bash_profile \
'if [ -z "$SSH_AUTH_SOCK" -o -z "$SSH_CONNECTION" ]; then'
assertFileContains $bash_profile \
'export SSH_AUTH_SOCK=${
if pkgs.stdenv.hostPlatform.isDarwin then
"$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)"
else
"$XDG_RUNTIME_DIR"
}/proton-pass-agent'
'';
}

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>KeepAlive</key>
<dict>
<key>Crashed</key>
<true/>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>Label</key>
<string>org.nix-community.home.proton-pass-agent</string>
<key>ProcessType</key>
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>-c</string>
<string>@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path $(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/proton-pass-agent/socket</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/home/hm-user/Library/Logs/Proton Pass CLI/ssh-agent-stderr.log</string>
<key>StandardOutPath</key>
<string>/home/hm-user/Library/Logs/Proton Pass CLI/ssh-agent-stdout.log</string>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
[Install]
WantedBy=default.target
[Service]
ExecStart=@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path %t/proton-pass-agent/socket
KeyringMode=shared
[Unit]
Description=Proton Pass SSH agent
Documentation=https://protonpass.github.io/pass-cli/commands/ssh-agent/#ssh-agent-integration

View file

@ -0,0 +1,23 @@
{ config, pkgs, ... }:
{
services.proton-pass-agent = {
enable = true;
socket = "proton-pass-agent/socket";
};
nmt.script =
if pkgs.stdenv.hostPlatform.isDarwin then
''
plistFile=LaunchAgents/org.nix-community.home.proton-pass-agent.plist
assertFileExists $plistFile
assertFileContent $plistFile ${./basic-service-expected.plist}
''
else
''
serviceFile=home-files/.config/systemd/user/proton-pass-agent.service
assertFileExists $serviceFile
assertFileContent $serviceFile ${./basic-service-expected.service}
'';
}

View file

@ -0,0 +1,7 @@
{
proton-pass-agent-basic-service = ./basic-service.nix;
proton-pass-agent-full-service = ./full-service.nix;
proton-pass-agent-bash-integration = ./bash-integration.nix;
proton-pass-agent-fish-integration = ./fish-integration.nix;
proton-pass-agent-nushell-integration = ./nushell-integration.nix;
}

View file

@ -0,0 +1,23 @@
{ pkgs, ... }:
{
services.proton-pass-agent = {
enable = true;
enableBashIntegration = true;
};
programs.fish.enable = true;
nmt.script = ''
fish_config=home-files/.config/fish/config.fish
assertFileContains $fish_config \
'if test -z "$SSH_AUTH_SOCK"; or test -z "$SSH_CONNECTION'
assertFileContains $fish_config \
'set -x SSH_AUTH_SOCK ${
if pkgs.stdenv.hostPlatform.isDarwin then
"$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)"
else
"$XDG_RUNTIME_DIR"
}/proton-pass-agent'
'';
}

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>KeepAlive</key>
<dict>
<key>Crashed</key>
<true/>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>Label</key>
<string>org.nix-community.home.proton-pass-agent</string>
<key>ProcessType</key>
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>-c</string>
<string>@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path $(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/proton-pass-agent/socket --share-id 123456789 --vault-name MySshKeyVault --refresh-interval 7200 --create-new-identities MySshKeyVault</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/home/hm-user/Library/Logs/Proton Pass CLI/ssh-agent-stderr.log</string>
<key>StandardOutPath</key>
<string>/home/hm-user/Library/Logs/Proton Pass CLI/ssh-agent-stdout.log</string>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
[Install]
WantedBy=default.target
[Service]
ExecStart=@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path %t/proton-pass-agent/socket --share-id 123456789 --vault-name MySshKeyVault --refresh-interval 7200 --create-new-identities MySshKeyVault
KeyringMode=shared
[Unit]
Description=Proton Pass SSH agent
Documentation=https://protonpass.github.io/pass-cli/commands/ssh-agent/#ssh-agent-integration

View file

@ -0,0 +1,33 @@
{ config, pkgs, ... }:
{
services.proton-pass-agent = {
enable = true;
socket = "proton-pass-agent/socket";
extraArgs = [
"--share-id"
"123456789"
"--vault-name"
"MySshKeyVault"
"--refresh-interval"
"7200"
"--create-new-identities"
"MySshKeyVault"
];
};
nmt.script =
if pkgs.stdenv.hostPlatform.isDarwin then
''
plistFile=LaunchAgents/org.nix-community.home.proton-pass-agent.plist
assertFileExists $plistFile
assertFileContent $plistFile ${./full-service-expected.plist}
''
else
''
serviceFile=home-files/.config/systemd/user/proton-pass-agent.service
assertFileExists $serviceFile
assertFileContent $serviceFile ${./full-service-expected.service}
'';
}

View file

@ -0,0 +1,27 @@
{ pkgs, ... }:
{
services.proton-pass-agent = {
enable = true;
enableNushellIntegration = true;
};
programs.nushell.enable = true;
nmt.script =
let
unsetOrEmpty = var: ''("${var}" not-in $env) or ($env.${var} | is-empty)'';
in
''
nu_config=home-files/.config/nushell/config.nu
assertFileContains $nu_config \
'if ${unsetOrEmpty "SSH_AUTH_SOCK"} or ${unsetOrEmpty "SSH_CONNECTION"} {'
assertFileContains $nu_config \
'$env.SSH_AUTH_SOCK = $"${
if pkgs.stdenv.hostPlatform.isDarwin then
"(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)"
else
"($env.XDG_RUNTIME_DIR)"
}/proton-pass-agent"'
'';
}