summaryrefslogtreecommitdiff
path: root/link/ssh.sh
diff options
context:
space:
mode:
Diffstat (limited to 'link/ssh.sh')
-rwxr-xr-xlink/ssh.sh70
1 files changed, 70 insertions, 0 deletions
diff --git a/link/ssh.sh b/link/ssh.sh
new file mode 100755
index 0000000..62efb52
--- /dev/null
+++ b/link/ssh.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+# ssh client config. Dry run by default; pass `yes` to apply.
+
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=lib.sh
+source "$(dirname "$0")/lib.sh" "$@"
+
+# ~/.ssh/config itself is the entry point: it reads ~/.config/ssh/config-fleet
+# first, if another layer has linked one in, then this host's
+# ~/.config/ssh/config-local -- see config/ssh/entry. Only the file is linked;
+# ~/.ssh stays a real directory, because what else lives in it (keys,
+# known_hosts, control and agent sockets) is this host's own.
+#
+# Each step below is skipped, loudly, rather than forced, because link() starts
+# with `rm -rf`:
+# - a symlinked ~/.ssh. On the Macs it once pointed into iCloud Drive, a
+# single directory shared by two machines, so replacing a file in it on one
+# deleted it on the other.
+# - a real ~/.ssh/config holding anything but `Include ~/.config/ssh/config`,
+# the one line each host was given by hand on 2026-09-07, before this file
+# was linked. Anything else is host-specific and belongs in config-local;
+# move it there first.
+ssh_dir="$HOME/.ssh"
+if [[ -L "$ssh_dir" ]]
+then
+ red "$ssh_dir is a symlink to $(readlink "$ssh_dir"); skipping ssh links -- make it a real local directory first"
+ exit 0
+fi
+
+if [[ -z "${is_dry_run}" && ! -d "$ssh_dir" ]]
+then
+ install -d -m 700 "$ssh_dir"
+fi
+
+ssh_config_extra=""
+if [[ -f "$ssh_dir/config" && ! -L "$ssh_dir/config" ]]
+then
+ ssh_config_extra="$(grep -vE '^[[:space:]]*(#|$)' "$ssh_dir/config" \
+ | grep -vxF 'Include ~/.config/ssh/config' || true)"
+fi
+if [[ -n "$ssh_config_extra" ]]
+then
+ red "$ssh_dir/config has host-specific lines; move them to ~/.config/ssh/config-local, then re-run"
+else
+ link "$config_dir/ssh/entry" "$ssh_dir/config"
+fi
+
+# Links this repo made while host blocks and their key pins were tracked here,
+# now that another layer owns them: only a dangling link in ~/.config/ssh that
+# points into this repo's config/ssh is removed -- never a real file, and never
+# a link someone else made. Compared as real paths: the link holds whatever
+# spelling this repo had when it was made, which on a machine whose checkout is
+# reached through a symlink is not this run's.
+ssh_repo_dir="$(realpath "$config_dir/ssh")"
+for stale in "$xdg_config_dir"/ssh/*
+do
+ if [[ -L "$stale" && ! -e "$stale" ]] \
+ && [[ "$(realpath "$(dirname "$(readlink "$stale")")" 2>/dev/null)" == "$ssh_repo_dir" ]]
+ then
+ if [[ -n "${is_dry_run}" ]]
+ then
+ yellow "$stale is a dangling link into this repo and will be removed"
+ else
+ rm "$stale"
+ fi
+ fi
+done