diff options
| author | Craig Jennings <c@cjennings.net> | 2026-08-19 15:02:32 -0700 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-08-19 15:02:32 -0700 |
| commit | 62a7fbf695405d8804220c1cf1b280ee8d612082 (patch) | |
| tree | 66f5c4123b512fcdb2422b335a7e14d5e5446167 /scripts/tests | |
| parent | 10d4e3b17dddbe8df47a80444688d13be15dc016 (diff) | |
| download | rulesets-62a7fbf695405d8804220c1cf1b280ee8d612082.tar.gz rulesets-62a7fbf695405d8804220c1cf1b280ee8d612082.zip | |
fix(agent-text): relay through a host that holds the account
The relay target was hardcoded to velox, and velox's reinstall wiped its signal-cli registration. A send from velox then relayed to itself, and any machine without a local account relayed into the same dead end. Only ratio still worked, because it holds the account as a linked device and sends directly. The old failure text blamed velox for being down. It was up, just unregistered.
The target is now an ordered list, overridable per invocation, and the loop skips any candidate matching this machine's name. The skip is the actual fix. Reaching the relay branch means the account isn't local, so relaying to ourselves can't work whatever the list order. Both sides of that comparison are domain-stripped, or an FQDN nodename would silently disable the guard.
A relay host that is reachable but unregistered exits 1, not 0, so the loop advances instead of reporting a delivery that never happened. I checked that rather than assume it. Silent non-delivery is the worst failure this tool has, and a test pins it.
The failure message now names which branch failed, so a local send failure no longer sends anyone chasing the tailnet.
protocols.org and the pager runbook both asserted the topology the reinstall destroyed. They now say how to derive which machine holds the account instead of naming one. The snapshot form is what rotted. The runbook's linking recipe is marked impossible for now, since the primary's keys are gone and a linked device can't authorize another.
Diffstat (limited to 'scripts/tests')
| -rw-r--r-- | scripts/tests/agent-text.bats | 101 |
1 files changed, 92 insertions, 9 deletions
diff --git a/scripts/tests/agent-text.bats b/scripts/tests/agent-text.bats index e5d80fc..d4c5f9b 100644 --- a/scripts/tests/agent-text.bats +++ b/scripts/tests/agent-text.bats @@ -1,11 +1,14 @@ #!/usr/bin/env bats # agent-text — the runtime-neutral Signal phone messenger ("text me"). Reaches # Craig over Signal from any machine on the tailnet: sends directly wherever the -# account is registered locally (velox, or any linked device), and ssh-relays to -# velox from a machine that doesn't hold the account. These tests stub -# ssh/signal-cli on PATH to verify command construction without a network or a -# phone. The signal-cli stub answers `listAccounts` to control which branch the -# dispatch takes. The final test covers the deprecated agent-page shim. +# account is registered locally (any machine holding it, as primary or as a +# linked device), and otherwise ssh-relays to the first relay host that holds +# it. These tests stub ssh/signal-cli/uname on PATH to verify command +# construction without a network or a phone. The signal-cli stub answers +# `listAccounts` to control which branch the dispatch takes; the uname stub +# fixes the machine identity, so the self-relay skip is exercised deterministically +# rather than depending on which machine runs the suite. The final test covers +# the deprecated agent-page shim. setup() { REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" @@ -29,7 +32,14 @@ fi echo "signal-cli \$*" >> "$LOG" exit 0 EOF - chmod +x "$STUBS/ssh" "$STUBS/signal-cli" + # SELF_HOST fixes what the script sees as this machine. Defaults to a name + # in neither relay list, so the plain relay tests reach the first host. + cat > "$STUBS/uname" <<EOF +#!/bin/bash +[ "\$1" = "-n" ] && { echo "\${SELF_HOST:-somewhere-else}"; exit 0; } +exec /usr/bin/uname "\$@" +EOF + chmod +x "$STUBS/ssh" "$STUBS/signal-cli" "$STUBS/uname" } teardown() { @@ -42,14 +52,59 @@ teardown() { [[ "$output" == *"usage"* ]] } -@test "relays through ssh to velox when the account is not local" { +@test "relays to the first relay host when the account is not local" { HAS_ACCOUNT=0 PATH="$STUBS:$PATH" run bash "$PAGE" build finished [ "$status" -eq 0 ] - grep -q "^ssh .*velox" "$LOG" + grep -q "^ssh .*ratio" "$LOG" grep -q "15045173983" "$LOG" grep -q "b1b5601e-6126-47f8-afaa-0a59f5188fde" "$LOG" # printf %q escapes the space, so the relayed message reads build\ finished. grep -qF 'build\ finished' "$LOG" + # First host answered, so the second is never tried. + ! grep -q "^ssh .*velox" "$LOG" +} + +@test "never relays to this machine — the self host is skipped" { + # On ratio, relaying to ratio cannot work: reaching this branch means the + # account is not local, so the round trip lands on the same empty store. + SELF_HOST=ratio HAS_ACCOUNT=0 PATH="$STUBS:$PATH" run bash "$PAGE" hello + [ "$status" -eq 0 ] + ! grep -q "^ssh .*ratio" "$LOG" + grep -q "^ssh .*velox" "$LOG" +} + +@test "the self skip survives an FQDN nodename on either side" { + # uname -n returns whatever /etc/hostname holds, which may be fully + # qualified. Both sides are stripped, so the guard must still fire -- if it + # compares a stripped candidate against an unqualified self, an FQDN + # nodename silently restores the self-relay bug on the away channel. + SELF_HOST=ratio.tailf3bb8c.ts.net HAS_ACCOUNT=0 PATH="$STUBS:$PATH" \ + run bash "$PAGE" hi + [ "$status" -eq 0 ] + ! grep -q "^ssh .*ratio" "$LOG" + grep -q "^ssh .*velox" "$LOG" +} + +@test "falls through to the next host when the first relay fails" { + cat > "$STUBS/ssh" <<EOF +#!/bin/bash +echo "ssh \$*" >> "$LOG" +case "\$*" in *ratio*) exit 255 ;; esac +exit 0 +EOF + chmod +x "$STUBS/ssh" + HAS_ACCOUNT=0 PATH="$STUBS:$PATH" run bash "$PAGE" retry me + [ "$status" -eq 0 ] + grep -q "^ssh .*ratio" "$LOG" + grep -q "^ssh .*velox" "$LOG" +} + +@test "AGENT_TEXT_RELAYS overrides the default relay list" { + AGENT_TEXT_RELAYS="spare.example.net" HAS_ACCOUNT=0 PATH="$STUBS:$PATH" \ + run bash "$PAGE" custom + [ "$status" -eq 0 ] + grep -q "^ssh .*spare.example.net" "$LOG" + ! grep -q "^ssh .*ratio" "$LOG" } @test "sends directly (no ssh) when the pager account is registered locally" { @@ -59,7 +114,7 @@ teardown() { ! grep -q "^ssh " "$LOG" } -@test "a failed relay reports the desktop fallback and propagates failure" { +@test "every relay failing reports the desktop fallback and propagates failure" { cat > "$STUBS/ssh" <<'EOF' #!/bin/bash exit 255 @@ -70,6 +125,34 @@ EOF [[ "$output" == *"notify"* ]] } +@test "no reachable relay at all still fails rather than reporting success" { + # Every candidate is this machine, so the loop body never runs. rc must stay + # at its non-zero seed: a skipped loop is not a delivered message. + AGENT_TEXT_RELAYS="ratio.tailf3bb8c.ts.net" SELF_HOST=ratio HAS_ACCOUNT=0 \ + PATH="$STUBS:$PATH" run bash "$PAGE" nowhere to go + [ "$status" -ne 0 ] + [[ "$output" == *"notify"* ]] + ! grep -q "^ssh " "$LOG" +} + +@test "a local send failure blames the local send, not the relay list" { + # On the machine that holds the account, no relay is ever consulted. A + # message naming the relay list sends whoever is debugging it off chasing + # the tailnet while the fault is on the box in front of them. + cat > "$STUBS/signal-cli" <<EOF +#!/bin/bash +if [ "\$1" = "listAccounts" ]; then echo "Number: +15045173983"; exit 0; fi +exit 1 +EOF + chmod +x "$STUBS/signal-cli" + # This stub reports the account unconditionally, so it drives the direct + # branch on its own -- HAS_ACCOUNT would be decorative here. + PATH="$STUBS:$PATH" run bash "$PAGE" boom + [ "$status" -ne 0 ] + [[ "$output" == *"local signal-cli send failed"* ]] + [[ "$output" != *"no relay reachable"* ]] +} + @test "the deprecated agent-page shim delegates to agent-text" { HAS_ACCOUNT=1 PATH="$STUBS:$PATH" run bash "$SHIM" via shim [ "$status" -eq 0 ] |
