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/TEMPLATES/servers.html
SiteDriftSense self-monitor on local
Kindlocal
Captured at2026-07-11 23:15:14
Captured SHAb85517f9ee2ee4a3b2596cfcdf36c89e45612fa3b3a5037ff9c95224669b111d
Current state on disk
Live check just now. If SHAs match, the restore is a no-op.
File presentyes
Size17969 bytes
Current SHA66b67331501d
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. +0 additions, -8 deletions, 278 unchanged context lines.
11<div class="page-head">
22 <span class="page-eyebrow"><span class="dot"></span> Configure</span>
33 <h1 class="page-title">Servers &amp; agents</h1>
44 <p class="page-subtitle">The nodes DriftSense knows about &mdash; local (this box) and SSH agents (remote servers scanned over a restricted-key SSH connection). Add one at a time or bulk-import a list of hostnames sharing a single key.</p>
5</div>
6
7<div class="ds-guide">
8 <div class="dsg-icon">i</div>
9 <div>
10 <span class="dsg-title">Two flavors:</span>
11 <strong>Local</strong> = scanner runs on the same box as the file being watched (fast, zero-network). <strong>SSH agent</strong> = scanner SSHes into the target box using a restricted key; only footprint on that box is one <code>authorized_keys</code> entry. Great for <em>stealth deployment</em> where you don't want developers on the monitored box to see DriftSense installed. Paste a PEM key below to store it <span class="ds-help" data-help-title="Encrypted at rest" data-help-body="PEM keys pasted here are encrypted with an HMAC-SHA-256 CTR construction using a per-install master key at <code>/etc/drift_sense/master.key</code> (mode 0640, group psacln). Neither the DB nor a filesystem snapshot exposes plaintext keys."></span> in the DB (encrypted). Test any row with the "Test" button.
12 </div>
135</div>
146
157[if:$has_imported]
168<div class="banner banner-ok" style="margin:0 0 16px;padding:10px 14px;border-radius:10px;background:rgba(52,211,153,.10);border:1px solid rgba(52,211,153,.35);color:#a7f3d0;font-size:13px">
179 Bulk import created $imported server(s). They'll be scanned on the next agent tick (within 5 minutes).
1810</div>
1911[/if]
2012
2113[if:!$crypto_ready]
2214<div class="banner banner-warn" style="margin:0 0 16px;padding:10px 14px;border-radius:10px;background:rgba(245,158,11,.10);border:1px solid rgba(245,158,11,.35);color:#fcd34d;font-size:13px">
2315 <strong>Master encryption key missing.</strong> Encrypted key storage requires <code>/etc/drift_sense/master.key</code>. Run on the server: <code>sudo perl -e 'use lib "/var/www/vhosts/3dshawn.com/site1"; use MODS::Crypto; MODS::Crypto::ensure_master()'</code>. Until then, encrypted-blob storage is unavailable and only on-disk <code>ssh_key_path</code> works.
2416</div>
2517[/if]
2618
2719<!-- ============================ TOP TABS ============================ -->
2820<div style="display:flex;gap:8px;margin-bottom:16px">
2921 <a class="tr-chip[if:!$bulk_mode] is-active[/if]" href="/servers.cgi">Single-server form</a>
3022 <a class="tr-chip[if:$bulk_mode] is-active[/if]" href="/servers.cgi?bulk=1">Bulk import</a>
3123</div>
3224
3325<div class="dash-grid" style="grid-template-columns:1fr 1fr;margin-bottom:20px">
3426 <!-- ============================ SINGLE-SERVER FORM ============================ -->
3527 [if:!$bulk_mode]
3628 <div class="module glow-teal">
3729 <div class="module-head"><div class="left">
3830 <div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/></svg></div>
3931 <div class="module-title">[if:$is_edit]Edit server #$server_id[/if][if:!$is_edit]Add server[/if]</div>
4032 <span class="module-subtitle">Pick <code>local</code> for the DriftSense host itself, or <code>ssh_agent</code> for anything remote.</span>
4133 </div></div>
4234 <div class="module-body">
4335 <form method="post" action="/servers.cgi" style="display:flex;flex-direction:column;gap:12px">
4436 <input type="hidden" name="op" value="$form_op">
4537 [if:$is_edit]<input type="hidden" name="server_id" value="$server_id">[/if]
4638
4739 <div style="display:flex;gap:12px">
4840 <label style="flex:2;display:flex;flex-direction:column;gap:4px">
4941 <span class="rb-label">Server name</span>
5042 <input type="text" name="server_name" value="$server_name" required placeholder="e.g. prod-web-1"
5143 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:inherit">
5244 </label>
5345 <label style="flex:1;display:flex;flex-direction:column;gap:4px">
5446 <span class="rb-label">Kind</span>
5547 <select name="kind" style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px">
5648 <option value="local" $k_local_sel>local</option>
5749 <option value="ssh_agent" $k_ssh_sel>ssh_agent</option>
5850 </select>
5951 </label>
6052 </div>
6153
6254 <div style="display:flex;gap:12px">
6355 <label style="flex:2;display:flex;flex-direction:column;gap:4px">
6456 <span class="rb-label">SSH host</span>
6557 <input type="text" name="ssh_host" value="$ssh_host" placeholder="e.g. prod-web-1.internal or 10.0.0.42"
6658 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px">
6759 </label>
6860 <label style="flex:1;display:flex;flex-direction:column;gap:4px">
6961 <span class="rb-label">User</span>
7062 <input type="text" name="ssh_user" value="$ssh_user"
7163 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px">
7264 </label>
7365 <label style="flex:1;display:flex;flex-direction:column;gap:4px">
7466 <span class="rb-label">Port</span>
7567 <input type="number" name="ssh_port" value="$ssh_port" min="1" max="65535"
7668 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px">
7769 </label>
7870 </div>
7971
8072 <label style="display:flex;flex-direction:column;gap:4px">
8173 <span class="rb-label">SSH key path <span class="dim" style="text-transform:none;letter-spacing:0;font-size:10.5px">(on-disk fallback if you don't paste a key below)</span></span>
8274 <input type="text" name="ssh_key_path" value="$ssh_key_path" placeholder="/etc/drift_sense/keys/agent_key"
8375 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px">
8476 </label>
8577
8678 <label style="display:flex;flex-direction:column;gap:4px">
8779 <span class="rb-label">
8880 Paste PEM key (encrypted at rest with master key)
8981 [if:$has_key_blob]<span class="pill" style="background:rgba(52,211,153,.14);color:#86efac;border-color:rgba(52,211,153,.35);margin-left:6px">key already stored</span>[/if]
9082 </span>
9183 <textarea name="ssh_key_blob_plain" rows="4" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----&#10;...&#10;-----END OPENSSH PRIVATE KEY-----"
9284 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:11.5px;resize:vertical"></textarea>
9385 </label>
9486
9587 <label style="display:flex;flex-direction:column;gap:4px">
9688 <span class="rb-label">Extra SSH options <span class="dim" style="text-transform:none;letter-spacing:0;font-size:10.5px">(optional, space-separated <code>-o key=value</code> pairs)</span></span>
9789 <input type="text" name="ssh_options" value="$ssh_options" placeholder="e.g. IdentitiesOnly=yes ProxyJump=bastion.internal"
9890 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:11.5px">
9991 </label>
10092
10193 <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:6px">
10294 [if:$is_edit]<a href="/servers.cgi" class="tr-chip">Cancel</a>[/if]
10395 <button type="submit" class="tr-chip is-active" style="border:none">[if:$is_edit]Save[/if][if:!$is_edit]Add server[/if]</button>
10496 </div>
10597 </form>
10698 </div>
10799 </div>
108100 [/if]
109101
110102 <!-- ============================ BULK IMPORT ============================ -->
111103 [if:$bulk_mode]
112104 <div class="module glow-emerald" style="grid-column:1 / span 2">
113105 <div class="module-head"><div class="left">
114106 <div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg></div>
115107 <div class="module-title">Bulk import</div>
116108 <span class="module-subtitle">Paste one hostname per line + one shared SSH key + a base scan config. DriftSense fans out.</span>
117109 </div></div>
118110 <div class="module-body">
119111 <form method="post" action="/servers.cgi" style="display:flex;flex-direction:column;gap:14px">
120112 <input type="hidden" name="op" value="bulk_import">
121113
122114 <label style="display:flex;flex-direction:column;gap:4px">
123115 <span class="rb-label">Hostnames &mdash; one per line, optional <code>label|host</code> or <code>label=host</code> format</span>
124116 <textarea name="hosts" rows="7" placeholder="prod-web-1.internal&#10;prod-web-2 = 10.0.0.11&#10;webstls | webstls.com&#10;staging | staging.internal"
125117 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:12.5px;resize:vertical" required></textarea>
126118 </label>
127119
128120 <div style="display:flex;gap:12px">
129121 <label style="flex:1;display:flex;flex-direction:column;gap:4px">
130122 <span class="rb-label">User</span>
131123 <input type="text" name="ssh_user" value="root"
132124 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px">
133125 </label>
134126 <label style="flex:1;display:flex;flex-direction:column;gap:4px">
135127 <span class="rb-label">Port</span>
136128 <input type="number" name="ssh_port" value="22"
137129 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px">
138130 </label>
139131 </div>
140132
141133 <label style="display:flex;flex-direction:column;gap:4px">
142134 <span class="rb-label">Shared PEM key (encrypted at rest) &mdash; optional if all hosts use <code>ssh_key_path</code></span>
143135 <textarea name="ssh_key_blob_plain" rows="4" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----&#10;...&#10;-----END OPENSSH PRIVATE KEY-----"
144136 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:11.5px;resize:vertical"></textarea>
145137 </label>
146138
147139 <hr style="border:none;border-top:1px solid var(--border);margin:4px 0">
148140
149141 <div class="rb-label" style="margin-bottom:-6px">Base scan config &mdash; applied to every host</div>
150142
151143 <label style="display:flex;flex-direction:column;gap:4px">
152144 <span class="rb-label">Scan path</span>
153145 <input type="text" name="scan_path" value="/etc" placeholder="/etc or /var/www"
154146 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px" required>
155147 </label>
156148
157149 <div style="display:flex;gap:12px">
158150 <label style="flex:2;display:flex;flex-direction:column;gap:4px">
159151 <span class="rb-label">Ignore globs (comma-separated)</span>
160152 <input type="text" name="ignore_list" value="*.log,*.pid,*.sock,*.tmp,tmp/*,cache/*,logs/*,.git/*,node_modules/*"
161153 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:11.5px">
162154 </label>
163155 <label style="flex:1;display:flex;flex-direction:column;gap:4px">
164156 <span class="rb-label">File-type filter <span class="dim" style="text-transform:none;letter-spacing:0;font-size:10.5px">(blank = all)</span></span>
165157 <input type="text" name="file_type_filter" value="" placeholder="cgi,pl,pm,html,css,js,md,sql,conf"
166158 style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:11.5px">
167159 </label>
168160 </div>
169161
170162 <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:6px">
171163 <a href="/servers.cgi" class="tr-chip">Cancel</a>
172164 <button type="submit" class="tr-chip is-active" style="border:none">Import all</button>
173165 </div>
174166 </form>
175167 </div>
176168 </div>
177169 [/if]
178170
179171 <!-- ============================ QUICK HELP CARD ============================ -->
180172 [if:!$bulk_mode]
181173 <div class="module glow-sky">
182174 <div class="module-head"><div class="left">
183175 <div class="module-title">Quick help</div>
184176 <span class="module-subtitle">The three most common setup shapes.</span>
185177 </div></div>
186178 <div class="module-body" style="font-size:13.5px;color:var(--text-dim);line-height:1.75">
187179 <p style="margin-bottom:10px"><strong style="color:var(--text)">Same server</strong> &mdash; pick <code>local</code>, skip all the SSH fields. This is the default install monitoring itself.</p>
188180 <p style="margin-bottom:10px"><strong style="color:var(--text)">Remote via SSH</strong> &mdash; pick <code>ssh_agent</code>, fill <code>ssh_host</code> + <code>ssh_user</code>, and either paste the PEM key below (encrypted at rest with the master key) OR give a path to a keyfile on disk.</p>
189181 <p style="margin-bottom:10px"><strong style="color:var(--text)">Test the connection</strong> &mdash; after saving, hit the "Test" button on the server row. Runs an SSH probe and reports back inline.</p>
190182 <p><strong style="color:var(--text)">Bulk import 20 hosts at once</strong> &mdash; switch to the "Bulk import" tab. Paste hostnames, one shared key, one base scan config. DriftSense fans out and starts scanning within 5 minutes.</p>
191183 </div>
192184 </div>
193185 [/if]
194186</div>
195187
196188<!-- ============================ SERVER LIST ============================ -->
197189<div class="module glow-sky">
198190 <div class="module-head"><div class="left">
199191 <div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/></svg></div>
200192 <div class="module-title">Registered servers</div>
201193 <span class="module-subtitle">$total total.</span>
202194 </div></div>
203195 <div class="module-body tight">
204196 [if:$has_rows]
205197 <table class="tbl">
206198 <thead><tr>
207199 <th style="width:140px">Kind / status</th>
208200 <th>Name</th>
209201 <th style="width:180px">Reach</th>
210202 <th style="width:130px">Key</th>
211203 <th style="width:150px">Last scan</th>
212204 <th style="width:220px">Actions</th>
213205 </tr></thead>
214206 <tbody>
215207 [loop:@rows]
216208 <tr>
217209 <td>
218210 <span class="pill $kind_pill">$kind</span>
219211 <span class="pill $status_pill" style="margin-left:4px">$status</span>
220212 </td>
221213 <td>
222214 <a href="$edit_url" style="color:var(--accent-hi);text-decoration:none"><strong>$server_name</strong></a>
223215 [if:$err_short]<div class="dim" style="font-size:11px;color:var(--accent-danger)">$err_short</div>[/if]
224216 </td>
225217 <td class="mono dim" style="font-size:12px">$ssh_user@$host_display:$ssh_port</td>
226218 <td class="mono dim" style="font-size:11.5px">$key_display</td>
227219 <td class="mono dim"><span class="ts" data-ts="$last_scan_epoch" data-fmt="relative">$last_scan_h</span></td>
228220 <td>
229221 <button type="button" class="tr-chip" onclick="dsTestConn($server_id, this)">Test</button>
230222 <a href="$edit_url" class="tr-chip">Edit</a>
231223 <form method="post" action="/servers.cgi" style="display:inline" onsubmit="return confirm('Disable this server?')">
232224 <input type="hidden" name="op" value="delete">
233225 <input type="hidden" name="server_id" value="$server_id">
234226 <button type="submit" class="tr-chip" title="Disable">&times;</button>
235227 </form>
236228 </td>
237229 </tr>
238230 [/loop]
239231 </tbody>
240232 </table>
241233 [/if]
242234 [if:!$has_rows]
243235 <div style="padding:36px 14px;text-align:center;color:var(--text-dim);font-size:13.5px">
244236 No servers registered. Use the form above to add one, or switch to Bulk Import.
245237 </div>
246238 [/if]
247239 </div>
248240</div>
249241
250242<script>
251243(function () {
252244 window.dsTestConn = async function (id, btn) {
253245 var orig = btn.textContent;
254246 btn.textContent = 'Testing...';
255247 btn.disabled = true;
256248 try {
257249 var r = await fetch('/servers.cgi?op=test&id=' + id + '&nc=' + Date.now(), { cache: 'no-store' });
258250 var j = await r.json();
259251 if (j.ok) {
260252 btn.textContent = 'OK';
261253 btn.style.background = '#34d399';
262254 btn.style.color = '#0a1220';
263255 btn.style.borderColor = '#34d399';
264256 alert('SSH probe OK\n\n' + (j.message || ''));
265257 } else {
266258 btn.textContent = 'FAIL';
267259 btn.style.background = '#f43f5e';
268260 btn.style.color = '#fff';
269261 btn.style.borderColor = '#f43f5e';
270262 alert('SSH probe failed\n\n' + (j.message || 'unknown error'));
271263 }
272264 } catch (e) {
273265 alert('Probe error: ' + e.message);
274266 btn.textContent = orig;
275267 } finally {
276268 setTimeout(function () {
277269 btn.disabled = false;
278270 btn.textContent = orig;
279271 btn.style.background = '';
280272 btn.style.color = '';
281273 btn.style.borderColor = '';
282274 }, 3500);
283275 }
284276 };
285277})();
286278</script>
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.