sync: auto-sync from HOWARD-HOME at 2026-06-22 18:54:25

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-22 18:54:25
This commit is contained in:
2026-06-22 18:54:58 -07:00
parent bec21647d4
commit 86c789a7f9
5 changed files with 863 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# Runbook — Cascades Planned Power Outage (2026-06-23, 05:3009:00)
- **Client:** Cascades of Tucson
- **Event:** Building-wide electrical work; **no power 05:3009:00 MST, 2026-06-23**.
- **Authored:** 2026-06-22 by Howard Enos (Howard-Home).
- **Why this runbook exists:** Avoid a repeat of the **2026-06-17 unplanned outage**, where a
*dirty* pfSense power-loss caused duplicate dhcpd + a 2nd-floor switch with one-way L2 forwarding
+ a Cox-modem-reboot scramble. This outage is **planned**, so we shut down **clean** and skip all of it.
Incident basis: `clients/cascades-tucson/reports/2026-06-17-power-outage-incident.md`.
## Roles
- **Howard (remote):** graceful shutdowns at ~05:20; monitors + remediates bring-up from 09:00.
- **John Trozzi (onsite):** physical power-on of CS-SERVER + Synology at ~09:00 once power is confirmed stable.
- **Standing rule:** no Cascades prod change without explicit per-change go (memory `feedback_cascades` #4).
## Decision: CLEAN graceful shutdown, do NOT let the UPS drain
- CS-SERVER has a **DEGRADED RAID-1 OS mirror on a single old spindle** — a dirty power-loss is the
worst-case data-integrity event. Graceful shutdown is mandatory.
- Once everything is soft-off the UPS load collapses, so it may **not** drain in 3.5h → devices won't
see a power-loss→restore event → they **stay off**. So we plan on **John powering devices on at 09:00**;
AC-recovery/auto-restart settings are only a backstop.
---
## PRE-FLIGHT — VERIFIED 2026-06-22 ~18:50 (read-only)
| Check | Result |
|---|---|
| CS-SERVER cloud backup last-run | **[OK] Success @ 2026-06-22 00:11** — MSP360 FFI file backup, Full consistency check PASSED, 578.2 GB scanned, 6.81 GB uploaded, **0 failed / 0 errors**. (File-level FFI, not bare-metal image — image-based remains a separate roadmap item.) |
| CS-SERVER iDRAC "AC Power Recovery" (backstop) | **[OK] On** (verified via OMSA `omreport chassis biossetup` → "AC Power Recovery Mode: On" — self-boots when AC returns) |
| Synology "Restart automatically after power failure" (backstop) | **[OK] Enabled** (DSM API `SYNO.Core.Hardware.PowerRecovery``rc_power_config: true`) |
| CS-SERVER GuruRMM agent (resolve live) | **[OK]** `c39f1de7-d5b6-45ae-b132-e06977ab1713`, online (last seen 01:46Z 6/23) |
| Remote access paths (for the 05:20 shutdowns) | **[OK] pfSense** via OpenVPN tunnel (up 4d, single clean dhcpd). **[OK] Synology** DSM API over tunnel (auth OK, reachable). **[OK] CS-SERVER** via RMM cloud. |
| What is on battery-backed UPS outlets (pfSense was on surge-only 6/17 — Mike moved it) | **TODO — John/onsite** confirm pfSense + core/PoE switches on battery side |
---
## SHUTDOWN SEQUENCE (start 05:28) — dependents first, gateway last
> Start at **05:28**. All three target devices are on the UPS, so the battery carries them through the
> 05:30 cut — the graceful shutdowns complete on UPS power even if they run slightly past 5:30. Kick off
> CS-SERVER FIRST (slowest to come down); pfSense last.
1. **CS-SERVER** (192.168.2.254) — graceful Windows shutdown via `rmm` (backup already verified).
DC/DNS/DHCP-role/Hyper-V/file+print server — the fragile box (degraded RAID). Best-effort stop
Hyper-V VMs first, then shut down:
```powershell
Get-VM | Where-Object State -eq 'Running' | Stop-VM -Force -ErrorAction SilentlyContinue
Stop-Computer -Force
```
(Dispatch via RMM `command_type: powershell`. Confirm the agent goes offline = down.)
2. **Synology cascadesDS** (192.168.0.120) — graceful DSM shutdown via API (auth verified tonight):
`SYNO.API.Auth login` → `SYNO.Core.System method=shutdown` (over the OpenVPN tunnel).
3. **UniFi switches/APs** — drop with building power (resilient; no special action). Note UPS-backed ones.
4. **pfSense** (192.168.0.1) — clean shutdown **last**, so DHCP/routing stays up while the rest drain:
`bash .claude/skills/unifi-wifi/scripts/pfsense-ssh.sh cascades-tucson run "shutdown -p now"`
---
## POWER-ON SEQUENCE (~09:00, John presses buttons, Howard monitors) — bottom-up
1. **pfSense first.** Verify single dhcpd and WAN up:
- `pgrep -f "dhcpd -user" | wc -l` → must be **1** (not 2 — the 6/17 failure).
- WAN up: dpinger WAN_DHCP + WANCOAX_DHCP healthy.
- **If WAN does NOT establish → reboot the Cox modem** (the missed post-restore step on 6/17).
2. **Switching/APs re-adopt** (core → distribution → APs). UniFi is SLOW here — watch the UOS controller
(172.16.3.29, site `va6iba3v`) until **12/12 switches + 77/77 APs** report connected.
3. **CS-SERVER** boots → verify AD/DNS, DHCP role, Hyper-V VMs, file + print shares. Then **Synology**.
4. **Straggler sweep:** power-cycle any printer/POS/IoT that booted into a DHCP-down window and cached a
disconnected state. **Known: kitchen thermal printer (iPad POS ticket printer).** These are invisible
to a DHCP scan (they stop requesting) — expect a few "won't connect" reports, each fixed by a power-cycle.
---
## WATCH-LIST (the 2026-06-17 casualties — confirm each recovers)
- **Switch 2nd Floor #2** (USL24PB, `192.168.2.193`) — broke one-way L2 forwarding last time; floors 2/3/4
hang off it. If those floors don't come back → **reset + re-adopt** the switch.
- **Duplicate dhcpd** on pfSense — a clean shutdown should prevent it, but verify (step 1 above).
- **Cox modem / WAN** — reboot if WAN doesn't re-establish.
## Access reference
- pfSense: `clients/cascades-tucson/pfsense-firewall` · `pfsense-ssh.sh cascades-tucson run "<cmd>"` (logs PLAIN TEXT, not clog).
- CS-SERVER: GuruRMM (`rmm`, resolve agent live) / ScreenConnect. iDRAC `192.168.2.65`.
- Synology DSM: `http://192.168.0.120:5000` — vault `clients/cascades-tucson/synology-cascadesds`.
- UOS controller: `https://172.16.3.29:11443`, site `va6iba3v` / `685f39068e65331c46ef6dd2`.
</content>
</invoke>

View File

@@ -0,0 +1,398 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dataforth | Shared Drives Reorganization Plan</title>
<style>
:root{
/* warm-paper engineering-report palette, OKLCH, tinted neutrals */
--paper: oklch(0.985 0.004 85);
--surface: oklch(0.998 0.003 85);
--ink: oklch(0.255 0.014 70);
--ink-soft: oklch(0.420 0.016 70);
--muted: oklch(0.540 0.016 72);
--hair: oklch(0.905 0.006 78);
--hair-soft:oklch(0.945 0.005 80);
--accent: oklch(0.420 0.052 235); /* deep petrol, not corporate royal-blue */
--accent-d: oklch(0.330 0.050 238);
--ochre: oklch(0.560 0.090 70); /* warm emphasis */
/* sensitivity roles (meaningful color, kept muted + harmonious) */
--dept: oklch(0.470 0.062 165); --dept-bg: oklch(0.965 0.022 165);
--restr: oklch(0.480 0.110 30); --restr-bg: oklch(0.960 0.030 35);
--common: oklch(0.520 0.080 72); --common-bg:oklch(0.965 0.030 80);
--user: oklch(0.460 0.072 330); --user-bg: oklch(0.965 0.022 330);
--arch: oklch(0.500 0.010 70); --arch-bg: oklch(0.955 0.005 75);
--serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
margin:0; background:var(--paper); color:var(--ink);
font:16px/1.62 var(--sans);
-webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
.wrap{max-width:820px; margin:0 auto; padding:0 30px 84px;}
/* ---- masthead ---- */
.masthead{border-bottom:2px solid var(--ink); padding:40px 0 22px; margin-bottom:8px;}
.firm{
font:600 12.5px/1 var(--sans); letter-spacing:.26em; text-transform:uppercase;
color:var(--accent);
}
.masthead h1{
font:400 35px/1.12 var(--serif); margin:18px 0 0; color:var(--ink);
letter-spacing:-.01em; max-width:18ch;
}
.docline{
display:flex; flex-wrap:wrap; gap:6px 22px; margin-top:20px;
font-size:13.5px; color:var(--muted);
}
.docline b{color:var(--ink-soft); font-weight:600;}
/* ---- lead ---- */
.lead{margin:30px 0 14px;}
.lead p{font-size:17px; line-height:1.62; color:var(--ink-soft); max-width:68ch;}
.lead p.first{font-size:19px; line-height:1.55; color:var(--ink);}
.lead strong{color:var(--ink); font-weight:600;}
.note{
background:var(--surface); border:1px solid var(--hair); border-radius:10px;
padding:15px 18px; margin:22px 0; max-width:70ch;
}
.note .lbl{
display:block; font:700 11px/1 var(--sans); letter-spacing:.12em; text-transform:uppercase;
color:var(--accent); margin-bottom:7px;
}
.note p{margin:0; font-size:14.5px; color:var(--ink-soft);}
/* ---- sections ---- */
section{margin-top:52px;}
.shead{display:flex; align-items:baseline; gap:16px; margin-bottom:6px;}
.shead .n{font:400 30px/1 var(--serif); color:var(--accent); min-width:32px;}
.shead h2{font:600 22px/1.2 var(--sans); margin:0; letter-spacing:-.01em;}
section > p{max-width:68ch; color:var(--ink-soft);}
section > p.intro{margin-top:4px;}
/* ---- tiers (structure) ---- */
.tiers{margin-top:24px; border-top:1px solid var(--hair);}
.tier{
display:grid; grid-template-columns:190px 1fr; gap:26px;
padding:20px 0; border-bottom:1px solid var(--hair-soft);
}
.tier:last-child{border-bottom:1px solid var(--hair);}
.tlabel{padding-top:2px;}
.tname{display:flex; align-items:center; gap:9px; font:600 16.5px/1.2 var(--sans);}
.dot{width:11px; height:11px; border-radius:50%; flex:0 0 auto;}
.d-dept{background:var(--dept)} .d-restr{background:var(--restr)}
.d-common{background:var(--common)} .d-user{background:var(--user)} .d-arch{background:var(--arch)}
.trule{
display:inline-block; margin-top:9px; font:700 10.5px/1 var(--sans);
letter-spacing:.08em; text-transform:uppercase; padding:4px 9px; border-radius:999px;
}
.r-dept{color:var(--dept); background:var(--dept-bg)}
.r-restr{color:var(--restr); background:var(--restr-bg)}
.r-common{color:var(--common); background:var(--common-bg)}
.r-user{color:var(--user); background:var(--user-bg)}
.r-arch{color:var(--arch); background:var(--arch-bg)}
.tdesc{margin:0 0 10px; color:var(--ink-soft); font-size:15px; max-width:60ch;}
.chips{display:flex; flex-wrap:wrap; gap:6px;}
.chip{
font:500 13px/1.3 var(--sans); color:var(--ink-soft);
background:var(--surface); border:1px solid var(--hair); border-radius:7px; padding:4px 10px;
}
.infra{margin-top:22px; color:var(--muted); font-size:13.5px; font-style:italic; max-width:70ch;}
/* ---- access model ---- */
.levels{display:grid; grid-template-columns:1fr 1fr; gap:18px; margin:22px 0 26px; max-width:62ch;}
.level h3{font:600 15px/1.2 var(--sans); margin:0 0 4px; display:flex; align-items:center; gap:8px;}
.level p{margin:0; font-size:14px; color:var(--muted);}
.pill{
display:inline-block; font:700 10.5px/1 var(--sans); letter-spacing:.05em;
padding:4px 8px; border-radius:6px;
}
.pill-rw{background:var(--accent); color:var(--paper);}
.pill-ro{background:transparent; color:var(--accent); box-shadow:inset 0 0 0 1.5px var(--accent);}
table{width:100%; border-collapse:collapse; margin:14px 0 8px; font-size:13.5px;}
caption{caption-side:top; text-align:left; font-size:13.5px; color:var(--ink-soft);
max-width:66ch; margin-bottom:10px;}
th,td{padding:9px 6px; border-bottom:1px solid var(--hair-soft); text-align:center;}
thead th{
font:700 11px/1.3 var(--sans); letter-spacing:.02em; text-transform:uppercase;
color:var(--muted); border-bottom:1.5px solid var(--hair); vertical-align:bottom;
}
tbody th{text-align:left; font:600 13.5px/1.3 var(--sans); color:var(--ink); white-space:nowrap;}
tbody tr:hover td, tbody tr:hover th{background:var(--surface);}
.cell-rw{color:var(--accent-d); font-weight:700;}
.cell-ro{color:var(--muted); font-weight:600;}
.cell-no{color:var(--hair);}
.cell-x{color:var(--restr); font-weight:600; font-size:12px;}
.legend{font-size:12.5px; color:var(--muted); margin-top:4px;}
.legend b{color:var(--ink-soft);}
/* ---- asks ---- */
.asks{margin-top:24px; counter-reset:ask; max-width:70ch;}
.ask{
display:grid; grid-template-columns:34px 1fr; gap:14px; padding:15px 0;
border-top:1px solid var(--hair-soft);
}
.ask:first-child{border-top:1px solid var(--hair);}
.ask::before{
counter-increment:ask; content:counter(ask);
font:400 20px/1 var(--serif); color:var(--ochre); padding-top:2px;
}
.ask p{margin:0; font-size:15px; color:var(--ink-soft);}
.ask b{color:var(--ink); font-weight:600;}
.closing{
margin-top:26px; padding:18px 20px; background:var(--surface);
border:1px solid var(--hair); border-radius:10px; max-width:70ch;
}
.closing p{margin:0; font-size:15px; color:var(--ink-soft);}
.closing strong{color:var(--ink); font-weight:600;}
footer{
margin-top:54px; padding-top:18px; border-top:1px solid var(--hair);
font-size:12.5px; color:var(--muted); line-height:1.7;
}
footer b{color:var(--ink-soft); font-weight:600;}
a{color:var(--accent); text-underline-offset:2px;}
:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:3px;}
@media (max-width:660px){
.wrap{padding:0 20px 60px;}
.masthead h1{font-size:28px;}
.tier{grid-template-columns:1fr; gap:12px;}
.levels{grid-template-columns:1fr;}
.table-scroll{overflow-x:auto;}
table{min-width:560px;}
}
@media (prefers-reduced-motion:no-preference){
tbody tr td, tbody tr th{transition:background .18s cubic-bezier(.2,.7,.3,1);}
}
@media print{
body{background:#fff;}
.wrap{max-width:none; padding:0;}
.note,.closing,.chip,.surface{-webkit-print-color-adjust:exact; print-color-adjust:exact;}
section{break-inside:avoid;}
.tier,.ask{break-inside:avoid;}
a{color:var(--ink);}
}
</style>
</head>
<body>
<div class="wrap">
<header class="masthead">
<div class="firm">Arizona Computer Guru</div>
<h1>Shared Drives Reorganization &amp; Access Plan</h1>
<div class="docline">
<span>Prepared for <b>Dataforth Corporation</b></span>
<span><b>Draft</b> for review</span>
<span>June 2026</span>
</div>
</header>
<div class="lead">
<p class="first">Today, every shared drive at Dataforth is open to every employee. Anyone
who logs in can open, change, or delete anything, including Payroll, OSHA records,
Purchase Orders, and the accounting files.</p>
<p>This plan reorganizes those drives so each <strong>department</strong> sees only what it
needs, the sensitive areas are locked down, and access stays simple to manage. The
encouraging part: your drives are <strong>already arranged by department</strong>. We are
largely tidying that structure and adding the access controls that should have been there
all along.</p>
</div>
<div class="note">
<span class="lbl">How to read this</span>
<p>Section 1 is the proposed folder layout. Section 2 is who would get access. Section 3 is
the short list of things we need from you to finalize it. Nothing on your systems changes
until you approve the plan.</p>
</div>
<!-- 1 -->
<section>
<div class="shead"><span class="n">1</span><h2>Proposed folder layout</h2></div>
<p class="intro">Everything would sit under one clear, consistent structure. You still reach
your files the same way (your familiar mapped drives can stay). This is about how folders
are grouped, and who can open them.</p>
<div class="tiers">
<div class="tier">
<div class="tlabel">
<div class="tname"><span class="dot d-dept"></span>Departments</div>
<span class="trule r-dept">Team access</span>
</div>
<div class="tcontent">
<p class="tdesc">Each team's working files. People see their own department; another
department is added only when there is a reason to.</p>
<div class="chips">
<span class="chip">Engineering &amp; Test Engineering</span>
<span class="chip">Manufacturing / Production</span>
<span class="chip">Quality / Calibration</span>
<span class="chip">Sales &amp; Marketing</span>
<span class="chip">Shipping / Receiving</span>
<span class="chip">Purchasing</span>
<span class="chip">IT</span>
</div>
</div>
</div>
<div class="tier">
<div class="tlabel">
<div class="tname"><span class="dot d-restr"></span>Restricted</div>
<span class="trule r-restr">Named people only</span>
</div>
<div class="tcontent">
<p class="tdesc">Sensitive data, walled off from general staff. Only specific people
(HR, Finance, management) are granted access.</p>
<div class="chips">
<span class="chip">Accounting &amp; Finance (Sage, QuickBooks, invoices)</span>
<span class="chip">Payroll</span>
<span class="chip">HR</span>
<span class="chip">OSHA / Safety records</span>
<span class="chip">Purchase Orders</span>
</div>
</div>
</div>
<div class="tier">
<div class="tlabel">
<div class="tname"><span class="dot d-common"></span>Company-Wide</div>
<span class="trule r-common">Everyone: view</span>
</div>
<div class="tcontent">
<p class="tdesc">Shared resources everyone can read, with editing limited to the
owners so nothing gets changed by accident.</p>
<div class="chips">
<span class="chip">Forms</span>
<span class="chip">Policies</span>
<span class="chip">Templates</span>
<span class="chip">Scanned Documents</span>
<span class="chip">General Documents</span>
</div>
</div>
</div>
<div class="tier">
<div class="tlabel">
<div class="tname"><span class="dot d-user"></span>User Folders</div>
<span class="trule r-user">Private</span>
</div>
<div class="tcontent">
<p class="tdesc">A private home folder per employee. Only that person and IT can see
inside. This replaces the loose person-named folders scattered across the drives today.</p>
</div>
</div>
<div class="tier">
<div class="tlabel">
<div class="tname"><span class="dot d-arch"></span>Archive</div>
<span class="trule r-arch">Read-only history</span>
</div>
<div class="tcontent">
<p class="tdesc">Old engineering archives and material from former staff, kept for
reference, read-only, out of everyone's daily view.</p>
</div>
</div>
</div>
<p class="infra">Behind-the-scenes systems stay exactly where they are so nothing breaks:
the DOS test stations, the website datasheet system, the IT software library, and the live
Sage accounting database. We handle those separately.</p>
</section>
<!-- 2 -->
<section>
<div class="shead"><span class="n">2</span><h2>Who would get access</h2></div>
<p class="intro">Access is granted by <strong>department group</strong>, not person by
person. We add an employee to their department group and they immediately get the right
folders; if they change teams, we move the membership. There are two simple levels:</p>
<div class="levels">
<div class="level">
<h3><span class="pill pill-rw">RW</span> Read / Write</h3>
<p>Open, edit, and save files in their department's folders.</p>
</div>
<div class="level">
<h3><span class="pill pill-ro">RO</span> Read-Only</h3>
<p>View files another department owns, without changing them.</p>
</div>
</div>
<div class="table-scroll">
<table>
<caption>Our <b>starting assumption</b>: each department owns its own area, and the
sensitive folders are restricted. This is the grid we would like you to confirm or
correct in Section 3.</caption>
<thead>
<tr>
<th style="text-align:left">Department</th>
<th>Engr</th><th>Mfg</th><th>Quality</th><th>Sales</th>
<th>Shipping</th><th>Purch</th><th>Company-Wide</th><th>Restricted</th>
</tr>
</thead>
<tbody>
<tr><th>Engineering</th><td class="cell-rw">RW</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td></tr>
<tr><th>Manufacturing / Prod.</th><td class="cell-ro">RO</td><td class="cell-rw">RW</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td></tr>
<tr><th>Quality / Calibration</th><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-rw">RW</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td></tr>
<tr><th>Sales &amp; Marketing</th><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-rw">RW</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td></tr>
<tr><th>Shipping / Receiving</th><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-rw">RW</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-no">&middot;</td></tr>
<tr><th>Purchasing</th><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-rw">RW</td><td class="cell-ro">RO</td><td class="cell-x">PO only</td></tr>
<tr><th>Accounting / Finance</th><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-rw">RW</td></tr>
<tr><th>HR / Payroll</th><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-no">&middot;</td><td class="cell-ro">RO</td><td class="cell-rw">RW</td></tr>
<tr><th>IT</th><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-rw">RW</td><td class="cell-x">by request</td></tr>
<tr><th>Management / Exec</th><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-ro">RO</td><td class="cell-x">as needed</td></tr>
</tbody>
</table>
</div>
<p class="legend"><b>RW</b> read &amp; write &nbsp;&middot;&nbsp; <b>RO</b> read-only
&nbsp;&middot;&nbsp; <b>&middot;</b> no access. Restricted covers Payroll, OSHA, Accounting,
and Purchase Orders.</p>
</section>
<!-- 3 -->
<section>
<div class="shead"><span class="n">3</span><h2>What we need from you</h2></div>
<p class="intro">A few answers let us finalize the plan and build it. A short call works
too if that is easier.</p>
<div class="asks">
<div class="ask"><p><b>Confirm the departments</b> in Section 1. Add, remove, or rename
any that are off.</p></div>
<div class="ask"><p><b>Confirm or correct the access grid</b> in Section 2 (who gets
Read/Write, Read-Only, or no access for each area).</p></div>
<div class="ask"><p><b>Name the people for the sensitive areas.</b> Exactly who should
see Payroll, OSHA records, Purchase Orders, and Accounting? This usually needs HR and
Finance sign-off.</p></div>
<div class="ask"><p><b>Department rosters.</b> Which employees are in which department.
An existing org chart or staff list is perfect.</p></div>
<div class="ask"><p><b>Cleanup.</b> Are there folders (the "do not use" ones, old
per-person folders) you already know are safe to archive or remove?</p></div>
<div class="ask"><p><b>Exceptions.</b> Anyone who needs cross-department access, plus any
contractors or outside parties.</p></div>
</div>
<div class="closing">
<p>Once we have this, we send back a final "who sees what" map for your sign-off, then
implement it <strong>in stages</strong> so nobody loses access unexpectedly.
<strong>Nothing changes until you approve the plan.</strong></p>
</div>
</section>
<footer>
<b>Arizona Computer Guru</b> &nbsp;&middot;&nbsp; Prepared for Dataforth Corporation
&nbsp;&middot;&nbsp; Draft, June 2026<br>
Questions? Reply to our email or call. We are glad to walk through it on a quick call.
</footer>
</div>
</body>
</html>

View File

@@ -0,0 +1,198 @@
# Dataforth — Proposed Target Folder Structure (DRAFT / strawman)
**By:** ACG (Howard) · **Date:** 2026-06-22 · **Status:** DRAFT — pre-client-input
**Inputs:** inferred from existing shares + folder contents in
[current-state-2026-06-10.md](./current-state-2026-06-10.md),
[acl-audit-detail-2026-06-10.md](./acl-audit-detail-2026-06-10.md), and the ENGR
exploration notes. Refine against Dataforth's access matrix (Phase 1 reply) before sign-off.
> Purpose: lay out as much of the Phase 2 target-state design as we can **from the data
> we already have** — the way Dataforth has their shares arranged today already tells us
> their departments and data domains. This maps the current sprawl onto the common
> departmental-share pattern. Nothing here is implemented; it is the proposal we hand the
> client (simplified) for confirmation.
---
## 1. What today's layout tells us (departments inferred from the data)
Their existing shares/folders are effectively **organized by department already** — just
spread across eight shares with no access control. Reading the structure backwards gives us
a strong starting department list:
| Evidence in current shares/folders | Implied department / domain |
|---|---|
| `Engineering` (B:), `e-drive` ENGR/ECO'S/FMEA/TE, `archive` (Y:), ATE/DESIGN/Project Reports | **Engineering** (+ Test Engineering sub) |
| c-drive Manufacturing / Production Control / SMT; e-drive MANUFACT | **Manufacturing / Production** |
| FMEA, ECO'S, Test Equipment, calibration/ATE | **Quality / Calibration** |
| `sales` (W:) — marketing, contacts, RMAs, shipping handoffs | **Sales & Marketing** |
| c-drive Shipping; sales shipping handoffs | **Shipping / Receiving** |
| c-drive Purchasing, **Purchase Orders** | **Purchasing** |
| `sage` (S:), e-drive **QBfiles**, invoices, financial reports | **Accounting / Finance** (restricted) |
| c-drive **Payroll** | **Payroll / HR** (restricted) |
| c-drive **OSHA 300 / OSHA Safety Training** | **HR / Safety** (restricted) |
| `itsvc`, `webshare` (datasheet automation) | **IT** (+ app/infra) |
| Person-named + "Do not use" folders across c-drive/sales | legacy → **Archive / cleanup** |
Departments we can confidently propose: **Engineering, Manufacturing/Production,
Quality/Calibration, Sales & Marketing, Shipping/Receiving, Purchasing, Accounting/Finance,
HR/Payroll, IT, Management/Exec.** (Matches the discovery-email starter list — the existing
data corroborates it.)
---
## 2. Target structure — the "north star" (consolidated departmental share)
The standard pattern: **one logical tree**, departmental subfolders, a broken-inheritance
**Restricted** branch for sensitive data, a read-mostly **Company-Wide** area, per-user
**Users** home folders, and a read-only **Archive**. Access-Based Enumeration (ABE) on so
people only see what they can open.
```
Company\ (one tree; can stay multi-drive-letter mapped — see §4)
|
+-- Departments\
| +-- Engineering\ ENGR, ECO'S, FMEA, DESIGN, Project Reports, MTBF, LABEL
| | +-- Test-Engineering\ ATE, Test Equipment, TESTLOGS, Tester Notebooks
| | +-- Custom-Products\
| +-- Manufacturing\ Production Control, SMT, MANUFACT, Scanned (mfg travelers)
| +-- Quality\ FMEA (quality copy), Calibration, Test Equipment records
| +-- Sales-Marketing\ contacts, RMAs, videos, weekly updates, marketing assets
| +-- Shipping-Receiving\ shipping handoffs, packing/labels
| +-- Purchasing\ vendor files, (Purchase Orders -> see Restricted)
| +-- IT\ tools/notes (software depot stays in ITSvc, see §5)
|
+-- Restricted\ (inheritance BROKEN; no Domain Users; per-area groups)
| +-- Accounting-Finance\ Sage data refs, invoices, financial reports, QBfiles
| +-- Payroll\ (from c-drive Payroll)
| +-- HR\ personnel, policies-confidential
| +-- OSHA\ OSHA 300, Safety Training records
| +-- Purchase-Orders\ (from c-drive — finance-sensitive)
|
+-- Company-Wide\ (all staff: Read; limited Write groups)
| +-- Forms\
| +-- Policies\ (non-confidential, published)
| +-- Templates\
| +-- Scanned-Documents\ (general intake; mfg-specific -> Manufacturing)
| +-- Documents\ (general company docs from c-drive)
|
+-- Users\ (per-user home folders; only owner + admins)
|
+-- Archive\ (read-only historical; legacy + "Do not use" landing zone)
+-- Engineering-Archive\ (current Y: archive)
+-- Former-Staff\ (person-named folders pending cleanup decision)
```
**App / infra shares stay OUT of this tree** and are handled case-by-case (§5).
---
## 3. Where each current share/folder lands (migration map)
| Today | Target location | Notes |
|---|---|---|
| Q: c-drive \ Documents | `Company-Wide\Documents` | confirm any dept-specific subfolders |
| Q: c-drive \ Manufacturing, Production Control, SMT | `Departments\Manufacturing` | |
| Q: c-drive \ Shipping | `Departments\Shipping-Receiving` | |
| Q: c-drive \ Purchasing | `Departments\Purchasing` | |
| Q: c-drive \ Scanned Documents | `Company-Wide\Scanned-Documents` | split mfg travelers to Manufacturing if needed |
| Q: c-drive \ **Payroll** | `Restricted\Payroll` | broken inheritance, HR/Payroll group only |
| Q: c-drive \ **OSHA 300 / OSHA Safety Training** | `Restricted\OSHA` | HR/Safety group only |
| Q: c-drive \ **Purchase Orders** | `Restricted\Purchase-Orders` | Purchasing + Finance only |
| Q: c-drive \ person-named / "Do not use" | `Archive\Former-Staff` | after migration-gap audit clears |
| T: e-drive \ ENGR, ECO'S, FMEA | `Departments\Engineering` | |
| T: e-drive \ Test Engineering (TE) | `Departments\Engineering\Test-Engineering` | |
| T: e-drive \ MANUFACT | `Departments\Manufacturing` | dedupe vs c-drive Manufacturing |
| T: e-drive \ **QBfiles** (QuickBooks) | `Restricted\Accounting-Finance` | get it off the open eng drive |
| S: sage (Sage ERP) | `Restricted\Accounting-Finance` (refs) | **app paths stay put — see §5 caution** |
| W: sales | `Departments\Sales-Marketing` | shipping handoffs -> Shipping-Receiving subfolder or shared |
| Y: archive (ENGR archive) | `Archive\Engineering-Archive` | read-only |
| B: Engineering (ENGR: ATE/DESIGN/etc.) | `Departments\Engineering` (+ Test-Engineering) | **largest store; AD1 C: ~90% full — destination decision needed** |
| itsvc | stays `ITSvc` (IT depot) | not in dept tree; §5 |
| X: webshare | stays `webshare` | app/automation; preserve `svc_testdatadb`; §5 |
| test | stays `test` | DOS/SMB1 — untouched, excluded |
---
## 4. Drive-letter strategy (keep habits, change permissions)
Two ways to deliver the structure above:
- **Option A — Keep current drive letters (recommended for phase 1 of rollout).** Leave
Q/S/T/W/Y/B mapped where they are; reorganize folders *within* each share and apply
department groups. Lowest disruption, no app/path breakage, no retraining. The
"Company / Departments / Restricted" tree is realized *logically* across the existing
shares rather than physically consolidated on day one.
- **Option B — Consolidate to one mapped drive** (e.g. one `Company` share, ABE on, single
letter) once apps and muscle-memory allow. Cleaner long-term, but risks hard-coded UNC
paths (DOS, Sage, datasheet pipeline, GageTrak/Epicor shortcuts) and user retraining.
**Recommendation:** ship **Option A** structure + groups first (safe, reversible), hold
**Option B** consolidation as a later optional phase after the app-path audit. Either way the
*permission model is identical* — only the physical/mapping layout differs.
---
## 5. Excluded app / infra shares (do NOT fold into the dept tree)
- `test` (AD2) — DOS test stations, SMB1 + Guest:Read. **Leave exactly as-is.**
- `webshare` (AD2) — datasheet automation. **Preserve `svc_testdatadb:Full`**; restrict
human access to IT/Engineering; do not move paths.
- `ITSvc` (AD1) — IT software depot. Keep `Domain Computers:Read` (deployment); IT-RW.
- `sage` app data (SAGE-SQL) — Sage ERP reads/writes here; **do not relocate the live data
path.** Restrict via group at the share, but keep the UNC stable for the app/SQL.
- `NETLOGON` / `SYSVOL` — never touch.
---
## 6. AD security groups this implies (naming `SG-<Resource>-<RW|RO>`)
Derived directly from the structure above — RW for the owning dept, RO where another dept
needs visibility (confirm RO grants with the client matrix):
```
SG-Engineering-RW SG-Engineering-RO
SG-Manufacturing-RW SG-Manufacturing-RO
SG-Quality-RW SG-Quality-RO
SG-Sales-RW SG-Sales-RO
SG-Shipping-RW SG-Shipping-RO
SG-Purchasing-RW SG-Purchasing-RO
SG-IT-RW
SG-Accounting-RW SG-Accounting-RO (Restricted\Accounting-Finance)
SG-Payroll-RW (Restricted\Payroll)
SG-HR-RW (Restricted\HR, OSHA)
SG-PurchaseOrders-RW SG-PurchaseOrders-RO (Purchasing + Finance)
SG-CompanyWide-RW (everyone = RO by default via Authenticated Users:Read)
```
- Users get **Modify** via the RW group (never Full); SYSTEM/Administrators keep Full.
- Restricted branch: **no `Domain Users`**, inheritance broken, only the named group.
- Management/Exec cross-access handled by adding execs to the RO groups they need (not by
re-opening shares).
---
## 7. What still needs the client (gates Phase 2 sign-off)
This draft fills in everything inferable from the existing layout. Still **must come from
Dataforth** before build:
1. **Confirm the department list** (we inferred it; they validate).
2. **The access matrix** — for each department, RW / RO / none per area (the grid in the
discovery email). Our map above assumes "owning dept RW, others none" except where noted.
3. **Sensitive-data named access** — exactly who sees Payroll, OSHA, POs, Accounting (likely
HR/Finance sign-off, not just Dan).
4. **Rosters** — who is in each department (to populate groups).
5. **Cleanup approval** — which person-named / "Do not use" folders archive vs delete.
6. **Engineering destination** — AD1 C: ~90% full; the big ENGR store needs a target volume
before any restructure/consolidation.
---
## 8. Sequencing note
This slots into **Phase 2 (Target-state design)** of [roadmap.md](./roadmap.md). It is the
strawman to (a) sanity-check internally and (b) simplify into the client sign-off doc once
the Phase 1 matrix arrives. Build order stays lowest-risk-first
(archive -> sales -> c-drive/e-drive -> Engineering -> Restricted last), additive groups
first, remove `Everyone`/`Domain Users` only after pilot validation.

View File

@@ -17,6 +17,12 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
<!-- Append entries below this line -->
2026-06-22 | Howard-Home | gururmm/uninstall-engine | [correction] assumed AnyDesk needs remote removal; it has UninstallString '...AnyDesk.exe --uninstall' and supports --silent, so it is silently removable -- added vendor rule
2026-06-22 | Howard-Home | bash/json | [friction] hand-built JSON literal with C: backslashes collapsed to single backslash in Git-Bash (invalid JSON, ConvertFrom-Json failed); fix: build JSON with jq --arg / extract from existing valid json
2026-06-22 | Howard-Home | gitea/pr-api | PR create returned 'invalid token'; vault.sh get-field services/gitea credentials.api-token returned 4 chars (wrong field resolution) [ctx: repo=gururmm endpoint=172.16.3.20:3000]
2026-06-22 | Howard-Home | sync/submodules | [friction] Phase-3 'git submodule update --init --recursive' reset guru-rmm submodule to pinned commit, discarding feature branch + commits mid-build; fix: submodule_update_safe() skips branch/dirty submodules [ctx: ref=sync.sh:525 fixed]
2026-06-22 | Howard-Home | save/wiki-compile | [friction] /save Phase 3 emits 'project:guru-rmm' (from submodule dir name) but canonical wiki article is 'gururmm'; guru-rmm.md is a tombstone redirect. Map guru-rmm -> gururmm in the slug derivation. [ctx: ref=wiki-slug-tombstone proj=guru-rmm]

View File

@@ -0,0 +1,175 @@
## User
- **User:** Howard Enos (howard)
- **Machine:** Howard-Home
- **Role:** tech
## Session Summary
Built the GuruRMM remote software-uninstall feature (SPEC-030) end to end, starting
from Howard's reference to `dUninstaller.exe` (a closed-source Codejock GUI binary —
nothing to lift). Confirmed the agent already inventories installed software and has a
robust command pipeline, so the gap was: capture uninstall metadata, an uninstall
engine, and a dashboard. Shaped the feature via `/shape-spec` into
`projects/msp-tools/guru-rmm/specs/remote-software-uninstall/`, then prototyped a
standalone PowerShell engine (`agent/scripts/uninstall-engine.ps1`) implementing a
silent-first tier ladder (MSI `/qn`, QuietUninstallString, detected NSIS/InnoSetup
switch, winget), validated by dry-run across 67 real programs and a live `-List` on
test box DESKTOP-MS42HNC.
Chose "Route B" (server-orchestrated): the server embeds the engine via `include_str!`
and dispatches it over the existing `powershell` command pipeline — no agent rebuild or
redeploy. Built `server/src/api/software.rs` (`GET /software`, `POST /software/uninstall`)
+ dashboard `SoftwareManager.tsx` in the Inventory tab (multi-select, confirm-gated bulk
uninstall, per-program results). Merged to main (PR #47, #48) and deployed; verified live
by removing Everything and FastStone on DESKTOP-MS42HNC through the real endpoints.
Then layered the removal knowledge loop: a per-agent tracking table (migration 061) and,
after Howard refined the model, a fleet knowledge catalog (migration 062,
`software_knowledge`) with three classifications — silent / requires_ui / unknown — keyed
by exact DisplayName, logs kept only for unknowns, dashboard promotion of unknowns. Added
BCU (Bulk Crap Uninstaller, Apache-2.0) informed engine upgrades: NSIS detection by binary
signature, MSI `REBOOT=ReallySuppress` + WiX Package Cache, fail-fast 120s timeout, and a
critical exit-code-capture fix (`Start-Process -PassThru` left `.ExitCode` null →
everything falsely reported success; switched to `System.Diagnostics.Process`). Ran a
multi-round live test battery (MSI, NSIS x2, quiet, vendor Firefox/OneDrive, interactive
AnyDesk, MSI-1605 path) — all correct, removals verified.
Captured three follow-on designs as specs: GuruConnect SPEC-019 (private Backstage GUI
desktop for interactive uninstall), rip-and-replace Tier 1.4 (vendor AV/RMM removal tools
for client takeovers), and Tier 1.5 (BCU-style headless UI automator). Fixed a fleet-wide
`sync.sh` bug that was repeatedly clobbering submodule branch work. Finished with a scoped
3-pass Opus audit (`/rmm-audit`) of the SPEC-030 code against GuruRMM standards; fixed all
HIGH + MEDIUM findings. The engine/catalog work remains on branch
`feat/engine-bcu-improvements` (pushed, NOT merged — Howard wants more validation first).
## Key Decisions
- **Route B (server-orchestrated) over a native agent command type.** The server embeds
the validated engine and dispatches it via the existing `powershell` pipeline, so the
feature works on the currently-deployed agent with no rebuild/redeploy. The native
command-type port is a later internal refactor behind the same REST shape.
- **Windows-only for now; show-only on other OSs.** Removal is gated to Windows agents
(server returns 501 for non-Windows; dashboard shows the live uninstall UI only on
Windows and a read-only installed-software list elsewhere). Linux/macOS removal is a
tracked follow-on.
- **Knowledge catalog keyed by exact DisplayName**, three states (silent/requires_ui/
unknown); logs saved ONLY for unknown (undocumented) installers; promotion of unknowns
done via a dashboard action (silent methods still added in engine code, not data-driven).
- **Use vendors' own removal tools for AV/RMM rip-and-replace** (Avast clear, McAfee MCPR,
etc.) rather than reverse-engineering; host vetted checksummed copies on our infra.
- **GuruConnect owns interactive (Tier-2) removal** — silent removal is figured out first;
SPEC-019 extends the existing SPEC-013 backstage from terminal to a private GUI desktop.
- **Engine exits 0 on per-target failures** (failures reported in JSON, not exit code) so
one failed program can't fail a whole bulk batch.
- **Fleet knowledge endpoints are admin-only** (cross-tenant logs + shared writes), matching
the `list_commands` convention.
## Problems Encountered
- **sync.sh repeatedly reset the guru-rmm submodule**, discarding committed branch work
mid-build (HEAD jumped to a stale pinned commit twice). Root cause: Phase-3 post-rebase
ran `git submodule update --init --recursive` unconditionally. Fixed with
`submodule_update_safe()` that skips any submodule on a branch or with uncommitted
changes; pushed to parent main so the whole fleet gets it. Recovered orphaned commits via
cherry-pick onto a feature branch.
- **AnyDesk `--uninstall --silent` hung ~5+ min** (silent flag not honored on the tested
build). Dropped the AnyDesk vendor rule → it now classifies as needs_remote instantly
(interactive tier, no launch). Logged as a correction.
- **Exit codes were not captured** — `Start-Process -PassThru` returned `.ExitCode` null,
so every uninstall mapped to exit 0 / false "success" (a failed MSI 1603 would read as
removed). Switched to `System.Diagnostics.Process` with async stream reads; verified
1605 + exit-0 now captured.
- **Engine embedded in server but the server build change-gate only watched `server/`** —
an engine-only change would silently ship the old engine. Fixed `build-server.sh` to also
watch `agent/scripts/uninstall-engine.ps1`.
- **Git-Bash `curl` started failing "Permission denied"** (AV/EDR on the workstation after
many calls). Pivoted RMM API calls to PowerShell `Invoke-RestMethod`.
- **Hand-built JSON with `C:\\` backslashes was mangled** in Git-Bash (collapsed to single
backslash → invalid JSON, ConvertFrom-Json failed). Fixed by building targets JSON with
`jq` / extracting from already-valid JSON. Logged as friction.
- **PR auto-create failed** — `vault.sh get-field services/gitea credentials.api-token`
mis-resolved (returned 4 chars). Worked around by parsing the api-token line directly;
validated against the Gitea API before use.
## Configuration Changes
Branch `feat/engine-bcu-improvements` (guru-rmm submodule, pushed, NOT merged):
- `agent/scripts/uninstall-engine.ps1` — new engine (tiers, vendor table, binary-NSIS,
Package Cache, fail-fast, exit-code fix, hardened self-uninstall guard)
- `server/src/api/software.rs` — endpoints (list/uninstall/removal-status/resolve/
knowledge/classify), os_type gate, admin gating, error-leak fixes, pagination
- `server/src/api/mod.rs` — routes
- `server/src/db/software_removal.rs`, `server/src/db/software_knowledge.rs`, `db/mod.rs`
- `server/migrations/061_software_removal_attempts.sql`, `062_software_knowledge.sql`
- `dashboard/src/api/client.ts`, `dashboard/src/components/SoftwareManager.tsx`,
`dashboard/src/components/InventoryTab.tsx`, `dashboard/src/pages/AgentDetail.tsx`
- `deploy/build-pipeline/build-server.sh` — change-gate watches the embedded engine
- `specs/remote-software-uninstall/` — plan, shape, references, standards, task1-results,
bcu-research-and-tiers, knowledge-base-design, rip-and-replace-removal-tools
- `reports/2026-06-22-spec030-software-uninstall-audit.md`
Already on guru-rmm main (deployed): base inventory+uninstall + per-device tracking
(PR #47 merge 42681f2c, PR #48 merge c4c0ea7).
guru-connect submodule: `docs/specs/SPEC-019-private-backstage-session.md` +
`docs/FEATURE_ROADMAP.md` on branch `feat/spec-019-backstage-uninstall` (pushed, off main).
Parent claudetools (pushed to main): `.claude/scripts/sync.sh` (submodule_update_safe),
`.claude/memory/feedback_submodule_autosync_discipline.md`, `errorlog.md`.
## Credentials & Secrets
- No new credentials created. RMM admin creds read from vault
`infrastructure/gururmm-server.sops.yaml` fields
`credentials.gururmm-api.admin-email` / `admin-password` (used for API auth during
testing). Gitea API token at `services/gitea` field `credentials.api-token` (used for
PR create/merge). Temp credential files written under `.claude/tmp/` during testing were
shredded; `.claude/tmp` is gitignored.
## Infrastructure & Servers
- GuruRMM API/server: `http://172.16.3.30:3001` (prod; also the build host, user `guru`,
repo `/home/guru/gururmm`). Beta dashboard: `https://rmm-beta.azcomputerguru.com`
(built from main, talks to prod API). Prod dashboard: `https://rmm.azcomputerguru.com`.
- Gitea internal API: `http://172.16.3.20:3000` (repo `azcomputerguru/gururmm`,
`azcomputerguru/guru-connect`). Public host `git.azcomputerguru.com` is behind
Cloudflare (blocks curl).
- Test box: **DESKTOP-MS42HNC** — AZ Computer Guru / Howard-VM, Windows, agent id
`0de89b88-b21d-4647-ab64-96157ba87cc5`.
## Commands & Outputs
- Run engine standalone: `powershell -NoProfile -ExecutionPolicy Bypass -File
agent/scripts/uninstall-engine.ps1 -List` (JSON inventory) /
`... -TargetsJson <file> [-DryRun]`.
- Server build check: `SQLX_OFFLINE=true cargo check -p gururmm-server` (clean).
- Dashboard: `npx tsc -p tsconfig.app.json --noEmit` + `npm run build` (clean).
- Live results: classification 104/120 silent-capable on DESKTOP-MS42HNC; removed
Everything, FastStone, HandBrake, ImgBurn, Paint.NET, GIMP, Firefox, OneDrive, AIMP
(verified gone); AnyDesk correctly retained as needs_remote; fake-GUID MSI → exit 1605
"not installed".
- Gitea PR+merge via `Invoke-RestMethod` / token from vault api-token line.
## Pending / Incomplete Tasks
- **Merge + deploy `feat/engine-bcu-improvements`** (engine improvements + knowledge
catalog + audit fixes). Not merged per Howard ("keep testing before live"). Post-deploy:
verify catalog populates + promote an unknown live; the catalog/dashboard cannot be
exercised end-to-end until deployed (live server still runs the old engine).
- **Audit LOW items** (tracked in the report): `warn!` on audit-write failure, randomized
temp filename, TS interface completeness, empty-states, ASCII em-dash/ellipsis cleanup.
- **GuruConnect SPEC-019** (private Backstage GUI desktop) — branch pushed, not merged.
- **Rip-and-replace Tier 1.4** (AV/RMM vendor removal tools) — spec written, not built.
- **Tier 1.5** (headless UI automator) — spec written, not built.
- **Linux/macOS removal** — Windows-only today; tracked follow-on.
## Reference Information
- Branch: `feat/engine-bcu-improvements` (guru-rmm) — latest commit `c982352`.
- Merged to guru-rmm main: PR #47 (`42681f2c`), PR #48 (`c4c0ea7`).
- guru-connect branch: `feat/spec-019-backstage-uninstall`; SPEC-019.
- Parent commits: `9108f94` (sync fix), `7ad4353` (memory).
- Specs: `projects/msp-tools/guru-rmm/specs/remote-software-uninstall/` (8 docs).
- Audit report: `projects/msp-tools/guru-rmm/reports/2026-06-22-spec030-software-uninstall-audit.md`.
- BCUninstaller (Apache-2.0): https://github.com/BCUninstaller/Bulk-Crap-Uninstaller
- Engine embed path: server `include_str!("../../../agent/scripts/uninstall-engine.ps1")`.