Diff -- /var/www/vhosts/3dshawn.com/site1/user_information.html

O Operator
Diff

/var/www/vhosts/3dshawn.com/site1/user_information.html

added on local at 2026-07-11 23:02:35

Added
+744
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 1a03114eaf37
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<!doctype html>
2<html lang="en">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>DriftSense — How your customers use it</title>
7<style>
8 :root {
9 --bg-0: #030710;
10 --bg-1: #0a1220;
11 --bg-2: #131e30;
12 --bg-3: #1a2740;
13 --bg-4: #223050;
14 --gray-hi: #cbd5e1;
15 --gray: #96a3b8;
16 --gray-lo: #6a7a94;
17 --border: #223050;
18 --border-hi: #2d3d5f;
19 --accent: #4c8bf5; /* dark-blue accent, not brand-teal, per Shawn's palette */
20 --accent-hi: #6ea0ff;
21 --accent-lo: #1e3a8a;
22 --success: #34d399;
23 --warn: #f59e0b;
24 --danger: #f43f5e;
25 --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
26 --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
27 }
28 *,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0; }
29 html { scroll-behavior: smooth; }
30 body {
31 background: var(--bg-0);
32 background-image:
33 radial-gradient(1200px 500px at 20% -10%, rgba(76,139,245,0.10), transparent 60%),
34 radial-gradient(900px 400px at 90% 10%, rgba(76,139,245,0.06), transparent 60%);
35 color: var(--gray-hi);
36 font-family: var(--sans);
37 line-height: 1.65;
38 -webkit-font-smoothing: antialiased;
39 min-height: 100vh;
40 }
41 .wrap { max-width: 1180px; margin: 0 auto; padding: 48px 32px 96px; }
42
43 /* --- Header --------------------------------------------------------- */
44 .brand {
45 display: inline-flex; align-items: center; gap: 12px;
46 padding: 6px 14px 6px 6px;
47 background: linear-gradient(90deg, var(--bg-2), var(--bg-1));
48 border: 1px solid var(--border-hi);
49 border-radius: 999px;
50 margin-bottom: 24px;
51 }
52 .brand .logo {
53 width: 32px; height: 32px; border-radius: 50%;
54 background: linear-gradient(135deg, var(--accent), var(--accent-lo));
55 display: grid; place-items: center;
56 font-weight: 800; font-size: 12px; color: #fff;
57 letter-spacing: -0.5px;
58 box-shadow: 0 0 0 1px rgba(76,139,245,0.35), 0 6px 16px rgba(76,139,245,0.25);
59 }
60 .brand .name { font-weight: 700; letter-spacing: -0.2px; }
61 .brand .name .accent { color: var(--accent-hi); }
62 .brand .eyebrow { color: var(--gray); font-size: 11.5px; letter-spacing: 1.4px; text-transform: uppercase; }
63
64 h1.hero {
65 font-size: clamp(30px, 4.5vw, 48px);
66 line-height: 1.15;
67 letter-spacing: -0.8px;
68 color: #f4f7fb;
69 max-width: 20ch;
70 margin-bottom: 18px;
71 }
72 h1.hero em { color: var(--accent-hi); font-style: normal; }
73 .lede {
74 font-size: 17px;
75 color: var(--gray-hi);
76 max-width: 62ch;
77 margin-bottom: 32px;
78 }
79 .lede .strong { color: #fff; font-weight: 600; }
80
81 /* --- Sections ------------------------------------------------------- */
82 section { margin-top: 56px; }
83 .section-eyebrow {
84 display: inline-flex; align-items: center; gap: 8px;
85 color: var(--accent-hi);
86 font-size: 11px; font-weight: 700;
87 letter-spacing: 1.6px; text-transform: uppercase;
88 margin-bottom: 8px;
89 }
90 .section-eyebrow .dot {
91 width: 8px; height: 8px; border-radius: 50%;
92 background: var(--accent);
93 box-shadow: 0 0 12px var(--accent);
94 }
95 h2 {
96 font-size: 24px;
97 color: #f4f7fb;
98 letter-spacing: -0.3px;
99 margin-bottom: 20px;
100 }
101 h3 { font-size: 15px; color: #e2e8f0; letter-spacing: -0.1px; margin: 0 0 6px; }
102 p { color: var(--gray-hi); }
103 p + p { margin-top: 10px; }
104
105 /* --- Cards ---------------------------------------------------------- */
106 .card {
107 background: linear-gradient(180deg, var(--bg-2), var(--bg-1));
108 border: 1px solid var(--border);
109 border-radius: 14px;
110 padding: 22px 24px;
111 transition: border-color .15s ease, transform .15s ease;
112 }
113 .card:hover { border-color: var(--border-hi); }
114 .grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; }
115 .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
116 @media (max-width: 800px) {
117 .grid-2, .grid-3 { grid-template-columns: 1fr; }
118 }
119
120 /* --- Persona / audience row ---------------------------------------- */
121 .persona {
122 display: flex; align-items: center; gap: 12px;
123 padding: 10px 14px;
124 background: var(--bg-2);
125 border: 1px solid var(--border);
126 border-radius: 12px;
127 font-size: 13.5px;
128 }
129 .persona .who {
130 padding: 3px 10px;
131 border-radius: 999px;
132 font-weight: 700;
133 font-size: 11.5px;
134 letter-spacing: 0.8px;
135 text-transform: uppercase;
136 background: rgba(76,139,245,0.14);
137 color: var(--accent-hi);
138 border: 1px solid rgba(76,139,245,0.35);
139 }
140 .persona .who.ops { background: rgba(52,211,153,0.14); color: #86efac; border-color: rgba(52,211,153,0.35); }
141 .persona .who.audit { background: rgba(245,158,11,0.14); color: #fcd34d; border-color: rgba(245,158,11,0.35); }
142 .persona .who.leader { background: rgba(244,63,94,0.14); color: #fda4af; border-color: rgba(244,63,94,0.35); }
143
144 /* --- Steps ---------------------------------------------------------- */
145 .steps { counter-reset: step; }
146 .step {
147 position: relative;
148 padding: 20px 22px 20px 78px;
149 background: var(--bg-2);
150 border: 1px solid var(--border);
151 border-left: 3px solid var(--accent);
152 border-radius: 12px;
153 margin-top: 14px;
154 }
155 .step::before {
156 counter-increment: step;
157 content: counter(step);
158 position: absolute;
159 left: 18px; top: 20px;
160 width: 44px; height: 44px;
161 display: grid; place-items: center;
162 background: linear-gradient(135deg, var(--accent), var(--accent-lo));
163 color: #fff; font-weight: 800;
164 border-radius: 12px;
165 box-shadow: 0 4px 14px rgba(76,139,245,0.35);
166 font-size: 17px;
167 }
168 .step h3 { margin-bottom: 8px; }
169 .step p { color: var(--gray-hi); font-size: 14px; }
170
171 /* --- Code blocks ---------------------------------------------------- */
172 pre, code {
173 font-family: var(--mono);
174 font-size: 12.5px;
175 }
176 code.inline {
177 background: rgba(76,139,245,0.10);
178 color: var(--accent-hi);
179 padding: 1px 6px;
180 border-radius: 4px;
181 border: 1px solid rgba(76,139,245,0.20);
182 font-size: 12px;
183 }
184 pre {
185 background: var(--bg-0);
186 color: #d1e0f5;
187 border: 1px solid var(--border);
188 border-radius: 10px;
189 padding: 14px 18px;
190 overflow-x: auto;
191 margin-top: 12px;
192 line-height: 1.55;
193 }
194 pre .cmt { color: var(--gray-lo); }
195 pre .kw { color: var(--accent-hi); }
196 pre .str { color: #86efac; }
197
198 /* --- Pills / meta --------------------------------------------------- */
199 .pill {
200 display: inline-block;
201 padding: 3px 9px;
202 border-radius: 999px;
203 font-size: 11px;
204 font-weight: 700;
205 letter-spacing: 0.4px;
206 background: rgba(76,139,245,0.14);
207 color: var(--accent-hi);
208 border: 1px solid rgba(76,139,245,0.30);
209 }
210 .pill.ok { background: rgba(52,211,153,0.14); color: #86efac; border-color: rgba(52,211,153,0.30); }
211 .pill.warn { background: rgba(245,158,11,0.14); color: #fcd34d; border-color: rgba(245,158,11,0.30); }
212
213 /* --- Table --------------------------------------------------------- */
214 table {
215 width: 100%;
216 border-collapse: separate;
217 border-spacing: 0;
218 background: var(--bg-2);
219 border: 1px solid var(--border);
220 border-radius: 12px;
221 overflow: hidden;
222 font-size: 13.5px;
223 }
224 th, td { text-align: left; padding: 12px 16px; vertical-align: top; }
225 th {
226 background: var(--bg-1);
227 color: var(--gray);
228 font-size: 11.5px;
229 letter-spacing: 1.2px;
230 text-transform: uppercase;
231 font-weight: 700;
232 border-bottom: 1px solid var(--border);
233 }
234 td { border-bottom: 1px solid var(--border); color: var(--gray-hi); }
235 tr:last-child td { border-bottom: 0; }
236 td strong { color: #e2e8f0; }
237
238 /* --- Callout ------------------------------------------------------- */
239 .callout {
240 display: flex; gap: 14px;
241 background: linear-gradient(135deg, rgba(76,139,245,0.10), rgba(30,58,138,0.06));
242 border: 1px solid rgba(76,139,245,0.30);
243 border-radius: 12px;
244 padding: 16px 18px;
245 margin-top: 18px;
246 }
247 .callout .ico {
248 flex: 0 0 auto;
249 width: 32px; height: 32px;
250 border-radius: 8px;
251 background: rgba(76,139,245,0.20);
252 display: grid; place-items: center;
253 color: var(--accent-hi);
254 font-weight: 800;
255 }
256 .callout p { color: var(--gray-hi); font-size: 13.5px; }
257 .callout .title { color: #fff; font-weight: 700; margin-bottom: 4px; }
258
259 /* --- Feature stripe ------------------------------------------------- */
260 .feature-icon {
261 width: 40px; height: 40px;
262 border-radius: 10px;
263 background: linear-gradient(135deg, rgba(76,139,245,0.20), rgba(30,58,138,0.10));
264 border: 1px solid rgba(76,139,245,0.30);
265 display: grid; place-items: center;
266 color: var(--accent-hi);
267 margin-bottom: 12px;
268 }
269
270 /* --- Nav bar -------------------------------------------------------- */
271 nav.toc {
272 position: sticky; top: 12px; z-index: 10;
273 display: flex; flex-wrap: wrap; gap: 6px;
274 padding: 10px 12px;
275 background: rgba(3,7,16,0.75);
276 backdrop-filter: blur(10px);
277 -webkit-backdrop-filter: blur(10px);
278 border: 1px solid var(--border);
279 border-radius: 12px;
280 margin-top: 32px;
281 }
282 nav.toc a {
283 padding: 5px 11px;
284 border-radius: 999px;
285 color: var(--gray);
286 text-decoration: none;
287 font-size: 12px;
288 font-weight: 600;
289 border: 1px solid transparent;
290 transition: all .15s ease;
291 }
292 nav.toc a:hover { color: #fff; border-color: var(--border-hi); background: var(--bg-2); }
293
294 hr.split {
295 border: 0;
296 height: 1px;
297 background: linear-gradient(90deg, transparent, var(--border-hi), transparent);
298 margin: 56px 0 0;
299 }
300
301 footer {
302 margin-top: 72px;
303 padding-top: 24px;
304 border-top: 1px solid var(--border);
305 font-size: 12.5px;
306 color: var(--gray-lo);
307 }
308 footer .accent { color: var(--accent-hi); }
309</style>
310</head>
311<body>
312<div class="wrap">
313
314 <!-- ============================ HERO ============================ -->
315 <div class="brand">
316 <div class="logo">DS</div>
317 <div>
318 <div class="name">Drift<span class="accent">Sense</span></div>
319 <div class="eyebrow">Customer guide &middot; how it gets used</div>
320 </div>
321 </div>
322
323 <h1 class="hero">The <em>silent safety net</em> for teams without git discipline.</h1>
324 <p class="lede">
325 <span class="strong">DriftSense captures every file and schema change on your servers</span>,
326 keeps them content-addressable + gzipped in one storage DB,
327 lets you diff any two moments in time, and alerts you (Slack/Discord/webhook)
328 the second something drifts. It's a <span class="strong">standalone install</span>
329 you run inside your own infrastructure &mdash; no SaaS, no phone-home, no external
330 dependencies beyond core Perl and MariaDB.
331 </p>
332
333 <nav class="toc">
334 <a href="#what">1. What it is</a>
335 <a href="#who">2. Who uses it</a>
336 <a href="#getit">3. How they get it</a>
337 <a href="#install">4. Install (10 min)</a>
338 <a href="#setup">5. First-time setup</a>
339 <a href="#dayto">6. Day-to-day use</a>
340 <a href="#pricing">7. Delivery model</a>
341 <a href="#stealth">8. Stealth deployment</a>
342 <a href="#faq">9. FAQ</a>
343 </nav>
344
345 <!-- ============================ WHAT ============================ -->
346 <section id="what">
347 <div class="section-eyebrow"><span class="dot"></span> 01 &middot; the product</div>
348 <h2>What DriftSense actually does</h2>
349 <div class="grid-3">
350 <div class="card">
351 <div class="feature-icon">
352 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
353 </div>
354 <h3>Silent file capture</h3>
355 <p>Every add / modify / delete across configured paths is captured. Content is stored SHA-256-addressed + gzipped, so 100 versions of the same file take one copy plus a ref-count.</p>
356 </div>
357 <div class="card">
358 <div class="feature-icon">
359 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v6c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 11v6c0 1.7 4 3 9 3s9-1.3 9-3v-6"/></svg>
360 </div>
361 <h3>Silent schema capture</h3>
362 <p>MariaDB / MySQL DDL is snapshotted every 5 minutes. Someone adds a column at 2 AM? You'll see the exact <code class="inline">ALTER</code> in the diff viewer the next morning.</p>
363 </div>
364 <div class="card">
365 <div class="feature-icon">
366 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><polyline points="21 15 16 20 12 16 3 25"/><polyline points="21 15 21 9"/></svg>
367 </div>
368 <h3>Diff viewer + timeline</h3>
369 <p>Click any change to see a unified diff of the before / after content. The 90-day activity chart shows drift rhythms per file / per table.</p>
370 </div>
371 <div class="card">
372 <div class="feature-icon">
373 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
374 </div>
375 <h3>Alert rules &amp; webhooks</h3>
376 <p>Rule engine matches on path glob + status + kind, then POSTs to Slack / Discord / any HTTP endpoint. Rate limits, coalescing, full audit trail per delivery.</p>
377 </div>
378 <div class="card">
379 <div class="feature-icon">
380 <svg viewBox="0 0 24 24" width="18" height="18" 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="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
381 </div>
382 <h3>Compliance CSV export</h3>
383 <p>Auditor asks "prove nothing changed in Q3": pick a range, hit download. Rows include SHA-256 blob pointers so auditors can independently verify content.</p>
384 </div>
385 <div class="card">
386 <div class="feature-icon">
387 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 13V7a2 2 0 0 0-2-2h-4l-2-3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5"/><path d="M14 19l2 2 4-4"/></svg>
388 </div>
389 <h3>Named releases</h3>
390 <p>Bookmark a moment ("before the migration", "prod v2.4 cutover") &mdash; DriftSense pins those blobs so auto-purge never touches them.</p>
391 </div>
392 </div>
393 </section>
394
395 <!-- ============================ WHO ============================ -->
396 <section id="who">
397 <div class="section-eyebrow"><span class="dot"></span> 02 &middot; the buyers</div>
398 <h2>Who actually reaches for it</h2>
399 <p style="margin-bottom:18px">Three personas end up on the pricing page most often. Each gets a different kind of value out of the same install.</p>
400 <div class="grid-3">
401 <div class="card">
402 <div class="persona"><span class="who ops">DevOps / SRE</span> Small shop, no git for /etc</div>
403 <h3 style="margin-top:14px">"I need to know what changed last night."</h3>
404 <p>They run a handful of servers where /etc, nginx configs, and cron files evolve by hand. When something breaks, "did anyone touch nginx?" is the first question. DriftSense answers it in five seconds.</p>
405 <p><strong style="color:#fff">Primary usage:</strong> Dashboard + diff viewer. Alert rules on <code class="inline">/etc/nginx/**</code> and <code class="inline">/etc/cron.d/**</code>.</p>
406 </div>
407 <div class="card">
408 <div class="persona"><span class="who audit">Compliance / audit</span> Financial or medical</div>
409 <h3 style="margin-top:14px">"Prove nothing changed during the window."</h3>
410 <p>Auditors want an immutable record of file + schema state over time. DriftSense's content-addressable blob store gives them SHA-256 receipts they can verify independently.</p>
411 <p><strong style="color:#fff">Primary usage:</strong> Compliance CSV export + Named releases pinning quarter-end snapshots.</p>
412 </div>
413 <div class="card">
414 <div class="persona"><span class="who leader">Solo founder / dev</span> Ships production alone</div>
415 <h3 style="margin-top:14px">"I need a safety net for myself."</h3>
416 <p>One-person team, no code-review process. Modifies configs live. DriftSense lets them undo any mistake by clicking through history — no discipline required, no workflow to adopt.</p>
417 <p><strong style="color:#fff">Primary usage:</strong> File changes list + diff viewer. Occasional Slack alert on deletions.</p>
418 </div>
419 </div>
420 </section>
421
422 <!-- ============================ GET IT ============================ -->
423 <section id="getit">
424 <div class="section-eyebrow"><span class="dot"></span> 03 &middot; acquisition</div>
425 <h2>How a customer gets DriftSense</h2>
426 <div class="grid-2">
427 <div class="card">
428 <h3>Buy → download → install</h3>
429 <p>Landing page &rarr; checkout (Stripe) &rarr; email with a signed download link + license key. The tarball is a single <code class="inline">drift_sense-x.y.tar.gz</code> plus a short PDF quickstart.</p>
430 <p style="margin-top:10px"><span class="pill">Self-hosted</span> <span class="pill ok">No phone-home</span> <span class="pill warn">One-time or subscription</span></p>
431 </div>
432 <div class="card">
433 <h3>What ships in the tarball</h3>
434 <ul style="margin-left:20px;color:var(--gray-hi);font-size:14px;line-height:1.85">
435 <li>All CGIs + Perl modules (~50 files)</li>
436 <li><code class="inline">_schema/</code> — v2 schema + alerts migration</li>
437 <li><code class="inline">_ops/drift_sense.cron</code> — cron template</li>
438 <li><code class="inline">_config/drift_sense.conf.example</code></li>
439 <li><code class="inline">assets/</code> — CSS, JS, images</li>
440 <li>Installer script (planned: <code class="inline">./install.sh</code>)</li>
441 </ul>
442 </div>
443 </div>
444 </section>
445
446 <!-- ============================ INSTALL ============================ -->
447 <section id="install">
448 <div class="section-eyebrow"><span class="dot"></span> 04 &middot; install</div>
449 <h2>The 10-minute install</h2>
450 <p style="margin-bottom:12px">Assumes stock CentOS / Debian / Ubuntu / macOS with MariaDB or MySQL already running. Prereqs are boringly minimal on purpose:</p>
451
452 <div class="callout">
453 <div class="ico">i</div>
454 <div>
455 <div class="title">Prereqs (all standard on every mainstream Linux):</div>
456 <p>Perl 5.10+ · MariaDB / MySQL 5.5+ · Apache or nginx with CGI enabled · <code class="inline">curl</code> binary. That's it. Zero CPAN installs required.</p>
457 </div>
458 </div>
459
460 <div class="steps">
461 <div class="step">
462 <h3>Untar into a docroot</h3>
463 <p>Point the vhost at a fresh directory, drop the tarball, extract.</p>
464<pre><span class="cmt"># pick any docroot</span>
465<span class="kw">cd</span> /var/www/drift_sense
466tar xzf ~/drift_sense-1.0.tar.gz --strip-components=1</pre>
467 </div>
468
469 <div class="step">
470 <h3>Create the DB + import the schema</h3>
471 <p>One DB, one user with all-privileges on it.</p>
472<pre><span class="kw">mysql</span> -u root -p &lt;&lt; SQL
473CREATE DATABASE drift_sense CHARACTER SET utf8mb4;
474GRANT ALL PRIVILEGES ON drift_sense.* TO 'drift_sense'@'localhost'
475 IDENTIFIED BY <span class="str">'change-me'</span>;
476SQL
477
478<span class="kw">mysql</span> -u drift_sense -p drift_sense &lt; _schema/drift_sense_v2_schema.sql
479<span class="kw">mysql</span> -u drift_sense -p drift_sense &lt; _schema/drift_sense_alerts.sql</pre>
480 </div>
481
482 <div class="step">
483 <h3>Write /etc/drift_sense/drift_sense.conf</h3>
484 <p>Copy the example, fill in DB creds and your public URL.</p>
485<pre>db_engine=mysql
486db_host=localhost
487db_name=drift_sense
488db_user=drift_sense
489db_password=<span class="str">change-me</span>
490public_url=<span class="str">https://drift.example.com</span></pre>
491 </div>
492
493 <div class="step">
494 <h3>Install the cron entry</h3>
495 <p>Two scanners + one dispatcher; cron.d convention keeps them isolated.</p>
496<pre>cp _ops/drift_sense.cron /etc/cron.d/drift_sense
497mkdir -p /var/log/drift_sense</pre>
498 </div>
499
500 <div class="step">
501 <h3>Point your browser at the vhost</h3>
502 <p>The dashboard renders on first visit. Empty because nothing is monitored yet &mdash; that's the setup step, next.</p>
503 </div>
504 </div>
505 </section>
506
507 <!-- ============================ SETUP ============================ -->
508 <section id="setup">
509 <div class="section-eyebrow"><span class="dot"></span> 05 &middot; first-time setup</div>
510 <h2>Configure what to watch</h2>
511 <p style="margin-bottom:14px">Everything past install happens in the browser. Three configuration screens, all optional depending on what you care about.</p>
512
513 <div class="grid-2">
514 <div class="card">
515 <h3>File monitors <span class="pill">/file_monitors.cgi</span></h3>
516 <p>Add one row per directory tree you want captured. Give it a scan path, an ignore-glob list (git, node_modules, logs), an optional file-type filter, and a max file size. Scanner runs every 2 minutes with mtime-first optimization &mdash; unchanged files skip hashing.</p>
517 </div>
518 <div class="card">
519 <h3>Database monitors <span class="pill">/databases.cgi</span></h3>
520 <p>Add one row per DB to snapshot. Just needs a read-only user with <code class="inline">SELECT</code> on that DB and <code class="inline">information_schema</code>. Uses <code class="inline">UPDATE_TIME</code> for an early-exit when nothing has changed.</p>
521 </div>
522 <div class="card">
523 <h3>Alert rules <span class="pill">/alerts.cgi</span></h3>
524 <p>Optional. Rules match on kind (file / schema / either) + path glob + status filter, then POST to Slack, Discord, or any HTTP endpoint. Add rate limits and coalescing to keep from paging yourself into oblivion.</p>
525 </div>
526 <div class="card">
527 <h3>Named releases <span class="pill">/named_releases.cgi</span></h3>
528 <p>Bookmark a moment ("v2.4 prod cutover") and DriftSense pins every blob referenced by that moment. Auto-purge never touches pinned blobs &mdash; the point-in-time state is restorable forever.</p>
529 </div>
530 </div>
531 </section>
532
533 <!-- ============================ DAY TO DAY ============================ -->
534 <section id="dayto">
535 <div class="section-eyebrow"><span class="dot"></span> 06 &middot; daily use</div>
536 <h2>What the customer actually does</h2>
537 <p style="margin-bottom:16px">DriftSense is deliberately low-touch. Most weeks the customer never opens the UI &mdash; they only visit when Slack pings them or something goes wrong. When they do visit, here are the tools they reach for, in order of frequency.</p>
538
539 <table>
540 <thead><tr>
541 <th style="width:24%">Page</th>
542 <th style="width:34%">What they do there</th>
543 <th style="width:22%">How often</th>
544 <th>Trigger</th>
545 </tr></thead>
546 <tbody>
547 <tr>
548 <td><strong>Dashboard</strong><br><code class="inline">/dashboard.cgi</code></td>
549 <td>Glance at the 90-day activity timeline, spot spikes, click a busy day to drill in.</td>
550 <td><span class="pill ok">Once a week</span></td>
551 <td>Curiosity / weekly triage</td>
552 </tr>
553 <tr>
554 <td><strong>File changes</strong><br><code class="inline">/file_changes.cgi</code></td>
555 <td>Filter by date range or path, click a row to see the unified diff of what changed.</td>
556 <td><span class="pill">On demand</span></td>
557 <td>"Did nginx config change?"</td>
558 </tr>
559 <tr>
560 <td><strong>Diff viewer</strong><br><code class="inline">/diff.cgi</code></td>
561 <td>Read the before/after content of a single change. Restore by copy-paste if needed.</td>
562 <td><span class="pill">On demand</span></td>
563 <td>Slack alert click-through</td>
564 </tr>
565 <tr>
566 <td><strong>Alerts</strong><br><code class="inline">/alerts.cgi</code></td>
567 <td>Add or tune rules, check delivery history, verify webhooks are still landing.</td>
568 <td><span class="pill warn">Rarely</span></td>
569 <td>New watch target</td>
570 </tr>
571 <tr>
572 <td><strong>Compliance export</strong><br><code class="inline">/export.cgi</code></td>
573 <td>Pick a date window, download CSV. Send to auditor.</td>
574 <td><span class="pill warn">Quarterly</span></td>
575 <td>Audit request</td>
576 </tr>
577 <tr>
578 <td><strong>Named releases</strong><br><code class="inline">/named_releases.cgi</code></td>
579 <td>Bookmark a state before a risky change; look at old bookmarks after the fact.</td>
580 <td><span class="pill warn">Per release</span></td>
581 <td>Change management</td>
582 </tr>
583 </tbody>
584 </table>
585 </section>
586
587 <!-- ============================ PRICING ============================ -->
588 <section id="pricing">
589 <div class="section-eyebrow"><span class="dot"></span> 07 &middot; delivery model</div>
590 <h2>How it's sold + supported</h2>
591 <div class="grid-2">
592 <div class="card">
593 <h3>Ownership: fully theirs</h3>
594 <p>DriftSense installs on their server, their DB, their infrastructure. No phone-home. No SaaS backend. If your company disappears tomorrow, the customer's install keeps working forever. That's the <em style="color:var(--accent-hi);font-style:normal">ownership principle</em> &mdash; the whole point of a self-hosted product.</p>
595 </div>
596 <div class="card">
597 <h3>Update model: opt-in</h3>
598 <p>Version 1.0 today, 1.1 later. Customer receives an update tarball; runs a schema-migration script + swaps in the new code. Old versions keep working &mdash; there's no forced upgrade cycle.</p>
599 </div>
600 <div class="card">
601 <h3>Pricing shape (candidates)</h3>
602 <ul style="margin-left:20px;color:var(--gray-hi);font-size:13.5px;line-height:1.9">
603 <li><strong style="color:#fff">One-time</strong> &mdash; $199 per server, ships with 1 yr of updates</li>
604 <li><strong style="color:#fff">Subscription</strong> &mdash; $12/mo per server, updates included forever</li>
605 <li><strong style="color:#fff">Portfolio</strong> &mdash; $499/yr flat for up to 10 servers</li>
606 </ul>
607 <p style="margin-top:10px;font-size:12.5px;color:var(--gray)">Placeholder numbers &mdash; final pricing lives on the sales page.</p>
608 </div>
609 <div class="card">
610 <h3>Support</h3>
611 <p>Email support included for subscribers. Community forum for the one-time tier. Priority incident response as an add-on for enterprise customers who need SLA-backed help.</p>
612 </div>
613 </div>
614 </section>
615
616 <!-- ============================ STEALTH ============================ -->
617 <section id="stealth">
618 <div class="section-eyebrow"><span class="dot"></span> 08 &middot; stealth deployment</div>
619 <h2>Running invisibly to the developers</h2>
620 <p style="margin-bottom:14px">
621 A common ask from sys-admins in shops without code review:
622 "I want DriftSense as a code + config backup, but I don't want the developers messing with it."
623 Two flavors, ordered by paranoia level. Both are supported by the same install &mdash; just configured differently.
624 </p>
625
626 <div class="callout" style="border-color:rgba(245,158,11,0.30);background:linear-gradient(135deg,rgba(245,158,11,0.08),rgba(120,53,15,0.04))">
627 <div class="ico" style="background:rgba(245,158,11,0.20);color:#fcd34d">!</div>
628 <div>
629 <div class="title">Honest about the threat model</div>
630 <p>Neither flavor stops a root-privileged actor determined to hide. This is <em>deterrence + evidence-preservation</em> against casual discovery and accidental tampering, not adversarial security. Sysadmin needs an out-of-band alert path (personal email, SMS) so a dev deactivating alerts doesn't blind them.</p>
631 </div>
632 </div>
633
634 <div class="grid-2" style="margin-top:22px">
635 <div class="card">
636 <h3><span class="pill">Flavor 1</span> Same-server, obscured</h3>
637 <p style="margin-top:8px">DriftSense stays on the server it's monitoring, but nothing screams "audit tool":</p>
638 <ul style="margin:10px 0 0 20px;color:var(--gray-hi);font-size:13.5px;line-height:1.85">
639 <li>Install to <code class="inline">/opt/sys_audit_log/</code> instead of <code class="inline">/var/www/drift_sense/</code></li>
640 <li>UI mounted at a bastion-only URL (not the public domain)</li>
641 <li>Apache <code class="inline">Allow from &lt;sysadmin-IP&gt;</code> in front of every CGI</li>
642 <li>Cron entries in <strong>root's crontab</strong>, not <code class="inline">/etc/cron.d/</code> (less-browsed location)</li>
643 <li>Innocuous DB name (<code class="inline">system_metrics</code> instead of <code class="inline">drift_sense</code>)</li>
644 <li>Alert deliveries to sysadmin-private Slack workspace or personal email</li>
645 <li>Add DriftSense's own dir to every FILE_MONITOR ignore list so it doesn't watch itself</li>
646 </ul>
647 <p style="margin-top:10px;color:var(--gray);font-size:12.5px">Blocks casual discovery. Doesn't block a determined root actor.</p>
648 </div>
649 <div class="card">
650 <h3><span class="pill ok">Flavor 2</span> Bastion agent-mode (true stealth)</h3>
651 <p style="margin-top:8px">DriftSense lives on a separate server developers don't touch. Monitored servers have <strong>zero DriftSense footprint</strong> beyond one SSH key entry.</p>
652 <ul style="margin:10px 0 0 20px;color:var(--gray-hi);font-size:13.5px;line-height:1.85">
653 <li>Bastion runs the code, DB, cron, dashboard &mdash; all invisible from monitored server</li>
654 <li>Scanner SSHes into monitored server with a <em>restricted</em> read-only key</li>
655 <li>Only footprint on monitored server: one line in <code class="inline">/root/.ssh/authorized_keys</code>, name it something like <code class="inline">backup</code></li>
656 <li>SSH sessions show in <code class="inline">sshd</code> logs but look like ordinary backup connections</li>
657 <li>Multiple monitored servers into one central bastion &mdash; portfolio view for free</li>
658 </ul>
659 </div>
660 </div>
661
662 <h3 style="margin-top:28px;color:#e2e8f0">Apache allowlist snippet (Flavor 1)</h3>
663 <p style="font-size:13.5px">Restrict the UI to the sysadmin's static IP or VPN CIDR. Drop this in the vhost's <code class="inline">.htaccess</code> or Apache conf:</p>
664<pre><span class="cmt"># Only sysadmin's IP + office VPN can reach the UI</span>
665<span class="kw">&lt;RequireAll&gt;</span>
666 <span class="kw">Require</span> ip 203.0.113.42
667 <span class="kw">Require</span> ip 10.20.30.0/24
668<span class="kw">&lt;/RequireAll&gt;</span>
669
670<span class="cmt"># Optional -- return 404 (not 403) so scanners see "nothing here" not "protected page"</span>
671ErrorDocument 403 <span class="str">"/404.html"</span></pre>
672
673 <h3 style="margin-top:28px;color:#e2e8f0">SSH key restriction (Flavor 2)</h3>
674 <p style="font-size:13.5px">On the monitored server, add the bastion's public key to <code class="inline">/root/.ssh/authorized_keys</code> with these restrictions &mdash; the key can only run the specific scan commands, nothing else:</p>
675<pre><span class="cmt"># /root/.ssh/authorized_keys on the monitored server</span>
676command=<span class="str">"/root/.ssh/drift_agent_wrapper.sh"</span>,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <span class="kw">ssh-ed25519</span> AAAA...bastion-key backup
677
678<span class="cmt"># /root/.ssh/drift_agent_wrapper.sh -- only allow the scanner's commands</span>
679<span class="kw">#!/bin/bash</span>
680<span class="kw">case</span> <span class="str">"$SSH_ORIGINAL_COMMAND"</span> <span class="kw">in</span>
681 <span class="str">"drift-find "*</span>) <span class="kw">exec</span> find $&#123;SSH_ORIGINAL_COMMAND#drift-find } -type f -not -path <span class="str">'*/.*'</span> -printf <span class="str">'%p\t%s\t%T@\n'</span> ;;
682 <span class="str">"drift-hash "*</span>) <span class="kw">exec</span> sha256sum $&#123;SSH_ORIGINAL_COMMAND#drift-hash } ;;
683 <span class="str">"drift-read "*</span>) <span class="kw">exec</span> cat $&#123;SSH_ORIGINAL_COMMAND#drift-read } ;;
684 <span class="str">*</span>) <span class="kw">exit</span> 1 ;;
685<span class="kw">esac</span></pre>
686
687 <h3 style="margin-top:28px;color:#e2e8f0">Add an ssh_agent server via SQL</h3>
688 <p style="font-size:13.5px">The dashboard doesn't yet expose ssh_agent servers in the UI (roadmap). For now, insert directly:</p>
689<pre><span class="kw">INSERT INTO</span> SERVERS (server_name, kind, ssh_host, ssh_user, ssh_port, ssh_key_path, status)
690<span class="kw">VALUES</span> (<span class="str">'prod-web-1'</span>, <span class="str">'ssh_agent'</span>, <span class="str">'prod-web-1.internal'</span>, <span class="str">'root'</span>, 22,
691 <span class="str">'/etc/drift_sense/keys/agent_key'</span>, <span class="str">'active'</span>);
692
693<span class="kw">INSERT INTO</span> FILE_MONITOR_SETTINGS
694 (server_id, scan_path, ignore_list, file_type_filter, status, scan_name)
695<span class="kw">VALUES</span>
696 (LAST_INSERT_ID(), <span class="str">'/etc'</span>, <span class="str">'*.log,*.pid,*.sock'</span>, <span class="str">''</span>, 1,
697 <span class="str">'prod-web-1 /etc config drift'</span>);</pre>
698 <p style="font-size:13.5px;color:var(--gray);margin-top:12px">
699 The <code class="inline">_agent_scan.pl</code> daemon picks up any <code class="inline">ssh_agent</code> row on its next tick and starts collecting. Same FILE_CHANGES table as local mode, so the UI shows everything uniformly.
700 </p>
701 </section>
702
703 <!-- ============================ FAQ ============================ -->
704 <section id="faq">
705 <div class="section-eyebrow"><span class="dot"></span> 09 &middot; faq</div>
706 <h2>What people ask before buying</h2>
707 <div class="grid-2">
708 <div class="card">
709 <h3>Isn't this just git?</h3>
710 <p>Git works when your team commits. DriftSense works when they don't. It's a safety net for the drift that happens outside your workflow &mdash; live edits, cron jobs writing config, someone SSHing in at 3 AM. Both can coexist happily.</p>
711 </div>
712 <div class="card">
713 <h3>Does it slow the server down?</h3>
714 <p>File scanner uses mtime-first, so unchanged files never get hashed &mdash; a 10 GB codebase scans in seconds. Schema scanner uses <code class="inline">information_schema.TABLES.UPDATE_TIME</code> as an early-exit. Baseline load is negligible.</p>
715 </div>
716 <div class="card">
717 <h3>How much disk does it use?</h3>
718 <p>Content is SHA-256-addressed + gzipped in BLOB_STORE. Duplicates cost one row plus a ref-count bump. A 10 GB codebase with normal churn typically sits under 500 MB after a month.</p>
719 </div>
720 <div class="card">
721 <h3>What about containers?</h3>
722 <p>Roadmap: host-side OverlayFS reader captures container drift without needing an in-container agent. Ship separately once the base product is battle-tested.</p>
723 </div>
724 <div class="card">
725 <h3>Multi-server?</h3>
726 <p>Central DB, agent binary per server. Central UI shows every host in one view. Included in the product &mdash; per-server licensing is on the honor system.</p>
727 </div>
728 <div class="card">
729 <h3>Backup / restore?</h3>
730 <p>Standard <code class="inline">mysqldump</code> works. Or use Named Releases to pin specific moments forever &mdash; those blobs never auto-purge.</p>
731 </div>
732 </div>
733 </section>
734
735 <hr class="split">
736
737 <footer>
738 <p><span class="accent">DriftSense</span> &middot; silent safety net for code + schema drift &middot; <span style="color:var(--gray)">customer guide draft, 2026-07-11</span></p>
739 <p style="margin-top:6px">Companion to the in-product README &mdash; keep this doc alongside the tarball for the "how do I explain this to a lead?" moment.</p>
740 </footer>
741
742</div>
743</body>
744</html>