Stop demo diagnostics at failed tailnet readiness

Failed setup now yields local-only, topology-free diagnostics. Require recognised Running state and successful tailnet ping before TCP, retain probe exits without raw private output or fallback probes, and exercise twenty synthetic readiness/privacy cases. Parent controls expose false success and private output on both channels. Keep workflow identities, secrets, action pins, mutation gates and release scope unchanged.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-01 17:36:26 +01:00
parent ee2fb78d05
commit bb1903e48d
5 changed files with 203 additions and 43 deletions
+48 -38
View File
@@ -8,21 +8,29 @@ TCP_PORT="${DEMO_SERVER_PORT:-22}"
TCP_ATTEMPTS="${DEMO_TCP_ATTEMPTS:-6}"
TCP_RETRY_SECONDS="${DEMO_TCP_RETRY_SECONDS:-5}"
if [ "$MODE" != "check" ] && [ "$MODE" != "diagnose" ]; then
echo "Usage: $0 [check|diagnose]" >&2
exit 2
fi
# Only local daemon state is collected here. Never print tailnet addresses,
# names, tags, relay locations or raw CLI errors into public workflow logs.
# Success means the daemon is running, not that the demo is reachable.
print_safe_status() {
local status_file
local status_file status_result=0
if ! command -v tailscale >/dev/null 2>&1; then
echo "Tailscale CLI is not available."
return 0
return 1
fi
status_file="$(mktemp)"
if ! tailscale status --json >"$status_file" 2>/dev/null; then
echo "Tailscale status JSON is unavailable."
rm -f "$status_file"
return 0
return 1
fi
python3 - "$DEMO_SERVER_HOST" "$status_file" <<'PY' || true
python3 - "$DEMO_SERVER_HOST" "$status_file" <<'PY' || status_result=$?
import json
import sys
@@ -30,66 +38,69 @@ host = sys.argv[1]
try:
with open(sys.argv[2], encoding="utf-8") as status_file:
status = json.load(status_file)
except (json.JSONDecodeError, OSError):
except (ValueError, OSError):
print("Tailscale status JSON is unavailable.")
raise SystemExit(0)
raise SystemExit(1)
states = {"NoState", "InUseOtherUser", "NeedsLogin", "NeedsMachineAuth", "Stopped", "Starting", "Running"}
state = status.get("BackendState") if isinstance(status, dict) else None
if not isinstance(state, str) or state not in states:
print("Tailscale backend: unknown")
raise SystemExit(1)
print(f"Tailscale backend: {state}")
if state != "Running":
raise SystemExit(1)
self_node = status.get("Self") or {}
self_ips = [ip for ip in self_node.get("TailscaleIPs") or [] if ":" not in ip]
self_dns = (self_node.get("DNSName") or "").rstrip(".")
self_tags = sorted(self_node.get("Tags") or [])
target = None
for peer in (status.get("Peer") or {}).values():
peer_ips = peer.get("TailscaleIPs") or []
peer_dns = (peer.get("DNSName") or "").rstrip(".")
peers = status.get("Peer")
for peer in peers.values() if isinstance(peers, dict) else ():
if not isinstance(peer, dict):
continue
peer_ips = peer.get("TailscaleIPs")
peer_ips = peer_ips if isinstance(peer_ips, list) else []
peer_dns = peer.get("DNSName")
peer_dns = peer_dns.rstrip(".") if isinstance(peer_dns, str) else ""
if host in peer_ips or host.rstrip(".") == peer_dns:
target = peer
break
print(f"Tailscale backend: {status.get('BackendState', 'unknown')}")
print(f"Runner Tailscale IPv4: {self_ips[0] if self_ips else 'unavailable'}")
print(f"Runner Tailscale DNS: {self_dns or 'unavailable'}")
print(f"Runner Tailscale tags: {','.join(self_tags) if self_tags else 'none'}")
if target is None:
print("Demo peer is not present in the runner peer map yet.")
else:
print(
"Demo peer state: "
f"online={bool(target.get('Online'))} "
f"active={bool(target.get('Active'))} "
f"relay={target.get('Relay') or 'none'}"
f"online={target.get('Online') is True} "
f"active={target.get('Active') is True}"
)
PY
rm -f "$status_file"
}
diagnose() {
print_safe_status
if command -v tailscale >/dev/null 2>&1; then
tailscale ping --c 1 --timeout 5s "$DEMO_SERVER_HOST" || true
fi
nc -z -w 5 "$DEMO_SERVER_HOST" "$TCP_PORT" || true
return "$status_result"
}
if [ "$MODE" = "diagnose" ]; then
diagnose
# Both callers use this after the setup action fails. A provider refusal must
# not become another network attempt or a direct-host fallback diagnostic.
print_safe_status || true
echo "Diagnostic mode is local-only; demo connectivity and installed state remain unverified."
exit 0
fi
if [ "$MODE" != "check" ]; then
echo "Usage: $0 [check|diagnose]" >&2
exit 2
if ! print_safe_status; then
echo "::error::Tailscale setup has not established a running backend; no demo reachability probes attempted. This is not evidence that the demo host is down."
exit 1
fi
print_safe_status
if ! tailscale ping --c 3 --timeout 10s "$DEMO_SERVER_HOST"; then
# Probe output can contain private peer names/addresses or raw client errors.
# Retain the exit verdict, not that topology, in public workflow diagnostics.
if ! tailscale ping --c 3 --timeout 10s "$DEMO_SERVER_HOST" >/dev/null 2>&1; then
echo "::error::Tailscale cannot reach the demo peer. Verify that the workflow tag is authorized to reach the demo host tag and that the peer is online."
diagnose
# Never try TCP after a failed tailnet readiness probe, or replay that probe
# as a diagnostic. Preserve the failed result for the workflow owner.
exit 1
fi
for attempt in $(seq 1 "$TCP_ATTEMPTS"); do
if nc -z -w 5 "$DEMO_SERVER_HOST" "$TCP_PORT"; then
if nc -z -w 5 "$DEMO_SERVER_HOST" "$TCP_PORT" >/dev/null 2>&1; then
echo "Demo SSH transport is reachable over Tailscale."
exit 0
fi
@@ -101,5 +112,4 @@ for attempt in $(seq 1 "$TCP_ATTEMPTS"); do
done
echo "::error::Tailscale reached the demo peer, but TCP/${TCP_PORT} remained closed. Verify sshd and the host firewall on tailscale0."
diagnose
exit 1