added on local at 2026-07-11 18:57:53
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # DriftSense -- /alert_test_catcher.cgi | |
| 4 | # | |
| 5 | # Tiny endpoint that accepts POSTs from the alert dispatcher and appends | |
| 6 | # the raw body + headers to /var/log/drift_sense/test_catcher.log. Used | |
| 7 | # for end-to-end alert testing without needing an external webhook. | |
| 8 | # | |
| 9 | # GET /alert_test_catcher.cgi -> HTML view of the last N events | |
| 10 | # POST anything -> log it, return 200 JSON | |
| 11 | # GET /alert_test_catcher.cgi?raw=1 -> tail the log as text/plain | |
| 12 | #====================================================================== | |
| 13 | use strict; | |
| 14 | use warnings; | |
| 15 | use CGI (); | |
| 16 | use POSIX (); | |
| 17 | ||
| 18 | my $LOG = '/var/log/drift_sense/test_catcher.log'; | |
| 19 | ||
| 20 | $|=1; | |
| 21 | my $cgi = CGI->new; | |
| 22 | my $method = $ENV{REQUEST_METHOD} || 'GET'; | |
| 23 | ||
| 24 | if ($method eq 'POST') { | |
| 25 | my $body = ''; | |
| 26 | # Prefer CGI.pm's POSTDATA -- handles Plesk/nginx buffering + suEXEC | |
| 27 | # STDIN quirks reliably; fall back to sysread if that returns empty. | |
| 28 | $body = $cgi->param('POSTDATA') // ''; | |
| 29 | if ($body eq '' && defined $ENV{CONTENT_LENGTH} && $ENV{CONTENT_LENGTH} > 0) { | |
| 30 | binmode STDIN; | |
| 31 | my $want = int($ENV{CONTENT_LENGTH}); | |
| 32 | while (length($body) < $want) { | |
| 33 | my $chunk = ''; | |
| 34 | my $n = sysread(STDIN, $chunk, $want - length($body)); | |
| 35 | last unless $n; | |
| 36 | $body .= $chunk; | |
| 37 | } | |
| 38 | } | |
| 39 | my $stamp = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime); | |
| 40 | my @hdrs; | |
| 41 | foreach my $k (sort keys %ENV) { | |
| 42 | next unless $k =~ /^(HTTP_|CONTENT_|REQUEST_|REMOTE_)/; | |
| 43 | push @hdrs, " $k = $ENV{$k}"; | |
| 44 | } | |
| 45 | if (open(my $fh, '>>', $LOG)) { | |
| 46 | print $fh "==================================================\n"; | |
| 47 | print $fh "[$stamp] delivery received\n"; | |
| 48 | print $fh join("\n", @hdrs), "\n"; | |
| 49 | print $fh "-- body ($ENV{CONTENT_LENGTH} bytes) --\n"; | |
| 50 | print $fh $body, "\n"; | |
| 51 | close($fh); | |
| 52 | } | |
| 53 | print "Content-Type: application/json\n\n"; | |
| 54 | print '{"ok":true,"received_bytes":' . length($body) . '}'; | |
| 55 | exit 0; | |
| 56 | } | |
| 57 | ||
| 58 | # GET modes | |
| 59 | if ($cgi->param('raw')) { | |
| 60 | print "Content-Type: text/plain; charset=utf-8\nCache-Control: no-cache\n\n"; | |
| 61 | if (open(my $fh, '<', $LOG)) { | |
| 62 | my @lines = <$fh>; close($fh); | |
| 63 | # tail last 500 lines | |
| 64 | my $start = @lines > 500 ? scalar(@lines) - 500 : 0; | |
| 65 | print join('', @lines[$start .. $#lines]); | |
| 66 | } else { | |
| 67 | print "(no deliveries yet)\n"; | |
| 68 | } | |
| 69 | exit 0; | |
| 70 | } | |
| 71 | ||
| 72 | # HTML view | |
| 73 | print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n"; | |
| 74 | my $body = ''; | |
| 75 | if (open(my $fh, '<', $LOG)) { | |
| 76 | local $/; $body = <$fh>; close($fh); | |
| 77 | } | |
| 78 | my $entries = 0; | |
| 79 | $entries++ while $body =~ /===+/g; | |
| 80 | ||
| 81 | my $esc = $body // ''; | |
| 82 | $esc =~ s/&/&/g; $esc =~ s/</</g; $esc =~ s/>/>/g; | |
| 83 | ||
| 84 | print <<"HTML"; | |
| 85 | <!doctype html><html><head><meta charset="utf-8"><title>Alert catcher -- DriftSense</title> | |
| 86 | <link rel="stylesheet" href="/assets/styles/drift_sense.css"> | |
| 87 | <meta http-equiv="refresh" content="10"> | |
| 88 | </head><body> | |
| 89 | <div style="padding:24px;max-width:1200px;margin:0 auto"> | |
| 90 | <h1 style="color:var(--accent);font-family:var(--sans)">Alert test catcher</h1> | |
| 91 | <p style="color:var(--text-dim)">POST anything here and it gets appended to <code>$LOG</code>. Auto-refreshes every 10s. <a href="?raw=1" style="color:var(--accent-hi)">raw view</a></p> | |
| 92 | <div style="color:var(--text);font-size:12.5px;font-family:var(--mono);background:var(--bg-2);border:1px solid var(--border);border-radius:12px;padding:14px 16px;white-space:pre-wrap;max-height:75vh;overflow:auto">$esc</div> | |
| 93 | </div> | |
| 94 | </body></html> | |
| 95 | HTML |