Add SSH Agent (gcr-ssh-agent) as an optional service install (#9399)

* Add SSH Agent as an optional service install

Enables gcr-ssh-agent (already shipped via the gnome-keyring dependency)
so passphrase-protected SSH keys work from any context: the agent prompts
graphically on first use, with opt-in passphrase storage in the keyring.
Adds install/remove commands and Install > Service / Remove > Service
menu entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BReZWvvLsDRUaqgjiRzvZ5

* Clear the agent's SSH_AUTH_SOCK when removing the SSH agent service

The socket's ExecStartPost exports SSH_AUTH_SOCK into the user manager with set-environment, which disabling the socket does not undo, so apps launched after removal still pointed at a stopped agent. The environment.d file also has to be deleted before disable reloads the manager, or the generator re-exports it. Only an SSH_AUTH_SOCK pointing at gcr is cleared, so another agent's stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move SSH Agent setup to Setup > Security

Turning on an SSH agent is security configuration of the machine rather than installing software, so it sits with SSHD and the other Setup > Security entries. It hides once enabled, as Sudoless Docker does, and Remove > Service > SSH Agent turns it back off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
This commit is contained in:
authored and GitHub committed 2026-09-28 08:46:30 +02:00
1 parent 3faafba234
commit 324f0ba5e6
4 files changed
+89

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# omarchy:summary=Disable the gcr-ssh-agent SSH agent and its environment override.
# The file goes before disable reloads the manager, or the environment generator keeps exporting it.
rm -f "$HOME/.config/environment.d/90-gcr-ssh-agent.conf"
systemctl --user disable --now gcr-ssh-agent.socket 2>/dev/null || true
systemctl --user stop gcr-ssh-agent.service 2>/dev/null || true
# The socket exported SSH_AUTH_SOCK to the user manager, which outlives the agent.
if systemctl --user show-environment | grep -Fqx "SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh"; then
systemctl --user unset-environment SSH_AUTH_SOCK
fi
echo ""
echo "SSH agent has been disabled. Log out and back in for it to take full effect."
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# omarchy:summary=Enable an SSH agent (gcr-ssh-agent) that prompts for key passphrases graphically.
set -e
ENV_FILE="$HOME/.config/environment.d/90-gcr-ssh-agent.conf"
systemctl --user enable --now gcr-ssh-agent.socket
mkdir -p "$(dirname "$ENV_FILE")"
echo 'SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh' >"$ENV_FILE"
echo ""
echo "SSH agent has been enabled. Log out and back in to make it available everywhere."
echo "The first use of a passphrase-protected key opens a dialog, with the option to"
echo "remember the passphrase in the keyring."
+2
View File
@@ -183,6 +183,7 @@
"setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","when":"omarchy-hw-fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"},
"setup.security.fido2": {"icon":"","label":"Fido2","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
"setup.security.sshd": {"icon":"󰣀","label":"SSHD","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sshd"},
"setup.security.ssh-agent": {"icon":"󰣀","label":"SSH Agent","when":"! systemctl --user -q is-enabled gcr-ssh-agent.socket","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-ssh-agent"},
"setup.security.passwordless-sudo": {"icon":"󰟵","label":"Passwordless Sudo","action":"omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless"},
"setup.security.sudoless-docker": {"icon":"󰡨","label":"Sudoless Docker","when":"omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
@@ -310,6 +311,7 @@
"remove.browser.zen": {"icon":"","label":"Zen","when":"omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser zen'"},
"remove.service.dropbox": {"icon":"","label":"Dropbox","when":"omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-dropbox"},
"remove.service.tailscale": {"icon":"","label":"Tailscale","when":"omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-tailscale"},
"remove.service.ssh-agent": {"icon":"󰣀","label":"SSH Agent","when":"systemctl --user -q is-enabled gcr-ssh-agent.socket","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-ssh-agent"},
"remove.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-chatgpt"},
"remove.ai.claude": {"icon":"","iconFont":"omarchy","label":"Claude Desktop","when":"omarchy-pkg-present claude-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-claude"},
"remove.ai.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
mock_bin="$test_tmp/bin"
mkdir -p "$mock_bin"
# A user manager with systemd's two environment blocks: set-environment's, which
# outlives the socket that wrote it, and the generator's, re-read from
# environment.d on every reload, which disable triggers.
cat >"$mock_bin/systemctl" <<'SH'
#!/bin/bash
state=$OMARCHY_TEST_MANAGER
case $2 in
disable)
if [[ -f $HOME/.config/environment.d/90-gcr-ssh-agent.conf ]]; then
echo "SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh" >"$state/generated"
else
: >"$state/generated"
fi
;;
unset-environment) : >"$state/explicit" ;;
show-environment) cat "$state/generated" "$state/explicit" ;;
esac
exit 0
SH
chmod +x "$mock_bin/systemctl"
run_remove() {
local explicit="$1"
export HOME="$test_tmp/home" XDG_RUNTIME_DIR="$test_tmp/run" OMARCHY_TEST_MANAGER="$test_tmp/manager"
rm -rf "$HOME" "$OMARCHY_TEST_MANAGER"
mkdir -p "$HOME/.config/environment.d" "$OMARCHY_TEST_MANAGER"
echo 'SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh' >"$HOME/.config/environment.d/90-gcr-ssh-agent.conf"
echo "SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/gcr/ssh" >"$OMARCHY_TEST_MANAGER/generated"
echo "$explicit" >"$OMARCHY_TEST_MANAGER/explicit"
PATH="$mock_bin:$PATH" "$ROOT/bin/omarchy-remove-service-ssh-agent" >/dev/null
PATH="$mock_bin:$PATH" systemctl --user show-environment
}
env_after=$(run_remove "SSH_AUTH_SOCK=$test_tmp/run/gcr/ssh")
[[ -z $env_after ]] || fail "removal leaves no SSH_AUTH_SOCK pointing at the stopped agent" "$env_after"
pass "removal leaves no SSH_AUTH_SOCK pointing at the stopped agent"
env_after=$(run_remove "SSH_AUTH_SOCK=$test_tmp/run/other-agent.sock")
[[ $env_after == "SSH_AUTH_SOCK=$test_tmp/run/other-agent.sock" ]] || fail "removal keeps an SSH_AUTH_SOCK another agent set" "$env_after"
pass "removal keeps an SSH_AUTH_SOCK another agent set"