#!/usr/bin/env bash set -euo pipefail TEST_URL="http://npub1crpldvy49ef8z34wlacwujnfudy4nd7k96aqdx5wgn6ckztz7z8q9t59ud.fips/index.html" CURL_TIMEOUT="${CURL_TIMEOUT:-20}" MIN_BYTES="${MIN_BYTES:-1000}" if ! command -v curl >/dev/null 2>&1; then echo "Error: curl is required." >&2 exit 1 fi if ! command -v ip >/dev/null 2>&1; then echo "Error: iproute2 (ip command) is required." >&2 exit 1 fi if ! command -v python3 >/dev/null 2>&1; then echo "Error: python3 is required." >&2 exit 1 fi get_cmd_output() { local out_file err_file out_file="$(mktemp)" err_file="$(mktemp)" if "$@" >"${out_file}" 2>"${err_file}"; then cat "${out_file}" rm -f "${out_file}" "${err_file}" return 0 fi if command -v sudo >/dev/null 2>&1; then if sudo "$@" >"${out_file}" 2>"${err_file}"; then cat "${out_file}" rm -f "${out_file}" "${err_file}" return 0 fi fi cat "${err_file}" >&2 || true rm -f "${out_file}" "${err_file}" return 1 } json_get() { local json_input="$1" local path="$2" python3 - "$path" "$json_input" <<'PY' import json import sys path = sys.argv[1].split('.') obj = json.loads(sys.argv[2]) cur = obj for p in path: if isinstance(cur, dict) and p in cur: cur = cur[p] else: print("n/a") raise SystemExit(0) if isinstance(cur, (dict, list)): print(json.dumps(cur, separators=(",", ":"))) else: print(cur) PY } first_connected_peer() { local json_input="$1" python3 - "$json_input" <<'PY' import json import sys obj = json.loads(sys.argv[1]) peers = obj.get("peers", []) connected = [p for p in peers if p.get("connectivity") == "connected"] if not connected: print("none") raise SystemExit(0) p = connected[0] name = p.get("display_name") or p.get("npub") or "unknown" addr = p.get("transport_addr") or "n/a" print(f"{name} @ {addr}") PY } echo "==> Checking local FIPS service status" if command -v systemctl >/dev/null 2>&1; then if ! systemctl is-active --quiet fips.service; then echo "Error: fips.service is not active." >&2 echo "Start it with: sudo systemctl start fips" >&2 exit 1 fi else echo "Warning: systemctl not found; skipping service-state check." fi if ! command -v fips >/dev/null 2>&1; then echo "Error: fips binary not found in PATH." >&2 exit 1 fi if ! command -v fipsctl >/dev/null 2>&1; then echo "Error: fipsctl binary not found in PATH." >&2 exit 1 fi FIPS_VERSION="$(fips --version | head -n1)" FIPSCTL_VERSION="$(fipsctl --version | head -n1)" STATUS_JSON="$(get_cmd_output fipsctl show status)" PEERS_JSON="$(get_cmd_output fipsctl show peers)" TUN_STATE="$(json_get "${STATUS_JSON}" tun_state)" TUN_NAME="$(json_get "${STATUS_JSON}" tun_name)" if [[ "${TUN_NAME}" == "n/a" || -z "${TUN_NAME}" ]]; then TUN_NAME="fips0" fi if grep -Eq '^[[:space:]]*nameserver[[:space:]]+127\.0\.0\.1([[:space:]]|$)' /etc/resolv.conf 2>/dev/null; then RESOLV_127_STATUS="present" else RESOLV_127_STATUS="missing" fi if [[ "${TUN_STATE}" != "active" ]]; then echo "Error: expected tun_state=active, got ${TUN_STATE}." >&2 exit 1 fi if ! ip -o link show dev "${TUN_NAME}" >/dev/null 2>&1; then echo "Error: expected TUN interface ${TUN_NAME} to exist." >&2 exit 1 fi FD00_ROUTE_LINE="$(ip -6 route show fd00::/8 2>/dev/null | head -n1 || true)" if [[ -z "${FD00_ROUTE_LINE}" ]]; then echo "Error: missing fd00::/8 route." >&2 exit 1 fi if [[ "${FD00_ROUTE_LINE}" != *"dev ${TUN_NAME}"* ]]; then echo "Error: fd00::/8 route is not installed via ${TUN_NAME}: ${FD00_ROUTE_LINE}" >&2 exit 1 fi if [[ "${RESOLV_127_STATUS}" == "missing" ]]; then echo "Warning: /etc/resolv.conf does not contain nameserver 127.0.0.1; .fips DNS may fail in some environments." >&2 fi echo "==> Testing .fips URL reachability" TMP_BODY="$(mktemp)" trap 'rm -f "${TMP_BODY}"' EXIT HTTP_CODE="$(curl -sS --max-time "${CURL_TIMEOUT}" -o "${TMP_BODY}" -w '%{http_code}' "${TEST_URL}")" BYTES="$(wc -c < "${TMP_BODY}" | tr -d '[:space:]')" if [[ "${HTTP_CODE}" != "200" ]]; then echo "Error: expected HTTP 200, got ${HTTP_CODE}." >&2 exit 1 fi if (( BYTES < MIN_BYTES )); then echo "Error: response too small (${BYTES} bytes, expected at least ${MIN_BYTES})." >&2 exit 1 fi echo "" echo "FIPS health summary (10 key metrics)" echo "1) fips_version: ${FIPS_VERSION}" echo "2) fipsctl_version: ${FIPSCTL_VERSION}" echo "3) node_state: $(json_get "${STATUS_JSON}" state)" echo "4) uptime_secs: $(json_get "${STATUS_JSON}" uptime_secs)" echo "5) npub: $(json_get "${STATUS_JSON}" npub)" echo "6) ipv6_addr: $(json_get "${STATUS_JSON}" ipv6_addr)" echo "7) peer_count: $(json_get "${STATUS_JSON}" peer_count)" echo "8) link_count: $(json_get "${STATUS_JSON}" link_count)" echo "9) session_count: $(json_get "${STATUS_JSON}" session_count)" echo "10) connected_peer_example: $(first_connected_peer "${PEERS_JSON}")" echo "11) tun_state: ${TUN_STATE}" echo "12) tun_name: ${TUN_NAME}" echo "13) fd00_route: ${FD00_ROUTE_LINE}" echo "14) resolv_conf_nameserver_127.0.0.1: ${RESOLV_127_STATUS}" echo "" echo "URL test: HTTP=${HTTP_CODE}, bytes=${BYTES}"