- C# 91%
- PowerShell 9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Build and Test / build (push) Successful in 58s
Reviewed-on: #6 |
||
| .forgejo/workflows | ||
| samples | ||
| src/VigilanceSentinel | ||
| tests/VigilanceSentinel.Tests | ||
| .gitignore | ||
| ARCHITECTURE.md | ||
| CONTRIBUTING.md | ||
| Directory.Build.props | ||
| LICENSE | ||
| publish.ps1 | ||
| README.md | ||
| SECURITY.md | ||
| SETUP-GUIDE.md | ||
| THIRD-PARTY-NOTICES.md | ||
| VigilanceSentinel.sln | ||
Vigilance Sentinel
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:
- A check fails. The consecutive failure counter increments.
- When the counter reaches
consecutiveFailuresBeforeAlert(default: 3), the first alert email is sent. Set this to1to alert on the very first failure. - 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. - 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_gmtmust be among the_fieldsrequested. The check refuses to guess fromdate, 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.
timeZoneaccepts IANA identifiers such asAmerica/Chicagoon 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.