A network reliability toolkit: TLS expiry monitoring, capacity forecasting, and change-validation diffs.
Three reliability problems show up in nearly every network team's backlog: certs that expire on a Friday afternoon, links that hit 100% utilization weeks before anyone notices the slope, and config changes that quietly break things downstream. relnet runs all three checks on a schedule against your gear, persists every observation to a single SQLite file, and fires state-transition alerts to stdout or Slack only when something actually changes. Built for SRE and netops teams who want one auditable tool with no external services, no message bus, and no agents.
$ relnet init
Wrote starter config to /home/op/relnet/config.yaml.
Initialised database at /home/op/relnet/relnet.db.
$ relnet check tls
tls: ok
$ relnet forecast --device edge1 --interface Gi0/1
edge1/Gi0/1: ~18.3 days to 85% (slope 1.92%/day)
$ relnet alerts --active
2026-05-28T08:14:02+00:00 critical tls/expired.badssl.com:443 expired.badssl.com:443 expires in -1290 days. [active]
2026-05-28T11:02:17+00:00 warn capacity/edge1/Gi0/1 edge1/Gi0/1 utilization in=78.4% out=72.1% (warn). [active]
+-------------------+
| relnet CLI | (click; one-shot + daemon entry points)
+---------+---------+
|
+-----------------+-----------------+
| | |
+-------v------+ +-------v------+ +-------v-------+
| TLS collector| | Capacity | | Change |
| (ssl/socket) | | collector | | collector |
| | | (snmp/mock) | | (netmiko/mock)|
+-------+------+ +------+-------+ +-------+-------+
| | |
| +-------------+--------------+ |
| | forecasting/linear.py | | (numpy polyfit, used by capacity)
| +----------------------------+ |
v v v
+---+----------------+------------------+---+
| core/alert_dispatcher.py | (state-transition gate)
+---------+-------------------------+--------+
| |
+-------v------+ +--------v------+
| alerters/ | | alerters/ |
| stdout.py | | slack.py |
+--------------+ +---------------+
|
v
+---------+----------------------+
| core/repository.py (SQLite) |
+--------------------------------+
| scheduler.py + metrics.py |
+--------------------------------+
core/ holds the framework primitives: config loader, JSON logger, SQLite schema and connection helper, the typed repository, the state-transition alert dispatcher, the scheduler, and the in-memory metrics registry. collectors/ holds the three collectors, each backed by a pluggable source layer (mock or real). alerters/ holds the two shipped backends. forecasting/ holds the linear-fit saturation predictor used by the capacity collector. cli.py wires everything together and is the only module that calls click.echo or print directly.
git clone https://github.com/RajParmar2003/relnet-toolkit.git
cd relnet-toolkit
pip install -e ".[dev]"
relnet init
relnet daemonThat's it. The default config runs end-to-end with mock data: no real network or hardware required. The capacity collector generates seeded random-walk counters, the change collector reads bundled cisco_ios fixtures, and the TLS collector hits the three example hosts in config.example.yaml (one of which is expired.badssl.com, included so the first run produces a recognisable critical alert).
Drop --editable if you only need to run the tool. Use Python 3.11 or 3.12.
The full annotated example lives in config.example.yaml. The key sections:
database_path: ./relnet.db
log:
level: INFO # DEBUG | INFO | WARNING | ERROR
# file: ./relnet.log
collectors:
tls:
enabled: true
interval_seconds: 21600
warn_days: 30
critical_days: 7
targets:
- {host: example.com, port: 443}
capacity:
enabled: true
interval_seconds: 300
warn_pct_immediate: 75.0
critical_pct_immediate: 90.0
warn_days_predicted: 21
critical_days_predicted: 7
predicted_target_pct: 85.0
devices:
- name: edge1
source: mock
interfaces: [Gi0/1, Gi0/2]
change:
enabled: true
interval_seconds: 14400
warn_lines_threshold: 20
devices:
- name: edge1
device_type: cisco_ios
source: mock
fixture_dir: ./tests/fixtures/cisco_ios
alerters:
stdout:
enabled: true
# slack:
# enabled: true
# webhook_url: ${SLACK_WEBHOOK_URL}Environment variables. Secrets are kept out of YAML: ${VAR} references are resolved against os.environ at config-load time, and a missing required variable raises a clear error before any collector runs. The shipped variables you may need:
SLACK_WEBHOOK_URL: webhook for the Slack alerter.- SSH credentials and SNMP communities flow through device-specific config blocks when those sources are wired in; the default configuration uses local mocks throughout.
Following twelve-factor, configuration that varies between environments (URLs, credentials) belongs in the environment, not the YAML.
| Command | One-line | Example |
|---|---|---|
relnet init |
Create the SQLite DB and config.yaml from the example. Idempotent. |
relnet init |
relnet daemon |
Run the scheduling loop foreground until SIGTERM/SIGINT. | relnet daemon --config /etc/relnet/config.yaml |
relnet check tls |
One-shot TLS collector run. | relnet check tls |
relnet check capacity |
One-shot capacity collector run. | relnet check capacity |
relnet check change |
One-shot change collector run, auto-labeled. | relnet check change |
relnet snapshot --label NAME |
Change collector run with an operator-supplied label. | relnet snapshot --label pre-change-2026-05-28-1430 |
relnet status |
Table of collectors with last_run_at, next_run_at, last_status. | relnet status |
relnet alerts [--active] [--severity LEVEL] |
List recent alerts. | relnet alerts --active --severity critical |
relnet diff LABEL-A LABEL-B [--device NAME] |
Diff two stored snapshots by label. | relnet diff pre-change-... post-change-... |
relnet forecast [--device NAME] [--interface NAME] |
Current saturation predictions for configured interfaces. | relnet forecast --device edge1 |
relnet metrics |
Internal tool metrics as a single JSON object on stdout. | relnet metrics | jq . |
Every command accepts --config <path> to override ./config.yaml.
The five tradeoffs most worth flagging up front. The long-form rationale lives in docs/architecture.md.
SQLite over Postgres. A single embedded file is enough for one operator team's worth of data, removes the operational burden of a database server, and makes the tool deployable as a single binary plus a config file. The repository pattern keeps the swap to Postgres a config change, not a rewrite.
Sleep-loop scheduler over APScheduler / Celery / cron. State lives in the collector_state table, not in scheduler memory, so the daemon is restart-safe by construction. Adding a dependency on a job queue would buy us multi-process scheduling we don't need and an in-memory state model we'd then have to reconcile with the table.
State-transition alerts, not threshold pings. Every observation produces a current-state event handed to the dispatcher; the dispatcher only emits when the state for (collector, target) actually changes. This makes on-call paging quiet by default and makes resolution events explicit (severity: ok) so the operator knows when something self-healed.
Stdlib logging with a JSON formatter, not loguru/structlog. A single 60-line formatter beats taking on a dependency for the same outcome. Every log line is one JSON object with ts, level, logger, msg, plus any structured extra fields, which composes well with jq, journald, and standard ingestion pipelines.
Linear forecasting only in v1. numpy.polyfit(deg=1) over a configurable lookback window is enough to catch the weekly-and-monthly slopes that drive capacity planning; Holt-Winters and ARIMA come back in proportion to the variance you can't explain with a line. Future work is to add weighted/seasonal predictors behind the same Forecast | None interface.
- Weighted and seasonal forecasting (Holt-Winters, ARIMA, Prophet) behind the existing
forecast_time_to_thresholdcontract. - Structured per-command parsers for the change collector (e.g., BGP-neighbor state from
show ip bgp summary) so diffs can be classified semantically, not by line count. - Additional alerter backends: PagerDuty, Opsgenie, generic webhook with templated payload.
- Postgres backend behind the repository abstraction for teams running multi-node operators or wanting OLAP-style queries.
- Per-host config overrides (group inheritance, profile composition) for fleets where every device has bespoke commands or thresholds.
- Prometheus
/metricsHTTP endpoint exposing the same in-memory countersrelnet metricsreturns today. - Distributed multi-instance coordination (leader election, leased ticks) for high-availability deployments.
Released under the MIT License.
Author: Rajwinder Parmar (@RajParmar2003), a network automation engineer with a Verizon NMC background and an MBA in MIS & AI in progress; building an AI governance platform on the side.