Skip to content

Instantly share code, notes, and snippets.

@Jordan-Hall
Last active October 14, 2025 23:19
Show Gist options
  • Select an option

  • Save Jordan-Hall/62f8c5f1f21f3165325aaca47e241d5d to your computer and use it in GitHub Desktop.

Select an option

Save Jordan-Hall/62f8c5f1f21f3165325aaca47e241d5d to your computer and use it in GitHub Desktop.

Revisions

  1. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 20 additions and 0 deletions.
    20 changes: 20 additions & 0 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -167,6 +167,26 @@ kubectl create secret generic kubero-secrets \
    # 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)
    # -------------------------
  2. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 113 additions and 4 deletions.
    117 changes: 113 additions & 4 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -179,6 +179,7 @@ metadata:
    spec:
    serviceName: pd
    replicas: $PD_REPLICAS
    podManagementPolicy: Parallel
    selector:
    matchLabels:
    app: pd
    @@ -190,15 +191,47 @@ spec:
    containers:
    - name: pd
    image: pingcap/pd:latest
    command:
    - /pd-server
    args:
    - --name=pd
    - --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
    @@ -245,15 +278,55 @@ spec:
    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.tikv.svc.cluster.local:2379
    - --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
    @@ -299,18 +372,54 @@ spec:
    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"
    - "--store"
    - "tikv://pd.tikv.svc.cluster.local:2379"
    - "--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

    # -------------------------
  3. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 30 additions and 6 deletions.
    36 changes: 30 additions & 6 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -362,12 +362,36 @@ echo "Prometheus monitoring stack installed via Kubero..."
    # -------------------------
    # WAIT FOR ALL PODS READY
    # -------------------------
    echo "Waiting for all pods in all namespaces..."
    kubectl wait --for=condition=Ready pods --all --timeout=300s
    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 "Coolify: https://coolify.$DOMAIN"
    echo "SurrealDB: https://surrealdb.$DOMAIN"
    echo "Kubero Dashboard: https://kubero.$DOMAIN"
    echo "Grafana: https://grafana.$DOMAIN"
    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."
  4. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion install.sh
    Original file line number Diff line number Diff line change
    @@ -132,7 +132,12 @@ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/late
    # INSTALL CERT MANAGER
    # -------------------------
    echo "Installing Cert Manager..."
    kubectl apply -f https://operatorhub.io/install/cert-manager.yaml
    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
  5. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 49 additions and 75 deletions.
    124 changes: 49 additions & 75 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -113,25 +113,54 @@ for ns in tikv surrealdb coolify kubero cert-manager monitoring; do
    done

    # -------------------------
    # INSTALL KUBERO CLI
    # 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

    # -------------------------
    if ! command -v kubero >/dev/null 2>&1; then
    echo "Installing Kubero CLI..."
    curl -fsSL https://get.kubero.dev | bash
    fi
    # 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://operatorhub.io/install/cert-manager.yaml

    # -------------------------
    # INSTALL KUBERO COMPONENTS
    # -------------------------
    echo "Installing Kubero components via Kubero CLI..."
    kubero install -c kubernetes --domain $DOMAIN
    kubero install -c olm --domain $DOMAIN
    kubero install -c ingress --domain $DOMAIN
    kubero install -c metrics --domain $DOMAIN
    kubero install -c certmanager --domain $DOMAIN
    kubero install -c kubero-operator --domain $DOMAIN
    kubero install -c monitoring --domain $DOMAIN
    kubero install -c kubero-ui --domain $DOMAIN --user $ADMIN_USER --user-password $ADMIN_PASS
    # 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

    # -------------------------
    # DEPLOY PD (Placement Driver)
    @@ -317,68 +346,13 @@ spec:
    targetPort: 3000
    EOF

    # -------------------------
    # DEPLOY KUBERO (DASHBOARD + OPTIONAL MODULES)
    # -------------------------
    cat <<EOF | kubectl apply -f -
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: kubero
    namespace: kubero
    spec:
    replicas: $KUBERO_REPLICAS
    selector:
    matchLabels:
    app: kubero
    template:
    metadata:
    labels:
    app: kubero
    spec:
    containers:
    - name: kubero
    image: kubero-dev/kubero:latest
    ports:
    - containerPort: 8080
    - name: kubero-dashboard
    image: kubero-dev/kubero-dashboard:latest
    ports:
    - containerPort: 9090
    EOF

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
    name: kubero
    namespace: kubero
    spec:
    selector:
    app: kubero
    ports:
    - port: 8080
    targetPort: 8080
    EOF
    # Kubero UI is now installed via the official Kubero manifest above

    # -------------------------
    # DEPLOY PROMETHEUS + GRAFANA
    # PROMETHEUS + GRAFANA
    # -------------------------
    kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/raw/main/bundle.yaml
    kubectl wait --for=condition=Ready pods --all -n monitoring --timeout=180s

    cat <<EOF | kubectl apply -f -
    apiVersion: monitoring.coreos.com/v1
    kind: Prometheus
    metadata:
    name: tikv-prometheus
    namespace: monitoring
    spec:
    serviceAccountName: prometheus
    resources:
    requests:
    memory: 400Mi
    EOF
    # Note: Prometheus is now installed via Kubero monitoring stack above
    echo "Prometheus monitoring stack installed via Kubero..."

    # -------------------------
    # WAIT FOR ALL PODS READY
  6. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 54 additions and 91 deletions.
    145 changes: 54 additions & 91 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -2,30 +2,48 @@
    set -e

    # -------------------------
    # CONFIGURATION
    # DEFAULT CONFIGURATION
    # -------------------------
    HOST_IP=$(hostname -I | awk '{print $1}')
    SSLIP_DOMAIN="$HOST_IP.sslip.io"
    EMAIL="jordan@libertyware.co.uk" # replace with real email
    HOST_IP_DEFAULT=$(hostname -I | awk '{print $1}')
    DOMAIN_DEFAULT="$HOST_IP_DEFAULT.sslip.io"
    EMAIL_DEFAULT="jordan@libertyware.co.uk"
    ADMIN_USER_DEFAULT="admin"
    ADMIN_PASS_DEFAULT="admin123"

    SURREALDB_REPLICAS=3
    COOLIFY_REPLICAS=1
    KUBERO_REPLICAS=1
    PD_REPLICAS=3
    TIKV_REPLICAS=3
    STORAGE_PATH="/mnt/tikv-data"
    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

    # Setup firewall
    echo "Configuring firewall..."
    sudo ufw default deny incoming
    sudo ufw default allow outgoing
    sudo ufw allow 22/tcp
    @@ -52,7 +70,7 @@ fi
    # K3S INSTALL FUNCTIONS
    # -------------------------
    install_k3s_master() {
    echo "Installing 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
    @@ -62,7 +80,7 @@ install_k3s_master() {
    install_k3s_agent() {
    local master_ip="$1"
    local token="$2"
    echo "Installing k3s agent..."
    echo "Installing K3s agent..."
    curl -sfL https://get.k3s.io | K3S_URL="https://$master_ip:6443" K3S_TOKEN="$token" sh -
    }

    @@ -75,7 +93,7 @@ wait_nodes_ready() {
    # INSTALL K3S
    # -------------------------
    if [ -f /etc/rancher/k3s/k3s.yaml ]; then
    echo "k3s already installed."
    echo "K3s already installed."
    else
    if [ ! -f $K3S_MASTER_TOKEN_FILE ]; then
    install_k3s_master
    @@ -95,33 +113,25 @@ for ns in tikv surrealdb coolify kubero cert-manager monitoring; do
    done

    # -------------------------
    # INSTALL INGRESS-NGINX
    # INSTALL KUBERO CLI
    # -------------------------
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/k3s/deploy.yaml
    if ! command -v kubero >/dev/null 2>&1; then
    echo "Installing Kubero CLI..."
    curl -fsSL https://get.kubero.dev | bash
    fi

    # -------------------------
    # INSTALL CERT-MANAGER
    # INSTALL KUBERO COMPONENTS
    # -------------------------
    kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.crds.yaml
    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
    kubectl wait --for=condition=Ready pods --all -n cert-manager --timeout=180s

    cat <<EOF | kubectl apply -f -
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
    name: letsencrypt-sslip
    spec:
    acme:
    email: $EMAIL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
    name: letsencrypt-sslip-key
    solvers:
    - http01:
    ingress:
    class: nginx
    EOF
    echo "Installing Kubero components via Kubero CLI..."
    kubero install -c kubernetes --domain $DOMAIN
    kubero install -c olm --domain $DOMAIN
    kubero install -c ingress --domain $DOMAIN
    kubero install -c metrics --domain $DOMAIN
    kubero install -c certmanager --domain $DOMAIN
    kubero install -c kubero-operator --domain $DOMAIN
    kubero install -c monitoring --domain $DOMAIN
    kubero install -c kubero-ui --domain $DOMAIN --user $ADMIN_USER --user-password $ADMIN_PASS

    # -------------------------
    # DEPLOY PD (Placement Driver)
    @@ -357,7 +367,6 @@ EOF
    kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/raw/main/bundle.yaml
    kubectl wait --for=condition=Ready pods --all -n monitoring --timeout=180s

    # Simple Prometheus instance
    cat <<EOF | kubectl apply -f -
    apiVersion: monitoring.coreos.com/v1
    kind: Prometheus
    @@ -372,60 +381,14 @@ spec:
    EOF

    # -------------------------
    # TLS-ENABLED INGRESS
    # WAIT FOR ALL PODS READY
    # -------------------------
    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
    name: multi-platform-ingress
    namespace: coolify
    annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-sslip"
    spec:
    tls:
    - hosts:
    - coolify.$SSLIP_DOMAIN
    - surrealdb.$SSLIP_DOMAIN
    - kubero.$SSLIP_DOMAIN
    secretName: multi-platform-tls
    rules:
    - host: coolify.$SSLIP_DOMAIN
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: coolify
    port:
    number: 80
    - host: surrealdb.$SSLIP_DOMAIN
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: surrealdb
    port:
    number: 8000
    - host: kubero.$SSLIP_DOMAIN
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: kubero
    port:
    number: 8080
    EOF
    echo "Waiting for all pods in all namespaces..."
    kubectl wait --for=condition=Ready pods --all --timeout=300s

    echo "✅ Full production-ready cluster installed!"
    echo "Coolify: https://coolify.$SSLIP_DOMAIN"
    echo "SurrealDB: https://surrealdb.$SSLIP_DOMAIN"
    echo "Kubero Dashboard: https://kubero.$SSLIP_DOMAIN"
    echo "Grafana: https://grafana.$SSLIP_DOMAIN"
    echo "Coolify: https://coolify.$DOMAIN"
    echo "SurrealDB: https://surrealdb.$DOMAIN"
    echo "Kubero Dashboard: https://kubero.$DOMAIN"
    echo "Grafana: https://grafana.$DOMAIN"
    echo "🎉 Multi-node ready — copy k3s_token.txt & k3s_master_ip.txt to new nodes to join agents."
  7. Jordan-Hall revised this gist Oct 14, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion install.sh
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ set -e
    # -------------------------
    HOST_IP=$(hostname -I | awk '{print $1}')
    SSLIP_DOMAIN="$HOST_IP.sslip.io"
    EMAIL="your-email@example.com" # replace with real email
    EMAIL="jordan@libertyware.co.uk" # replace with real email

    SURREALDB_REPLICAS=3
    COOLIFY_REPLICAS=1
  8. Jordan-Hall created this gist Oct 14, 2025.
    431 changes: 431 additions & 0 deletions install.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,431 @@
    #!/bin/bash
    set -e

    # -------------------------
    # CONFIGURATION
    # -------------------------
    HOST_IP=$(hostname -I | awk '{print $1}')
    SSLIP_DOMAIN="$HOST_IP.sslip.io"
    EMAIL="your-email@example.com" # replace with real email

    SURREALDB_REPLICAS=3
    COOLIFY_REPLICAS=1
    KUBERO_REPLICAS=1
    PD_REPLICAS=3
    TIKV_REPLICAS=3
    STORAGE_PATH="/mnt/tikv-data"

    K3S_MASTER_TOKEN_FILE="/root/k3s_token.txt"
    K3S_MASTER_IP_FILE="/root/k3s_master_ip.txt"

    # -------------------------
    # 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

    # Setup 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 INGRESS-NGINX
    # -------------------------
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/k3s/deploy.yaml

    # -------------------------
    # INSTALL CERT-MANAGER
    # -------------------------
    kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.crds.yaml
    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
    kubectl wait --for=condition=Ready pods --all -n cert-manager --timeout=180s

    cat <<EOF | kubectl apply -f -
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
    name: letsencrypt-sslip
    spec:
    acme:
    email: $EMAIL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
    name: letsencrypt-sslip-key
    solvers:
    - http01:
    ingress:
    class: nginx
    EOF

    # -------------------------
    # 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
    selector:
    matchLabels:
    app: pd
    template:
    metadata:
    labels:
    app: pd
    spec:
    containers:
    - name: pd
    image: pingcap/pd:latest
    args:
    - --name=pd
    - --data-dir=/pd/data
    - --client-urls=http://0.0.0.0:2379
    - --peer-urls=http://0.0.0.0: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
    ports:
    - containerPort: 2379
    - containerPort: 2380
    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:
    containers:
    - name: tikv
    image: pingcap/tikv:latest
    args:
    - --pd=pd.tikv.svc.cluster.local:2379
    - --addr=0.0.0.0:20160
    - --data-dir=/tikv/data
    ports:
    - containerPort: 20160
    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:
    containers:
    - name: surrealdb
    image: surrealdb/surrealdb:latest
    args:
    - "--log"
    - "info"
    - "--bind"
    - "0.0.0.0:8000"
    - "--store"
    - "tikv://pd.tikv.svc.cluster.local:2379"
    ports:
    - containerPort: 8000
    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

    # -------------------------
    # DEPLOY KUBERO (DASHBOARD + OPTIONAL MODULES)
    # -------------------------
    cat <<EOF | kubectl apply -f -
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: kubero
    namespace: kubero
    spec:
    replicas: $KUBERO_REPLICAS
    selector:
    matchLabels:
    app: kubero
    template:
    metadata:
    labels:
    app: kubero
    spec:
    containers:
    - name: kubero
    image: kubero-dev/kubero:latest
    ports:
    - containerPort: 8080
    - name: kubero-dashboard
    image: kubero-dev/kubero-dashboard:latest
    ports:
    - containerPort: 9090
    EOF

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Service
    metadata:
    name: kubero
    namespace: kubero
    spec:
    selector:
    app: kubero
    ports:
    - port: 8080
    targetPort: 8080
    EOF

    # -------------------------
    # DEPLOY PROMETHEUS + GRAFANA
    # -------------------------
    kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/raw/main/bundle.yaml
    kubectl wait --for=condition=Ready pods --all -n monitoring --timeout=180s

    # Simple Prometheus instance
    cat <<EOF | kubectl apply -f -
    apiVersion: monitoring.coreos.com/v1
    kind: Prometheus
    metadata:
    name: tikv-prometheus
    namespace: monitoring
    spec:
    serviceAccountName: prometheus
    resources:
    requests:
    memory: 400Mi
    EOF

    # -------------------------
    # TLS-ENABLED INGRESS
    # -------------------------
    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
    name: multi-platform-ingress
    namespace: coolify
    annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-sslip"
    spec:
    tls:
    - hosts:
    - coolify.$SSLIP_DOMAIN
    - surrealdb.$SSLIP_DOMAIN
    - kubero.$SSLIP_DOMAIN
    secretName: multi-platform-tls
    rules:
    - host: coolify.$SSLIP_DOMAIN
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: coolify
    port:
    number: 80
    - host: surrealdb.$SSLIP_DOMAIN
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: surrealdb
    port:
    number: 8000
    - host: kubero.$SSLIP_DOMAIN
    http:
    paths:
    - path: /
    pathType: Prefix
    backend:
    service:
    name: kubero
    port:
    number: 8080
    EOF

    echo "✅ Full production-ready cluster installed!"
    echo "Coolify: https://coolify.$SSLIP_DOMAIN"
    echo "SurrealDB: https://surrealdb.$SSLIP_DOMAIN"
    echo "Kubero Dashboard: https://kubero.$SSLIP_DOMAIN"
    echo "Grafana: https://grafana.$SSLIP_DOMAIN"
    echo "🎉 Multi-node ready — copy k3s_token.txt & k3s_master_ip.txt to new nodes to join agents."