config/bash/shell exported SSH_ASKPASS=ksshaskpass with SSH_ASKPASS_REQUIRE=prefer on every machine. `prefer` means ssh and git reach for that dialog BEFORE the terminal prompt, so on a headless server the prompt that would have worked was skipped in favour of a KDE window that cannot open: error: /usr/bin/ksshaskpass died of signal 6 fatal: could not read Username for 'https://git.gbrown.org' A push from a server failed to ASK for a username rather than failing to authenticate. The shell environment is the one half of Panama a server does install, so nothing downstream was going to catch this. The export is now conditional on a session existing to draw in, and on the binary being there to run. The headless branch also CLEARS both variables rather than merely declining to set them: an askpass inherited from a parent that had a display, or from a login predating this guard, is exactly as unusable and a guard that only withholds leaves it in place. A fresh login shell on the repaired server still reported prefer until it cleared them. A desktop is unchanged; a server, a serial console and a plain SSH session fall back to the terminal prompt, which works. tests/setup/headless-askpass-contract pins every branch, the inherited case included, and fails against the previous code and against a guard that only withholds. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
84 lines
3.8 KiB
Bash
Executable File
84 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# The graphical askpass, and the machines that have no graphics.
|
|
#
|
|
# config/bash/shell used to export SSH_ASKPASS=ksshaskpass with
|
|
# SSH_ASKPASS_REQUIRE=prefer unconditionally. `prefer` means ssh and git reach
|
|
# for that dialog BEFORE the terminal prompt, so on a server the prompt that
|
|
# would have worked was skipped in favour of a KDE window that cannot open:
|
|
#
|
|
# error: /usr/bin/ksshaskpass died of signal 6
|
|
# fatal: could not read Username for 'https://...'
|
|
#
|
|
# A `git push` from a headless box failed to ASK for a username rather than
|
|
# failing to authenticate -- and the shell environment is the one half of
|
|
# Panama a server does install, so nothing else was going to catch it.
|
|
#
|
|
# Pinned here: headless sessions never prefer a dialog, and a desktop with the
|
|
# binary present still gets one.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
shell_file="$repo_dir/config/bash/shell"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT
|
|
mkdir -p "$work/home"
|
|
|
|
# Sources the shell environment in a sandbox and reports one variable from it.
|
|
# HOME is empty so the personal env file is not read, and the session is
|
|
# non-interactive with no SSH_CONNECTION so the tmux hand-off cannot fire.
|
|
askpass_state() {
|
|
env -u DISPLAY -u WAYLAND_DISPLAY -u SSH_CONNECTION -u SSH_ASKPASS \
|
|
-u SSH_ASKPASS_REQUIRE "$@" \
|
|
HOME="$work/home" PANAMA_PATH="$repo_dir" \
|
|
bash -c 'source "$1" >/dev/null 2>&1
|
|
printf "%s|%s" "${SSH_ASKPASS:-}" "${SSH_ASKPASS_REQUIRE:-}"' \
|
|
_ "$shell_file"
|
|
}
|
|
|
|
# ── Headless is the case that broke ──────────────────────────────────────────
|
|
got="$(askpass_state)"
|
|
[[ "$got" == "|" ]] \
|
|
|| note "a headless session still prefers a graphical askpass (got '$got')"
|
|
|
|
got="$(askpass_state SSH_CONNECTION='10.0.0.1 22 10.0.0.2 22')"
|
|
[[ "$got" == "|" ]] \
|
|
|| note "an SSH session still prefers a graphical askpass (got '$got')"
|
|
|
|
# The case the first version of this contract missed: declining to SET the
|
|
# variables does nothing about ones already in the environment, inherited from
|
|
# a parent that had a display or from a login predating the guard. A fresh
|
|
# login shell on the repaired server still reported prefer until the headless
|
|
# branch cleared them outright, so the clearing is what gets pinned.
|
|
got="$(askpass_state SSH_ASKPASS=/usr/bin/ksshaskpass SSH_ASKPASS_REQUIRE=prefer)"
|
|
[[ "$got" == "|" ]] \
|
|
|| note "an inherited askpass survived a headless session (got '$got')"
|
|
|
|
# ── A desktop still gets its dialog ──────────────────────────────────────────
|
|
# Only assertable where the binary exists; saying so beats asserting that a
|
|
# machine without KDE installed fails to configure KDE's dialog.
|
|
if [[ -x /usr/bin/ksshaskpass ]]; then
|
|
got="$(askpass_state WAYLAND_DISPLAY=wayland-0)"
|
|
[[ "$got" == "/usr/bin/ksshaskpass|prefer" ]] \
|
|
|| note "a Wayland session does not prefer the graphical askpass (got '$got')"
|
|
|
|
got="$(askpass_state DISPLAY=:0)"
|
|
[[ "$got" == "/usr/bin/ksshaskpass|prefer" ]] \
|
|
|| note "an X session does not prefer the graphical askpass (got '$got')"
|
|
else
|
|
printf 'headless askpass contract: ksshaskpass is not installed, so the desktop half was not verified\n' >&2
|
|
fi
|
|
|
|
# ── Report ───────────────────────────────────────────────────────────────────
|
|
if (( ${#findings[@]} > 0 )); then
|
|
printf 'headless askpass contract FAILED:\n' >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
printf 'headless askpass contract passed\n'
|