sync: auto-sync from GURU-5070 at 2026-06-10 20:18:48
Author: Mike Swanson Machine: GURU-5070 Timestamp: 2026-06-10 20:18:48
This commit is contained in:
36
.claude/commands/vault.md
Normal file
36
.claude/commands/vault.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# /vault — Consistent SOPS vault operations
|
||||||
|
|
||||||
|
The one canonical way to read, store, update, and verify secrets in the ClaudeTools SOPS+age
|
||||||
|
vault. Use instead of raw `sops` or guessed paths. Full reference: `.claude/skills/vault/SKILL.md`.
|
||||||
|
|
||||||
|
## Quick reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# READ
|
||||||
|
bash .claude/scripts/vault.sh get <path>
|
||||||
|
bash .claude/scripts/vault.sh get-field <path> credentials.api_key
|
||||||
|
bash .claude/scripts/vault.sh search <query>
|
||||||
|
bash .claude/scripts/vault.sh list [subdir]
|
||||||
|
|
||||||
|
# STORE / UPDATE (non-interactive — these work in this harness; `vault edit` does not)
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh new <path> --kind api-key \
|
||||||
|
--name "..." [--url ..] [--tag ..] --set api_key=SECRET [--set username=foo]
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh set <path> --set password=NEW
|
||||||
|
|
||||||
|
# VERIFY (after any write, before any commit)
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh verify <path>
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh check [subdir]
|
||||||
|
|
||||||
|
# PUBLISH
|
||||||
|
bash .claude/scripts/sync.sh # Phase 6 commits + pushes the vault repo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules (non-negotiable)
|
||||||
|
|
||||||
|
1. Never paste a secret into chat / ticket / commit / channel — share the vault path instead.
|
||||||
|
2. Secrets ALWAYS go under `credentials:` (only those keys get encrypted; anything else = plaintext).
|
||||||
|
3. Use the scripts above — never hand-roll `sops` + a guessed path, never use `VAULT_ROOT_ENV` for vault access.
|
||||||
|
4. Finish: write → `verify` → publish (sync). Don't hand off the push.
|
||||||
|
|
||||||
|
Paths are vault-root-relative (`clients/<slug>/...`, `msp-tools/...`, `infrastructure/...`,
|
||||||
|
`services/...`), with or without `.sops.yaml`.
|
||||||
26
.claude/memory/feedback_ascii_only_api_payloads.md
Normal file
26
.claude/memory/feedback_ascii_only_api_payloads.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: feedback_ascii_only_api_payloads
|
||||||
|
description: On Windows/Git-bash, non-ASCII chars (em-dash, arrow, smart quotes) in JSON payload TEXT passed to curl get mangled and rejected — Discord bot-alert returns 400, the coord API returns "error parsing the body". Use ASCII-only in API payload text, or a single-quoted heredoc.
|
||||||
|
metadata:
|
||||||
|
type: feedback
|
||||||
|
---
|
||||||
|
|
||||||
|
When building JSON API payloads on Windows/Git-bash and sending via `curl`, **non-ASCII characters
|
||||||
|
in the text fields get mangled in transit and rejected by the server**, even though `jq -n`
|
||||||
|
produces valid UTF-8 JSON. Hit twice on 2026-06-01:
|
||||||
|
- `post-bot-alert.sh` → Discord **400** `{"message":"The request body contains invalid JSON","code":50109}` on a message containing `—` (em-dash) and `→` (arrow).
|
||||||
|
- Coord todos API (`POST /api/coord/todos`) → **`{"detail":"There was an error parsing the body"}`** on todo text containing em-dashes (both the inline `$(jq -n ...)` and the `P=$(jq -n ...); curl --data-binary "$P"` patterns failed).
|
||||||
|
|
||||||
|
**Why:** the round-trip through a bash variable → `curl --data-binary` re-encodes/mangles the
|
||||||
|
multibyte UTF-8 (Git-bash codepage quirk), so the bytes the server receives are no longer valid JSON.
|
||||||
|
|
||||||
|
**Fix:** keep API payload text **ASCII-only** — use `-` not `—`, `->` not `→`, straight quotes not
|
||||||
|
smart quotes. The most robust transport is a **single-quoted heredoc** piped to curl:
|
||||||
|
```bash
|
||||||
|
curl -s -X POST "$API" -H "Content-Type: application/json" --data-binary @- <<'JSON'
|
||||||
|
{"text":"ASCII only - no em-dashes or arrows","project_key":"..."}
|
||||||
|
JSON
|
||||||
|
```
|
||||||
|
This bit the Syncro bot-alert (resolved by ASCII retry) and the coord-todo filings the same day.
|
||||||
|
NOTE-TO-SELF tie-in: the project's NO-EMOJIS rule already pushes ASCII markers; extend that habit to
|
||||||
|
all API payload text, not just console output.
|
||||||
12
.claude/memory/reference_backblaze_storage_rate.md
Normal file
12
.claude/memory/reference_backblaze_storage_rate.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: reference_backblaze_storage_rate
|
||||||
|
description: ACG's Backblaze B2 storage cost rate ($0.00695/GB) for the GuruRMM mspbackups storage-cost calculation
|
||||||
|
metadata:
|
||||||
|
type: reference
|
||||||
|
---
|
||||||
|
|
||||||
|
ACG's Backblaze B2 storage rate is **$0.00695 per GB**. Use this as the cost input when calculating client storage cost in the GuruRMM **mspbackups** (MSP360) ability.
|
||||||
|
|
||||||
|
- Cost = stored_GB x 0.00695 (USD).
|
||||||
|
- This is ACG's cost basis; client-facing markup/billing is a separate decision, not this figure.
|
||||||
|
- The B2 storage-management credential is the vault entry `projects/claudetools/backblaze-b2.sops.yaml` (key name "ClaudeTools", manages buckets/keys for the mspbackups feature).
|
||||||
33
.claude/memory/reference_sqlx_migrations_immutable.md
Normal file
33
.claude/memory/reference_sqlx_migrations_immutable.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
name: reference_sqlx_migrations_immutable
|
||||||
|
description: NEVER edit an already-applied sqlx migration file — even a comment. sqlx::migrate! checksums each file at compile time and validates against _sqlx_migrations at startup; a changed checksum crash-loops the server with "migration N was previously applied but has been modified". Code review MUST flag any edit to an applied migration.
|
||||||
|
metadata:
|
||||||
|
type: reference
|
||||||
|
---
|
||||||
|
|
||||||
|
GuruRMM and GuruConnect both apply DB migrations at server startup via `sqlx::migrate!()`
|
||||||
|
(embedded at COMPILE time from `server/migrations/`). sqlx stores a **checksum** of each migration
|
||||||
|
in the `_sqlx_migrations` table when it first applies it, and on every startup re-validates the
|
||||||
|
embedded migration files' checksums against that table.
|
||||||
|
|
||||||
|
**Editing an already-applied migration file — even just a COMMENT — changes its checksum** and the
|
||||||
|
server fails to boot:
|
||||||
|
```
|
||||||
|
ERROR Failed to run migrations: migration 8 was previously applied but has been modified
|
||||||
|
```
|
||||||
|
systemd then crash-loops it and eventually trips the start-limit ("Start request repeated too quickly").
|
||||||
|
|
||||||
|
**Incident 2026-06-01 (GuruConnect):** a one-line `ON CONFLICT` fix in `server/src/db/machines.rs`
|
||||||
|
was bundled with a *comment-only* edit to `server/migrations/008_machine_uid.sql`. The code fix was
|
||||||
|
correct, but the migration comment edit took the relay down for ~6 min on deploy. Both the Coding
|
||||||
|
Agent and the Code Review Agent explicitly judged the comment edit "zero runtime effect" — WRONG.
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- Applied migrations are **immutable**. Never touch them. To change schema, write a NEW migration.
|
||||||
|
- If documentation about a migration needs fixing, put it in code comments / docs, NOT the migration file.
|
||||||
|
- **Code review must reject ANY diff that touches a file under `server/migrations/` that has already
|
||||||
|
been applied in prod** (or require a brand-new migration instead).
|
||||||
|
- **Recovery:** restore the migration's exact original bytes (`git checkout <prev> -- path/to/NNN.sql`),
|
||||||
|
rebuild (sqlx embeds at compile time, so a rebuild is required), restart. If systemd shows
|
||||||
|
"Start request repeated too quickly", clear the limiter first: `sudo systemctl reset-failed <svc>`
|
||||||
|
then `sudo systemctl start <svc>`.
|
||||||
136
.claude/skills/vault/SKILL.md
Normal file
136
.claude/skills/vault/SKILL.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
---
|
||||||
|
name: vault
|
||||||
|
description: "The ONE canonical way to use the ClaudeTools SOPS+age secret vault — read, store, update, and verify credentials. Use this whenever a task involves a password, API key, token, secret, connection string, SSH key, or any credential: retrieving one to use it, storing a newly created/discovered one, or checking what's vaulted. Stops the per-session improvising (raw sops, guessed paths, VAULT_ROOT_ENV hacks, plaintext-field mistakes). Triggers: vault, store/save a secret, add to vault, get the password/api key for X, where is the credential for X, sops, encrypt this secret, decrypt, rotate a credential, 1password fallback, vault a new key."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vault — one consistent way to handle secrets
|
||||||
|
|
||||||
|
Vaulting should be identical every time. It is. This skill is the single source of truth so no
|
||||||
|
session has to re-derive it. Two tools, one rule:
|
||||||
|
|
||||||
|
- **Reads / search / list** → the canonical `vault.sh` (auto-resolves the vault from identity.json).
|
||||||
|
- **Create / update / verify (non-interactive)** → `vault-helper.sh` in this skill. (The base
|
||||||
|
`vault.sh add`/`edit` are interactive `$EDITOR` flows Claude can't drive — do NOT use them; do
|
||||||
|
NOT fall back to raw `sops` with hand-built paths.)
|
||||||
|
|
||||||
|
## THE RULE (read this first)
|
||||||
|
|
||||||
|
1. **Never paste a secret into chat, a ticket, a commit message, or a coord/Discord channel.**
|
||||||
|
Point people at the vault path instead (teammates with vault access decrypt it themselves).
|
||||||
|
2. **Never write a secret to a field outside `credentials:`.** The `.sops.yaml` only encrypts keys
|
||||||
|
named `credentials | password | secret | api_key | token | pre_shared_key | notes | content`.
|
||||||
|
A secret placed anywhere else commits in **plaintext**. Always put secrets under `credentials:`.
|
||||||
|
3. **Never hand-roll the vault path or `sops` command.** Use the two scripts below — they resolve
|
||||||
|
the vault root the same way on every machine.
|
||||||
|
4. **Finish the job:** create/update → verify it's encrypted → publish (sync). Don't stop at
|
||||||
|
"it's on disk, you push it."
|
||||||
|
|
||||||
|
## Read a secret (canonical)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# whole entry (decrypted)
|
||||||
|
bash .claude/scripts/vault.sh get <path>
|
||||||
|
# one field, dot-notation (e.g. credentials.api_key, credentials.admin.password)
|
||||||
|
bash .claude/scripts/vault.sh get-field <path> credentials.api_key
|
||||||
|
# find where something lives
|
||||||
|
bash .claude/scripts/vault.sh search <query>
|
||||||
|
bash .claude/scripts/vault.sh list [subdir]
|
||||||
|
```
|
||||||
|
|
||||||
|
`<path>` is relative to the vault root, with or without the `.sops.yaml` suffix
|
||||||
|
(e.g. `clients/kittle/gururmm-site-main` or `msp-tools/computerguru-user-manager.sops.yaml`).
|
||||||
|
|
||||||
|
The repo wrapper `.claude/scripts/vault.sh` reads `vault_path` from `.claude/identity.json` and
|
||||||
|
delegates to the real `vault.sh` in the vault repo. That is the ONLY entry point you need for
|
||||||
|
reads. (If `vault_path` is missing on a machine, fix identity.json — don't paper over it with
|
||||||
|
`VAULT_ROOT_ENV`, which is a separate remediation-tool-script quirk, not how you read the vault.)
|
||||||
|
|
||||||
|
## Store a NEW secret (non-interactive, one shot)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh new <path> \
|
||||||
|
--kind <api-key|server|m365|vpn|note|generic> \
|
||||||
|
--name "Human-readable name" [--url https://...] [--tag client] [--tag service] \
|
||||||
|
--set api_key=THE_SECRET [--set username=foo] [--set password=bar]
|
||||||
|
```
|
||||||
|
|
||||||
|
This writes the plaintext template (metadata at top level, every `--set` under `credentials:`),
|
||||||
|
encrypts it in place with `sops`, verifies the round-trip, and tells you to publish. It refuses if
|
||||||
|
the file already exists.
|
||||||
|
|
||||||
|
## Update / add a field on an existing entry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh set <path> --set password=NEW_VALUE
|
||||||
|
```
|
||||||
|
|
||||||
|
Decrypts, merges the field(s) into `credentials:`, re-encrypts, verifies. Use this instead of
|
||||||
|
`vault edit` (which needs an interactive editor).
|
||||||
|
|
||||||
|
## Verify (always, after any write — and before any commit)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh verify <path> # one entry: encrypted + decrypts
|
||||||
|
bash .claude/skills/vault/scripts/vault-helper.sh check [subdir] # scan for ANY plaintext *.sops.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
`check` is the safety net against the plaintext footgun — run `check` over a dir (or the whole
|
||||||
|
vault) before committing if you hand-edited anything.
|
||||||
|
|
||||||
|
## Publish (the last mile — do it yourself)
|
||||||
|
|
||||||
|
The main sync handles the vault repo too:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash .claude/scripts/sync.sh # Phase 6 commits + pushes the vault repo
|
||||||
|
```
|
||||||
|
|
||||||
|
(Equivalent: `cd <vault_path> && git add -A && git commit -m "..." && git push`.) Don't park
|
||||||
|
"you push it" as a task — a clean encrypted entry is routine. Only hand off if `git push` itself
|
||||||
|
fails (auth/conflict). The Windows LF→CRLF warning on the yaml is benign — SOPS integrity is over
|
||||||
|
the `ENC[...]` values, not line endings.
|
||||||
|
|
||||||
|
## Layout & file format
|
||||||
|
|
||||||
|
Vault root subdirs: `clients/<slug>/`, `msp-tools/`, `infrastructure/`, `services/`, `projects/`,
|
||||||
|
`business/`, `ssh-keys/`, `tailscale/`. Put a client credential under `clients/<slug>/`, an MSP app
|
||||||
|
under `msp-tools/`, shared infra under `infrastructure/` or `services/`.
|
||||||
|
|
||||||
|
A decrypted entry looks like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
kind: api-key # api-key | server | m365-tenant | vpn | note | generic
|
||||||
|
name: Human-readable name
|
||||||
|
url: https://... # optional, plaintext metadata
|
||||||
|
status: active
|
||||||
|
tags: [client, service] # plaintext, searchable
|
||||||
|
credentials: # <-- EVERYTHING secret goes here (this whole block is encrypted)
|
||||||
|
api_key: "..."
|
||||||
|
username: "..."
|
||||||
|
password: "..."
|
||||||
|
notes: "" # encrypted too
|
||||||
|
```
|
||||||
|
|
||||||
|
Plaintext metadata (kind/name/url/tags/status and arbitrary non-secret structure like `client:` /
|
||||||
|
`site:`) stays readable so `search` works. Only `credentials`/`notes` (and the other regex keys)
|
||||||
|
are encrypted.
|
||||||
|
|
||||||
|
## 1Password fallback
|
||||||
|
|
||||||
|
The SOPS vault is primary. 1Password is the fallback when a secret isn't in SOPS or for
|
||||||
|
human-shared items — use the `1password` skill / `op` CLI for that. If you store something new and
|
||||||
|
it belongs in the team flow, prefer the SOPS vault so it syncs with the repo.
|
||||||
|
|
||||||
|
## Gotchas (already handled — don't re-discover them)
|
||||||
|
|
||||||
|
- **Interactive `vault edit` / `vault add`** don't work in this harness ($EDITOR). Use
|
||||||
|
`vault-helper.sh set` / `new` instead.
|
||||||
|
- **yq blocked on Windows (WDAC/Device Guard)** — `vault.sh` auto-falls back to a bundled Python
|
||||||
|
YAML parser. Nothing to do.
|
||||||
|
- **`VAULT_ROOT_ENV`** is only a workaround for the *remediation-tool* scripts mis-resolving their
|
||||||
|
root; it is NOT the vault access pattern. For vault work use the two scripts here.
|
||||||
|
- **Encrypted-field regex** is the one real footgun — secrets must be under `credentials:` (or a
|
||||||
|
top-level `password`/`api_key`/`token`/`secret`/`pre_shared_key`/`notes`/`content`). `verify`
|
||||||
|
catches a miss.
|
||||||
|
- The vault repo has its own pre-commit `harness-guard`/hook that warns on plaintext; `check` is
|
||||||
|
your proactive version of the same.
|
||||||
180
.claude/skills/vault/scripts/vault-helper.sh
Normal file
180
.claude/skills/vault/scripts/vault-helper.sh
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# vault-helper.sh — non-interactive safety rails on top of the canonical vault.sh.
|
||||||
|
#
|
||||||
|
# Why this exists: the base vault.sh `add`/`edit` flow is interactive ($EDITOR),
|
||||||
|
# which Claude Code cannot drive — so sessions improvise with raw `sops` and get
|
||||||
|
# it wrong (plaintext fields, wrong paths, forgotten encrypt). This wrapper does
|
||||||
|
# create / set / verify NON-interactively and refuses to leave a secret in
|
||||||
|
# plaintext. Reads delegate to the canonical vault.sh.
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# new <path> --kind <k> [--name "..."] [--url "..."] [--tag T]... --set k=v [--set k=v]...
|
||||||
|
# Create a new ENCRYPTED entry in one shot.
|
||||||
|
# set <path> --set k=v [--set k=v]...
|
||||||
|
# Add/update credential field(s) on an existing entry.
|
||||||
|
# verify <path> Assert one entry is encrypted + round-trips.
|
||||||
|
# check [dir] Scan a dir (default whole vault) for any *.sops.yaml
|
||||||
|
# that is NOT encrypted (catches plaintext leaks).
|
||||||
|
# get <path> [field] Delegate to vault.sh get / get-field (read).
|
||||||
|
# find <query> Delegate to vault.sh search (read).
|
||||||
|
# list [dir] Delegate to vault.sh list (read).
|
||||||
|
# root Print the resolved vault root + how it was found.
|
||||||
|
#
|
||||||
|
# Secrets ALWAYS go under `credentials:` (the .sops.yaml encrypted_regex encrypts
|
||||||
|
# the keys: credentials|password|secret|api_key|token|pre_shared_key|notes|content).
|
||||||
|
# Anything you put OUTSIDE those keys is committed in PLAINTEXT — never do that.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Resolve vault root (the #1 thing sessions get wrong) ──────────────────────
|
||||||
|
# Order: $VAULT_PATH override → the repo we're standing in (correct identity) →
|
||||||
|
# $HOME identity vault_path → $HOME identity claudetools_root → that repo's identity.
|
||||||
|
resolve_vault() {
|
||||||
|
local how p root idf
|
||||||
|
if [[ -n "${VAULT_PATH:-}" && -d "${VAULT_PATH}" ]]; then echo "$VAULT_PATH|env:VAULT_PATH"; return 0; fi
|
||||||
|
if [[ -n "${VAULT_ROOT_ENV:-}" && -d "${VAULT_ROOT_ENV}" ]]; then echo "$VAULT_ROOT_ENV|env:VAULT_ROOT_ENV"; return 0; fi
|
||||||
|
# repo we're standing in (most reliable: uses the in-repo identity.json that has vault_path)
|
||||||
|
if root=$(git rev-parse --show-toplevel 2>/dev/null); then
|
||||||
|
idf="$root/.claude/identity.json"
|
||||||
|
if [[ -f "$idf" ]]; then
|
||||||
|
p=$(_id_field "$idf" vault_path); [[ -n "$p" && -d "$p" ]] && { echo "$p|repo-identity:$idf"; return 0; }
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# home identity vault_path
|
||||||
|
idf="$HOME/.claude/identity.json"
|
||||||
|
if [[ -f "$idf" ]]; then
|
||||||
|
p=$(_id_field "$idf" vault_path); [[ -n "$p" && -d "$p" ]] && { echo "$p|home-identity"; return 0; }
|
||||||
|
# home identity -> claudetools_root -> repo identity vault_path
|
||||||
|
local ctr; ctr=$(_id_field "$idf" claudetools_root)
|
||||||
|
if [[ -n "$ctr" && -f "$ctr/.claude/identity.json" ]]; then
|
||||||
|
p=$(_id_field "$ctr/.claude/identity.json" vault_path); [[ -n "$p" && -d "$p" ]] && { echo "$p|home->repo-identity"; return 0; }
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_id_field() { # <identity.json> <field>
|
||||||
|
local f="$1" k="$2" v=""
|
||||||
|
if command -v jq >/dev/null 2>&1; then v=$(jq -r --arg k "$k" '.[$k] // empty' "$f" 2>/dev/null); fi
|
||||||
|
if [[ -z "$v" ]]; then
|
||||||
|
local fp="$f"; command -v cygpath >/dev/null 2>&1 && fp=$(cygpath -m "$f")
|
||||||
|
local py; for py in py python3 python; do command -v "$py" >/dev/null 2>&1 && { v=$("$py" -c "import json;print(json.load(open(r'$fp')).get('$k',''))" 2>/dev/null) && break; }; done
|
||||||
|
fi
|
||||||
|
echo "$v"
|
||||||
|
}
|
||||||
|
|
||||||
|
VR_RAW=$(resolve_vault) || { echo "[ERROR] Could not resolve the vault root." >&2
|
||||||
|
echo " Set \$VAULT_PATH, or add vault_path to .claude/identity.json, or run from inside the ClaudeTools repo." >&2; exit 3; }
|
||||||
|
VAULT_DIR="${VR_RAW%%|*}"
|
||||||
|
VAULT_HOW="${VR_RAW##*|}"
|
||||||
|
|
||||||
|
VAULT_SH="$VAULT_DIR/scripts/vault.sh" # canonical read tool (delegate to it)
|
||||||
|
|
||||||
|
_py() { local p; for p in py python3 python; do command -v "$p" >/dev/null 2>&1 && { echo "$p"; return 0; }; done; return 1; }
|
||||||
|
PY=$(_py) || { echo "[ERROR] python (py/python3/python) required" >&2; exit 3; }
|
||||||
|
|
||||||
|
abspath() { # path arg -> absolute vault file (.sops.yaml appended if missing)
|
||||||
|
local p="$1"; [[ "$p" == *.sops.yaml ]] || p="$p.sops.yaml"
|
||||||
|
echo "$VAULT_DIR/$p"
|
||||||
|
}
|
||||||
|
|
||||||
|
# assert a file on disk is encrypted (has ENC[ blobs) and round-trips
|
||||||
|
_is_encrypted() { grep -q 'ENC\[AES256_GCM' "$1" 2>/dev/null; }
|
||||||
|
|
||||||
|
cmd_verify() {
|
||||||
|
local f; f=$(abspath "${1:?usage: verify <path>}")
|
||||||
|
[[ -f "$f" ]] || { echo "[ERROR] not found: $f" >&2; exit 1; }
|
||||||
|
if ! _is_encrypted "$f"; then echo "[FAIL] $1 — NO encrypted values found (plaintext?)"; exit 1; fi
|
||||||
|
if ! ( cd "$VAULT_DIR" && sops -d "$f" >/dev/null 2>&1 ); then echo "[FAIL] $1 — encrypted but does not decrypt (key mismatch?)"; exit 1; fi
|
||||||
|
echo "[OK] $1 — encrypted and decrypts cleanly"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_check() {
|
||||||
|
local dir="${1:-}"; local scan="$VAULT_DIR"; [[ -n "$dir" ]] && scan="$VAULT_DIR/$dir"
|
||||||
|
local bad=0 total=0 f
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
total=$((total+1))
|
||||||
|
if ! _is_encrypted "$f"; then echo "[PLAINTEXT] ${f#$VAULT_DIR/}"; bad=$((bad+1)); fi
|
||||||
|
done < <(find "$scan" -name '*.sops.yaml' -type f -print0 2>/dev/null)
|
||||||
|
if [[ $bad -eq 0 ]]; then echo "[OK] $total entr(ies) scanned — all encrypted"; else
|
||||||
|
echo "[CRITICAL] $bad of $total *.sops.yaml file(s) are NOT encrypted — fix before committing"; exit 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_new() {
|
||||||
|
local path="" kind="generic" name="" url="" tags=() sets=()
|
||||||
|
path="${1:?usage: new <path> --kind <k> --set k=v ...}"; shift
|
||||||
|
while [[ $# -gt 0 ]]; do case "$1" in
|
||||||
|
--kind) kind="$2"; shift 2;; --name) name="$2"; shift 2;; --url) url="$2"; shift 2;;
|
||||||
|
--tag) tags+=("$2"); shift 2;; --set) sets+=("$2"); shift 2;;
|
||||||
|
*) echo "[ERROR] unknown arg: $1" >&2; exit 64;; esac; done
|
||||||
|
[[ ${#sets[@]} -gt 0 ]] || { echo "[ERROR] need at least one --set key=value (the secret)" >&2; exit 64; }
|
||||||
|
local f; f=$(abspath "$path")
|
||||||
|
[[ -e "$f" ]] && { echo "[ERROR] already exists: ${f#$VAULT_DIR/} (use 'set' to update)" >&2; exit 1; }
|
||||||
|
mkdir -p "$(dirname "$f")"
|
||||||
|
# Build plaintext YAML with python (safe quoting); secrets go under credentials:
|
||||||
|
KIND="$kind" NAME="$name" URL="$url" TAGS="$(printf '%s\n' "${tags[@]:-}")" SETS="$(printf '%s\n' "${sets[@]}")" \
|
||||||
|
"$PY" - "$f" <<'PY'
|
||||||
|
import os, sys, yaml
|
||||||
|
f=sys.argv[1]
|
||||||
|
doc={"kind":os.environ["KIND"],"name":os.environ.get("NAME","") or ""}
|
||||||
|
if os.environ.get("URL"): doc["url"]=os.environ["URL"]
|
||||||
|
doc["status"]="active"
|
||||||
|
tags=[t for t in os.environ.get("TAGS","").splitlines() if t.strip()]
|
||||||
|
if tags: doc["tags"]=tags
|
||||||
|
creds={}
|
||||||
|
for kv in os.environ.get("SETS","").splitlines():
|
||||||
|
if not kv.strip(): continue
|
||||||
|
k,_,v=kv.partition("="); creds[k.strip()]=v
|
||||||
|
doc["credentials"]=creds
|
||||||
|
doc["notes"]=""
|
||||||
|
with open(f,"w",encoding="utf-8",newline="\n") as fh:
|
||||||
|
yaml.safe_dump(doc,fh,default_flow_style=False,sort_keys=False,allow_unicode=True)
|
||||||
|
PY
|
||||||
|
( cd "$VAULT_DIR" && sops --encrypt --in-place "$f" ) || { echo "[ERROR] sops encrypt failed; removing plaintext" >&2; rm -f "$f"; exit 1; }
|
||||||
|
cmd_verify "$path"
|
||||||
|
echo "[INFO] Created ${f#$VAULT_DIR/}. Publish with: bash .claude/scripts/sync.sh (Phase 6 commits+pushes the vault)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_set() {
|
||||||
|
local path="" sets=()
|
||||||
|
path="${1:?usage: set <path> --set k=v ...}"; shift
|
||||||
|
while [[ $# -gt 0 ]]; do case "$1" in --set) sets+=("$2"); shift 2;; *) echo "[ERROR] unknown arg: $1" >&2; exit 64;; esac; done
|
||||||
|
[[ ${#sets[@]} -gt 0 ]] || { echo "[ERROR] need at least one --set key=value" >&2; exit 64; }
|
||||||
|
local f; f=$(abspath "$path")
|
||||||
|
[[ -f "$f" ]] || { echo "[ERROR] not found: ${f#$VAULT_DIR/} (use 'new' to create)" >&2; exit 1; }
|
||||||
|
local tmp; tmp=$(mktemp)
|
||||||
|
( cd "$VAULT_DIR" && sops -d "$f" ) > "$tmp" 2>/dev/null || { echo "[ERROR] decrypt failed" >&2; rm -f "$tmp"; exit 1; }
|
||||||
|
SETS="$(printf '%s\n' "${sets[@]}")" "$PY" - "$tmp" <<'PY'
|
||||||
|
import os,sys,yaml
|
||||||
|
f=sys.argv[1]
|
||||||
|
doc=yaml.safe_load(open(f,encoding="utf-8")) or {}
|
||||||
|
doc.setdefault("credentials",{})
|
||||||
|
for kv in os.environ["SETS"].splitlines():
|
||||||
|
if not kv.strip(): continue
|
||||||
|
k,_,v=kv.partition("="); doc["credentials"][k.strip()]=v
|
||||||
|
yaml.safe_dump(doc,open(f,"w",encoding="utf-8",newline="\n"),default_flow_style=False,sort_keys=False,allow_unicode=True)
|
||||||
|
PY
|
||||||
|
cp "$tmp" "$f"; rm -f "$tmp"
|
||||||
|
( cd "$VAULT_DIR" && sops --encrypt --in-place "$f" ) || { echo "[ERROR] re-encrypt failed" >&2; exit 1; }
|
||||||
|
cmd_verify "$path"
|
||||||
|
echo "[INFO] Updated ${f#$VAULT_DIR/}. Publish with: bash .claude/scripts/sync.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
new) shift; cmd_new "$@" ;;
|
||||||
|
set) shift; cmd_set "$@" ;;
|
||||||
|
verify) shift; cmd_verify "$@" ;;
|
||||||
|
check) shift; cmd_check "$@" ;;
|
||||||
|
get) shift; if [[ -n "${2:-}" ]]; then bash "$VAULT_SH" get-field "$1" "$2"; else bash "$VAULT_SH" get "$1"; fi ;;
|
||||||
|
find) shift; bash "$VAULT_SH" search "$@" ;;
|
||||||
|
list) shift; bash "$VAULT_SH" list "$@" ;;
|
||||||
|
root) echo "vault root: $VAULT_DIR"; echo "resolved via: $VAULT_HOW" ;;
|
||||||
|
*) cat >&2 <<EOF
|
||||||
|
vault-helper.sh — non-interactive vault ops (rails on top of vault.sh)
|
||||||
|
new <path> --kind <k> [--name ..] [--url ..] [--tag T].. --set k=v [--set k=v]..
|
||||||
|
set <path> --set k=v [--set k=v]..
|
||||||
|
verify <path> check [dir]
|
||||||
|
get <path> [field] find <query> list [dir] root
|
||||||
|
Secrets ALWAYS go under credentials:. Publish changes with: bash .claude/scripts/sync.sh
|
||||||
|
EOF
|
||||||
|
exit 64 ;;
|
||||||
|
esac
|
||||||
28
clients/vons-carstar/cloud/azure.md
Normal file
28
clients/vons-carstar/cloud/azure.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Azure / Cloud Services
|
||||||
|
|
||||||
|
## Azure Subscription
|
||||||
|
- Subscription Name:
|
||||||
|
- Subscription ID:
|
||||||
|
- Resource Group(s):
|
||||||
|
- Region:
|
||||||
|
- Monthly Spend (approx):
|
||||||
|
|
||||||
|
## Virtual Machines
|
||||||
|
| VM Name | Size | OS | IP | Purpose |
|
||||||
|
|---------------|------------|------------|------------|-----------------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
- Virtual Network:
|
||||||
|
- Address Space:
|
||||||
|
- Subnets:
|
||||||
|
- VPN Gateway to On-Prem: Yes/No
|
||||||
|
- ExpressRoute: Yes/No
|
||||||
|
|
||||||
|
## Other Cloud Services
|
||||||
|
<!-- AWS, Google Workspace, third-party SaaS -->
|
||||||
|
| Service | Purpose | Admin URL | Notes |
|
||||||
|
|-----------------|------------------|------------------|-----------------|
|
||||||
|
| | | | |
|
||||||
|
|
||||||
|
## Notes
|
||||||
51
clients/vons-carstar/cloud/m365.md
Normal file
51
clients/vons-carstar/cloud/m365.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Microsoft 365
|
||||||
|
|
||||||
|
## Tenant Info
|
||||||
|
- Tenant Name: Von's Carstar
|
||||||
|
- Tenant ID: 53de51b9-a063-4f46-88ff-7c3468828ed9
|
||||||
|
- Primary Domain: vonscarstar.com
|
||||||
|
- Tenant Type: Managed (not federated)
|
||||||
|
- Admin Portal URL: https://admin.microsoft.com
|
||||||
|
|
||||||
|
## ComputerGuru Management Access
|
||||||
|
- **App suite onboarded:** 2026-06-01 (Tenant Admin consented by Rob; rest auto-consented + roles assigned via `onboard-tenant.sh`).
|
||||||
|
- Tenant Admin → Conditional Access Administrator
|
||||||
|
- Security Investigator → Exchange Administrator
|
||||||
|
- Exchange Operator → Exchange Administrator
|
||||||
|
- User Manager → User Administrator + Authentication Administrator
|
||||||
|
- Defender Add-on → **incomplete** (2 ATP perms failed — no Microsoft Defender for Endpoint license; re-run onboard if MDE is added)
|
||||||
|
- **GDAP:** not required for ongoing access — the app-suite consent above gives durable, **non-expiring** admin access independent of GDAP, so the impending GDAP expiry is a non-issue. Reissue GDAP via the suite/CIPP only if delegated/portal admin is ever specifically needed. (Aside: the CIPP API client `ClaudeCipp2`/`420cb849` currently has no CIPP role — 403 on every endpoint — so CIPP-API automation is unavailable until a role is assigned; not blocking anything here.)
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
<!-- Verified via remediation tool (Graph) 2026-06-01: 10 users total. -->
|
||||||
|
| License Type | Quantity | Assigned | Available |
|
||||||
|
|--------------------------------------|----------|----------|-----------|
|
||||||
|
| Exchange Online (Plan 1) — EXCHANGESTANDARD | 8 | 8 | 0 |
|
||||||
|
|
||||||
|
Total users: **10** (8 licensed; 2 unlicensed — likely shared mailboxes / admin).
|
||||||
|
|
||||||
|
## Exchange Online
|
||||||
|
- Mail Domain(s): vonscarstar.com
|
||||||
|
- MX Record Points To: `vonscarstar-com.mail.protection.outlook.com` (M365 / EOP, pref 0)
|
||||||
|
- **Stale secondary MX:** `mx00.1and1.com` (1&1 IONOS, pref 10) — leftover from a prior host; should be removed to avoid split/misrouted delivery.
|
||||||
|
- SPF Record: <!-- TBD -->
|
||||||
|
- DKIM Enabled: <!-- TBD -->
|
||||||
|
- DMARC Policy: <!-- TBD -->
|
||||||
|
- Shared Mailboxes:
|
||||||
|
- Distribution Groups:
|
||||||
|
- Mail Flow Rules:
|
||||||
|
|
||||||
|
## SharePoint / OneDrive
|
||||||
|
- External Sharing: <!-- TBD -->
|
||||||
|
|
||||||
|
## Entra ID (Azure AD)
|
||||||
|
- MFA Enforced: <!-- TBD -->
|
||||||
|
- Conditional Access Policies: <!-- TBD (Tenant Admin SP now holds CA Admin) -->
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- Defender for Office 365: <!-- TBD -->
|
||||||
|
- MDE (Defender for Endpoint): No (Defender Add-on onboarding failed on missing MDE license)
|
||||||
|
- Audit Log Retention: <!-- TBD -->
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Onboarding + GDAP work: session 2026-06-01. tenants.md row = Onboarded: YES.
|
||||||
19
clients/vons-carstar/issues/log.md
Normal file
19
clients/vons-carstar/issues/log.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Issue Log
|
||||||
|
|
||||||
|
Record past issues and their resolutions here. This helps the AI learn from historical
|
||||||
|
troubleshooting and avoid repeating failed approaches.
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
### [DATE] - [Brief Description]
|
||||||
|
- **Reported By:**
|
||||||
|
- **Severity:** Low / Medium / High / Critical
|
||||||
|
- **Symptoms:**
|
||||||
|
- **Root Cause:**
|
||||||
|
- **Resolution:**
|
||||||
|
- **Time to Resolve:**
|
||||||
|
- **Lessons Learned:**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Add new issues above this line, newest first -->
|
||||||
31
clients/vons-carstar/network/dhcp.md
Normal file
31
clients/vons-carstar/network/dhcp.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# DHCP Configuration
|
||||||
|
|
||||||
|
## DHCP Server
|
||||||
|
- Server Name:
|
||||||
|
- Server IP:
|
||||||
|
- Failover Partner:
|
||||||
|
|
||||||
|
## Scopes
|
||||||
|
|
||||||
|
### Scope - [VLAN Name]
|
||||||
|
- Subnet:
|
||||||
|
- Range Start:
|
||||||
|
- Range End:
|
||||||
|
- Subnet Mask:
|
||||||
|
- Default Gateway:
|
||||||
|
- DNS Servers:
|
||||||
|
- Lease Duration:
|
||||||
|
- Exclusions:
|
||||||
|
|
||||||
|
<!-- Copy the block above for each DHCP scope -->
|
||||||
|
|
||||||
|
## Reservations
|
||||||
|
| Device Name | MAC Address | IP Address | Scope | Notes |
|
||||||
|
|-----------------|-------------------|-----------------|---------------|---------------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
## DHCP Relay
|
||||||
|
- Relay agents configured on:
|
||||||
|
- Helper address:
|
||||||
|
|
||||||
|
## Notes
|
||||||
33
clients/vons-carstar/network/dns.md
Normal file
33
clients/vons-carstar/network/dns.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# DNS Configuration
|
||||||
|
|
||||||
|
## Internal DNS Servers
|
||||||
|
| Server Name | IP Address | Role |
|
||||||
|
|-------------|-----------|-------------------|
|
||||||
|
| | | Primary |
|
||||||
|
| | | Secondary |
|
||||||
|
|
||||||
|
## DNS Forwarders
|
||||||
|
- Forwarder 1:
|
||||||
|
- Forwarder 2:
|
||||||
|
|
||||||
|
## Conditional Forwarders
|
||||||
|
| Domain | Forward To | Purpose |
|
||||||
|
|----------------------|-----------------|-------------------|
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
## Key DNS Records
|
||||||
|
| Record Type | Name | Value | Notes |
|
||||||
|
|-------------|------------------|------------------|------------------|
|
||||||
|
| A | | | |
|
||||||
|
| CNAME | | | |
|
||||||
|
| MX | | | |
|
||||||
|
| TXT | | | |
|
||||||
|
|
||||||
|
## External DNS
|
||||||
|
- Registrar:
|
||||||
|
- Hosted At:
|
||||||
|
- Primary Domain:
|
||||||
|
- Management URL:
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
<!-- Split-brain DNS, special zones, etc. -->
|
||||||
47
clients/vons-carstar/network/firewall.md
Normal file
47
clients/vons-carstar/network/firewall.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Firewall Configuration
|
||||||
|
|
||||||
|
## Device Info
|
||||||
|
- Vendor/Model:
|
||||||
|
- Firmware Version:
|
||||||
|
- Management IP:
|
||||||
|
- Management URL:
|
||||||
|
- HA Pair: Yes/No
|
||||||
|
- License Expiry:
|
||||||
|
|
||||||
|
## Interfaces
|
||||||
|
| Interface | Zone | IP Address | VLAN | Description |
|
||||||
|
|-----------|-----------|-----------------|------|-------------------|
|
||||||
|
| WAN1 | WAN | | | Primary Internet |
|
||||||
|
| WAN2 | WAN | | | Backup Internet |
|
||||||
|
| LAN | LAN | | | |
|
||||||
|
| DMZ | DMZ | | | |
|
||||||
|
|
||||||
|
## NAT Rules
|
||||||
|
| Name | Source | Destination | Port(s) | NAT To |
|
||||||
|
|-------------------|---------------|----------------|-------------|-----------------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
## Key Firewall Policies
|
||||||
|
| Name | Source Zone | Dest Zone | Service | Action | Notes |
|
||||||
|
|-------------------|--------------|---------------|-------------|--------|--------|
|
||||||
|
| | | | | | |
|
||||||
|
|
||||||
|
## VPN
|
||||||
|
### Site-to-Site VPNs
|
||||||
|
| Peer Name | Peer IP | Local Subnet | Remote Subnet | Status |
|
||||||
|
|-------------------|--------------|----------------|---------------|--------|
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
|
### SSL/Client VPN
|
||||||
|
- Enabled: Yes/No
|
||||||
|
- Portal URL:
|
||||||
|
- Auth Method:
|
||||||
|
- IP Pool:
|
||||||
|
- Split Tunnel: Yes/No
|
||||||
|
|
||||||
|
## Content Filtering
|
||||||
|
- Web Filter Profile:
|
||||||
|
- App Control Profile:
|
||||||
|
- DNS Filter:
|
||||||
|
|
||||||
|
## Notes
|
||||||
43
clients/vons-carstar/network/topology.md
Normal file
43
clients/vons-carstar/network/topology.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Network Topology
|
||||||
|
|
||||||
|
## Internet Connection
|
||||||
|
- ISP:
|
||||||
|
- Circuit Type:
|
||||||
|
- Speed (Down/Up):
|
||||||
|
- Public IP:
|
||||||
|
- Gateway:
|
||||||
|
- Modem Model:
|
||||||
|
|
||||||
|
## Core Switch
|
||||||
|
- Model:
|
||||||
|
- IP Address:
|
||||||
|
- Management URL:
|
||||||
|
- Firmware Version:
|
||||||
|
- Location:
|
||||||
|
|
||||||
|
## Additional Switches
|
||||||
|
<!-- Copy this block for each switch -->
|
||||||
|
### Switch - [Name/Location]
|
||||||
|
- Model:
|
||||||
|
- IP Address:
|
||||||
|
- Port Count:
|
||||||
|
- PoE: Yes/No
|
||||||
|
- Uplink To:
|
||||||
|
|
||||||
|
## Wireless
|
||||||
|
- Controller Model:
|
||||||
|
- Controller IP:
|
||||||
|
- Number of APs:
|
||||||
|
- AP Model(s):
|
||||||
|
|
||||||
|
### Access Points
|
||||||
|
<!-- Copy for each AP -->
|
||||||
|
- AP Name:
|
||||||
|
- Location:
|
||||||
|
- IP Address:
|
||||||
|
- Connected Switch/Port:
|
||||||
|
|
||||||
|
## WAN / SD-WAN
|
||||||
|
- SD-WAN Vendor:
|
||||||
|
- Number of Sites:
|
||||||
|
- Hub Site:
|
||||||
21
clients/vons-carstar/network/vlans.md
Normal file
21
clients/vons-carstar/network/vlans.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# VLANs
|
||||||
|
|
||||||
|
## VLAN Table
|
||||||
|
|
||||||
|
| VLAN ID | Name | Subnet | Gateway | DHCP Scope | Purpose |
|
||||||
|
|---------|---------------|-----------------|-----------------|------------------|------------------------|
|
||||||
|
| 1 | Default | | | | |
|
||||||
|
| 10 | Management | | | | Network devices |
|
||||||
|
| 20 | Servers | | | | Server infrastructure |
|
||||||
|
| 30 | Workstations | | | | End user devices |
|
||||||
|
| 40 | VoIP | | | | Phone system |
|
||||||
|
| 50 | WiFi-Corp | | | | Corporate wireless |
|
||||||
|
| 60 | WiFi-Guest | | | | Guest wireless |
|
||||||
|
| 100 | Security | | | | Cameras / access ctrl |
|
||||||
|
|
||||||
|
## Inter-VLAN Routing
|
||||||
|
- Performed by:
|
||||||
|
- Routing device IP:
|
||||||
|
|
||||||
|
## VLAN Notes
|
||||||
|
<!-- Any special considerations, trunk ports, tagged/untagged config -->
|
||||||
34
clients/vons-carstar/overview.md
Normal file
34
clients/vons-carstar/overview.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Client Overview
|
||||||
|
|
||||||
|
## Company Name
|
||||||
|
Von's Carstar (Carstar collision-repair / auto body franchise)
|
||||||
|
|
||||||
|
## Primary Contact
|
||||||
|
- Name: <!-- TBD -->
|
||||||
|
- Phone:
|
||||||
|
- Email:
|
||||||
|
|
||||||
|
## IT Contact
|
||||||
|
- Name: Rob <!-- did the M365 Tenant Admin consent 2026-06-01; full name/contact TBD -->
|
||||||
|
- Phone:
|
||||||
|
- Email:
|
||||||
|
|
||||||
|
## Contract Details
|
||||||
|
- Service Level: <!-- TBD -->
|
||||||
|
- Hours Covered:
|
||||||
|
- Contract Renewal Date:
|
||||||
|
|
||||||
|
## Environment Summary
|
||||||
|
- Total Users: 10 (8 licensed — Exchange Online Plan 1)
|
||||||
|
- Total Locations:
|
||||||
|
- Domain Name: vonscarstar.com
|
||||||
|
- Primary Site Address:
|
||||||
|
- RMM Agent Count: <!-- not yet enrolled in GuruRMM -->
|
||||||
|
- Workstation Count:
|
||||||
|
- Server Count:
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Microsoft 365 client. Tenant `53de51b9-a063-4f46-88ff-7c3468828ed9`.
|
||||||
|
- **Onboarded to the ComputerGuru M365 app suite 2026-06-01** (Tenant Admin consented by Rob; full suite roles assigned — see `cloud/m365.md`).
|
||||||
|
- **GDAP not required** — the app-suite consent provides durable, non-expiring admin access, so the impending GDAP expiry is moot. Reissue via the suite only if delegated/portal admin is ever specifically needed.
|
||||||
|
- User/license/site detail still TBD — pull via the remediation tool now that the tenant is onboarded.
|
||||||
34
clients/vons-carstar/rmm/rmm.md
Normal file
34
clients/vons-carstar/rmm/rmm.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# RMM / Monitoring
|
||||||
|
|
||||||
|
## RMM Solution
|
||||||
|
- Product:
|
||||||
|
- Console URL:
|
||||||
|
- Agent Version:
|
||||||
|
|
||||||
|
## Agent Deployment
|
||||||
|
- Total Devices:
|
||||||
|
- Servers Monitored:
|
||||||
|
- Workstations Monitored:
|
||||||
|
- Network Devices Monitored:
|
||||||
|
|
||||||
|
## Monitoring Policies
|
||||||
|
| Policy Name | Applies To | Alert Condition | Action |
|
||||||
|
|-------------------|----------------|-------------------------|---------------|
|
||||||
|
| Disk Space | All Servers | < 10% free | Alert + Ticket|
|
||||||
|
| CPU | All Servers | > 90% for 15 min | Alert |
|
||||||
|
| Service Monitor | All Servers | | |
|
||||||
|
| Backup Monitor | | | |
|
||||||
|
| Offline Alert | All Agents | Offline > 30 min | Alert |
|
||||||
|
|
||||||
|
## Patch Management
|
||||||
|
- Patch Policy:
|
||||||
|
- Patch Window:
|
||||||
|
- Auto-approve: Yes/No
|
||||||
|
- Exclusions:
|
||||||
|
|
||||||
|
## Scripting / Automation
|
||||||
|
| Script Name | Schedule | Purpose |
|
||||||
|
|---------------------|-------------|--------------------------|
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
## Notes
|
||||||
26
clients/vons-carstar/security/antivirus.md
Normal file
26
clients/vons-carstar/security/antivirus.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Endpoint Security / Antivirus
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
- Product:
|
||||||
|
- Console URL:
|
||||||
|
- License Count:
|
||||||
|
- License Expiry:
|
||||||
|
- Managed By:
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
- Real-time Protection: Yes/No
|
||||||
|
- Scheduled Scans: (frequency)
|
||||||
|
- Exclusions:
|
||||||
|
|
||||||
|
## Deployment Status
|
||||||
|
- Total Endpoints:
|
||||||
|
- Protected:
|
||||||
|
- Missing Agent:
|
||||||
|
- Out of Date:
|
||||||
|
|
||||||
|
## EDR / XDR
|
||||||
|
- EDR Enabled: Yes/No
|
||||||
|
- Product:
|
||||||
|
- Console URL:
|
||||||
|
|
||||||
|
## Notes
|
||||||
34
clients/vons-carstar/security/backup.md
Normal file
34
clients/vons-carstar/security/backup.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Backup and Disaster Recovery
|
||||||
|
|
||||||
|
## Backup Solution
|
||||||
|
- Product:
|
||||||
|
- Console URL:
|
||||||
|
- License/Subscription:
|
||||||
|
|
||||||
|
## Backup Targets
|
||||||
|
| Target Name | Type | Location | Capacity | Encrypted |
|
||||||
|
|----------------|----------------|-----------------|--------------|-----------|
|
||||||
|
| | Local NAS | | | Yes/No |
|
||||||
|
| | Cloud | | | Yes/No |
|
||||||
|
| | Offsite | | | Yes/No |
|
||||||
|
|
||||||
|
## Backup Jobs
|
||||||
|
| Job Name | Source | Target | Schedule | Retention | Status |
|
||||||
|
|-----------------|-------------------|------------|---------------|-------------|--------|
|
||||||
|
| | | | | | |
|
||||||
|
|
||||||
|
## M365 Backup
|
||||||
|
- M365 Backup Product:
|
||||||
|
- Exchange Backed Up: Yes/No
|
||||||
|
- SharePoint Backed Up: Yes/No
|
||||||
|
- OneDrive Backed Up: Yes/No
|
||||||
|
- Teams Backed Up: Yes/No
|
||||||
|
|
||||||
|
## Disaster Recovery Plan
|
||||||
|
- RTO Target:
|
||||||
|
- RPO Target:
|
||||||
|
- DR Site:
|
||||||
|
- Last DR Test Date:
|
||||||
|
- DR Test Result:
|
||||||
|
|
||||||
|
## Notes
|
||||||
49
clients/vons-carstar/servers/server_template.md
Normal file
49
clients/vons-carstar/servers/server_template.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Server: [SERVER NAME]
|
||||||
|
|
||||||
|
## General Info
|
||||||
|
- Hostname:
|
||||||
|
- IP Address:
|
||||||
|
- OS:
|
||||||
|
- OS Version:
|
||||||
|
- Physical / Virtual:
|
||||||
|
- Host (if virtual):
|
||||||
|
- Location:
|
||||||
|
- Last Patched:
|
||||||
|
|
||||||
|
## Hardware (if physical)
|
||||||
|
- Make/Model:
|
||||||
|
- CPU:
|
||||||
|
- RAM:
|
||||||
|
- Storage:
|
||||||
|
- Warranty Expiry:
|
||||||
|
|
||||||
|
## Roles and Services
|
||||||
|
<!-- List all roles this server performs -->
|
||||||
|
- [ ] Domain Controller
|
||||||
|
- [ ] DNS Server
|
||||||
|
- [ ] DHCP Server
|
||||||
|
- [ ] File Server
|
||||||
|
- [ ] Print Server
|
||||||
|
- [ ] Application Server
|
||||||
|
- [ ] Database Server
|
||||||
|
- [ ] Backup Target
|
||||||
|
- [ ] RDS / Terminal Server
|
||||||
|
- [ ] Hyper-V Host
|
||||||
|
|
||||||
|
## Shares (if file server)
|
||||||
|
| Share Name | Path | Permissions Group | Notes |
|
||||||
|
|---------------|-------------------|---------------------|----------------|
|
||||||
|
| | | | |
|
||||||
|
|
||||||
|
## Applications Installed
|
||||||
|
| Application | Version | Purpose | License |
|
||||||
|
|-------------------|------------|----------------------|---------------|
|
||||||
|
| | | | |
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
- Backup Method:
|
||||||
|
- Backup Schedule:
|
||||||
|
- Backup Target:
|
||||||
|
- Last Verified Restore:
|
||||||
|
|
||||||
|
## Notes
|
||||||
Reference in New Issue
Block a user