Docs ›
Install with an AI agent
Rasputin’s install path is scriptable end to end, and this page is the contract for it — written for AI coding agents (Claude Code, Codex, and friends) and for the humans driving them. Everything here is also how you’d automate an install with plain shell, no AI involved.
If you’re a human who wants an agent to do the work, paste this into it:
Read https://rasputin.geekdojo.com/llms.txt and
https://rasputin.geekdojo.com/docs/agents/index.md, then help me install
Rasputin. Plan the flash with RASPUTIN_DRY_RUN=1 first and show me the plan;
only write to a disk after I confirm. My first node's hardware is:
<Raspberry Pi 4/5/CM5, or an Intel N100 / amd64 box>.
Or skip the prompt: this contract ships packaged as an agent skill in geekdojo/rasputin-agents.
- Claude Code:
/plugin marketplace add geekdojo/rasputin-agents, then/plugin install rasputin@geekdojo - Codex (or any Agent Skills client):
git clone https://github.com/geekdojo/rasputin-agents && cd rasputin-agents && codex— the skill andAGENTS.mdare auto-discovered
Machine-readable endpoints
| URL | What it is |
|---|---|
| /llms.txt | Index of everything on this page’s level — docs, manifests, install one-liner. |
| /releases.json | Latest stable versions + image download URLs. Regenerated daily and on every release. |
| OS manifest | Authoritative per-artifact SHA-256 checksums, sizes, and signer for the newest stable OS release. Stable URL — no API, no auth, no HTML scraping. |
| Firewall manifest | Same, for the firewall image. |
| /bootstrap.sh | The flasher itself — plain bash, commented, and the source of truth for the env contract below. |
| /rasputin-root-ca.pem | The public Rasputin root CA — the trust anchor for release signatures, and the same cert baked into every image at /etc/rasputin/trust/root-ca.pem. |
| Docs as markdown | Every docs page has a raw-markdown mirror at index.md — e.g. /docs/provisioning/index.md. Skip the HTML entirely. |
Pin a specific release by swapping latest/download for download/<tag> in the manifest
and image URLs.
Non-interactive install
bootstrap.sh prompts a human for three things; every prompt has an env-var override, so
an agent can drive it deterministically:
| Variable | Meaning |
|---|---|
RASPUTIN_ARCH | Target hardware: arm64 (Raspberry Pi 4/5/CM5) or amd64 (Intel N100 / any amd64 box). |
RASPUTIN_NODE_ID | Control-plane node id (default cp-1). Short lowercase name: letters, digits, hyphens. |
RASPUTIN_SSH_AUTHORIZED_KEY | Your SSH public key line. Or set RASPUTIN_SSH_KEY_FILE to a .pub path instead. |
RASPUTIN_RELEASE | Pin a release tag. Default: latest stable. |
RASPUTIN_DISK | Target device (e.g. /dev/disk4, /dev/sdb). Skips the disk-picker prompt. |
RASPUTIN_ASSUME_YES | =1 skips the typed flash confirmation. |
RASPUTIN_DRY_RUN | =1 prints the resolved plan (disk, image URL) and stops before any write. |
RASPUTIN_ALLOW_INTERNAL | =1 also offers internal disks. Dangerous; leave unset. |
The script verifies the image’s SHA-256 against the release manifest, refuses internal disks by default, writes the seed, and block-level reads it back. The recommended agent flow, in order:
# 1. Preflight — no writes, prove the plan to the user first.
curl -fsSL https://rasputin.geekdojo.com/bootstrap.sh | sudo \
RASPUTIN_ARCH=arm64 RASPUTIN_NODE_ID=cp-1 \
RASPUTIN_SSH_KEY_FILE=$HOME/.ssh/id_ed25519.pub \
RASPUTIN_DRY_RUN=1 bash
# 2. Flash — same command, dry-run swapped for the confirmed target disk.
curl -fsSL https://rasputin.geekdojo.com/bootstrap.sh | sudo \
RASPUTIN_ARCH=arm64 RASPUTIN_NODE_ID=cp-1 \
RASPUTIN_SSH_KEY_FILE=$HOME/.ssh/id_ed25519.pub \
RASPUTIN_DISK=/dev/disk4 RASPUTIN_ASSUME_YES=1 bash
Agents: always dry-run first and show the human the plan before flashing. Writing a disk image is destructive, and picking the wrong disk is the one mistake this page can’t undo for you.
Flashing by hand instead (Windows, or no script): the Download page has the manual steps, and a copy-exact seed template lives at /rasputin-seed.env.example. The full seed reference is Provisioning (markdown).
Verifying the install
After the node boots (first boot takes a few minutes — the browser refusing to connect during it is normal, not a failure):
curl -fsS http://rasputin.local/healthz
# → {"status":"ok"}
/healthz answers unauthenticated on plain HTTP as soon as the control plane is up. If
rasputin.local doesn’t resolve (routine on Windows without mDNS, and behind some
routers), find the host named rasputin in the router’s DHCP lease table and probe the IP
directly.
Beyond the probe, the trust chain is fetchable too: the cluster’s CA certificate is at
http://rasputin.local/mesh-ca.pem (unauthenticated by design — first-run trust is TOFU
on your own LAN), and http://rasputin.local lands on a trust page with per-OS
CA-install instructions before HTTPS sign-in.
Verifying signatures
The trust anchor is the Rasputin root CA:
/rasputin-root-ca.pem — CN=Rasputin Root CA, O=Geekdojo,
valid to 2046, SHA-256 fingerprint
67:7E:57:06:13:87:3E:08:A3:2C:F5:F4:52:76:10:33:8D:57:5A:C4:E9:67:5C:CD:91:C4:43:FE:BD:27:C1:B9.
Cross-check the fingerprint with openssl x509 -in rasputin-root-ca.pem -noout -fingerprint -sha256.
What’s signed, precisely:
Firewall artifacts — the
-ab.img.gzinitial-flash disk and the.rootfsOTA artifact each ship a detached DER CMS.sigalongside them on the release. The signature embeds the leaf and intermediate certs, so the root alone completes the chain:curl -fsSLO https://rasputin.geekdojo.com/rasputin-root-ca.pem openssl cms -verify -binary -inform DER \ -in rasputin-fw-n100-<version>-ab.img.gz.sig \ -content rasputin-fw-n100-<version>-ab.img.gz \ -CAfile rasputin-root-ca.pem -out /dev/nullOS update bundles (
.raucb) are CMS-signed by RAUC; on a Linux box withraucinstalled:rauc info --keyring rasputin-root-ca.pem rasputin-os-<sku>-<version>.raucb. Devices do this on every update against the baked-in root — you’re re-running their check.OS flashable images (
.img.xz) carry no per-image.sig; they chain to the root through the signed release manifest.manifest.jsonships a detached DER CMS signature (manifest.json.sig) on the release — verify it, then check the image’s SHA-256 against the manifest’simageSha256:openssl cms -verify -binary -inform DER \ -in manifest.json.sig \ -content manifest.json \ -CAfile rasputin-root-ca.pem -out /dev/nullReleases cut before the manifest signature landed have no
manifest.json.sig; for those, the HTTPS-fetchedimageSha256is the whole integrity story.
Where the agent must hand off
Two steps are deliberately human-only:
- Passkey registration. Sign-in is passkey-only — Touch ID, Windows Hello, or a
security key at
https://rasputin.local/setup. No passwords exist, so there is nothing for an agent to type. Walk the human to the browser and wait. - Adding more nodes. The dashboard’s Add-node wizard mints each new node’s one-liner
with an id-bound join token baked in. Don’t hand-construct compute seeds
(
RASPUTIN_NATS_URL,RASPUTIN_CP_JOIN_TOKEN) — run the wizard’s one-liner as given.
Known failure modes
| Symptom | Cause and fix |
|---|---|
| Browser says the certificate is expired on a fresh node | The node’s clock is wrong — common on boards with no battery-backed clock (Pi 5) when NTP is broken. See Time sync. |
rasputin.local never resolves | No mDNS on the client (Windows) or the router blocks it. Use the DHCP-lease IP for host rasputin. |
| Seed didn’t take; first boot waits forever | The seed must be named rasputin-seed.env, at the root of the FAT volume labeled RASPUTIN-OS — go by label, not size; the Pi image has several FAT partitions. The SSH key line must be double-quoted. |
| First hour took more than an hour | That’s a bug by definition. File it — blunt reports are the valuable kind. |
It’s pre-alpha: image layouts and update formats still change without notice, and this contract can change with them — re-read this page rather than caching it. Something wrong or missing here blocks every agent-driven install, so docs bugs count double.
Something wrong or missing on this page? Tell us — docs bugs count too.