Files
claudetools/.claude/skills/onboard365/scripts/onboard365.sh
Mike Swanson da87d314c5 harness: fleet-wide functional-error + correction + friction logging
Add .claude/scripts/log-skill-error.sh — the canonical agent error log helper
(writes errorlog.md in DATE | MACHINE | skill | [type] error format, soft-fails).
Three categories: execution failures (default), user corrections (--correction),
and preventable self-inflicted friction (--friction; cite ref= when it repeats a
documented gotcha). Goal: stop paying tokens twice for the same avoidable mistake.

- CLAUDE.md: make logging mandatory for all skills + corrections + friction.
- skill-creator: new skills must wire in the helper (guidance + checklist).
- Retrofit every skill script's genuine failure branches to call the helper
  (b2/bitdefender/mailprotector/packetdial/coord python CLIs; remediation-tool
  + onboard365 bash; vault, rmm-auth, post-bot-alert, agy, grok, 1password,
  run-onboarding-diagnostic). Handled conditions + self-tests left alone.
- errorlog.md: broaden header to cover skills + harness + corrections; seed this
  session's corrections (INKY, Mail.Send token-audience, omnibox-strictness) and
  friction (git-bash /tmp, env-persistence, argv-limit, PowerShell var-case).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:40:25 -07:00

116 lines
4.5 KiB
Bash

#!/usr/bin/env bash
# Onboard365 — single-consent onboarding of a customer M365 tenant to the
# ComputerGuru remediation app suite.
#
# The customer Global Admin consents ONCE to the "ComputerGuru Tenant Admin" app.
# Everything else (the other SPs, their Graph/EXO/Defender permissions, and the
# Entra directory roles) is provisioned programmatically by the reused
# remediation-tool/onboard-tenant.sh — no further customer clicks.
#
# Usage:
# onboard365.sh <domain|tenant-id> # smart: link if not consented, else provision
# onboard365.sh link <domain|tenant-id> # print the ONE consent URL + customer steps
# onboard365.sh status <domain|tenant-id> # dry-run: show current consent/role state
# onboard365.sh provision <domain|tenant-id> # after consent: provision all apps + roles
#
# Exit codes mirror onboard-tenant.sh for provision/status (0 ok, 2 not consented,
# 10 partial). link always exits 0.
set -euo pipefail
TENANT_ADMIN_APPID="709e6eed-0711-4875-9c44-2d3518c47063"
CONSENT_BASE="https://login.microsoftonline.com"
CONSENT_REDIRECT="https://azcomputerguru.com"
__ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)}"
# ── Locate the reused remediation-tool scripts ────────────────────────────────
# Prefer the applied global copy (stable path on every fleet machine); fall back
# to the repo copy via identity.json.claudetools_root.
find_rtool() {
local cands=("$HOME/.claude/skills/remediation-tool/scripts")
local idf="$HOME/.claude/identity.json"
if [[ -f "$idf" ]] && command -v jq >/dev/null 2>&1; then
local root
root=$(jq -r '.claudetools_root // empty' "$idf" 2>/dev/null || true)
[[ -n "$root" ]] && cands+=("$root/.claude/skills/remediation-tool/scripts")
fi
local c
for c in "${cands[@]}"; do
[[ -f "$c/onboard-tenant.sh" ]] && { echo "$c"; return 0; }
done
return 1
}
RT="$(find_rtool)" || {
echo "[ERROR] remediation-tool scripts not found." >&2
echo " Expected: \$HOME/.claude/skills/remediation-tool/scripts/onboard-tenant.sh" >&2
echo " Run a repo sync, or check identity.json.claudetools_root." >&2
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "onboard365" "onboard365: remediation-tool scripts not found (onboard-tenant.sh missing on this machine)" >/dev/null 2>&1 || true
exit 3
}
# ── Parse args (allow a bare domain as smart mode) ────────────────────────────
SUB="${1:-}"
[[ -z "$SUB" ]] && { echo "usage: onboard365.sh <link|status|provision|auto> <domain|tenant-id>" >&2; exit 64; }
case "$SUB" in
link|status|provision|auto)
TARGET="${2:-}"
[[ -z "$TARGET" ]] && { echo "usage: onboard365.sh $SUB <domain|tenant-id>" >&2; exit 64; }
;;
*)
TARGET="$SUB"; SUB="auto"
;;
esac
resolve() { "$RT/resolve-tenant.sh" "$1" 2>/dev/null || echo "$1"; }
print_link() {
local t="$1"
cat <<EOF
============================================================
Onboard365 — Single-Consent Link
Customer: $TARGET
Tenant: $t
============================================================
Send the ONE link below to the customer's GLOBAL ADMIN. They sign
in and click Accept. That single consent is all they do — ACG
provisions everything else automatically.
${CONSENT_BASE}/${t}/adminconsent?client_id=${TENANT_ADMIN_APPID}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent
App they will see: "ComputerGuru Tenant Admin"
(Customer instructions template: references/customer-consent-instructions.md)
After they confirm Accept, run:
onboard365.sh provision $TARGET
============================================================
EOF
}
# returns 0 if Tenant Admin token acquires (consented), 1 otherwise
is_consented() { "$RT/get-token.sh" "$1" tenant-admin >/dev/null 2>/tmp/onboard365-tok.err; }
case "$SUB" in
link)
print_link "$(resolve "$TARGET")"
;;
status)
echo "[INFO] Onboard365 status (dry-run) for $TARGET — no changes will be made"
"$RT/onboard-tenant.sh" "$TARGET" --dry-run
;;
provision)
echo "[INFO] Onboard365 provisioning suite for $TARGET (single-consent model)"
"$RT/onboard-tenant.sh" "$TARGET"
;;
auto)
T="$(resolve "$TARGET")"
if is_consented "$T"; then
echo "[INFO] Tenant Admin already consented for $TARGET — provisioning the suite..."
"$RT/onboard-tenant.sh" "$TARGET"
else
echo "[INFO] Tenant Admin not yet consented for $TARGET — generating the single consent link."
print_link "$T"
exit 2
fi
;;
esac