Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion claude-code/hooks/mdm/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1718,7 +1718,7 @@ def _is_reparse_point(path: Path) -> bool:


def _claude_desktop_support_dirs(home: Path) -> List[Path]:
"""Claude Desktop app support dir(s) for a home. Team/SSO desktop sessions
r"""Claude Desktop app support dir(s) for a home. Team/SSO desktop sessions
cache the active account's oauthAccount under local-agent-mode-sessions/ here.

Taken from unbound.py, keyed off `home` instead of Path.home()/APPDATA: MDM
Expand Down
18 changes: 8 additions & 10 deletions mdm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Runs all five MDM setup steps for an admin device enrollment in one shot:
2. **Cursor** MDM setup
3. **Codex** MDM setup
4. **GitHub Copilot** MDM setup
5. **Coding-discovery** scan (separate repo, separate API key)
5. **Coding-discovery** scan (separate repo)

Steps 1–4 use `--api-key` (the admin MDM key). Step 5 uses `--discovery-key` (a separate discovery-specific key — the two are different credentials and the backend distinguishes them).
Steps 1–4 use `--api-key` (the admin MDM key). The discovery scan runs with the **device owner's** key, which onboard.py resolves by exchanging the admin key + hardware serial via `/api/v1/automations/mdm/get_application_api_key/` (the same exchange the per-tool MDM scripts do), so the scan is attributed to the owner rather than the admin. If that exchange fails the Discovery step is reported failed; it never falls back to the admin key. `--discovery-key` / `-DiscoveryKey` is still accepted (deprecated) and, when given, skips the exchange and scans with that key.

Each step runs in its own subprocess; a failure in one does not abort the others. A summary at the end lists which steps succeeded and which failed.

Expand All @@ -17,7 +17,7 @@ Each step runs in its own subprocess; a failure in one does not abort the others
MDM setup requires Administrator privileges. Download and execute the PowerShell wrapper:

```powershell
Invoke-WebRequest -Uri 'https://getunbound.ai/setup/mdm/windows/onboard' -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_API_KEY -DiscoveryKey YOUR_DISCOVERY_KEY
Invoke-WebRequest -Uri 'https://getunbound.ai/setup/mdm/windows/onboard' -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_API_KEY
```

The wrapper automatically:
Expand All @@ -29,13 +29,13 @@ The wrapper automatically:
Optional parameters:
```powershell
# Tenant deployment URLs
.\onboard.ps1 -ApiKey YOUR_KEY -DiscoveryKey YOUR_KEY -BackendUrl https://backend.example.com -GatewayUrl https://api.example.com
.\onboard.ps1 -ApiKey YOUR_KEY -BackendUrl https://backend.example.com -GatewayUrl https://api.example.com

# Enable backfill of historical transcripts (opt-in)
.\onboard.ps1 -ApiKey YOUR_KEY -DiscoveryKey YOUR_KEY -Backfill
.\onboard.ps1 -ApiKey YOUR_KEY -Backfill

# Claude Code only: install the hook script, leave managed-settings.json alone
.\onboard.ps1 -ApiKey YOUR_KEY -DiscoveryKey YOUR_KEY -SkipManagedSettings
.\onboard.ps1 -ApiKey YOUR_KEY -SkipManagedSettings
```

### Clearing Setup (Windows)
Expand All @@ -50,14 +50,12 @@ MDM setup requires root privileges. Pass the script to `python3 -c` via command

```bash
sudo python3 -c "$(curl -fsSL https://getunbound.ai/setup/mdm/onboard)" \
--api-key YOUR_ADMIN_API_KEY \
--discovery-key YOUR_DISCOVERY_KEY
--api-key YOUR_ADMIN_API_KEY
```

```bash
sudo python3 -c "$(curl -fsSL https://raw.githubusercontent.com/websentry-ai/setup/refs/heads/main/mdm/onboard.py)" \
--api-key YOUR_ADMIN_API_KEY \
--discovery-key YOUR_DISCOVERY_KEY
--api-key YOUR_ADMIN_API_KEY
```

Optional overrides for tenant deployments: `--backend-url <url>`, `--gateway-url <url>` (defaults: `https://backend.getunbound.ai`, `https://api.getunbound.ai`). The `--backend-url` value also becomes the discovery scan's `--domain`.
Expand Down
45 changes: 24 additions & 21 deletions mdm/onboard.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
The MDM admin API key (required unless -Clear is specified)

.PARAMETER DiscoveryKey
The discovery-specific API key, separate from ApiKey (required unless -Clear is specified)
Deprecated, optional: scan with this key instead of the device owner's key that
onboard.py resolves from ApiKey + the hardware serial.

.PARAMETER BackendUrl
Backend URL override for tenant deployments (default: https://backend.getunbound.ai)
Expand All @@ -49,20 +50,20 @@
Remove MDM configuration for all four tools (no discovery scan, no backfill)

.EXAMPLE
# Standard onboarding with both keys
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/onboard.ps1" -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_KEY -DiscoveryKey YOUR_DISCOVERY_KEY
# Standard onboarding
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/windows/onboard" -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_KEY

.EXAMPLE
# With backfill of historical transcripts (opt-in)
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/onboard.ps1" -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_KEY -DiscoveryKey YOUR_DISCOVERY_KEY -Backfill
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/windows/onboard" -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_KEY -Backfill

.EXAMPLE
# Tenant deployment with custom URLs
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/onboard.ps1" -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_KEY -DiscoveryKey YOUR_DISCOVERY_KEY -BackendUrl "https://backend.example.com" -GatewayUrl "https://api.example.com"
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/windows/onboard" -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_KEY -BackendUrl "https://backend.example.com" -GatewayUrl "https://api.example.com"

.EXAMPLE
# Clear MDM setup
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/onboard.ps1" -OutFile onboard.ps1; .\onboard.ps1 -Clear
Invoke-WebRequest -Uri "https://getunbound.ai/setup/mdm/windows/onboard" -OutFile onboard.ps1; .\onboard.ps1 -Clear

.NOTES
Requires: Python 3, Administrator privileges
Expand Down Expand Up @@ -141,11 +142,7 @@ function Main {
# Validate parameters (unless -Clear is specified)
if (-not $Clear) {
if ([string]::IsNullOrWhiteSpace($ApiKey)) {
Exit-WithError "-ApiKey is required. Usage: & ([scriptblock]::Create((iwr 'https://getunbound.ai/setup/mdm/onboard.ps1' -UseBasicParsing).Content)) -ApiKey YOUR_KEY -DiscoveryKey YOUR_KEY"
}

if ([string]::IsNullOrWhiteSpace($DiscoveryKey)) {
Exit-WithError "-DiscoveryKey is required. Usage: & ([scriptblock]::Create((iwr 'https://getunbound.ai/setup/mdm/onboard.ps1' -UseBasicParsing).Content)) -ApiKey YOUR_KEY -DiscoveryKey YOUR_KEY"
Exit-WithError "-ApiKey is required. Usage: Invoke-WebRequest -Uri 'https://getunbound.ai/setup/mdm/windows/onboard' -OutFile onboard.ps1; .\onboard.ps1 -ApiKey YOUR_ADMIN_API_KEY"
}
}

Expand Down Expand Up @@ -174,8 +171,12 @@ function Main {
} else {
$pythonArgs += "--api-key"
$pythonArgs += $ApiKey
$pythonArgs += "--discovery-key"
$pythonArgs += $DiscoveryKey
# -DiscoveryKey is deprecated and only forwarded when given. Otherwise
# onboard.py resolves the device owner's key from ApiKey + the serial.
if (-not [string]::IsNullOrWhiteSpace($DiscoveryKey)) {
$pythonArgs += "--discovery-key"
$pythonArgs += $DiscoveryKey
}
}

# URL overrides apply to both normal and clear modes
Expand Down Expand Up @@ -204,9 +205,12 @@ function Main {
$pythonArgs += "--skip-managed-settings"
}

# Execute the Python script and capture exit code
# Execute the Python script. Its stdout flows straight to the host
# because Main is invoked bare at the entry point; the exit code is
# stashed script-scoped instead of returned. (`$x = Main` would capture
# the Python output into $x and `exit` on that array reports 0.)
& $pythonCmd @pythonArgs
$exitCode = $LASTEXITCODE
$script:pythonExitCode = $LASTEXITCODE

} finally {
# Clean up temporary files
Expand All @@ -217,13 +221,12 @@ function Main {
Remove-Item $tempPyFile -ErrorAction SilentlyContinue
}
}

# Return the exit code
return $exitCode
}

# Entry point - capture exit code from Main
$exitCode = Main
# Entry point. Defaults to failure so anything that stops Main before Python
# runs can never report success to the caller (e.g. Intune remediation).
$script:pythonExitCode = 1
Main

# Self-destruct: Remove this script file after execution completes
# This allows users to run without manual cleanup: Invoke-WebRequest ... -OutFile onboard.ps1; .\onboard.ps1 -ApiKey ...
Expand All @@ -232,4 +235,4 @@ if ($MyInvocation.MyCommand.Path) {
}

# Exit with the Python script's exit code
exit $exitCode
exit $script:pythonExitCode
Loading