From 2afdfd093b9f391694533b1410bdc2936c56cf98 Mon Sep 17 00:00:00 2001 From: bcherb2 Date: Mon, 17 Aug 2026 22:52:11 -0400 Subject: [PATCH] fix: accept either bootstrap URL shape, in dotup and in the harness Three files each appended /bootstrap.env to a value whose shape nobody had pinned down, and they did not agree: - the rotation scripts print the FULL file URL and say to store THAT in Bitwarden, so that is what gets pasted - dotup asked for the directory and appended /bootstrap.env itself - .tests/e2e.sh independently duplicated dotup's assumption Pasting the saved value made the request .../bootstrap.env/bootstrap.env. The 404 tripped curl --fail and surfaced as "endpoint refused the credentials. The machine stays public-only" -- blaming the password for a URL shape, which is about the most expensive wrong error message this path could produce. dotup and the wrapper now both strip a trailing slash and a trailing /bootstrap.env before building the request, so either form works and the existing Bitwarden entry needs no editing. Four assertions cover all four shapes -- directory and file URL, each with and without a trailing slash. They eval the normalisation lifted straight out of dotup rather than a copy, and that binding was mutation-tested: deleting the line from dotup makes them fail with exactly the production symptom, https://h/r/bootstrap.env/bootstrap.env. 101 -> 105. The second instance, in e2e.sh, was found only by running the harness with the file URL instead of the directory. Every earlier run passed because it was fed the shape that happened to be in a variable, not the shape a person has in a password manager. Both shapes now run end to end and pass. The normalisation exists in two places because the harness cannot source dotup's internals. That is the same duplication that caused this, and only dotup's copy is covered by the suite. --- .tests/e2e.sh | 9 +++++++++ .tests/test.sh | 15 +++++++++++++++ dot_local/bin/executable_dotup | 7 +++++++ 3 files changed, 31 insertions(+) diff --git a/.tests/e2e.sh b/.tests/e2e.sh index 8878a4e..95a1d40 100755 --- a/.tests/e2e.sh +++ b/.tests/e2e.sh @@ -40,6 +40,15 @@ trap cleanup EXIT INT TERM printf 'Bootstrap URL (paste from Bitwarden): ' >&2 IFS= read -r BOOT_URL [ -n "$BOOT_URL" ] || { echo "no URL given"; exit 1; } +# Normalise exactly as dotup does, and for the same reason: the rotation scripts +# print the full file URL, so that is what Bitwarden holds and what gets pasted. +# This wrapper appends /bootstrap.env below, so without stripping it first the +# request becomes .../bootstrap.env/bootstrap.env and the preflight reports +# "wrong password, wrong route, or endpoint down" -- blaming the credentials for +# a URL shape, which is the precise failure dotup was just fixed to stop doing. +# Caught by running this harness with the file URL rather than the directory. +BOOT_URL=${BOOT_URL%/} +BOOT_URL=${BOOT_URL%/bootstrap.env} printf 'Username [ben]: ' >&2 IFS= read -r BOOT_USER diff --git a/.tests/test.sh b/.tests/test.sh index 17c181c..4404990 100755 --- a/.tests/test.sh +++ b/.tests/test.sh @@ -162,6 +162,21 @@ is "…and leaves a credential-free remote untouched" \ is "…including scp-style ssh" \ " git@git.example.com:ben/x.git" \ "$(split 'git@git.example.com:ben/x.git')" +# The bootstrap URL a human pastes comes from Bitwarden, where the rotation +# scripts put the FULL file URL. dotup appends /bootstrap.env itself, so without +# normalisation that becomes .../bootstrap.env/bootstrap.env -- a 404 that +# curl --fail turns into "endpoint refused the credentials", blaming the +# password for a URL shape. Both forms must land on the same request. +norm() { P_URL=$1; eval "$(sed -n '/^\tP_URL=\${P_URL%\//p;/^\tP_URL=\${P_URL%\/bootstrap.env}/p' $D)" + printf '%s\n' "${P_URL%/}/bootstrap.env"; } +is "a directory URL resolves to the blob" "https://h/r/bootstrap.env" \ + "$(norm 'https://h/r/')" +is "...without a trailing slash too" "https://h/r/bootstrap.env" \ + "$(norm 'https://h/r')" +is "...and the full file URL, which is what Bitwarden holds" "https://h/r/bootstrap.env" \ + "$(norm 'https://h/r/bootstrap.env')" +is "...even with a trailing slash on the file URL" "https://h/r/bootstrap.env" \ + "$(norm 'https://h/r/bootstrap.env/')" rd() { eval "$(sed -n '/^redact_url()/p' $D)"; redact_url "$1"; } is "redact_url strips userinfo" "https://@git.example.com/x.git" \ "$(rd 'https://ben:deadbeefcafe@git.example.com/x.git')" diff --git a/dot_local/bin/executable_dotup b/dot_local/bin/executable_dotup index 9754786..f8adbc1 100755 --- a/dot_local/bin/executable_dotup +++ b/dot_local/bin/executable_dotup @@ -784,6 +784,13 @@ cmd_private() { P_URL=''; P_USER=''; P_PW='' printf ' Bootstrap URL: ' >&2; IFS= read -r P_URL || : [ -n "$P_URL" ] || { say " public-only machine. Nothing was asked for."; return 0; } + # Accept the directory OR the full file URL, because both are in circulation: + # the rotation scripts print the file form and say to store THAT in Bitwarden, + # so pasting what you saved is the likely case. Without this, the request + # becomes .../bootstrap.env/bootstrap.env, the 404 trips curl --fail, and the + # error below blames the credentials for what is actually a URL shape. + P_URL=${P_URL%/} + P_URL=${P_URL%/bootstrap.env} printf ' Username: ' >&2; IFS= read -r P_USER || : printf ' Password: ' >&2 stty -echo 2>/dev/null || :; IFS= read -r P_PW || :; stty echo 2>/dev/null || :; printf '\n' >&2