#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" INVENTORY_FILE="${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}" MIN_DAYS="${LAB_CERT_MIN_DAYS:-21}" DEFAULT_DOMAIN="${LAB_DOMAIN:-lab2025.duckdns.org}" DEFAULT_PUBLIC_URL="${LAB_PUBLIC_URL:-https://${DEFAULT_DOMAIN}/}" DEFAULT_GITEA_URL="${LAB_GITEA_ROOT_URL:-${LAB_GITEA_URL:-https://${DEFAULT_DOMAIN}/git/}}" failures=0 hosts_file="" cleanup() { if [ -n "$hosts_file" ] && [ -f "$hosts_file" ]; then rm -f "$hosts_file" fi } trap cleanup EXIT section() { printf '\n== %s ==\n' "$1" } have() { command -v "$1" >/dev/null 2>&1 } mark_fail() { failures=$((failures + 1)) printf 'fail: %s\n' "$1" } inventory_scalar() { local path="$1" if have ruby && [ -f "$INVENTORY_FILE" ]; then ruby -ryaml -e ' data = YAML.load_file(ARGV[0]) value = ARGV[1].split(".").reduce(data) { |memo, key| memo.respond_to?(:[]) ? memo[key] : nil } puts value if value.is_a?(String) ' "$INVENTORY_FILE" "$path" 2>/dev/null || true fi } inventory_hosts() { if have ruby && [ -f "$INVENTORY_FILE" ]; then ruby -ryaml -e ' data = YAML.load_file(ARGV[0]) domain = data.fetch("domain", {}) hosts = [domain["base"]] + Array(domain["subdomains"]) puts hosts.compact.uniq ' "$INVENTORY_FILE" 2>/dev/null || true fi } domain_base="$(inventory_scalar domain.base)" public_url="$(inventory_scalar domain.public_url)" gitea_url="$(inventory_scalar domain.gitea_url)" domain_base="${domain_base:-$DEFAULT_DOMAIN}" public_url="${public_url:-$DEFAULT_PUBLIC_URL}" gitea_url="${gitea_url:-$DEFAULT_GITEA_URL}" hosts="$(inventory_hosts)" if [ -z "$hosts" ]; then hosts="$domain_base" fi hosts_file="$(mktemp "${TMPDIR:-/tmp}/homelab-cert-hosts.XXXXXX")" printf '%s\n' "$hosts" >"$hosts_file" section "DNS Resolution" while IFS= read -r host; do [ -n "$host" ] || continue if have getent; then if getent hosts "$host" >/dev/null 2>&1; then printf '%-40s ok\n' "$host" else printf '%-40s fail\n' "$host" failures=$((failures + 1)) fi elif have dig; then if dig "$host" A +time=3 +tries=1 +short | grep -q .; then printf '%-40s ok\n' "$host" else printf '%-40s fail\n' "$host" failures=$((failures + 1)) fi else echo "getent or dig is required for DNS checks." failures=$((failures + 1)) break fi done <"$hosts_file" section "TLS Expiry" while IFS= read -r host; do [ -n "$host" ] || continue if python3 - "$host" "$MIN_DAYS" <<'PY' import datetime import os import socket import ssl import sys host = sys.argv[1] minimum_days = int(sys.argv[2]) context = ssl.create_default_context() try: with socket.create_connection((host, 443), timeout=8) as sock: with context.wrap_socket(sock, server_hostname=host) as tls_sock: cert = tls_sock.getpeercert() except Exception as exc: print(f"{host:<40} fail tls_connect={exc}") sys.exit(1) not_after = datetime.datetime.strptime( cert["notAfter"], "%b %d %H:%M:%S %Y %Z" ).replace(tzinfo=datetime.timezone.utc) days = (not_after - datetime.datetime.now(datetime.timezone.utc)).days issuer_parts = cert.get("issuer", ()) issuer = ",".join("=".join(item) for group in issuer_parts for item in group) status = "ok" if days >= minimum_days else "warn" print(f"{host:<40} {status} expires={not_after.date()} days={days} issuer={issuer}") sys.exit(0 if days >= minimum_days else 1) PY then : else failures=$((failures + 1)) fi done <"$hosts_file" section "Public HTTP" for url in "$public_url" "$gitea_url"; do status="$(curl -k -sS -o /dev/null -w '%{http_code}' --max-time 10 "$url" 2>/dev/null || true)" case "$status" in 200|301|302|307|308) printf '%-60s ok http=%s\n' "$url" "$status" ;; *) printf '%-60s fail http=%s\n' "$url" "${status:-none}" failures=$((failures + 1)) ;; esac done section "Edge IP Drift" edge_ip="$(inventory_scalar hosts.oci_edge.public_ip)" if [ -n "$edge_ip" ] && have dig; then resolved="$(dig "$domain_base" A +time=3 +tries=1 +short | tail -1)" if [ "$resolved" = "$edge_ip" ]; then printf '%-40s ok resolved=%s\n' "$domain_base" "$resolved" else mark_fail "$domain_base resolves to ${resolved:-none}, expected OCI edge $edge_ip" fi else echo "Skipping edge IP drift check; dig or OCI edge inventory value is unavailable." fi if [ "$failures" -gt 0 ]; then printf '\ncert-check found %s issue(s).\n' "$failures" exit 1 fi printf '\ncert-check found no blocking issues.\n'