# Distribution and upgrade

> How Fermix is packaged as a single self-contained binary, distributed via Homebrew, and self-updated with signature verification, a safe in-place swap, and rollback.

Fermix is distributed as a single download per platform that you can run on its own, and it keeps itself up to date. It ships as a single self-contained binary per platform, built with [Burrito](https://github.com/burrito-elixir/burrito). The binary bundles everything it needs to run, so no Elixir or Erlang installation is required on the target machine. `fermix upgrade` handles unmanaged installs (those you downloaded and placed yourself) directly; package-manager installs (Homebrew, dpkg) are detected and redirected to the appropriate upgrade command.

## Binary targets

Burrito produces one artifact per platform. The four supported targets are:

| Target | Binary name |
|--------|-------------|
| macOS (Apple Silicon) | `fermix_macos_aarch64` |
| macOS (Intel) | `fermix_macos_x86_64` |
| Linux (ARM64) | `fermix_linux_aarch64` |
| Linux (x86-64) | `fermix_linux_x86_64` |

Building from source additionally requires [Zig](https://ziglang.org), Burrito's packaging dependency. See [installation](/docs/installation) for the toolchain versions and the full build-from-source steps.

## Homebrew tap

The primary distribution channel for macOS and Linux is the Homebrew tap:

```bash
brew install tezra-io/tap/fermix
```

After `brew upgrade fermix`, run `fermix restart`. The upgrade swaps the binary on disk, but the daemon (the background Fermix process) keeps running the old version until it is restarted. While the running daemon's version differs from the installed binary, `fermix status` appends a warning and `fermix doctor`'s daemon-socket check degrades to a warning, each naming both versions and the `fermix restart` fix.

If the OS service unit (the file that tells your operating system how to run Fermix in the background) drifted across the upgrade — an updated `PATH` or template — re-run `fermix setup` instead. It rewrites and reloads the unit, which a plain restart never does, so the daemon picks up the change without a manual `fermix service install`.

## `fermix upgrade`

`fermix upgrade` is the self-update command for installs not managed by a package manager. Each release also carries an updated signed plugin catalog inside the binary, so newly published and updated plugins become installable after upgrading. Browse and install them from the **Plugins** page of `fermix setup`. (On the command line, `fermix plugins catalog` lists only the plugins already bundled or installed — not the full set of plugins a release makes available; the setup Plugins page renders the complete catalog.)

### Package-manager detection

Fermix detects whether the running binary is owned by a package manager — a Homebrew Cellar/prefix path, or a path tracked by dpkg. If it is, `fermix upgrade` refuses to self-modify and prints the correct command to use instead on standard error, then exits non-zero (exit code 2):

```
fermix upgrade: managed by homebrew; run: brew upgrade fermix, then `fermix restart` — the daemon keeps running the old version until restarted
```

For dpkg-managed installs the equivalent guidance is printed:

```
fermix upgrade: managed by dpkg; run: sudo apt update && sudo apt upgrade fermix, then `fermix restart` — the daemon keeps running the old version until restarted
```

### Upgrade flow for unmanaged installs

For binaries not under a package manager, `fermix upgrade` runs this sequence:

1. Fetch `releases.json` over HTTPS from the GitHub release.
2. Compare `manifest.latest` to the running version.
3. Select the platform-specific artifact URL, SHA-256, and signature URL.
4. Download the artifact, signature, and certificate.
5. Stream-SHA verify the downloaded artifact against the manifest SHA-256 (confirm the download's fingerprint matches the expected one, so it was not corrupted or tampered with).
6. Run `cosign verify-blob` (cosign checks the release's cryptographic signature) with the certificate identity bound to the exact manifest version. The identity regex enforces `refs/tags/v<exact-version>` to prevent substitution attacks (swapping in a different signed file).
7. Snapshot the current binary to `~/.fermix/.previous` (keep a copy of the old version in case rollback is needed).
8. Atomic rename to install the new binary (swap in the new file in one step, so there is never a half-installed state).
9. Restart the OS service.
10. Poll the daemon's status over its control socket for up to 10 seconds (checking every 500 ms) until it answers healthy and reports the new version — a semver comparison against `manifest.latest`, so a stale daemon that survived the restart fails the check rather than passing as a false green.
11. If the health check fails, roll back from `~/.fermix/.previous`, restart the service so the restored binary is the one running, and write the failure to the audit record.

Pass `--check` to query the manifest and report whether an update is available without downloading anything.

### `cosign` requirement

Fermix verifies release artifacts with [cosign](https://github.com/sigstore/cosign). `cosign` must be on `PATH` before running `fermix upgrade` or activating plugins. Install it with:

```bash
brew install cosign
```

The OS service unit pins a `PATH` so the supervised daemon resolves `cosign` the same way an interactive shell does. That pinned `PATH` also lets the daemon find Homebrew-installed `node` and `python`, the runtimes used by MCP plugins. A stripped or bare unit `PATH` that omits the Homebrew prefix surfaces as a misleading `signature invalid` failure when installing a plugin: the daemon cannot find `cosign`, so verification never actually runs. Re-running `fermix setup` rewrites the unit with the correct `PATH` and fixes it.

### Rollback

The previous binary is preserved at `~/.fermix/.previous` before each swap. If the health check after restart does not pass within the timeout, the swapper restores the previous binary from `~/.fermix/.previous` and restarts the service, so the restored binary is the one actually running — restoring the file alone would leave the wrong-version daemon still executing. The upgrade failure is written to the audit log.

### Audit log

Every upgrade attempt appends a structured record to `~/.fermix/upgrades.jsonl`. Each record includes status, old and new versions, sha256 of the artifact, and the timestamp.

```json
{"status":"ok","from":"<old>","to":"<new>","sha256":"a1b2c3...","timestamp":"2026-01-01T00:00:00Z"}
{"status":"error","from":"<old>","to":"<new>","sha256":"d4e5f6...","timestamp":"2026-01-02T00:00:00Z"}
```

## Service unit reconciliation

After any upgrade (self-upgrade or package-manager), re-running `fermix setup` is safe and idempotent (running it again does no harm and changes nothing if everything is already in sync). It detects whether the installed service unit differs from what the current binary would write and reconciles it if needed. This keeps the daemon in sync when the unit template changes between releases without requiring a manual `fermix service install`.

See [setup](/docs/setup) for setup flags and [auth and secrets](/docs/auth-and-secrets) for keychain and credential management across upgrades.
