Saphan StudioDocs
Deployment

An install, end to end

One command, what it verifies before it writes anything, the line it prints, and the wizard it hands you — the whole path in the order you meet it.

This is the whole path, in the order you meet it. Each step links the page that goes deeper.

Measured on both supported platformslinux-amd64 and darwin-arm64 — over a real HTTPS server with certificate and hostname validation on. Where a step below states an outcome, that is the outcome that was observed on both.

1 · One command

curl -fsSL https://saphan.ai/install.sh | sh

That is the install. There is no address to look up, no port to pick and nothing to compose — the line is the same on every machine you install.

Run it on each machine you are adding. It detects the platform, fetches the machine payload, verifies every binary before anything lands, and ends by printing the address you set the machine up at.

2 · The shell is sh, and that is not a detail

The script's shebang is #!/bin/sh and it is POSIX, self-contained, and was run end to end under dash — so | sh is right and | bash would contradict the file. It matters because sh exists where bash does not: a minimal container, an Alpine image, a stripped host.

To pass a flag through the pipe you need -s --:

curl -fsSL https://saphan.ai/install.sh | sh -s -- --prefix <dir>

| sh --prefix … does not pass the flag to the script — the shell takes it. This is the one place where a correct-looking line does something else.

3 · What it verifies before it writes anything

In this order, and each step is a request you can see rather than a promise the script makes:

  1. install.sh itself, over validated TLS;
  2. SHA256SUMS from the same route;
  3. each binary of the machine payload — saphan, saphan-agent, saphan-gateway.

Each binary is checked against the manifest before anything is installed, and a mismatch refuses with nothing installed. On both platforms the digest of what landed equalled the digest of what was served.

The copy into ~/.saphan/bin/ is a same-directory temp file and a rename — never an in-place overwrite, which on macOS can poison the per-inode code-signature cache and kill the next run.

4 · Two streams, and why you can use them

stdout carries exactly one line — the next command. stderr carries the narration. So this works:

NEXT=$(curl -fsSL https://saphan.ai/install.sh | sh)

If you watch the two streams merged over an ssh session, the command can appear before the sentence introducing it. That is the multiplexing of your ssh connection reordering two streams, not the installer — measured again with both streams written to one file on the host, and the order is correct there.

5 · It offers the next act rather than stopping at "installed"

Once the binaries are in place the installer offers to generate this machine's certificate signing request — the first act of admitting it to a fleet:

Machine id for this CSR [<hostname>]:
Generate a machine CSR now? [Y/n]

Declining prints the command to run later instead. Piped through curl | sh with no controlling terminal the prompt is skipped outright rather than hanging. If a device key already exists, the offer is skipped — that machine has already begun its admission.

6 · It ends by naming the address you set the machine up at

The last thing a network install prints is the two commands that bring the console up and the address to open. --serve runs them for you instead of printing them.

⇒ From there the machine is configured in a browser: First run: setting the machine up at /setup — the window that opens and shuts, the six screens, the ceremony that does not repeat, and the one step that is deliberately left to your hand.

7 · Then check what you got

Verifying an install — and read that page rather than trusting a version string, because a version check has been observed passing on a binary that behaved as older code.

If the command refuses

Every refusal names what it measured and what to do about it. Nothing is installed when one fires — a refused install does not half-apply, so you fix the thing it named and run the same line again.

The one case worth knowing in advance is a checksum mismatch. The installer verifies every binary against the manifest before it writes anything, and on a mismatch it refuses with nothing installed rather than continuing. That is the check working, not a broken download.