diff options
Diffstat (limited to 'link/ssh.sh')
| -rwxr-xr-x | link/ssh.sh | 70 |
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 |
