#!/bin/bash # usage: etc/final_deployment.sh [-e|--docker-export] [-c|--helm-chart] [-k|--kustomize] [-p|--prepare-k3s-pipeline] [-o|--output-dir DIR] # # This script handles post-installation deployment tasks for Knoe. # 1st use case: -e|--docker-export # Runs docker image export for each image required to deploy Knoe. # Artifacts are written to DOCKER_IMPORT_DIR. # 2nd use case: -c|--helm-chart # Creates a Helm chart from the current k8s manifests. # Artifacts are written to HELM_CHART_DIR. # 3rd use case: -k|--kustomize # Creates kustomize deployments (one per component). # Artifacts are written to KUSTOMIZE_DIR. # 4th use case: -p|--prepare-k3s-pipeline # Stages all artifacts for k3s delivery via OpenTofu + ArgoCD. # Copies manifests, generates tfvars, and creates ArgoCD Application manifests. usage() { echo "Usage: $0 [options]" echo "" echo "Options:" echo " -e, --docker-export Export required docker images to DOCKER_IMPORT_DIR" echo " -c, --helm-chart Create Helm chart from k8s manifests" echo " -k, --kustomize Create kustomize deployments from k8s manifests" echo " -p, --prepare-k3s-pipeline Stage artifacts for k3s delivery (OpenTofu + ArgoCD)" echo " -o, --output-dir DIR Base directory for docker-import/, helm-chart/, and kustomize/" echo "" } do_docker_export=0 do_helm_chart=0 do_kustomize=0 do_k3s_pipeline=0 export_dir="" if [[ $# -eq 0 ]]; then usage exit 1 fi while [[ $# -gt 0 ]]; do case "$1" in -e|--docker-export) do_docker_export=1 shift ;; -c|--helm-chart) do_helm_chart=1 shift ;; -k|--kustomize) do_kustomize=1 shift ;; -p|--prepare-k3s-pipeline) do_k3s_pipeline=1 shift ;; -o|--output-dir) if [[ -z "${2:-}" ]]; then echo "Missing value for $1" usage exit 1 fi export_dir="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) echo "Unknown option: $1" usage exit 1 ;; esac done if [[ $do_docker_export -eq 0 && $do_helm_chart -eq 0 && $do_kustomize -eq 0 && $do_k3s_pipeline -eq 0 ]]; then usage exit 1 fi PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" export PROJECT_ROOT # Load environment if env.sh exists if [[ -f "${PROJECT_ROOT}/env.sh" ]]; then source "${PROJECT_ROOT}/env.sh" fi default_owner="$(id -un 2>/dev/null || echo knoe)" knoe_data="${PROLE_DATA:-/opt/knoe/data/${default_owner}}" knoe_data="${knoe_data%/}" if [[ -n "$export_dir" ]]; then export_dir="${export_dir%/}" DOCKER_IMPORT_DIR="${export_dir}/docker-import" HELM_CHART_DIR="${export_dir}/helm-chart" KUSTOMIZE_DIR="${export_dir}/kustomize" fi DOCKER_IMPORT_DIR="${DOCKER_IMPORT_DIR:-${knoe_data}/docker-import}" HELM_CHART_DIR="${HELM_CHART_DIR:-${knoe_data}/helm-chart}" KUSTOMIZE_DIR="${KUSTOMIZE_DIR:-${knoe_data}/kustomize}" export_docker_images() { echo "Exporting Docker images to: ${DOCKER_IMPORT_DIR}" mkdir -p "${DOCKER_IMPORT_DIR}" # Collect images from K8s manifests # Robust extraction of image names from YAML files find "${PROJECT_ROOT}/k8s/knoe" "${PROJECT_ROOT}/k8s/openbao" "${PROJECT_ROOT}/k8s/opentofu" -name "*.yaml" -exec grep -h "image:" {} + | awk -F'image:' '{print $2}' | awk '{print $1}' | sed "s/['\"]//g" > /tmp/knoe_images.txt # Also check Supabase compose if it exists if [[ -f "${PROJECT_ROOT}/supabase/docker/docker-compose.yml" ]]; then grep -h "image:" "${PROJECT_ROOT}/supabase/docker/docker-compose.yml" | awk -F'image:' '{print $2}' | awk '{print $1}' | sed "s/['\"]//g" >> /tmp/knoe_images.txt fi # Add Kerberos proxy image if defined KRB_IMG="${KRB5_AD_PROXY_IMAGE:-alpine/socat}" if [[ -n "$KRB_IMG" ]]; then echo "$KRB_IMG" >> /tmp/knoe_images.txt fi # Add Kerberos test image (used by init_kerberos_test.sh) KRB_TEST_IMG="${KRB5_TEST_IMAGE:-${PROLE_KRB_TEST_IMAGE:-ubuntu:24.04}}" if [[ -n "$KRB_TEST_IMG" ]]; then echo "$KRB_TEST_IMG" >> /tmp/knoe_images.txt fi # Unique images sort -u /tmp/knoe_images.txt > /tmp/knoe_images_unique.txt while read -r image; do if [[ -z "$image" ]]; then continue; fi # Ensure we have the image locally echo "Checking image: $image" if ! docker image inspect "$image" >/dev/null 2>&1; then echo "Pulling $image (platform linux/arm64)..." docker pull --platform linux/arm64 "$image" fi safe_name=$(echo "$image" | sed 's/\//_/g' | sed 's/:/_/g') tar_path="${DOCKER_IMPORT_DIR}/${safe_name}.tar" echo "Exporting $image to $tar_path..." docker save "$image" -o "$tar_path" if [[ $? -eq 0 ]]; then echo "[OK] Exported $image" else echo "[ERROR] Failed to export $image" fi done < /tmp/knoe_images_unique.txt rm -f /tmp/knoe_images.txt /tmp/knoe_images_unique.txt echo "Docker export complete." } create_helm_chart() { local chart_dir="${HELM_CHART_DIR}" local chart_name="knoe" local chart_version="0.1.0" local app_version app_version="$(date -u +%Y.%m.%d)" echo "Creating Helm chart in: ${chart_dir}" mkdir -p "${chart_dir}/templates" # Clean managed template subdirs to avoid stale manifests. rm -rf "${chart_dir}/templates/knoe" "${chart_dir}/templates/openbao" "${chart_dir}/templates/opentofu" cat > "${chart_dir}/Chart.yaml" < "${chart_dir}/values.yaml" <<'EOF' # Values are unused by default. Manifests are installed as-is. EOF copy_manifests() { local src_dir="$1" local dest_subdir="$2" if [[ ! -d "$src_dir" ]]; then echo "Skipping missing manifest dir: $src_dir" return fi local dest_dir="${chart_dir}/templates/${dest_subdir}" mkdir -p "${dest_dir}" local found=0 while IFS= read -r -d '' file; do if [[ "$(basename "$file")" == "kustomization.yaml" ]]; then continue fi cp "$file" "${dest_dir}/" found=1 done < <(find "$src_dir" -maxdepth 1 -type f -name "*.yaml" -print0) if [[ $found -eq 0 ]]; then echo "No manifests found in $src_dir" fi } copy_manifests "${PROJECT_ROOT}/k8s/knoe" "knoe" copy_manifests "${PROJECT_ROOT}/k8s/openbao" "openbao" copy_manifests "${PROJECT_ROOT}/k8s/opentofu" "opentofu" echo "Helm chart creation complete." } create_kustomize_deployments() { local base_dir="${KUSTOMIZE_DIR}" echo "Creating kustomize deployments in: ${base_dir}" mkdir -p "${base_dir}" create_kustomize_package() { local name="$1" local src_dir="$2" if [[ ! -d "$src_dir" ]]; then echo "Skipping missing manifest dir: $src_dir" return fi local dest_dir="${base_dir}/${name}" mkdir -p "${dest_dir}" # Copy manifests except kustomization.yaml local files=() while IFS= read -r -d '' file; do if [[ "$(basename "$file")" == "kustomization.yaml" ]]; then continue fi cp "$file" "${dest_dir}/" files+=("$(basename "$file")") done < <(find "$src_dir" -maxdepth 1 -type f -name "*.yaml" -print0) if [[ ${#files[@]} -eq 0 ]]; then echo "No manifests found in $src_dir" return fi { echo "apiVersion: kustomize.config.k8s.io/v1beta1" echo "kind: Kustomization" echo "resources:" for f in "${files[@]}"; do echo " - ${f}" done } > "${dest_dir}/kustomization.yaml" } create_kustomize_package "knoe" "${PROJECT_ROOT}/k8s/knoe" create_kustomize_package "openbao" "${PROJECT_ROOT}/k8s/openbao" create_kustomize_package "opentofu" "${PROJECT_ROOT}/k8s/opentofu" echo "Kustomize deployments creation complete." } if [[ $do_docker_export -eq 1 ]]; then export_docker_images fi if [[ $do_helm_chart -eq 1 ]]; then create_helm_chart fi if [[ $do_kustomize -eq 1 ]]; then create_kustomize_deployments fi prepare_k3s_pipeline() { echo "========================================" echo "Preparing k3s pipeline (OpenTofu + ArgoCD)" echo "========================================" echo "" local pipeline_dir="${PROJECT_ROOT}/deploy/opentofu/k3s" local manifest_root="${pipeline_dir}/manifests" local argocd_dir="${pipeline_dir}/argocd" mkdir -p "${manifest_root}" "${argocd_dir}" echo "==> Staging k8s manifests to ${manifest_root}" local total=0 for component in knoe openbao opentofu; do local src="${PROJECT_ROOT}/k8s/${component}" if [[ ! -d "$src" ]]; then echo " [SKIP] ${component}/ (not found)" continue fi local dst="${manifest_root}/${component}" mkdir -p "$dst" local count=0 for f in "${src}"/*.yaml; do [[ -f "$f" ]] || continue cp "$f" "$dst/" count=$((count + 1)) done total=$((total + count)) echo " [OK] ${component}/ -> manifests/${component}/ (${count} files)" done echo " Total manifests staged: ${total}" # Stage Supabase manifests if present local supabase_k8s="${PROJECT_ROOT}/supabase/k8s" if [[ -d "$supabase_k8s" ]]; then local dst="${manifest_root}/supabase" mkdir -p "$dst" local count=0 for f in "${supabase_k8s}"/*.yaml; do [[ -f "$f" ]] || continue cp "$f" "$dst/" count=$((count + 1)) done if [[ $count -gt 0 ]]; then echo " [OK] supabase/k8s/ -> manifests/supabase/ (${count} files)" fi fi # Write tfvars local k3s_server="${K3S_SERVER_URL:-}" local k3s_token="${K3S_TOKEN:-}" local namespace="${PROLE_NAMESPACE:-default}" echo "" echo "==> Writing opentofu.auto.tfvars" echo " k3s_server_url = ${k3s_server:-(not set)}" echo " namespace = ${namespace}" cat > "${pipeline_dir}/opentofu.auto.tfvars" < Generating ArgoCD Application manifests in argocd/" local repo_url="${PROLE_GIT_REPO:-}" if [[ -z "$repo_url" ]]; then repo_url=$(cd "${PROJECT_ROOT}" && git remote get-url origin 2>/dev/null || true) fi repo_url="${repo_url:-https://github.com/knoe-dev/knoe.git}" local target_revision="${PROLE_GIT_BRANCH:-main}" for component in knoe openbao opentofu; do cat > "${argocd_dir}/application-${component}.yaml" < k3s pipeline preparation complete." echo " Pipeline dir: ${pipeline_dir}" echo " ArgoCD apps: ${argocd_dir}" echo "" echo "Next steps:" echo " cd ${pipeline_dir} && tofu init && tofu plan && tofu apply" echo " kubectl apply -f ${argocd_dir}/" } if [[ $do_k3s_pipeline -eq 1 ]]; then prepare_k3s_pipeline fi