|
| 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. Read the port the sessions were actually built against |
| 49 | +# rather than assuming 9901 — a session that started during a reload window |
| 50 | +# baked whatever port it got, and asserting the default would pass while |
| 51 | +# every live session dialled a dead one. |
| 52 | +port=$(sed -n 's/.*"CLAUDE_CODE_CACHE_FIX_PORT" *: *"\([0-9]*\)".*/\1/p' "$HOME/.claude.json" 2>/dev/null | head -1) |
| 53 | +port=${port:-9901} |
| 54 | +if out=$(curl -sf --max-time 3 --noproxy '*' "http://127.0.0.1:$port/health" 2>&1); then |
| 55 | + case "$out" in |
| 56 | + *'"forward_proxy":true'*) say proxy-live OK "port $port: $out" ;; |
| 57 | + # Answering in reverse-proxy mode is not "up": that mode sets |
| 58 | + # ANTHROPIC_BASE_URL, which is what disables Remote Control. |
| 59 | + *) say proxy-live FAIL "port $port answers but forward_proxy is not true: $out" ;; |
| 60 | + esac |
| 61 | +else |
| 62 | + say proxy-live FAIL "nothing on 127.0.0.1:$port" |
| 63 | +fi |
| 64 | + |
| 65 | +exit $fail |
0 commit comments