## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-5070
- **Role:** admin

## Session Summary

New-asset provisioning + remote-access work for Grabb & Durando's user Jeff Williams (laptop LapTopNinja, Workgroup, ARM64 ASUS Copilot+, at the "Jeff's House" site). All work via GuruRMM (LapTopNinja agent ccb55043; GND-SERVER agent cd086074).

De-bloated LapTopNinja: killed + fully removed the "PC App Store" PUP (Fast Corporation LTD — processes, a "Watchdog" persistence helper, service, folder, registry, autostart; its interactive uninstaller hung under -Wait so removal was done manually). Removed ASUS OEM bloat via silent MSI uninstall (Virtual Assistant, GlideX, StoryCube) + Virtual Pet (run-key/folder) + leftover ASUS folders (AdobePromotion, GlideX/StoryCube residue, AsLogDumpTool). Removed new Outlook (Microsoft.OutlookForWindows, all-users). Left AsusScreenXpert (ScreenPad hardware dependency — pending Mike's confirm) and the Microsoft-signed promo-looking Appx (benign placeholders).

Set up remote access. Investigation showed GND-SERVER (Windows Server 2019, DC for gd.local, 192.168.242.200) runs a Windows RRAS VPN with SSTP still exposed on 443 (PPTP + L2TP were correctly closed at the UniFi). SSTP uses a valid public Let's Encrypt cert (vpn.grabblaw.com). Recommended reusing SSTP over installing OpenVPN (same security posture, zero install, on the same DC). Enabled jwilliams' dial-in (msNPAllowDialin) and created the SSTP client profile on LapTopNinja.

VPN then immediately disconnected after sign-in (reason code 829). Deep diagnosis: it authenticated, got a pool IP, fully connected, then RRAS tore it down ~2s later — reproducible for ALL users and a minimal profile (reproduced with the gd.local Administrator break-glass cred via rasdial). Ruled out cert (SstpSvc hash matched live 443 cert), IP-pool conflict (pool free), auth, and subnet overlap. Root cause: the RRAS RemoteAccess service was in a stale state; **restarting RemoteAccess fixed it** — the real "Grabb & Durando Office" profile then stayed connected (tunnel IP 192.168.242.251, office reachable).

Hardened against the SSTP brute-force seen in the logs (user1/test/vpn/admin from Contabo/abuse VPS ranges): deployed a PowerShell fail2ban on GND-SERVER (scheduled task, auto-blocks repeat offenders via Windows Firewall) AND blocked the two attacker /24s at the UGW3 edge (after fixing the gw-control block-ips bug that made the rule proto=gre).

Diagnosed application crashes on LapTopNinja (SystemSettings/TabTip/Explorer-Taskbar) as build-level shell bugs from the 26H1 / build-28000 preview Windows train the Copilot+ ARM laptop is on (not de-bloat, not corruption — store clean, SFC + Settings re-register done, crashes persisted). Added an RDP desktop shortcut to 192.168.242.61. Decision: jwill is travelling; the machine is functional as-is, and it will be **reinstalled to stable retail Win11 24H2 when he returns** (coord todo 24eb7cb8).

## Key Decisions

- **Reuse SSTP, not install OpenVPN** — SSTP is TLS-over-443 with a public LE cert (same security as Mike's OpenVPN goal), already working, zero install; OpenVPN would add attack surface on the same DC for no gain.
- **Fix VPN by restarting RemoteAccess** — after isolating the drop to a server-side, all-users, all-profiles teardown (not cert/IP/auth/profile), a service restart cleared the stale RRAS state.
- **Defense-in-depth brute-force block** — fail2ban on the server (catches new individual offenders) + edge /24 drop on the UGW3 (keeps the abuse ranges off the DC entirely).
- **Did NOT remove AsusScreenXpert** — it drives the ScreenPad secondary display; removing it blind could break hardware. Left the Microsoft-signed odd-named Appx alone (benign).
- **Defer LapTopNinja reinstall** — crashes are cosmetic/self-recovering build bugs; reinstall to stable 24H2 when jwill returns rather than disrupt his travel.
- **RDP shortcut on Public Desktop** — shows for jwill regardless of OneDrive desktop redirection.

## Problems Encountered

- **PC App Store interactive uninstaller hung** under Start-Process -Wait (no /S support as SYSTEM) -> manual removal; a "Watchdog" process held the folder open until killed.
- **VPN connect-then-drop (829):** stale RRAS service; fixed by restarting RemoteAccess. 829 is non-standard (net helpmsg blank); diagnosis required reproducing via rasdial + isolating client vs server.
- **gw-control block-ips created a proto=gre drop rule** (cloned the disabled PPTP GRE rule's schema) -> ineffective; fixed via controller REST PUT setting protocol=all. Logged as a skill bug.
- **vault get-field returned a wrong 4-char value** for bare `password` (recurring resolution bug) -> 401; fixed by using `credentials.password`. Logged (cites the 2026-06-22 gitea entry).
- **UniFi controller REST 401/403:** REST path needs the site SHORT name (resolve via /api/self/sites), not the _id; and the CSRF token must be read case-insensitively (resp.headers.get, not dict()). Both logged.

## Configuration Changes

- **LapTopNinja (ccb55043):** removed PC App Store + ASUS bloat + new Outlook; created SSTP VPN profile "Grabb & Durando Office"; re-registered immersivecontrolpanel + SFC; added RDP shortcut `C:\Users\Public\Desktop\Office PC (192.168.242.61).rdp`.
- **GND-SERVER (cd086074):** enabled `jwilliams` msNPAllowDialin=$true; restarted RemoteAccess service (VPN fix); deployed `C:\Scripts\vpn-fail2ban.ps1` + scheduled task "VPN Fail2Ban" (every 10 min, SYSTEM) + Windows Firewall rule `Fail2Ban-VPN-Block` (seeded 161.97.182.206, 185.141.216.32). (Administrator dial-in was temporarily toggled for testing and reverted.)
- **UniFi UGW3 (Grabb and Durando site, short ui1n7yfh):** address-group `gw-control blocklist` (161.97.182.0/24, 185.141.216.0/24) + WAN_IN drop rule `Block gw-control blocklist` (protocol corrected gre->all).
- **Repo:** `clients/grabb-durando/vpn-fail2ban.ps1` (version-controlled copy of the deployed script); this session log.

## Credentials & Secrets

- No new credentials created. Used existing vault: `clients/grabb-durando/gd-local-domain-admin.sops.yaml` (gd.local\Administrator break-glass — used for VPN rasdial repro; already noted as having transited the RMM command log) and `infrastructure/uos-server-network-api-rw.sops.yaml` (UOS controller admin `claudetools`, field `credentials.password`).
- VPN auth = users' gd.local AD credentials (jwilliams enters his on first connect; profile has RememberCredential).

## Infrastructure & Servers

- **GND-SERVER:** Windows Server 2019, DC for `gd.local`, LAN 192.168.242.200, single NIC. RRAS VPN: SSTP (443), RAS dial-in pool 192.168.242.249-254 (server RAS iface .249). PPTP/L2TP closed.
- **VPN endpoint:** `vpn.grabblaw.com` -> office WAN 174.76.185.203 (UGW3 forwards 80,443 -> .200). Let's Encrypt cert (win-acme, monthly renew; 3 certs present, current valid to 2026-09-20).
- **LapTopNinja:** Workgroup, ARM64 ASUS Copilot+, Win11 build 28000.2340 (DisplayVersion 26H1, br_release — preview train). User profile `jwill` = AD `jwilliams` (Jeff Williams, Domain Admin). Agent ccb55043-b310-47df-afe3-2671c8ff113c.
- **UniFi:** Grabb and Durando on the UOS controller 172.16.3.29:11443 (site _id 6080d597f91fdd010f7c7155, short `ui1n7yfh`), UGW3 gateway. NOT in the cloud Site Manager.
- **RDP target:** 192.168.242.61 (office, over VPN).

## Commands & Outputs

- RMM auth: `eval "$(bash .claude/scripts/rmm-auth.sh)"` -> $TOKEN/$RMM. Dispatch via POST /api/agents/<id>/command.
- VPN fix: `Restart-Service RemoteAccess -Force` on GND-SERVER -> tunnel stays up (validated: tunnel IP 192.168.242.251, Test-Connection 192.168.242.200 = True).
- Edge block fix: controller login (172.16.3.29:11443) -> resolve short name via /api/self/sites -> PUT /proxy/network/api/s/<short>/rest/firewallrule/<id> protocol=all (CSRF via resp.headers.get, case-insensitive).
- VPN profile: SSTP, MSChapv2, split-tunnel, route 192.168.242.0/24, DnsSuffix gd.local, RememberCredential.

## Pending / Incomplete Tasks

- **Reinstall LapTopNinja to stable Win11 24H2 when jwill returns** (coord todo 24eb7cb8) — then redo de-bloat + VPN profile + RDP shortcut.
- **AsusScreenXpert** removal — pending Mike's ScreenPad confirmation.
- **Prevent VPN recurrence:** add a post-Let's-Encrypt-renewal hook to auto-restart RemoteAccess (offered; not yet done) so the stale-RRAS drop doesn't recur ~monthly.
- **RDP shortcut username** not pre-filled (prompts on connect) — set if Mike wants.
- Security note: `jwilliams` is a Domain Admin VPNing from a workgroup laptop — worth tightening later.

## Reference Information

- Coord todo: 24eb7cb8 (LapTopNinja reinstall). RMM agents: LapTopNinja ccb55043, GND-SERVER cd086074.
- fail2ban: `clients/grabb-durando/vpn-fail2ban.ps1` (deployed at C:\Scripts\ on GND-SERVER).
- Vault: clients/grabb-durando/gd-local-domain-admin.sops.yaml, infrastructure/uos-server-network-api-rw.sops.yaml.
- gw-control: `.claude/skills/unifi-wifi/scripts/gw-control.sh "Grabb and Durando" pf-list|fw-list|block-ips`.

## Update: ~14:15 PT — Billed #32455 + systemic Syncro prepay fix

**Billing.** Created Syncro ticket **#32455 "Jeff Laptop Setup"** (Grabb & Durando Law Office, cust 14232794), posted a customer-visible resolution comment (emailed, do_not_email:false), billed **2.0 h remote** (product 1190473 @ $150), invoiced, marked Invoiced, #bot-alerts posted. Ticket id 113001195, invoice id 1650779485.

**Prepay correction.** My preview said "no block -> $300 real charge" — WRONG. The customer SEARCH endpoint returned `prepay_hours: null`, but the full `GET /customers/{id}` showed a real prepaid block. The 2.0 h were correctly debited from the block (**20.5 -> 18.5**) and the invoice netted **$0.00** ("Applied 2.0 Prepay Hours") — the correct outcome. Fixed the invoice note from the wrong upsell to "Block hours remaining: 18.5."

**Systemic fix (Mike: this keeps recurring, make it reliable).** Root cause: reading `prepay_hours` off the customer search/list endpoint (returns null/stale) instead of the full customer GET. Durable fixes:
- `.claude/commands/syncro.md` — new HARD RULE: `prepay_hours` is ONLY reliable from `GET /customers/{id}`; never read it from search/list; never assert "no block / real charge / $N" in a preview from search data; the Step-1 full GET must precede the PREVIEW (not just the invoice). Also annotated the customers search snippet.
- New feedback memory `.claude/memory/feedback_syncro_prepay_full_get_only.md` + MEMORY.md index line.
- errorlog friction entry (syncro/billing-prepay).

**Refs:** ticket #32455 (id 113001195, invoice 1650779485). Skill commit 7b252335. Earlier this-session commits: 373883fb (GND log + fail2ban script).
