From e4ca94ba8e574330902a0d36398a4ca6fe3ef950 Mon Sep 17 00:00:00 2001 From: "Asad Iqbal (Saadi)" Date: Thu, 5 Mar 2026 13:39:26 +0500 Subject: [PATCH 1/6] Add installation of Tracebloc client Helm chart to script (#30) * Add installation of Tracebloc client Helm chart - Updated install-k8s.ps1 to include a new function for installing the Tracebloc client Helm chart as step 6. - Created install-client-helm.sh script to handle user input for configuration values and manage Helm chart installation. - Modified cluster.sh to ensure necessary directories for Tracebloc are created before cluster setup. - Updated install.sh and install-k8s.sh to source the new installation script and call the installation function during the setup process. * Update install-client-helm.sh to set Helm repository name and chart name - Set TRACEBLOC_HELM_REPO_NAME to "tracebloc" for clarity. - Updated TRACEBLOC_CHART_NAME to "client" for consistency with naming conventions. * Update values schema and test cases for docker registry secret creation - Modified the values schema to allow the dockerRegistry type to be either an object or null, clarifying its usage for public images. - Updated test case descriptions to accurately reflect the behavior of docker registry secret creation when the 'create' field is omitted. * Add client ID and password prompts in installation scripts - Enhanced install-k8s.ps1 and install-client-helm.sh to include prompts for client ID and password during installation. - Provided a link for users to create a client, emphasizing that the setup is free. --- client/Chart.yaml | 4 +- client/templates/NOTES.txt | 2 +- client/templates/_helpers.tpl | 20 +++ client/templates/docker-registry-secret.yaml | 2 + client/templates/jobs-manager-deployment.yaml | 2 + client/templates/logs-pvc.yaml | 4 +- client/templates/mysql-deployment.yaml | 2 + client/templates/mysql-storage-pvc.yaml | 4 +- .../templates/resource-monitor-daemonset.yaml | 2 + client/templates/shared-images-pvc.yaml | 4 +- client/templates/storage-class.yaml | 2 +- client/tests/secrets_test.yaml | 24 ++- client/tests/values-public-images.yaml | 3 + client/values.schema.json | 11 +- client/values.yaml | 22 ++- scripts/install-k8s.ps1 | 167 +++++++++++++++++- scripts/install-k8s.sh | 3 + scripts/install.sh | 2 + scripts/lib/cluster.sh | 14 +- scripts/lib/install-client-helm.sh | 151 ++++++++++++++++ 20 files changed, 416 insertions(+), 29 deletions(-) create mode 100644 client/tests/values-public-images.yaml create mode 100644 scripts/lib/install-client-helm.sh diff --git a/client/Chart.yaml b/client/Chart.yaml index 6ffa41c..1fb00f1 100644 --- a/client/Chart.yaml +++ b/client/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: client description: A unified Helm chart for tracebloc on AKS, EKS, bare-metal, and OpenShift type: application -version: 1.0.1 -appVersion: "1.0.1" +version: 1.0.2 +appVersion: "1.0.2" keywords: - tracebloc - kubernetes diff --git a/client/templates/NOTES.txt b/client/templates/NOTES.txt index 4fa07fd..0230d78 100644 --- a/client/templates/NOTES.txt +++ b/client/templates/NOTES.txt @@ -18,7 +18,7 @@ {{ "\033[1;34m" }}Storage:{{ "\033[0m" }} {{ "\033[0;33m" }}hostPath (bare-metal){{ "\033[0m" }} {{ "\033[1;34m" }}Host dirs:{{ "\033[0m" }} {{ "\033[0;33m" }}/tracebloc/data, /tracebloc/logs, /tracebloc/mysql (on the node){{ "\033[0m" }} {{- else }} - {{ "\033[1;34m" }}Storage:{{ "\033[0m" }} {{ "\033[0;33m" }}dynamic PVC ({{ .Values.storageClass.name }}){{ "\033[0m" }} + {{ "\033[1;34m" }}Storage:{{ "\033[0m" }} {{ "\033[0;33m" }}dynamic PVC ({{ include "tracebloc.storageClassName" . }}){{ "\033[0m" }} {{- end }} {{- if .Values.openshift.scc.enabled }} {{ "\033[1;34m" }}OpenShift SCC:{{ "\033[0m" }} {{ "\033[0;33m" }}tracebloc-resource-monitor-{{ .Release.Name }}{{ "\033[0m" }} diff --git a/client/templates/_helpers.tpl b/client/templates/_helpers.tpl index 54062f8..abcb4a0 100644 --- a/client/templates/_helpers.tpl +++ b/client/templates/_helpers.tpl @@ -69,6 +69,26 @@ mysql-pvc {{ .Release.Name }}-regcred {{- end }} +{{/* + StorageClass name: when storageClass.create is true, use a release-unique name + so each release gets its own StorageClass (avoids Helm ownership conflicts). + When create is false, use the user-provided storageClass.name for an existing class. +*/}} +{{- define "tracebloc.storageClassName" -}} +{{- if .Values.storageClass.create -}} +{{ .Release.Name }}-storage-class +{{- else -}} +{{ .Values.storageClass.name }} +{{- end -}} +{{- end -}} + +{{/* Whether to create registry secret and add imagePullSecrets. Only when dockerRegistry is present and create is true; omit dockerRegistry or set create: false for public images. */}} +{{- define "tracebloc.useImagePullSecrets" -}} +{{- if and .Values.dockerRegistry (default false .Values.dockerRegistry.create) -}} +true +{{- end -}} +{{- end }} + {{/* Image reference — defaults to docker.io when no registry is provided. Tag defaults to "prod" when CLIENT_ENV is omitted or empty. diff --git a/client/templates/docker-registry-secret.yaml b/client/templates/docker-registry-secret.yaml index a47c40a..07ea427 100644 --- a/client/templates/docker-registry-secret.yaml +++ b/client/templates/docker-registry-secret.yaml @@ -1,3 +1,4 @@ +{{- if include "tracebloc.useImagePullSecrets" . }} apiVersion: v1 kind: Secret metadata: @@ -8,3 +9,4 @@ metadata: type: kubernetes.io/dockerconfigjson data: .dockerconfigjson: {{ template "imagePullSecret" . }} +{{- end }} diff --git a/client/templates/jobs-manager-deployment.yaml b/client/templates/jobs-manager-deployment.yaml index 51aaec8..28c5df8 100644 --- a/client/templates/jobs-manager-deployment.yaml +++ b/client/templates/jobs-manager-deployment.yaml @@ -133,8 +133,10 @@ spec: value: {{ $value | quote }} {{- end }} {{- end }} + {{- if include "tracebloc.useImagePullSecrets" . }} imagePullSecrets: - name: {{ include "tracebloc.registrySecretName" . }} + {{- end }} volumes: - name: shared-volume persistentVolumeClaim: diff --git a/client/templates/logs-pvc.yaml b/client/templates/logs-pvc.yaml index 2ebcbc3..3f32c2a 100644 --- a/client/templates/logs-pvc.yaml +++ b/client/templates/logs-pvc.yaml @@ -8,7 +8,7 @@ metadata: labels: {{- include "tracebloc.labels" . | nindent 4 }} spec: - storageClassName: {{ .Values.storageClass.name }} + storageClassName: {{ include "tracebloc.storageClassName" . }} capacity: storage: {{ $storage }} accessModes: @@ -31,7 +31,7 @@ metadata: labels: {{- include "tracebloc.labels" . | nindent 4 }} spec: - storageClassName: {{ .Values.storageClass.name }} + storageClassName: {{ include "tracebloc.storageClassName" . }} accessModes: - {{ .Values.pvcAccessMode | default "ReadWriteMany" }} resources: diff --git a/client/templates/mysql-deployment.yaml b/client/templates/mysql-deployment.yaml index 0ae150a..0ed55f4 100644 --- a/client/templates/mysql-deployment.yaml +++ b/client/templates/mysql-deployment.yaml @@ -75,8 +75,10 @@ spec: mountPath: /etc/mysql/conf.d/ - name: mysql-logs mountPath: /var/log/mysql/ + {{- if include "tracebloc.useImagePullSecrets" . }} imagePullSecrets: - name: {{ include "tracebloc.registrySecretName" . }} + {{- end }} volumes: - name: mysql-persistent-storage persistentVolumeClaim: diff --git a/client/templates/mysql-storage-pvc.yaml b/client/templates/mysql-storage-pvc.yaml index f63ad33..fcb77af 100644 --- a/client/templates/mysql-storage-pvc.yaml +++ b/client/templates/mysql-storage-pvc.yaml @@ -8,7 +8,7 @@ metadata: labels: {{- include "tracebloc.labels" . | nindent 4 }} spec: - storageClassName: {{ .Values.storageClass.name }} + storageClassName: {{ include "tracebloc.storageClassName" . }} capacity: storage: {{ $storage }} accessModes: @@ -31,7 +31,7 @@ metadata: labels: {{- include "tracebloc.labels" . | nindent 4 }} spec: - storageClassName: {{ .Values.storageClass.name }} + storageClassName: {{ include "tracebloc.storageClassName" . }} accessModes: - {{ .Values.pvcAccessMode | default "ReadWriteMany" }} resources: diff --git a/client/templates/resource-monitor-daemonset.yaml b/client/templates/resource-monitor-daemonset.yaml index 003b14a..31b6696 100644 --- a/client/templates/resource-monitor-daemonset.yaml +++ b/client/templates/resource-monitor-daemonset.yaml @@ -76,8 +76,10 @@ spec: cpu: 200m memory: 256Mi terminationGracePeriodSeconds: 15 + {{- if include "tracebloc.useImagePullSecrets" . }} imagePullSecrets: - name: {{ include "tracebloc.registrySecretName" . }} + {{- end }} volumes: - name: host-proc hostPath: diff --git a/client/templates/shared-images-pvc.yaml b/client/templates/shared-images-pvc.yaml index cfa88f8..aa4138a 100644 --- a/client/templates/shared-images-pvc.yaml +++ b/client/templates/shared-images-pvc.yaml @@ -8,7 +8,7 @@ metadata: labels: {{- include "tracebloc.labels" . | nindent 4 }} spec: - storageClassName: {{ .Values.storageClass.name }} + storageClassName: {{ include "tracebloc.storageClassName" . }} capacity: storage: {{ $storage }} accessModes: @@ -31,7 +31,7 @@ metadata: labels: {{- include "tracebloc.labels" . | nindent 4 }} spec: - storageClassName: {{ .Values.storageClass.name }} + storageClassName: {{ include "tracebloc.storageClassName" . }} accessModes: - {{ .Values.pvcAccessMode | default "ReadWriteMany" }} resources: diff --git a/client/templates/storage-class.yaml b/client/templates/storage-class.yaml index e49f656..4124fce 100644 --- a/client/templates/storage-class.yaml +++ b/client/templates/storage-class.yaml @@ -2,7 +2,7 @@ apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: - name: {{ .Values.storageClass.name }} + name: {{ include "tracebloc.storageClassName" . }} labels: {{- include "tracebloc.labels" . | nindent 4 }} provisioner: {{ required "storageClass.provisioner is required when storageClass.create is true" .Values.storageClass.provisioner }} diff --git a/client/tests/secrets_test.yaml b/client/tests/secrets_test.yaml index 463a002..3d027cc 100644 --- a/client/tests/secrets_test.yaml +++ b/client/tests/secrets_test.yaml @@ -25,10 +25,11 @@ tests: path: metadata.labels["app.kubernetes.io/managed-by"] pattern: Helm - - it: should create docker registry secret + - it: should create docker registry secret when create is true template: templates/docker-registry-secret.yaml set: dockerRegistry: + create: true server: https://index.docker.io/v1/ username: testuser password: testpass @@ -44,3 +45,24 @@ tests: value: kubernetes.io/dockerconfigjson - isNotEmpty: path: data[".dockerconfigjson"] + + - it: should not create docker registry secret when create is omitted (default false) + template: templates/docker-registry-secret.yaml + set: + dockerRegistry: + server: https://index.docker.io/v1/ + username: testuser + password: testpass + email: test@test.com + asserts: + - hasDocuments: + count: 0 + + - it: should not create docker registry secret when dockerRegistry is omitted (public images) + template: templates/docker-registry-secret.yaml + values: + - values-public-images.yaml + asserts: + - hasDocuments: + count: 0 + diff --git a/client/tests/values-public-images.yaml b/client/tests/values-public-images.yaml new file mode 100644 index 0000000..9e5359c --- /dev/null +++ b/client/tests/values-public-images.yaml @@ -0,0 +1,3 @@ +# Use this values file to test public images (no registry secret). +# When dockerRegistry is null, no registry secret is created and no imagePullSecrets are added. +dockerRegistry: null diff --git a/client/values.schema.json b/client/values.schema.json index 40996aa..cb0bbd8 100644 --- a/client/values.schema.json +++ b/client/values.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft-07/schema#", "title": "Tracebloc Helm Chart Values", "type": "object", - "required": ["clientId", "clientPassword", "dockerRegistry"], + "required": ["clientId", "clientPassword"], "properties": { "env": { "type": "object", @@ -156,9 +156,14 @@ "description": "Client authentication password" }, "dockerRegistry": { - "type": "object", - "required": ["server", "username", "password", "email"], + "type": ["object", "null"], + "description": "Optional. Omit entirely or set null for public images (no secret or imagePullSecrets). Only create when set and create is true.", "properties": { + "create": { + "type": "boolean", + "default": false, + "description": "When true, create registry secret and add imagePullSecrets to workloads. Omit dockerRegistry or set false for public images." + }, "server": { "type": "string", "format": "uri" diff --git a/client/values.yaml b/client/values.yaml index c3deb7e..5f69796 100644 --- a/client/values.yaml +++ b/client/values.yaml @@ -25,10 +25,12 @@ env: { # RUNTIME_CLASS_NAME: "" } # -- StorageClass configuration -# Set create: false to use an existing storage class +# When create: true, the StorageClass name is release-unique (e.g. "-storage-class") +# so each release gets its own class and Helm ownership conflicts are avoided. +# Set create: false to use an existing storage class; then name must match that class. storageClass: create: true - name: client-storage-class + name: client-storage-class # only used when create: false provisioner: "" allowVolumeExpansion: true # Optional fields — omitted from rendered YAML when empty @@ -71,10 +73,12 @@ openshift: clientId: "" clientPassword: "" -# -- Docker registry credentials -# Secret name is auto-generated as {{ .Release.Name }}-regcred -dockerRegistry: - server: https://index.docker.io/v1/ - username: "" - password: "" - email: "" +# -- Docker registry credentials (optional; only used when dockerRegistry is set and create is true) +# Omit dockerRegistry entirely, or set create: false, for public images (no imagePullSecrets). +# When create is true, secret name is {{ .Release.Name }}-regcred. +# dockerRegistry: +# create: true +# server: https://index.docker.io/v1/ +# username: "" +# password: "" +# email: "" diff --git a/scripts/install-k8s.ps1 b/scripts/install-k8s.ps1 index abd01bb..ad2389a 100644 --- a/scripts/install-k8s.ps1 +++ b/scripts/install-k8s.ps1 @@ -20,6 +20,7 @@ # $env:HTTP_PORT = "80" default: 80 # $env:HTTPS_PORT = "443" default: 443 # $env:HOST_DATA_DIR = "C:\data" default: $env:USERPROFILE\.tracebloc +# $env:CLIENT_ENV = "dev" optional; if not set, CLIENT_ENV is not added to env in values # ============================================================================= #Requires -Version 5.1 @@ -103,6 +104,7 @@ $K8S_VERSION = if ($env:K8S_VERSION) { $env:K8S_VERSION } else { "v1.29.4- $HTTP_PORT = if ($env:HTTP_PORT) { $env:HTTP_PORT } else { "80" } $HTTPS_PORT = if ($env:HTTPS_PORT) { $env:HTTPS_PORT } else { "443" } $HOST_DATA_DIR = if ($env:HOST_DATA_DIR) { $env:HOST_DATA_DIR } else { "$env:USERPROFILE\.tracebloc" } +$CLIENT_ENV = $env:CLIENT_ENV # if not set, do not add CLIENT_ENV to env in values $GPU_VENDOR = "none" # nvidia | amd | amd_unsupported | none $NVIDIA_DRIVER_OK = $false @@ -128,6 +130,7 @@ Environment variable overrides: HTTP_PORT Host HTTP ingress port (default: 80) HTTPS_PORT Host HTTPS ingress port (default: 443) HOST_DATA_DIR Persistent data directory (default: ~\.tracebloc) + CLIENT_ENV Client env (e.g. prod, dev); if not set, not added to values macOS / Linux: curl -fsSL https://raw.githubusercontent.com/tracebloc/client/main/scripts/install.sh | bash @@ -503,7 +506,7 @@ function Install-Kubectl { # ============================================================================= function Install-K3dAndHelm { - Step "Step 5/5 -- k3d and Helm" + Step "Step 5/6 -- k3d and Helm" # -- k3d -- if (-not (Has "k3d")) { @@ -675,6 +678,167 @@ function Confirm-GpuNode { else { Warn "GPU not yet visible. Re-check: kubectl describe node | Select-String 'nvidia'" } } +# ============================================================================= +# STEP 6 -- INSTALL TRACEBLOC CLIENT HELM CHART +# ============================================================================= + +$TRACEBLOC_HELM_REPO_URL = "https://tracebloc.github.io/client" +$TRACEBLOC_HELM_REPO_NAME = "tracebloc" +$TRACEBLOC_CHART_NAME = "client" + + +function Get-TraceblocYamlValue { + param([string]$Path, [string]$Key) + if (-not (Test-Path $Path)) { return "" } + $line = Get-Content $Path -ErrorAction SilentlyContinue | Where-Object { $_ -match "^\s*${Key}\s*:" } | Select-Object -First 1 + if (-not $line) { return "" } + $val = $line -replace "^\s*${Key}\s*:\s*", "" + $val = $val.Trim().Trim('"').Trim("'") + return $val +} + +function Install-ClientHelm { + Step "Step 6/6 -- Installing Tracebloc client Helm chart" + + if (-not (Test-Path $HOST_DATA_DIR)) { + New-Item -ItemType Directory -Path $HOST_DATA_DIR -Force | Out-Null + } + $valuesFile = Join-Path $HOST_DATA_DIR "values.yaml" + + $defaultNamespace = "default" + $defaultClientId = "" + $defaultClientPassword = "" + + if (Test-Path $valuesFile) { + Info "Existing values file found: $valuesFile" + do { + $useExisting = Read-Host "Use values from it as defaults? [Y/n]" + $useExisting = if ($useExisting) { $useExisting.Trim().ToLowerInvariant() } else { "y" } + if ($useExisting -eq "y" -or $useExisting -eq "yes" -or $useExisting -eq "n" -or $useExisting -eq "no" -or $useExisting -eq "") { break } + Warn "Please enter y or n." + } while ($true) + + if ($useExisting -eq "y" -or $useExisting -eq "yes" -or $useExisting -eq "") { + $defaultClientId = Get-TraceblocYamlValue -Path $valuesFile -Key "clientId" + $defaultClientPassword = Get-TraceblocYamlValue -Path $valuesFile -Key "clientPassword" + if ($defaultClientId) { Info "Using existing clientId as default." } + if ($defaultClientPassword) { Info "Using existing clientPassword as default." } + } + } + + Info "Enter values for the Tracebloc client installation:" + $namespacePrompt = if ($defaultNamespace) { "Namespace [$defaultNamespace]" } else { "Namespace [default]" } + $nsInput = Read-Host $namespacePrompt + $TB_NAMESPACE = if ($nsInput) { $nsInput } else { $defaultNamespace } + + Write-Host "" + Step "Client ID & Password" + Write-Host "Need credentials? Create a client at: " -NoNewline; Write-Host "https://ai.tracebloc.io/clients" -ForegroundColor Yellow + Write-Host "Setting up a client is free." -ForegroundColor Yellow + Write-Host "" + if ($defaultClientId) { + $idInput = Read-Host "Client ID [$defaultClientId]" + $TB_CLIENT_ID = if ($idInput) { $idInput } else { $defaultClientId } + } else { + $TB_CLIENT_ID = Read-Host "Client ID" + } + if (-not $TB_CLIENT_ID) { Err "Client ID cannot be empty." } + + if ($defaultClientPassword) { + $pwInput = Read-Host "Client password [press Enter to keep existing]" -AsSecureString + if ($pwInput -and $pwInput.Length -gt 0) { + $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwInput) + try { $TB_CLIENT_PASSWORD = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) } + } else { + $TB_CLIENT_PASSWORD = $defaultClientPassword + } + } else { + $pwInput = Read-Host "Client password" -AsSecureString + $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwInput) + try { $TB_CLIENT_PASSWORD = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) } + } + if (-not $TB_CLIENT_PASSWORD) { Err "Client password cannot be empty." } + + $passwordEscaped = $TB_CLIENT_PASSWORD -replace "'", "''" + + $gpuVal = "" + if ($GPU_VENDOR -eq "nvidia" -and $NVIDIA_DRIVER_OK) { + $gpuVal = "nvidia.com/gpu=1" + Info "NVIDIA GPU detected -- setting GPU_LIMITS and GPU_REQUESTS to nvidia.com/gpu=1" + } else { + Info "No NVIDIA GPU -- GPU_LIMITS and GPU_REQUESTS left empty" + } + + Info "Writing values to $valuesFile" + $envBlock = "env:`n" + if ($CLIENT_ENV) { + $envBlock += " CLIENT_ENV: $CLIENT_ENV`n" + } + $envBlock += @" + RESOURCE_LIMITS: "cpu=2,memory=8Gi" + RESOURCE_REQUESTS: "cpu=2,memory=8Gi" + GPU_LIMITS: "$gpuVal" + GPU_REQUESTS: "$gpuVal" + RUNTIME_CLASS_NAME: "" + +storageClass: + create: true + name: client-storage-class + provisioner: manual + allowVolumeExpansion: true + parameters: {} + +hostPath: + enabled: true + dataPath: "shared-data" + logsPath: "logs" + mysqlPath: "mysql" + initJob: true + +pvc: + mysql: 2Gi + logs: 10Gi + data: 50Gi + +pvcAccessMode: ReadWriteOnce + +clusterScope: true + +clientId: "$TB_CLIENT_ID" +clientPassword: '$passwordEscaped' + +"@ + $valuesContent = @" +# ============================================================ +# Generated by install-k8s.ps1 -- Tracebloc client Helm values +# ============================================================ + +$envBlock +"@ + Set-Content -Path $valuesFile -Value $valuesContent -Encoding UTF8 + Ok "Values file written to $valuesFile" + + $repoList = helm repo list 2>&1 | Out-String + if ($repoList -notmatch [regex]::Escape($TRACEBLOC_HELM_REPO_NAME)) { + Info "Adding Helm repo: $TRACEBLOC_HELM_REPO_URL" + helm repo add $TRACEBLOC_HELM_REPO_NAME $TRACEBLOC_HELM_REPO_URL + if ($LASTEXITCODE -ne 0) { Err "Failed to add Helm repo." } + } + Info "Updating Helm repos..." + helm repo update + if ($LASTEXITCODE -ne 0) { Warn "helm repo update had issues -- continuing." } + + Info "Installing $TB_NAMESPACE from $TRACEBLOC_HELM_REPO_NAME/$TRACEBLOC_CHART_NAME in namespace '$TB_NAMESPACE'..." + helm upgrade --install $TB_NAMESPACE "$TRACEBLOC_HELM_REPO_NAME/$TRACEBLOC_CHART_NAME" ` + --namespace $TB_NAMESPACE ` + --create-namespace ` + --values $valuesFile + if ($LASTEXITCODE -ne 0) { Err "Helm install failed -- see output above." } + + Ok "Tracebloc client Helm chart installed in namespace '$TB_NAMESPACE'." + Info "Values file: $valuesFile" +} + # ============================================================================= # CLUSTER VERIFICATION # ============================================================================= @@ -754,6 +918,7 @@ Install-K3dAndHelm New-K3dCluster Install-GpuDevicePlugin Confirm-GpuNode +Install-ClientHelm Confirm-Cluster Print-Summary diff --git a/scripts/install-k8s.sh b/scripts/install-k8s.sh index 013af32..47810ca 100755 --- a/scripts/install-k8s.sh +++ b/scripts/install-k8s.sh @@ -21,6 +21,7 @@ # HTTP_PORT=80 default: 80 (host → cluster ingress) # HTTPS_PORT=443 default: 443 # HOST_DATA_DIR=~/.tracebloc default: ~/.tracebloc +# CLIENT_ENV=dev optional; if not set, CLIENT_ENV is not added to env in values # TRACEBLOC_SKIP_REBOOT_PROMPT=1 (Linux) skip "Reboot now?" after NVIDIA driver install # ============================================================================= @@ -48,6 +49,7 @@ source "${LIB_DIR}/setup-macos.sh" source "${LIB_DIR}/setup-linux.sh" source "${LIB_DIR}/cluster.sh" source "${LIB_DIR}/gpu-plugins.sh" +source "${LIB_DIR}/install-client-helm.sh" source "${LIB_DIR}/summary.sh" trap install_cleanup EXIT @@ -73,6 +75,7 @@ main() { create_cluster deploy_gpu_device_plugin verify_gpu + install_client_helm verify_cluster print_summary } diff --git a/scripts/install.sh b/scripts/install.sh index 3e58032..f7e8f6a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,6 +5,7 @@ # Usage (macOS / Linux): # curl -fsSL https://raw.githubusercontent.com/tracebloc/client/main/scripts/install.sh | bash # curl -fsSL ... | BRANCH=develop bash +# curl -fsSL ... | BRANCH=develop CLIENT_ENV=dev bash # # Windows (PowerShell as Administrator): # irm https://raw.githubusercontent.com/tracebloc/client/main/scripts/install.ps1 | iex @@ -39,6 +40,7 @@ FILES=( "scripts/lib/setup-linux.sh" "scripts/lib/cluster.sh" "scripts/lib/gpu-plugins.sh" + "scripts/lib/install-client-helm.sh" "scripts/lib/summary.sh" ) diff --git a/scripts/lib/cluster.sh b/scripts/lib/cluster.sh index 64f62e9..cbf4466 100755 --- a/scripts/lib/cluster.sh +++ b/scripts/lib/cluster.sh @@ -12,9 +12,17 @@ _cluster_exists() { fi } +# Ensure host dirs exist so /tracebloc/data, /tracebloc/logs, /tracebloc/mysql exist inside nodes (HOST_DATA_DIR is mounted as /tracebloc) +_ensure_tracebloc_dirs() { + mkdir -p "$HOST_DATA_DIR" "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" + chmod -R 777 "$HOST_DATA_DIR" 2>/dev/null || true +} + create_cluster() { step "Creating k3d Cluster: '$CLUSTER_NAME'" + _ensure_tracebloc_dirs + if _cluster_exists; then _handle_existing_cluster else @@ -49,11 +57,7 @@ _handle_existing_cluster() { } _create_new_cluster() { - if [[ ! -d "$HOST_DATA_DIR" ]]; then - info "Creating host data directory: $HOST_DATA_DIR" - mkdir -p "$HOST_DATA_DIR" - fi - + # HOST_DATA_DIR and data/logs/mysql subdirs already created by _ensure_tracebloc_dirs K3D_ARGS=( cluster create "$CLUSTER_NAME" --servers "$SERVERS" diff --git a/scripts/lib/install-client-helm.sh b/scripts/lib/install-client-helm.sh new file mode 100644 index 0000000..33dc698 --- /dev/null +++ b/scripts/lib/install-client-helm.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +# ============================================================================= +# install-client-helm.sh — Install Tracebloc client Helm chart (step 6) +# Generates values from defaults + user prompts (namespace, clientId, clientPassword) +# and GPU detection. Values file is written to HOST_DATA_DIR/values.yaml. +# ============================================================================= + +TRACEBLOC_HELM_REPO_URL="https://tracebloc.github.io/client" +TRACEBLOC_HELM_REPO_NAME="tracebloc" +TRACEBLOC_CHART_NAME="client" + +# Extract a key's value from a simple YAML file (handles "value", 'value', or value) +_extract_yaml_value() { + local file="$1" key="$2" + local line + line=$(grep -E "^${key}:" "$file" 2>/dev/null | head -1) + [[ -z "$line" ]] && return + line="${line#*:}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line#\"}"; line="${line%\"}" + line="${line#\'}"; line="${line%\'}" + printf '%s' "$line" +} + +install_client_helm() { + step "Installing Tracebloc client Helm chart" + + mkdir -p "$HOST_DATA_DIR" "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" + chmod -R 777 "$HOST_DATA_DIR" 2>/dev/null || true + local values_file="${HOST_DATA_DIR}/values.yaml" + + local use_existing="" + local default_namespace="default" + local default_client_id="" + local default_client_password="" + + if [[ -f "$values_file" ]]; then + info "Existing values file found: $values_file" + while true; do + read -r -p "Use values from it as defaults? [Y/n]: " use_existing + use_existing="$(echo "${use_existing}" | tr '[:upper:]' '[:lower:]')" + [[ "$use_existing" == "y" || "$use_existing" == "yes" || "$use_existing" == "n" || "$use_existing" == "no" || -z "$use_existing" ]] && break + warn "Please enter y or n." + done + if [[ "$use_existing" == "y" || "$use_existing" == "yes" || -z "$use_existing" ]]; then + default_client_id=$(_extract_yaml_value "$values_file" "clientId") + default_client_password=$(_extract_yaml_value "$values_file" "clientPassword") + [[ -n "$default_client_id" ]] && info "Using existing clientId as default." + [[ -n "$default_client_password" ]] && info "Using existing clientPassword as default." + fi + fi + + # ── Prompt for user inputs ───────────────────────────────────────────────── + info "Enter values for the Tracebloc client installation:" + read -r -p "Namespace [${default_namespace}]: " TB_NAMESPACE_INPUT + TB_NAMESPACE="${TB_NAMESPACE_INPUT:-$default_namespace}" + + echo "" + step "Client ID & Password" + echo -e "${BOLD}${YELLOW}Need credentials? Create a client at: https://ai.tracebloc.io/clients${RESET}" + echo -e "${BOLD}${YELLOW}Setting up a client is free.${RESET}" + echo "" + if [[ -n "$default_client_id" ]]; then + read -r -p "Client ID [${default_client_id}]: " TB_CLIENT_ID_INPUT + TB_CLIENT_ID="${TB_CLIENT_ID_INPUT:-$default_client_id}" + else + read -r -p "Client ID: " TB_CLIENT_ID + fi + [[ -z "$TB_CLIENT_ID" ]] && error "Client ID cannot be empty." + + if [[ -n "$default_client_password" ]]; then + read -r -s -p "Client password [press Enter to keep existing]: " TB_CLIENT_PASSWORD_INPUT + echo "" + TB_CLIENT_PASSWORD="${TB_CLIENT_PASSWORD_INPUT:-$default_client_password}" + else + read -r -s -p "Client password: " TB_CLIENT_PASSWORD + echo "" + fi + [[ -z "$TB_CLIENT_PASSWORD" ]] && error "Client password cannot be empty." + + # Escape single quotes for YAML: ' -> '' + TB_CLIENT_PASSWORD_ESCAPED="${TB_CLIENT_PASSWORD//\'/\'\'}" + + # ── GPU limits: nvidia.com/gpu=1 if NVIDIA GPU available, else "" ────────── + local gpu_val + if [[ "${GPU_VENDOR:-}" == "nvidia" ]]; then + gpu_val="nvidia.com/gpu=1" + info "NVIDIA GPU detected — setting GPU_LIMITS and GPU_REQUESTS to nvidia.com/gpu=1" + else + gpu_val="" + info "No NVIDIA GPU — GPU_LIMITS and GPU_REQUESTS left empty" + fi + + # ── Write generated values.yaml to .tracebloc ────────────────────────────── + info "Writing values to $values_file" + cat < "$values_file" +# ============================================================ +# Generated by install-k8s.sh — Tracebloc client Helm values +# ============================================================ + +env: +$([ -n "${CLIENT_ENV:-}" ] && printf ' CLIENT_ENV: "%s"\n' "$CLIENT_ENV") + RESOURCE_LIMITS: "cpu=2,memory=8Gi" + RESOURCE_REQUESTS: "cpu=2,memory=8Gi" + GPU_LIMITS: "$gpu_val" + GPU_REQUESTS: "$gpu_val" + RUNTIME_CLASS_NAME: "" + +storageClass: + create: true + name: client-storage-class + provisioner: manual + allowVolumeExpansion: true + parameters: {} + +hostPath: + enabled: true + +pvc: + mysql: 2Gi + logs: 10Gi + data: 50Gi + +pvcAccessMode: ReadWriteOnce + +clusterScope: true + +clientId: "$TB_CLIENT_ID" +clientPassword: '$TB_CLIENT_PASSWORD_ESCAPED' + +EOF + + success "Values file written to $values_file" + + # ── Add repo and install ─────────────────────────────────────────────────── + if ! helm repo list 2>/dev/null | grep -q "^${TRACEBLOC_HELM_REPO_NAME}[[:space:]]"; then + info "Adding Helm repo: $TRACEBLOC_HELM_REPO_URL" + helm repo add "$TRACEBLOC_HELM_REPO_NAME" "$TRACEBLOC_HELM_REPO_URL" + fi + info "Updating Helm repos..." + helm repo update + + info "Installing $TB_NAMESPACE from $TRACEBLOC_HELM_REPO_NAME/$TRACEBLOC_CHART_NAME in namespace '$TB_NAMESPACE'..." + helm upgrade --install "$TB_NAMESPACE" "$TRACEBLOC_HELM_REPO_NAME/$TRACEBLOC_CHART_NAME" \ + --namespace "$TB_NAMESPACE" \ + --create-namespace \ + --values "$values_file" + + success "Tracebloc client Helm chart installed in namespace '$TB_NAMESPACE'." + info "Values file: $values_file" +} From 6ca72bbd166a90d40c5aa9879bb6109865d7686a Mon Sep 17 00:00:00 2001 From: Asad Iqbal Date: Thu, 5 Mar 2026 14:30:45 +0500 Subject: [PATCH 2/6] Update installation scripts for Tracebloc client Helm chart - Changed the color of the client creation link in install-k8s.ps1 to white for better visibility. - Modified permissions in cluster.sh and install-client-helm.sh to restrict access to specific subdirectories instead of the entire HOST_DATA_DIR. - Added a command to set the permissions of the values.yaml file to 600 for enhanced security. --- scripts/install-k8s.ps1 | 2 +- scripts/lib/cluster.sh | 5 +++-- scripts/lib/install-client-helm.sh | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/install-k8s.ps1 b/scripts/install-k8s.ps1 index ad2389a..0f22739 100644 --- a/scripts/install-k8s.ps1 +++ b/scripts/install-k8s.ps1 @@ -733,7 +733,7 @@ function Install-ClientHelm { Write-Host "" Step "Client ID & Password" - Write-Host "Need credentials? Create a client at: " -NoNewline; Write-Host "https://ai.tracebloc.io/clients" -ForegroundColor Yellow + Write-Host "Need credentials? Create a client at: " -NoNewline; Write-Host "https://ai.tracebloc.io/clients" -ForegroundColor White Write-Host "Setting up a client is free." -ForegroundColor Yellow Write-Host "" if ($defaultClientId) { diff --git a/scripts/lib/cluster.sh b/scripts/lib/cluster.sh index cbf4466..b16b0d2 100755 --- a/scripts/lib/cluster.sh +++ b/scripts/lib/cluster.sh @@ -12,10 +12,11 @@ _cluster_exists() { fi } -# Ensure host dirs exist so /tracebloc/data, /tracebloc/logs, /tracebloc/mysql exist inside nodes (HOST_DATA_DIR is mounted as /tracebloc) +# Ensure host dirs exist so /tracebloc/data, /tracebloc/logs, /tracebloc/mysql exist inside nodes (HOST_DATA_DIR is mounted as /tracebloc). +# Only chmod the container data subdirs; do not make HOST_DATA_DIR or files like values.yaml world-readable. _ensure_tracebloc_dirs() { mkdir -p "$HOST_DATA_DIR" "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" - chmod -R 777 "$HOST_DATA_DIR" 2>/dev/null || true + chmod -R 777 "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" 2>/dev/null || true } create_cluster() { diff --git a/scripts/lib/install-client-helm.sh b/scripts/lib/install-client-helm.sh index 33dc698..b68931e 100644 --- a/scripts/lib/install-client-helm.sh +++ b/scripts/lib/install-client-helm.sh @@ -26,7 +26,7 @@ install_client_helm() { step "Installing Tracebloc client Helm chart" mkdir -p "$HOST_DATA_DIR" "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" - chmod -R 777 "$HOST_DATA_DIR" 2>/dev/null || true + chmod -R 777 "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" 2>/dev/null || true local values_file="${HOST_DATA_DIR}/values.yaml" local use_existing="" @@ -57,7 +57,7 @@ install_client_helm() { echo "" step "Client ID & Password" - echo -e "${BOLD}${YELLOW}Need credentials? Create a client at: https://ai.tracebloc.io/clients${RESET}" + echo -e "${BOLD}${YELLOW}Need credentials? Create a client at: ${RESET}${BOLD}\033[1;37mhttps://ai.tracebloc.io/clients${RESET}" echo -e "${BOLD}${YELLOW}Setting up a client is free.${RESET}" echo "" if [[ -n "$default_client_id" ]]; then @@ -130,6 +130,7 @@ clientPassword: '$TB_CLIENT_PASSWORD_ESCAPED' EOF + chmod 600 "$values_file" 2>/dev/null || true success "Values file written to $values_file" # ── Add repo and install ─────────────────────────────────────────────────── From 5c8e0098a86539173731ede8f18f891f655288d8 Mon Sep 17 00:00:00 2001 From: Asad Iqbal Date: Thu, 5 Mar 2026 14:48:05 +0500 Subject: [PATCH 3/6] Enhance YAML value extraction in install-client-helm.sh - Updated _extract_yaml_value function to handle single-quoted YAML values correctly by unescaping '' back to ' for proper credential management. - Refactored directory creation and permission setting in install_client_helm function to use _ensure_tracebloc_dirs for improved clarity and maintainability. --- scripts/lib/install-client-helm.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/lib/install-client-helm.sh b/scripts/lib/install-client-helm.sh index b68931e..97c3450 100644 --- a/scripts/lib/install-client-helm.sh +++ b/scripts/lib/install-client-helm.sh @@ -10,6 +10,7 @@ TRACEBLOC_HELM_REPO_NAME="tracebloc" TRACEBLOC_CHART_NAME="client" # Extract a key's value from a simple YAML file (handles "value", 'value', or value) +# For single-quoted YAML values, unescapes '' back to ' so credentials round-trip correctly. _extract_yaml_value() { local file="$1" key="$2" local line @@ -17,16 +18,21 @@ _extract_yaml_value() { [[ -z "$line" ]] && return line="${line#*:}" line="${line#"${line%%[![:space:]]*}"}" - line="${line#\"}"; line="${line%\"}" - line="${line#\'}"; line="${line%\'}" + if [[ "$line" == \'*\' ]]; then + line="${line#\'}" + line="${line%\'}" + line="${line//\'\'/\'}" + else + line="${line#\"}" + line="${line%\"}" + fi printf '%s' "$line" } install_client_helm() { step "Installing Tracebloc client Helm chart" - mkdir -p "$HOST_DATA_DIR" "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" - chmod -R 777 "$HOST_DATA_DIR/data" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/mysql" 2>/dev/null || true + _ensure_tracebloc_dirs local values_file="${HOST_DATA_DIR}/values.yaml" local use_existing="" From a6201575b2356aee1d4f923f8750e63cf5445b22 Mon Sep 17 00:00:00 2001 From: Asad Iqbal Date: Thu, 5 Mar 2026 15:20:58 +0500 Subject: [PATCH 4/6] Enhance YAML value extraction in install-k8s.ps1 - Improved handling of quoted YAML scalars by stripping surrounding quotes and unescaping single-quoted values for accurate retrieval. - Removed deprecated hostPath configuration entries for clarity and to streamline the script. --- scripts/install-k8s.ps1 | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/install-k8s.ps1 b/scripts/install-k8s.ps1 index 0f22739..711ddbb 100644 --- a/scripts/install-k8s.ps1 +++ b/scripts/install-k8s.ps1 @@ -693,7 +693,19 @@ function Get-TraceblocYamlValue { $line = Get-Content $Path -ErrorAction SilentlyContinue | Where-Object { $_ -match "^\s*${Key}\s*:" } | Select-Object -First 1 if (-not $line) { return "" } $val = $line -replace "^\s*${Key}\s*:\s*", "" - $val = $val.Trim().Trim('"').Trim("'") + $val = $val.Trim() + + # Handle quoted YAML scalars and unescape single-quoted style + if ($val.StartsWith("'") -and $val.EndsWith("'") -and $val.Length -ge 2) { + # Strip surrounding single quotes + $val = $val.Substring(1, $val.Length - 2) + # YAML single-quoted style uses '' to represent a literal ' + $val = $val -replace "''", "'" + } elseif ($val.StartsWith('"') -and $val.EndsWith('"') -and $val.Length -ge 2) { + # Strip surrounding double quotes + $val = $val.Substring(1, $val.Length - 2) + } + return $val } @@ -790,10 +802,6 @@ storageClass: hostPath: enabled: true - dataPath: "shared-data" - logsPath: "logs" - mysqlPath: "mysql" - initJob: true pvc: mysql: 2Gi From a79b6b1afaec5a796d59fc57eeba1f4468ae4f6f Mon Sep 17 00:00:00 2001 From: Asad Iqbal Date: Thu, 5 Mar 2026 15:55:35 +0500 Subject: [PATCH 5/6] Update values schema to enforce required fields for user credentials - Added "required" fields for server, username, password, and email in the values schema to ensure necessary user information is provided during configuration. --- client/values.schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/values.schema.json b/client/values.schema.json index cb0bbd8..6795ba4 100644 --- a/client/values.schema.json +++ b/client/values.schema.json @@ -177,7 +177,8 @@ "email": { "type": "string" } - } + }, + "required": ["server", "username", "password", "email"] } } } From 9838758133d4afdacbe1ced33ab13c83aea3030f Mon Sep 17 00:00:00 2001 From: Asad Iqbal Date: Thu, 5 Mar 2026 16:07:57 +0500 Subject: [PATCH 6/6] Refine values schema to conditionally require user credentials - Updated the values schema to enforce that server, username, password, and email are required only when the 'create' property is set to true, enhancing validation logic for user credential input. --- client/values.schema.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/values.schema.json b/client/values.schema.json index 6795ba4..d244ac8 100644 --- a/client/values.schema.json +++ b/client/values.schema.json @@ -178,7 +178,19 @@ "type": "string" } }, - "required": ["server", "username", "password", "email"] + "allOf": [ + { + "if": { + "properties": { + "create": { "const": true } + }, + "required": ["create"] + }, + "then": { + "required": ["server", "username", "password", "email"] + } + } + ] } } }