Three paths lead to feint, and they do not serve the same need. The container image requires nothing to install and suits a CI; the verified binary is the recommended path on a workstation, and the only one that gives access to real machines; the GitHub action installs that binary in a pipeline, verifying its checksum before running it. This page covers all three, and the integrity verification that goes with them.
What you will get
By the end of this page, feint version will answer v0.13.0 on your
machine, installed through the channel matching your verification
requirements, and you will know how to run it as a service in a pipeline.
- Choose the installation channel that fits your use.
- Verify who published the binary before trusting it.
- Run the emulator as a service in GitHub Actions or GitLab CI.
- Know when a new version is out, with no forced network call.
Which path to choose
| Need | Path | Real machines |
|---|---|---|
| Try it in one command | Container image | no |
| CI pipeline with a service | Container image | no |
| GitHub Actions without containers | setup-feint action | no |
| Personal workstation, one command | Binary through Homebrew | yes, with Incus |
| Tools pinned per project | Binary through mise | yes, with Incus |
| Sensitive or shared machine | Binary from the verified release | yes, with Incus |
| Real machines, SSH, OVN isolation | Binary, whichever channel | yes |
The image runs no machines, and that is stated rather than hidden: it runs
feint serve --vm off, because real machines are a property of the binary on an
Incus host. An image claiming otherwise would be the half-truth this project
refuses elsewhere.
Installing the binary
Three paths lead to the same binary, and they do not verify the same thing. The table below states what each one checks before letting you run the program, because that is the only criterion that truly separates them: they all install the same published version.
| Path | What is verified | For whom |
|---|---|---|
| Verified release | Sigstore signature of the list, then checksum of the bytes | a sensitive machine, a shared one, a runner |
| Homebrew | the SHA-256 sum the formula carries, not the signature | a personal workstation, in one command |
| mise | the GitHub provenance attestations, verified at install time | anyone already pinning tools per project |
Download the version's binary, verify who published it, then verify the bytes. The order matters: a checksum proves nothing if you do not know who produced the list containing it.
-
Fetch the version's artefacts.
Fenêtre de terminal base=https://github.com/stephrobert/feint/releases/download/v0.13.0curl -fsSLO "$base/feint-linux-amd64"curl -fsSLO "$base/checksums.txt"curl -fsSLO "$base/checksums.txt.cosign.bundle" -
Verify the signature of the checksum list, before trusting a single line of its content.
Fenêtre de terminal cosign verify-blob --bundle checksums.txt.cosign.bundle \--certificate-identity-regexp '^https://github\.com/stephrobert/feint/\.github/workflows/release\.yml@refs/tags/v' \--certificate-oidc-issuer https://token.actions.githubusercontent.com \checksums.txtThe expected output is two words:
Verified OK. Any other answer must stop the installation. Note the precision of the identity pattern: it names the release workflow and the tag prefix, where a loose pattern would accept any workflow in the repository. A compromised repository whose secondary workflow signed a binary would not pass this check. -
Verify the bytes against the signed list.
Fenêtre de terminal sha256sum -c checksums.txt --ignore-missingThe expected result is
feint-linux-amd64: OK. Without--ignore-missing, the command fails on every platform you did not download, which wrongly looks like a corrupted binary. -
Install the binary and confirm the version.
Fenêtre de terminal install -m 0755 feint-linux-amd64 ~/.local/bin/feintfeint versionThe output must print
v0.13.0.
If you prefer provenance to the signature, gh verifies which workflow and
which commit produced the binary:
gh attestation verify feint-linux-amd64 --repo stephrobert/feintThe shortest path, on macOS as on Linux.
brew install stephrobert/feint/feintfeint versionThe formula is produced by the release itself, from the sums the release signed: the value Homebrew checks is therefore the official binary's.
One nuance worth knowing rather than ignoring: Homebrew checks the sum, not the
signature. It tells you the bytes match what the formula announces, not
who published them. That is enough on a personal workstation, and it is
precisely the difference with the previous tab. No version is named here on
purpose: brew resolves the current formula from the formula repository.
If you already manage your tools with mise, feint installs like the others, with its version pinned in your configuration.
mise use -g github:stephrobert/feint@0.13.0feint versionThe github backend does more than download: it verifies the provenance
attestations published by the repository before installing.
mise github:stephrobert/feint@0.13.0 [2/3] ✓ GitHub attestations verifiedmise github:stephrobert/feint@0.13.0 ✓ installedv0.13.0To try it without installing anything permanently, mise x runs the
requested version for the duration of a single command:
mise x github:stephrobert/feint@0.13.0 -- feint versionThe ubi: backend still works and gives the same binary, but mise flags it
as deprecated in favour of github:.
Running the emulator in a container
An image is published with every version, and it serves the control plane alone. This is the shortest path: one service, one port, no binary to install on the machine.
docker run --rm -p 127.0.0.1:4599:4599 \ ghcr.io/stephrobert/feint:v0.13.0@sha256:d46639ac7c7a0fa2b6b69d0c5d74bdca0757e3124675563209e8ada58e6a6040curl -s http://127.0.0.1:4599/_feint/health | jq -c '{status, machines}'{"status":"ok","machines":"none"}In a pipeline, the image is declared as an ordinary service, and no secret needs adding: that is the whole point for a team refusing to put cloud credentials in its CI.
services: feint: image: ghcr.io/stephrobert/feint:v0.13.0@sha256:d46639ac7c7a0fa2b6b69d0c5d74bdca0757e3124675563209e8ada58e6a6040 ports: - 4599:4599The runner holds the steps until the image's healthcheck answers, so the first step can talk to the emulator immediately.
services: - name: ghcr.io/stephrobert/feint:v0.13.0@sha256:d46639ac7c7a0fa2b6b69d0c5d74bdca0757e3124675563209e8ada58e6a6040 alias: feintWithout containers, the official action installs the published binary, verifies its checksum before running it, and waits for the emulator to answer.
- uses: stephrobert/setup-feint@b7eba1d4fcaccf65cf9124bf97a0d995996709b9 # v1.0.0 with: version: 0.13.0 provider: scaleway # exports what the official client expectsThe image is verified like the binaries, with the same workflow identity:
cosign verify ghcr.io/stephrobert/feint:v0.13.0 \ --certificate-identity-regexp '^https://github\.com/stephrobert/feint/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.comKnowing when a new version is out
feint version --check asks GitHub whether a newer version exists, and prints
the command to install it. Two principles govern that command: nothing goes on
the network until the flag is typed, and the binary never updates itself.
feint version --checkWhen a newer version is published, the output gives the installation command
pinned to that exact version. It names the version, never latest: a mutable
reference downloads whatever is newest at call time, and that content can no longer
be held against the checksum printed just below it. If you are already up to
date, the command recalls your version then concludes in one line:
v0.13.0this is the latest releaseA binary built from source carries dev, and an installation through
go install carries a timestamped pseudo-version. Neither compares to a
version tag, so the tool announces the latest release without claiming your
copy is out of date.
Key takeaways
- Three paths: the image to try it and for CI, the binary for a workstation, the action for GitHub Actions.
- The image runs no machines: real machines stay a property of the binary on an Incus host.
- Verification precedes execution: signature of the list, then checksums, then installation.
- The identity pattern names the release workflow, not merely the repository.
- No
latest: pin the tag, and the digest in a pipeline. feint version --checkonly goes on the network when you ask, and never updates by itself.