Last active
October 14, 2025 23:19
-
-
Save Jordan-Hall/62f8c5f1f21f3165325aaca47e241d5d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| # ------------------------- | |
| # DEFAULT CONFIGURATION | |
| # ------------------------- | |
| HOST_IP_DEFAULT=$(hostname -I | awk '{print $1}') | |
| DOMAIN_DEFAULT="$HOST_IP_DEFAULT.sslip.io" | |
| EMAIL_DEFAULT="[email protected]" | |
| ADMIN_USER_DEFAULT="admin" | |
| ADMIN_PASS_DEFAULT="admin123" | |
| SURREALDB_REPLICAS_DEFAULT=3 | |
| COOLIFY_REPLICAS_DEFAULT=1 | |
| KUBERO_REPLICAS_DEFAULT=1 | |
| PD_REPLICAS_DEFAULT=3 | |
| TIKV_REPLICAS_DEFAULT=3 | |
| STORAGE_PATH_DEFAULT="/mnt/tikv-data" | |
| K3S_MASTER_TOKEN_FILE="/root/k3s_token.txt" | |
| K3S_MASTER_IP_FILE="/root/k3s_master_ip.txt" | |
| # ------------------------- | |
| # USER PARAMETERS | |
| # ------------------------- | |
| HOST_IP="${HOST_IP:-$HOST_IP_DEFAULT}" | |
| DOMAIN="${DOMAIN:-$DOMAIN_DEFAULT}" | |
| EMAIL="${EMAIL:-$EMAIL_DEFAULT}" | |
| ADMIN_USER="${ADMIN_USER:-$ADMIN_USER_DEFAULT}" | |
| ADMIN_PASS="${ADMIN_PASS:-$ADMIN_PASS_DEFAULT}" | |
| SURREALDB_REPLICAS="${SURREALDB_REPLICAS:-$SURREALDB_REPLICAS_DEFAULT}" | |
| COOLIFY_REPLICAS="${COOLIFY_REPLICAS:-$COOLIFY_REPLICAS_DEFAULT}" | |
| KUBERO_REPLICAS="${KUBERO_REPLICAS:-$KUBERO_REPLICAS_DEFAULT}" | |
| PD_REPLICAS="${PD_REPLICAS:-$PD_REPLICAS_DEFAULT}" | |
| TIKV_REPLICAS="${TIKV_REPLICAS:-$TIKV_REPLICAS_DEFAULT}" | |
| STORAGE_PATH="${STORAGE_PATH:-$STORAGE_PATH_DEFAULT}" | |
| # ------------------------- | |
| # SYSTEM UPDATE & FIREWALL | |
| # ------------------------- | |
| echo "Updating system..." | |
| sudo apt update -y && sudo apt upgrade -y | |
| sudo apt install -y curl wget gnupg lsb-release ufw software-properties-common apt-transport-https | |
| echo "Configuring firewall..." | |
| sudo ufw default deny incoming | |
| sudo ufw default allow outgoing | |
| sudo ufw allow 22/tcp | |
| sudo ufw allow 6443/tcp | |
| sudo ufw allow 80,443/tcp | |
| sudo ufw --force enable | |
| # ------------------------- | |
| # DOCKER INSTALLATION | |
| # ------------------------- | |
| if ! command -v docker >/dev/null 2>&1; then | |
| echo "Installing Docker..." | |
| sudo apt remove -y docker docker-engine docker.io containerd runc || true | |
| sudo mkdir -p /etc/apt/keyrings | |
| curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg | |
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null | |
| sudo apt update -y | |
| sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin | |
| sudo systemctl enable docker | |
| sudo systemctl start docker | |
| fi | |
| # ------------------------- | |
| # K3S INSTALL FUNCTIONS | |
| # ------------------------- | |
| install_k3s_master() { | |
| echo "Installing K3s master..." | |
| curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644" sh - | |
| sleep 10 | |
| echo $(sudo cat /var/lib/rancher/k3s/server/node-token) > $K3S_MASTER_TOKEN_FILE | |
| echo $HOST_IP > $K3S_MASTER_IP_FILE | |
| } | |
| install_k3s_agent() { | |
| local master_ip="$1" | |
| local token="$2" | |
| echo "Installing K3s agent..." | |
| curl -sfL https://get.k3s.io | K3S_URL="https://$master_ip:6443" K3S_TOKEN="$token" sh - | |
| } | |
| wait_nodes_ready() { | |
| echo "Waiting for all nodes to be ready..." | |
| kubectl wait --for=condition=Ready nodes --all --timeout=180s | |
| } | |
| # ------------------------- | |
| # INSTALL K3S | |
| # ------------------------- | |
| if [ -f /etc/rancher/k3s/k3s.yaml ]; then | |
| echo "K3s already installed." | |
| else | |
| if [ ! -f $K3S_MASTER_TOKEN_FILE ]; then | |
| install_k3s_master | |
| else | |
| MASTER_IP=$(cat $K3S_MASTER_IP_FILE) | |
| K3S_TOKEN=$(cat $K3S_MASTER_TOKEN_FILE) | |
| install_k3s_agent $MASTER_IP $K3S_TOKEN | |
| fi | |
| fi | |
| wait_nodes_ready | |
| # ------------------------- | |
| # CREATE NAMESPACES | |
| # ------------------------- | |
| for ns in tikv surrealdb coolify kubero cert-manager monitoring; do | |
| kubectl create namespace $ns --dry-run=client -o yaml | kubectl apply -f - | |
| done | |
| # ------------------------- | |
| # INSTALL NGINX INGRESS CONTROLLER | |
| # ------------------------- | |
| echo "Installing Nginx Ingress Controller..." | |
| kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml | |
| kubectl wait --namespace ingress-nginx \ | |
| --for=condition=ready pod \ | |
| --selector=app.kubernetes.io/component=controller \ | |
| --timeout=180s | |
| # ------------------------- | |
| # INSTALL METRICS SERVER | |
| # ------------------------- | |
| echo "Installing Metrics Server..." | |
| kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml | |
| # ------------------------- | |
| # INSTALL CERT MANAGER | |
| # ------------------------- | |
| echo "Installing Cert Manager..." | |
| kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml | |
| echo "Waiting for cert-manager to be ready..." | |
| kubectl wait --namespace cert-manager \ | |
| --for=condition=ready pod \ | |
| --selector=app.kubernetes.io/instance=cert-manager \ | |
| --timeout=180s | |
| # ------------------------- | |
| # INSTALL KUBERO OPERATOR | |
| # ------------------------- | |
| echo "Installing Kubero Operator..." | |
| kubectl apply -f https://raw.githubusercontent.com/kubero-dev/kubero-operator/main/deploy/operator.yaml | |
| # ------------------------- | |
| # INSTALL MONITORING STACK (Prometheus) | |
| # ------------------------- | |
| echo "Installing Prometheus monitoring stack..." | |
| kubectl apply -f https://raw.githubusercontent.com/kubero-dev/kubero-operator/main/config/samples/application_v1alpha1_kuberoprometheus.yaml | |
| # ------------------------- | |
| # INSTALL KUBERO UI | |
| # ------------------------- | |
| echo "Installing Kubero UI..." | |
| kubectl create namespace kubero --dry-run=client -o yaml | kubectl apply -f - | |
| # Create Kubero secrets | |
| echo "Creating Kubero secrets..." | |
| kubectl create secret generic kubero-secrets \ | |
| --from-literal=KUBERO_WEBHOOK_SECRET=$(openssl rand -hex 20) \ | |
| --from-literal=KUBERO_SESSION_KEY=$(openssl rand -hex 20) \ | |
| -n kubero --dry-run=client -o yaml | kubectl apply -f - | |
| # Deploy Kubero UI | |
| kubectl apply -f https://raw.githubusercontent.com/kubero-dev/kubero-operator/main/config/samples/application_v1alpha1_kubero.yaml -n kubero | |
| # ------------------------- | |
| # CLEAN UP EXISTING TIKV RESOURCES (if upgrading) | |
| # ------------------------- | |
| echo "Checking for existing TiKV resources..." | |
| if kubectl get statefulset pd -n tikv >/dev/null 2>&1; then | |
| echo "Deleting existing PD StatefulSet..." | |
| kubectl delete statefulset pd -n tikv --cascade=orphan || true | |
| fi | |
| if kubectl get statefulset tikv -n tikv >/dev/null 2>&1; then | |
| echo "Deleting existing TiKV StatefulSet..." | |
| kubectl delete statefulset tikv -n tikv --cascade=orphan || true | |
| fi | |
| if kubectl get deployment surrealdb -n surrealdb >/dev/null 2>&1; then | |
| echo "Deleting existing SurrealDB Deployment..." | |
| kubectl delete deployment surrealdb -n surrealdb || true | |
| fi | |
| echo "Waiting for resources to be cleaned up..." | |
| sleep 5 | |
| # ------------------------- | |
| # DEPLOY PD (Placement Driver) | |
| # ------------------------- | |
| cat <<EOF | kubectl apply -f - | |
| apiVersion: apps/v1 | |
| kind: StatefulSet | |
| metadata: | |
| name: pd | |
| namespace: tikv | |
| spec: | |
| serviceName: pd | |
| replicas: $PD_REPLICAS | |
| podManagementPolicy: Parallel | |
| selector: | |
| matchLabels: | |
| app: pd | |
| template: | |
| metadata: | |
| labels: | |
| app: pd | |
| spec: | |
| containers: | |
| - name: pd | |
| image: pingcap/pd:latest | |
| command: | |
| - /pd-server | |
| args: | |
| - --name=\$(POD_NAME) | |
| - --data-dir=/pd/data | |
| - --client-urls=http://0.0.0.0:2379 | |
| - --advertise-client-urls=http://\$(POD_NAME).pd.tikv.svc.cluster.local:2379 | |
| - --peer-urls=http://0.0.0.0:2380 | |
| - --advertise-peer-urls=http://\$(POD_NAME).pd.tikv.svc.cluster.local:2380 | |
| - --initial-cluster=pd-0=http://pd-0.pd.tikv.svc.cluster.local:2380,pd-1=http://pd-1.pd.tikv.svc.cluster.local:2380,pd-2=http://pd-2.pd.tikv.svc.cluster.local:2380 | |
| env: | |
| - name: POD_NAME | |
| valueFrom: | |
| fieldRef: | |
| fieldPath: metadata.name | |
| ports: | |
| - containerPort: 2379 | |
| name: client | |
| - containerPort: 2380 | |
| name: peer | |
| resources: | |
| requests: | |
| memory: "512Mi" | |
| cpu: "250m" | |
| limits: | |
| memory: "1Gi" | |
| cpu: "1000m" | |
| readinessProbe: | |
| httpGet: | |
| path: /health | |
| port: 2379 | |
| initialDelaySeconds: 30 | |
| periodSeconds: 10 | |
| timeoutSeconds: 5 | |
| livenessProbe: | |
| httpGet: | |
| path: /health | |
| port: 2379 | |
| initialDelaySeconds: 60 | |
| periodSeconds: 20 | |
| timeoutSeconds: 5 | |
| volumeMounts: | |
| - name: pd-data | |
| mountPath: /pd/data | |
| volumeClaimTemplates: | |
| - metadata: | |
| name: pd-data | |
| spec: | |
| accessModes: ["ReadWriteOnce"] | |
| resources: | |
| requests: | |
| storage: 5Gi | |
| --- | |
| apiVersion: v1 | |
| kind: Service | |
| metadata: | |
| name: pd | |
| namespace: tikv | |
| spec: | |
| clusterIP: None | |
| selector: | |
| app: pd | |
| ports: | |
| - port: 2379 | |
| targetPort: 2379 | |
| EOF | |
| # ------------------------- | |
| # DEPLOY TiKV | |
| # ------------------------- | |
| cat <<EOF | kubectl apply -f - | |
| apiVersion: apps/v1 | |
| kind: StatefulSet | |
| metadata: | |
| name: tikv | |
| namespace: tikv | |
| spec: | |
| serviceName: tikv | |
| replicas: $TIKV_REPLICAS | |
| selector: | |
| matchLabels: | |
| app: tikv | |
| template: | |
| metadata: | |
| labels: | |
| app: tikv | |
| spec: | |
| initContainers: | |
| - name: wait-for-pd | |
| image: busybox:latest | |
| command: | |
| - sh | |
| - -c | |
| - | | |
| until nc -z pd-0.pd.tikv.svc.cluster.local 2379; do | |
| echo "Waiting for PD to be ready..." | |
| sleep 5 | |
| done | |
| containers: | |
| - name: tikv | |
| image: pingcap/tikv:latest | |
| command: | |
| - /tikv-server | |
| args: | |
| - --pd=pd-0.pd.tikv.svc.cluster.local:2379,pd-1.pd.tikv.svc.cluster.local:2379,pd-2.pd.tikv.svc.cluster.local:2379 | |
| - --addr=0.0.0.0:20160 | |
| - --advertise-addr=\$(POD_NAME).tikv.tikv.svc.cluster.local:20160 | |
| - --data-dir=/tikv/data | |
| - --log-level=info | |
| env: | |
| - name: POD_NAME | |
| valueFrom: | |
| fieldRef: | |
| fieldPath: metadata.name | |
| ports: | |
| - containerPort: 20160 | |
| name: server | |
| resources: | |
| requests: | |
| memory: "1Gi" | |
| cpu: "500m" | |
| limits: | |
| memory: "2Gi" | |
| cpu: "2000m" | |
| readinessProbe: | |
| tcpSocket: | |
| port: 20160 | |
| initialDelaySeconds: 60 | |
| periodSeconds: 10 | |
| timeoutSeconds: 5 | |
| livenessProbe: | |
| tcpSocket: | |
| port: 20160 | |
| initialDelaySeconds: 120 | |
| periodSeconds: 20 | |
| timeoutSeconds: 5 | |
| volumeMounts: | |
| - name: tikv-data | |
| mountPath: /tikv/data | |
| volumeClaimTemplates: | |
| - metadata: | |
| name: tikv-data | |
| spec: | |
| accessModes: ["ReadWriteOnce"] | |
| resources: | |
| requests: | |
| storage: 10Gi | |
| --- | |
| apiVersion: v1 | |
| kind: Service | |
| metadata: | |
| name: tikv | |
| namespace: tikv | |
| spec: | |
| clusterIP: None | |
| selector: | |
| app: tikv | |
| ports: | |
| - port: 20160 | |
| targetPort: 20160 | |
| EOF | |
| # ------------------------- | |
| # DEPLOY SURREALDB | |
| # ------------------------- | |
| cat <<EOF | kubectl apply -f - | |
| apiVersion: apps/v1 | |
| kind: Deployment | |
| metadata: | |
| name: surrealdb | |
| namespace: surrealdb | |
| spec: | |
| replicas: $SURREALDB_REPLICAS | |
| selector: | |
| matchLabels: | |
| app: surrealdb | |
| template: | |
| metadata: | |
| labels: | |
| app: surrealdb | |
| spec: | |
| initContainers: | |
| - name: wait-for-tikv | |
| image: busybox:latest | |
| command: | |
| - sh | |
| - -c | |
| - | | |
| until nc -z pd-0.pd.tikv.svc.cluster.local 2379; do | |
| echo "Waiting for PD to be ready..." | |
| sleep 5 | |
| done | |
| echo "PD is ready, waiting for TiKV..." | |
| sleep 30 | |
| containers: | |
| - name: surrealdb | |
| image: surrealdb/surrealdb:latest | |
| args: | |
| - start | |
| - "--log" | |
| - "info" | |
| - "--bind" | |
| - "0.0.0.0:8000" | |
| - "--kvs" | |
| - "tikv://pd-0.pd.tikv.svc.cluster.local:2379" | |
| ports: | |
| - containerPort: 8000 | |
| name: http | |
| resources: | |
| requests: | |
| memory: "256Mi" | |
| cpu: "250m" | |
| limits: | |
| memory: "512Mi" | |
| cpu: "500m" | |
| readinessProbe: | |
| httpGet: | |
| path: /health | |
| port: 8000 | |
| initialDelaySeconds: 30 | |
| periodSeconds: 10 | |
| timeoutSeconds: 5 | |
| livenessProbe: | |
| httpGet: | |
| path: /health | |
| port: 8000 | |
| initialDelaySeconds: 60 | |
| periodSeconds: 20 | |
| timeoutSeconds: 5 | |
| EOF | |
| # ------------------------- | |
| # DEPLOY COOLIFY | |
| # ------------------------- | |
| cat <<EOF | kubectl apply -f - | |
| apiVersion: apps/v1 | |
| kind: Deployment | |
| metadata: | |
| name: coolify | |
| namespace: coolify | |
| spec: | |
| replicas: $COOLIFY_REPLICAS | |
| selector: | |
| matchLabels: | |
| app: coolify | |
| template: | |
| metadata: | |
| labels: | |
| app: coolify | |
| spec: | |
| containers: | |
| - name: coolify | |
| image: coollabsio/coolify:latest | |
| ports: | |
| - containerPort: 3000 | |
| --- | |
| apiVersion: v1 | |
| kind: Service | |
| metadata: | |
| name: coolify | |
| namespace: coolify | |
| spec: | |
| selector: | |
| app: coolify | |
| ports: | |
| - port: 80 | |
| targetPort: 3000 | |
| EOF | |
| # Kubero UI is now installed via the official Kubero manifest above | |
| # ------------------------- | |
| # PROMETHEUS + GRAFANA | |
| # ------------------------- | |
| # Note: Prometheus is now installed via Kubero monitoring stack above | |
| echo "Prometheus monitoring stack installed via Kubero..." | |
| # ------------------------- | |
| # WAIT FOR ALL PODS READY | |
| # ------------------------- | |
| echo "Waiting for critical pods to be ready..." | |
| # Wait for PD StatefulSet | |
| echo "Waiting for PD pods..." | |
| kubectl rollout status statefulset/pd -n tikv --timeout=300s || true | |
| # Wait for TiKV StatefulSet | |
| echo "Waiting for TiKV pods..." | |
| kubectl rollout status statefulset/tikv -n tikv --timeout=300s || true | |
| # Wait for SurrealDB | |
| echo "Waiting for SurrealDB pods..." | |
| kubectl rollout status deployment/surrealdb -n surrealdb --timeout=300s || true | |
| # Wait for Coolify | |
| echo "Waiting for Coolify pods..." | |
| kubectl rollout status deployment/coolify -n coolify --timeout=300s || true | |
| # Wait for Kubero Operator | |
| echo "Waiting for Kubero Operator..." | |
| kubectl rollout status deployment/kubero-operator-controller-manager -n kubero-operator-system --timeout=300s || true | |
| echo "" | |
| echo "β Full production-ready cluster installed!" | |
| echo "" | |
| echo "π Service URLs:" | |
| echo " Coolify: https://coolify.$DOMAIN" | |
| echo " SurrealDB: https://surrealdb.$DOMAIN" | |
| echo " Kubero Dashboard: https://kubero.$DOMAIN" | |
| echo " Grafana: https://grafana.$DOMAIN" | |
| echo "" | |
| echo "π Check pod status with: kubectl get pods --all-namespaces" | |
| echo "π Multi-node ready β copy k3s_token.txt & k3s_master_ip.txt to new nodes to join agents." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment