#!/usr/bin/env bash set -euo pipefail [ -r "./env-k0s" ] && . ./env-k0s K0S_VERSION="${K0S_VERSION:-1.35.3+k0s.0}" K0S_ROLE="${K0S_ROLE:-controller+worker}" K0S_DATA_DIR="${K0S_DATA_DIR:-/var/lib/k0s}" K0S_DISABLE_IPV6="${K0S_DISABLE_IPV6:-true}" K0S_CLUSTER_NAME="${K0S_CLUSTER_NAME:-k0s}" K0S_DISABLE_KUBE_PROXY="${K0S_DISABLE_KUBE_PROXY:-true}" K0S_STORAGE_BACKEND="${K0S_STORAGE_BACKEND:-dqlite}" K0S_KINE_DIR="${K0S_KINE_DIR:-}" ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/' -e 's/armv7l/arm/')" K0S_TAG="v${K0S_VERSION}" K0S_BINARY_URL="https://github.com/k0sproject/k0s/releases/download/${K0S_TAG}/k0s-${K0S_TAG}-${ARCH}" CMD_TASK="${1:-install}" echo "=== k0s: checking prerequisites (cmd=${CMD_TASK}) ===" if [ "${CMD_TASK}" = "reinstall" ]; then echo "=== k0s: reinstall — stopping and resetting cluster ===" systemctl stop k0scontroller 2>/dev/null || true k0s reset --data-dir "${K0S_DATA_DIR}" 2>/dev/null || true systemctl daemon-reload echo "=== k0s: cluster reset complete, proceeding with fresh install ===" elif k0s version 2>/dev/null | grep -q "${K0S_VERSION}"; then if systemctl is-active --quiet k0scontroller 2>/dev/null; then echo "=== k0s: v${K0S_VERSION} already running — skipping install ===" exit 0 fi # Service stopped but binary present: run reset to clean partial state before reinstall echo "=== k0s: binary present but service stopped — cleaning state ===" systemctl stop k0scontroller 2>/dev/null || true k0s reset --data-dir "${K0S_DATA_DIR}" 2>/dev/null || true systemctl daemon-reload fi echo "=== k0s: installing v${K0S_VERSION} (${ARCH}, role=${K0S_ROLE}) ===" # Disable IPv6 before k0s starts — avoids dual-stack complexity if [ "${K0S_DISABLE_IPV6}" = "true" ]; then echo "=== k0s: disabling IPv6 ===" sysctl -w net.ipv6.conf.all.disable_ipv6=1 sysctl -w net.ipv6.conf.default.disable_ipv6=1 sysctl -w net.ipv6.conf.lo.disable_ipv6=1 if ! grep -q "net.ipv6.conf.all.disable_ipv6" /etc/sysctl.d/99-k0s.conf 2>/dev/null; then cat > /etc/sysctl.d/99-k0s.conf <<'EOF' net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.default.disable_ipv6=1 net.ipv6.conf.lo.disable_ipv6=1 EOF fi fi # Download and install k0s binary curl -sSfL "${K0S_BINARY_URL}" -o /usr/local/bin/k0s chmod 0755 /usr/local/bin/k0s echo "=== k0s: version $(k0s version) installed ===" KINE_DIR="${K0S_KINE_DIR:-${K0S_DATA_DIR}/kine}" mkdir -p /etc/k0s "${K0S_DATA_DIR}" "${KINE_DIR}" if [ -f ./templates/k0s.yaml ]; then cp ./templates/k0s.yaml /etc/k0s/k0s.yaml echo "=== k0s: config deployed from bundle ===" elif [ ! -f /etc/k0s/k0s.yaml ]; then echo "ERROR: /etc/k0s/k0s.yaml not found and templates/k0s.yaml not in bundle" >&2 exit 1 fi echo "=== k0s: config at /etc/k0s/k0s.yaml (storage=${K0S_STORAGE_BACKEND}, kubeProxy.disabled=${K0S_DISABLE_KUBE_PROXY}) ===" # Install as systemd service echo "=== k0s: installing systemd service (role=${K0S_ROLE}) ===" k0s install controller \ --enable-worker \ --config /etc/k0s/k0s.yaml \ --data-dir "${K0S_DATA_DIR}" echo "=== k0s: starting service ===" k0s start echo "=== k0s: waiting for API server ===" RETRIES=30 for i in $(seq 1 ${RETRIES}); do if k0s kubectl get nodes --no-headers 2>/dev/null | grep -q "Ready"; then echo "=== k0s: node Ready ===" break fi if [ "${i}" -eq "${RETRIES}" ]; then echo "ERROR: k0s node did not reach Ready after ${RETRIES} attempts" >&2 exit 1 fi sleep 10 done # Single-node (controller+worker): remove control-plane taint so workloads can schedule if [ "${K0S_ROLE}" = "controller_worker" ] || [ "${K0S_ROLE}" = "controller+worker" ]; then NODE_NAME="$(k0s kubectl get nodes --no-headers -o custom-columns=':metadata.name' | head -1)" if [ -n "${NODE_NAME}" ]; then k0s kubectl taint nodes "${NODE_NAME}" node-role.kubernetes.io/control-plane:NoSchedule- 2>/dev/null || true echo "=== k0s: removed control-plane taint from ${NODE_NAME} ===" fi fi # Symlink kubelet paths for CSI drivers that expect /var/lib/kubelet/ if [ "${K0S_DATA_DIR}" != "/var/lib/k0s" ]; then mkdir -p /var/lib/kubelet for subdir in plugins_registry plugins pods; do [ -d "${K0S_DATA_DIR}/kubelet/${subdir}" ] && \ ln -sfn "${K0S_DATA_DIR}/kubelet/${subdir}" "/var/lib/kubelet/${subdir}" 2>/dev/null || true done echo "=== k0s: kubelet symlinks created for CSI compatibility ===" fi # Export kubeconfig to standard location mkdir -p /root/.kube k0s kubeconfig admin > /root/.kube/config chmod 600 /root/.kube/config echo "=== k0s: ready (version=v${K0S_VERSION}, cluster=${K0S_CLUSTER_NAME}, data_dir=${K0S_DATA_DIR}) ==="