=============================================================================== LinkGuard 1.0.0 Connectivity watchdog with an append-only outage ledger Techlosoft "Network Control Center" product line =============================================================================== WHAT IT IS ------------------------------------------------------------------------------- LinkGuard watches a set of network targets on a schedule and keeps a permanent, append-only record of every time one of them went down or came back. From that record it computes real availability arithmetic: uptime percentage, outage count, total downtime, longest outage and mean time to recovery (MTTR). The point of the tool is the HISTORY and the ARITHMETIC, not a one-shot ping. The ledger is a TRANSITION LOG, never a sample dump. If you probe a target every 5 seconds for a week and it never fails, the ledger contains exactly one line for that target. A line is written only when: * a target is observed for the very first time -> event "initial" * a target's state actually changes UP<->DOWN -> event "transition" That is what makes the file small enough to keep forever and meaningful enough to compute statistics from. LinkGuard is a sibling of NetLens. NetLens answers "what is open right now and how fast does it answer" (port scanning, latency). LinkGuard answers "how reliable has this been over time" (transitions, outages, MTTR). They do not overlap. BUILD ------------------------------------------------------------------------------- Requires Go 1.24 or newer. Go standard library only - no third-party modules, no network access needed to build. go build -o linkguard . Cross-compiling: GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o dist/linkguard-windows-amd64.exe . GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o dist/linkguard-darwin-arm64 . GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o dist/linkguard-darwin-amd64 . GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o dist/linkguard-linux-amd64 . Prebuilt binaries for those four platforms are in dist/. COMMANDS ------------------------------------------------------------------------------- linkguard watch --config --ledger [flags] linkguard probe --config [--timeout ] [--json] linkguard report --ledger [--json] linkguard help | -h | --help linkguard version watch Probes every target once per interval and appends state transitions to the ledger. --config Target config JSON (required) --ledger Ledger JSONL, created if it does not exist (required) --interval Time between rounds (default 5s) --timeout Per-probe timeout (default 3s) --once Run exactly one round and exit --quiet Print transitions only, not every probe result On start, watch reads the existing ledger to recover each target's last known state. That means repeated "--once" invocations drive exactly the same state machine as one long-running watch process, which makes the tool usable from cron or a CI job, and makes its behaviour deterministic and testable. Ctrl-C (SIGINT) and SIGTERM stop the loop cleanly and exit 0. Every ledger record is fsynced at the moment it is written, so a signal, a crash or a power loss cannot lose a transition that was already reported. probe A single round against all targets. Prints the state of each one and exits. Never touches a ledger. Use it to validate a config or for an ad-hoc health check. report Reads a ledger and prints per-target and fleet-wide availability statistics. --json emits the same numbers as machine-readable JSON with full float precision. CONFIG FILE FORMAT ------------------------------------------------------------------------------- JSON. Either an object with a "targets" array, or a bare array of targets. { "targets": [ {"name": "api", "type": "http", "target": "https://api.example.com/health", "expected_status": 200}, {"name": "db", "type": "tcp", "target": "db.internal:5432"}, {"name": "resolver","type": "dns", "target": "example.com"} ] } Fields: name Unique label for the target. Required. This is the key the ledger and the statistics are grouped by, so keep it stable across config edits. type "tcp", "http" or "dns". Required. target tcp -> "host:port" http -> a full http:// or https:// URL dns -> a hostname expected_status http only. If set, the target is UP only when the response status matches this number exactly. If unset, any 2xx counts as UP. Setting it to 404 or 401 is a legitimate way to monitor an endpoint that is supposed to return that status. The config is validated up front: unknown types, duplicate names, empty targets, malformed URLs and tcp targets missing a port are all rejected with a specific message before any probing starts. PROBE SEMANTICS ------------------------------------------------------------------------------- tcp A TCP connection is opened to host:port and immediately closed. UP means the handshake completed. Nothing is sent and nothing is read. http A GET request. Redirects ARE followed; the status that is checked is the final one. UP means the status matched expected_status, or was any 2xx when expected_status is unset. The response body is read and discarded (capped at 64 KiB) so the connection can be released. HTTP probes deliberately IGNORE HTTP_PROXY/HTTPS_PROXY environment variables and always connect directly. A watchdog must observe the target itself, not a proxy sitting in front of it. dns The hostname is resolved with the system resolver. UP means at least one address came back. Resolving to an unreachable address still counts as UP - this probe tests name resolution, not reachability. All targets in a round are probed CONCURRENTLY, each under its own hard timeout. One dead or blackholed target therefore cannot delay the others: a round takes about as long as its slowest single probe, bounded by --timeout, not the sum of all probes. LEDGER FORMAT ------------------------------------------------------------------------------- JSON Lines - one JSON object per line, appended, never rewritten. {"ts":"2026-08-10T04:12:00.156070577Z","target":"flappy-web","type":"http", "addr":"http://127.0.0.1:18811/","state":"down","event":"transition", "reason":"dial tcp 127.0.0.1:18811: connect: connection refused"} ts RFC3339 with nanoseconds, always UTC target the target's name from the config type tcp | http | dns addr the address/URL that was probed state up | down event initial | transition reason why it is down (absent when up) The file is plain text and append-only, so it can be tailed, shipped to a log collector, rotated by date, or concatenated across hosts. Any tool that can read JSON Lines can read it. HOW THE STATISTICS ARE DEFINED ------------------------------------------------------------------------------- These definitions are applied consistently everywhere and are worth reading, because availability numbers are meaningless without them. Observation window A target's window starts at ITS first ledger record and ends at the LAST record in the whole ledger - the most recent moment any target was known to be observed. The window end is therefore taken from the data, not from the current wall clock, so a report over a given ledger always produces the same numbers no matter when you run it. Outage A "down" record, lasting until the next record for that target. If a down record is the last one for that target, the outage is ONGOING and is measured up to the end of the window. Ongoing outages are marked with an asterisk in the outage count. Total downtime The sum of all outage durations, ongoing outages INCLUDED. Longest outage The maximum of all outage durations, ongoing outages INCLUDED. Uptime % (window - total downtime) / window * 100. If the window has zero duration (a ledger with a single instant in it), uptime is reported as 100% if the target is up and 0% if it is down. MTTR The mean duration of COMPLETED outages only. An outage that has not recovered yet is EXCLUDED from MTTR - you cannot average a recovery that has not happened - while still counting toward the outage count, the total downtime and the longest outage. When a target has no completed outages, MTTR prints as "n/a" and is 0 in JSON with completed_outages: 0. Fleet summary Fleet uptime % is duration-weighted: (sum of windows - sum of downtime) / sum of windows. Fleet MTTR is the mean over every completed outage of every target pooled together, not the average of the per-target MTTRs. EXIT CODES ------------------------------------------------------------------------------- 0 the command ran successfully 1 bad invocation, bad config, unreadable/corrupt ledger, I/O error 0 explicit help (-h, --help, help) Exit status reflects whether the TOOL worked, not whether your targets are healthy. "probe" exits 0 even when every target is down; read the output or the --json summary to decide what to do about it. EXAMPLES ------------------------------------------------------------------------------- Validate a config and see current state: linkguard probe --config targets.json Run one round from cron every minute (state is recovered from the ledger): * * * * * /usr/local/bin/linkguard watch --config /etc/targets.json \ --ledger /var/log/linkguard.jsonl --once --quiet Run continuously in the foreground: linkguard watch --config targets.json --ledger link.jsonl --interval 30s Weekly availability numbers: linkguard report --ledger link.jsonl linkguard report --ledger link.jsonl --json | jq '.summary.uptime_percent' WHAT IS IMPLEMENTED ------------------------------------------------------------------------------- * TCP connect probing with a per-probe timeout * HTTP/HTTPS GET probing with exact or any-2xx status matching * DNS resolution probing * Concurrent probing of all targets, one hard timeout per probe * Scheduled rounds (--interval) and single rounds (--once) * State recovery from an existing ledger on start * Append-only JSON Lines transition ledger, fsynced per record * Clean SIGINT/SIGTERM shutdown * Availability statistics: uptime %, outage count, total downtime, longest outage, MTTR, per target and fleet-wide * Text and JSON output for probe and report * Up-front config validation with specific error messages WHAT IS NOT IMPLEMENTED ------------------------------------------------------------------------------- Stated plainly so nobody is surprised. * ICMP PING IS NOT IMPLEMENTED. Sending an ICMP echo request requires a raw socket, which requires root/administrator privileges (or the CAP_NET_RAW capability, or a privileged helper) on every platform LinkGuard ships for. A portable, dependency-free CLI that ordinary users can run without elevation cannot do it. LinkGuard uses TCP connect, HTTP GET and DNS resolution instead. For availability monitoring this is usually BETTER than ICMP anyway: it proves the service is actually answering, not merely that a host's network stack is alive. But it is not ping, and where this document says "reachable" it means "the TCP/HTTP/DNS probe succeeded". * No traceroute or path analysis. Also raw-socket / privileged territory. * No latency trending or bandwidth measurement. Probe latency is displayed live by "probe" and "watch", but it is NOT stored in the ledger and does not appear in reports. The ledger records transitions only, by design. * No alerting. Nothing is emailed, posted to a webhook, or pushed anywhere. Transitions are printed to stdout and written to the ledger; wiring that into an alerting system is left to the caller. * No daemon/service integration. There is no install command, no unit file, no Windows service registration. Run it under systemd, launchd, a supervisor, or cron with --once. * No ledger rotation, compaction or retention policy. The file only grows on transitions, so it grows very slowly, but managing it long-term is yours. * No authentication, custom headers, request bodies or client certificates for HTTP probes. GET only. * No IPv4/IPv6 forcing, no source-address selection, no per-target retry or flap damping. A single failed probe is a DOWN transition. * No concurrency limit. Every target in the config is probed in its own goroutine simultaneously. This is fine for the tens-to-hundreds of targets the tool is designed for; a config with many thousands of targets would want a worker pool. ROADMAP ------------------------------------------------------------------------------- * ICMP echo probing where privileges allow, with automatic fallback to TCP * Traceroute-style path analysis to locate where connectivity breaks * Bandwidth and latency trending, with percentile summaries over time * Alerting on transitions via webhook or email, with flap damping * Running as a first-class system service (systemd unit, launchd plist, Windows service) with an install command ===============================================================================