Last updated: 2026-09-18 · commit
397b4d902
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.
- An Apple Silicon Mac. The binary targets macOS 14 (
provider-swift/Package.swift,.macOS(.v14)); the installer checks onlyuname=Darwinanduname -m=arm64and prints the macOS version without gating on it. Sizing (RAM, disk, which models fit) is in hardware requirements. - Outbound HTTPS to the coordinator (
https://api.darkbloom.dev). - No
sudo. The script writes to~/.darkbloom, appends onePATHline to~/.zshrc(or~/.bashrc), and tries — best effort, no prompt — to link/usr/local/bin/darkbloom.
[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-<pid>-<random>`;
- 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-<pid>-<random>`, 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 — verification setup.** `configure_device_verification` uses the
local `sw_vers` major version. On macOS 27 or later it skips profile checks,
download and System Settings, and directs the user to login/start/status for
App Attest approval. Older macOS retains legacy enrollment and prints the
upgrade option and upcoming MDM deactivation notice. If `profiles status
-type enrollment` reports no management, the script posts `{}` to
`$COORD_URL/v1/enroll`, saves the profile under
`${TMPDIR:-/tmp}/Darkbloom-Enroll.XXXXXX/`, and opens System Settings for
approval. Existing management is preserved without claiming it is Darkbloom
verification. Unknown OS versions download no profile and direct users to
`darkbloom enroll`. Setup choice grants no serving authorization; see the
[authorization contract](../reference/provider-authorization.md).
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
darkbloom doctorContinue with the quickstart to download a model, link the account and start serving.
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 OKdarkbloom update --check-only # report only
darkbloom update # download, verify, atomic replace, restart the servicedarkbloom 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 <url>, --config.
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
(the jitter default in its provider.toml table).
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). 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.
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/darkbloomdarkbloom 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 <id>
before removing the CLI.
- Quickstart — login, start, status.
- CLI reference — every flag, path and runtime constant.
- Troubleshooting — installer exit messages and fixes.
- Attestation — what the Secure Enclave key and MDM profile buy you.
- Hardware requirements.