diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b9a4723..c68bd60 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -51,7 +51,11 @@ "source=crucible-dev-ssh,target=/home/vscode/.ssh,type=volume", // MSBuild files for local library development "source=${localWorkspaceFolder}/.devcontainer/msbuild/Directory.Build.props,target=/mnt/data/Directory.Build.props,type=bind", - "source=${localWorkspaceFolder}/.devcontainer/msbuild/Directory.Build.targets,target=/mnt/data/Directory.Build.targets,type=bind" + "source=${localWorkspaceFolder}/.devcontainer/msbuild/Directory.Build.targets,target=/mnt/data/Directory.Build.targets,type=bind", + // Minikube state config persists across container rebuilds + // Minikube feature uses a volume named minikube by default, + // but this conflicts if multiple containers on the system use minikube + "source=crucible-dev-minikube,target=/home/vscode/.minikube,type=volume" ], "postCreateCommand": "bash -l .devcontainer/postcreate.sh", "postStartCommand": "bash -l .devcontainer/poststart.sh", diff --git a/.devcontainer/postcreate.sh b/.devcontainer/postcreate.sh index 829eefa..82b9757 100755 --- a/.devcontainer/postcreate.sh +++ b/.devcontainer/postcreate.sh @@ -90,7 +90,6 @@ declare -A HELM_REPOS=( [prometheus-community]="https://prometheus-community.github.io/helm-charts" [ingress-nginx]="https://kubernetes.github.io/ingress-nginx" [kvaps]="https://kvaps.github.io/charts" - [selfhosters]="https://self-hosters-by-night.github.io/helm-charts" [runix]="https://helm.runix.net" [grafana]="https://grafana.github.io/helm-charts" ) diff --git a/.gitignore b/.gitignore index 5c83c6e..d7c3a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -74,9 +74,9 @@ Crucible.AppHost/resources/misp/certs/*.key # db dumps to restore into postgres db-dumps/ -# helm charts -helm-charts/*/charts/* -helm-charts/crucible/files/*.crt +# minikube deployment +minikube/*/charts/* +minikube/crucible/files/*.crt # playwright mcp logs .playwright-mcp/ diff --git a/Crucible.AppHost/AppHost.cs b/Crucible.AppHost/AppHost.cs index f387705..8be22e8 100644 --- a/Crucible.AppHost/AppHost.cs +++ b/Crucible.AppHost/AppHost.cs @@ -321,7 +321,7 @@ public static void AddCaster(this IDistributedApplicationBuilder builder, IResou "cmu-sei-crucible-caster-api", casterDb.Resource.ConnectionStringExpression); - var minikubeStart = builder.AddExecutable("minikube", "bash", $"{builder.AppHostDirectory}/../scripts/", [ + var minikubeStart = builder.AddExecutable("minikube", "bash", $"{builder.AppHostDirectory}/../minikube/", [ "-c", "./start-minikube.sh" ]); diff --git a/helm-charts/helm-deploy.sh b/helm-charts/helm-deploy.sh deleted file mode 100755 index 47761f5..0000000 --- a/helm-charts/helm-deploy.sh +++ /dev/null @@ -1,644 +0,0 @@ -#!/bin/bash -# Copyright 2025 Carnegie Mellon University. All Rights Reserved. -# Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. - -set -euo pipefail - -# Get script and repo directories -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -REPO_ROOT="$( cd "${SCRIPT_DIR}/.." && pwd )" - -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[1;34m' -BOLD='\033[1m' -RESET='\033[0m' - -# Defaults -UNINSTALL=false -UPDATE_CHARTS=false -PURGE_CLUSTER=false -DELETE_CLUSTER=false -NO_INSTALL=false -SELECT_INFRA=false -SELECT_APPS=false -SELECT_MONITORING=false -USE_LOCAL_CHARTS=false - -CHARTS_DIR=/mnt/data/crucible/helm-charts/charts -SEI_HELM_REPO_NAME=cmusei -CRUCIBLE_DOMAIN=crucible -PGADMIN_EMAIL=pgadmin@crucible.dev - -# Disable Helm 4's server-side apply by default because several upstream charts -# still rely on client-side merge semantics (SSA triggers managedFields errors). -HELM_UPGRADE_FLAGS=${HELM_UPGRADE_FLAGS:---wait --timeout 15m --server-side=false} - -# ----------------------------------------------------------------------------- -# Utility Functions -# ----------------------------------------------------------------------------- - -log_header() { echo -e "\n${BLUE}${BOLD}# $1${RESET}\n"; } -log_warn() { echo -e "${YELLOW}$1${RESET}"; } -log_error() { echo -e "${RED}$1${RESET}"; } - -refresh_current_namespace() { - CURRENT_NAMESPACE=$(kubectl config view --minify -o jsonpath='{..namespace}' 2>/dev/null || true) - CURRENT_NAMESPACE=${CURRENT_NAMESPACE:-default} -} - -show_usage() { - cat < apps -> monitoring -By default, all three charts are selected. -Example: --infra --apps selects infra + apps without monitoring. -EOF -} - -# ----------------------------------------------------------------------------- -# Helm Operations -# ----------------------------------------------------------------------------- - -helm_uninstall_if_exists() { - local release=$1 - if helm status "$release" &>/dev/null; then - log_header "Uninstalling Helm release ${release}" - helm uninstall "$release" --wait --timeout 300s - else - echo "Helm release ${release} not found, skipping uninstall" - fi -} - -helm_deploy() { - local chart=$1 values_file=$2 chart_ref - - if $USE_LOCAL_CHARTS; then - chart_ref="${CHARTS_DIR}/${chart}" - else - chart_ref="${SEI_HELM_REPO_NAME}/${chart}" - fi - - local values_flag="" - [[ -f "$values_file" ]] && values_flag="-f ${values_file}" && echo "Using local values file: ${values_file}" - - if helm status "$chart" &>/dev/null; then - echo "Existing release detected; running helm upgrade" - helm upgrade "$chart" "$chart_ref" ${HELM_UPGRADE_FLAGS} ${values_flag} - else - echo "Release not found; running helm install" - helm install "$chart" "$chart_ref" ${HELM_UPGRADE_FLAGS} ${values_flag} - fi -} - -# ----------------------------------------------------------------------------- -# Cleanup Functions -# ----------------------------------------------------------------------------- - -delete_finished_pods_for_release() { - local release=$1 selector="app.kubernetes.io/instance=${1}" - local pod_list deleted=false - - pod_list=$(kubectl get pods -n "$CURRENT_NAMESPACE" -l "$selector" \ - -o jsonpath='{range .items[*]}{.metadata.name} {.status.phase}{"\n"}{end}' 2>/dev/null || true) - - [[ -z "$pod_list" ]] && { echo "No pods found for release ${release}"; return; } - - while read -r pod_name pod_phase; do - [[ -z "$pod_name" ]] && continue - if [[ "$pod_phase" == "Succeeded" || "$pod_phase" == "Failed" ]]; then - deleted=true - echo "Deleting pod ${pod_name} (phase: ${pod_phase})" - kubectl delete pod "$pod_name" -n "$CURRENT_NAMESPACE" --ignore-not-found --force - fi - done <<< "$pod_list" - - $deleted || echo "No Completed/Failed pods for release ${release}" -} - -delete_pvcs_for_release() { - local release=$1 ns="$CURRENT_NAMESPACE" - local pvcs=( - "data-${release}-nfs-server-provisioner-0" - "${release}-topomojo-api-nfs" - "${release}-gameboard-api-nfs" - "${release}-caster-api-nfs" - ) - - for pvc in "${pvcs[@]}"; do - echo "Deleting PVC ${ns}/${pvc} (if present)" - kubectl delete pvc "$pvc" -n "$ns" --ignore-not-found - - # Find and delete the PV bound to this PVC to avoid orphaned volumes - local pv_name - pv_name=$(kubectl get pv -o json | jq -r --arg ns "$ns" --arg claim "$pvc" \ - '.items[] | select(.spec.claimRef.namespace==$ns and .spec.claimRef.name==$claim) | .metadata.name' | head -n1) - if [[ -n "$pv_name" && "$pv_name" != "null" ]]; then - echo "Deleting PV ${pv_name} bound to ${ns}/${pvc}" - kubectl delete pv "$pv_name" --ignore-not-found - else - echo "No PV found for ${ns}/${pvc}" - fi - done -} - -# ----------------------------------------------------------------------------- -# Certificate Handling -# ----------------------------------------------------------------------------- - -stage_custom_ca_certs() { - local src="/usr/local/share/ca-certificates/custom" - local dest="${HOME}/.minikube/files/etc/ssl/certs/custom" - - if compgen -G "${src}"'/*.crt' > /dev/null; then - log_header "Staging custom CA certificates for minikube" - mkdir -p "$dest" - cp "${src}"/*.crt "$dest"/ - echo "Copied custom CA certificates to ${dest}" - else - log_warn "No custom CA certificates found in ${src}; skipping copy" - fi -} - -# Find a certificate file in dev-certs first, then legacy certs directory -find_cert_file() { - local filename=$1 - local cert_dev="${REPO_ROOT}/.devcontainer/dev-certs/${filename}" - local cert_legacy="${REPO_ROOT}/.devcontainer/certs/${filename}" - - if [[ -f "$cert_dev" ]]; then - echo "$cert_dev" - elif [[ -f "$cert_legacy" ]]; then - echo "$cert_legacy" - fi -} - -setup_tls_and_ca_secrets() { - local cert_source_legacy="${REPO_ROOT}/.devcontainer/certs" - local cert_source_dev="${REPO_ROOT}/.devcontainer/dev-certs" - local tls_secret="crucible-cert" - local ca_configmap="crucible-ca-cert" - - # Find TLS certificate and key - local crucible_dev_crt crucible_dev_key - crucible_dev_crt=$(find_cert_file "crucible-dev.crt") - crucible_dev_key=$(find_cert_file "crucible-dev.key") - - # Create TLS secret if both cert and key exist - if [[ -n "$crucible_dev_crt" && -n "$crucible_dev_key" ]]; then - echo "Creating TLS secret ${tls_secret} from local certificates..." - kubectl create secret tls "${tls_secret}" \ - --cert="${crucible_dev_crt}" --key="${crucible_dev_key}" \ - --dry-run=client -o yaml | kubectl apply -f - - echo "TLS secret created/updated successfully" - else - log_warn "TLS certificate files not found, skipping TLS secret creation" - fi - - # Create CA ConfigMap from all available certificates - local temp_dir - temp_dir=$(mktemp -d) - local has_certs=false - - # Copy crucible-dev.crt if found - [[ -n "$crucible_dev_crt" ]] && cp "$crucible_dev_crt" "${temp_dir}/" && has_certs=true - - # Copy other certificates from both directories - for dir in "$cert_source_legacy" "$cert_source_dev"; do - [[ -d "$dir" ]] || continue - for cert_file in "$dir"/*.crt; do - [[ -f "$cert_file" ]] || continue - local basename - basename=$(basename "$cert_file") - [[ ! -f "${temp_dir}/${basename}" ]] && cp "$cert_file" "${temp_dir}/" && has_certs=true - done - done - - if $has_certs; then - echo "Creating CA certificates ConfigMap ${ca_configmap}..." - kubectl create configmap "${ca_configmap}" --from-file="${temp_dir}" \ - --dry-run=client -o yaml | kubectl apply -f - - echo "CA ConfigMap created/updated successfully" - else - log_warn "No CA certificate files found, skipping CA ConfigMap creation" - fi - - rm -rf "${temp_dir}" -} - -# ----------------------------------------------------------------------------- -# CoreDNS Configuration -# ----------------------------------------------------------------------------- - -ensure_nodehosts_entry() { - local ingress_service="${INGRESS_SERVICE:-crucible-infra-ingress-nginx-controller}" - local ingress_namespace="${INGRESS_NAMESPACE:-$CURRENT_NAMESPACE}" - local dns_namespace="kube-system" - local dns_configmap="coredns" - - log_header "Ensuring CoreDNS NodeHosts contains ${CRUCIBLE_DOMAIN}" - - # Wait for ingress controller IP - local ingress_ip="" waited=0 - while [[ $waited -lt 60 ]]; do - ingress_ip=$(kubectl -n "$ingress_namespace" get svc "$ingress_service" \ - -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true) - [[ -n "$ingress_ip" && "$ingress_ip" != "" ]] && break - ((waited++)) && sleep 1 - done - - if [[ -z "$ingress_ip" || "$ingress_ip" == "" ]]; then - log_warn "Unable to determine ClusterIP for ${ingress_service}; skipping NodeHosts update" - return - fi - - # Patch CoreDNS ConfigMap - local cm_patch - cm_patch=$(mktemp) - cat > "$cm_patch" < "$deploy_patch" </dev/null | jq -r ' - .items[]? | . as $ing | - ([.spec.tls[]?.hosts[]?] | join(",")) as $tls | - .spec.rules[]? | .host as $host | - .http.paths[]? | "\($host)|\(.path // "/")|\(.backend.service.name // $ing.metadata.name)|\($tls)" - ') - - if [[ -z "$output" ]]; then - echo "No ingress endpoints were found." - else - echo "$output" | sort -u - fi -} - -print_secret_credentials() { - local header=$1 secret_name=$2 password_key=$3 username=$4 - - log_header "$header" - - local encoded_password - if ! encoded_password=$(kubectl get secret "$secret_name" -n "$CURRENT_NAMESPACE" \ - -o jsonpath="{.data.${password_key}}" 2>/dev/null) || [[ -z "$encoded_password" ]]; then - echo "Secret ${secret_name} not found or missing ${password_key} key." - return - fi - - local decoded_password - if ! decoded_password=$(echo "$encoded_password" | base64 --decode 2>/dev/null); then - echo "Failed to decode password from secret ${secret_name}." - return - fi - - echo " Username: $username" - echo " Password: $decoded_password" -} - -# ----------------------------------------------------------------------------- -# Minikube Cluster Management -# ----------------------------------------------------------------------------- - -start_minikube_cluster() { - stage_custom_ca_certs - log_header "Checking minikube cluster status" - "${REPO_ROOT}/scripts/start-minikube.sh" -} - -purge_minikube_cluster() { - log_header "Purging minikube cluster" - echo "Attempting sudo umount of ~/.minikube to release mounts (may prompt for sudo)" - sudo umount "${HOME}/.minikube" 2>/dev/null || log_warn "sudo umount ~/.minikube did not succeed; continuing" - minikube delete --all --purge - start_minikube_cluster -} - -delete_minikube_cluster() { - log_header "Deleting minikube cluster (preserving cache)" - minikube delete --all - start_minikube_cluster -} - -# ----------------------------------------------------------------------------- -# Chart Dependency Management -# ----------------------------------------------------------------------------- - -chart_dependencies_ready() { - local chart_path=$1 - [[ -f "${chart_path}/Chart.lock" ]] || return 1 - [[ "${chart_path}/Chart.yaml" -nt "${chart_path}/Chart.lock" ]] && return 1 - [[ -d "${chart_path}/charts" ]] || return 1 - helm dependency list "$chart_path" 2>/dev/null | grep -qE '[[:space:]]missing$' && return 1 - return 0 -} - -build_chart_dependencies() { - local chart_path="${CHARTS_DIR}/${1}" - echo "Building dependencies for chart ${chart_path}" - if ! helm dependency build "$chart_path"; then - echo "Dependency build failed; refreshing lockfile and fetching missing charts" - helm dependency update "$chart_path" - helm dependency build "$chart_path" - fi -} - -ensure_chart_dependencies() { - local chart=$1 chart_path="${CHARTS_DIR}/${1}" - - if $UPDATE_CHARTS; then - log_header "Forcing dependency rebuild for ${chart_path}" - build_chart_dependencies "$chart" - elif chart_dependencies_ready "$chart_path"; then - log_header "Dependencies for ${chart_path} already present, skipping rebuild" - else - log_header "Dependencies missing for ${chart_path}, rebuilding" - build_chart_dependencies "$chart" - fi -} - -# ----------------------------------------------------------------------------- -# Chart-Specific Deployment Logic -# ----------------------------------------------------------------------------- - -deploy_infra_chart() { - log_header "Deploying crucible-infra chart" - - setup_tls_and_ca_secrets - helm_deploy "crucible-infra" "${SCRIPT_DIR}/crucible-infra.values.yaml" - - # Configure CoreDNS immediately after infra deployment - log_header "Updating CoreDNS with current ingress controller IP" - ensure_nodehosts_entry - - # Create PostgreSQL credentials secret for the crucible chart - log_header "Creating PostgreSQL credentials secret for crucible chart" - local postgres_secret="crucible-infra-postgresql" - local infra_postgres_password - infra_postgres_password=$(kubectl get secret "${postgres_secret}" -n "$CURRENT_NAMESPACE" \ - -o jsonpath='{.data.postgres-password}' 2>/dev/null | base64 --decode || echo "") - - if [[ -z "$infra_postgres_password" ]]; then - log_warn "Could not retrieve PostgreSQL password from ${postgres_secret} secret" - echo "Ensure the infra chart has deployed successfully before continuing." - else - kubectl create secret generic "$postgres_secret" \ - --from-literal=username=postgres \ - --from-literal=postgres-password="$infra_postgres_password" \ - --dry-run=client -o yaml | kubectl apply -f - - echo "PostgreSQL credentials secret created/updated successfully" - - # Ensure PostgreSQL user password is set correctly in the database - echo "Ensuring PostgreSQL user password is set correctly..." - if kubectl exec -n "$CURRENT_NAMESPACE" "statefulset/crucible-infra-postgresql" -- \ - sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U postgres -c "ALTER USER postgres WITH PASSWORD '\''$POSTGRES_PASSWORD'\'';"' &>/dev/null; then - echo "PostgreSQL user password verified/updated successfully" - else - log_warn "Could not update PostgreSQL password - database may not be ready yet" - fi - fi -} - -deploy_apps_chart() { - log_header "Deploying crucible (apps) chart" - helm_deploy "crucible" "${SCRIPT_DIR}/crucible.values.yaml" -} - -deploy_monitoring_chart() { - log_header "Deploying crucible-monitoring chart" - helm_deploy "crucible-monitoring" "${SCRIPT_DIR}/crucible-monitoring.values.yaml" -} - -# ----------------------------------------------------------------------------- -# Uninstall Logic -# ----------------------------------------------------------------------------- - -uninstall_charts() { - # Uninstall in reverse order: monitoring -> apps -> infra - for ((i=${#CHARTS[@]}-1; i>=0; i--)); do - helm_uninstall_if_exists "${CHARTS[i]}" - done - - log_header "Deleting completed/failed pods" - for ((i=${#CHARTS[@]}-1; i>=0; i--)); do - delete_finished_pods_for_release "${CHARTS[i]}" - done - - log_header "Deleting PVCs/PVs" - for ((i=${#CHARTS[@]}-1; i>=0; i--)); do - delete_pvcs_for_release "${CHARTS[i]}" - done - - # Clean up manually created secrets and configmaps - for ((i=${#CHARTS[@]}-1; i>=0; i--)); do - case "${CHARTS[i]}" in - "crucible-infra") - log_header "Deleting secrets and ConfigMaps created by deployment script (infra)" - kubectl delete secret crucible-cert -n "$CURRENT_NAMESPACE" --ignore-not-found - kubectl delete configmap crucible-ca-cert -n "$CURRENT_NAMESPACE" --ignore-not-found - kubectl delete secret crucible-infra-postgresql -n "$CURRENT_NAMESPACE" --ignore-not-found - ;; - "crucible") - log_header "Deleting secrets and ConfigMaps created by deployment script (apps)" - kubectl delete secret crucible-oidc-client-secrets -n "$CURRENT_NAMESPACE" --ignore-not-found - ;; - esac - done - - echo -e "\n${GREEN}${BOLD}# Uninstall complete${RESET}\n" -} - -# ============================================================================= -# Main Script -# ============================================================================= - -refresh_current_namespace - -# Parse arguments -while [[ $# -gt 0 ]]; do - case "$1" in - --uninstall) UNINSTALL=true ;; - --update-charts) UPDATE_CHARTS=true ;; - --purge) PURGE_CLUSTER=true ;; - --delete) DELETE_CLUSTER=true ;; - --no-install) NO_INSTALL=true ;; - --local) USE_LOCAL_CHARTS=true ;; - --infra) SELECT_INFRA=true ;; - --apps) SELECT_APPS=true ;; - --monitoring) SELECT_MONITORING=true ;; - --infra-only) log_warn "Deprecated: use --infra instead of --infra-only."; SELECT_INFRA=true ;; - --apps-only) log_warn "Deprecated: use --apps instead of --apps-only."; SELECT_APPS=true ;; - --monitoring-only) log_warn "Deprecated: use --monitoring instead of --monitoring-only."; SELECT_MONITORING=true ;; - -h|--help) show_usage; exit 0 ;; - *) log_error "Unknown option: $1"; show_usage; exit 1 ;; - esac - shift -done - -# Validate options -if $PURGE_CLUSTER && $DELETE_CLUSTER; then - log_error "Cannot specify both --purge and --delete." - exit 1 -fi - -# Determine which charts to operate on -CHARTS=() -if $SELECT_INFRA || $SELECT_APPS || $SELECT_MONITORING; then - $SELECT_INFRA && CHARTS+=("crucible-infra") - $SELECT_APPS && CHARTS+=("crucible") - $SELECT_MONITORING && CHARTS+=("crucible-monitoring") -else - CHARTS=("crucible-infra" "crucible" "crucible-monitoring") -fi - -# Handle cluster operations -if $PURGE_CLUSTER; then - purge_minikube_cluster - refresh_current_namespace -elif $DELETE_CLUSTER; then - delete_minikube_cluster - refresh_current_namespace -fi - -# Handle uninstall -if $UNINSTALL; then - uninstall_charts - exit 0 -fi - -# Handle no-install -if $NO_INSTALL; then - log_warn "--no-install specified; skipping install phase" - exit 0 -fi - -# Prepare chart sources -if $USE_LOCAL_CHARTS; then - log_header "Using local chart files from ${CHARTS_DIR}" - for chart in "${CHARTS[@]}"; do - ensure_chart_dependencies "$chart" - done -elif $UPDATE_CHARTS; then - log_header "Updating Helm repositories" - helm repo update -fi - -# Ensure minikube cluster is running -log_header "Ensuring minikube cluster is running" -start_minikube_cluster - -# Deploy charts in order: infra -> apps -> monitoring -for chart in "${CHARTS[@]}"; do - case "$chart" in - "crucible-infra") deploy_infra_chart ;; - "crucible") deploy_apps_chart ;; - "crucible-monitoring") deploy_monitoring_chart ;; - esac -done - -# Ensure CoreDNS is configured (fallback for non-infra deployments) -if [[ ! " ${CHARTS[*]} " =~ " crucible-infra " ]]; then - log_header "Ensuring CoreDNS has correct ingress controller IP" - ensure_nodehosts_entry -fi - -# Enable K8s port forwarding to allow connection to web apps from host -log_header "Enabling port-forwarding" -pkill -f "port-forward.*443:443" 2>/dev/null || true -nohup kk port-forward -n default "service/crucible-infra-ingress-nginx-controller" "443:443" > /dev/null 2>&1 & - -# Print URLs and credentials -print_web_app_urls -print_secret_credentials "Keycloak admin credentials" "crucible-keycloak-auth" "admin-password" "keycloak-admin" -print_secret_credentials "Crucible realm admin credentials" "crucible-oidc-client-secrets" "realm-admin-password" "admin" -print_secret_credentials "pgAdmin credentials" "crucible-infra-pgadmin" "password" "${PGADMIN_EMAIL}" - -echo -e "\n${GREEN}${BOLD}Crucible deployment complete${RESET}\n" diff --git a/helm-charts/README.md b/minikube/README.md similarity index 67% rename from helm-charts/README.md rename to minikube/README.md index 1e27b80..9dc19de 100644 --- a/helm-charts/README.md +++ b/minikube/README.md @@ -1,19 +1,19 @@ -# Helm Charts Deployment Scripts +# Minikube Deployment -This directory contains shell scripts for deploying and managing the Crucible stack on Minikube using Helm charts. +This directory contains scripts and values files for deploying and managing the Crucible stack on Minikube using Helm charts. -After running this script, you will have a full Crucible deployment that uses the values files in this directory and the umbrella charts for [crucible](https://github.com/cmu-sei/helm-charts/tree/main/charts/crucible), [crucible-infra](https://github.com/cmu-sei/helm-charts/tree/main/charts/crucible-infra), and [crucible-monitoring](https://github.com/cmu-sei/helm-charts/tree/main/charts/crucible-monitoring). The script will print URLs to access all of the web applications that are running in Minikube and configure port-forwarding to support browsing those sites from your host. **You will need to configure a hosts file entry on your host in order to browse the sites - configure the `crucible` hostname to resolve to `127.0.0.1` to allow the port-forwarding to handle traffic direction to the minikube ingress.** +After running this script, you will have a full Crucible deployment that uses the values files in this directory and the umbrella charts for [crucible-apps](https://github.com/cmu-sei/helm-charts/tree/main/charts/crucible-apps), [crucible-infra](https://github.com/cmu-sei/helm-charts/tree/main/charts/crucible-infra), and [crucible-monitoring](https://github.com/cmu-sei/helm-charts/tree/main/charts/crucible-monitoring). The script will print URLs to access all of the web applications that are running in Minikube and configure port-forwarding to support browsing those sites from your host. **You will need to configure a hosts file entry on your host in order to browse the sites - configure the `crucible` hostname to resolve to `127.0.0.1` to allow the port-forwarding to handle traffic direction to the minikube ingress.** ## Scripts Overview -### helm-deploy.sh +### crucible-deploy.sh Deploys the Crucible stack to Minikube using Helm charts. **Purpose**: Main deployment script that orchestrates the installation of Crucible infrastructure, applications, and monitoring components to a Minikube cluster. **Key Features**: -- Deploys three main chart releases in order: `crucible-infra` → `crucible` → `crucible-monitoring` +- Deploys four chart releases in order: `crucible-operators` → `crucible-infra` → `crucible-apps` → `crucible-monitoring` - Manages Kubernetes secrets and ConfigMaps for TLS certificates and Keycloak realm configuration - Configures CoreDNS for internal hostname resolution - Sets up port forwarding for accessing web applications from the host @@ -23,42 +23,39 @@ Deploys the Crucible stack to Minikube using Helm charts. **Options**: ``` ---uninstall Removes the Helm releases and associated resources --update-charts Forces rebuilding Helm chart dependencies --purge Deletes and recreates the minikube cluster before deploying --delete Deletes and restarts minikube using cached artifacts ---no-install Skips the install phase (useful for dependency building only) +--local Use local chart files instead of the SEI Helm repository +--operators Deploy crucible-operators chart (can be combined with other flags) --infra Deploy crucible-infra chart (can be combined with other flags) ---apps Deploy crucible (apps) chart (can be combined with other flags) +--apps Deploy crucible-apps chart (can be combined with other flags) --monitoring Deploy crucible-monitoring chart (can be combined with other flags) ``` -**Default Behavior**: Without any chart selection flags, all three charts are deployed. +**Default Behavior**: Without any chart selection flags, all four charts are deployed. **Examples**: ```bash -# Deploy everything (infra + apps + monitoring) -./helm-deploy.sh +# Deploy everything (operators + infra + apps + monitoring) +./crucible-deploy.sh # Deploy only infrastructure -./helm-deploy.sh --infra +./crucible-deploy.sh --infra # Deploy infrastructure and applications (skip monitoring) -./helm-deploy.sh --infra --apps +./crucible-deploy.sh --infra --apps # Rebuild chart dependencies and deploy -./helm-deploy.sh --update-charts +./crucible-deploy.sh --update-charts # Clean slate deployment (fresh minikube cluster) -./helm-deploy.sh --purge +./crucible-deploy.sh --purge -# Uninstall Helm deployments -./helm-deploy.sh --uninstall ``` **Environment Variables**: -- `HELM_UPGRADE_FLAGS` - Additional flags for helm upgrade/install (default: `--wait --timeout 15m --server-side=false`) -- `MINIKUBE_FLAGS` - Additional flags for minikube start (default: `--mount-string=/mnt/data/terraform/root:/terraform/root --embed-certs`) +- `HELM_DEPLOY_FLAGS` - Additional flags for helm upgrade/install (default: `--server-side=false`) **What It Does**: 1. **Pre-deployment**: @@ -66,23 +63,27 @@ Deploys the Crucible stack to Minikube using Helm charts. - Ensures Minikube cluster is running (starts it if not) - Stages custom CA certificates for Minikube nodes -2. **Infrastructure Deployment** (`crucible-infra`): +2. **Operator Deployment** (`crucible-operators`): + - Installs the Keycloak Operator and CloudNative-PG Operator + - Waits for operators to become available before proceeding + +3. **Infrastructure Deployment** (`crucible-infra`): - Creates TLS secrets from certificate files in `files/` directory - Creates CA certificate ConfigMaps for trust chain - - Deploys PostgreSQL, pgAdmin, ingress-nginx, and NFS storage provisioner + - Deploys CNPG PostgreSQL cluster, pgAdmin, ingress-nginx, and NFS storage provisioner + - Waits for CNPG PostgreSQL cluster to reach Ready state - Configures CoreDNS with ingress controller IP for hostname resolution - - Creates PostgreSQL credentials secret for use by application charts -3. **Application Deployment** (`crucible`): - - Creates Keycloak realm ConfigMap from `files/crucible-realm.json` - - Deploys Keycloak, Player, Caster, Alloy, TopoMojo, Steamfitter, CITE, Gallery, Blueprint, and Gameboard services +4. **Application Deployment** (`crucible-apps`): + - Deploys Keycloak (via Keycloak Operator CR), Player, Caster, Alloy, TopoMojo, Steamfitter, CITE, Gallery, Blueprint, Gameboard, and Moodle + - Auto-generates a Keycloak realm with OIDC client secrets - Configures each service with database connections and OAuth integration -4. **Monitoring Deployment** (`crucible-monitoring`): - - Deploys Prometheus and Grafana for observability +5. **Monitoring Deployment** (`crucible-monitoring`): + - Deploys Prometheus, Grafana, Loki, Tempo, and Grafana Alloy for observability - Configures Grafana with Keycloak authentication -5. **Post-deployment**: +6. **Post-deployment**: - Sets up port forwarding (host port 443 → ingress controller) - Prints web application URLs for easy navigation - Displays Keycloak admin and pgAdmin credentials @@ -135,7 +136,7 @@ Builds a Docker image locally and loads it into the Minikube cache. - Build changes locally - Load them into Minikube - Update your Helm values file with `pullPolicy: Never` -- Redeploy the service with `./helm-deploy.sh --apps` +- Redeploy the service with `./crucible-deploy.sh --apps` ### clean-postgres.sh @@ -177,15 +178,15 @@ Ensures all Minikube PostgreSQL data is deleted for a fresh deployment. ./clean-postgres.sh # Redeploy with fresh database -./helm-deploy.sh --infra +./crucible-deploy.sh --infra # Or uninstall first for complete clean slate ./clean-postgres.sh -./helm-deploy.sh --uninstall && ./helm-deploy.sh +./crucible-deploy.sh --uninstall && ./crucible-deploy.sh ``` ## Additional Resources -- **crucible-infra.values.yaml** - Infrastructure chart values (PostgreSQL, ingress, storage) -- **crucible.values.yaml** - Application chart values (Keycloak, microservices) -- **crucible-monitoring.values.yaml** - Monitoring chart values (Prometheus, Grafana) +- **crucible-infra.values.yaml** - Infrastructure chart values (CNPG PostgreSQL, ingress-nginx, NFS storage) +- **crucible-apps.values.yaml** - Application chart values (Keycloak, microservices) +- **crucible-monitoring.values.yaml** - Monitoring chart values (Prometheus, Grafana, Loki, Tempo, Alloy) diff --git a/helm-charts/build-and-load-image.sh b/minikube/build-and-load-image.sh similarity index 100% rename from helm-charts/build-and-load-image.sh rename to minikube/build-and-load-image.sh diff --git a/helm-charts/clean-postgres.sh b/minikube/clean-postgres.sh similarity index 100% rename from helm-charts/clean-postgres.sh rename to minikube/clean-postgres.sh diff --git a/helm-charts/crucible.values.yaml b/minikube/crucible-apps.values.yaml similarity index 90% rename from helm-charts/crucible.values.yaml rename to minikube/crucible-apps.values.yaml index 7acb8c4..14ff138 100644 --- a/helm-charts/crucible.values.yaml +++ b/minikube/crucible-apps.values.yaml @@ -2,86 +2,52 @@ # This chart deploys Keycloak and all Crucible applications # IMPORTANT: This chart assumes crucible-infra is already deployed -# The infra chart provides: PostgreSQL, Ingress Controller, NFS Storage, pgAdmin +# The infra chart provides: PostgreSQL (CNPG), Ingress Controller (nginx), NFS Storage, pgAdmin global: domain: crucible namespace: default - security: - allowInsecureImages: true - - keycloak: - # Base path for Keycloak (relative to global.domain) - basePath: "/keycloak" - # Keycloak realm name - realm: "crucible" + ingress: + className: nginx -# Deploy Keycloak IdP -# https://artifacthub.io/packages/helm/bitnami/keycloak +# Deploy Keycloak IdP (via Keycloak Operator) +# https://www.keycloak.org/operator/installation keycloak: enabled: true - image: - # This bitnamilegacy image requires allowing insecure images - repository: bitnamilegacy/keycloak - auth: - # default admin creds come from an existing secret - adminUser: "keycloak-admin" - existingSecret: "{{ .Release.Name }}-keycloak-auth" - proxyHeaders: xforwarded - httpRelativePath: "/keycloak/" + instances: 1 ingress: enabled: true ingressClassName: nginx - hostname: "{{ .Values.global.domain }}" - path: "/keycloak/" + tlsSecretName: crucible-cert annotations: nginx.ingress.kubernetes.io/server-snippet: | location ~ ^/keycloak/?$ { return 302 $scheme://$host/keycloak/admin/crucible/console/; } - tls: true - extraTls: - - hosts: - - "{{ .Values.global.domain }}" - secretName: crucible-cert - # Automatically generate OIDC client secrets and create a realm ConfigMap createRealm: true realmAdminPassword: "admin" - # Realm import configuration - enabled for local development - # Import realm on startup (if the realm already exists, keycloak will not overwrite it) - extraEnvVars: - - name: KEYCLOAK_EXTRA_ARGS - value: "--import-realm" - extraVolumes: - - name: realm-import - secret: - secretName: "{{ .Release.Name }}-realm" - extraVolumeMounts: - - name: realm-import - mountPath: /opt/bitnami/keycloak/data/import - readOnly: true - # Use the external postgres instead of the one provided by the bitnami chart - postgresql: - enabled: false + importRealmSecret: "" externalDatabase: - host: "crucible-infra-postgresql" - user: "postgres" + host: "crucible-infra-postgresql-rw" + port: 5432 database: keycloak - existingSecret: "crucible-infra-postgresql" - existingSecretPasswordKey: "postgres-password" + user: "keycloak" + existingSecret: "crucible-infra-db-keycloak" + existingSecretUserKey: "username" + existingSecretPasswordKey: "password" alloy: enabled: true alloy-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "alloy" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-alloy" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" ingress: - className: "nginx" + className: nginx hosts: - host: "{{ .Values.global.domain }}" paths: @@ -123,7 +89,7 @@ alloy: alloy-ui: ingress: - className: "nginx" + className: nginx hosts: - host: "{{ .Values.global.domain }}" paths: @@ -159,12 +125,12 @@ blueprint: enabled: true blueprint-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "blueprint" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-blueprint" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) # Only needed if you use self-signed certificates or corporate proxies (e.g., Zscaler) # You must create the ConfigMap separately before enabling this @@ -253,12 +219,12 @@ caster: enabled: true caster-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "caster" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-caster" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" # Git credentials - placeholder for local development @@ -334,12 +300,12 @@ cite: enabled: true cite-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "cite" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-cite" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: @@ -405,12 +371,12 @@ gallery: enabled: true gallery-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "gallery" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-gallery" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: @@ -476,21 +442,17 @@ gameboard: enabled: true gameboard-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "gameboard" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-gameboard" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: enabled: true className: nginx - annotations: - nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - nginx.ingress.kubernetes.io/proxy-body-size: 30m hosts: - host: "{{ .Values.global.domain }}" paths: @@ -504,12 +466,10 @@ gameboard: existing: "{{ .Release.Name }}-gameboard-api-nfs" existingSecret: "{{ .Release.Name }}-gameboard-api-custom" # This gets added to a secret - # In prod, the data should come from an external secret store gameEngineClientSecret: "" # auto-generated when keycloak.createRealm is true env: PathBase: /gameboard # Game Engine (TopoMojo) configuration - # Use K8s service addresses here to keep traffic between GB and TM in-cluster Core__GameEngineUrl: http://{{ .Release.Name }}-topomojo-api Core__ChallengeDocUrl: http://{{ .Release.Name }}-topomojo-api Core__GameEngineDeployBatchSize: "6" @@ -570,12 +530,12 @@ player: enabled: true player-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "player" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-player" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: @@ -612,7 +572,6 @@ player: SSL_CERT_FILE: /usr/local/share/ca-certificates/crucible-dev.crt SSL_CERT_DIR: /usr/local/share/ca-certificates # Optional: Configure OpenTelemetry (enabled for local development with monitoring stack) - # Only needed if you have deployed the crucible-monitoring chart OTEL_EXPORTER_OTLP_ENDPOINT: http://crucible-monitoring-grafana-alloy:4317 OTEL_EXPORTER_OTLP_PROTOCOL: grpc player-ui: @@ -644,12 +603,12 @@ player: url: https://{{ .Values.global.domain }}/player/hubs vm-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "player_vm" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-player-vm" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: @@ -733,12 +692,12 @@ steamfitter: enabled: true steamfitter-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "steamfitter" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-steamfitter" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: @@ -821,19 +780,17 @@ topomojo: enabled: true topomojo-api: database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" port: 5432 name: "topomojo" - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-topomojo" usernameKey: "username" - passwordKey: "postgres-password" + passwordKey: "password" # Optional: Use a configmap to define a custom CA certificate (enabled for local development) certificateMap: "crucible-ca-cert" ingress: enabled: true className: nginx - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: 6g hosts: - host: "{{ .Values.global.domain }}" paths: @@ -869,8 +826,6 @@ topomojo: ingress: enabled: true className: nginx - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: 6g hosts: - host: "{{ .Values.global.domain }}" paths: @@ -918,12 +873,12 @@ moodle: site: url: "https://{{ .Values.global.domain }}" database: - host: "crucible-infra-postgresql" + host: "crucible-infra-postgresql-rw" name: "moodle" user: "postgres" # Fallback if existingSecretUserKey is not set - existingSecret: "crucible-infra-postgresql" + existingSecret: "crucible-infra-db-moodle" existingSecretUserKey: "username" - existingSecretPasswordKey: "postgres-password" + existingSecretPasswordKey: "password" ## OIDC OAuth2 Configuration oidc: enabled: true @@ -963,10 +918,11 @@ moodle: mountPath: /opt/sei/certs readOnly: true ingress: + className: nginx hostname: "{{ .Values.global.domain }}" path: / pathType: Prefix tls: - hosts: - "{{ .Values.global.domain }}" - secretName: crucible-cert + secretName: crucible-cert \ No newline at end of file diff --git a/minikube/crucible-deploy.sh b/minikube/crucible-deploy.sh new file mode 100755 index 0000000..77297a7 --- /dev/null +++ b/minikube/crucible-deploy.sh @@ -0,0 +1,398 @@ +#!/bin/bash +# Copyright 2025 Carnegie Mellon University. All Rights Reserved. +# Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. + +set -euo pipefail + +# Get script and repo directories +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$( cd "${SCRIPT_DIR}/.." && pwd )" + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +BOLD='\033[1m' +RESET='\033[0m' + +# Defaults +UPDATE_CHARTS=false +PURGE_CLUSTER=false +DELETE_CLUSTER=false +SELECT_OPERATORS=false +SELECT_INFRA=false +SELECT_APPS=false +SELECT_MONITORING=false +USE_LOCAL_CHARTS=false + +CHARTS_DIR=/mnt/data/crucible/helm-charts/charts +SEI_HELM_REPO_NAME=cmusei +CRUCIBLE_DOMAIN=crucible + +# Disable Helm 4's server-side apply by default because several upstream charts +# still rely on client-side merge semantics (SSA triggers managedFields errors). +HELM_DEPLOY_FLAGS=${HELM_DEPLOY_FLAGS:---server-side=false} + +# ----------------------------------------------------------------------------- +# Utility Functions +# ----------------------------------------------------------------------------- + +log_header() { echo -e "\n${BLUE}${BOLD}# $1${RESET}\n"; } +log_warn() { echo -e "${YELLOW}$1${RESET}"; } +log_error() { echo -e "${RED}$1${RESET}"; } + +refresh_current_namespace() { + CURRENT_NAMESPACE=$(kubectl config view --minify -o jsonpath='{..namespace}' 2>/dev/null || true) + CURRENT_NAMESPACE=${CURRENT_NAMESPACE:-default} +} + +show_usage() { + cat < infra -> apps -> monitoring +By default, all four charts are selected. +Example: --infra --apps selects infra + apps without operators or monitoring. +EOF +} + +# ----------------------------------------------------------------------------- +# Helm Operations +# ----------------------------------------------------------------------------- + +helm_deploy() { + local chart=$1 values_file=$2 chart_ref timeout=${3:-15m} + + if $USE_LOCAL_CHARTS; then + chart_ref="${CHARTS_DIR}/${chart}" + else + chart_ref="${SEI_HELM_REPO_NAME}/${chart}" + fi + + local values_flag="" + [[ -f "$values_file" ]] && values_flag="-f ${values_file}" && echo "Using local values file: ${values_file}" + + helm upgrade --install "$chart" "$chart_ref" \ + --wait --timeout "$timeout" ${HELM_DEPLOY_FLAGS} ${values_flag} +} + +# ----------------------------------------------------------------------------- +# CoreDNS Configuration +# ----------------------------------------------------------------------------- + +ensure_nodehosts_entry() { + local ingress_service="${INGRESS_SERVICE:-crucible-infra-ingress-nginx-controller}" + local ingress_namespace="${INGRESS_NAMESPACE:-$CURRENT_NAMESPACE}" + local dns_namespace="kube-system" + local dns_configmap="coredns" + + log_header "Ensuring CoreDNS NodeHosts contains ${CRUCIBLE_DOMAIN}" + + # Wait for ingress controller IP + local ingress_ip="" waited=0 + while [[ $waited -lt 60 ]]; do + ingress_ip=$(kubectl -n "$ingress_namespace" get svc "$ingress_service" \ + -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true) + [[ -n "$ingress_ip" && "$ingress_ip" != "" ]] && break + ((waited++)) && sleep 1 + done + + if [[ -z "$ingress_ip" || "$ingress_ip" == "" ]]; then + log_warn "Unable to determine ClusterIP for ${ingress_service}; skipping NodeHosts update" + return + fi + + # Patch CoreDNS ConfigMap + local cm_patch + cm_patch=$(mktemp) + cat > "$cm_patch" < "$deploy_patch" </dev/null | jq -r ' + .items[]? | . as $ing | + ([.spec.tls[]?.hosts[]?] | join(",")) as $tls | + .spec.rules[]? | .host as $host | + .http.paths[]? | "\($host)|\(.path // "/")|\(.backend.service.name // $ing.metadata.name)|\($tls)" + ') + + if [[ -z "$output" ]]; then + echo "No ingress endpoints were found." + else + echo "$output" | sort -u + fi +} + +print_secret_credentials() { + local header=$1 secret_name=$2 password_key=$3 username=$4 + + log_header "$header" + + local encoded_password + if ! encoded_password=$(kubectl get secret "$secret_name" -n "$CURRENT_NAMESPACE" \ + -o jsonpath="{.data.${password_key}}" 2>/dev/null) || [[ -z "$encoded_password" ]]; then + echo "Secret ${secret_name} not found or missing ${password_key} key." + return + fi + + local decoded_password + if ! decoded_password=$(echo "$encoded_password" | base64 --decode 2>/dev/null); then + echo "Failed to decode password from secret ${secret_name}." + return + fi + + local display_username="$username" + if [[ "$username" == "AUTO" ]]; then + local encoded_username + if encoded_username=$(kubectl get secret "$secret_name" -n "$CURRENT_NAMESPACE" \ + -o jsonpath='{.data.username}' 2>/dev/null) && [[ -n "$encoded_username" ]]; then + display_username=$(echo "$encoded_username" | base64 --decode 2>/dev/null || echo "$username") + fi + fi + + echo " Username: $display_username" + echo " Password: $decoded_password" +} + +# ----------------------------------------------------------------------------- +# Chart Dependency Management +# ----------------------------------------------------------------------------- + +chart_dependencies_ready() { + local chart_path=$1 + [[ -f "${chart_path}/Chart.lock" ]] || return 1 + [[ "${chart_path}/Chart.yaml" -nt "${chart_path}/Chart.lock" ]] && return 1 + [[ -d "${chart_path}/charts" ]] || return 1 + helm dependency list "$chart_path" 2>/dev/null | grep -qE '[[:space:]]missing$' && return 1 + return 0 +} + +build_chart_dependencies() { + local chart_path="${CHARTS_DIR}/${1}" + echo "Building dependencies for chart ${chart_path}" + if ! helm dependency build "$chart_path" 2>/dev/null; then + echo "Dependency build failed; running helm dependency update (refreshes lock + downloads)" + helm dependency update "$chart_path" + fi +} + +ensure_chart_dependencies() { + local chart=$1 chart_path="${CHARTS_DIR}/${1}" + + if $UPDATE_CHARTS; then + log_header "Forcing dependency rebuild for ${chart_path}" + build_chart_dependencies "$chart" + elif chart_dependencies_ready "$chart_path"; then + log_header "Dependencies for ${chart_path} already present, skipping rebuild" + else + log_header "Dependencies missing for ${chart_path}, rebuilding" + build_chart_dependencies "$chart" + fi +} + +# ============================================================================= +# Main Script +# ============================================================================= + +refresh_current_namespace + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --update-charts) UPDATE_CHARTS=true ;; + --purge) PURGE_CLUSTER=true ;; + --delete) DELETE_CLUSTER=true ;; + --local) USE_LOCAL_CHARTS=true ;; + --operators) SELECT_OPERATORS=true ;; + --infra) SELECT_INFRA=true ;; + --apps) SELECT_APPS=true ;; + --monitoring) SELECT_MONITORING=true ;; + -h|--help) show_usage; exit 0 ;; + *) log_error "Unknown option: $1"; show_usage; exit 1 ;; + esac + shift +done + +# Validate options +if $PURGE_CLUSTER && $DELETE_CLUSTER; then + log_error "Cannot specify both --purge and --delete." + exit 1 +fi + +# Determine which charts to operate on +CHARTS=() +if $SELECT_OPERATORS || $SELECT_INFRA || $SELECT_APPS || $SELECT_MONITORING; then + $SELECT_OPERATORS && CHARTS+=("crucible-operators") + $SELECT_INFRA && CHARTS+=("crucible-infra") + $SELECT_APPS && CHARTS+=("crucible-apps") + $SELECT_MONITORING && CHARTS+=("crucible-monitoring") +else + CHARTS=("crucible-operators" "crucible-infra" "crucible-apps" "crucible-monitoring") +fi + +# Handle cluster reset operations +if $PURGE_CLUSTER; then + "${SCRIPT_DIR}/reset-minikube.sh" --purge + refresh_current_namespace +elif $DELETE_CLUSTER; then + "${SCRIPT_DIR}/reset-minikube.sh" --delete + refresh_current_namespace +fi + +# Prepare chart sources +if $USE_LOCAL_CHARTS; then + log_header "Using local chart files from ${CHARTS_DIR}" + for chart in "${CHARTS[@]}"; do + ensure_chart_dependencies "$chart" + done +elif $UPDATE_CHARTS; then + log_header "Updating Helm repositories" + helm repo update +fi + +# Ensure minikube cluster is running (also handles certificates) +"${SCRIPT_DIR}/start-minikube.sh" +refresh_current_namespace + +# Deploy charts in order: operators -> infra -> apps -> monitoring +for chart in "${CHARTS[@]}"; do + log_header "Deploying ${chart} chart" + + case "$chart" in + "crucible-operators") + helm_deploy "$chart" "${SCRIPT_DIR}/${chart}.values.yaml" 5m + + echo "Waiting for Keycloak Operator to be ready..." + kubectl wait deployment keycloak-operator \ + --for=condition=Available --timeout=120s 2>/dev/null || \ + log_warn "Keycloak Operator deployment not found or not ready yet" + ;; + + "crucible-infra") + # Infra chart skips --wait: Helm can't track CNPG Cluster readiness. + # We handle readiness explicitly with kubectl wait below. + helm_deploy "$chart" "${SCRIPT_DIR}/${chart}.values.yaml" 5m + + log_header "Waiting for CNPG PostgreSQL cluster to be ready" + if kubectl get cluster crucible-infra-postgresql -n "$CURRENT_NAMESPACE" &>/dev/null; then + kubectl wait cluster/crucible-infra-postgresql -n "$CURRENT_NAMESPACE" \ + --for=condition=Ready --timeout=300s || \ + log_warn "CNPG Cluster not ready within timeout — apps may fail to connect" + else + log_warn "CNPG Cluster resource not found — ensure the CNPG operator is installed" + fi + + log_header "Updating CoreDNS with current ingress controller IP" + ensure_nodehosts_entry + ;; + + *) + helm_deploy "$chart" "${SCRIPT_DIR}/${chart}.values.yaml" + ;; + esac +done + +# Ensure CoreDNS is configured (fallback for non-infra deployments) +if [[ ! " ${CHARTS[*]} " =~ " crucible-infra " ]]; then + log_header "Ensuring CoreDNS has correct ingress controller IP" + ensure_nodehosts_entry +fi + +# Enable K8s port forwarding to allow connection to web apps from host +log_header "Enabling port-forwarding" +pkill -f "port-forward.*443:443" 2>/dev/null || true +nohup kubectl port-forward -n default "service/crucible-infra-ingress-nginx-controller" "443:443" > /dev/null 2>&1 & + +# Print URLs and credentials +print_web_app_urls +print_secret_credentials "Keycloak admin credentials" "crucible-apps-keycloak-initial-admin" "password" "AUTO" +print_secret_credentials "Crucible realm admin credentials" "crucible-oidc-client-secrets" "realm-admin-password" "admin" +print_secret_credentials "pgAdmin credentials" "crucible-infra-pgadmin" "password" "admin@crucible.local" + +echo -e "\n${GREEN}${BOLD}Crucible deployment complete${RESET}\n" diff --git a/helm-charts/crucible-infra.values.yaml b/minikube/crucible-infra.values.yaml similarity index 63% rename from helm-charts/crucible-infra.values.yaml rename to minikube/crucible-infra.values.yaml index b7bb79a..590a1b7 100644 --- a/helm-charts/crucible-infra.values.yaml +++ b/minikube/crucible-infra.values.yaml @@ -4,20 +4,16 @@ global: # Domain name for the Crucible deployment domain: crucible - - # Kubernetes namespace (use Release.Namespace in most cases) namespace: default # TLS Certificate Configuration -# Reference existing Kubernetes secret created by helm-deploy.sh tls: - create: false # Secret created by helm-deploy.sh before chart deployment + create: false # Secret created before chart deployment secretName: "crucible-cert" # Custom CA Certificates -# Reference existing ConfigMap created by helm-deploy.sh caCerts: - create: false # ConfigMap created by helm-deploy.sh before chart deployment + create: false # ConfigMap created before chart deployment configMapName: "crucible-ca-cert" # NFS Server Provisioner @@ -28,6 +24,12 @@ nfs-server-provisioner: persistence: enabled: true +# NFS Persistent Volume Claims +# namePrefix must match the crucible-apps release name so that +# the app chart's {{ .Release.Name }}-*-nfs PVC references resolve +nfs: + namePrefix: "crucible-apps" + # Ingress NGINX Controller # Routes external traffic to services within the cluster # https://kubernetes.github.io/ingress-nginx/ @@ -38,42 +40,33 @@ ingress-nginx: hsts: "false" annotations-risk-level: critical allowSnippetAnnotations: true + # Set the default TLS certificate so all ingresses get HTTPS without explicit TLS config + extraArgs: + default-ssl-certificate: "default/crucible-cert" -# PostgreSQL Database -# Primary database for Crucible applications -# https://github.com/self-hosters-by-night/helm-charts/tree/main/charts/postgres +# PostgreSQL via CloudNative-PG +# Managed by the CNPG operator (install via crucible-operators chart first) postgresql: enabled: true - image: - registry: docker.io - repository: postgres - tag: "18.1" - - # Environment variables for PostgreSQL - env: - vars: - # Default postgres superuser name - POSTGRES_USER: "postgres" - # Default postgres database - POSTGRES_DB: "postgres" - # Use a subdirectory so postgres owns the data dir without chmod on the mount root - PGDATA: "/var/lib/postgresql/18/docker/pgdata" - - # Password is managed via secret (see templates/secret.yaml) - fromSecret: - POSTGRES_PASSWORD: - from: "{{ .Release.Name }}-postgresql" - key: postgres-password - - # Persistent storage for database + instances: 1 persistence: - enabled: true - annotations: {} - storageClass: null - accessModes: - - ReadWriteOnce size: "1Gi" - selector: null + storageClass: null + databases: + - name: keycloak + - name: alloy + - name: blueprint + - name: caster + - name: cite + - name: gallery + - name: gameboard + - name: player + - name: player_vm + - name: player_vm_logging + owner: player_vm + - name: steamfitter + - name: topomojo + - name: moodle # pgAdmin4 # Web-based PostgreSQL administration tool @@ -109,7 +102,7 @@ pgadmin4: crucible-postgres: Name: "Crucible PostgreSQL" Group: "Servers" - Host: "{{ .Release.Name }}-postgresql" + Host: "{{ .Release.Name }}-postgresql-rw" Port: "5432" Username: "postgres" SSLMode: "prefer" @@ -126,4 +119,4 @@ pgadmin4: tls: - hosts: - "{{ .Values.global.domain }}" - secretName: crucible-cert + secretName: crucible-cert \ No newline at end of file diff --git a/helm-charts/crucible-monitoring.values.yaml b/minikube/crucible-monitoring.values.yaml similarity index 97% rename from helm-charts/crucible-monitoring.values.yaml rename to minikube/crucible-monitoring.values.yaml index b6b2bdc..0dcb158 100644 --- a/helm-charts/crucible-monitoring.values.yaml +++ b/minikube/crucible-monitoring.values.yaml @@ -12,14 +12,14 @@ global: namespace: default # TLS secret name (accessible to subcharts via global values) - tlsSecretName: crucible-cert # Created by helm-deploy.sh from local certs + tlsSecretName: crucible-cert # Created from local certs # Custom CA Certificates Configuration # Enable this if Grafana needs to trust custom CA certificates (e.g., for OAuth with Keycloak behind corporate proxy) # When enabled, ALL certificate files in the ConfigMap will be mounted to Grafana caCerts: enabled: true - configMapName: crucible-ca-cert # Created by helm-deploy.sh from local certs + configMapName: crucible-ca-cert # Created from local certs # Prometheus - Metrics Collection and Storage # https://artifacthub.io/packages/helm/prometheus-community/prometheus @@ -48,6 +48,10 @@ grafana: - "{{ .Values.global.domain }}" path: /grafana pathType: Prefix + tls: + - secretName: "{{ .Values.global.tlsSecretName }}" + hosts: + - "{{ .Values.global.domain }}" # Grafana configuration grafana.ini: @@ -56,7 +60,7 @@ grafana: serve_from_sub_path: true auth: - disable_login_form: true # OAuth only (no local users) + disable_login_form: true # OAuth only (no local users) signout_redirect_url: "https://{{ .Values.global.domain }}/keycloak/realms/crucible/protocol/openid-connect/logout?redirect_uri=https://{{ .Values.global.domain }}/grafana/" # OAuth integration @@ -431,4 +435,4 @@ alloy: } } - // End OpenTelemetry configuration + // End OpenTelemetry configuration \ No newline at end of file diff --git a/helm-charts/files/.gitignore b/minikube/files/.gitignore similarity index 66% rename from helm-charts/files/.gitignore rename to minikube/files/.gitignore index 0474494..042c173 100644 --- a/helm-charts/files/.gitignore +++ b/minikube/files/.gitignore @@ -6,10 +6,6 @@ *.p12 *.pfx -# Ignore realm configurations except the default crucible realm -*realm*.json -!crucible-realm.json - # Ignore environment-specific files *.env *.local diff --git a/helm-charts/files/README.md b/minikube/files/README.md similarity index 93% rename from helm-charts/files/README.md rename to minikube/files/README.md index b43c607..c44669a 100644 --- a/helm-charts/files/README.md +++ b/minikube/files/README.md @@ -4,7 +4,7 @@ This directory stores configuration files and certificates for local development ## How It Works -When you run `./helm-charts/helm-deploy.sh`, the script automatically: +When you run `./minikube/crucible-deploy.sh`, the script automatically: 1. Checks for certificate files in both `.devcontainer/certs` and `.devcontainer/dev-certs` directories 2. **Creates Kubernetes secrets** from your certificate files using `kubectl` @@ -53,7 +53,7 @@ ls -la /workspaces/crucible-development/.devcontainer/certs/ ### Verify Secrets Were Created -After running helm-deploy.sh, verify the secrets exist: +After running crucible-deploy.sh, verify the secrets exist: ```bash # Check TLS secret @@ -68,7 +68,7 @@ kubectl describe secret crucible-cert ### Manual Secret Creation -If needed, you can manually create certificate secrets instead of relying on `helm-deploy.sh` to create them for you: +If needed, you can manually create certificate secrets instead of relying on `crucible-deploy.sh` to create them for you: ```bash # TLS secret (adjust paths as needed) diff --git a/minikube/reset-minikube.sh b/minikube/reset-minikube.sh new file mode 100755 index 0000000..eed289e --- /dev/null +++ b/minikube/reset-minikube.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Copyright 2025 Carnegie Mellon University. All Rights Reserved. +# Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Color codes +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +BOLD='\033[1m' +RESET='\033[0m' + +log_header() { echo -e "\n${BLUE}${BOLD}# $1${RESET}\n"; } +log_warn() { echo -e "${YELLOW}$1${RESET}"; } +log_error() { echo -e "${RED}$1${RESET}"; } + +show_usage() { + cat </dev/null; then + docker stop "${container}" 2>/dev/null || true + docker rm "${container}" 2>/dev/null || true + fi + done + echo "Registry mirrors stopped." +} + +if [[ "$MODE" == "purge" ]]; then + log_header "Stopping registry mirrors" + stop_registry_mirrors + + log_header "Purging minikube cluster" + echo "Attempting sudo umount of ~/.minikube to release mounts (may prompt for sudo)" + sudo umount "${HOME}/.minikube" 2>/dev/null || log_warn "sudo umount ~/.minikube did not succeed; continuing" + minikube delete --all --purge + + log_header "Clearing registry mirror cache" + rm -rf /mnt/data/registry + echo "Registry mirror cache cleared." +elif [[ "$MODE" == "delete" ]]; then + log_header "Stopping registry mirrors" + stop_registry_mirrors + + log_header "Deleting minikube cluster (preserving cache)" + minikube delete --all +fi + +# Restart minikube after reset +"${SCRIPT_DIR}/start-minikube.sh" diff --git a/minikube/start-minikube.sh b/minikube/start-minikube.sh new file mode 100755 index 0000000..147cef6 --- /dev/null +++ b/minikube/start-minikube.sh @@ -0,0 +1,232 @@ +#!/bin/bash +# Copyright 2025 Carnegie Mellon University. All Rights Reserved. +# Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. + +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_ROOT="$( cd "${SCRIPT_DIR}/.." && pwd )" + +# Color codes +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +BOLD='\033[1m' +RESET='\033[0m' + +log_header() { echo -e "\n${BLUE}${BOLD}# $1${RESET}\n"; } +log_warn() { echo -e "${YELLOW}$1${RESET}"; } + +# ----------------------------------------------------------------------------- +# Certificate Handling +# ----------------------------------------------------------------------------- + +stage_custom_ca_certs() { + local src="/usr/local/share/ca-certificates/custom" + local dest="${HOME}/.minikube/files/etc/ssl/certs/custom" + + if compgen -G "${src}"'/*.crt' > /dev/null; then + log_header "Staging custom CA certificates for minikube" + mkdir -p "$dest" + cp "${src}"/*.crt "$dest"/ + echo "Copied custom CA certificates to ${dest}" + else + log_warn "No custom CA certificates found in ${src}; skipping copy" + fi +} + +# Find a certificate file in dev-certs first, then legacy certs directory +find_cert_file() { + local filename=$1 + local cert_dev="${REPO_ROOT}/.devcontainer/dev-certs/${filename}" + local cert_legacy="${REPO_ROOT}/.devcontainer/certs/${filename}" + + if [[ -f "$cert_dev" ]]; then + echo "$cert_dev" + elif [[ -f "$cert_legacy" ]]; then + echo "$cert_legacy" + fi +} + +setup_tls_and_ca_secrets() { + local cert_source_legacy="${REPO_ROOT}/.devcontainer/certs" + local cert_source_dev="${REPO_ROOT}/.devcontainer/dev-certs" + local tls_secret="crucible-cert" + local ca_configmap="crucible-ca-cert" + + local ns + ns=$(kubectl config view --minify -o jsonpath='{..namespace}' 2>/dev/null || true) + ns=${ns:-default} + + # Find TLS certificate and key + local crucible_dev_crt crucible_dev_key + crucible_dev_crt=$(find_cert_file "crucible-dev.crt") + crucible_dev_key=$(find_cert_file "crucible-dev.key") + + # Create TLS secret if both cert and key exist + if [[ -n "$crucible_dev_crt" && -n "$crucible_dev_key" ]]; then + echo "Creating TLS secret ${tls_secret} from local certificates..." + kubectl create secret tls "${tls_secret}" \ + --cert="${crucible_dev_crt}" --key="${crucible_dev_key}" \ + --dry-run=client -o yaml | kubectl apply -f - + echo "TLS secret created/updated successfully" + else + log_warn "TLS certificate files not found, skipping TLS secret creation" + fi + + # Create CA ConfigMap from all available certificates + local temp_dir + temp_dir=$(mktemp -d) + local has_certs=false + + # Copy crucible-dev.crt if found + [[ -n "$crucible_dev_crt" ]] && cp "$crucible_dev_crt" "${temp_dir}/" && has_certs=true + + # Copy other certificates from both directories + for dir in "$cert_source_legacy" "$cert_source_dev"; do + [[ -d "$dir" ]] || continue + for cert_file in "$dir"/*.crt; do + [[ -f "$cert_file" ]] || continue + local basename + basename=$(basename "$cert_file") + [[ ! -f "${temp_dir}/${basename}" ]] && cp "$cert_file" "${temp_dir}/" && has_certs=true + done + done + + if $has_certs; then + echo "Creating CA certificates ConfigMap ${ca_configmap}..." + kubectl create configmap "${ca_configmap}" --from-file="${temp_dir}" \ + --dry-run=client -o yaml | kubectl apply -f - + echo "CA ConfigMap created/updated successfully" + else + log_warn "No CA certificate files found, skipping CA ConfigMap creation" + fi + + rm -rf "${temp_dir}" +} + +setup_caster_certs() { + local certs_dir="${REPO_ROOT}/.devcontainer/certs" + local dev_cert_dir="/home/vscode/.aspnet/dev-certs/trust" + + echo "Regenerating caster-certs ConfigMap..." + + local cert_args=() + for dir in "$certs_dir" "$dev_cert_dir"; do + for f in "$dir"/*.crt "$dir"/*.pem; do + [ -f "$f" ] && cert_args+=("--from-file=$f") + done + done + + if [ ${#cert_args[@]} -gt 0 ]; then + kubectl create configmap caster-certs "${cert_args[@]}" \ + --dry-run=client -o yaml | kubectl apply -f - + echo "caster-certs ConfigMap created/updated with ${#cert_args[@]} certificate(s)." + else + echo "Warning: No certificate files found." + fi +} + +# ----------------------------------------------------------------------------- +# Minikube Start +# ----------------------------------------------------------------------------- + +stage_custom_ca_certs + +# ----------------------------------------------------------------------------- +# Registry Mirror Configuration +# Pull-through cache containers for minikube image pulls. +# Image blobs are stored under /mnt/data/registry/ on the persistent +# crucible-dev-data volume and survive `minikube delete` (but not --purge). +# Mirror ports: docker.io=5001, ghcr.io=5002, quay.io=5003, registry.k8s.io=5004 +# ----------------------------------------------------------------------------- + +declare -A REGISTRY_MIRRORS=( + ["docker.io"]="5001:https://registry-1.docker.io" + ["ghcr.io"]="5002:https://ghcr.io" + ["quay.io"]="5003:https://quay.io" + ["registry.k8s.io"]="5004:https://registry.k8s.io" +) + +start_registry_mirrors() { + local mirrors_dir="${HOME}/.minikube/files/etc/containerd/certs.d" + mkdir -p /mnt/data/registry + + # Mount the devcontainer's CA bundle so registry containers trust any + # TLS-inspecting proxies when connecting to upstream registries. + local ca_bundle_mount=() + if [[ -f /etc/ssl/certs/ca-certificates.crt ]]; then + ca_bundle_mount=(-v /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro) + fi + + for registry in "${!REGISTRY_MIRRORS[@]}"; do + IFS=':' read -r port upstream_host upstream_path <<< "${REGISTRY_MIRRORS[$registry]}" + local upstream="https:${upstream_path}" + local container="crucible-registry-${registry//[.\/]/-}" + local data_dir="/mnt/data/registry/${registry}" + mkdir -p "${data_dir}" + + # Remove the container if it exists but isn't running (crashed or stopped) + # so it gets recreated with current config. + if docker inspect "${container}" &>/dev/null; then + local state + state=$(docker inspect --format='{{.State.Status}}' "${container}" 2>/dev/null) + if [[ "${state}" == "running" ]]; then + : # already up, nothing to do + else + echo "Registry mirror ${registry} is ${state}, recreating..." + docker rm -f "${container}" 2>/dev/null || true + fi + fi + + if ! docker inspect "${container}" &>/dev/null; then + echo "Starting registry mirror for ${registry} on port ${port}..." + docker run -d \ + --name "${container}" \ + -p "${port}:5000" \ + -v "${data_dir}:/var/lib/registry" \ + "${ca_bundle_mount[@]}" \ + -e "REGISTRY_PROXY_REMOTEURL=${upstream}" \ + -e "REGISTRY_LOG_LEVEL=warn" \ + -e "REGISTRY_STORAGE_DELETE_ENABLED=true" \ + registry:2 + fi + + # Stage containerd mirror config — injected into the minikube node on start + local hosts_dir="${mirrors_dir}/${registry}" + mkdir -p "${hosts_dir}" + cat > "${hosts_dir}/hosts.toml" </dev/null || echo '{}') + +HOST_STATE=$(echo "$STATUS" | jq -r '.Host // empty') +KUBELET_STATE=$(echo "$STATUS" | jq -r '.Kubelet // empty') +APISERVER_STATE=$(echo "$STATUS" | jq -r '.APIServer // empty') + +if [[ "$HOST_STATE" == "Running" && "$KUBELET_STATE" == "Running" && "$APISERVER_STATE" == "Running" ]]; then + echo "minikube is operational." +else + echo "minikube is not running. Starting now..." + minikube start --mount-string="/mnt/data/terraform:/mnt/data/terraform" --embed-certs \ + --container-runtime=containerd + + echo "Configuring kubelet for parallel image pulls..." + minikube ssh "sudo sed -i 's/serializeImagePulls: true/serializeImagePulls: false/' /var/lib/kubelet/config.yaml || echo 'serializeImagePulls: false' | sudo tee -a /var/lib/kubelet/config.yaml" + minikube ssh "sudo systemctl restart kubelet" +fi + +# Set up certificates and secrets in the cluster +log_header "Setting up TLS secrets and CA certificates" +setup_tls_and_ca_secrets +setup_caster_certs diff --git a/scripts/start-minikube.sh b/scripts/start-minikube.sh deleted file mode 100755 index 424b1f3..0000000 --- a/scripts/start-minikube.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -STATUS=$(minikube status --output json) - -# Extract specific states using jq -HOST_STATE=$(echo $STATUS | jq -r .Host) -KUBELET_STATE=$(echo $STATUS | jq -r .Kubelet) -APISERVER_STATE=$(echo $STATUS | jq -r .APIServer) - -if [[ "$HOST_STATE" == "Running" && "$KUBELET_STATE" == "Running" && "$APISERVER_STATE" == "Running" ]]; then - echo "minikube is operational." -else - echo "minikube is not running. Starting now..." - minikube start --mount-string="/mnt/data/terraform:/mnt/data/terraform" --embed-certs - - echo "Configuring kubelet for parallel image pulls..." - minikube ssh "sudo sed -i 's/serializeImagePulls: true/serializeImagePulls: false/' /var/lib/kubelet/config.yaml || echo 'serializeImagePulls: false' | sudo tee -a /var/lib/kubelet/config.yaml" - minikube ssh "sudo systemctl restart kubelet" -fi - -# Regenerate caster-certs ConfigMap with all trusted certificates -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CERTS_DIR="$SCRIPT_DIR/../.devcontainer/certs" -DEV_CERT_DIR="/home/vscode/.aspnet/dev-certs/trust" - -echo "Regenerating caster-certs ConfigMap..." - -CERT_ARGS=() -for dir in "$CERTS_DIR" "$DEV_CERT_DIR"; do - for f in "$dir"/*.crt "$dir"/*.pem; do - [ -f "$f" ] && CERT_ARGS+=("--from-file=$f") - done -done - -if [ ${#CERT_ARGS[@]} -gt 0 ]; then - kubectl create configmap caster-certs "${CERT_ARGS[@]}" \ - --dry-run=client -o yaml | kubectl apply -f - - echo "caster-certs ConfigMap created/updated with ${#CERT_ARGS[@]} certificate(s)." -else - echo "Warning: No certificate files found." -fi