# Install, update, and uninstall the provider > Last updated: 2026-09-03 · commit `5d400cf75` How to put the `darkbloom` CLI on an Apple Silicon Mac with `scripts/install.sh`, what the script verifies before it touches an existing install, how the binary is updated afterwards, and how to remove everything. For operators; at the end `darkbloom doctor` runs and reports the machine's state. ## Prerequisites - An Apple Silicon Mac. The binary targets macOS 14 (`provider-swift/Package.swift`, `.macOS(.v14)`); the installer checks only `uname` = `Darwin` and `uname -m` = `arm64` and prints the macOS version without gating on it. Sizing (RAM, disk, which models fit) is in [hardware requirements](./hardware-requirements.md). - Outbound HTTPS to the coordinator (`https://api.darkbloom.dev`). - No `sudo`. The script writes to `~/.darkbloom`, appends one `PATH` line to `~/.zshrc` (or `~/.bashrc`), and tries — best effort, no prompt — to link `/usr/local/bin/darkbloom`. ## Steps ### 1. Run the installer [backend] enabled_models = [] idle_timeout_mins = 60 # free when idle; 0 = always ready (see `darkbloom idle`) max_model_slots = 3 [gemma_optimizations] prefill_layer18 = true weighted_r1 = true [coordinator] url = "wss://api.darkbloom.dev/ws/provider" private_only = false ``` The coordinator serves `scripts/install.sh` at `/install.sh` with its own base URL substituted for the `__DARKBLOOM_COORD_URL__` placeholder; the embedded copy `coordinator/api/install.sh` is regenerated byte-for-byte by `scripts/sync-install-embed.sh`. To run the script from a checkout, set the placeholder yourself: `COORD_URL=https://api.darkbloom.dev bash scripts/install.sh`. The script performs these actions in order (`scripts/install.sh`; failures exit 1 and are listed in [troubleshooting](./troubleshooting.md#installer-exits)): 1. **Preflight.** Aborts unless `uname` is `Darwin` and `uname -m` is `arm64`. Prints chip (`sysctl machdep.cpu.brand_string`), RAM (`hw.memsize`) and macOS version (`sw_vers`). 2. **Step 1/5 — release metadata.** `GET $COORD_URL/v1/releases/latest`, then extracts `url`, `bundle_hash`, `binary_hash`, `metallib_hash`, `version`, `backend` with `sed`. Missing `url`, `bundle_hash` or `version` aborts. 3. **Step 2/5 — download and verify.** Creates `~/.darkbloom` and `~/.darkbloom/bin`, downloads the tarball to `/tmp/darkbloom-bundle.tar.gz` and requires `shasum -a 256` to equal `bundle_hash` (mismatch deletes the file and aborts). `install_bundle_atomically` then: - extracts into `~/.darkbloom/.install-staging--`; - requires `bin/darkbloom`, `bin/darkbloom-enclave`, `bin/mlx.metallib`; - hashes `bin/darkbloom` against `binary_hash` and `bin/mlx.metallib` against `metallib_hash` (`verify_file_hash`; a hash the release record omits is skipped); - when the tarball contains `Darkbloom.app`: `verify_staged_app_payload` (both hashes are then mandatory and `Contents/MacOS/darkbloom` and `Contents/MacOS/mlx.metallib` are re-hashed) and `verify_staged_app`: `codesign --verify --deep --strict -R="$DARKBLOOM_DESIGNATED_REQUIREMENT"` where the requirement is `anchor apple generic and identifier "io.darkbloom.provider" and certificate leaf[subject.OU] = "SLDQ2GJ6TL"`; the fan-helper triple (`darkbloom-fan-helper-v1` string in the binary ⇔ marker `Contents/Resources/darkbloom-runtime-capabilities/fan-helper-v1` = `1` ⇔ `Contents/Helpers/darkbloom-fan-helper` present, a regular file, mode `0755`, signed to `anchor apple generic and identifier "io.darkbloom.fan-helper" and certificate leaf[subject.OU] = "SLDQ2GJ6TL"`); the paged-kernel pair (`engine_v2_kv_backend` string in the binary ⇔ marker `paged-kernel-v1` = `1`, plus exactly one non-empty `Contents/Resources/mlx-swift-lm_MLXLMCommon.bundle/pagedattention.metal`); and a runtime smoke test: `DARKBLOOM_NO_UPDATE_CHECK=1 DARKBLOOM_GEMMA4_PREFILL_CHUNK_EVAL=18 MLX_GEMMA4_FUSED_WEIGHTED_UNSORT=1 MLX_GATHER_QMM_EXPERT_SLICES=1 darkbloom runtime-smoke` (`provider-swift/Sources/darkbloom/RuntimeSmokeCommand.swift`, `RuntimeSmoke`); - `commit_staged_app` moves any existing `~/.darkbloom/Darkbloom.app` to `~/.darkbloom/.install-backup--`, moves the staged app in, writes the symlinks `~/.darkbloom/bin/darkbloom`, `darkbloom-enclave`, `mlx.metallib` → `../Darkbloom.app/Contents/MacOS/*` and the legacy alias `bin/eigeninference-enclave → darkbloom-enclave`, and `chmod +x`. Any failure moves the backup back; - a tarball without `Darkbloom.app` (legacy flat layout) gets `codesign --verify --strict -R=…` on `bin/darkbloom` and `commit_staged_flat_bundle` swaps `~/.darkbloom/bin` the same way; - the staging directory is removed; on any failure the script prints `Existing installation was left unchanged.` and exits 1. 4. **PATH.** `ln -sf ~/.darkbloom/bin/darkbloom /usr/local/bin/darkbloom` (errors ignored). The rc file is `~/.zshrc`, or `~/.bashrc` only when `~/.zshrc` does not exist. If the rc does not already mention `.darkbloom/bin`, lines referencing `.dginf/bin`, `.eigeninference/bin`, `alias eigeninf`, `alias dginf`, `# EigenInference` and `# Darkbloom` are deleted and `# Darkbloom` + `export PATH="$HOME/.darkbloom/bin:$PATH"` is appended; the rc is then sourced. 5. **Legacy install migration.** For each real directory `~/.dginf` and `~/.eigeninference`: `cp -n` of `enclave_key.data`, `wallet_key` and `auth_token` into `~/.darkbloom`, then the old directory is replaced by a symlink to `~/.darkbloom`. `provider.toml` is not migrated by the script; the CLI copies a config found at a legacy path to `~/.config/darkbloom/provider.toml` on its next run (`provider-swift/Sources/darkbloom/Darkbloom.swift`, `migrateConfigIfNeeded`). 6. **Step 3/5 — Secure Enclave identity.** Runs `darkbloom-enclave info` (`provider-swift/Sources/darkbloom-enclave-cli/EnclaveCLI.swift`), which creates the P-256 key if missing. Failure prints a warning; the install continues with reduced trust (see [attestation](./attestation.md)). 7. **Step 4/5 — enrollment.** If `profiles status -type enrollment` does not report `MDM enrollment: Yes`, the script `POST`s `{}` to `$COORD_URL/v1/enroll`, saves the `.mobileconfig` under `${TMPDIR:-/tmp}/Darkbloom-Enroll.XXXXXX/`, opens it and the System Settings Profiles pane, waits for Enter (interactive) or 3 s (piped), then re-checks. An unreachable coordinator prints `enroll later with: darkbloom enroll`. 8. **Step 5/5 — catalog.** `GET $COORD_URL/v1/models/catalog?type=text`; interactive runs print up to 20 entries. Nothing is downloaded. ### 2. Reload your shell ```bash source ~/.zshrc # or open a new terminal ``` ### 3. Check the machine ```bash darkbloom doctor ``` Continue with the [quickstart](./quickstart.md) to download a model, link the account and start serving. ## Verify ```bash darkbloom --version # prints ProviderCore.version, e.g. 0.8.16 ls -l ~/.darkbloom/bin # symlinks into ../Darkbloom.app/Contents/MacOS/ codesign --verify --deep --strict \ -R='anchor apple generic and identifier "io.darkbloom.provider" and certificate leaf[subject.OU] = "SLDQ2GJ6TL"' \ ~/.darkbloom/Darkbloom.app && echo signature OK ``` ## Update ### Manual ```bash darkbloom update --check-only # report only darkbloom update # download, verify, atomic replace, restart the service ``` `darkbloom update` (`provider-swift/Sources/darkbloom/UpdateCommand.swift`, `Update`) drives `SelfUpdater` (`provider-swift/Sources/ProviderCore/Update/SelfUpdater.swift`): it fetches `GET {coordinator}/v1/releases/latest?platform=macos-arm64`, verifies the SHA-256 of the bundle and, when published, of the binary and `mlx.metallib`, verifies the code signature (production always constructs the updater with `verifyCodeSignatures: true`), replaces the install, and — if the LaunchAgent is loaded — restarts it via `ProcessLifecycle.restartAfterUpdate()`. Flags: `--check-only`, `--override-quarantine`, `--coordinator `, `--config`. ### Automatic `provider.auto_update` (default `true`, `provider-swift/Sources/ProviderCore/Config/ProviderConfig.swift`) is toggled with `darkbloom autoupdate enable|disable|status` (`provider-swift/Sources/darkbloom/AutoUpdateCommand.swift`). When enabled the daemon checks once at start (`runStartupAutoUpdate`, `provider-swift/Sources/darkbloom/StartCommand+Modes.swift`) and then on a loop (`provider-swift/Sources/ProviderCore/ProviderLoop+AutoUpdate.swift`): | Step | Symbol | |---|---| | First in-daemon check after start | `autoUpdateInitialDelay` | | Check interval | `autoUpdateInterval` | | Random delay before installing | up to `provider.update_jitter_seconds` (`updateJitterSeconds`) | | Drain of in-flight requests before restart | `updateDrainTimeout` | | Skip every check (banner, start, loop, watchdog) | `DARKBLOOM_NO_UPDATE_CHECK` set to any value | The values are in [`cli-reference.md`](./cli-reference.md#runtime-constants) (the jitter default in its [`provider.toml` table](./cli-reference.md#providertoml-keys-read-by-the-cli)). A freshly installed version that crashes `rollbackThreshold` times before surviving `defaultStabilizationSeconds` is quarantined on this machine (`provider-swift/Sources/ProviderCore/Update/UpdateRecoveryState.swift`; values in [runtime constants](./cli-reference.md#runtime-constants)). Only that exact version is blocked; a newer release installs normally. `darkbloom update --override-quarantine` reinstalls it anyway, and `darkbloom doctor` reports the quarantine under `up to date`. The unprivileged updater never touches the root fan helper; after an update run `sudo darkbloom fan enable` again if you use [fan control](./fan-control.md). ## Uninstall ```bash sudo darkbloom fan uninstall # only if fan control was enabled darkbloom stop --uninstall # stops the daemon, removes both LaunchAgent plists darkbloom unenroll # opens System Settings to remove the MDM profile; offers to delete config + tokens rm -rf ~/.darkbloom ~/.config/darkbloom sudo rm -f /usr/local/bin/darkbloom ``` `darkbloom stop --uninstall` (`provider-swift/Sources/darkbloom/StopCommand.swift`) disarms the watchdog first, deletes `~/Library/LaunchAgents/io.darkbloom.watchdog.plist` and `io.darkbloom.provider.plist`, and disables both labels in launchd. `darkbloom unenroll` (`provider-swift/Sources/darkbloom/UnenrollCommand.swift`) opens System Settings → Device Management (macOS does not let a binary remove a profile) and, after confirmation or with `--force`, deletes `~/.config/darkbloom/`, `~/.darkbloom/auth_token` and the legacy key files. Remove the `# Darkbloom` `PATH` line from your rc file by hand. Model weights live in `~/.cache/huggingface/hub`; delete them with `darkbloom models remove ` before removing the CLI. ## Related - [Quickstart](./quickstart.md) — login, start, status. - [CLI reference](./cli-reference.md) — every flag, path and runtime constant. - [Troubleshooting](./troubleshooting.md) — installer exit messages and fixes. - [Attestation](./attestation.md) — what the Secure Enclave key and MDM profile buy you. - [Hardware requirements](./hardware-requirements.md).