#!/usr/bin/env bash set -euo pipefail KUBECONFIG_PATH="${KUBECONFIG:-}" APPLIANCE_ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" STORAGE_MANIFEST="$APPLIANCE_ROOT/manifests/local-path-storage.yaml" STORAGE_PATH="/var/mnt/alga-data/local-path-provisioner" SMOKE_NAMESPACE="storage-smoke" DRY_RUN=false usage() { cat <<'EOF' Usage: install-storage.sh --kubeconfig [options] Options: --kubeconfig Kubeconfig path --dry-run Print the commands without mutating the cluster --help Show this help EOF } require_command() { if ! command -v "$1" >/dev/null 2>&1; then echo "Required command not found: $1" >&2 exit 1 fi } run_cmd() { if $DRY_RUN; then printf '+' for arg in "$@"; do printf ' %q' "$arg" done printf '\n' return 0 fi "$@" } kubectl_cmd() { run_cmd kubectl --kubeconfig "$KUBECONFIG_PATH" "$@" } wait_for_rollout() { if $DRY_RUN; then echo "+ kubectl --kubeconfig $KUBECONFIG_PATH -n local-path-storage rollout status deployment/local-path-provisioner --timeout=5m" return 0 fi kubectl --kubeconfig "$KUBECONFIG_PATH" -n local-path-storage rollout status deployment/local-path-provisioner --timeout=5m } prepare_storage_path() { local manifest if $DRY_RUN; then cat </dev/null 2>&1; then return 0 fi local reclaim_policy="" reclaim_policy="$(kubectl --kubeconfig "$KUBECONFIG_PATH" get storageclass local-path -o jsonpath='{.reclaimPolicy}' 2>/dev/null || true)" if [ "$reclaim_policy" = "Retain" ]; then return 0 fi local pv_count pvc_count pv_count="$(kubectl --kubeconfig "$KUBECONFIG_PATH" get pv -o jsonpath='{range .items[?(@.spec.storageClassName=="local-path")]}x{end}' 2>/dev/null | wc -c | tr -d ' ')" pvc_count="$(kubectl --kubeconfig "$KUBECONFIG_PATH" get pvc -A -o jsonpath='{range .items[?(@.spec.storageClassName=="local-path")]}x{end}' 2>/dev/null | wc -c | tr -d ' ')" if [ "${pv_count:-0}" != "0" ] || [ "${pvc_count:-0}" != "0" ]; then echo "Existing local-path StorageClass has reclaimPolicy=$reclaim_policy and is already in use; refusing to replace it automatically." >&2 exit 1 fi echo "Replacing unused local-path StorageClass with appliance Retain policy." kubectl --kubeconfig "$KUBECONFIG_PATH" delete storageclass local-path } run_smoke_test() { local manifest if $DRY_RUN; then cat </dev/null kubectl --kubeconfig "$KUBECONFIG_PATH" delete namespace "$SMOKE_NAMESPACE" --ignore-not-found >/dev/null } while [ "$#" -gt 0 ]; do case "$1" in --kubeconfig) KUBECONFIG_PATH="$2" shift 2 ;; --dry-run) DRY_RUN=true shift ;; --help|-h) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 1 ;; esac done require_command kubectl if [ -z "$KUBECONFIG_PATH" ]; then echo "Kubeconfig path is required via --kubeconfig or KUBECONFIG." >&2 exit 1 fi if [ ! -f "$KUBECONFIG_PATH" ] && ! $DRY_RUN; then echo "Kubeconfig file not found: $KUBECONFIG_PATH" >&2 exit 1 fi if [ ! -f "$STORAGE_MANIFEST" ]; then echo "Storage manifest not found: $STORAGE_MANIFEST" >&2 exit 1 fi reconcile_existing_storage_class kubectl_cmd apply -f "$STORAGE_MANIFEST" if $DRY_RUN; then echo "+ kubectl --kubeconfig $KUBECONFIG_PATH create namespace msp --dry-run=client -o yaml | kubectl --kubeconfig $KUBECONFIG_PATH apply -f -" else kubectl --kubeconfig "$KUBECONFIG_PATH" create namespace msp --dry-run=client -o yaml | kubectl --kubeconfig "$KUBECONFIG_PATH" apply -f - fi kubectl_cmd label namespace msp \ pod-security.kubernetes.io/enforce=privileged \ pod-security.kubernetes.io/audit=privileged \ pod-security.kubernetes.io/warn=privileged \ --overwrite prepare_storage_path wait_for_rollout run_smoke_test echo "Storage prerequisites are ready."