Install kthx
Install the kthx engine on the offsite cluster, from the OpenTofu roots to the first passkey.
Use this runbook to install the kthx engine on the offsite cluster, or to rebuild it. The engine is the web and reconciler processes of apps/kthx-engine/ and their database. kthx lists the live addresses.
Before you start#
Get Atlantis access (Apply an OpenTofu change), the operator age key (Manage SOPS secrets), and offsite access (Get cluster admin access).
The console hostname,
<hostname>, is kthx'scontrol.hostinclusters/offsite/apps/kthx/helm-release.yaml. The first passkey is registered to it, so browsers must reach it.<public-hostname>is theKTHX_ENGINE_PUBLIC_HOSTNAMEvalue inhelm-release.yaml. The Cloudflare tunnel forwards the GitHub webhook and/mcpto it.For another namespace, change
spindriftinnamespace.yamland on each line that this command prints:git grep -n -e 'spindrift:spindrift' -e 'namespace: spindrift$' -e 'spindrift\.spindrift\.svc'Then change the
spindriftsubject in theKubeAuthenticationConfigofclusters/folly/talos/patches.tfandclusters/offsite/talos/patches.tf, which Atlantis applies.
Declare the installation#
clusters/offsite/apps/kthx-engine/ is the installation, and packages/charts/kthx-engine/values.yaml describes its values. A Target is where kthx deploys built apps.
In
helm-release.yaml, sethostnameto<hostname>andingress.enabledtofalse.kthx's
kthx-controlroute is the console's edge.control.consoleinclusters/offsite/apps/kthx/helm-release.yamlnames this Service, andkthx-route-grant.yamladmits the route.Make sure each Kubernetes Target cluster declares an Apps Gateway and binds the Target ClusterRoles.
offsite declares both in
gateway.yamlandtarget-rbac.yaml, and folly inclusters/folly/apps/kthx-engine-target/.
Write the installation Secret#
secret.sops.yaml is the Secret kthx-engine-env. The engine reads each key below under its KTHX_ENGINE_ name only, and secret.sops.yaml keys each value that way.
| Key | Value |
|---|---|
KTHX_ENGINE_ENROLMENT_TOKEN | Claims the installation |
KTHX_ENGINE_CREDENTIAL_KEYRING | Encrypts stored credentials |
KTHX_ENGINE_GITHUB_APP_ID, KTHX_ENGINE_GITHUB_APP_PRIVATE_KEY, KTHX_ENGINE_GITHUB_WEBHOOK_SECRET | Optional. Adopts a GitHub App |
KTHX_ENGINE_VERCEL_TOKEN, KTHX_ENGINE_CLOUDFLARE_TOKEN | Optional. Vercel and Cloudflare Pages tokens |
warning
A copied enrolment token lets its holder claim the installation. Make a new token for each installation.
warning
The chart keeps the CNPG Cluster spindrift-db after an uninstall. A new keyring cannot open the credentials in it.
Find out if the database exists.
kubectl --context offsite -n spindrift get cluster.postgresql.cnpg.io spindrift-dbResult:
NotFoundfor a new installation.
warning
Steps 2 and 3 print secrets. Do not run them through an agent or in a logged terminal.
If the database does not exist, make a keyring.
bun -e "const k=require('crypto').randomBytes(32).toString('base64url');console.log(JSON.stringify({active:'k1',keys:{k1:k}}))"If it exists, keep the keyring. To rotate it, add a new
activekey and keep the others.Result: A JSON line with
activeandkeys.Make an enrolment token.
bun -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"Result: A 43-character base64url string.
Open the Secret in your editor.
SOPS_AGE_KEY_FILE=~/.config/age/keys.txt sops clusters/offsite/apps/kthx-engine/secret.sops.yamlSet each key under
stringData. Save the file and close the editor.Result: sops encrypts the file again.
Open a pull request with these changes.
Apply the OpenTofu roots#
The roots terraform/gcp/projects/bluenose/ and terraform/gcp/projects/trusted-builds/ need resources from each other. Apply them in three phases.
Comment
atlantis plan -d terraform/gcp/projects/trusted-buildson the pull request.Result: Atlantis comments the plan.
caution
On a new installation, the first trusted-builds apply fails with HTTP 400, because bluenose has no kthx-engine-controller service account yet.
Comment
atlantis apply -d terraform/gcp/projects/trusted-builds.Result: The controller grants fail. The apply creates the attestor and imports the KMS keys.
Do steps 1 and 2 for
terraform/gcp/projects/bluenose.Result: The apply creates
kthx-engine-controller.Do steps 1 and 2 for
terraform/gcp/projects/trusted-buildsagain.Result: Atlantis merges the pull request.
Make sure the installation runs#
Make sure the installation is ready.
flux --context offsite get sources oci -n spindrift flux --context offsite get helmreleases -n spindrift kubectl --context offsite -n spindrift get cluster.postgresql.cnpg.io spindrift-db kubectl --context offsite -n spindrift get deploy,jobResult:
READYisTrue, the Cluster is healthy, thespindrift-migrate-*Job isComplete, and then both Deployments are1/1.
Enrol the first passkey#
note
The enrolment token works once. A new token replaces every passkey and ends every session.
Open
https://<hostname>.Result: "Claim this installation", or "Sign in" if the database existed.
If the page shows "Sign in", sign in with your passkey. Skip steps 3 to 8.
Enter the value of
KTHX_ENGINE_ENROLMENT_TOKEN.Select "Enrol a passkey".
Register the passkey when the browser asks.
Result: The onboarding wizard opens.
If you have a file from "Download this installation" in Settings, select "Restore from a file".
Complete the wizard.
For "Where artifacts are published", use
registryfrom thesupply_chain_manifest_blockoutput in the last trusted-builds apply comment.Select "Configure this installation".
Result: The page shows "This installation is configured."
Connect GitHub and Targets#
If Repositories shows "Create the App on GitHub", select it. Create the App on GitHub.
If you adopt an App, select "Active" under Webhook in its GitHub settings.
Use
https://<public-hostname>/internal/github/webhookandKTHX_ENGINE_GITHUB_WEBHOOK_SECRET.
caution
A private GitHub App installs only on its owner account. A public App cannot become private while another account has it installed.
- If a repository belongs to another account, select "Make public" in the App's Advanced settings.
- Select "Install on GitHub". Connect each repository in Repositories and each Target in Targets.
If something goes wrong#
| Symptom | Cause | Action |
|---|---|---|
A Target call fails with unable to get issuer certificate. | ca-bundle.yaml lacks part of that chain. | Run nix develop -c bash scripts/pki/post-rotate.sh folly offsite. Commit and merge the output (PKI). |
| A credential fails with "cannot be opened by this keyring". | The keyring lost its key. | Restore that key. |
| You cannot sign in. | The passkey is lost. | Rotate the enrolment token in secret.sops.yaml and merge. Select "Recover with a rotated token". |
Related#
Linked from 3
- kthxThe lab's hosting product on the offsite cluster, which the owner and agents use to serve a directory at <name>.kthx.dev or to build a repository and deploy it to a cluster or a cloud.
- Connect an agent to kthxMint an agent token in the kthx console and connect an MCP client, Rowbutt or the owner's workstation to the kthx engine, which serves each console command as a tool.
- RunbooksStep-by-step procedures for the hosts, clusters, network, secrets and apps, grouped by area.