#!/usr/bin/env bash set -euo pipefail [ -r "./env-cilium" ] && . ./env-cilium # kubeconfig — env-cilium override → kubeadm → k0s → generate from k0s K0S_DATA_DIR="${K0S_DATA_DIR:-/var/lib/k0s}" if [ -z "${KUBECONFIG:-}" ] || [ ! -f "${KUBECONFIG}" ]; then if [ -f "/etc/kubernetes/admin.conf" ]; then KUBECONFIG="/etc/kubernetes/admin.conf" elif [ -f "${K0S_DATA_DIR}/pki/admin.conf" ]; then KUBECONFIG="${K0S_DATA_DIR}/pki/admin.conf" else KUBECONFIG="/tmp/k0s-admin.conf" k0s kubeconfig admin > "${KUBECONFIG}" fi fi export KUBECONFIG # Detect kubectl: native binary or k0s wrapper if command -v kubectl &>/dev/null; then KUBECTL="kubectl" elif command -v k0s &>/dev/null; then KUBECTL="k0s kubectl" else echo "ERROR: neither kubectl nor k0s found in PATH" >&2 exit 1 fi OS="$(uname | tr '[:upper:]' '[:lower:]')" ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/' -e 's/armv7l/arm/')" CILIUM_VERSION="${CILIUM_VERSION:-1.19.3}" CILIUM_CLUSTER="${CILIUM_CLUSTER_NAME:-}" CILIUM_CLUSTER_ID="${CILIUM_CLUSTER_ID:-0}" CILIUM_INGRESS="${CILIUM_ENABLE_INGRESS_CONTROLLER:-false}" CILIUM_INGRESS_LB="${CILIUM_INGRESS_LB_MODE:-shared}" CILIUM_GATEWAY_API="${CILIUM_ENABLE_GATEWAY_API:-false}" CILIUM_GATEWAY_API_VERSION="${CILIUM_GATEWAY_API_VERSION:-v1.2.1}" CILIUM_KPR="${CILIUM_KUBE_PROXY_REPLACEMENT:-false}" CILIUM_SOCKET_LB="${CILIUM_SOCKET_LB:-true}" CILIUM_ENVOY_ENABLED="${CILIUM_ENVOY_ENABLED:-true}" CILIUM_L2_ANNOUNCEMENT="${CILIUM_ENABLE_L2_ANNOUNCEMENT:-false}" CILIUM_L2_NEIGH="${CILIUM_ENABLE_L2_NEIGH_DISCOVERY:-false}" # Install cilium CLI if missing or outdated CILIUM_CLI_VERSION="${CILIUM_CLI_VERSION:-v0.19.0}" if ! cilium version 2>/dev/null | grep -q "${CILIUM_CLI_VERSION}"; then echo "=== cilium-cli: installing ${CILIUM_CLI_VERSION} ===" curl -sL --remote-name-all \ "https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-${OS}-${ARCH}.tar.gz"{,.sha256sum} sha256sum --check "cilium-${OS}-${ARCH}.tar.gz.sha256sum" sudo tar xzfC "cilium-${OS}-${ARCH}.tar.gz" /usr/local/bin rm "cilium-${OS}-${ARCH}.tar.gz" "cilium-${OS}-${ARCH}.tar.gz.sha256sum" fi CILIUM_ALREADY_INSTALLED=false if cilium status --wait=false 2>/dev/null | grep -q "OK"; then CILIUM_ALREADY_INSTALLED=true fi # Gateway API CRDs — must exist before cilium install/upgrade if [ "${CILIUM_GATEWAY_API}" = "true" ]; then echo "=== cilium: installing Gateway API CRDs ${CILIUM_GATEWAY_API_VERSION} ===" GW_STD="https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${CILIUM_GATEWAY_API_VERSION}/config/crd/standard" for crd in \ gateway.networking.k8s.io_gatewayclasses.yaml \ gateway.networking.k8s.io_gateways.yaml \ gateway.networking.k8s.io_httproutes.yaml \ gateway.networking.k8s.io_referencegrants.yaml \ gateway.networking.k8s.io_grpcroutes.yaml; do ${KUBECTL} apply -f "${GW_STD}/${crd}" done # TLSRoute and TCPRoute are experimental-channel only; Cilium 1.19+ operator # requires TLSRoute or the reconciler loop errors before creating the GatewayClass. # TCPRoute is needed for raw-TCP Gateway listeners (e.g. Radicle P2P). GW_EXP="https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${CILIUM_GATEWAY_API_VERSION}/config/crd/experimental" ${KUBECTL} apply -f "${GW_EXP}/gateway.networking.k8s.io_tlsroutes.yaml" ${KUBECTL} apply -f "${GW_EXP}/gateway.networking.k8s.io_tcproutes.yaml" fi if [ "${CILIUM_ALREADY_INSTALLED}" = "true" ]; then echo "=== cilium: already installed — upgrading to v${CILIUM_VERSION} ===" else echo "=== cilium: installing v${CILIUM_VERSION} ===" fi INSTALL_ARGS=( "--version" "${CILIUM_VERSION}" "--set" "ipam.mode=kubernetes" ) if [ -n "${CILIUM_CLUSTER}" ]; then INSTALL_ARGS+=( "--set" "cluster.name=${CILIUM_CLUSTER}" "--set" "cluster.id=${CILIUM_CLUSTER_ID}" ) fi if [ "${CILIUM_KPR}" = "true" ]; then K8S_HOST="${CILIUM_K8S_SERVICE_HOST:-}" K8S_PORT="${CILIUM_K8S_SERVICE_PORT:-6443}" if [ -z "${K8S_HOST}" ]; then _api_url="$(${KUBECTL} config view --raw -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || true)" if [ -z "${_api_url}" ]; then echo "ERROR: kube-proxy-replacement=true requires CILIUM_K8S_SERVICE_HOST or a reachable KUBECONFIG" >&2 exit 1 fi _stripped="${_api_url#https://}" K8S_HOST="${_stripped%%:*}" _maybe_port="${_stripped##*:}" [ "${_maybe_port}" != "${K8S_HOST}" ] && K8S_PORT="${_maybe_port}" fi INSTALL_ARGS+=( "--set" "kubeProxyReplacement=true" "--set" "k8sServiceHost=${K8S_HOST}" "--set" "k8sServicePort=${K8S_PORT}" ) echo " kube-proxy replacement: enabled (apiserver=${K8S_HOST}:${K8S_PORT})" fi if [ "${CILIUM_SOCKET_LB}" = "false" ]; then INSTALL_ARGS+=( "--set" "socketLB.enabled=false" ) echo " socketLB: disabled (prevents eBPF socket hooks on host network)" else INSTALL_ARGS+=( "--set" "socketLB.enabled=true" ) echo " socketLB: enabled (required for source-IP preservation via FIPs)" fi if [ "${CILIUM_ENVOY_ENABLED}" = "false" ]; then INSTALL_ARGS+=( "--set" "envoy.enabled=false" ) echo " envoy: disabled (L7 policies unavailable — saves ~200MB RAM on constrained nodes)" else INSTALL_ARGS+=( "--set" "envoy.enabled=true" ) echo " envoy: enabled (required for Gateway API and L7 policies)" fi if [ "${CILIUM_INGRESS}" = "true" ]; then INSTALL_ARGS+=( "--set" "ingressController.enabled=true" "--set" "ingressController.loadbalancerMode=${CILIUM_INGRESS_LB}" "--set" "ingressController.default=true" ) echo " ingress controller: enabled (lb-mode=${CILIUM_INGRESS_LB})" fi if [ "${CILIUM_GATEWAY_API}" = "true" ]; then INSTALL_ARGS+=( "--set" "gatewayAPI.enabled=true" ) echo " gateway API: enabled (CRDs ${CILIUM_GATEWAY_API_VERSION})" fi if [ "${CILIUM_L2_NEIGH}" = "true" ]; then INSTALL_ARGS+=( "--set" "l2NeighDiscovery.enabled=true" ) echo " L2 neighbor discovery: enabled (prerequisite for L2 announcements)" fi if [ "${CILIUM_L2_ANNOUNCEMENT}" = "true" ]; then INSTALL_ARGS+=( "--set" "l2announcements.enabled=true" ) echo " L2 announcements: enabled (ARP-based VIP advertisement on L2 segments)" fi if [ "${CILIUM_ALREADY_INSTALLED}" = "true" ]; then cilium upgrade "${INSTALL_ARGS[@]}" else cilium install "${INSTALL_ARGS[@]}" fi echo "=== cilium: waiting for status OK (timeout 5m) ===" cilium status --wait --wait-duration 5m0s || cilium status echo "=== cilium: ready ===" # Provision cluster-level public Gateway when public_fip is declared in component settings. # This is idempotent: kubectl apply is safe to re-run on upgrade. # When gateway_sharing_key is set (HCCM clusters with shared LB IPs), the Gateway # is annotated with lbipam.cilium.io/sharing-key so multiple Gateways can share the IP. # # The Gateway manifests below apply ONLY the base http:80 listener — but # spec.listeners is x-kubernetes-list-type=map (merge key "name"), and every # app component appends its own https listener afterward via a JSON-patch # add (catalog/lib/gateway-tls.sh:_lib_gateway_patch_https). Plain `kubectl # apply` (client-side, 3-way merge from this script's own last-applied-config) # does not know about that OpenAPI merge-key semantics and replaces the whole # array — wiping every app-added listener on the next cilium upgrade. Server- # Side Apply with a stable field-manager respects the merge key and only ever # asserts this script's own "http" entry, leaving other managers' entries # alone. --force-conflicts takes ownership of fields previously set by plain # client-side apply (the original bootstrap), which would otherwise conflict. CILIUM_GATEWAY_PUBLIC_IP="${CILIUM_GATEWAY_PUBLIC_IP:-}" CILIUM_GATEWAY_NAME="${CILIUM_GATEWAY_NAME:-${CILIUM_CLUSTER}}" CILIUM_GATEWAY_SHARING_KEY="${CILIUM_GATEWAY_SHARING_KEY:-}" if [ "${CILIUM_GATEWAY_API}" = "true" ] && [ -n "${CILIUM_GATEWAY_PUBLIC_IP}" ] && [ -n "${CILIUM_GATEWAY_NAME}" ]; then echo "=== cilium: provisioning cluster Gateway '${CILIUM_GATEWAY_NAME}' @ ${CILIUM_GATEWAY_PUBLIC_IP} ===" ${KUBECTL} apply -f - <