# Install

https://shipcheck.run/docs/install/

> What shipcheck needs on the machine, and how to install it.

What a repository needs depends on its [mode](https://shipcheck.run/docs/config/#modes):

- Host mode needs no Docker. On a Mac it needs only shipcheck. On Linux it needs shipcheck and bubblewrap.
- act mode, and any `[jobs]` entry with `runner = "act"`, needs act 0.2.86 or later and a running Docker engine: Docker Desktop 4.44.3 or later, OrbStack, or colima as the Docker engine. On Linux, Docker Engine with runc 1.2.8 or 1.3.3 or later. shipcheck refuses older versions.
- A job with `engine = "colima"` needs colima and lima on the Mac, besides act and the running Docker engine above, which shipcheck checks before every act run and which runs the jobs a colima job needs. The job's own steps use the Docker inside its VM.

Every repository also needs the [shipcheck App](#post-the-checks) to post its checks on PRs.

## Set up a Mac

Tested on macOS with Docker Desktop on Apple silicon. Host mode uses macOS's own `sandbox-exec`.

1. Install shipcheck with [Homebrew](https://brew.sh). It also installs act, git and the Docker client:

   ```sh
   brew install gabrielkoerich/tap/shipcheck
   ```

2. For act mode, install and start a Docker engine. Host mode needs none:

   | Engine | Install | What ran on it |
   |---|---|---|
   | Docker Desktop 4.44.3 or later | [docker.com](https://www.docker.com/products/docker-desktop/), then open it once | The act mode trials and benchmarks on these pages. Recommended |
   | OrbStack | [orbstack.dev](https://orbstack.dev) | act jobs. It sometimes showed a job the files of the previous run at the same path, under `/var/folders`. shipcheck fails such a job |
   | colima | `brew install colima && colima start` | act jobs, with the repository and `SHIPCHECK_HOME` under your home directory, the only one colima shares by default. Elsewhere a job sees an empty checkout and fails |

   OrbStack and colima also switch Docker's default context to their own when they start. Here colima is only the Docker engine behind act's containers. `engine = "colima"`, a VM of its own for one job, is a different thing ([engines](https://shipcheck.run/docs/config/#engines)).

   shipcheck refuses Docker Desktop before 4.44.3, which let any container reach the Docker engine.

3. Check it:

   ```sh
   shipcheck doctor
   ```

   It checks git, the OS sandbox or act and the Docker engine, the disk and the repository, marks each line `ok` or `fix`, and prints the command that fixes each `fix`. It exits 0 when a run can work.

macOS disks use APFS, so each job's checkout is a copy-on-write clone and costs no disk. On Apple silicon, jobs run arm64 images. A job whose tools ship no arm64 build can get an amd64 image of its own with `act.jobs` in [`shipcheck.toml`](https://shipcheck.run/docs/config.md). Turn on Rosetta in Docker Desktop's settings so amd64 images run faster.

shipcheck keeps its results, logs and checkouts in `~/.local/state/shipcheck`. An older shipcheck kept them in `~/Library/Application Support/shipcheck`: the first command of a newer one moves that folder and says so. Then run `shipcheck install` once, so the daemon logs to the new folder. `shipcheck doctor` says when it still logs to the old one.

## Set up Linux

Tested on Debian 13 with Docker Engine 29 on x86_64.

For host mode, install bubblewrap, which runs each check in a sandbox: `sudo apt install bubblewrap` or `sudo dnf install bubblewrap`.

Ubuntu 23.10 and later let only programs with an AppArmor profile create user namespaces, and bubblewrap needs them. Allow bwrap alone with the profile apparmor ships, and leave the restriction on for everything else:

```sh
sudo apt install apparmor-profiles
sudo cp /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
```

`shipcheck doctor` runs bwrap once. When it fails and `/proc/sys/kernel/apparmor_restrict_unprivileged_userns` is 1, it prints these commands, or only the last one when the profile is in `/etc/apparmor.d` but not loaded. Without a working bubblewrap, host mode refuses to run.

1. For act mode, install [Docker Engine](https://docs.docker.com/engine/install/) from Docker's own package repository, which brings a current runc. shipcheck refuses runc before 1.2.8 or 1.3.3, which have known container escapes.

2. Let your user run Docker without sudo, then log out and in:

   ```sh
   sudo usermod -aG docker "$USER"
   ```

   Membership of the `docker` group is as strong as root on that machine. shipcheck uses the Docker socket to start jobs, and never gives the socket to a job.

3. Install shipcheck. The install script needs no other tool:

   ```sh
   curl -fsSL https://shipcheck.run/install.sh | sh
   ```

   Homebrew on Linux works too, and also installs act, git and the Docker client: `brew install gabrielkoerich/tap/shipcheck`. Without Homebrew, install git from your distribution and act from its [releases](https://github.com/nektos/act/releases).

4. Check it:

   ```sh
   shipcheck doctor
   ```

   It prints the command that fixes each line it marks `fix`, as on a Mac.

On ext4 there is no copy-on-write, so each job's checkout is a plain git worktree: a full copy of the files and a slower start. btrfs, or XFS with reflink, gives copy-on-write clones as on a Mac. Jobs run as `runner`, uid 1001, so shipcheck gives the checkout to that uid before each job and back to your user after it.

## Other ways to install

With cargo, from crates.io. This builds it with Rust 1.89 or later:

```sh
cargo install shipcheck
```

Or with [cargo-binstall](https://github.com/cargo-bins/cargo-binstall), which downloads the binary of the latest release, signed and notarized on macOS, with no Rust needed. [mise](https://mise.jdx.dev) uses it too:

```sh
cargo binstall shipcheck
mise use -g cargo:shipcheck
```

mise waits a while before it offers a new release, so right after one it can install the one before.

With the install script, on macOS or Linux, arm64 or x86_64:

```sh
curl -fsSL https://shipcheck.run/install.sh | sh
```

It downloads the binary from the latest [GitHub release](https://github.com/gabrielkoerich/shipcheck/releases), checks it against the release's `SHA256SUMS`, and installs it to `~/.local/bin`. Set `SHIPCHECK_INSTALL_DIR` to install it somewhere else.

Or build it from source, from the repository or from a clone, with Rust 1.89 or later:

```sh
cargo install --git https://github.com/gabrielkoerich/shipcheck shipcheck
git clone https://github.com/gabrielkoerich/shipcheck && cargo install --path shipcheck
```

A build with cargo, from crates.io or from source, gets a new signature each time, so the keychain asks again after it ([where shipcheck keeps its keys](#where-shipcheck-keeps-its-keys)).

## Post the checks

The [shipcheck App](https://github.com/apps/shipcheck-run) puts the checks on your PRs. Only a GitHub App can create checks, and a ruleset can require each check to come from that App, so an agent's own token cannot post a passing one ([require the checks](https://shipcheck.run/docs/branch-protection.md)). Your machine runs the checks, and shipcheck's service posts the result with the App. The App's key stays in the service, never on your machine.

The App has three permissions:

| Permission | For |
|---|---|
| Checks: write | Posting the job checks and the `shipcheck` summary |
| Pull requests: write | Listing a repository's open PRs for the daemon, finding a commit's PRs, learning who pushed a PR's head, and the [report comment](https://shipcheck.run/docs/how-it-works/#the-report-comment) |
| Metadata: read | Basic repository data, which every App has |

It has no Contents permission, so it cannot read or change your code.

### Install the App

1. Open [github.com/apps/shipcheck-run/installations/new](https://github.com/apps/shipcheck-run/installations/new). `shipcheck init` prints this link and opens it when you apply.
2. Pick your user or an organization. On an organization, an owner installs it, or approves the request of a member who asks.
3. Choose all repositories, or only the ones that use shipcheck. Public and private repositories work the same.

To add a repository later, open [github.com/settings/installations](https://github.com/settings/installations), then shipcheck-run, Configure. For an organization, open its settings, GitHub Apps, shipcheck-run, Configure.

On a private repository, the checks and their log tails are visible to everyone who can read the repository. On a public one, everyone sees them ([`logs`](https://shipcheck.run/docs/config.md)).

### Sign in each machine

Each machine that posts needs a machine token. You need write, maintain or admin access to the repository to post its checks.

```sh
shipcheck login
```

It prints a one-time code and the page github.com/login/device. In a terminal, Enter opens that page in the browser. Enter the code there and approve. shipcheck then says `signed in as <login>` and keeps the token, in the login keychain on macOS ([where shipcheck keeps its keys](#where-shipcheck-keeps-its-keys)). shipcheck's service revokes the GitHub token from the sign-in once it knows who you are, and keeps only a hash of the machine token.

`shipcheck init` and `shipcheck install` sign the machine in the same way when it cannot post yet and they run in a terminal. Without a terminal they print one line that says to run `shipcheck login` or set `SHIPCHECK_TOKEN`, and go on.

- A machine token stops working after 90 days without a post. Run `shipcheck login` again then.
- `shipcheck logout` revokes this machine's token and deletes it.
- The service receives the check's text and the report comment, never your code. The text holds the end of each job's log ([the checks on the PR](https://shipcheck.run/docs/how-it-works/#the-checks-on-the-pr)).

### Unattended machines

A VM, a CI runner or an agent box has no person to enter the code. Make a token for it on a machine where you can open a browser:

```sh
shipcheck token --name agent-vm
```

It prints a code to enter at github.com/login/device on stderr, as `login` does, then only the token on stdout. It does not touch this machine's own sign-in, and it keeps no copy. Put the token in the other machine's environment as `SHIPCHECK_TOKEN`, or in the agent's secret store. It lasts 90 days after its last post. `shipcheck logout` on that machine revokes it.

If a machine with a token is compromised, run `shipcheck logout --all` on any signed-in machine. It revokes every token of your GitHub user at once, this machine's too, so sign the others in again after. Each GitHub user can hold 50 live tokens, and `logout --all` also clears that limit.

### The daemon

The daemon posts with the same machine token. It runs a PR only when the GitHub login you signed in as, or a login `daemon_authors` lists, opened the PR and pushed its head ([when a PR changes on GitHub](https://shipcheck.run/docs/how-it-works/#when-a-pr-changes-on-github)). The service `init` and `install` start gets only `PATH` from your environment, so it uses the `shipcheck login` token, never `SHIPCHECK_TOKEN`. Until the machine is signed in, the daemon posts nothing and its log says to run `shipcheck login`.

### Check it

`shipcheck doctor` shows two lines about posting:

- `post` says which token posts: the `shipcheck login` token or the `SHIPCHECK_TOKEN` machine token. On a machine with neither, the line stays `ok` and says ``not signed in. Optional: `shipcheck login`, or SHIPCHECK_TOKEN from `shipcheck token`, lets `shipcheck post` put the check on PRs``.
- `app`, on a machine that can post, asks GitHub for the repository's open PRs. It says `installed on <owner>/<repo>, runs can post its checks`, or, when the App is not installed there or you have no access, marks it `fix` with `add <owner>/<repo> to the shipcheck App: https://github.com/settings/installations (shipcheck-run, Configure)`.

A post that fails leaves the same reason and fix in `shipcheck status`, `shipcheck run`, the push's output and the daemon's log.

## What works without the GitHub App

These work on any repository with `shipcheck.toml` on `main`, with no App:

- `shipcheck run` runs the checks locally and gives the verdict as an exit code.
- The result cache answers at once for a tree that already passed.
- The git hooks start runs in the background on commit or push.
- `shipcheck status` and `shipcheck logs` show the stored result and logs of a commit.

## Where shipcheck keeps its keys

shipcheck keeps two secrets on your machine: the machine token from `shipcheck login`, and the secrets act jobs get, one file per repository at `secrets/<owner>/<repo>.env` in `SHIPCHECK_HOME`, in act's `NAME=value` format. `shipcheck token` prints a token for another machine and keeps no copy. The App's private key lives only in shipcheck's service.

On macOS, `shipcheck login` writes its token straight into an item of your login keychain. A secrets file you write moves into an item the first time shipcheck reads it, and the file goes to the Trash. Only shipcheck reads an item without asking, so an agent running as you gets a keychain prompt instead of the secret. To change the secrets of a repository, write the file again. An item an older shipcheck kept under its old home, `~/Library/Application Support/shipcheck`, gets its new name the first time shipcheck reads it.

The keychain knows shipcheck by its code signature. Release builds, from Homebrew or the install script, are signed with one Developer ID and notarized by Apple, so an upgrade keeps access. A build from `cargo install` or from source gets a new signature each time: macOS then asks once per item whether the new shipcheck may read it. Run `shipcheck doctor` at the Mac and choose Always Allow. Choose it only for shipcheck, never for `security` or another program.

On Linux they stay files of mode `0600`, which any program you run can read. The Linux keyring has no per-program access, so it would not keep an agent out.
