Repository navigation
Development
This page is for people who change Sextant. To just build it, see Building from source.
The internal project notes (
AGENTS.md,CONTEXT.md,docs/architecture.mdand the decision records indocs/adr/) are written in English. They are the detailed source for architecture and conventions; this page summarizes what you need to get going.
| Path | What |
|---|---|
crates/sextant-core |
Everything about clusters (kube-rs): kubeconfig, connection, discovery, watches, actions, logs, exec, port forwarding, metrics, Helm, Argo CD, settings, the agent access policy. No Tauri dependency, so it can back another front end later. |
src-tauri |
The desktop application (crate sextant): Tauri commands, events, the local terminal, window handling, and the MCP server (src-tauri/src/mcp/, src-tauri/src/mcp_proxy.rs). |
src |
The interface: React + TypeScript + Vite. src/api is the only bridge to the backend. |
src/features/<name> |
Vertical features, picked up automatically from their index.ts. |
src/locales/<lang>/<namespace>.json |
Interface texts: English (en/) is the source, every other language has the same keys. A new language is a new directory. Rules: src/locales/TRANSLATING.md. |
scripts/ |
The test cluster, fixtures, and checks used by CI. |
e2e/ |
End-to-end tests of the whole window (WebdriverIO + tauri-driver). |
docs/ |
Architecture, decision records, the README screenshot. |
Dependencies point one way: src/ (React) → src-tauri/ (the sextant crate) → crates/sextant-core/.
- Names in code are English.
- Every interface string is a key in
src/locales/<lang>/<namespace>.json; string literals in JSX are forbidden by eslint. A new or changed string goes into every language in the same change:src/locales/locales.test.tsfails otherwise (see Translations). -
@tauri-apps/*may be imported only insidesrc/api/. - Types that cross the boundary are
#[serde(rename_all = "camelCase")]plusts-rs; the generated TypeScript insrc/api/generated/is never edited by hand: runnpm run gen:typesand commit the result. Command and event names are written by hand insrc-tauri/src/lib.rs,src/api/commands.tsandsrc-tauri/tests/commands.rs. - Anything long-lived (a subscription, a stream, a session, a port forward) must live in
AppStateso that disconnecting a cluster and closing the window stop it. - All kube errors go through
error::kube_error, which removes exec-plugin secrets; Secret values are removed in the core and sent to the interface only on explicit request. -
sextant-coremust not depend ontauriorportable-pty(checked byscripts/check-core-deps.mjs). - Code must work on Linux, macOS and Windows; CI runs on all three.
The interface is translated into 13 languages besides English (see Translating for the list and how to report a bad translation). For code changes this means:
- Add the English text to
src/locales/en/<namespace>.jsonand the same key to every othersrc/locales/<lang>/directory.npx vitest run src/locales/locales.test.tschecks the files, keys,{{placeholders}},$t(…)nesting, tag-like text, empty texts and plural forms of every language against English. - Counts use plural keys: English
_oneand_other; every other language exactly its CLDR forms (_one/_few/_many/_otherfor ru, uk, pl;_one/_many/_otherfor fr, es, it, pt-BR;_otherfor zh-CN, zh-TW, ja, ko). Every form keeps{{count}}. - One key per whole sentence: never glue keys or
count + " pods"together; a list goes into one placeholder, joined byformatListfromsrc/i18n/format.ts, which also formats dates and numbers in the interface language. - Kubernetes kinds and API terms stay English in every language; the glossary in
src/locales/TRANSLATING.mdfixes the common words.
Corrections from native speakers are welcome as issues or pull requests that change the JSON files.
Run from the repository root:
export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
npm ci
npm run build # a clean clone needs the frontend built once: the crate embeds dist/
npm run check # everything CI runsnpm run check runs: a case-collision check of file names, the version check, rustfmt, clippy -D warnings, the core-dependency check, the Rust tests, the generated-types check, tsc (including e2e/), eslint and vitest. To keep target/ small: CARGO_PROFILE_DEV_DEBUG=0 CARGO_INCREMENTAL=0 npm run check.
Smaller steps:
cargo test -p sextant-core <name> # one Rust test (or `-p sextant`)
npx vitest run <file> # one frontend test file, e.g. src/layout
npm run gen:types # regenerate src/api/generated from the Rust types
npm run check:version # the version is the same everywhere-
Core, without a cluster: unit tests next to the code; tests that need HTTP use a fake API server (
crates/sextant-core/src/fake_api.rs, test builds only). -
Core, against a real cluster:
crates/sextant-core/tests/<feature>.rsagainst the kind test cluster below. -
Commands:
src-tauri/tests/commands.rscalls every Tauri command on a mock runtime, isolated from yourKUBECONFIGand~/.kube/config. -
Interface: Vitest with Testing Library, with the backend replaced by
mockBackendfromsrc/api. Accessibility is part of it:src/test/a11y.test.tsxandsrc/test/a11y-pages.test.tsxrun axe over the main screens and every registered page (axeViolationsfromsrc/test/axe.ts; add a check for a new screen),src/test/keyboard.test.tsxcovers the keyboard model, andsrc/styles/contrast.test.tscomputes the WCAG contrast of the theme colours (a new colour is a token insrc/styles/global.css, in both themes). Measurements for very large clusters: see Large clusters. -
The whole window:
e2e/.
Integration tests that need a cluster are skipped without SEXTANT_TEST_KUBECONFIG and then count as passed: a green cargo test --workspace without a cluster says nothing about the cluster.
A local kind cluster named sextant-test with fixtures. Needs kind, kubectl and Docker.
scripts/test-cluster.sh up # cluster + metrics-server + fixtures
scripts/test-cluster.sh up --prometheus # also Prometheus in `monitoring`
scripts/test-cluster.sh up --gitops # also Flux and Argo CD with fixtures (flags combine)
scripts/test-cluster.sh up --gateway # also the Gateway API CRDs and fixtures (see Gateway API)
scripts/test-cluster.sh kubeconfig # path to its admin kubeconfig
scripts/test-cluster.sh kubeconfig limited # kubeconfig of a ServiceAccount that cannot read Secrets
scripts/test-cluster.sh down # delete the clusterup --gateway needs openssl for a self-signed certificate; what it sets up is described in Gateway API. The script never touches your ~/.kube/config; its files live in ~/.cache/sextant-test (override with SEXTANT_TEST_DIR).
Integration tests of the core against that cluster:
SEXTANT_TEST_KUBECONFIG=$(scripts/test-cluster.sh kubeconfig) \
cargo test -p sextant-core --tests -- --test-threads=1The GitOps tests (Flux, Argo CD) also need SEXTANT_TEST_GITOPS=1 and a cluster started with up --gitops:
SEXTANT_TEST_GITOPS=1 SEXTANT_TEST_KUBECONFIG=$(scripts/test-cluster.sh kubeconfig) \
cargo test -p sextant-core --test gitops --test gitops_flux --test gitops_argocd --test gitops_argocd_diff -- --test-threads=1A load fixture (150 CRDs and 500 pods in the namespace sextant-load) backs one timing test and is skipped without it: scripts/fixtures/load/load.sh apply, and scripts/fixtures/load/load.sh delete afterwards. Tests that stop the kind container run only with SEXTANT_TEST_DOCKER_RESTART=1.
npm run e2e starts the debug app through tauri-driver and walks through the features against sextant-test: connecting, the pod table, details, logs, a shell, YAML editing, scaling, port forwarding, a Helm release, the overview, Settings and the narrow layout. It also takes the README screenshot (docs/screenshot.png).
cargo install tauri-driver --locked
sudo apt install webkit2gtk-driver # Linux: WebKitWebDriver
scripts/test-cluster.sh up --prometheus
npm run e2eThe app runs with throwaway config and data folders and only the test cluster's kubeconfig. Everything the tests create lives in the namespace sextant-e2e, deleted afterwards. A failed scenario leaves a screenshot in target/e2e/. SEXTANT_E2E_SKIP_BUILD=1 reuses the last build (target/debug/sextant).
Known quirks: the config fixes tauri-driver's port at 4444 (the native port defaults to 4445), and the driver fails to start if they are busy; tauri-driver --port 4447 --native-port 4448 helps. WebKitWebDriver drops repeated keys and spaces in browser.keys and mistypes / and :; use the typeText helper in e2e/app.ts.
| Variable | Used for |
|---|---|
SEXTANT_TEST_KUBECONFIG |
Admin kubeconfig of the test cluster; turns on the integration tests and e2e |
SEXTANT_TEST_KUBECONFIG_LIMITED |
Kubeconfig of the limited ServiceAccount (default: kubeconfig-limited next to the admin one) |
SEXTANT_TEST_DIR |
Folder of the test cluster files (default ~/.cache/sextant-test) |
SEXTANT_TEST_DOCKER_RESTART |
Lets tests stop and start the kind container |
SEXTANT_TEST_GITOPS |
1 turns on the GitOps integration tests |
SEXTANT_E2E_SKIP_BUILD |
e2e reuses the existing debug binary |
CARGO_PROFILE_DEV_DEBUG, CARGO_INCREMENTAL
|
Turn off debug info and incremental builds to keep target/ small |
For the user's view see MCP server. Where things are:
| What | Where |
|---|---|
The access policy: types, decide, the sets (preset_rules), with no I/O |
crates/sextant-core/src/access.rs |
Operations used by the tools: resources::list, resources::events, logs::tail, exec::run_command
|
crates/sextant-core/src/ |
The server (Streamable HTTP on 127.0.0.1, rmcp and axum), the HTTP checks, the token, the guard that applies rules and asks for confirmation, the audit log, the tools |
src-tauri/src/mcp/ |
sextant mcp (the stdio bridge) |
src-tauri/src/mcp_proxy.rs, started from src-tauri/src/main.rs
|
| Commands for the interface |
src-tauri/src/commands/mcp.rs, src-tauri/src/commands/access.rs
|
| Settings section, confirmation dialog, activity log | src/features/mcp/ |
| AI agent access in the cluster settings | src/features/agent-access/ |
The access rules are the single source of truth in the core (the interface asks the backend for the rules of each set instead of copying them). A new tool must check its rights through the call's Guard right before it acts, and must not put Secret values, YAML or command output into the journal.
The application version is written in five places: Cargo.toml ([workspace.package]), Cargo.lock, src-tauri/tauri.conf.json, package.json and package-lock.json. Bump all of them together and run npm run check:version; node scripts/check-version.mjs --tag vX.Y.Z also checks it against a release tag.
Pushing a tag v* equal to that version (for example v0.2.0) runs release.yml. It refuses a tag that does not match the version, builds installers for Linux (.deb, .AppImage), macOS (universal .dmg) and Windows (.msi), signs the update bundles, builds latest.json for the in-app updater and publishes a GitHub Release only if every platform is there. The release notes list the closed issues of the milestone named like the tag. To rebuild an existing tag without rewriting it: gh workflow run release.yml -f tag=vX.Y.Z (Actions → Release → Run workflow); a rebuild of an older tag does not become the latest release.
Signing needs the repository secrets TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD, the private half of the key whose public half is plugins.updater.pubkey. Losing the private key means installed copies will not accept updates. Installers are not signed with operating-system certificates.
ci.yml runs npm run check on Linux, macOS and Windows for every push and pull request to main (and manually). Things that break only on macOS or Windows: file names that differ only in case, GNU-only tools in scripts (stat -c), /proc, path rules of the host, and IPC test URLs (tauri://localhost versus http://tauri.localhost).
Plans live in GitHub Issues and milestones.
Sextant is licensed under MIT or Apache-2.0. Found a mistake in this wiki? Open an issue.
Sextant
- Home
- Installation
- Getting started
- Using Sextant
- YAML editor
- Terminal
- Views and workspaces
- Kubeconfig management
- All clusters
- Safe apply (Preview changes)
- Upgrade readiness
- Security posture
- Storage
- Right-sizing
- Cost
- Resource map
- Change timeline
- Cleanup
- Autoscaling
- Connectivity
- Compare and drift
- RBAC: who can do what
- cert-manager and certificates
- Problems and notifications
- Gateway API
- Service mesh
- Installing Flux and Argo CD
- MCP server
- Extensions
- Writing extensions
- Large clusters
- Accessibility
- Settings
- Troubleshooting
For contributors