Guided installer (AWS Nitro Enclaves)
The fastest way to run the Edge in attested mode. One script asks for what it needs, creates everything in your AWS account, and finishes by printing the endpoint your application calls. There are two, which build the same deployment:
| Download | Runs on | |
|---|---|---|
| macOS (and Linux) | chainos-edge-nitro.sh | The bash macOS ships, any Linux, AWS CloudShell |
| Windows | chainos-edge-nitro.ps1 | Windows PowerShell 5.1 (every supported Windows) or PowerShell 7 |
Or fetch it from a terminal, as below.
macOS (and Linux)
curl -fsSLO https://docs.chainos.cloud/install/chainos-edge-nitro.sh
bash chainos-edge-nitro.sh
Needs bash (the one macOS ships is enough) and curl. If the AWS CLI v2 is missing, it
offers to install it into ~/.local, without sudo. In AWS CloudShell it uses CloudShell's own
credentials.
Windows
Invoke-WebRequest https://docs.chainos.cloud/install/chainos-edge-nitro.ps1 -OutFile chainos-edge-nitro.ps1
powershell -ExecutionPolicy Bypass -File .\chainos-edge-nitro.ps1
If the AWS CLI v2 is missing, it installs the official MSI from awscli.amazonaws.com; Windows
asks for administrator approval for that step only. -ExecutionPolicy Bypass applies to this one run:
Windows otherwise refuses an unsigned script downloaded from the internet.
Read whichever you use before you run it — it is what will create resources in your account. The commands they run on the instance are identical: both are generated from one source.
What it asks
| Question | Notes |
|---|---|
| AWS credentials | The credentials already in effect (an instance role, CloudShell, a default profile), an existing profile, a new access key, or AWS IAM Identity Center (SSO). It checks your permissions first and lists any that are missing |
| Region | Any region with an enclave-capable instance type |
| Seed | Seeds sealed by earlier installs in that account and region, or a new one — see below |
| Deployment name | For a new seed. Every resource is named from it |
| Release | The chainos-edge version, from Docker Hub. The newest is the default |
| API key | Your flk_live_… or flk_test_… key; it decides the environment. Stored as an SSM SecureString |
| VPC and subnets | Public subnets get a public IP for outbound traffic only; private ones need a NAT gateway |
| Who may reach the Edge | A CIDR inside your network. Never 0.0.0.0/0 |
| Load balancer | Optional internal NLB for a stable private DNS name |
| Spend caps | The autonomous-spend policy, sealed with the seed |
| Recovery principal | The IAM role allowed to open the recovery copy — ideally a break-glass role |
What it creates
- An S3 bucket, private and versioned, holding the two sealed envelopes, the enclave image and its measurements. Its policy forbids deleting the envelopes.
- Two KMS keys. The seal key releases the seed only to the enclave whose PCR0 matches the image built for you. The recovery key opens only for the principal you named. Neither key lets IAM policies grant decryption: an administrator can manage them, not use them.
- An enclave-enabled EC2 instance in an Auto Scaling group of one, in an ECS cluster, running the Edge as an unprivileged container beside a small launcher that starts the enclave.
- A security group opening the local API (port 8787) to your CIDR only, and optionally an internal Network Load Balancer.
- Three IAM roles (instance, task, execution) and a CloudWatch log group.
An existing seed, or a new one
Before generating anything, the installer looks in KMS for seeds sealed by earlier installs in the account and region, and lists them:
Found 1. Using one keeps its wallet — the same addresses and funds:
1) chainos-edge key 2d4f5c48… enabled live identity 8dac5484… created 2026-10-11
envelopes in s3://chainos-edge-123456789012-eu-west-1
2) Generate a new seed — a new wallet with new addresses, beside the ones above
Reusing is the default, because a new seed is a new wallet and cannot be undone by choosing again. Choosing an existing seed:
- reinstalls that deployment around it — same identity, same addresses — and resumes anything half-built;
- if its keys are scheduled for deletion, offers to cancel the deletion and re-enable them;
- if its bucket is gone, asks for the copies the installer saved (
<name>-install/primary.sealed.jsonandrecovery.sealed.json), checks they were sealed under that seed's keys, and puts them back; - keeps the spend policy sealed in it, and refuses an API key for the other environment.
Only keys this installer made are listed: tagged with their deployment, with the recovery key beside them. A new seed under a name that already has one is refused.
Nobody sees the recovery phrase
The phrase is generated on the instance, inside the seal-mnemonic --generate process, and
sealed immediately — twice:
- For the enclave, under the seal key. Only an attested enclave running the exact image can decrypt it.
- For recovery, under the recovery key. This is the only backup of a phrase nobody wrote down, so the installer will not generate one without it.
It is never printed, logged or written to disk in plaintext. What you get is the Edge's identity fingerprint and its first addresses — public values, to check against the portal.
After sealing, the installer pins the seal key to the enclave measurement and removes the instance's ability to seal or replace envelopes.
Keep recovery.sealed.json — the installer saves a copy next to where you ran it — somewhere
outside this AWS account, and never delete the recovery key while the wallet holds funds. Alert
on any kms:Decrypt with the recovery key in CloudTrail: it should only ever be a reseal or a
paper backup you made yourself.
A paper backup, when you want one
A phrase nobody has seen ties the wallet to this AWS account: the recovery copy is the only way back to it. To have an offline backup as well — or to take the wallet to another system — make a paper backup:
bash chainos-edge-nitro.sh reveal # macOS, Linux
powershell -ExecutionPolicy Bypass -File .\chainos-edge-nitro.ps1 reveal # Windows
It is deliberate by design:
- Only the recovery principal can do it. It opens the recovery copy, which only that principal's credentials can decrypt. Sign in as it first; anyone else is refused by KMS.
- On your screen, nowhere else. It runs the release's own
reveal-mnemonicin Docker on your computer, so it needs Docker (Docker Desktop on macOS and Windows). Nothing happens on the instance. It refuses to run unless it is attached to a terminal, so the words cannot end up in a file, a pipe or a log. - You type a confirmation before anything is decrypted, and then the 24 words are shown on a temporary screen. When you have written them down, it asks for three of them by position to check the paper is right, then clears the screen and its scrollback. A wrong word shows the phrase again; three wrong attempts end without a backup.
- It is recorded. Each run is a
kms:Decryptwith the recovery key in CloudTrail, by the principal that ran it.
Anyone who sees the words controls the wallet. Be alone, with no screen sharing or recording, write by hand, never photograph or type the words anywhere, and store the paper like cash. After this, the paper is a backup like any other: see Mnemonic management.
When it finishes
It prints the Edge endpoint — http://<internal NLB DNS>:8787, or the instance's private IP
without a load balancer — and what your application does with it:
- In the portal, Settings → Edge readiness: confirm the identity the installer printed.
- Settings → Local API Keys: mint a
zkl_live_…orzkl_test_…key. - Point your application at
http://<endpoint>:8787/v1with the headerX-API-Key: zkl_…. Every/v1call goes through the Edge to ChainOS Cloud; the ones that need a signature are signed inside the enclave on the way. Your keys never leave your account.
The endpoint is plain HTTP inside your VPC. Reach it from your application's network, a VPN or a bastion; to terminate TLS in front of it, add a TLS listener to the load balancer with your ACM certificate.
Later
bash chainos-edge-nitro.sh status # endpoint, instance, recent log lines
bash chainos-edge-nitro.sh upgrade # another release; re-pins the key, restarts once
bash chainos-edge-nitro.sh uninstall # removes it; keys and envelopes kept unless you say so
bash chainos-edge-nitro.sh reveal # a paper backup of the phrase, as the recovery principal
On Windows, the same commands: powershell -ExecutionPolicy Bypass -File .\chainos-edge-nitro.ps1 status.
Upgrading builds the new release's enclave image on the instance, adds its PCR0 to the seal key's policy beside the current one (so a rollback needs no policy change), and rolls the Edge. It refuses a release whose enclave measures the same as the running one: every release embeds its own version, so an unchanged PCR0 would mean the new binary is not in the image. The restart is quick enough for the spend ledger's snapshot to be restored, so capped spending is not frozen.
Changing the spend policy is a reseal from the recovery copy, run as the recovery principal — the only identity allowed to open it — with the release image, so nothing needs installing but Docker:
B=s3://<deployment>-<account>-<region>
aws s3 cp $B/sealed/recovery.json .
aws configure export-credentials --format env-no-export > creds.env # short-lived; removed below
docker run --rm --user "$(id -u)" --env-file creds.env -e AWS_REGION=<region> \
-v "$PWD:/w" -w /w ziklag/chainos-edge:<release> \
seal-mnemonic --from-envelope=recovery.json --provider=nitro --key=<seal key ARN> \
--environment=<live|sandbox> --label=primary --policy=new-policy.json --out=primary.json
rm creds.env
aws s3 cp primary.json $B/sealed/primary.json
aws ecs update-service --cluster <deployment> --service edge --force-new-deployment
The identity it prints must be the one the Edge already has; if it is not, stop. The upload is a new object version, so the old envelope is kept. The instance takes the new envelope from the bucket within a minute, and the Edge uses it from its next start — the last command. That restart is quick, so the spend ledger restores rather than freezing.
During the reseal the phrase is, briefly, in the memory of the seal-mnemonic process on the
machine that ran it. Run it somewhere you would trust with the wallet, and expect the
kms:Decrypt on the recovery key in CloudTrail — that is the reseal, and it should be the only
one.
For automation
Every question can be answered by an environment variable — CHAINOS_PROFILE (default for
the credentials already in effect), CHAINOS_REGION, CHAINOS_SEED (a deployment name, or
new), CHAINOS_NAME, CHAINOS_VERSION, CHAINOS_API_KEY, CHAINOS_VPC,
CHAINOS_SUBNETS, CHAINOS_ALLOWED_CIDR, CHAINOS_POLICY_FILE, CHAINOS_RECOVERY_PRINCIPAL
and the rest listed by --help (-Command help on Windows) — and --yes (-Yes) accepts every default. To pull the release from
your own ECR mirror, set CHAINOS_EDGE_IMAGE to the repository URI; the instance role already
may pull from it.
What it does not do
- It does not make the Edge highly available. One instance runs one enclave; a replacement instance starts from the same cached image and envelope, but restarting on a new instance freezes capped spending for one window, because the ledger snapshot stayed on the old disk. See High availability for multi-replica layouts.
- It does not trust a Ziklag-signed image (PCR8): Ziklag does not sign Edge enclave images, so
that nobody holding a Ziklag key can ship an image your seed is released to. The enclave image
is built in your account from the release, and its PCR0 is what the key trusts. Each instance records what it built —
the release image digest, the binary's SHA-256, the enclave image and EIF hashes, and PCR0 —
in
s3://<bucket>/install/ready/<release>/<instance>. The image is built deterministically — a sorted tarball with fixed times and owners, imported rather than built from a Dockerfile — so the same release and the samenitro-cliversion give the same PCR0 wherever they run.