26 KiB
Gateway Bootstrap Architecture
This document describes how OpenShell bootstraps a single-node k3s gateway inside a Docker container, for both local and remote (SSH) targets.
Goals and Scope
- Provide a single bootstrap flow through
navigator-bootstrapfor local and remote gateway lifecycle. - Keep Docker as the only runtime dependency for provisioning and lifecycle operations.
- Package the OpenShell gateway as one container image, transferred to the target host via registry pull.
- Support idempotent
deploybehavior (safe to re-run). - Persist gateway access artifacts (kubeconfig, metadata, mTLS certs) in the local XDG config directory.
- Track the active gateway so most CLI commands resolve their target automatically.
Out of scope:
- Multi-node orchestration.
Components
crates/navigator-cli/src/main.rs: CLI entry point;clap-based command parsing.crates/navigator-cli/src/run.rs: CLI command implementations (gateway_start,gateway_stop,gateway_destroy,gateway_info,gateway_tunnel).crates/navigator-cli/src/bootstrap.rs: Auto-bootstrap helpers forsandbox create(offers to deploy a gateway when one is unreachable).crates/navigator-bootstrap/src/lib.rs: Gateway lifecycle orchestration (deploy_gateway,deploy_gateway_with_logs,gateway_handle,check_existing_deployment).crates/navigator-bootstrap/src/docker.rs: Docker API wrappers (network, volume, container, image operations).crates/navigator-bootstrap/src/image.rs: Remote image registry pull with XOR-obfuscated distribution credentials.crates/navigator-bootstrap/src/runtime.rs: In-container operations viadocker exec(kubeconfig readiness, health polling, stale node cleanup, deployment restart).crates/navigator-bootstrap/src/kubeconfig.rs: Kubeconfig rewriting, storage, and merge into local~/.kube/config.crates/navigator-bootstrap/src/metadata.rs: Gateway metadata creation, storage, and active gateway tracking.crates/navigator-bootstrap/src/mtls.rs: Gateway TLS detection and CLI mTLS bundle extraction.crates/navigator-bootstrap/src/push.rs: Local development image push into k3s containerd.crates/navigator-bootstrap/src/paths.rs: XDG path resolution.crates/navigator-bootstrap/src/constants.rs: Shared constants (image name, network name, container/volume naming).deploy/docker/Dockerfile.cluster: Container image definition (k3s base + Helm charts + manifests + entrypoint).deploy/docker/cluster-entrypoint.sh: Container entrypoint (DNS proxy, registry config, manifest injection).deploy/docker/cluster-healthcheck.sh: Docker HEALTHCHECK script.- Docker daemon(s):
- Local daemon for local deploys.
- Remote daemon over SSH for remote deploy container operations.
CLI Commands
All gateway lifecycle commands live under openshell gateway:
| Command | Description |
|---|---|
openshell gateway start [--name NAME] [--remote user@host] [--ssh-key PATH] |
Provision or update a gateway |
openshell gateway stop [--name NAME] [--remote user@host] |
Stop the container (preserves state) |
openshell gateway destroy [--name NAME] [--remote user@host] |
Destroy container, attached volumes, kubeconfig directory, metadata, and network |
openshell gateway info [--name NAME] |
Show deployment details (endpoint, kubeconfig path, SSH host) |
openshell gateway tunnel [--name NAME] [--remote user@host] [--print-command] |
Start or print SSH tunnel for kubectl access |
openshell status |
Show gateway health via gRPC/HTTP |
openshell gateway select <name> |
Set the active gateway |
openshell gateway select |
List all gateways with metadata |
The --name flag defaults to "openshell". When omitted on commands that accept it, the CLI resolves the active gateway via: --gateway flag, then OPENSHELL_GATEWAY env, then ~/.config/openshell/active_gateway file.
For remote dev/test deploys from a local checkout, scripts/remote-deploy.sh
wraps a different workflow: it rsyncs the repository to a remote host, builds
the release CLI plus cluster/server/sandbox images on that machine, and then
invokes openshell gateway start with explicit flags such as --recreate,
--plaintext, or --disable-gateway-auth only when requested.
Local Task Flows (mise)
Development task entrypoints split bootstrap behavior:
| Task | Behavior |
|---|---|
mise run cluster |
Bootstrap or incremental deploy: creates gateway if needed (fast recreate), then detects changed files and rebuilds/pushes only impacted components |
For mise run cluster, .env acts as local source-of-truth for GATEWAY_NAME, GATEWAY_PORT, and OPENSHELL_GATEWAY. Missing keys are appended; existing values are preserved. If GATEWAY_PORT is missing, the task selects a free local port and persists it.
Fast mode ensures a local registry (127.0.0.1:5000) is running and configures k3s to mirror pulls via host.docker.internal:5000, so the cluster task can push/pull local component images consistently.
Bootstrap Sequence Diagram
sequenceDiagram
participant U as User
participant C as navigator-cli
participant B as navigator-bootstrap
participant L as Local Docker daemon
participant R as Remote Docker daemon (SSH)
U->>C: openshell gateway start --remote user@host
C->>B: deploy_gateway(DeployOptions)
B->>B: create_ssh_docker_client (ssh://, 600s timeout)
B->>R: pull_remote_image (registry auth, platform-aware)
B->>R: tag image to local ref
Note over B,R: Docker socket APIs only, no extra host dependencies
B->>B: resolve SSH host for extra TLS SANs
B->>R: ensure_network (bridge, attachable)
B->>R: ensure_volume
B->>R: ensure_container (privileged, k3s server)
B->>R: start_container
B->>R: wait_for_kubeconfig (30 attempts, 2s apart)
B->>B: rewrite and store kubeconfig
B->>R: clean_stale_nodes (kubectl delete node)
B->>R: wait_for_gateway_ready (180 attempts, 2s apart)
B->>R: poll for secret navigator-cli-client (90 attempts, 2s apart)
R-->>B: ca.crt, tls.crt, tls.key
B->>B: atomically store mTLS bundle
B->>B: create and persist gateway metadata JSON
B->>B: save_active_gateway
B-->>C: GatewayHandle (kubeconfig_path, metadata, docker client)
End-State Connectivity Diagram
flowchart LR
subgraph WS[User workstation]
NAV[navigator-cli]
KUBECTL[kubectl]
KC[stored kubeconfig ~/.config/openshell/gateways/NAME/kubeconfig]
MTLS[mTLS bundle ca.crt, tls.crt, tls.key]
TUN[ssh -L 6443:127.0.0.1:6443 user@host]
end
subgraph HOST[Target host]
DOCKER[Docker daemon]
K3S[openshell-cluster-NAME single k3s container]
KAPI[Kubernetes API :6443]
G8080[Gateway :port -> :30051 mTLS default 8080]
SBX[Sandbox runtime]
end
KC --> KUBECTL
KUBECTL --> KAPI
TUN -. remote mode .-> KAPI
NAV --> G8080
MTLS -. client cert auth .-> G8080
DOCKER --> K3S
K3S --> KAPI
K3S --> G8080
K3S --> G80
K3S --> G443
Deploy Flow
1) Entry and client selection
deploy_gateway(DeployOptions) in crates/navigator-bootstrap/src/lib.rs chooses execution mode:
DeployOptionsfields:name: String,image_ref: Option<String>,remote: Option<RemoteOptions>,port: u16(default 8080).RemoteOptionsfields:destination: String,ssh_key: Option<String>.- Local deploy: Create one Docker client with
Docker::connect_with_local_defaults(). - Remote deploy: Create SSH Docker client via
Docker::connect_with_ssh()with a 600-second timeout (for large image transfers). The destination is prefixed withssh://if not already present.
The deploy_gateway_with_logs variant accepts an FnMut(String) callback for progress reporting. The CLI wraps this in a GatewayDeployLogPanel for interactive terminals.
Pre-deploy check (CLI layer in gateway_start): In interactive terminals, check_existing_deployment inspects whether a container or volume already exists. If found, the user is prompted to destroy and recreate or reuse the existing gateway.
2) Image readiness
Image ref resolution in default_gateway_image_ref():
- If
OPENSHELL_CLUSTER_IMAGEis set and non-empty, use it verbatim. - Otherwise, use the published distribution image base (
<distribution-registry>/openshell/cluster) with its default tag behavior.
- Local deploy:
ensure_image()inspects the image on the local daemon and pulls from the configured registry if missing (using built-in distribution credentials when pulling from the default distribution host). - Remote deploy:
pull_remote_image()queries the remote daemon's architecture viaDocker::version(), pulls the matching platform variant from the distribution registry (with XOR-decoded credentials), and tags the pulled image to the expected local ref (for exampleopenshell/cluster:devwhen an explicit local tag is requested).
3) Runtime infrastructure
For the target daemon (local or remote):
- Ensure bridge network
openshell-cluster(attachable, bridge driver) viaensure_network(). - Ensure volume
openshell-cluster-{name}viaensure_volume(). - Compute extra TLS SANs:
- For local deploys: Check
DOCKER_HOSTfor a non-loopbacktcp://endpoint (e.g.,tcp://docker:2375in CI). If found, extract the host as an extra SAN. The functionlocal_gateway_host_from_docker_host()skipslocalhost,127.0.0.1, and::1. - For remote deploys: Extract the host from the SSH destination (handles
user@host,ssh://user@host), resolve viassh -Gto get the canonical hostname/IP. Include both the resolved host and original SSH host (if different) as extra SANs.
- For local deploys: Check
- Ensure container
openshell-cluster-{name}viaensure_container():-
k3s server command:
server --disable=traefik --tls-san=127.0.0.1 --tls-san=localhost --tls-san=host.docker.internalplus computed extra SANs. -
Privileged mode.
-
Volume bind mount:
openshell-cluster-{name}:/var/lib/rancher/k3s. -
Network:
openshell-cluster. -
Extra host:
host.docker.internal:host-gateway. -
Port mappings:
Container Port Host Port Purpose 6443/tcp 6443 Kubernetes API 30051/tcp configurable (default 8080) OpenShell service NodePort (mTLS) -
Container environment variables (see Container Environment Variables below).
-
If the container exists with a different image ID (compared by inspecting the content-addressable ID), it is stopped, force-removed, and recreated. If the image matches, the existing container is reused.
-
- Start container via
start_container(). Tolerates already-running 409 conflict.
4) Readiness and artifact extraction
After the container starts:
- Poll kubeconfig:
wait_for_kubeconfig()runscat /etc/rancher/k3s/k3s.yamlviadocker execup to 30 times, 2 seconds apart (60s total). Each attempt first checks that the container is still running. Validates the output containsapiVersion:andclusters:. - Rewrite kubeconfig: Replace the server address with
https://127.0.0.1:6443. Renamedefaultentries (cluster, context, user) to the gateway name. Remote mode appends-remotesuffix (e.g.,{name}-remote) to avoid collisions with local contexts. - Store kubeconfig at
~/.config/openshell/gateways/{name}/kubeconfig(or$XDG_CONFIG_HOME/openshell/gateways/{name}/kubeconfig). - Clean stale nodes:
clean_stale_nodes()findsNotReadynodes viakubectl get nodesand deletes them. This is needed when a container is recreated but reuses the persistent volume -- k3s registers a new node (using the container ID as hostname) while old node entries persist in etcd. Non-fatal on error; returns the count of removed nodes. - Push local images (optional, local deploy only): If
OPENSHELL_PUSH_IMAGESis set, the comma-separated image refs are exported from the local Docker daemon as a single tar, uploaded into the container viadocker put_archive, and imported into containerd viactr images importin thek8s.ionamespace. After import,kubectl rollout restart deployment/navigator -n navigatoris run, followed bykubectl rollout status --timeout=180sto wait for completion. Seecrates/navigator-bootstrap/src/push.rs. - Wait for gateway health:
wait_for_gateway_ready()polls the Docker HEALTHCHECK status up to 180 times, 2 seconds apart (6 min total). A background task streams container logs during this wait. Failure modes:- Container exits during polling: error includes recent log lines.
- Container has no HEALTHCHECK instruction: fails immediately.
- HEALTHCHECK reports unhealthy on final attempt: error includes recent logs.
5) mTLS bundle capture
TLS is always required. fetch_and_store_cli_mtls() polls for Kubernetes secret navigator-cli-client in namespace navigator (90 attempts, 2 seconds apart, 3 min total). Each attempt checks the container is still running. The secret's base64-encoded ca.crt, tls.crt, and tls.key fields are decoded and stored.
Storage location: ~/.config/openshell/gateways/{name}/mtls/
Write is atomic: write to .tmp directory, validate all three files are non-empty, rename existing directory to .bak, rename .tmp to final path, then remove .bak.
6) Metadata persistence
create_gateway_metadata() produces a GatewayMetadata struct:
- Local: endpoint
https://127.0.0.1:{port}by default, orhttps://{docker_host}:{port}whenDOCKER_HOSTis a non-loopbacktcp://endpoint.is_remote=false. - Remote: endpoint
https://{resolved_host}:{port},is_remote=true, plus SSH destination and resolved host.
Metadata fields:
| Field | Type | Description |
|---|---|---|
name |
String |
Gateway name |
gateway_endpoint |
String |
HTTPS endpoint with port (e.g., https://127.0.0.1:8080) |
is_remote |
bool |
Whether gateway is remote |
gateway_port |
u16 |
Host port mapped to the gateway NodePort |
remote_host |
Option<String> |
SSH destination (e.g., user@host) |
resolved_host |
Option<String> |
Resolved hostname/IP from ssh -G |
Metadata location: ~/.config/openshell/gateways/{name}_metadata.json
Note: metadata is stored at the gateways/ level (not nested inside {name}/ like kubeconfig and mTLS).
After deploy, the CLI calls save_active_gateway(name), writing the gateway name to ~/.config/openshell/active_gateway. Subsequent commands that don't specify --gateway or OPENSHELL_GATEWAY resolve to this active gateway.
Container Image
The gateway image is defined in deploy/docker/Dockerfile.cluster:
Base: rancher/k3s:v1.35.2-k3s1
Layers added:
- Custom entrypoint:
deploy/docker/cluster-entrypoint.sh->/usr/local/bin/cluster-entrypoint.sh - Healthcheck script:
deploy/docker/cluster-healthcheck.sh->/usr/local/bin/cluster-healthcheck.sh - Packaged Helm charts:
deploy/docker/.build/charts/*.tgz->/var/lib/rancher/k3s/server/static/charts/ - Kubernetes manifests:
deploy/kube/manifests/*.yaml->/opt/navigator/manifests/
Bundled manifests include:
navigator-helmchart.yaml(OpenShell Helm chart auto-deploy)envoy-gateway-helmchart.yaml(Envoy Gateway for Gateway API)agent-sandbox.yaml
The HEALTHCHECK is configured as: --interval=5s --timeout=5s --start-period=20s --retries=60.
Entrypoint Script
deploy/docker/cluster-entrypoint.sh runs before k3s starts. It performs:
DNS proxy setup
On Docker custom networks, /etc/resolv.conf contains 127.0.0.11 (Docker's internal DNS). k3s detects this loopback and falls back to 8.8.8.8, which does not work on Docker Desktop. The entrypoint solves this by:
- Discovering Docker's real DNS listener ports from the
DOCKER_OUTPUTiptables chain. - Getting the container's
eth0IP as a routable address. - Adding DNAT rules in PREROUTING to forward DNS from pod namespaces through to Docker's DNS.
- Writing a custom resolv.conf pointing to the container IP.
- Passing
--resolv-conf=/etc/rancher/k3s/resolv.confto k3s.
Falls back to 8.8.8.8 / 8.8.4.4 if iptables detection fails.
Registry configuration
Writes /etc/rancher/k3s/registries.yaml from REGISTRY_HOST, REGISTRY_ENDPOINT, REGISTRY_USERNAME, REGISTRY_PASSWORD, and REGISTRY_INSECURE environment variables so that k3s/containerd can authenticate when pulling component images at runtime.
Manifest injection
Copies bundled manifests from /opt/navigator/manifests/ to /var/lib/rancher/k3s/server/manifests/. This is needed because the volume mount on /var/lib/rancher/k3s overwrites any files baked into that path at image build time.
Image configuration overrides
When environment variables are set, the entrypoint modifies the HelmChart manifest at /var/lib/rancher/k3s/server/manifests/navigator-helmchart.yaml:
IMAGE_REPO_BASE: Rewritesrepository:,sandboxImage:, andjobImage:in the HelmChart.PUSH_IMAGE_REFS: In push mode, parses comma-separated image refs and rewrites the exact gateway, sandbox, and pki-job image references (matching on path component/server:,/sandbox:,/pki-job:).IMAGE_TAG: Replaces:latesttags with the specified tag on gateway, sandbox, and pki-job images. Handles both quoted and unquotedtag: latestformats.IMAGE_PULL_POLICY: ReplacespullPolicy: Alwayswith the specified policy (e.g.,IfNotPresent).SSH_GATEWAY_HOST/SSH_GATEWAY_PORT: Replaces__SSH_GATEWAY_HOST__and__SSH_GATEWAY_PORT__placeholders.EXTRA_SANS: Builds a YAML flow-style list from the comma-separated SANs and replacesextraSANs: [].
Healthcheck Script
deploy/docker/cluster-healthcheck.sh validates cluster readiness through a series of checks:
- Kubernetes API:
kubectl get --raw='/readyz' - OpenShell StatefulSet: Checks that
statefulset/navigatorin namespacenavigatorexists and has 1 ready replica. - Gateway: Checks that
gateway/navigator-gatewayin namespacenavigatorhas theProgrammedcondition. - mTLS secret (conditional): If
NAV_GATEWAY_TLS_ENABLEDis true (or inferred from the HelmChart manifest using the same two-path detection logic as the bootstrap code), checks that secretnavigator-cli-clientexists with non-emptyca.crt,tls.crt, andtls.keydata.
Remote Image Transfer
flowchart LR
B[bootstrap] -->|query| RP[Remote platform: Docker version API]
RP --> Auth[Authenticate with distribution registry]
Auth --> Pull[Pull platform-specific image layers]
Pull --> Tag[Tag to local image ref]
Tag --> OK[Image available as openshell/cluster:TAG]
- Remote platform is queried via
Docker::version()and normalized (e.g.,x86_64->amd64,aarch64->arm64). - Distribution registry credentials are XOR-encoded in the binary (lightweight obfuscation, not a security boundary).
- If the image ref looks local (no
/in repository), thelatesttag is used from the distribution registry regardless of the localIMAGE_TAG.
Access Model
Kubernetes API access
- Kubeconfig always targets
https://127.0.0.1:6443. - For remote gateways, the user must open an SSH local-forward tunnel:
ssh -L 6443:127.0.0.1:6443 -N user@host
CLI helper:
openshell gateway tunnel --name <name>
The --remote flag is optional; the CLI resolves the SSH destination from stored gateway metadata. Pass --print-command to print the SSH command without executing it.
Gateway endpoint exposure
- Local:
https://127.0.0.1:{port}(orhttps://{docker_host}:{port}whenDOCKER_HOSTis a non-loopback TCP endpoint). Default port is 8080. - Remote:
https://<resolved-remote-host>:{port}. - The host port (configurable via
--port, default 8080) maps to container port 30051 (OpenShell service NodePort).
Lifecycle Operations
stop
GatewayHandle::stop() calls stop_container(), which tolerates 404 (not found) and 409 (already stopped).
destroy
Bootstrap layer (GatewayHandle::destroy() -> destroy_gateway_resources()):
- Stop the container.
- Remove the container (
force=true). Tolerates 404. - Remove the volume (
force=true). Tolerates 404. - Remove the stored kubeconfig file.
- Remove the network if no containers remain attached (
cleanup_network_if_unused()).
CLI layer (gateway_destroy() in run.rs additionally):
- Remove the metadata JSON file via
remove_gateway_metadata(). - Clear the active gateway reference if it matches the destroyed gateway.
Idempotency and Error Behavior
- Re-running deploy is safe:
- Existing network/volume are reused (inspect before create).
- If a container exists with the same image ID, it is reused; if the image changed, the container is recreated.
start_containertolerates already-running state (409).
- In interactive terminals, the CLI prompts the user to optionally destroy and recreate an existing gateway before redeploying.
- Error handling surfaces:
- Docker API failures from inspect/create/start/remove.
- SSH connection failures when creating the remote Docker client.
- Kubeconfig readiness timeout (60s) with recent container logs in the error message.
- Health check timeout (6 min) with recent container logs.
- Container exit during any polling phase (kubeconfig, health, mTLS) with diagnostic information (exit code, OOM status, recent logs).
- mTLS secret polling timeout (3 min).
- Local image ref without registry prefix: clear error with build instructions rather than a failed Docker Hub pull.
Auto-Bootstrap from sandbox create
When openshell sandbox create cannot connect to a gateway (connection refused, DNS error, missing default TLS certs), the CLI offers to bootstrap one automatically:
should_attempt_bootstrap()incrates/navigator-cli/src/bootstrap.rschecks the error type. It returnstruefor connectivity errors and missing default TLS materials, butfalsefor TLS handshake/auth errors.- If running in a terminal, the user is prompted to confirm.
run_bootstrap()deploys a gateway named"openshell", sets it as active, and returns freshTlsOptionspointing to the newly-written mTLS certs.
Container Environment Variables
Variables set on the container by ensure_container() in docker.rs:
| Variable | Value | When Set |
|---|---|---|
REGISTRY_MODE |
"external" |
Always |
REGISTRY_HOST |
Distribution registry host (or OPENSHELL_REGISTRY_HOST override) |
Always |
REGISTRY_INSECURE |
"true" or "false" |
Always |
IMAGE_REPO_BASE |
{registry_host}/{namespace} (or IMAGE_REPO_BASE/OPENSHELL_IMAGE_REPO_BASE override) |
Always |
REGISTRY_ENDPOINT |
Custom endpoint URL | When OPENSHELL_REGISTRY_ENDPOINT is set |
REGISTRY_USERNAME |
Registry auth username | When credentials available |
REGISTRY_PASSWORD |
Registry auth password | When credentials available |
EXTRA_SANS |
Comma-separated extra TLS SANs | When extra SANs computed |
SSH_GATEWAY_HOST |
Resolved remote hostname/IP | Remote deploys only |
SSH_GATEWAY_PORT |
Configured host port (default 8080) |
Remote deploys only |
IMAGE_TAG |
Image tag (e.g., "dev") |
When IMAGE_TAG env is set or push mode |
IMAGE_PULL_POLICY |
"IfNotPresent" |
Push mode only |
PUSH_IMAGE_REFS |
Comma-separated image refs | Push mode only |
Host-Side Environment Variables
Environment variables that affect bootstrap behavior when set on the host:
| Variable | Effect |
|---|---|
OPENSHELL_CLUSTER_IMAGE |
Overrides entire image ref if set and non-empty |
IMAGE_TAG |
Sets image tag (default: "dev") when OPENSHELL_CLUSTER_IMAGE is not set |
NAV_GATEWAY_TLS_ENABLED |
Overrides HelmChart manifest for TLS enabled check (true/1/yes/false/0/no) |
XDG_CONFIG_HOME |
Base config directory (default: $HOME/.config) |
KUBECONFIG |
Target kubeconfig path for merge (first colon-separated path; default: $HOME/.kube/config) |
DOCKER_HOST |
When tcp:// and non-loopback, the host is added as a TLS SAN and used as the gateway endpoint |
OPENSHELL_PUSH_IMAGES |
Comma-separated image refs to push into the gateway's containerd (local deploy only) |
OPENSHELL_REGISTRY_HOST |
Override the distribution registry host |
OPENSHELL_REGISTRY_NAMESPACE |
Override the registry namespace (default: "navigator") |
IMAGE_REPO_BASE / OPENSHELL_IMAGE_REPO_BASE |
Override the image repository base path |
OPENSHELL_REGISTRY_INSECURE |
Use HTTP instead of HTTPS for registry mirror |
OPENSHELL_REGISTRY_ENDPOINT |
Custom registry mirror endpoint |
OPENSHELL_REGISTRY_USERNAME |
Override registry auth username |
OPENSHELL_REGISTRY_PASSWORD |
Override registry auth password |
OPENSHELL_GATEWAY |
Set the active gateway name for CLI commands |
File System Layout
Artifacts stored under $XDG_CONFIG_HOME/openshell/ (default ~/.config/openshell/):
openshell/
active_gateway # plain text: active gateway name
gateways/
{name}_metadata.json # GatewayMetadata JSON
{name}/
kubeconfig # rewritten kubeconfig
mtls/ # mTLS bundle (when TLS enabled)
ca.crt
tls.crt
tls.key
Implementation References
crates/navigator-bootstrap/src/lib.rs-- public API, deploy orchestrationcrates/navigator-bootstrap/src/docker.rs-- Docker API wrapperscrates/navigator-bootstrap/src/image.rs-- registry pull, XOR credentialscrates/navigator-bootstrap/src/runtime.rs-- exec, health polling, stale node cleanupcrates/navigator-bootstrap/src/kubeconfig.rs-- kubeconfig rewrite and mergecrates/navigator-bootstrap/src/metadata.rs-- metadata CRUD, active gateway, SSH resolutioncrates/navigator-bootstrap/src/mtls.rs-- TLS detection, secret extraction, atomic writecrates/navigator-bootstrap/src/push.rs-- local image push into k3s containerdcrates/navigator-bootstrap/src/constants.rs-- naming conventionscrates/navigator-bootstrap/src/paths.rs-- XDG path helperscrates/navigator-cli/src/main.rs-- CLI command definitionscrates/navigator-cli/src/run.rs-- CLI command implementationscrates/navigator-cli/src/bootstrap.rs-- auto-bootstrap from sandbox createdeploy/docker/Dockerfile.cluster-- container image definitiondeploy/docker/cluster-entrypoint.sh-- container entrypoint scriptdeploy/docker/cluster-healthcheck.sh-- Docker HEALTHCHECK scriptdeploy/kube/manifests/navigator-helmchart.yaml-- OpenShell Helm chart manifestdeploy/kube/manifests/envoy-gateway-helmchart.yaml-- Envoy Gateway manifest