sync: auto-sync from HOWARD-HOME at 2026-06-25 12:45:08

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-25 12:45:08
This commit is contained in:
2026-06-25 12:45:43 -07:00
parent bd1e84d32c
commit e9ece35c2a
3 changed files with 182 additions and 3 deletions

View File

@@ -44,6 +44,7 @@
- [AD2 SSH MTU blackhole](ad2-ssh-mtu-blackhole.md) — AD2 SSH "lockouts"/mid-session read-errors over the Dataforth OpenVPN were a PMTU blackhole (tunnel PMTU ~1424 vs adapter MTU 1500), NOT a ban/account-lockout/flaky tunnel. Fix: pin the OpenVPN adapter MTU to 1400 (done on GURU-5070 via its SYSTEM RMM agent); permanent = `mssfix 1360` on the OpenVPN server. Diagnose over RMM, not SSH.
- [DSCA33/45 resolved via Hoffman](project_dsca33_45_resolved_via_hoffman.md) — The "lost" DSCA33/45 spec files are recoverable from the Hoffman API (original certs survived the wipe); do NOT ask John. 56/58 models mined into projects/dataforth-dos/dsca33-45-templates.json; only DSCA33-1948 + DSCA45-1746 (24 units) lack an original. AD2 handoff: DSCA33-45-HOFFMAN-RECOVERY-2026-06-18.md.
- [AD2 comms via sync only](ad2-comms-via-sync-only.md) — The AD2 Dataforth-box Claude session is coord-API-isolated (Gitea only); coord msg/lock/todo never reach it. Coordinate with AD2 ONLY via git /sync (committed docs + ## Note blocks).
- [reference_syncro_agent_handle_leak](reference_syncro_agent_handle_leak.md) -- RDS "no available computers in the pool" (0x3/0x408) can really be a SyncroLive.Agent.Runner handle leak starving the box. How to spot + fix.
## Users
- [Howard Enos](user_howard.md) — Mike's brother, technician, full access. Machines: ACG-TECH03L, Howard-Home (authoritative in users.json).
@@ -124,6 +125,7 @@
- [feedback_bitdefender_unattended_install](feedback_bitdefender_unattended_install.md) -- Bitdefender unattended RMM install must use the FULL KIT as SYSTEM (silent, no UAC) — the downloader stub fails headless and triggers UAC
- [Broken [[backlinks]] are write-me-later markers — flesh out from session history, don't delete](feedback_broken_backlinks_are_writeme_markers.md) -- A [[name]] link in a memory body whose target file doesn't exist is NOT an error to clean up — it's an intentional marker that that memory is worth writing. When you hit one (or memory-dream lists them), flesh the missing memory out from the session logs / session history, don't strip the link.
- [feedback_rmm_longops_fire_and_forget](feedback_rmm_longops_fire_and_forget.md) -- Long-running RMM endpoint ops (software installs, big downloads) must be fire-and-forget, not live-monitored
- [Broken [[backlinks]] are write-me-later markers — flesh out from session history, don't delete](feedback_broken_backlinks_are_writeme_markers.md) -- A [[name]] link in a memory body whose target file doesn't exist is NOT an error to clean up — it's an intentional marker that that memory is worth writing. When you hit one (or memory-dream lists them), flesh the missing memory out from the session logs / session history, don't strip the link.
## Machine
- [GURU-5070 Workstation Setup](reference_workstation_setup.md) — Mike's primary (owner confirmed 2026-05-26). Windows 11 Pro. Renamed from OC-5070 → ACG-5070/acg-guru-5070 → GURU-5070; all the same box, all Mike's.

View File

@@ -38,6 +38,7 @@ import argparse
import dataclasses
import json
import os
import re
import subprocess
import sys
@@ -312,11 +313,15 @@ def cmd_company_delete(client, args):
def cmd_endpoints(client, args):
if args.company and not _require_oid(args.company, "company"):
return 2
_emit(client.list_endpoints(args.company, per_page=args.per_page),
args.json, _print_endpoint_table)
def cmd_endpoint(client, args):
if not _require_oid(args.endpoint_id, "endpoint"):
return 2
_emit(client.get_endpoint_details(args.endpoint_id), args.json, _print_kv)
@@ -348,6 +353,8 @@ def cmd_policy(client, args):
# getPolicyDetails returns the FULL granular module configuration (verified
# live 2026-06-21). Use --json for the complete settings tree; the table
# view shows the top-level keys only.
if not _require_oid(args.policy_id, "policy"):
return 2
_emit(client.get_policy_details(args.policy_id), args.json, _print_kv)
@@ -649,6 +656,12 @@ def cmd_inventory(client, args):
def cmd_create_package(client, args):
if args.company and not _require_oid(args.company, "company"):
return 2
if not _gated(f"create installer package '{args.name}'"
+ (f" in company {args.company}" if args.company else ""),
args.confirm):
return 3
result = client.create_package(
package_name=args.name,
company_id=args.company,
@@ -656,6 +669,7 @@ def cmd_create_package(client, args):
language=args.language,
)
_emit({"created": args.name, "result": result}, args.json, _print_kv)
return 0
def cmd_install_links(client, args):
@@ -664,16 +678,33 @@ def cmd_install_links(client, args):
def cmd_scan(client, args):
for t in args.targets:
if not _require_oid(t, "scan target"):
return 2
if not _gated(f"start a type-{args.type} scan on {len(args.targets)} "
f"target(s): {','.join(args.targets)}", args.confirm):
return 3
result = client.create_scan_task(
target_ids=args.targets, scan_type=args.type, name=args.name
)
_emit({"scanTask": result}, args.json, _print_kv)
return 0
def cmd_move(client, args):
for e in args.endpoints:
if not _require_oid(e, "endpoint"):
return 2
if not _require_oid(args.group, "group"):
return 2
if not _gated(f"move {len(args.endpoints)} endpoint(s) into group "
f"{args.group} (changes inherited policy): "
f"{','.join(args.endpoints)}", args.confirm):
return 3
result = client.move_endpoints(args.endpoints, args.group)
_emit({"moved": args.endpoints, "to": args.group, "result": result},
args.json, _print_kv)
return 0
def _print_tags(tags) -> None:
@@ -709,8 +740,15 @@ def cmd_reconfigure(client, args):
def cmd_make_group(client, args):
if args.parent and not _require_oid(args.parent, "parent group"):
return 2
if not _gated(f"create custom group '{args.name}'"
+ (f" under parent {args.parent}" if args.parent else ""),
args.confirm):
return 3
result = client.create_custom_group(args.name, args.parent)
_emit({"createdGroup": args.name, "result": result}, args.json, _print_kv)
return 0
# Substrings that mark a JSON-RPC method as state-destroying. `raw` can reach
@@ -724,7 +762,11 @@ DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove",
"configurenotif", "createcompany", "suspendcompany",
"activatecompany", "setendpointlabel", "createreport",
"createrestore", "createcustomrule", "changeincident",
"updateincident", "sendtestpush")
"updateincident", "sendtestpush",
# state-changing methods also exposed as gated
# subcommands - keep them gated via `raw` too.
"moveendpoints", "movecustomgroup", "createscan",
"createpackage", "createcustomgroup")
def _is_destructive_method(method: str) -> bool:
@@ -750,11 +792,31 @@ def cmd_raw(client, args):
return 0
# --- input validation ---------------------------------------------------------
# GravityZone object IDs are 24-char hex (Mongo ObjectId). Validating client-side
# stops a malformed/empty id from hitting the live tenant AND from being mislogged
# as a functional skill error (the API's "Expected format: 24-char hex ID" reply
# matches none of the expected-error markers, so it would otherwise land in
# errorlog.md as noise - the bulk of the 2026-06-21 bitdefender entries).
_OID_RE = re.compile(r"^[0-9a-fA-F]{24}$")
def _require_oid(value, label: str) -> bool:
"""True if `value` is a valid 24-char hex id; else print [ERROR] and False.
The caller should `return 2` (user-input error) and must NOT log it."""
if value and _OID_RE.match(str(value)):
return True
print(f"[ERROR] {label} '{value}' is not a valid 24-char hex GravityZone id.",
file=sys.stderr)
return False
# --- destructive (gated) ------------------------------------------------------
def _gated(action_desc: str, confirm: bool) -> bool:
if not confirm:
print("[WARNING] Refusing destructive action without --confirm.")
print(f"[INFO] Would: {action_desc}")
print("[WARNING] Refusing destructive action without --confirm.",
file=sys.stderr)
print(f"[INFO] Would: {action_desc}", file=sys.stderr)
return False
return True