diff --git a/setup/setup-worker.sh b/setup/setup-worker.sh new file mode 100644 index 0000000..0127382 --- /dev/null +++ b/setup/setup-worker.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# curl -sfL https://get.quickstack.dev/setup-worker.sh | K3S_URL=:6443> JOIN_TOKEN= sh - + +if [ -z "${K3S_URL}" ]; then + echo "Error: Missing parameter 'K3S_URL'." + echo "Example K3S_URL https://:6443" + exit 1 +fi + +if [ -z "${JOIN_TOKEN}" ]; then + echo "Error: Missing parameter 'JOIN_TOKEN'." + exit 1 +fi + +k3sUrl="$1" +joinToken="$2" + +wait_until_all_pods_running() { + + # Waits another 5 seconds to make sure all pods are registered for the first time. + sleep 5 + + while true; do + OUTPUT=$(sudo k3s kubectl get pods -A --no-headers 2>&1) + + # Checks if there are no resources found --> Kubernetes ist still starting up + if echo "$OUTPUT" | grep -q "No resources found"; then + echo "Kubernetes is still starting up..." + else + # Extracts the STATUS column from the kubectl output and filters out the values "Running" and "Completed". + STATUS=$(echo "$OUTPUT" | awk '{print $4}' | grep -vE '^(Running|Completed)$') + + # If the STATUS variable is empty, all pods are running and the loop can be exited. + if [ -z "$STATUS" ]; then + echo "Pods started successfully." + break + else + echo "Waiting for all pods to come online..." + fi + fi + + # Waits for X seconds before checking the pod status again. + sleep 10 + done + + # Waits another 5 seconds to make sure all pods are ready. + sleep 5 + + sudo kubectl get node + sudo kubectl get pods -A +} + +# install nfs-common +sudo apt-get update +sudo apt-get install nfs-common -y + +# Installation of k3s +curl -sfL https://get.k3s.io | K3S_URL=${K3S_URL} K3S_TOKEN=${JOIN_TOKEN} sh - + +# Check for Ready node, takes ~30 seconds +sudo k3s kubectl get node + +echo "Waiting for Kubernetes to start..." +wait_until_all_pods_running \ No newline at end of file diff --git a/setup.sh b/setup/setup.sh similarity index 94% rename from setup.sh rename to setup/setup.sh index 411bb1c..36c36f1 100644 --- a/setup.sh +++ b/setup/setup.sh @@ -61,6 +61,8 @@ echo "Waiting for Cert-Manager to start..." wait_until_all_pods_running sudo kubectl -n cert-manager get pod +joinTokenForOtherNodes=$(sudo cat /var/lib/rancher/k3s/server/node-token) + # deploy QuickStack cat < quickstack-setup-job.yaml apiVersion: v1 @@ -103,6 +105,8 @@ spec: env: - name: START_MODE value: "setup" + - name: K3S_JOIN_TOKEN + value: "$joinTokenForOtherNodes" imagePullPolicy: Always restartPolicy: Never backoffLimit: 0 @@ -113,8 +117,7 @@ wait_until_all_pods_running sudo kubectl logs -f job/quickstack-setup-job -n quickstack # evaluate url to add node to cluster -joinTokenForOtherNodes=$(sudo cat /var/lib/rancher/k3s/server/node-token) echo "To add a worker node to the cluster, run the following command on the worker node:" echo "------------------------------------------------------------" -echo "curl -sfL https://get.k3s.io | K3S_URL=https://:6443 K3S_TOKEN=$joinTokenForOtherNodes sh -" +echo "curl -sfL https://get.quickstack.dev/setup-worker.sh | K3S_URL=https://:6443 JOIN_TOKEN=$joinTokenForOtherNodes sh -" echo "------------------------------------------------------------" \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 64ceffa..545cfdf 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -32,7 +32,7 @@ export default function RootLayout({
-
+
}> {children} diff --git a/src/app/settings/cluster/add-cluster-node-dialog.tsx b/src/app/settings/cluster/add-cluster-node-dialog.tsx new file mode 100644 index 0000000..3410c6f --- /dev/null +++ b/src/app/settings/cluster/add-cluster-node-dialog.tsx @@ -0,0 +1,53 @@ +'use client' + +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button" +import { Code } from "@/components/custom/code" + + + +export default function AddClusterNodeDialog({ children, clusterJoinToken }: { children: React.ReactNode; clusterJoinToken?: string }) { + + const [isOpen, setIsOpen] = useState(false); + const [command, setCommand] = useState(``); + + useEffect(() => { + setCommand(`curl -sfL https://get.quickstack.dev/setup-worker.sh | K3S_URL=https://MASTER-IP:6443 JOIN_TOKEN=${clusterJoinToken ?? ''} sh -`); + }, [clusterJoinToken]); + + return ( + <> +
setIsOpen(true)}> + {children} +
+ setIsOpen(false)}> + + + Add Cluster Node + + Add a new quickstack cluster node by running the following command on the node you want to add. + + + + {command} + +

Note:

+
    +
  • Replace MASTER-IP with the IP address or hostname of the master node.
  • +
  • Ensure the node you want to add has access to the internet and the master node's IP address.
  • +
  • Run the command on the node you want to add to the cluster.
  • +
  • If the token is invalid or not shown in command above, run sudo cat /var/lib/rancher/k3s/server/node-token on your master node to retrieve a new one.
  • +
+
+ + + +
+
+ + ) + + + +} \ No newline at end of file diff --git a/src/app/settings/cluster/page.tsx b/src/app/settings/cluster/page.tsx index 3d4ae52..b60d85f 100644 --- a/src/app/settings/cluster/page.tsx +++ b/src/app/settings/cluster/page.tsx @@ -4,16 +4,23 @@ import { getAuthUserSession } from "@/server/utils/action-wrapper.utils"; import PageTitle from "@/components/custom/page-title"; import clusterService from "@/server/services/node.service"; import NodeInfo from "./nodeInfo"; +import AddClusterNodeDialog from "./add-cluster-node-dialog"; +import { Button } from "@/components/ui/button"; +import paramService, { ParamService } from "@/server/services/param.service"; export default async function ClusterInfoPage() { const session = await getAuthUserSession(); const nodeInfo = await clusterService.getNodeInfo(); + const clusterJoinToken = await paramService.getString(ParamService.K3S_JOIN_TOKEN); return (
- + + +
diff --git a/src/components/custom/code.tsx b/src/components/custom/code.tsx index 9b84f68..f9a0f5b 100644 --- a/src/components/custom/code.tsx +++ b/src/components/custom/code.tsx @@ -1,10 +1,11 @@ 'use client' +import { ReactElement } from "react"; import { toast } from "sonner"; -export function Code({ children, copieable = true, copieableValue }: { children: string | null | undefined, copieable?: boolean, copieableValue?: string }) { +export function Code({ children, copieable = true, copieableValue, className }: { children: string | null | undefined, copieable?: boolean, copieableValue?: string, className?: string }) { return (children && - { if (!copieable) return; navigator.clipboard.writeText(copieableValue || children || ''); diff --git a/src/server.ts b/src/server.ts index c18cdb0..128e028 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,7 +4,7 @@ import next from 'next' import socketIoServer from './socket-io.server' import quickStackService from './server/services/qs.service' import { CommandExecutorUtils } from './server/utils/command-executor.utils' -import k3s from './server/adapter/kubernetes-api.adapter' +import paramService, { ParamService } from './server/services/param.service' // Source: https://nextjs.org/docs/app/building-your-application/configuring/custom-server @@ -28,6 +28,14 @@ async function initializeNextJs() { // update database console.log('Running db migration...'); await CommandExecutorUtils.runCommand('npx prisma migrate deploy'); + + if (process.env.K3S_JOIN_TOKEN && process.env.K3S_JOIN_TOKEN.trim()) { + console.log('Saving K3S_JOIN_TOKEN to database...'); + await paramService.save({ + name: ParamService.K3S_JOIN_TOKEN, + value: process.env.K3S_JOIN_TOKEN + }) + } } const app = next({ dev }) diff --git a/src/server/services/node.service.ts b/src/server/services/node.service.ts index 16d0f22..4b087f3 100644 --- a/src/server/services/node.service.ts +++ b/src/server/services/node.service.ts @@ -37,7 +37,6 @@ class ClusterService { revalidate: 10, tags: [Tags.nodeInfos()] })(); - } async setNodeStatus(nodeName: string, schedulable: boolean) { diff --git a/src/server/services/param.service.ts b/src/server/services/param.service.ts index e4b9419..5f07434 100644 --- a/src/server/services/param.service.ts +++ b/src/server/services/param.service.ts @@ -8,6 +8,7 @@ export class ParamService { static readonly QS_SERVER_HOSTNAME = 'qsServerHostname'; static readonly DISABLE_NODEPORT_ACCESS = 'disableNodePortAccess'; static readonly LETS_ENCRYPT_MAIL = 'letsEncryptMail'; + static readonly K3S_JOIN_TOKEN = 'k3sJoinToken'; async get(name: string) { return await unstable_cache(async (name: string) => await dataAccess.client.parameter.findFirstOrThrow({ diff --git a/src/server/services/qs.service.ts b/src/server/services/qs.service.ts index 18b0ec0..b4b7fdf 100644 --- a/src/server/services/qs.service.ts +++ b/src/server/services/qs.service.ts @@ -120,8 +120,8 @@ class QuickStackService { this.CLUSTER_ISSUER_NAME, // name of the custom resource clusterIssuerBody, // object manifest undefined, undefined, undefined, { - headers: { 'Content-Type': 'application/merge-patch+json' }, - } + headers: { 'Content-Type': 'application/merge-patch+json' }, + } ); } else { // create @@ -270,6 +270,10 @@ class QuickStackService { ...nextAuthHostname ? [{ name: 'NEXTAUTH_URL', value: `https://${nextAuthHostname}` + }] : [], + ...process.env.K3S_JOIN_TOKEN ? [{ + name: 'K3S_JOIN_TOKEN', + value: process.env.K3S_JOIN_TOKEN }] : [] ], volumeMounts: [{