Diff -- /var/www/vhosts/3dshawn.com/site1/user_information.html
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
to 1a03114eaf37
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 · 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 — 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 · 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 & 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") — 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 · 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 · 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 → checkout (Stripe) → 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 · 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 | |
| 466 | tar 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 << SQL | |
| 473 | CREATE DATABASE drift_sense CHARACTER SET utf8mb4; | |
| 474 | GRANT ALL PRIVILEGES ON drift_sense.* TO 'drift_sense'@'localhost' | |
| 475 | IDENTIFIED BY <span class="str">'change-me'</span>; | |
| 476 | SQL | |
| 477 | ||
| 478 | <span class="kw">mysql</span> -u drift_sense -p drift_sense < _schema/drift_sense_v2_schema.sql | |
| 479 | <span class="kw">mysql</span> -u drift_sense -p drift_sense < _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 | |
| 486 | db_host=localhost | |
| 487 | db_name=drift_sense | |
| 488 | db_user=drift_sense | |
| 489 | db_password=<span class="str">change-me</span> | |
| 490 | public_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 | |
| 497 | mkdir -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 — 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 · 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 — 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 — 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 · 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 — 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 · 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> — 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 — 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> — $199 per server, ships with 1 yr of updates</li> | |
| 604 | <li><strong style="color:#fff">Subscription</strong> — $12/mo per server, updates included forever</li> | |
| 605 | <li><strong style="color:#fff">Portfolio</strong> — $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 — 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 · 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 — 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 <sysadmin-IP></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 — 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 — 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"><RequireAll></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"></RequireAll></span> | |
| 669 | ||
| 670 | <span class="cmt"># Optional -- return 404 (not 403) so scanners see "nothing here" not "protected page"</span> | |
| 671 | ErrorDocument 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 — 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> | |
| 676 | command=<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 ${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 ${SSH_ORIGINAL_COMMAND#drift-hash } ;; | |
| 683 | <span class="str">"drift-read "*</span>) <span class="kw">exec</span> cat ${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 · 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 — 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 — 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 — 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 — 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> · silent safety net for code + schema drift · <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 — 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> |