Run an Instance
An Instance is one running Telecraft: one process over one estate, with its
own users, its own activated versions and its own verdicts. The same
telecraft serve that delivers configuration to collectors also serves the
console you sign in to and the API behind it.
It holds nothing that outlives it. Restarting signs everybody out and loses no record: users, teams, governance and every authored object live in the estate repository, and the readings come off the wire.
This guide assumes you have rendered an estate and have served configurations from it. Everything on this page is that server with its second address opened.
Build the binary with the console inside it
The console travels inside the binary, so one artefact is the whole Instance. Build it in two steps, from the repository root:
The release binary already carries the console, so downloading it from the latest release is enough and the rest of this section is for running an unreleased commit.
cd console
npm ci
npm run build
npm run bundle
cd ..
go build -o telecraft ./cmd/telecraft
npm run bundle stages the built console where the binary embeds it from.
Skip those four lines and the binary still builds and still serves the API;
the console route then answers a page saying the console was not built into
it.
Give somebody a way in
Sign-in needs one user the estate knows. Hash a secret, then add the user
beside teams.yaml in your estate checkout:
./telecraft passwd
The secret is read from standard input and the hash is printed. Put it in
users.yaml:
users:
- email: [email protected]
name: Jo Author
owner: gateway-owners
password: pbkdf2-sha256$600000$...
owner names an Owner in the team tree. What Jo may author follows from that
Owner's Team, so there is no second place to grant anything.
Commit the file. Everything the Instance reads about people is under review like every other authored object.
Run it
./telecraft serve -estate ../estate-demo
console and API on http://127.0.0.1:4321
OpAMP on 127.0.0.1:4320
the session key was drawn at start, so sessions last as long as this process
serve: serving head 870c9b8a26458402c1982359bcdea90fdb7ef73d on 127.0.0.1:4320, fetch interval 30s
Open http://127.0.0.1:4321 and sign in with the email and secret you hashed. The console reads the estate at the head the server is serving, and picks up a merge on the next poll without a restart.
Humans and collectors arrive on separate addresses, so you can expose one and not the other:
| Flag | What listens |
|---|---|
-http |
The console, the API and the two probes. |
-listen |
The OpAMP endpoint, at /v1/opamp. |
The OpAMP endpoint closes on an empty address:
./telecraft serve -estate ../estate-demo -listen ""
console and API on http://127.0.0.1:4321
the OpAMP endpoint is closed
That is the shape of an Instance whose collectors are all Foreign.
Put TLS in front
The process holds no certificate. Both endpoints speak plain HTTP, and TLS terminates in an ingress, a load balancer, a reverse proxy, or nowhere at all on a loopback address.
Tell the process what the outside sees:
./telecraft serve -repo https://forge.example/acme/estate.git \
-http 0.0.0.0:4321 \
-external-url https://telecraft.example
-external-url does two things. Its scheme decides whether session cookies
are marked Secure, and it is the address a redirect sign-in returns to.
Moving an Instance from http to https also renames its session cookie, so
everybody signs in again once.
It fails closed. An external URL naming a host that is not a loopback address, over plain HTTP, is refused:
serve: the external URL "http://telecraft.example" sends passwords and sessions across a network in clear text. Terminate TLS in front and name the https URL, or pass -insecure-http to say that plain HTTP is meant here
Add -insecure-http if you mean it.
Place the secrets
Telecraft reads secret material from files in one directory, whatever the deployment shape. Nothing carries a value on the command line or in the environment.
./telecraft serve -estate ../estate-demo -secrets-dir /run/secrets
A host process fills that directory with files owned by the service user. A
compose file presents its secrets: block at /run/secrets. A Kubernetes
deployment projects a Secret as a read-only volume. Whatever writes the
files, filling the directory is the whole interface: rotating a secret is
rewriting its file, with nothing to restart and nothing to renew.
These files have documented names, and all of them are optional:
| File | What it holds |
|---|---|
session-key |
The session signing key, at least 32 bytes. |
telemetry-key |
The credential for the telemetry backend. |
refresh-key |
The key a bare refresh request presents. |
push-secret |
The secret your git host signs its push notifications with. |
forge-key |
The private key the forge adapter authenticates with. |
forge-token |
A forge credential something else mints and keeps current. |
Point any of them somewhere else with the matching -*-file flag. A path you
name and the process cannot read stops the start; a file you never placed is
an absence, and an absence means the capability is unavailable rather than
broken.
Let the console propose changes
The console never writes to your estate. Composing a Blueprint, editing the governance policy, adding a Tier, claiming ungoverned collectors and activating a version all end the same way: a pull request against the estate repository, attributed to the person who asked for it, which your reviewers decide on.
That needs somewhere for a proposal to go. Name the estate repository and place the credential:
./telecraft serve -estate ../estate-demo \
-forge-repo https://forge.example/acme/estate \
-forge-app-id 123456 -forge-installation-id 7654321 \
-secrets-dir /run/secrets
with the private key in /run/secrets/forge-key. Where something else mints
a short-lived token and rewrites it in place, place that instead, as
/run/secrets/forge-token, and leave the two identifiers out: the file is
read at the moment it is used, so a rewritten token is picked up by the next
proposal with nothing to restart.
Without a credential the Instance serves everything there is to read, and every write endpoint answers that no forge credential was placed. Nothing else changes: collectors are served, the probes answer, and the shelf, topology and drawers all read as usual.
Name a licence, if you have one
Most deployments have none, and that is the whole free product: Standard Edition, unrestricted, with nothing to place and nothing to configure.
If you hold a licence, name the file:
./telecraft serve -estate ../estate-demo -licence-file /run/licence/acme.licence
It is not a secret and does not go under -secrets-dir. It is read at start
and again whenever it changes, and it never affects what a collector receives:
a licence that is absent, unreadable, or expired stops nothing being served.
Place a licence has the four states and what each one does.
Keep sessions across a restart
A session is a signed token, never a record. With no key placed, one is drawn at start and every session ends with the process:
head -c 32 /dev/urandom > /run/secrets/session-key
./telecraft serve -estate ../estate-demo -secrets-dir /run/secrets
Two processes with the same key accept each other's sessions. Rotating it signs everybody out, which takes a restart and is what a suspected compromise wants.
Offer sign-in through your identity provider
Basic auth is the bootstrap. For everyone else, declare the providers in
auth.yaml, beside teams.yaml and users.yaml:
providers:
- kind: oidc
name: staff
issuer: https://issuer.example
client_id: telecraft
secret: staff-oidc
- kind: basic
Each entry names its kind, the name the sign-in surface shows, its issuer, and the name of its secret. No field takes a value. Place the client secret in a file of that name:
printf %s "$CLIENT_SECRET" > /run/secrets/staff-oidc
A secret name is lower-case letters, digits and hyphens, so a name can never describe a path.
Register https://telecraft.example/api/v1/auth/staff/callback as the
redirect URI with your provider, replacing staff with the name you gave the
entry.
For SAML, save the identity provider's metadata document beside auth.yaml
and name it in the entry:
providers:
- kind: saml
name: staff
entity_id: https://telecraft.example/saml
metadata_file: idp-metadata.xml
Register the same callback address as the assertion consumer service, on the HTTP POST binding. SAML needs HTTPS: the assertion arrives as a form post from your identity provider's page, and the cookie that carries the sign-in attempt across it is only sent over an encrypted connection.
Editing auth.yaml is a pull request, exactly like editing who may author. A
file that names a secret nobody placed stops the start rather than
withdrawing sign-in quietly:
serve: provider "staff": the secret "staff-oidc" is named, and there is no file of that name in /run/secrets
With no auth.yaml, the Instance offers basic auth alone.
Place people by their identity provider groups
users.yaml names people one at a time, which is right while the list is
short enough to read. Where it is not, tell the provider which claim carries
group membership, and map a group to the Owner its members act as:
providers:
- kind: oidc
name: staff
issuer: https://issuer.example
client_id: telecraft
secret: staff-oidc
groups_claim: groups
groups:
- group: platform-engineering
owner: gateway-owners
- group: security
owner: pii-guardians
users.yaml still wins wherever it names somebody's email. The mapping
places everybody else, and it changes nothing in teams.yaml: the Owners and
the Teams stay exactly where they are, and a rule pointing at an Owner that
file does not hold stops the start. Somebody in two mapped groups acts as the
Owner of the first rule that matches, so write the rules in the order you
mean them.
The full format, and every way the file is refused, is in Sign-in.
Probe it
Two paths answer without a session, and answer a status word and nothing else:
| Path | Answers |
|---|---|
/healthz |
200 ok while the process runs. |
/readyz |
503 starting until the first snapshot is held, 200 ready after it. |
A later fetch that fails keeps the last snapshot and readiness stays green: a stale head still serves correct configuration for the commit it names.
There is one probe for the whole process. The OpAMP endpoint gets none of its own.
Configure it from the environment
Every flag has an environment variable: TELECRAFT_ plus the flag name
upper-cased, with dashes as underscores. A flag beats an environment
variable, which beats the default.
export TELECRAFT_ESTATE=/srv/estate
export TELECRAFT_HTTP=0.0.0.0:4321
export TELECRAFT_EXTERNAL_URL=https://telecraft.example
export TELECRAFT_SECRETS_DIR=/run/secrets
export TELECRAFT_FETCH_INTERVAL=15s
export TELECRAFT_LICENCE_FILE=/run/licence/acme.licence
./telecraft serve
There is no configuration file. What describes the estate is authored in the estate; the process carries only what git must not, and every secret among that is a file rather than a value.
What this Instance answers
Every read endpoint of the platform API is served from the estate at head: the shelf, the drawers, the collector list, the topology, the rollout ledger, the Blueprints, the Catalogue and its retained versions, the activations and the governance policy.
The endpoints that propose a change, and the two evaluators the composing surfaces call, are not answered yet. They come back as:
{ "error": "this instance does not answer /api/v1/validate yet" }
So Compose and the claim flow do not work against an Instance yet. Reading the estate does.
What next
- Deploy on Kubernetes installs this same process from a chart, with the estate checkout kept current beside it.
- Stage a Rollout moves one Tier's population onto a new Blueprint version in cohorts.
- Activate a version moves the estate onto a new Catalogue or Schema Registry version.
- Explore the demo is the same console over a curated estate, with a build-time snapshot in place of a server.
- Place a licence says where a licence file goes and what an Instance does in each state one can be in.