191 lines
5.0 KiB
Bash
Executable File
191 lines
5.0 KiB
Bash
Executable File
#!/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}"
|