Greentic · cloud-update-demo

Change a running environment from a web page

Your environment runs on your machine. This page holds a signing key it generated itself, signs an update plan with it, and uploads the bytes to a plan server. Your runtime notices, checks the signature against a key it already trusts, and switches version — on its own, with no restart.

Press a button here. Watch the packs change there.

Why this is safe

A plan is only accepted if it carries a signature from a key in your environment's trust root — a list of public keys on your disk. So step one is: this page shows you its public key, and you add it. Its private key never leaves your browser, and the plan server never sees it. The server stores bytes and hands them out; it cannot mint, alter, or forge a plan.

  ┌───────────────────────┐  sign in the browser   ┌──────────────────┐
  │  this page            │ ─────────────────────▶ │  plan server     │
  │  holds a private key  │      upload bytes      │  stores bytes.   │
  │  you never send       │                        │  holds no keys.  │
  └───────────────────────┘                        └──────────────────┘
              │                                             ▲
              │ you add its PUBLIC key                      │  poll every 60s
              │ to the env's trust root                     │  (anonymous)
              ▼                                             │
  ┌──────────────────────────────────────────────────────────────────┐
  │  greentic-start on YOUR machine                                  │
  │    verify signature vs trust root → snapshot → apply → rollback  │
  └──────────────────────────────────────────────────────────────────┘
                 no packs  →  v1 (1 pack)  →  v2 (2 packs)

1. Prepare your machine

One command installs the whole pinned toolchain. Everything else is bash, curl and grep. No Python, no jq, no Docker. Linux and macOS.

terminal — install
$ cargo binstall gtc
$ gtc install --release 1.1.2

$ export DEMO=~/greentic-demo
$ mkdir -p $DEMO/home && cd $DEMO

Release 1.1.2 pins greentic-deployer 1.1.11 and greentic-start 1.1.11, which is everything this demo needs.

There are no bundles to download.

The plan names its bundle with an oci:// reference, and op env apply pulls it from the registry itself. Nothing is copied to your machine ahead of time, and the page never needs to know a single path on your filesystem.

The plan still pins the bundle's sha256. Apply fails closed unless the bytes it pulled hash to exactly that — so the registry, exactly like the plan server, is an untrusted delivery channel and not a trusted party.

Use a throwaway terminal.

The next step sets HOME to $DEMO/home so nothing touches your real ~/.greentic. That also means every other command in that terminal sees the fake home. Close it when you're done.

2. The console

This runs entirely in your browser. The key is generated here and kept in this browser's local storage — nothing is uploaded but signed plans.

Page signing key generating…
Server namespace none yet
Claim a namespace to begin.

3. Start a clean environment, pointed at the server

Once you've claimed a namespace above, this block is filled in with your endpoint and your page key. It creates an environment with no packs at all, subscribes it to the server, and tells it to trust this page.

terminal — generated for you
# Claim a namespace in the console above and this fills itself in.

4. Check the environment — it is empty

terminal 2 — check config
$ greentic-deployer op env show local | grep -o '"bundle_digest":"sha256:[0-9a-f]*"' | tail -1

(no output — nothing is deployed yet)

That prints the digest of the revision currently taking traffic. Two values matter, and the page publishes only these:

DigestVersion
sha256:6e6f24f5…v1app-webchat-bot  (1 pack)
sha256:feb45faf…v2app-webchat-bot, messaging-telegram  (2 packs)

Remember to re-export DEMO and HOME in this second terminal, or it will look at your real home.

5. Press “Publish v1”, then check again

Go back to the console and press Publish v1. Within 60 seconds the runtime picks it up. Run the same check:

terminal 2
$ greentic-deployer op env show local | grep -o '"bundle_digest":"sha256:[0-9a-f]*"' | tail -1

"bundle_digest":"sha256:6e6f24f5c3f12c41…"   ← v1. 1 pack. It came from the page.

6. Press “Publish v2”, and check once more

terminal 2
$ greentic-deployer op env show local | grep -o '"bundle_digest":"sha256:[0-9a-f]*"' | tail -1

"bundle_digest":"sha256:feb45fafda62abe2…"   ← v2. 2 packs.

Now press Publish v1 again. It goes back to one pack. Flip between them as often as you like.

Going back to v1 is not a downgrade.

Anti-rollback protects the plan sequence, which only ever grows — 1, 2, 3, 4. The content a plan points at is free to move in either direction. Replaying an old signed plan stays impossible.

What this proves

ClaimWhat enforces it
The page can't be impersonated Plans are Ed25519-signed in the browser. The runtime accepts only signatures from a key in its own trust-root.json — the one you added by hand.
The server can't forge an update It stores bytes and holds no key. A plan it invented would fail verification on your machine.
The network can't either Plan reads are anonymous and unauthenticated on purpose. The signature is the trust anchor — not TLS, not the origin.
You can't be replayed an old version Each plan carries a monotonic sequence, read from the signed plan rather than from anything the server says.
Nothing is pushed at your runtime It polls. An environment with no updates block fetches nothing, ever.
A plan can't hijack the channel The updates block is stripped from a plan target before signing — here in the page, and in the CLI.

If something goes wrong

The runtime logs “no trusted signature”

The page's key is not in your environment's trust root. See what the env actually trusts:

bash
$ greentic-deployer op trust-root list local

If the key_id shown in the console above is not in that list, just re-run the op trust-root add step from section 3. The usual cause is clearing browser storage — the page then mints a new key, and the old one no longer matches.

Nothing happens after pressing a button

Give it a full minute — 60s is the poll floor. Then check the console log above: a successful publish prints the sequence number the server accepted. If the button reports an error, it is the server rejecting the upload, not your runtime.

Apply cannot pull the bundle

op env apply fetches the bundle from ghcr.io anonymously — no login, no Docker. If it fails, you are either offline or behind a proxy that blocks the registry. The error names the oci:// ref it tried.

A digest mismatch is different, and is the system working: apply pulled bytes that do not hash to the digest pinned in the signed plan, and refused them rather than deploying whatever the registry handed over.

Your browser has no Ed25519

The console needs WebCrypto Ed25519 (Chrome 137+, Firefox 130+, Safari 17+). On an older browser the buttons stay disabled and the key line says so.