|
| 1 | +#!/bin/bash |
| 2 | +# CCF — is the deploy actually LIVE on this machine? |
| 3 | +# One line per check: <name>\tOK|FAIL\t<detail>. Non-zero exit if any FAIL. |
| 4 | +set -u |
| 5 | +D="$HOME/.local/share/cache-fix-fork" |
| 6 | +CFG="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" |
| 7 | +fail=0 |
| 8 | +say() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; [ "$2" = FAIL ] && fail=1; return 0; } |
| 9 | + |
| 10 | +# 1. The launcher parses at all. A tree that cannot even be loaded is the one |
| 11 | +# failure that makes every other probe meaningless. |
| 12 | +launcher="$D/bin/claude-via-proxy.mjs" |
| 13 | +if [ ! -r "$launcher" ]; then say launcher FAIL "absent: $launcher" |
| 14 | +elif out=$(node --check "$launcher" 2>&1); then say launcher OK "parses" |
| 15 | +else say launcher FAIL "${out:-parse failed}"; fi |
| 16 | + |
| 17 | +# 2. The guard agrees with this machine's LIVE bundle. |
| 18 | +# |
| 19 | +# This is the check that earns its place: the bundle is written by a third |
| 20 | +# component (cc-wrapper's cachefix-ensure) from inputs neither this repo nor |
| 21 | +# the deploy controls, so a tree that is correct can still meet a bundle it |
| 22 | +# refuses. Probing bin/ca-trust.mjs — the module the launcher imports, not a |
| 23 | +# copy — is the point of it living in its own file. |
| 24 | +# |
| 25 | +# Absent bundle is OK, not FAIL: a host with no builder is a supported state |
| 26 | +# and the launcher falls back to its own CA. Absent CA is FAIL — forward-proxy |
| 27 | +# mode cannot work without one. |
| 28 | +if [ ! -r "$CFG/cache-fix-ca/ca.pem" ]; then |
| 29 | + say ca-trust FAIL "no proxy CA at $CFG/cache-fix-ca/ca.pem" |
| 30 | +elif [ ! -s "$CFG/ca-trust.pem" ]; then |
| 31 | + say ca-trust OK "no merged bundle on this host — launcher uses its own CA" |
| 32 | +else |
| 33 | + out=$(node -e ' |
| 34 | + import(process.argv[1] + "/bin/ca-trust.mjs").then(async m => { |
| 35 | + const fs = await import("node:fs"); |
| 36 | + const cfg = process.argv[2]; |
| 37 | + const ca = fs.readFileSync(cfg + "/cache-fix-ca/ca.pem"); |
| 38 | + const merged = fs.readFileSync(cfg + "/ca-trust.pem", "utf8"); |
| 39 | + const n = (merged.match(/^-----BEGIN CERTIFICATE-----$/gm) || []).length; |
| 40 | + const v = m.bundleCarriesOurCA(merged, ca); |
| 41 | + console.log(n + " certs: " + (v.ok ? "accepted" : "REFUSED — " + v.reason)); |
| 42 | + process.exit(v.ok ? 0 : 1); |
| 43 | + }).catch(e => { console.log("threw: " + e.message); process.exit(1); }); |
| 44 | + ' "$D" "$CFG" 2>&1) |
| 45 | + if [ $? -eq 0 ]; then say ca-trust OK "$out"; else say ca-trust FAIL "$out"; fi |
| 46 | +fi |
| 47 | + |
| 48 | +# 3. A proxy answers, on the port the sessions were actually built against — |
| 49 | +# a session that started during a reload window baked whatever port it got, |
| 50 | +# and asserting the default would pass while every live session dialled a |
| 51 | +# dead one. |
| 52 | +# |
| 53 | +# The run dir `<TMPDIR-or-/tmp>/cachefix-<port>` is where that port is |
| 54 | +# recorded; it is the same discovery cc-check-ccf's probe uses. This read |
| 55 | +# ~/.claude.json for CLAUDE_CODE_CACHE_FIX_PORT until 2026-08-01, and no |
| 56 | +# such key exists — the launcher's variable is CACHE_FIX_PROXY_PORT and it |
| 57 | +# lives in the proxy's own env. Measured on all three machines: 0 matches, |
| 58 | +# so the sed produced nothing and `${port:-9901}` supplied the default every |
| 59 | +# single run. The check asserted precisely the assumption the paragraph |
| 60 | +# above forbids, and would have said OK about a dead port on any machine |
| 61 | +# whose proxy had moved. |
| 62 | +# |
| 63 | +# No run dir ⇒ FAIL, never a default. A check that cannot find its subject |
| 64 | +# has not looked; reporting that as OK is the whole failure mode. |
| 65 | +# |
| 66 | +# Collected in a plain loop, NOT `$(for ... done | sort -u)`: bash 3.2 (which |
| 67 | +# is what macOS ships, and two of the three machines here run) mis-parses a |
| 68 | +# `case` pattern inside `$( )` — it reads the pattern's own `)` as the end of |
| 69 | +# the command substitution and dies on the following `;;`. Measured: both |
| 70 | +# macs exited 2 with "syntax error near unexpected token `;;'" while lmd42's |
| 71 | +# bash 5 ran it fine. The check failed loudly rather than passing, which is |
| 72 | +# the right failure mode, but it still could not do its job on the hosts that |
| 73 | +# had it. |
| 74 | +tmp=${TMPDIR:-/tmp}; tmp=${tmp%/} |
| 75 | +ports= |
| 76 | +for d in "$tmp"/cachefix-* /tmp/cachefix-*; do |
| 77 | + [ -d "$d" ] || continue |
| 78 | + p=${d##*cachefix-} |
| 79 | + case "$p" in ''|*[!0-9]*) continue ;; esac |
| 80 | + case " $ports " in *" $p "*) continue ;; esac # TMPDIR may already be /tmp |
| 81 | + ports="$ports $p" |
| 82 | +done |
| 83 | +if [ -z "$ports" ]; then |
| 84 | + say proxy-live FAIL "no cachefix-<port> run dir under $tmp or /tmp — nothing to probe" |
| 85 | +else |
| 86 | + for port in $ports; do |
| 87 | + if out=$(curl -sf --max-time 3 --noproxy '*' "http://127.0.0.1:$port/health" 2>&1); then |
| 88 | + case "$out" in |
| 89 | + *'"forward_proxy":true'*) say proxy-live OK "port $port: $out" ;; |
| 90 | + # Answering in reverse-proxy mode is not "up": that mode sets |
| 91 | + # ANTHROPIC_BASE_URL, which is what disables Remote Control. |
| 92 | + *) say proxy-live FAIL "port $port answers but forward_proxy is not true: $out" ;; |
| 93 | + esac |
| 94 | + else |
| 95 | + say proxy-live FAIL "run dir $tmp/cachefix-$port exists but nothing answers 127.0.0.1:$port" |
| 96 | + fi |
| 97 | + done |
| 98 | +fi |
| 99 | + |
| 100 | +exit $fail |
0 commit comments