Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions Makefile-quickstart
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,26 @@ openshell-saw-configure-gateway: .check-saw-name ## Configure openshell CLI for
fi; \
OIDC_OPTS=""; \
if [ -n "$$OIDC_ISSUER" ]; then OIDC_OPTS="--oidc-issuer $$OIDC_ISSUER --oidc-client-id $(OIDC_CLIENT_ID)"; fi; \
GW_CONFIG_DIR="$$HOME/.config/openshell/gateways/$(OPENSHELL_SAW_NAME)"; \
echo "Extracting CA certificate from cluster..."; \
mkdir -p "$$GW_CONFIG_DIR/mtls"; \
oc get secret $(OPENSHELL_SAW_NAME)-gateway-ca -n $(NS) \
-o jsonpath='{.data.ca\.crt}' 2>/dev/null \
| base64 -d > "$$GW_CONFIG_DIR/mtls/ca.crt" 2>/dev/null || true; \
if [ ! -s "$$GW_CONFIG_DIR/mtls/ca.crt" ]; then \
echo "Error: CA certificate not found. The setup job may still be running."; \
echo " Run 'make openshell-saw-logs' to check, then re-run this target."; \
rm -f "$$GW_CONFIG_DIR/mtls/ca.crt"; \
exit 1; \
fi; \
echo "CA certificate installed."; \
echo "Registering gateway '$(OPENSHELL_SAW_NAME)'..."; \
openshell gateway remove $(OPENSHELL_SAW_NAME) 2>/dev/null || true; \
openshell gateway add "$$GW_URL" --name $(OPENSHELL_SAW_NAME) --gateway-insecure $$OIDC_OPTS; \
openshell gateway add "$$GW_URL" --name $(OPENSHELL_SAW_NAME) $$OIDC_OPTS; \
openshell gateway select $(OPENSHELL_SAW_NAME); \
echo "Gateway '$(OPENSHELL_SAW_NAME)' configured. Endpoint: $$GW_URL"; \
OIDC_TOKEN_FILE="$(OIDC_TOKEN_DIR)/token.json"; \
GW_TOKEN_DIR="$$HOME/.config/openshell/gateways/$(OPENSHELL_SAW_NAME)"; \
GW_TOKEN_DIR="$$GW_CONFIG_DIR"; \
if [ -f "$$OIDC_TOKEN_FILE" ] && [ -n "$$OIDC_ISSUER" ]; then \
echo "Copying OIDC token to gateway config..."; \
mkdir -p "$$GW_TOKEN_DIR"; \
Expand Down Expand Up @@ -380,7 +393,7 @@ openshell-saw-logs: .check-saw-name ## Follow sandbox setup Job logs
oc -n $(NS) logs -f job/$(OPENSHELL_SAW_NAME)-setup

openshell-saw-ssh: .check-saw-name ## SSH into a sandbox
@ssh -o "ProxyCommand=openshell --gateway-insecure ssh-proxy --gateway-name $(OPENSHELL_SAW_NAME) --name $(OPENSHELL_SAW_NAME) " \
@ssh -o "ProxyCommand=openshell ssh-proxy --gateway-name $(OPENSHELL_SAW_NAME) --name $(OPENSHELL_SAW_NAME) " \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
Expand All @@ -392,11 +405,11 @@ nemoclaw-tui: .check-saw-name ## Launch NemoClaw TUI (e.g. make nemoclaw-tui SAN
@if [ -n "$(SANDBOX_NAME)" ]; then \
SANDBOX="$(SANDBOX_NAME)"; \
else \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) --gateway-insecure sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
fi; \
if [ -z "$$SANDBOX" ]; then echo "Error: No sandboxes found on gateway '$(OPENSHELL_SAW_NAME)' workspace '$(WORKSPACE)'."; exit 1; fi; \
echo "Connecting to sandbox '$$SANDBOX' workspace '$(WORKSPACE)' (nemoclaw)..."; \
ssh -o "ProxyCommand=openshell --gateway-insecure ssh-proxy --gateway-name $(OPENSHELL_SAW_NAME) --name $$SANDBOX " \
ssh -o "ProxyCommand=openshell ssh-proxy --gateway-name $(OPENSHELL_SAW_NAME) --name $$SANDBOX " \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
Expand All @@ -406,11 +419,11 @@ openclaw-tui: .check-saw-name ## Launch OpenClaw TUI (e.g. make openclaw-tui SAN
@if [ -n "$(SANDBOX_NAME)" ]; then \
SANDBOX="$(SANDBOX_NAME)"; \
else \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) --gateway-insecure sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
fi; \
if [ -z "$$SANDBOX" ]; then echo "Error: No sandboxes found on gateway '$(OPENSHELL_SAW_NAME)' workspace '$(WORKSPACE)'."; exit 1; fi; \
echo "Connecting to sandbox '$$SANDBOX' workspace '$(WORKSPACE)' (openclaw)..."; \
openshell --gateway-insecure sandbox exec -n $$SANDBOX --workspace $(WORKSPACE) --tty -- \
openshell sandbox exec -n $$SANDBOX --workspace $(WORKSPACE) --tty -- \
/bin/bash -ic 'export HOME=/sandbox OPENCLAW_HOME=/sandbox SQLITE_TMPDIR=/sandbox/.openclaw/state TMPDIR=/sandbox/.openclaw/state OPENCLAW_NIX_MODE=0 TERM=xterm-256color; exec openclaw tui'

openshell-saw-tui: .check-saw-name ## Launch TUI (alias for nemoclaw-tui)
Expand All @@ -420,7 +433,7 @@ nemoclaw-gui: .check-saw-name ## Open NemoClaw web UI (e.g. make nemoclaw-gui SA
@if [ -n "$(SANDBOX_NAME)" ]; then \
SANDBOX="$(SANDBOX_NAME)"; \
else \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) --gateway-insecure sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
fi; \
if [ -z "$$SANDBOX" ]; then echo "Error: No sandboxes found."; exit 1; fi; \
SANDBOX_NAME=$$SANDBOX GATEWAY_NAME=$(OPENSHELL_SAW_NAME) \
Expand All @@ -430,13 +443,12 @@ openclaw-gui: .check-saw-name ## Open OpenClaw web UI (e.g. make openclaw-gui SA
@if [ -n "$(SANDBOX_NAME)" ]; then \
SANDBOX="$(SANDBOX_NAME)"; \
else \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) --gateway-insecure sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
SANDBOX=$$(openshell --gateway $(OPENSHELL_SAW_NAME) sandbox list --workspace $(WORKSPACE) --names 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+$$' | head -1); \
fi; \
if [ -z "$$SANDBOX" ]; then echo "Error: No sandboxes found."; exit 1; fi; \
echo "Fetching openclaw gateway token..."; \
TOKEN=$$(openshell --gateway-insecure sandbox exec -n $$SANDBOX --workspace $(WORKSPACE) --no-tty -- \
TOKEN=$$(openshell sandbox exec -n $$SANDBOX --workspace $(WORKSPACE) --no-tty -- \
sh -c 'OPENCLAW_HOME=/sandbox cat /sandbox/.openclaw/openclaw.json' 2>/dev/null \
| grep -v 'TLS certificate' \
| python3 -c "import sys,json; c=json.load(sys.stdin); print(c.get('gateway',{}).get('auth',{}).get('token',''))" 2>/dev/null || true); \
if [ -z "$$TOKEN" ]; then echo "Error: Could not extract token."; exit 1; fi; \
GUI_PORT=$${GUI_PORT:-18789}; \
Expand All @@ -446,7 +458,7 @@ openclaw-gui: .check-saw-name ## Open OpenClaw web UI (e.g. make openclaw-gui SA
echo "OpenClaw UI: http://localhost:$$GUI_PORT/#token=$$TOKEN"; \
echo "Press Ctrl-C to stop."; \
echo ""; \
ssh -o "ProxyCommand=openshell --gateway-insecure ssh-proxy --gateway-name $(OPENSHELL_SAW_NAME) --name $$SANDBOX" \
ssh -o "ProxyCommand=openshell ssh-proxy --gateway-name $(OPENSHELL_SAW_NAME) --name $$SANDBOX" \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
Expand Down
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,10 @@ make copy-images
make login # Opens browser → login with alice / alice
export OPENSHELL_SAW_NAME=openshell-saw
make openshell-saw-configure-gateway
openshell gateway login $OPENSHELL_SAW_NAME --gateway-insecure # Authenticate CLI with gateway
openshell gateway login $OPENSHELL_SAW_NAME # Authenticate CLI with gateway

# 8. Verify
openshell --gateway-insecure sandbox list
openshell sandbox list
```

#### Option B: Quickstart (manual, step-by-step)
Expand Down Expand Up @@ -258,7 +258,7 @@ openshell gateway add https://$(oc get route openshell-saw-gateway -n openshell-
# Log in as alice / alice in the browser

# 15. Verify sandboxes
openshell --gateway-insecure sandbox list
openshell sandbox list

# 16. Launch TUI (pick one)
OPENSHELL_SAW_NAME=openshell-saw SANDBOX_NAME=cuda-sandbox make nemoclaw-tui # NemoClaw
Expand All @@ -269,8 +269,6 @@ OPENSHELL_SAW_NAME=openshell-saw SANDBOX_NAME=cuda-sandbox GUI_PORT=18789 make n
OPENSHELL_SAW_NAME=openshell-saw SANDBOX_NAME=notebook GUI_PORT=18790 make openclaw-gui
```

> **Note:** The gateway VM uses a self-signed TLS certificate. Pass `--gateway-insecure` to `openshell` commands, or set `export OPENSHELL_GATEWAY_INSECURE=true`.

> **Token expiry:** The OIDC access token lasts 10 hours. If it expires, run `make login` to re-authenticate, then `make openshell-saw-configure-gateway` to copy the fresh token. Alternatively, run `openshell gateway login` directly to re-authenticate with the gateway.

You can set `OPENSHELL_SAW_NAME` once via `export` and all `openshell-saw-*` targets will use it automatically.
Expand All @@ -293,7 +291,7 @@ You can set `OPENSHELL_SAW_NAME` once via `export` and all `openshell-saw-*` tar

```bash
# List sandboxes
openshell --gateway-insecure sandbox list
openshell sandbox list

# NemoClaw sandbox (TUI and GUI)
OPENSHELL_SAW_NAME=openshell-saw SANDBOX_NAME=cuda-sandbox make nemoclaw-tui
Expand Down
19 changes: 19 additions & 0 deletions charts/openshell-saw/files/export-ca-cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Phase: export gateway CA certificate to a Kubernetes Secret.
# The CLI reads this CA to verify the gateway's TLS certificate,
# eliminating the need for --gateway-insecure.
# Expects: VM_NAME, NS, guest_ssh (function)

echo "Exporting gateway CA certificate..."
CA_CERT="$(guest_ssh 'cat $HOME/.local/state/openshell/tls/ca.crt 2>/dev/null' || true)"

if [[ -z "${CA_CERT}" ]]; then
echo "ERROR: CA certificate not found on VM — gateway certs were not generated" >&2
return 1
fi

kubectl create secret generic "${VM_NAME}-gateway-ca" -n "${NS}" \
--from-literal=ca.crt="${CA_CERT}" \
--dry-run=client -o yaml | kubectl apply -f -

echo "CA certificate exported to secret/${VM_NAME}-gateway-ca"
9 changes: 6 additions & 3 deletions charts/openshell-saw/files/run-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,16 @@ source "${SCRIPTS_DIR}/wait-for-vm.sh"
# --- Phase 4: Upgrade OpenShell binaries ---
source "${SCRIPTS_DIR}/upgrade-openshell.sh"

# --- Phase 5: Governance check + SSH key fallback ---
# --- Phase 5: Export CA certificate ---
source "${SCRIPTS_DIR}/export-ca-cert.sh"

# --- Phase 6: Governance check + SSH key fallback ---
source "${SCRIPTS_DIR}/check-governance.sh"

# --- Phase 6: BOM profile setup ---
# --- Phase 7: BOM profile setup ---
source "${SCRIPTS_DIR}/setup-bom-profiles.sh"

# --- Phase 7: Dashboard setup ---
# --- Phase 8: Dashboard setup ---
if [[ "${DASHBOARD_ENABLED}" == "true" ]]; then
source "${SCRIPTS_DIR}/setup-keycloak-redirect.sh"
fi
Expand Down
2 changes: 1 addition & 1 deletion charts/openshell-saw/files/upgrade-openshell.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fi

# --- Restart gateway with new binaries ---
echo "Restarting gateway service..."
guest_ssh "systemctl --user restart openshell-gateway.service" || true
guest_ssh "systemctl --user daemon-reload && systemctl --user restart openshell-gateway.service" || true
GW_READY=0
for i in $(seq 1 10); do
if guest_ssh "systemctl --user is-active openshell-gateway.service" 2>/dev/null; then
Expand Down
15 changes: 15 additions & 0 deletions charts/openshell-saw/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ OpenShell rejects names longer than 19 chars with "name exceeds maximum length".
{{- end -}}
{{- end }}

{{/*
Resolve the external Route hostname for the gateway.
Priority: explicit route.host > computed from global.clusterDomain.
Used to add the route FQDN to the gateway TLS certificate SANs.
*/}}
{{- define "openshell-sandbox.routeHost" -}}
{{- if .Values.route.host -}}
{{- .Values.route.host -}}
{{- else if .Values.global -}}
{{- if .Values.global.clusterDomain -}}
{{- printf "%s-gateway-%s.apps.%s" (include "openshell-sandbox.fullname" .) .Release.Namespace .Values.global.clusterDomain -}}
{{- end -}}
{{- end -}}
{{- end }}

{{/*
Resolve the golden image DataSource name.
Priority: explicit source.dataSource > derived from containerRuntime.
Expand Down
16 changes: 16 additions & 0 deletions charts/openshell-saw/templates/cloudinit-sandbox.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ data:
{{- if or .Values.oidc.token $oidcIssuer }}
OPENSHELL_ENABLE_MTLS_AUTH=true
{{- end }}
{{- $routeHost := include "openshell-sandbox.routeHost" . }}
{{- if $routeHost }}
OPENSHELL_ROUTE_FQDN={{ $routeHost }}
{{- end }}

{{- $oidcIssuer := include "openshell-sandbox.oidcIssuerUrl" . }}
{{- if or .Values.oidc.token $oidcIssuer .Values.governance.enabled }}
Expand Down Expand Up @@ -106,6 +110,18 @@ data:
cp /etc/openshell/gateway.toml "${USER_HOME}/.config/openshell/gateway.toml"
fi
chown -R "${USER_NAME}:${USER_NAME}" "${USER_HOME}/.config/openshell"
ROUTE_FQDN=$(grep -oP 'OPENSHELL_ROUTE_FQDN=\K.*' "${USER_HOME}/.config/openshell/gateway.env" || true)
if [[ -n "${ROUTE_FQDN}" ]]; then
echo "Adding route FQDN to gateway cert SANs: ${ROUTE_FQDN}"
OVERRIDE_DIR="${USER_HOME}/.config/systemd/user/openshell-gateway.service.d"
install -d -m 0755 -o "${USER_NAME}" -g "${USER_NAME}" "${OVERRIDE_DIR}"
cat > "${OVERRIDE_DIR}/route-san.conf" <<SANEOF
[Service]
ExecStartPre=
ExecStartPre=/usr/local/bin/openshell-gateway generate-certs --output-dir \${OPENSHELL_LOCAL_TLS_DIR} --server-san host.openshell.internal --server-san ${ROUTE_FQDN}
SANEOF
chown "${USER_NAME}:${USER_NAME}" "${OVERRIDE_DIR}/route-san.conf"
fi
systemctl start openshell-gateway-setup.service

final_message: "openshell-saw cloud-init finished"
3 changes: 1 addition & 2 deletions scripts/demo-governance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ step() {
}

gw() {
openshell --gateway "${SAW_NAME}" --gateway-insecure "$@" 2>&1 \
| grep -v 'TLS certificate'
openshell --gateway "${SAW_NAME}" "$@" 2>&1
}

run_on_vm() {
Expand Down
5 changes: 2 additions & 3 deletions scripts/governance-profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ wait_for_sync() {
for i in $(seq 1 36); do
sleep 5
local profiles
profiles=$(openshell --gateway "${SAW_NAME}" --gateway-insecure provider list-profiles 2>&1 || true)
profiles=$(openshell --gateway "${SAW_NAME}" provider list-profiles 2>&1 || true)
if [[ -n "${expected_action}" && -n "${profile_name}" ]]; then
if [[ "${expected_action}" == "appear" ]] && echo "${profiles}" | grep -q "${profile_name}"; then
echo " Profile '${profile_name}' is now active. (${i} polls)"
Expand All @@ -68,8 +68,7 @@ wait_for_sync() {
cmd_list() {
echo "Active profiles (enforced on gateway):"
echo ""
openshell --gateway "${SAW_NAME}" --gateway-insecure provider list-profiles 2>&1 \
| grep -v 'TLS certificate'
openshell --gateway "${SAW_NAME}" provider list-profiles
}

cmd_add() {
Expand Down
13 changes: 11 additions & 2 deletions scripts/openshell-saw-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ if [[ "${NAMESPACE_MODE}" == "perUser" ]]; then
oc create namespace "${DEPLOY_NS}" --dry-run=client -o yaml | oc apply -f - 2>/dev/null
fi

# --- Compute route hostname ---
ROUTE_HOST=""
APPS_DOMAIN=$(oc get ingress.config.openshift.io cluster \
-o jsonpath='{.spec.domain}' 2>/dev/null || true)
if [[ -n "${APPS_DOMAIN}" ]]; then
ROUTE_HOST="${OPENSHELL_SAW_NAME}-gateway-${DEPLOY_NS}.${APPS_DOMAIN}"
fi

# --- Deploy ---
echo "Provisioning sandbox '${OPENSHELL_SAW_NAME}' for owner '${OWNER}' in namespace '${DEPLOY_NS}'..."

Expand All @@ -122,7 +130,8 @@ helm upgrade --install "${OPENSHELL_SAW_NAME}" "${SAW_CHART}" \
--set namespaceMode="${NAMESPACE_MODE}" \
--set containerRuntime="${CONTAINER_RUNTIME}" \
--set governance.enabled="${GOVERNANCE_ENABLED}" \
--set route.enabled=true --set route.dashboard=true
--set route.enabled=true --set route.dashboard=true \
${ROUTE_HOST:+--set route.host="${ROUTE_HOST}"}

echo ""
echo "Sandbox '${OPENSHELL_SAW_NAME}' deployed."
Expand All @@ -143,4 +152,4 @@ echo ""
echo "Next steps:"
echo " 1. make openshell-saw-configure-gateway OPENSHELL_SAW_NAME=${OPENSHELL_SAW_NAME} NS=${DEPLOY_NS}"
echo " 2. openshell gateway login"
echo " 3. openshell --gateway-insecure sandbox list"
echo " 3. openshell sandbox list"
9 changes: 4 additions & 5 deletions scripts/openshell-saw-gui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,20 @@ fi

# Fetch dashboard token via openshell sandbox exec
echo "Fetching dashboard token..."
TOKEN=$(openshell --gateway-insecure sandbox exec -n "${SANDBOX_NAME}" --no-tty -- \
TOKEN=$(openshell sandbox exec -n "${SANDBOX_NAME}" --no-tty -- \
cat /sandbox/.openclaw/openclaw.json 2>/dev/null \
| grep -v 'TLS certificate verification is disabled' \
| python3 -c "import sys,json; c=json.load(sys.stdin); print((c.get('gateway',{}).get('auth',{}).get('token','')))" 2>/dev/null | grep -oE '^[a-f0-9]+$' || true)

if [[ -z "${TOKEN}" ]]; then
TOKEN=$(openshell --gateway-insecure sandbox exec -n "${SANDBOX_NAME}" --no-tty -- \
TOKEN=$(openshell sandbox exec -n "${SANDBOX_NAME}" --no-tty -- \
cat /tmp/auth-token 2>/dev/null | grep -oE '[a-f0-9]{32,}' || true)
fi

if [[ -z "${TOKEN}" ]]; then
echo "Error: Could not extract dashboard token."
echo " Make sure the sandbox setup has completed and openclaw is configured."
echo ""
echo " Try: openshell --gateway-insecure sandbox list"
echo " Try: openshell sandbox list"
exit 1
fi

Expand All @@ -47,7 +46,7 @@ echo "Press Ctrl-C to stop."
echo ""

# Port-forward via openshell ssh-proxy — uses local OIDC token
ssh -o "ProxyCommand=openshell --gateway-insecure ssh-proxy --gateway-name ${GATEWAY_NAME} --name ${SANDBOX_NAME}" \
ssh -o "ProxyCommand=openshell ssh-proxy --gateway-name ${GATEWAY_NAME} --name ${SANDBOX_NAME}" \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR \
Expand Down