7c02ae1a
test: the cross-OS gate starts its Linux daemon on the box's own default key
a73x 2026-09-04 10:16
Commit message
test/xos.sh
| Old | New | ||
|---|---|---|---|
| @@ -134,6 +134,13 @@ lssh() { | |||
| 134 | # ---- the local side is a hermetic laptop ------------------------------ | 134 | # ---- the local side is a hermetic laptop ------------------------------ |
| 135 | # Every XDG dir mux reads, isolated. Short root because a unix socket path | 135 | # Every XDG dir mux reads, isolated. Short root because a unix socket path |
| 136 | # caps at 107 bytes. | 136 | # caps at 107 bytes. |
| 137 | # Whether the run is responsible for ~/.config/mux on the Mac. Leg 7's entry | ||
| 138 | # dial makes the Mac run `mux d endpoint`, which CREATES the default key file | ||
| 139 | # when there is none — the product's own behaviour on any box a person dials, | ||
| 140 | # not something the harness can spell away, since mux writes that ssh line | ||
| 141 | # itself. Recorded in leg 3 and undone in the cleanup, so a Mac that had no | ||
| 142 | # mux key before this gate has none after it. | ||
| 143 | MAC_CFG_MADE=no | ||
| 137 | SCRATCH=$(mktemp -d /tmp/muxxos.XXXXXX) | 144 | SCRATCH=$(mktemp -d /tmp/muxxos.XXXXXX) |
| 138 | export XDG_STATE_HOME="$SCRATCH/st" XDG_RUNTIME_DIR="$SCRATCH/rt" | 145 | export XDG_STATE_HOME="$SCRATCH/st" XDG_RUNTIME_DIR="$SCRATCH/rt" |
| 139 | export XDG_CONFIG_HOME="$SCRATCH/cf" XDG_CACHE_HOME="$SCRATCH/ca" | 146 | export XDG_CONFIG_HOME="$SCRATCH/cf" XDG_CACHE_HOME="$SCRATCH/ca" |
| @@ -150,9 +157,11 @@ sleep 0.3 | |||
| 150 | pkill -x mux 2>/dev/null || true | 157 | pkill -x mux 2>/dev/null || true |
| 151 | rm -rf ~/xos | 158 | rm -rf ~/xos |
| 152 | L | 159 | L |
| 153 | mssh 60 >/dev/null 2>&1 <<'M' || true | 160 | mssh 60 >/dev/null 2>&1 <<M || true |
| 154 | $HOME/.local/bin/mux d stop 2>/dev/null || true | 161 | \$HOME/.local/bin/mux d stop 2>/dev/null || true |
| 155 | rm -rf $HOME/xos | 162 | rm -rf \$HOME/xos |
| 163 | [ "$MAC_CFG_MADE" = yes ] && rm -rf \$HOME/.config/mux | ||
| 164 | exit 0 | ||
| 156 | M | 165 | M |
| 157 | rm -rf "$SCRATCH" | 166 | rm -rf "$SCRATCH" |
| 158 | exit "$_rc" | 167 | exit "$_rc" |
| @@ -267,7 +276,8 @@ leg | |||
| 267 | MPRE=$(mssh 60 <<'M' | 276 | MPRE=$(mssh 60 <<'M' |
| 268 | echo "uid=$(id -u)" | 277 | echo "uid=$(id -u)" |
| 269 | echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')" | 278 | echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')" |
| 270 | [ -e /tmp/mux-$(id -u) ] && echo "rt=present" || echo "rt=absent" | 279 | [ -e /tmp/mux-$(id -u)/muxd.sock ] && echo "rt=present" || echo "rt=absent" |
| 280 | [ -e $HOME/.config/mux ] && echo "cfg=present" || echo "cfg=absent" | ||
| 271 | ifconfig 2>/dev/null | sed -n 's/^[[:space:]]*inet \([0-9.]*\) .*/ip=\1/p' | 281 | ifconfig 2>/dev/null | sed -n 's/^[[:space:]]*inet \([0-9.]*\) .*/ip=\1/p' |
| 272 | M | 282 | M |
| 273 | ) | 283 | ) |
| @@ -277,14 +287,21 @@ printf '%s\n' "$MPRE" | grep -qx 'muxes=0' || { | |||
| 277 | echo "xos FAIL: reset: $MAC already runs a mux, and this gate will not kill it." | 287 | echo "xos FAIL: reset: $MAC already runs a mux, and this gate will not kill it." |
| 278 | echo " Stop it there (mux d stop) or run this against another Mac." | 288 | echo " Stop it there (mux d stop) or run this against another Mac." |
| 279 | exit 1; } | 289 | exit 1; } |
| 290 | # The SOCKET and not the directory. `pgrep` above already says no mux is | ||
| 291 | # running; what is left to rule out is a daemon serving at the path the | ||
| 292 | # Linux→Mac legs will dial. The directory itself is made by any mux at all, | ||
| 293 | # a plain client included, so on a Mac that has ever run one it is simply | ||
| 294 | # there — refusing on it would refuse every machine the gate is for. | ||
| 280 | printf '%s\n' "$MPRE" | grep -qx 'rt=absent' || | 295 | printf '%s\n' "$MPRE" | grep -qx 'rt=absent' || |
| 281 | fail reset "$MAC already has /tmp/mux-$MAC_UID; the daemon this gate meets must be the one it starts" | 296 | fail reset "$MAC already serves /tmp/mux-$MAC_UID/muxd.sock; the daemon this gate meets must be the one it starts" |
| 282 | # The address the QUIC legs dial, asserted against the Mac's own interface | 297 | # The address the QUIC legs dial, asserted against the Mac's own interface |
| 283 | # list rather than assumed from a variable: a stale MUX_MAC_IP would make | 298 | # list rather than assumed from a variable: a stale MUX_MAC_IP would make |
| 284 | # leg 8 grade some other machine's silence. | 299 | # leg 8 grade some other machine's silence. |
| 285 | printf '%s\n' "$MPRE" | grep -qx "ip=$MAC_IP" || | 300 | printf '%s\n' "$MPRE" | grep -qx "ip=$MAC_IP" || |
| 286 | fail reset "$MAC does not hold $MAC_IP; set MUX_MAC_IP. It answered: $(printf '%s' "$MPRE" | tr '\n' ' ')" | 301 | fail reset "$MAC does not hold $MAC_IP; set MUX_MAC_IP. It answered: $(printf '%s' "$MPRE" | tr '\n' ' ')" |
| 287 | MAC_SOCK="/tmp/mux-$MAC_UID/muxd.sock" | 302 | MAC_SOCK="/tmp/mux-$MAC_UID/muxd.sock" |
| 303 | # Leg 7's dial will create this if it is not there; see MAC_CFG_MADE. | ||
| 304 | if printf '%s\n' "$MPRE" | grep -qx 'cfg=absent'; then MAC_CFG_MADE=yes; fi | ||
| 288 | 305 | ||
| 289 | # The Linux box IS a throwaway, so it gets test/vm.sh's scorched earth. The | 306 | # The Linux box IS a throwaway, so it gets test/vm.sh's scorched earth. The |
| 290 | # reset is the hermeticity: each run starts from the mux-less box a new user | 307 | # reset is the hermeticity: each run starts from the mux-less box a new user |
| @@ -339,7 +356,20 @@ ok reset "$LAN reset to a mux-less box and given '$LAN_VER'; $MAC left running n | |||
| 339 | # `mux d keygen` always writes the default path, so the isolated | 356 | # `mux d keygen` always writes the default path, so the isolated |
| 340 | # XDG_CONFIG_HOME above is what puts it in the scratch tree. It is copied to | 357 | # XDG_CONFIG_HOME above is what puts it in the scratch tree. It is copied to |
| 341 | # ~/xos/key on both boxes and named on every QUIC command line; the Mac's | 358 | # ~/xos/key on both boxes and named on every QUIC command line; the Mac's |
| 342 | # own ~/.config/mux/key is never read and never written. | 359 | # own ~/.config/mux/key is never written by the HARNESS (leg 7's dial makes |
| 360 | # the product write it, which MAC_CFG_MADE undoes). | ||
| 361 | # | ||
| 362 | # On the Linux box the same bytes ALSO go to that box's default key path, | ||
| 363 | # and leg 5's daemon is started with no `--key` at all, which is how a | ||
| 364 | # person starts one after `mux d keygen`. That is not decoration. `mux d | ||
| 365 | # endpoint` resolves the key it ANNOUNCES from $MUX_KEY_FILE or the default | ||
| 366 | # path — it never asks the daemon which key its listener actually holds — so | ||
| 367 | # a daemon started with `--quic ... --key SOMEWHERE_ELSE` announces a key | ||
| 368 | # that does not authenticate against itself, creating the default file on | ||
| 369 | # the spot if it has to. Every ssh handoff to such a daemon then dies in | ||
| 370 | # silence and falls back to the pipe. Measured on this fixture 2026-09-04 | ||
| 371 | # and reported; it is not a macOS difference, and the two spellings agree | ||
| 372 | # again the moment the listener's key IS the default one. | ||
| 343 | leg | 373 | leg |
| 344 | "$MUX" d keygen >/dev/null 2>&1 || fail keys "mux d keygen refused under $XDG_CONFIG_HOME" | 374 | "$MUX" d keygen >/dev/null 2>&1 || fail keys "mux d keygen refused under $XDG_CONFIG_HOME" |
| 345 | cp "$XDG_CONFIG_HOME/mux/key" "$SCRATCH/key" | 375 | cp "$XDG_CONFIG_HOME/mux/key" "$SCRATCH/key" |
| @@ -350,6 +380,21 @@ chmod 600 "$SCRATCH/key" | |||
| 350 | scp -q $SSH_OPTS "$SCRATCH/key" "$LAN":xos/key || fail keys "could not install the key on $LAN" | 380 | scp -q $SSH_OPTS "$SCRATCH/key" "$LAN":xos/key || fail keys "could not install the key on $LAN" |
| 351 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose | 381 | # shellcheck disable=SC2086 # SSH_OPTS is words on purpose |
| 352 | scp -q $SSH_OPTS "$SCRATCH/key" "$MAC":xos/key || fail keys "could not install the key on $MAC" | 382 | scp -q $SSH_OPTS "$SCRATCH/key" "$MAC":xos/key || fail keys "could not install the key on $MAC" |
| 383 | # BOTH default key paths on the Linux box, because two different | ||
| 384 | # environments resolve "the default key" there and they must not disagree: | ||
| 385 | # everything the harness runs sees the prelude's isolated XDG_CONFIG_HOME, | ||
| 386 | # while the `mux d endpoint` at the far end of the MAC's entry dial is a | ||
| 387 | # fresh ssh with the box's own environment and resolves ~/.config/mux/key. | ||
| 388 | # Same bytes at both, so whichever one a process picks is this run's key. | ||
| 389 | lssh 30 >/dev/null <<'L' || fail keys "could not install the key at the default paths on $LAN" | ||
| 390 | set -e | ||
| 391 | for d in "$HOME/.config/mux" "${XDG_CONFIG_HOME:-$HOME/.config}/mux"; do | ||
| 392 | mkdir -p "$d" | ||
| 393 | chmod 700 "$d" | ||
| 394 | cp "$HOME/xos/key" "$d/key" | ||
| 395 | chmod 600 "$d/key" | ||
| 396 | done | ||
| 397 | L | ||
| 353 | # 0600 on both, because quic.Key.load refuses a group- or other-readable key | 398 | # 0600 on both, because quic.Key.load refuses a group- or other-readable key |
| 354 | # exactly as ssh refuses a private key, and scp does not carry the mode. | 399 | # exactly as ssh refuses a private key, and scp does not carry the mode. |
| 355 | KMODES=$( | 400 | KMODES=$( |
| @@ -377,8 +422,22 @@ ok keys "one 32-byte PSK at ~/xos/key, mode 600 on both boxes" | |||
| 377 | # this same daemon and marks are opt-in. | 422 | # this same daemon and marks are opt-in. |
| 378 | leg | 423 | leg |
| 379 | lssh 90 >/dev/null <<L || fail mac-to-linux "mux d start -d --quic was refused on $LAN" | 424 | lssh 90 >/dev/null <<L || fail mac-to-linux "mux d start -d --quic was refused on $LAN" |
| 380 | MUX_SHELL_INTEGRATION=1 ~/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT --key ~/xos/key | 425 | MUX_SHELL_INTEGRATION=1 ~/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT |
| 381 | L | 426 | L |
| 427 | # The listener's key and the key `mux d endpoint` announces, side by side. | ||
| 428 | # They are the same file here by construction (leg 4), and this reads them | ||
| 429 | # back rather than trusting that: an announce that names a key the listener | ||
| 430 | # does not hold is invisible from the client — the handshake simply never | ||
| 431 | # answers — and leg 5 would then be grading the ssh fallback while reporting | ||
| 432 | # QUIC. | ||
| 433 | ANN=$(lssh 30 <<'L' | ||
| 434 | ~/.local/bin/mux d endpoint | ||
| 435 | L | ||
| 436 | ) | ||
| 437 | case "$ANN" in | ||
| 438 | "endpoint $QUIC_PORT $(od -An -tx1 -v < "$SCRATCH/key" | tr -d ' \n')") ;; | ||
| 439 | *) fail mac-to-linux "$LAN announces '$ANN', which is not this run's key at port $QUIC_PORT" ;; | ||
| 440 | esac | ||
| 382 | LPID=$(lssh 30 <<'L' | 441 | LPID=$(lssh 30 <<'L' |
| 383 | pgrep -x mux | head -1 | 442 | pgrep -x mux | head -1 |
| 384 | L | 443 | L |