Restore

O Operator
Restore

Restore file to captured state

Every captured change carries its SHA-256-addressed content in BLOB_STORE. Restoring writes that content back to the original path — current file gets backed up to <path>.drift_restore_backup_<epoch> before overwrite.

What will change
Preview -- nothing has been written yet.
Target file/var/www/vhosts/3dshawn.com/site1/alert_test_catcher.cgi
SiteDriftSense self-monitor on local
Kindlocal
Captured at2026-07-11 18:55:00
Captured SHA0c1ec2c322e7b8b52f3f78b84ff0134d2b87b54aeae1adb7fd4119af6ecdaba9
Current state on disk
Live check just now. If SHAs match, the restore is a no-op.
File presentyes
Size3599 bytes
Current SHA215f8f105f61
Same as captured?no -- restore will change it
What restore will change — live diff
Left column shows current on-disk content; right shows what restore will write. +2 additions, -12 deletions, 83 unchanged context lines.
11#!/usr/bin/perl
22#======================================================================
33# DriftSense -- /alert_test_catcher.cgi
44#
55# Tiny endpoint that accepts POSTs from the alert dispatcher and appends
66# the raw body + headers to /var/log/drift_sense/test_catcher.log. Used
77# for end-to-end alert testing without needing an external webhook.
88#
99# GET /alert_test_catcher.cgi -> HTML view of the last N events
1010# POST anything -> log it, return 200 JSON
1111# GET /alert_test_catcher.cgi?raw=1 -> tail the log as text/plain
1212#======================================================================
1313use strict;
1414use warnings;
1515use CGI ();
1616use POSIX ();
1717
1818my $LOG = '/var/log/drift_sense/test_catcher.log';
1919
2020$|=1;
2121my $cgi = CGI->new;
2222my $method = $ENV{REQUEST_METHOD} || 'GET';
2323
2424if ($method eq 'POST') {
2525 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 }
26 if (defined $ENV{CONTENT_LENGTH} && $ENV{CONTENT_LENGTH} > 0) {
27 read(STDIN, $body, $ENV{CONTENT_LENGTH});
3828 }
3929 my $stamp = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime);
4030 my @hdrs;
4131 foreach my $k (sort keys %ENV) {
4232 next unless $k =~ /^(HTTP_|CONTENT_|REQUEST_|REMOTE_)/;
4333 push @hdrs, " $k = $ENV{$k}";
4434 }
4535 if (open(my $fh, '>>', $LOG)) {
4636 print $fh "==================================================\n";
4737 print $fh "[$stamp] delivery received\n";
4838 print $fh join("\n", @hdrs), "\n";
4939 print $fh "-- body ($ENV{CONTENT_LENGTH} bytes) --\n";
5040 print $fh $body, "\n";
5141 close($fh);
5242 }
5343 print "Content-Type: application/json\n\n";
5444 print '{"ok":true,"received_bytes":' . length($body) . '}';
5545 exit 0;
5646}
5747
5848# GET modes
5949if ($cgi->param('raw')) {
6050 print "Content-Type: text/plain; charset=utf-8\nCache-Control: no-cache\n\n";
6151 if (open(my $fh, '<', $LOG)) {
6252 my @lines = <$fh>; close($fh);
6353 # tail last 500 lines
6454 my $start = @lines > 500 ? scalar(@lines) - 500 : 0;
6555 print join('', @lines[$start .. $#lines]);
6656 } else {
6757 print "(no deliveries yet)\n";
6858 }
6959 exit 0;
7060}
7161
7262# HTML view
7363print "Content-Type: text/html; charset=utf-8\nCache-Control: no-cache\n\n";
7464my $body = '';
7565if (open(my $fh, '<', $LOG)) {
7666 local $/; $body = <$fh>; close($fh);
7767}
7868my $entries = 0;
7969$entries++ while $body =~ /===+/g;
8070
8171my $esc = $body // '';
8272$esc =~ s/&/&amp;/g; $esc =~ s/</&lt;/g; $esc =~ s/>/&gt;/g;
8373
8474print <<"HTML";
8575<!doctype html><html><head><meta charset="utf-8"><title>Alert catcher -- DriftSense</title>
8676<link rel="stylesheet" href="/assets/styles/drift_sense.css">
8777<meta http-equiv="refresh" content="10">
8878</head><body>
8979<div style="padding:24px;max-width:1200px;margin:0 auto">
9080 <h1 style="color:var(--accent);font-family:var(--sans)">Alert test catcher</h1>
9181 <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>
9282 <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>
9383</div>
9484</body></html>
9585HTML
Confirm restore
Backs current content to <target>.drift_restore_backup_<epoch>, writes the captured content, verifies SHA post-write.
Cancel Logged to RESTORE_LOG regardless of outcome.