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.
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)
One binary to install; bash, curl and
python3 you already have. Works on Linux and macOS.
$ cargo binstall greentic-deployer@1.1.10 greentic-start@1.1.9 $ export DEMO=~/greentic-demo $ mkdir -p $DEMO/bundles $DEMO/home && cd $DEMO $ REL=https://github.com/greenticai/greentic-cloud-update-demo/releases/download/content-v1 $ curl -fsSL -o bundles/v1.gtbundle $REL/v1.gtbundle $ curl -fsSL -o bundles/v2.gtbundle $REL/v2.gtbundle # the page needs this absolute path — copy what it prints $ echo $DEMO/bundles
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.
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.
generating…
none yet
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.
# Claim a namespace in the console above and this fills itself in.
$ greentic-deployer op env show local | python3 -c ' import json, sys e = json.load(sys.stdin)["result"]["environment"] live = {x["revision_id"] for s in e["traffic_splits"] for x in s["entries"] if x["weight_bps"] > 0} rows = [r for r in e["revisions"] if r["revision_id"] in live] print("packs:", ", ".join(p["pack_id"] for r in rows for p in r["pack_list"]) or "(none)")' packs: (none) ← nothing deployed yet
Remember to re-export DEMO and HOME in this second
terminal, or it will look at your real home.
Go back to the console and press Publish v1. Within 60 seconds the runtime picks it up. Run the same check:
packs: app-webchat-bot ← 1 pack. It came from the page.
packs: app-webchat-bot, messaging-telegram ← 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.
| Claim | What 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. |
The page's key is not in your trust root. That happens if you cleared browser
storage (the page then generates a new key) or ran
op env init after adding it. Re-run the trust-root step in
section 3 with the key_id currently shown in the console.
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.
The bundles directory you typed into the console is baked into
the signed plan as an absolute path. It must be the real path on the machine
running greentic-start. Fix the field and publish again.
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.