Run the GPUFarm Host daemon
gpufarm-host is a Rust daemon that turns a Linux machine with NVIDIA GPUs into GPUFarm capacity. It detects GPUs, proves them with a benchmark challenge, keeps a heartbeat, and runs accepted jobs in a locked-down container. It never runs in a browser, and its signing key never leaves the machine's keystore.Requirements#
| Component | Requirement |
|---|---|
| OS | Linux x86_64 or aarch64 (systemd recommended). macOS builds run only detect and the simulated development host — there is no CUDA there. |
| GPU | Any NVIDIA GPU the driver lists; models are not hardcoded. nvidia-smi must work (driver R525+ recommended); NVML is cross-checked when present and any disagreement excludes that GPU. MIG-partitioned GPUs are skipped; AMD and Apple GPUs are detected but not schedulable. |
| Containers | Docker Engine 20.10+ and the NVIDIA Container Toolkit (required for --gpus). gVisor (runsc with --nvproxy) is optional and preferred. |
| Disk | Room for job images and data; by default an offer is declined when the work directory has under 10 GiB free. |
| Network | Outbound HTTPS to the GPUFarm server; WebSocket to the coordinator when available (falls back to HTTP polling). No inbound ports. |
| Wallet | A wallet to sign in at /host. It approves the machine and receives payouts; it never touches the machine. |
Docker, NVIDIA Container Toolkit and gVisor#
Install Docker Engine and the NVIDIA Container Toolkit from their official repositories, then wire the toolkit into Docker:
# NVIDIA Container Toolkit (after adding NVIDIA's apt/yum repository)
sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# sanity check: the container must see your GPUs
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smiDon't make nvidia Docker's default runtime — GPUFarm only needs --gpus, and gpufarm-host detect warns when it is the default because containers that never asked for GPUs could get them.
Optional but recommended: register gVisor with GPU proxying so jobs run under a user-space kernel. With [sandbox] gvisor = "auto" the daemon uses it when runsc is registered with --nvproxy; "require" refuses jobs without it.
{
"runtimes": {
"runsc": { "path": "/usr/local/bin/runsc", "runtimeArgs": ["--nvproxy"] }
}
}docker group is root-equivalent on the host. Run the daemon as a dedicated user on a machine you use for GPUFarm, and keep personal data off it.Install and verify a release#
curl … | sh one-liner. Download the installer, read it, then run it — and it refuses to install anything whose checksum and signature don't both verify.RELEASE_SIGNING_PUBLIC_KEY isn't configured here, so there is no release key to verify against. Build from source (below) for now; the steps on this page apply unchanged once releases are published.Releases are published per target triple (x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu). Each binary carries two independent proofs:
| Published file | Contents |
|---|---|
<base>/latest | The current version, e.g. 0.1.0 (the installer reads it when --version is omitted). |
<base>/<version>/gpufarm-host-<target> | The binary itself — not an archive. |
gpufarm-host-<target>.sha256 | Its SHA-256 in sha256sum format (hex digest, two spaces, file name), so sha256sum -c checks it. |
gpufarm-host-<target>.sig | Hex Ed25519 signature by the GPUFarm release key over the exact bytes gpufarm-host <version> <sha256 hex> (no trailing newline). Binding the version means an old signed binary can't be relabeled as a newer release. |
<base>/manifest.json | The self-updater manifest (see Signed updates). |
<base>/install.sh | The installer (host/install/install.sh in the repository) with the release key embedded. |
Installer: download, read, run
The installer needs OpenSSL 3. It checks the SHA-256, then the Ed25519 signature, and only then installs /usr/local/bin/gpufarm-host, creates the gpufarm system user, a root-only key passphrase at /etc/gpufarm-host/key-passphrase and the hardened systemd unit (not started). Any mismatch, a missing signature or a missing key aborts before anything is installed.
curl --proto '=https' --tlsv1.2 -fsSLO https://<release host>/gpufarm-host/install.sh
less install.sh # read it first
sudo sh install.sh --base-url https://<release host>/gpufarm-host --public-key <release key hex>
# air-gapped: a directory holding gpufarm-host-<target>, .sha256 and .sig
sudo sh install.sh --from-dir ./0.1.0 --version 0.1.0 --public-key <release key hex>| Option | Effect |
|---|---|
--base-url URL | HTTPS release base (or GPUFARM_RELEASE_BASE_URL). |
--from-dir DIR | Verify and install pre-downloaded files instead; requires --version. |
--version X.Y.Z | Version to install (or GPUFARM_VERSION); default: <base>/latest. |
--public-key HEX | Release signing key, 64 hex characters (or GPUFARM_RELEASE_SIGNING_PUBLIC_KEY). Without a key the installer refuses to run. |
--prefix DIR | Install directory (default /usr/local/bin). |
--no-system | Only verify and install the binary: no service user, no passphrase, no systemd unit (also the only mode off Linux). |
--with-docker-group | Add the service user to the docker group — root-equivalent; read the warning above first. |
Verify a release by hand
The same two checks the installer and the self-updater run, with nothing but coreutils, xxd and OpenSSL 3:
VERSION=0.1.0; TARGET=x86_64-unknown-linux-gnu
KEY=<release key hex>
sha256sum -c gpufarm-host-$TARGET.sha256 # gpufarm-host-$TARGET: OK
SHA=$(sha256sum gpufarm-host-$TARGET | cut -d' ' -f1)
printf 'gpufarm-host %s %s' "$VERSION" "$SHA" > msg # the exact signed bytes
xxd -r -p gpufarm-host-$TARGET.sig > sig.bin
printf '302a300506032b6570032100%s' "$KEY" | xxd -r -p | openssl pkey -pubin -inform DER -out gpufarm-release.pem
openssl pkeyutl -verify -pubin -inkey gpufarm-release.pem -rawin -in msg -sigfile sig.bin
# only after "Signature Verified Successfully":
sudo install -m 0755 gpufarm-host-$TARGET /usr/local/bin/gpufarm-hostBuild from source
Rust 1.88 or newer, from a checkout of the GPUFarm repository (the daemon is the crate in host/):
cd host
cargo build --release --locked # target/release/gpufarm-host
cargo test # unit + end-to-end tests; no GPU or Docker needed
sudo install -m 0755 target/release/gpufarm-host /usr/local/bin/gpufarm-host
gpufarm-host version # version, target and the embedded release key
# embed the release key so `gpufarm-host update` can verify future releases
RELEASE_SIGNING_PUBLIC_KEY=<release key hex> cargo build --release --lockedA build without an embedded key can't self-update: it refuses every update unless GPUFARM_RELEASE_SIGNING_PUBLIC_KEY is set at runtime.
Pair the machine#
- On first pairing the daemon generates its own secp256k1 host key and stores it in the OS keystore (Secret Service / Keychain) or, on headless servers, an encrypted file (
GPUFARM_KEYSTORE=filewithGPUFARM_KEY_PASSPHRASEof 12+ characters, or the systemd credentialgpufarm-key-passphrase). - Pairing sends the host key address and the machine's facts (CPU, RAM, OS, container runtime) to
https://robingpu.farm, signed by that key, and prints a one-time code such asABCD-EFGHvalid for 15 minutes. - Open the printed link (/host/pair) while signed in with the wallet that owns the machine, choose a name and a coarse region, and approve. From then on the daemon logs in with a signed challenge — no password, no API key on disk.
- Pairing never asks for the machine's hostname, username or IP address, and none are shown publicly.
$ gpufarm-host detect # what the daemon sees; nothing is sent anywhere
$ gpufarm-host pair --server https://robingpu.farm --name "Rig-01"
Pairing code ABCD-EFGH
Approve at https://robingpu.farm/host/pair?code=ABCD-EFGH
Expires 2026-10-01T12:15:00.000Z (14 min)
Open the link, sign in with the wallet that owns this machine, and approve the code.
Waiting for approval… (Ctrl-C to cancel)
Approved. This machine is paired as 6f1c…
Start the daemon with: gpufarm-host run--name is optional (1–40 printable characters). A machine that is already paired refuses to pair again until you run gpufarm-host unpair --yes. With the systemd unit, pair as the service user so the key lands in its data directory — see Run as a service.
Benchmark verification#
No GPU is listed until it passes a server-issued benchmark challenge. The model name a driver reports is never trusted on its own. The benchmark runs in the same sandbox as jobs, using an official PyTorch CUDA image pinned by digest (a CUDA 12.6 build for GPUs older than compute capability 7.5), with only the GPU under test visible and no network.
| Measurement | How |
|---|---|
| FP32 / FP16 matmul | n×n matrices generated from the server's random seed; the host reports 16 cells of C = A·B that the server recomputes exactly, so results can't be precomputed or faked. |
| Memory bandwidth | Device-to-device copy of ≥ 1 GiB. |
| Stability | Coefficient of variation over ≥ 10 sustained FP16 runs across ≥ 30 s. |
| Inference proxy | Tokens/s of a fixed random-weight transformer stack — no downloads, no network. |
| Telemetry | Temperature, utilization, power and throttling sampled for that GPU UUID during the run. |
Raw and normalized numbers are stored — never one opaque score. Known GPU classes are tiered against reference ranges (S, A, B, C); a claimed H100 that performs like a 3060 is flagged and not schedulable. UUID reuse across machines, inconsistent re-benchmarks (> 25% drift) and runs that show no GPU utilization are flagged too. GPUs are re-benchmarked periodically and after driver changes.
gpufarm-host run benchmarks automatically whenever registration says a GPU needs it. To benchmark on demand, stop the daemon first (it holds the lock), then run gpufarm-host benchmark (all GPUs) or gpufarm-host benchmark --gpu GPU-…: it logs in, registers, runs the challenge per GPU and prints each verdict and tier.
Run as a service#
The installer writes this unit to /etc/systemd/system/gpufarm-host.service (abridged — the full file also locks down kernel tunables, namespaces, system calls and devices to the NVIDIA nodes):
[Service]
User=gpufarm
Group=gpufarm
SupplementaryGroups=docker
ExecStart=/usr/local/bin/gpufarm-host run
Environment=GPUFARM_DATA_DIR=/var/lib/gpufarm-host
Environment=GPUFARM_WORK_DIR=/var/cache/gpufarm-host
LoadCredential=gpufarm-key-passphrase:/etc/gpufarm-host/key-passphrase
StateDirectory=gpufarm-host
CacheDirectory=gpufarm-host
Restart=always
RestartSec=10
RestartPreventExitStatus=3 # machine revoked: restarting can't help
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yessudo systemd-run --pty --wait --collect --uid=gpufarm \
-p StateDirectory=gpufarm-host -p CacheDirectory=gpufarm-host \
-p LoadCredential=gpufarm-key-passphrase:/etc/gpufarm-host/key-passphrase \
--setenv=GPUFARM_DATA_DIR=/var/lib/gpufarm-host --setenv=GPUFARM_WORK_DIR=/var/cache/gpufarm-host \
/usr/local/bin/gpufarm-host pair --server https://robingpu.farm
sudo systemctl enable --now gpufarm-host
journalctl -u gpufarm-host -fInstalled without the installer? Create the passphrase yourself first: sudo sh -c 'umask 077; mkdir -p /etc/gpufarm-host; openssl rand -hex 32 > /etc/gpufarm-host/key-passphrase'. The daemon takes its directories from GPUFARM_DATA_DIR / GPUFARM_WORK_DIR, else systemd's STATE_DIRECTORY / CACHE_DIRECTORY, and refuses to start if one contains the other, so the key directory can never be reached through a job mount. The passphrase is read once and removed from the environment, so docker and nvidia-smi never inherit it.
Signed updates#
gpufarm-host update reads a release manifest; the manifest itself is not trusted. For this platform's artifact the daemon verifies the Ed25519 signature over gpufarm-host <version> <sha256> before downloading and the SHA-256 of the downloaded bytes after, and refuses downgrades, equal versions, other targets, plain HTTP, binaries over 256 MiB and any update at all when no release key is built in (or set via GPUFARM_RELEASE_SIGNING_PUBLIC_KEY). The previous binary is kept as gpufarm-host.prev.
{
"version": "0.1.0",
"notes": "What changed",
"artifacts": [
{ "target": "x86_64-unknown-linux-gnu",
"url": "https://<release host>/gpufarm-host/0.1.0/gpufarm-host-x86_64-unknown-linux-gnu",
"sha256": "<hex>", "signature": "<hex>" }
]
}gpufarm-host update --check --manifest-url https://<release host>/gpufarm-host/manifest.json
sudo gpufarm-host update --manifest-url https://<release host>/gpufarm-host/manifest.json
sudo systemctl restart gpufarm-hostThe manifest URL can also come from GPUFARM_RELEASE_MANIFEST_URL or [update] manifest_url. With [update] auto = true the running daemon checks every check_interval_hours, installs a verified update only while idle and exits with code 75 for its supervisor to restart it. Under the hardened unit the binary directory is read-only to the service user, so an automatic install is refused and logged — update with sudo gpufarm-host update instead.
Configuration#
config.toml lives in the data directory (written by pair) and never holds secrets. Unknown keys are rejected. The owner policy you set in the dashboard is enforced by the scheduler; [policy] adds machine-local limits the daemon applies to every offer.
server = "https://robingpu.farm"
heartbeat_interval_secs = 10 # 5..=15
[policy]
max_temp_c = 90.0 # decline when an assigned GPU is hotter
# max_job_runtime_secs = 86400
# min_rate_micro_usd_per_gpu_hour = 400000
# workload_allowlist = ["inference", "rendering"] # omit to accept every type
reserved_gpu_uuids = [] # GPUs kept for your own use
allow_egress = true # allow jobs that request allowlisted outbound HTTPS
min_free_disk_bytes = 10737418240
# allowed_registries = ["docker.io", "ghcr.io", "*.pkg.dev"] # unset = any registry; others are declined
[sandbox]
gvisor = "auto" # auto | require | off
runsc_runtime = "runsc"
pids_limit = 4096
max_shm_bytes = 17179869184
image_pull_timeout_secs = 3600
egress_proxy_port = 3128
keep_job_dirs = false # job dirs may hold customer data
[benchmark]
stability_secs = 30
inference = true
[update]
# manifest_url = "https://<release host>/gpufarm-host/manifest.json"
auto = false # install verified updates while idle, then exit 75
check_interval_hours = 6CLI reference#
Every subcommand prints its own --help; gpufarm-host --version prints the version.
| Command | What it does |
|---|---|
gpufarm-host detect [--json] | Prints GPUs (UUID, VRAM, driver, CUDA, compute capability, health), excluded GPUs with the reason, and the container runtime report. Sends nothing anywhere. |
gpufarm-host pair --server <url> [--name <name>] [--simulate] | Creates the host key (first run), starts pairing, prints the approval code and link, and waits for approval. |
gpufarm-host run [--server <url>] [--simulate] | Logs in, registers the machine and GPUs, runs required benchmarks, then heartbeats and executes offers until stopped (SIGTERM). |
gpufarm-host benchmark [--gpu <uuid>] [--server <url>] [--simulate] | Benchmarks now (server challenge → measurement → signed report). Stop the daemon first. |
gpufarm-host status [--json] | Pairing, server, host key storage, and the live daemon state (heartbeat age, GPUs, jobs) when it is running. |
gpufarm-host unpair --yes | Deletes the host key and forgets the pairing. Without --yes it only says what it would delete (exit 2). Revoke the machine in the dashboard too. |
gpufarm-host update [--check] [--manifest-url <url>] | Checks for, or verifies and installs, a signed release (see Signed updates). |
gpufarm-host version | Version, target triple and the embedded release-signing key (or that self-update is disabled for this build). |
--simulate | Development only: two fixture RTX 4090s that run only the GPUFarm selftest and benchmark on the CPU. Refused unless the server's chain is the local chain (31337); never shown on Robinhood Chain. |
| Environment | Purpose |
|---|---|
GPUFARM_SERVER | Default for --server (pair, run, benchmark). |
GPUFARM_DATA_DIR | Config, encrypted key file, status (default ~/.local/share/gpufarm-host; systemd StateDirectory). |
GPUFARM_WORK_DIR | Per-job scratch (default ~/.cache/gpufarm-host; systemd CacheDirectory). Must not overlap the data dir. |
GPUFARM_KEYSTORE | auto (OS keystore, else encrypted file), os or file. |
GPUFARM_KEY_PASSPHRASE | Unlocks the encrypted key file (12+ characters); or the systemd credential gpufarm-key-passphrase. |
GPUFARM_RELEASE_MANIFEST_URL | Default for update --manifest-url. |
GPUFARM_RELEASE_SIGNING_PUBLIC_KEY | Overrides the compiled-in release key. |
GPUFARM_LOG | Log filter (info, debug, …). Logs are structured JSON on stderr. |
Exit codes: 0 stopped normally · 1 error · 2 usage or missing confirmation · 3 the coordinator no longer accepts this machine (revoked or unpaired; the unit doesn't restart on it) · 75 a verified update was installed and the supervisor should restart the daemon.
Pricing, policy and earnings#
- Set a price per GPU class and your autoscheduling policy (temperature cap, available hours, minimum rate, workload allowlist, max runtime, reserved GPUs) in the host dashboard.
- Earnings accrue per verified, billable GPU-second at the rate locked in the offer you accepted. Failed or abandoned attempts aren't paid.
- After the customer's challenge window (or their early approval), payouts are credited onchain to your payout wallet in the RewardDistributor; claim them any time. See the trust model.