Skip to content

About

No description, website, or topics provided.

Resources

Stars

9 stars

Watchers

1 watching

Forks

Repository files navigation

Obot Sentry

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.

How it works

  • 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\Users group principal (logon + a 10-minute poll). Each run is a plain obot-sentry scan --submit --quiet in 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-configured ScanIntervalMinutes (default 60, clamped to 15–1440) against its per-user scan state, so admins retune the cadence from the MDM alone (scan --submit --force bypasses 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 runs obot-sentry hook-install at logon and hourly. Because hook-install targets 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-sentry on Windows, /Library/Application Support/obot/obot-sentry on macOS; both are prepared user-writable by the installer, with a per-user fallback when unavailable) and enrolls the public key via POST /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 via POST /api/devices/scans, attributed to the submitting user by the manifest's username. Local-agent audit logs land via POST /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 submit command 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 enforce command 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 to scan-logs/ — timestamp-sortable filenames, pruned by age and size — in obot-sentry's per-user cache dir (%LOCALAPPDATA%\obot\obot-sentry on Windows, ~/Library/Caches/obot/obot-sentry on macOS). Support and MDM freshness checks read these; recording problems never fail a scan.

Commands

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.

Configuration

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.

Tool-call enforcement

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.

It fails closed

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.

It never grants permission

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.

What can and cannot be allowlisted

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-stdio arrives as probe_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_x with servers github and github_enterprise) is reported as unidentified.

npx / uvx package resolution

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

Diagnosing a block

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

Tamper resistance, stated plainly

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.

Turning it off

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.

Removing the hooks

Run the following before removing the binary:

sudo obot-sentry hook-uninstall

On 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/.

MDM packaging

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.

Development

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.

Testing, releasing, and rolling out

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.

1. Test a change with real MDM assets

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.

2. Release

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.

3. Roll it out in obot

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: the default: tag on MDMAssetSource
  • docs/docs/configuration/server-configuration.md: the default column for OBOT_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.

About

No description, website, or topics provided.

Resources

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages