#!/bin/bash # Migration script from Cozystack ConfigMaps to Package-based configuration # This script converts cozystack, cozystack-branding, and cozystack-scheduling # ConfigMaps into a Package resource with the new values structure. set -e NAMESPACE="cozy-system" echo "=============================" echo " Cozystack Migration to v1.0 " echo "=============================" echo "" echo "This script will convert existing ConfigMaps to a Package resource." echo "" # Check if kubectl is available if ! command -v kubectl &> /dev/null; then echo "Error: kubectl is not installed or not in PATH" exit 1 fi # Check if jq is available if ! command -v jq &> /dev/null; then echo "Error: jq is not installed or not in PATH" exit 1 fi # Check if we can access the cluster if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then echo "Error: Cannot access namespace $NAMESPACE" exit 1 fi # Step 0: Annotate critical resources to prevent Helm from deleting them echo "Step 0: Protect critical resources from Helm deletion" echo "" echo "The following resources will be annotated with helm.sh/resource-policy=keep" echo "to prevent Helm from deleting them when the installer release is removed:" echo " - Namespace: $NAMESPACE" echo " - ConfigMap: $NAMESPACE/cozystack-version" echo "" read -p "Do you want to annotate these resources? (y/N) " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Annotating namespace $NAMESPACE..." kubectl annotate namespace "$NAMESPACE" helm.sh/resource-policy=keep --overwrite echo "Annotating ConfigMap cozystack-version..." kubectl annotate configmap -n "$NAMESPACE" cozystack-version helm.sh/resource-policy=keep --overwrite 2>/dev/null || echo " ConfigMap cozystack-version not found, skipping." echo "" echo "Resources annotated successfully." else echo "WARNING: Skipping annotation. If you remove the Helm installer release," echo "the namespace and its contents may be deleted!" fi echo "" # Step 1: Check for cozy-proxy HelmRelease with conflicting releaseName # In v0.41.x, cozy-proxy was incorrectly configured with releaseName "cozystack", # which conflicts with the installer helm release name. If not suspended, cozy-proxy # HelmRelease will overwrite the installer release and delete cozystack-operator. COZY_PROXY_RELEASE_NAME=$(kubectl get hr -n "$NAMESPACE" cozy-proxy -o jsonpath='{.spec.releaseName}' 2>/dev/null || true) if [ "$COZY_PROXY_RELEASE_NAME" = "cozystack" ]; then echo "WARNING: HelmRelease cozy-proxy has releaseName 'cozystack', which conflicts" echo "with the installer release. It must be suspended before proceeding, otherwise" echo "it will overwrite the installer and delete cozystack-operator." echo "" read -p "Suspend HelmRelease cozy-proxy? (y/N) " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then kubectl -n "$NAMESPACE" patch hr cozy-proxy --type=merge --field-manager=flux-client-side-apply -p '{"spec":{"suspend":true}}' echo "HelmRelease cozy-proxy suspended." else echo "ERROR: Cannot proceed with conflicting cozy-proxy HelmRelease active." echo "Please suspend it manually:" echo " kubectl -n $NAMESPACE patch hr cozy-proxy --type=merge -p '{\"spec\":{\"suspend\":true}}'" exit 1 fi echo "" fi # Read ConfigMap cozystack echo "Reading ConfigMap cozystack..." COZYSTACK_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack -o json 2>/dev/null || echo "{}") # Read ConfigMap cozystack-branding echo "Reading ConfigMap cozystack-branding..." BRANDING_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack-branding -o json 2>/dev/null || echo "{}") # Read ConfigMap cozystack-scheduling echo "Reading ConfigMap cozystack-scheduling..." SCHEDULING_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack-scheduling -o json 2>/dev/null || echo "{}") # Extract values from cozystack ConfigMap CLUSTER_DOMAIN=$(echo "$COZYSTACK_CM" | jq -r '.data["cluster-domain"] // "cozy.local"') ROOT_HOST=$(echo "$COZYSTACK_CM" | jq -r '.data["root-host"] // "example.org"') API_SERVER_ENDPOINT=$(echo "$COZYSTACK_CM" | jq -r '.data["api-server-endpoint"] // ""') OIDC_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["oidc-enabled"] // "false"') KEYCLOAK_REDIRECTS=$(echo "$COZYSTACK_CM" | jq -r '.data["extra-keycloak-redirect-uri-for-dashboard"] // ""' ) TELEMETRY_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["telemetry-enabled"] // "true"') BUNDLE_NAME=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-name"] // "paas-full"') BUNDLE_DISABLE=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-disable"] // ""') BUNDLE_ENABLE=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-enable"] // ""') EXPOSE_INGRESS=$(echo "$COZYSTACK_CM" | jq -r '.data["expose-ingress"] // "tenant-root"') EXPOSE_SERVICES=$(echo "$COZYSTACK_CM" | jq -r '.data["expose-services"] // ""') # Certificate issuer configuration (old undocumented field: clusterissuer) OLD_CLUSTER_ISSUER=$(echo "$COZYSTACK_CM" | jq -r '.data["clusterissuer"] // ""') # Convert old clusterissuer value to new solver/issuerName fields SOLVER="" ISSUER_NAME="" case "$OLD_CLUSTER_ISSUER" in cloudflare) SOLVER="dns01" ISSUER_NAME="letsencrypt-prod" ;; http01) SOLVER="http01" ISSUER_NAME="letsencrypt-prod" ;; "") # Field not set; omit from Package so chart defaults apply ;; *) # Unrecognised value — treat as custom ClusterIssuer name with no solver override ISSUER_NAME="$OLD_CLUSTER_ISSUER" ;; esac # Build certificates YAML block (empty string when no override needed) if [ -n "$SOLVER" ] || [ -n "$ISSUER_NAME" ]; then CERTIFICATES_SECTION=" certificates: solver: \"${SOLVER}\" issuerName: \"${ISSUER_NAME}\"" else CERTIFICATES_SECTION="" fi # Network configuration POD_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-pod-cidr"] // "10.244.0.0/16"') POD_GATEWAY=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-pod-gateway"] // "10.244.0.1"') SVC_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-svc-cidr"] // "10.96.0.0/16"') JOIN_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-join-cidr"] // "100.64.0.0/16"') EXTERNAL_IPS=$(echo "$COZYSTACK_CM" | jq -r '.data["expose-external-ips"] // ""') if [ -z "$EXTERNAL_IPS" ]; then EXTERNAL_IPS="[]" else EXTERNAL_IPS=$(echo "$EXTERNAL_IPS" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - "$0}') fi # Convert comma-separated lists to YAML arrays if [ -z "$BUNDLE_DISABLE" ]; then DISABLED_PACKAGES="[]" else DISABLED_PACKAGES=$(echo "$BUNDLE_DISABLE" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - cozystack."$0}') fi if [ -z "$BUNDLE_ENABLE" ]; then ENABLED_PACKAGES="[]" else ENABLED_PACKAGES=$(echo "$BUNDLE_ENABLE" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - cozystack."$0}') fi if [ -z "$EXPOSE_SERVICES" ]; then EXPOSED_SERVICES_YAML="[]" else EXPOSED_SERVICES_YAML=$(echo "$EXPOSE_SERVICES" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - "$0}') fi # Update bundle naming BUNDLE_NAME=$(echo "$BUNDLE_NAME" | sed 's/paas/isp/') # Extract branding if available BRANDING=$(echo "$BRANDING_CM" | jq -r '.data // {} | to_entries[] | "\(.key): \"\(.value)\""') if [ -z "$BRANDING" ]; then BRANDING="{}" else BRANDING=$(echo "$BRANDING" | awk 'BEGIN{print}{print " " $0}') fi # Extract scheduling if available SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CM" | jq -r '.data["globalAppTopologySpreadConstraints"] // ""') if [ -z "$SCHEDULING_CONSTRAINTS" ]; then SCHEDULING_CONSTRAINTS='""' else SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CONSTRAINTS" | awk 'BEGIN{print}{print " " $0}') fi echo "" echo "Extracted configuration:" echo " Cluster Domain: $CLUSTER_DOMAIN" echo " Root Host: $ROOT_HOST" echo " API Server Endpoint: $API_SERVER_ENDPOINT" echo " OIDC Enabled: $OIDC_ENABLED" echo " Bundle Name: $BUNDLE_NAME" echo " Certificate Solver: ${SOLVER:-http01 (default)}" echo " Issuer Name: ${ISSUER_NAME:-letsencrypt-prod (default)}" echo "" # Generate Package YAML PACKAGE_YAML=$(cat <