obot-sentry is a command-line tool designed to be used by MDMs for device scanning and agent hook configuration.
It enrolls each machine with an Obot server as a single device shared by all of the machine's users, submits device scan manifests attributed per user, and provides managed local-agent audit hooks. Inventory collection (MCP servers, skills, plugins) is currently stubbed out — scans ship an empty manifest so the enrollment and submission flow works end to end while the scan engine lands separately.
- Machine-wide scheduler entries. The MDM installer registers a scan task that the OS runs as each signed-in user: on Windows it uses a
BUILTIN\Usersgroup principal (logon + a 10-minute poll). Each run is a plainobot-sentry scan --submit --quietin the user's own session, so attribution, paths, and permissions are native — no privileged fan-out and no MDM per-user scheduling. The poll is cheap: obot-sentry throttles real submissions to the MDM-configuredScanIntervalMinutes(default 60, clamped to 15–1440) against its per-user scan state, so admins retune the cadence from the MDM alone (scan --submit --forcebypasses the throttle for a one-off manual submission). Users who aren't signed in aren't scanned; their inventory can't change while they're signed out, so nothing is missed beyond first-report latency for accounts that haven't signed in since install. The installer also registers an elevated SYSTEM task that runsobot-sentry hook-installat logon and hourly. Becausehook-installtargets only the active console user, it converges at each sign-in (after a one-minute settle) plus the install kick, with the hourly run picking up later drift and console-user switches. - Enrollment. Configuration (server URL + an
ode1-...enrollment credential) is pushed by the MDM — via registry values on Windows, a managed-preferences profile on macOS. On the machine's first scan — by whichever user runs first — obot-sentry generates a shared Ed25519 identity key in the machine-scoped data dir (%PROGRAMDATA%\obot\obot-sentryon Windows,/Library/Application Support/obot/obot-sentryon macOS; both are prepared user-writable by the installer, with a per-user fallback when unavailable) and enrolls the public key viaPOST /api/mdm/enroll(trust-on-first-use). The device ID derives from the machine ID + key fingerprint, so all users present one device — and a lost key simply mints a fresh device ID instead of a TOFU conflict. Each user's first scan re-enrolls the same identity, which is an idempotent update server-side. - Submission. Every submission is authenticated with a short-lived self-signed JWT (
aud=obot/device) verified server-side against the enrolled key; scans land viaPOST /api/devices/scans, attributed to the submitting user by the manifest'susername. Local-agent audit logs land viaPOST /api/local-agent-audit-logs, with the server stamping authoritative device attribution from the JWT. - Local-agent audit hooks. Managed hook configuration invokes the hidden
obot-sentry audit submitcommand for supported local agents. The hook parser normalizes terminal tool-call events, submits them fail-open, and writes only warnings to stderr when enrollment or submission is unavailable so agent execution is not blocked. - Tool-call enforcement hooks. With enforcement enabled in Obot, managed pre-tool hook configuration invokes the hidden
obot-sentry enforcecommand for Claude Code, Codex, Cursor, and Kiro. It is the opposite of the audit hook in every important way: synchronous, fail-closed, and stdout is the hook protocol channel rather than something that must stay empty. See Tool-call enforcement. - Audit spool. Transient audit-log submission failures are stored in an encrypted per-user spool under the obot-sentry cache directory and replayed after a later successful live submit. Server-side client errors are discarded instead of retried.
- Scan state + logs. Every scan run updates
scan.json(last scan/submit times, status, last error) and appends a JSON record toscan-logs/— timestamp-sortable filenames, pruned by age and size — in obot-sentry's per-user cache dir (%LOCALAPPDATA%\obot\obot-sentryon Windows,~/Library/Caches/obot/obot-sentryon macOS). Support and MDM freshness checks read these; recording problems never fail a scan.
obot-sentry scan # build + print the manifest (add --submit to enroll + upload)
# --submit --force ignores the scan interval and submits now
obot-sentry enroll # explicit enrollment, for verifying a configuration
obot-sentry hook-install # install managed local-agent hooks (root/Administrator)
# --enforce also installs the tool-call enforcement hooks
obot-sentry hook-uninstall # remove all marker-owned hooks (root/Administrator)
obot-sentry version
obot-sentry audit submit and obot-sentry enforce are hidden because they are intended for managed local-agent hook configurations, not direct operator use. Both authenticate as the enrolled device. obot-sentry enforce resolve is the one part of the enforcement command meant to be run by hand — see Tool-call enforcement.
Resolution order per key: flags > env > MDM store.
| Key | Flag | Env | Windows registry / macOS managed prefs |
|---|---|---|---|
| Server URL | --server-url |
OBOT_SENTRY_SERVER_URL |
ServerURL |
| Enrollment key | --enrollment-key |
OBOT_SENTRY_ENROLLMENT_KEY |
EnrollmentKey |
| Scan interval (minutes) | --scan-interval-minutes |
OBOT_SENTRY_SCAN_INTERVAL_MINUTES |
ScanIntervalMinutes |
| Enforcement enabled | hook-install --enforce |
OBOT_SENTRY_ENFORCEMENT_ENABLED |
EnforcementEnabled |
EnforcementEnabled is a boolean and every store spells it differently, so all
of these are accepted: a real plist boolean, 1/0 from a REG_DWORD,
true/false from a REG_SZ, and yes/no/on/off. A value that is not
recognized is treated as absent rather than as false, so a typo falls through to
the next layer instead of quietly disabling the control. Absent everywhere means
enforcement is off.
On Windows the registry value is what turns enforcement on for a device that is
already installed: the hook scheduled task registers a fixed hook-install
argument list, so the MSI property (ENFORCEMENT=1) or a pushed REG_DWORD is
the only way in.
MDM stores: HKLM\SOFTWARE\Obot\obot-sentry on Windows; /Library/Managed Preferences/ai.obot.obot-sentry.plist (fallback /Library/Preferences/...) on macOS.
hook-install writes the final MDM-owned executable path into every hook:
/usr/local/bin/obot-sentry on macOS and
C:\Program Files\Obot\obot-sentry\obot-sentry.exe on Windows. It fails before
changing hook files if the package has not installed a usable executable at
that location.
hook-install --enforce adds a pre-tool hook to Claude Code, Codex, Cursor,
and Kiro, on top of the post-tool audit hooks. Before each tool call runs, the hook
resolves what the call targets, asks obot for a verdict, and blocks the call
unless the answer is an explicit allow.
| Agent | File | Events added |
|---|---|---|
| Claude Code | ~/.claude/settings.json |
PreToolUse |
| Codex | /etc/codex/requirements.toml (macOS), %ProgramData%\OpenAI\Codex\requirements.toml |
PreToolUse |
| Cursor | /Library/Application Support/Cursor/hooks.json (macOS), %ProgramData%\Cursor\hooks.json |
beforeMCPExecution, preToolUse |
| Kiro | ~/.kiro/hooks/obot-sentry.json |
PreToolUse |
Visual Studio Code is not supported for enforcement.
Anything other than an explicit allow blocks the call: obot unreachable, slow
(the whole check is budgeted at 5 seconds), or returning something unparseable; a
device that is not enrolled or has no server configured; a payload the hook cannot
read; an MCP server it cannot identify. A device whose enrollment never
completed blocks every tool call in every enforced agent until enrollment succeeds.
hook-install provisions the machine identity before writing any hook file, so
the normal MDM path cannot land there — but a wiped identity directory or a
revoked enrollment key can, and it will be loud.
Turning enforcement off in obot stops the blocking immediately, for all devices: the server allows every call when enforcement is disabled, and logs nothing.
A permitted call is answered by withholding the denial, not by approving the
call — zero bytes for Claude Code, Codex, and Kiro, and Cursor's
{"permission":"allow"}, which means "this hook does not object". Your agents' own approval prompts still
apply to everything enforcement allows. The allowlist and an agent's permission
model are separate controls and this deliberately does not collapse them.
An MCP server can be allowlisted by URL, by its npx/uvx package, or —
for a claude.ai account connector — by display name.
A stdio server started from a local executable path (/opt/homebrew/bin/thing,
./node_modules/.bin/thing) presents none of those: no URL, no registry package,
nothing an allowlist entry can name. Such a call is reported as unidentified and
blocked by design. If a decision log is full of unresolvable stdio servers,
this is why, and the fix is to run those servers from a package or a URL rather
than to look for an allowlist entry that can match them.
Naming caveats that change what an allowlist entry has to say:
- Codex reports server names with punctuation folded to underscores. A config
key of
probe-npx-stdioarrives asprobe_npx_stdio. The device matches it back to the key and reports the key, so copy the name from the decision log rather than from the tool name. - Cursor display names may carry a scope prefix (
user-probe-uvx-stdio), and a name declared in more than one Cursor scope is reported as unidentified — the payload cannot say which one ran. Rename one of them. - Kiro tool names are lowercased (
mcp_<server>_<tool>). The device matches the server back to its configuration key, but the tool half stays lowercased (createissue), so copy tool names from the decision log. A tool name that two configured servers could both have produced (mcp_github_enterprise_xwith serversgithubandgithub_enterprise) is reported as unidentified.
The device resolves a stdio command to a package identity itself, strictly: it
either produces an exact (source, name, version) or reports the call as
unidentified. The command must be exactly npx or uvx (plus .cmd/.exe on
Windows); any path separator, and any other runner, is unidentified.
Flags that change what code runs are refused — npx -c, --registry,
--node-options, uvx --with, --index-url, --from git+…, package extras —
because the package name would no longer identify the code. Flags that cannot
change identity are accepted: -y, -q, --offline, npx -p/--package,
uvx --from, and uvx -p/--python (note -p means --package to npx and
--python to uvx).
The same rule applies to environment variables. An MCP entry is unidentified
when its env redirects the runner or package source or injects code, including
PATH, NODE_OPTIONS, NPM_CONFIG_*, COREPACK_*, UV_*, PYTHONPATH, and
dynamic-loader variables. Sensitive variables that are inputs to the server,
such as GITHUB_TOKEN, are accepted and never sent in the decision request.
Identity-changing variables inherited by obot-sentry are refused as well. An
ordinary inherited PATH is accepted, including one that finds npx or uvx
under Homebrew or another user-local installation; runner ownership is not an
allowlist dimension.
Names are canonicalized the way each registry does, on the device and again when
an allowlist entry is saved, so the two agree: PyPI by PEP 503
(Mcp_Server.Git → mcp-server-git), npm by lowercasing with the scope
preserved (@Scope/Pkg → @scope/pkg). Versions are reported verbatim, and an
allowlist entry with no version matches any version.
| Command | Resolves to |
|---|---|
npx -y @modelcontextprotocol/server-github |
npm @modelcontextprotocol/server-github, any version |
npx -y linear-mcp@1.2.3 |
npm linear-mcp 1.2.3 |
npx -p some-pkg -y other |
npm some-pkg (-p wins, as in npx) |
uvx mcp-server-git |
pypi mcp-server-git |
uvx awslabs.core-mcp-server@latest |
pypi awslabs-core-mcp-server latest |
uvx --from 'pkg==1.4.0' entry |
pypi pkg 1.4.0 |
uvx -p 3.11 mcp-server-git |
pypi mcp-server-git (a Python pin, not a package) |
npx -c 'foo && bar' |
unidentified — runs an arbitrary command |
npx --registry https://x/ pkg |
unidentified — redirects where the code comes from |
uvx --with pandas pkg |
unidentified — brings in more than the named package |
uvx --from git+https://… |
unidentified — not a registry package |
uvx --from 'pkg[all]' entry |
unidentified — extras |
/opt/homebrew/bin/uvx pkg |
unidentified — a path, not a bare runner |
uv tool run pkg, node server.js, docker run … |
unidentified — unsupported runner |
enforce resolve prints every configuration source the hook would consult, in
order, and what it concluded — from the same resolver the hook uses, so a trace
that says FOUND is evidence about production behavior. It exits non-zero when the
server cannot be identified.
$ obot-sentry enforce resolve --agent claude-code --server linear
1 /Library/Application Support/ClaudeCode/managed-mcp.json absent
2 /Users/me/proj/.mcp.json absent
3 /Users/me/.claude.json projects["/Users/me/proj"] no match
4 /Users/me/.claude.json mcpServers["linear"] FOUND
server name: linear
resolved: npm / linear-mcp / any version
An unidentified server prints the reason on the last line and exits 1:
server name: local-binary
unresolved: stdio command "/opt/homebrew/bin/some-server" is a path, not a bare package runner
The Codex and Cursor hook files are machine-scoped and administrator-owned. The
Claude Code and Kiro hook files are user-scoped (~/.claude/settings.json,
~/.kiro/hooks/obot-sentry.json), so the user whose calls are being enforced can
delete them. The hourly hook-install task re-converges them, so the bypass
window is bounded by that interval rather than permanent. We intend to find a
machine-scoped solution for Claude Code in the future.
Kiro runs no hooks in a workspace the user hasn't trusted, or in a window with
no folder open, so calls made there are neither enforced nor audited. A folder
trusted after Kiro starts stays unhooked until Kiro restarts
(kirodotdev/Kiro#11884). The
Kiro CLI runs these hooks only on its v3 agent engine (kiro-cli chat --v3, or
chat.agentEngine set to v3). Its default v2 engine ignores ~/.kiro/hooks,
so CLI sessions on v2 are neither enforced nor audited. obot-sentry does not
change the engine setting.
Set EnforcementEnabled to false (or pass --enforce=false) and the next
hook-install converges the audit hooks and removes every Obot Sentry-managed
enforcement hook. Third-party pre-tool hooks are left untouched. A plain
hook-install still follows the configured enforcement value, so explicitly set
EnforcementEnabled false (or use --enforce=false) when disabling it.
Run the following before removing the binary:
sudo obot-sentry hook-uninstallOn Windows, run the equivalent command in an elevated PowerShell. The command
removes all hook entries whose command contains --managed-by obot-sentry from
Claude Code, Codex, VS Code, Cursor, and Kiro while preserving third-party
hooks. It targets machine-wide files and the active console user's files, so
repeat it for each user on a shared machine. Stop the scheduled hook task or
uninstall the package afterward so a later convergence does not reinstall them.
Every entry Obot Sentry writes carries the --managed-by obot-sentry marker
— the same signal hook-install uses to recognize and replace its own entries
on each run — so it is also how you identify what to delete. The managed files
are:
| Agent | macOS | Windows |
|---|---|---|
| Claude Code | ~/.claude/settings.json |
%USERPROFILE%\.claude\settings.json |
| Codex | /etc/codex/requirements.toml |
%ProgramData%\OpenAI\Codex\requirements.toml |
| Copilot (VS Code) hook | ~/.copilot/hooks/obot-sentry.json |
%USERPROFILE%\.copilot\hooks\obot-sentry.json |
| Cursor | /Library/Application Support/Cursor/hooks.json |
%ProgramData%\Cursor\hooks.json |
| VS Code settings | ~/Library/Application Support/Code/User/settings.json |
%APPDATA%\Code\User\settings.json |
| Kiro | ~/.kiro/hooks/obot-sentry.json |
%USERPROFILE%\.kiro\hooks\obot-sentry.json |
The uninstall command intentionally changes only marker-owned hooks. It does not
delete files, remove Codex's unmarked [features] pins, or remove the unmarked
chat.hookFilesLocations values in VS Code settings because their previous
values cannot be recovered safely. Remove those supporting settings manually if
you no longer want them. Restart the agents afterward to drop the hooks. Per-OS
commands are in the INSTRUCTIONS.md for each configuration under build/.
Everything MDM-related lives in build/, one directory per OS with the
MDM channels nested under the OS they deliver to:
build/
manifest.json # authored: schemaVersion, fields, configurations (${VERSION} tokens)
mdm-assets.sh # completes + sanity-checks the manifest, stages dist/mdm-assets/
windows/ # the Windows installer (MSI, WiX v4)
msi.ps1 obot-sentry.wxs scan-task.ps1 hook-task.ps1 obot-sentry.ico
intune/ # Intune channel: .intunewin wrap + instructions
intunewin.ps1 INSTRUCTIONS.md.tmpl
manual/ # manual channel: instructions for the MSI + exe
INSTRUCTIONS.md.tmpl
macos/ # the macOS installer (pkg) + the assets every channel shares
pkg.sh distribution.xml postinstall uninstall.sh
ai.obot.obot-sentry.scan.plist # LaunchAgent: per-user scan
ai.obot.obot-sentry.hook-install.plist # LaunchDaemon: root hook convergence
ai.obot.obot-sentry.mobileconfig.tmpl # deployment configuration (MDM channels)
ai.obot.obot-sentry.pppc.mobileconfig # Full Disk Access grant (MDM channels)
intune/ # Intune channel: instructions
INSTRUCTIONS.md.tmpl
jamf/ # Jamf Pro channel: instructions
INSTRUCTIONS.md.tmpl
manual/ # manual channel: instructions for the pkg + bare binary
INSTRUCTIONS.md.tmpl
The installers are tenant-agnostic; per-tenant configuration (server URL
- an enrollment key created in obot) is applied at deploy time — as MSI
properties on Windows, as a managed-preferences profile on macOS — so one
installer serves every tenant. The macOS MDM channel and the DIY channel
ship the same
dist/obot-sentry-<version>.pkg; only the configuration surface around it differs. The Windows chain runs on Windows: obot-sentry.wxs via WiX Toolset v4 (dotnet tool install --global wix), wrapped by Microsoft's Win32 Content Prep Tool:
build\windows\msi.ps1 -Version 1.2.3 -Exe bin\obot-sentry.exe # dist\obot-sentry.msi (version inside, name stable)
build\windows\intune\intunewin.ps1 # dist\obot-sentry.intunewin
build/mdm-assets.sh 1.2.3 # dist/mdm-assets/ (any OS)
The macOS chain runs on macOS: pkgbuild, productbuild, and
productsign ship with the OS and notarytool with Xcode, so
pkg.sh needs no extra tooling. It takes the universal
binary only, and signing and notarization gate independently on credentials
being present, so a plain local run still produces an installable (unsigned)
pkg for smoke tests:
build/macos/pkg.sh 1.2.3 bin/obot-sentry # dist/obot-sentry-1.2.3.pkg (versioned name: Jamf keys packages by filename)
Signing reads INSTALLER_SIGN_P12 (the p12 path or its base64 contents)
and INSTALLER_SIGN_PASSWORD, importing the identity into a throwaway
keychain for productsign. The p12 must carry a Developer ID Installer
certificate — the only kind MDMs accept, and a separate certificate from the
Application one quill signs the binary with — and must be legacy-encoded,
because security import verifies SHA-1 MACs only and reports anything else
as a wrong password. It must also bundle the intermediate that issued the
leaf: productsign embeds only intermediates it finds in the keychain, and
macOS ships the G1 Developer ID intermediate but not G2. Mint one from the
certificate and key Apple issued, fetching the intermediate its issuer line
names:
openssl x509 -inform DER -in developerID_installer.cer -out installer-cert.pem
openssl x509 -in installer-cert.pem -noout -issuer # which intermediate to fetch
curl -sSfL https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer |
openssl x509 -inform DER -out DeveloperIDG2CA.pem
openssl pkcs12 -export -legacy -macalg sha1 -inkey privatekey.key \
-in installer-cert.pem -certfile DeveloperIDG2CA.pem -out installer.p12
CI (build.yaml) runs the same chain — a Linux job builds the binaries
with GoReleaser (.goreleaser.yaml): the Windows exe plus the macOS
universal (amd64+arm64) binary, signed and notarized with
quill without a macOS runner; a
Windows runner packages the MSI and a macOS runner the pkg — and
make mdm dispatches that workflow on your fork and downloads the
assembled dist/mdm-assets.
mdm-assets.sh produces the tree obot consumes: the platform installers
and instruction templates plus one manifest.json. The manifest is the
whole contract: fields is a JSON Schema obot renders the admin form from and
validates values against, and configurations lists the downloadable
(platform, OS) units — display names, descriptions, setup instructions,
and asset files included — so obot's wizard carries no platform
knowledge of its own. Obot renders the *.tmpl assets and serves each
unit as a zip. The enrollment key is never rendered — templates carry a
REPLACE_WITH_ENROLLMENT_KEY placeholder the admin fills in the MDM.
CI assembles the assets on every PR/push and publishes them as the
mdm-assets workflow artifact; releases add a tarball with a
cosign-signed checksums file. The Windows installers ship unsigned:
Intune installs them silently in SYSTEM context, so nothing gates on
Authenticode there. The macOS universal binary is Developer ID-signed
and notarized via quill when the QUILL_* secrets are available (the
same secrets obot's release workflow uses); PRs and forks fall back to
a dry run with an ad-hoc signature.
Obot reads the assembled tree via OBOT_SERVER_MDM_ASSET_SOURCE, which
takes a local directory, a tar archive, or an HTTP(S) tarball URL — so
pointing it at dist/mdm-assets serves a local build.
| Platform | installer | scheduling | tenant config |
|---|---|---|---|
| Intune (Windows) | .msi wrapped as .intunewin |
per-user scan task (logon + 10-min poll, submissions throttled to ScanIntervalMinutes) plus elevated SYSTEM hook-install task (logon + hourly) |
MSI properties → HKLM\SOFTWARE\Obot\obot-sentry |
| Intune, Jamf Pro (macOS) | .pkg |
per-user scan LaunchAgent (login + 15-min poll matching the ScanIntervalMinutes floor, submissions throttled to ScanIntervalMinutes) plus root hook-install LaunchDaemon (load + hourly) |
configuration profile → /Library/Managed Preferences/ai.obot.obot-sentry.plist |
| Manual (Windows) | .msi, or the bare exe |
as above for the MSI | MSI properties, or OBOT_SENTRY_* environment variables |
| Manual (macOS) | .pkg, or the bare universal binary |
as above for the pkg | sudo defaults write /Library/Preferences/ai.obot.obot-sentry, or OBOT_SENTRY_* environment variables |
The macOS pkg is identical across all three channels — the MDM channels add a configuration profile and a PPPC (Full Disk Access) profile, which macOS only honors from a user-approved MDM, so the DIY channel grants that access by hand instead.
make build # bin/obot-sentry
make test
make validate-go-code
Local end-to-end against a dev server:
OBOT_SENTRY_SERVER_URL=http://localhost:8080 OBOT_SENTRY_ENROLLMENT_KEY=ode1-... bin/obot-sentry scan --submit
Repeat submissions inside the scan interval are throttled; add --force to
submit every time.
A change reaches devices in three steps: build MDM assets from your branch and test them against a local obot, tag a release, then bump the default asset source in obot so new obot releases serve it.
Push your change to a scratch branch on this repo (not your fork) and build the full asset tree there:
git switch -c "$(git branch --show-current)-mdm"
git push upstream HEAD
MDM_REMOTE=upstream make mdm VERSION=0.1.8 # dispatches build.yaml, downloads dist/mdm-assets
The -mdm suffix keeps the push off main even if you start there. Switch
back to your branch afterward and delete the scratch branch from this repo when
you're done (git push upstream --delete <branch>-mdm).
Only this repo holds the signing secrets, and the macOS MDM channels need
them: Intune and Jamf accept only a Developer ID-signed pkg, and the PPPC
profile grants Full Disk Access only to a binary signed by our team. A plain
make mdm builds on your fork (origin) and skips signing, as PR builds do,
so its mdm-assets (and every PR's mdm-assets workflow artifact) are only
good for the Windows channels and the manual macOS channel.
Pass a VERSION above whatever is installed on your test machine. Without one
the build is versioned 0.0.<run number>, and the MSI refuses to downgrade a
machine that already runs a release.
Point a local obot at the result with an absolute path:
OBOT_SERVER_MDM_ASSET_SOURCE=$PWD/dist/mdm-assets
obot imports the source at startup whenever its value changes. Rebuilding into
the same directory does not change the value, so after a rebuild use Refresh
on obot's Inventory page (or POST /api/mdm/asset-source/refresh) to re-import
it. Then download a configuration from the Inventory wizard, install it on a
test machine, and check that the device enrolls and submits.
Releases are cut from main by pushing a v<x.y.z> tag to this repo:
git checkout main && git pull upstream main
git tag v0.1.8
git push upstream v0.1.8
release.yml runs the full build.yaml pipeline with that version and the
signing secrets, then creates a GitHub release carrying mdm-assets.tar.gz and
its checksums.txt. The version must be numeric x.y.z: prerelease tags such
as v0.1.8-rc1 are skipped entirely, because the MSI's ProductVersion can't
carry a suffix. The tarball's URL is what obot consumes:
https://github.com/obot-platform/obot-sentry/releases/download/v0.1.8/mdm-assets.tar.gz
Before rolling it out, point a local obot at that URL
(OBOT_SERVER_MDM_ASSET_SOURCE=<url>) and repeat the smoke test from step 1
against the release build.
obot defaults OBOT_SERVER_MDM_ASSET_SOURCE to a pinned release URL, so a new
obot-sentry release reaches deployments only after obot bumps that default. In
obot, open a PR (titled
chore: bump obot-sentry to v0.1.8 by convention) that changes the version in
both places the default is spelled out:
pkg/services/config.go: thedefault:tag onMDMAssetSourcedocs/docs/configuration/server-configuration.md: the default column forOBOT_SERVER_MDM_ASSET_SOURCE
Once the bump is in an obot release, upgraded servers see the new
source at startup and import it, and the Inventory wizard serves the new
installers. Already-deployed devices pick up the new version when admins push
the new installer through their MDM. Deployments that set
OBOT_SERVER_MDM_ASSET_SOURCE explicitly (for example via the Helm chart's
config.OBOT_SERVER_MDM_ASSET_SOURCE) keep their own value and must update it
themselves.