From 9ed7c27dbb9772b021d005c413ad7885112f525d Mon Sep 17 00:00:00 2001 From: Asad Iqbal Date: Wed, 15 Jul 2026 15:01:10 +0500 Subject: [PATCH] =?UTF-8?q?feat(installer):=20stop=20prompting=20for=20loc?= =?UTF-8?q?ation=20during=20register=20=E2=80=94=20auto-derive=20from=20ti?= =?UTF-8?q?mezone=20(#354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Register this machine" step prompted for both a name and a location zone. The location prompt was an explicit INTERIM measure, kept only because the deployed CLI hard-required --name/--location without a TTY. cli#137 has since shipped (CLI v0.9.2 / main; the installer pulls the CLI from releases/latest): `client create` auto-names -NN and treats --location as fully optional — never prompted, never required, sends nothing when absent so the backend records the client with no location. The blocker is gone. Now: prompt for the name only. Derive the carbon zone from the system timezone silently (no network); when detection yields nothing, provision with no --location rather than asking. A pinned TRACEBLOC_CLIENT_LOCATION still overrides for unattended installs. - provision.sh: drop the interactive location block (detected-zone accept + required-entry loop); refresh the interim comments and the _report_create_failure hint that referenced the removed prompt. - provision.bats: add coverage for the two new paths (auto-derive from timezone; no-detection → no --location, never prompts). - manifest.sha256: regenerated for the provision.sh change (R8 gate). Co-Authored-By: Claude Opus 4.8 --- scripts/lib/provision.sh | 65 ++++++++++-------------------------- scripts/manifest.sha256 | 2 +- scripts/tests/provision.bats | 40 ++++++++++++++++++++-- 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/scripts/lib/provision.sh b/scripts/lib/provision.sh index 8766653..a6021b5 100644 --- a/scripts/lib/provision.sh +++ b/scripts/lib/provision.sh @@ -83,8 +83,9 @@ _report_create_failure() { echo "" if grep -qiE 'location.*not a valid choice' "$out" 2>/dev/null; then warn "\"${loc:-that location}\" isn't a recognized carbon zone — the client wasn't created." - hint "Re-run and press Enter to accept the detected zone, or use a zone code" - hint "like DE, FR, US, GB (all codes: https://api.electricitymap.org/v3/zones)." + hint "The zone is auto-derived from this machine's timezone; to pin one, set" + hint "TRACEBLOC_CLIENT_LOCATION= (e.g. DE, FR, US, GB — all codes:" + hint "https://api.electricitymap.org/v3/zones) and re-run." return 0 fi errline="$(grep -aE 'Error:|HTTP [0-9][0-9][0-9]|refused|timed? ?out|unauthorized|forbidden|denied' "$out" 2>/dev/null | head -4)" @@ -231,18 +232,17 @@ provision_client() { # between mint and the explicit removal below — the secret must never linger. _PROVISION_CRED_FILE="$cred_file" rm -f "$cred_file" - # Name + location for this machine (RFC-0001 §6.4: "name this machine + confirm - # its location"). `client create` would prompt for these, but we redirect its - # output to the log below (the credential must never reach the terminal), so it - # can't — and it hard-requires --name when it can't prompt. Collect them here and - # pass explicitly. Precedence: env override (unattended) > interactive prompt on - # /dev/tty (works under `curl | bash`, whose stdin isn't the terminal) > fail closed. + # Name this machine, then provision. `client create` would prompt for the name + # itself, but we redirect its output to the log below (the credential must never + # reach the terminal), so it can't — collect the name here and pass it explicitly. + # Precedence: TRACEBLOC_CLIENT_NAME (unattended) > interactive prompt on /dev/tty + # (works under `curl | bash`, whose stdin isn't the terminal) > fail closed. # - # INTERIM — KEEP THIS PROMPT. The locked run-through drops the name/location - # question (machine auto-named -NN, location auto-derived), but the - # DEPLOYED CLI still hard-requires --name without a TTY (backend#992), so - # dropping it now would break `curl | bash`. Slated for removal when cli#137 - # ships the auto-name + optional-location `client create`; until then it stays. + # Location is NEVER prompted (RFC-0001 §6.4 target spec; cli#137). The CLI's + # `client create` now treats --location as optional, so we auto-derive the carbon + # zone from the system timezone below (silent, no network) and, when detection + # yields nothing, provision with NO location rather than asking. A pinned + # TRACEBLOC_CLIENT_LOCATION still overrides for unattended installs. local client_name="${TRACEBLOC_CLIENT_NAME:-}" client_location="${TRACEBLOC_CLIENT_LOCATION:-}" if [[ -z "$client_name" ]] && _prompt_tty; then # Read the name from the terminal, RETRYING on an empty line instead of @@ -261,35 +261,6 @@ provision_client() { [[ -n "$client_name" ]] && break # captured a name (incl. a no-newline partial) [[ "$_name_read_ok" == 0 ]] && break # EOF / no interactive input — retrying won't help done - if [[ -z "$client_location" ]]; then - local _loc_detect _loc_zone _loc_tz _loc_try - _loc_detect="$(_detect_location_zone)" - _loc_zone="${_loc_detect%% *}"; _loc_tz="${_loc_detect#* }" - if [[ -n "$_loc_zone" ]]; then - printf ' Location for carbon reporting — detected %s (from your timezone %s).\n' "$_loc_zone" "$_loc_tz" >/dev/tty 2>/dev/null || true - printf ' Press Enter to use it, or type another zone code: ' >/dev/tty 2>/dev/null || true - IFS= read -r client_location <"$TB_TTY" || true - # Trim BEFORE the non-empty check so a whitespace-only answer counts as - # "just pressed Enter" and falls back to the detected zone. - client_location="${client_location#"${client_location%%[![:space:]]*}"}"; client_location="${client_location%"${client_location##*[![:space:]]}"}" - [[ -n "$client_location" ]] || client_location="$_loc_zone" - else - # No detection on this machine. The released CLI still REQUIRES a zone - # (client create fails without --location when it can't prompt), so a - # blank answer here would doom the create — ask until non-empty. - # Optional-location ships with the next CLI release; then this prompt - # goes away entirely (spec: silent, no location). - for _loc_try in 1 2 3; do - printf ' Location zone for carbon reporting (e.g. DE): ' >/dev/tty 2>/dev/null || true - IFS= read -r client_location <"$TB_TTY" || true - # Trim BEFORE the non-empty check: a whitespace-only answer must NOT - # satisfy it, break the loop, and skip the "required" error below. - client_location="${client_location#"${client_location%%[![:space:]]*}"}"; client_location="${client_location%"${client_location##*[![:space:]]}"}" - [[ -n "$client_location" ]] && break - done - [[ -n "$client_location" ]] || error "A location zone is required to provision this client. Re-run and enter a zone code (e.g. DE), or set TRACEBLOC_CLIENT_LOCATION for unattended installs." - fi - fi fi # Trim surrounding whitespace from the (possibly typed) values FIRST, so a # whitespace-only answer (spaces then Enter) counts as "unset" for the silent @@ -297,11 +268,11 @@ provision_client() { client_name="${client_name#"${client_name%%[![:space:]]*}"}"; client_name="${client_name%"${client_name##*[![:space:]]}"}" client_location="${client_location#"${client_location%%[![:space:]]*}"}"; client_location="${client_location%"${client_location##*[![:space:]]}"}" - # No location yet (unattended env-name path, a TTY-less run, or a whitespace-only - # answer): fall back to the timezone-detected zone silently — the released CLI - # requires a zone, and this matches the target spec (silent auto-derive, never - # prompt). The detected code is already whitespace-free. If detection also comes - # up empty, the create's own error is surfaced by _report_create_failure below + # No location from TRACEBLOC_CLIENT_LOCATION: derive the carbon zone from the + # system timezone silently — never prompted, matching the target spec. The + # detected code is already whitespace-free. If detection also comes up empty we + # leave client_location unset and provision with NO --location (cli#137 makes it + # optional); a rejected zone still surfaces via _report_create_failure below # rather than failing blind here. if [[ -z "$client_location" ]]; then client_location="$(_detect_location_zone)"; client_location="${client_location%% *}" diff --git a/scripts/manifest.sha256 b/scripts/manifest.sha256 index e8b8f3f..3c78811 100644 --- a/scripts/manifest.sha256 +++ b/scripts/manifest.sha256 @@ -10,7 +10,7 @@ b9a1f3be69c0ea5fe78555e7da101b45e485960795a9881126361e36d6c740af scripts/lib/se 1790633a102040537707ba868ca350bee6a6d78dbbd8858bd51d2121d79f5a35 scripts/lib/gpu-plugins.sh 182f5d76f45590596ccf4becfd489a2f4124af634c81e02d650bcc4a52ab769a scripts/lib/install-client-helm.sh 7d180a17a6308a6039c6067bd7429be7af39c2af9cd504ee40d0092deb530151 scripts/lib/install-cli.sh -ea0d8a0e8e460ea06b90bfce4f436c635bc0a8e215d6c46e4299d4ae75511928 scripts/lib/provision.sh +bf283e1bb750348ea0b81d484f336c909017b2eb71ddeb848221dad91538728b scripts/lib/provision.sh e2ea63d844e6649f1d3aaae9fd4733845a1a39df37d68abbaeda00330f9e1c7e scripts/lib/assess.sh dfdaedb3cc8cfd3b5778efe388202dc14874b5a7d8cc081296c20db7c62a4542 scripts/lib/summary.sh 153ebc0d46157cfb5b9d6eeed2c534337f6c780a5dce6d1410daa47fb7d8f220 scripts/lib/diagnose.sh diff --git a/scripts/tests/provision.bats b/scripts/tests/provision.bats index 17def3d..176925f 100644 --- a/scripts/tests/provision.bats +++ b/scripts/tests/provision.bats @@ -275,7 +275,7 @@ _stub_tracebloc() { # RETRY past empty lines and still capture the real name. TB_TTY=/dev/stdin # lets us feed the queued blanks + the name on stdin. unset TRACEBLOC_CLIENT_NAME - export TRACEBLOC_CLIENT_LOCATION="DE" # skip the location sub-prompt; isolate the name read + export TRACEBLOC_CLIENT_LOCATION="DE" # pin the zone (skip the timezone auto-derive); isolate the name read _prompt_tty() { return 0; } # a terminal IS available TB_TTY=/dev/stdin _stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n' @@ -290,7 +290,7 @@ _stub_tracebloc() { # no interactive input (EOF) — e.g. a non-PTY ssh / IDE terminal. Must NOT loop # or hang, and must surface the set-TRACEBLOC_CLIENT_NAME guidance. unset TRACEBLOC_CLIENT_NAME - export TRACEBLOC_CLIENT_LOCATION="DE" + export TRACEBLOC_CLIENT_LOCATION="DE" # pin the zone; the read under test is the name _prompt_tty() { return 0; } TB_TTY=/dev/stdin _stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n' @@ -300,6 +300,42 @@ _stub_tracebloc() { [ ! -s "$CREATE_ARGS_FILE" ] } +@test "provision_client: interactive install auto-derives location from the timezone — never prompts (#354)" { + # Only the name is prompted. With no TRACEBLOC_CLIENT_LOCATION the zone is derived + # silently from the system timezone and passed as --location; the user is never + # asked. Feed ONLY a name on stdin — a lingering location prompt would block on the + # absent second line and the test would hang/fail. + unset TRACEBLOC_CLIENT_NAME TRACEBLOC_CLIENT_LOCATION + _prompt_tty() { return 0; } + TB_TTY=/dev/stdin + _detect_location_zone() { printf 'FR Europe/Paris\n'; } # detection succeeds + _stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n' + run provision_client <<< $'MyBox\n' + [ "$status" -eq 0 ] + [[ "$output" != *"carbon reporting"* ]] # the location prompt is gone + run cat "$CREATE_ARGS_FILE" + [[ "$output" == *"--name MyBox"* ]] + [[ "$output" == *"--location FR"* ]] # zone came from the timezone, silently +} + +@test "provision_client: interactive install with no detectable zone provisions with NO location — never prompts (#354)" { + # Timezone detection yields nothing. The installer must NOT ask for a zone (the old + # required-entry loop is gone) — it provisions with no --location, which the CLI + # now accepts (cli#137), and the backend records the client with no location. + unset TRACEBLOC_CLIENT_NAME TRACEBLOC_CLIENT_LOCATION + _prompt_tty() { return 0; } + TB_TTY=/dev/stdin + _detect_location_zone() { return 0; } # nothing detected + _stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n' + run provision_client <<< $'MyBox\n' + [ "$status" -eq 0 ] # no location is not fatal anymore + [[ "$output" != *"carbon reporting"* ]] # never prompted + [[ "$output" != *"location zone is required"* ]] + run cat "$CREATE_ARGS_FILE" + [[ "$output" == *"--name MyBox"* ]] + [[ "$output" != *"--location"* ]] # provisioned with no location +} + # ── cli#141: the #303 pre-flight's grep contract with `tracebloc client list` ── # _account_owns_namespace shells out to `tracebloc client list --plain` and greps # the output for `namespace=([[:space:]]|$)` to decide ownership. `client list`