Cross-platform .NET 8+ site monitoring console app with health checks, SSL expiry monitoring, wp-cron triggering, and email alerts
  • C# 91%
  • PowerShell 9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-21 14:12:18 -05:00
.forgejo/workflows Add publish.ps1, and stop trimming release artifacts 2026-08-21 14:03:39 -05:00
samples Add daily_post check: confirm today's post actually published 2026-08-21 13:26:43 -05:00
src/VigilanceSentinel Add daily_post check: confirm today's post actually published 2026-08-21 13:26:43 -05:00
tests/VigilanceSentinel.Tests Add daily_post check: confirm today's post actually published 2026-08-21 13:26:43 -05:00
.gitignore Initial implementation of Vigilance Sentinel 2026-06-03 15:13:34 -05:00
ARCHITECTURE.md Add publish.ps1, and stop trimming release artifacts 2026-08-21 14:03:39 -05:00
CONTRIBUTING.md Initial implementation of Vigilance Sentinel 2026-06-03 15:13:34 -05:00
Directory.Build.props fix: pin System.Private.Uri 4.3.2 for NuGetAudit vulnerability 2026-06-03 16:11:59 -05:00
LICENSE Initial implementation of Vigilance Sentinel 2026-06-03 15:13:34 -05:00
publish.ps1 Add publish.ps1, and stop trimming release artifacts 2026-08-21 14:03:39 -05:00
README.md Add publish.ps1, and stop trimming release artifacts 2026-08-21 14:03:39 -05:00
SECURITY.md Initial implementation of Vigilance Sentinel 2026-06-03 15:13:34 -05:00
SETUP-GUIDE.md Add daily_post check: confirm today's post actually published 2026-08-21 13:26:43 -05:00
THIRD-PARTY-NOTICES.md Initial implementation of Vigilance Sentinel 2026-06-03 15:13:34 -05:00
VigilanceSentinel.sln ci: convert solution to classic .sln for .NET 8 SDK compatibility 2026-06-05 11:02:00 -05:00

Vigilance Sentinel

Build and Test

A cross-platform site monitoring tool for WordPress and other web applications. Runs as a scheduled task (Windows Task Scheduler or Linux cron) and performs health checks, wp-cron triggering, SSL certificate expiry monitoring, daily publishing verification, and email alerting.

Features

  • wp-cron trigger - hits the WordPress cron endpoint on a schedule so scheduled posts publish on time, even with no site traffic
  • Daily post verification - confirms a post actually published today, because triggering wp-cron proves the endpoint responds and nothing more
  • Internal health check - monitors the origin server (IIS, Apache, nginx) via HTTP with optional Host header for multi-site setups
  • External health check - monitors the public-facing URL through reverse proxies and CDNs
  • SSL certificate expiry - checks TLS certificate expiry on external endpoints and alerts before renewal is due
  • Response time monitoring - alerts when endpoints exceed a configurable response time threshold
  • Email alerts - SMTP notifications with per-incident cooldown and automatic recovery messages
  • Multi-site - monitors multiple sites from a single installation using per-site JSON config files
  • Structured logging - per-site daily log files with automatic retention-based cleanup
  • Cross-platform - runs on Windows and Linux with .NET 8+

Quick Start

1. Install

Download the latest release for your platform from the Releases page, or build from source with the publish script:

.\publish.ps1

That runs the tests, then publishes a self-contained single-file binary for the current operating system into publish/<runtime>/. Use -Runtime linux-x64 or -Runtime all to target other platforms, and -SkipTests to skip the test run.

The equivalent dotnet invocation, if you prefer to run it directly:

dotnet publish src/VigilanceSentinel/VigilanceSentinel.csproj \
  --configuration Release \
  --runtime linux-x64 \
  --self-contained true \
  -p:PublishSingleFile=true \
  -o publish/

Do not add -p:PublishTrimmed=true. Trimming disables reflection-based serialization, and both the site config and state files are bound with System.Text.Json reflection. A trimmed build compiles with IL2026 warnings and then throws Reflection-based serialization has been disabled for this application on startup. Enabling it would require a JsonSerializerContext source generator for SiteConfig and SiteState.

2. Configure

Create a sites/ directory next to the executable and add a JSON config file for each site you want to monitor. See samples/example-site.json for the full config format.

vigilance-sentinel/
  VigilanceSentinel.exe    (or VigilanceSentinel on Linux)
  sites/
    my-blog.json
  state/                   (created automatically)
  logs/                    (created automatically)

3. Schedule

See SETUP-GUIDE.md for detailed instructions on creating a dedicated service account, setting file system permissions, and configuring a scheduled task (Windows) or cron job (Linux).

Quick start (Windows Task Scheduler):

Program: C:\tools\vigilance-sentinel\VigilanceSentinel.exe
Start in: C:\tools\vigilance-sentinel
Trigger: Every 5 minutes

Linux cron:

*/5 * * * * /opt/vigilance-sentinel/VigilanceSentinel

4. Secure the config directory

The sites/ directory contains SMTP credentials and should be restricted to the service account running the scheduled task. See SETUP-GUIDE.md for a complete walkthrough including dedicated service account creation and per-directory ACLs.

Quick start (Windows):

icacls "C:\tools\vigilance-sentinel\sites" /inheritance:r /grant:r "SYSTEM:(OI)(CI)F" /grant:r "Administrators:(OI)(CI)F"

Linux:

chmod 700 /opt/vigilance-sentinel/sites

Configuration Reference

Each JSON file in the sites/ directory defines one monitored site. All sections are optional; omit or disable sections you do not need.

Property Type Default Description
siteName string filename Identifier used in logs and alerts
wpCron.enabled bool true Whether to trigger wp-cron
wpCron.url string Internal URL to wp-cron.php
wpCron.timeoutSeconds int 30 Request timeout
healthCheck.internalUrl string Origin server URL (HTTP)
healthCheck.internalHostHeader string null Host header for multi-site IIS
healthCheck.externalUrl string Public URL (HTTPS)
healthCheck.timeoutSeconds int 30 Request timeout
healthCheck.responseTimeThresholdMs int 5000 Alert if response exceeds this
ssl.enabled bool true Whether to check SSL expiry
ssl.url string HTTPS URL to check
ssl.expiryWarningDays int 14 Alert this many days before expiry
contentFreshness.enabled bool true Whether to verify today's post published
contentFreshness.url string REST endpoint returning the newest published post
contentFreshness.timeZone string UTC Time zone deciding "today" and reading the cutoff
contentFreshness.expectPostByLocalTime string 11:00 Local time by which the day's post is due
contentFreshness.timeoutSeconds int 30 Request timeout
alerts.consecutiveFailuresBeforeAlert int 3 Failures before first alert
alerts.cooldownMinutes int 60 Minimum minutes between repeated alerts
smtp.host string SMTP server hostname
smtp.port int 587 SMTP port (587 for STARTTLS)
smtp.useTls bool true Enable STARTTLS
smtp.username string SMTP username
smtp.password string SMTP password
smtp.from string Sender email address
smtp.to string Recipient email address
logging.retentionDays int 30 Days to keep log files

Alerting Behavior

Alerts are tracked per check type (e.g., external_health, ssl_expiry, daily_post) independently. The alert lifecycle works as follows:

  1. A check fails. The consecutive failure counter increments.
  2. When the counter reaches consecutiveFailuresBeforeAlert (default: 3), the first alert email is sent. Set this to 1 to alert on the very first failure.
  3. While the incident is active, repeat alerts are suppressed for cooldownMinutes (default: 60). After the cooldown, if the check is still failing, another alert is sent.
  4. When the check succeeds again, a recovery email is sent automatically and the incident is cleared.

If smtp.host is empty or omitted, alerts are skipped and a warning is written to the log.

Verifying the Daily Post

The wp_cron check reports success whenever wp-cron.php returns HTTP 200. That proves the endpoint responds; it does not prove anything was published. A scheduled post whose cron event has been lost from WordPress's cron array stays in future status indefinitely while the endpoint keeps answering normally, so a site on a daily cadence can miss a day with every other check green.

The daily_post check closes that gap by asking the site what it last published:

"contentFreshness": {
  "enabled": true,
  "url": "https://www.example.com/wp-json/wp/v2/posts?per_page=1&orderby=date&order=desc&_fields=id,date_gmt,title,link",
  "timeZone": "America/Chicago",
  "expectPostByLocalTime": "11:00",
  "timeoutSeconds": 30
}

Before expectPostByLocalTime the check always passes, because the day's post is not due yet. From that time onwards it fails until something has been published on the current date in timeZone. Both the cutoff and the day boundary use that zone, because publishing schedules are set in site-local time.

Notes:

  • date_gmt must be among the _fields requested. The check refuses to guess from date, which WordPress emits in site-local time with no timezone designator.
  • No credentials are needed. The REST API exposes published posts anonymously and refuses anonymous requests for scheduled ones, so this reads only what a visitor could read.
  • The check reports the outcome, not the cause. It also fires for an empty publishing queue, a post left as a draft, and a post that was deleted. It cannot tell those apart.
  • It assumes a post every day. A site that publishes on weekdays only would alert every weekend; leave the check disabled on those sites.
  • timeZone accepts IANA identifiers such as America/Chicago on Windows as well as Linux. An empty value means UTC.

Console Output

When run manually, the application prints a banner with the version and timestamp, per-site progress with pass/fail indicators for each check, and a summary with the exit code. This output goes to stdout and does not affect the log files. When run from a scheduler, the console output is typically discarded.

Exit Codes

Code Meaning
0 All checks passed
1 One or more monitored checks failed (site issue)
2 Application or configuration error

SMTP Notes

This application uses System.Net.Mail.SmtpClient with STARTTLS on port 587. Implicit TLS on port 465 is not supported.

For SMTP servers that require authentication, provide username and password in the config. For internal relay servers that accept unauthenticated connections, omit username and password (or leave them as empty strings) and set useTls to false if the relay does not support STARTTLS. See samples/example-site-no-smtp-auth.json for a no-auth example.

Building from Source

dotnet build
dotnet test

Requires .NET 8.0 SDK or later. The application has zero external NuGet dependencies.

License

MIT