Greentic · gtc setup --env local

The environment wizard, question by question

Everything gtc-dev setup --env local asks you, what each field actually controls, and why it has to be there. Worked against your two-department Telegram run (one runtime, two bundles, two bots).

The big picture

The wizard does two things in one pass: it authors a manifest and then applies it.

You are filling in a single declarative document — greentic.env-manifest.v1 — that describes the desired wiring of one environment (here, local): its trust root, which bundles run, how traffic routes to them, which messaging channels exist, and which secrets they need.

That document is the durable artifact. It is written to ./local.env.json and meant to live in version control. Everything you typed can be replayed headlessly later:

# reproduce this exact environment, no prompts
gtc-dev setup --answers local.env.json --non-interactive

After authoring, the wizard hands the manifest to the apply engine, which compares it to the live environment and makes only the changes needed (the “plan”).

Component → Flow → Pack → Bundle → Environment deny-by-default multi-tenant: tenant / team secrets by reference, never inline

How it fits together

One runtime, two bundles, two Telegram bots. The fields you enter wire up this picture.

🏛️ LEGAL bot@BotFather 🧾 ACCT bot@BotFather cloudflared one tunnel → 127.0.0.1:8080 revision_serve path-prefix router /legal → legal /accounting → acct bundle: legal tenant=legal · /legal secret: legal/_/…/token bundle: accounting tenant=accounting · /accounting secret: accounting/_/…/token
Path prefix decides the bundle · route tenant decides which bot token resolves · the endpoint’s link is the admit gate.

0 Manifest file

Where the wizard writes the document it is building.

Manifest file default ./local.env.json

What: the path of the greentic.env-manifest.v1 JSON file this wizard creates (or edits, if it already exists). Relative bundle paths inside it are resolved against this file’s directory, so where it lives matters.

Why: this file is your environment definition. Commit it; re-apply it headlessly; diff it in PRs. The wizard is just a friendly front-end for writing it.

you pressed Enter → ./local.env.json

1 Trust root

The signing anchor that lets anything deploy at all.

Bootstrap the trust root? required

What: seeds the environment’s trust root with your local operator signing key. Greentic verifies every bundle/pack signature (DSSE / Ed25519) against this root.

Why: Greentic is deny-by-default — with no trust root, no bundle can be staged or deployed. It is idempotent: do it once per environment; re-runs are a no-op (operator key … already trusted). Leave it true the first time.

you → Enter (default true) · manifest: "trust_root": "bootstrap"

2 Bundles

Each row is one bundle deployment — a packaged digital worker — running in this environment, plus how inbound traffic reaches it. You added two: legal and accounting.

Bundle id required

What: the deployment’s natural key / handle, unique within the manifest.

Why: everything else refers to a bundle by this id — endpoints link to it, the router binds routes to it, the plan reports it. you → legal  ·  accounting

Bundle path required

What: the local .gtbundle artifact to deploy (a SquashFS image of packs + flows + config + SBOM). Relative paths resolve against the manifest file’s directory.

Why: this is what actually runs. The id names it; the path provides the bits.

you → bundle-workspace-legal/realbot-legal.gtbundle

Customer id optional

What: the billing principal this deployment is attributed to.

Why: required for non-local (cloud / commercial) environments so usage can be metered and billed. For local it is irrelevant — leave it blank.

you → (blank)

Config overrides (JSON) optional

What: last-mile per-pack config tweaks applied at deploy time — {"<pack_id>": {"<key>": value}}.

Why: change a pack’s settings without rebuilding the bundle. Three-valued: empty = leave untouched, {} = explicitly clear, a map = replace. You didn’t need it.

you → (blank — leave untouched)

Route hosts optional

What: hostnames (comma-separated) that route to this bundle — host-based routing, e.g. legal.bots.example.com.

Why: use it when bundles are separated by domain. This demo separates by path instead, so it’s blank.

you → (blank)

Route path prefixes optional

What: the HTTP path prefix(es) that dispatch inbound traffic to this bundle, each starting with / (e.g. /legal).

Why: this is how one runtime isolates many bundles. /legal/webhook/telegram → the legal bundle; /accounting/… → the accounting bundle. Without it the two bots would collide.

you → /legal  ·  /accounting

Route tenant optional (set with team)

What: the tenant this route binds to — the multi-tenancy scope (Global → Tenant → Team → User) threaded through every call.

Why it’s the crux of this demo: the tenant also scopes secret lookups. When the Telegram provider asks the host for TELEGRAM_BOT_TOKEN, the host expands it to secrets://local/<tenant>/_/messaging-telegram/telegram_bot_token. Two bundles with the same tenant would resolve the same token. Giving one legal and the other accounting is exactly what makes each bot use its own token.

you → legal  ·  accounting

Route team optional (set with tenant)

What: the team inside the tenant — the finer tenancy level.

Why: normally default, which the secret store canonicalises to _ (that’s the _ you see in the secret paths). Set it together with the tenant.

you → default  (→ _ in the path)

3 Messaging endpoints

Each row is a channel wired into the environment — here, one Telegram bot per department — and which bundle(s) its traffic is allowed to enter.

Endpoint name required

What: the endpoint’s manifest handle, its display name, and (on create) its provider instance identity, all in one.

Why: it’s the upsert key (together with provider type) — re-applying matches the same endpoint instead of creating a duplicate.

you → legal  ·  accounting

Provider type required

What: the provider class, e.g. messaging.telegram.bot.

Why: tells the runtime which provider component handles this endpoint’s ingest and egress (Telegram vs Slack vs Teams …).

you → messaging.telegram.bot

Linked bundle ids optional

What: the bundle_ids this endpoint is allowed to dispatch into (comma-separated).

Why: this is the admit gate / ACL. The legal endpoint admits only the legal bundle, so a message that somehow reached the wrong path would be refused rather than cross departments.

you → legal  ·  accounting

Welcome flow: bundle id / pack id / flow id optional · all 3 or none

What: a specific flow to run as a greeting when a user first contacts this endpoint.

Why: only if you want a fixed welcome screen wired at the endpoint level. Left blank here — the bundle’s own on_message flow already answers.

you → (all blank)

Secret refs optional

What: extra secret references forwarded to the endpoint when it’s created (comma-separated).

Why: for endpoint-level credentials passed at create time (e.g. a webhook secret-token). The bot token itself is handled by the derived secrets step below, so this stays blank.

you → (blank)

4 Secrets — derived, asked last

This is the part that changed. Instead of you typing secret paths up front, the wizard now figures out exactly which secrets your bundles need and asks only for the variable name.

How the wizard knew you needed two secrets

Each bundle ships provider packs that declare their secret requirements (secret-requirements.json). The Telegram pack declares it needs a telegram_bot_token. The wizard read that for each bundle, scoped it to the bundle’s route tenant, and produced the exact path — no typing:

BundleDerived secret pathYou only entered
legallegal/_/messaging-telegram/telegram_bot_tokenTELEGRAM_LEGAL_BOT_TOKEN
accountingaccounting/_/messaging-telegram/telegram_bot_tokenTELEGRAM_ACCOUNTING_BOT_TOKEN

env var name name only — never the value

What: the name of the environment variable that holds each secret value. The default suggestion (LEGAL_TELEGRAM_BOT_TOKEN) is just a hint; you typed your real variable names.

Why a name and not the value: the secret value never goes into the manifest, so the manifest stays safe to commit. At apply time the engine reads the value from that variable (or the dev-store) and writes it into the secret store under the derived path.

you → TELEGRAM_LEGAL_BOT_TOKEN  ·  TELEGRAM_ACCOUNTING_BOT_TOKEN

Mind the default vs your real names

The suggested default is <TENANT>_<KEY>LEGAL_TELEGRAM_BOT_TOKEN. Your shell exports them the other way around (TELEGRAM_LEGAL_BOT_TOKEN), so you correctly typed over the default. If you had pressed Enter, apply would look for an unset variable and report it missing.

5 The plan & apply

With the manifest written, the engine diffs desired (your manifest) against the current environment and lists only the changes — then asks before doing anything.

  1. ensure-environment — create the local env (default env-pack bindings + trust-root seed).
  2. bootstrap-trust-root — seed your operator signing key (the question 1 step).
  3. put-secret ×2 — write each bot token into the secret store, read from $TELEGRAM_*_BOT_TOKEN.
  4. deploy-bundle ×2 — stage + activate each bundle, bound to its path prefix and tenant.
  5. add-endpoint ×2 — register each Telegram endpoint.
  6. link-endpoint ×2 — link each endpoint to its bundle (the admit ACL).

Then the gate: apply 10 change(s) to env `local`? [y/N]. The whole thing is idempotent — re-running the same manifest shows no-op for anything already in place (you saw changed:10, no_op:0 because this env was fresh; a second run would flip that). The trailing JSON is the machine-readable result (changed / no_op / verify) for scripts and CI.

Why a plan-then-confirm shape

Plan and execute are deliberately separate: you see precisely what will change (create vs put vs no-op) before any mutation, and the same plan path powers a non-interactive --dry-run convergence check in CI.

That startup warning

“Greentic toolchain release context is not installed for channel 'dev'”

Harmless for this flow. That context is the pinned compiler/WASM toolchain used to build components. Authoring and applying an environment manifest doesn’t need it, so the wizard runs fine. Run gtc-dev install only when you start building components/packs locally.