#!/bin/bash set -o errexit # exit on any failure set -o nounset # exit on undeclared variables set -o pipefail # return value of all commands in a pipe ## set -o xtrace # command tracing shopt -s failglob # If no matches are found, an error message is printed and the command is not executed # PRESEED CONFIG: /machineConfigs/servers/preseed.cfg # ANSIBLE CONFIG: /ansible/k3s.yaml BLUE='\033[1;34m' NC='\033[0m' # https://www.how2shout.com/linux/how-to-install-and-configure-kvm-on-debian-11-bullseye-linux/ export LIBVIRT_DEFAULT_URI=qemu:///system export SWAP_PATH=${SWAP_PATH:-"/home/swapfile.img"} # HomeLab Functions function retry { local retries=$1 shift local count=0 until "$@"; do exit=$? wait=$((15 + 5 ** count)) count=$((count + 1)) if [[ ${count} -lt "${retries}" ]]; then echo "Retry ${count}/${retries} exited ${exit}, retrying in ${wait} seconds..." sleep "${wait}" else echo "Retry ${count}/${retries} exited ${exit}, no more retries left." return "${exit}" fi done return 0 } # wait_for_install_complete: Poll cluster operators for stability until timeout. # This is a workaround for OKD 5.0 where EvaluationConditionsDetected stays # Unknown/NoData on many operators in sandbox VMs, causing openshift-install # to falsely report "cluster operators not stable" even when healthy. # Only checks Progressing=False AND Degraded=False (ignores EvaluationConditionsDetected). # Usage: wait_for_install_complete [dir] # dir: optional assets directory (defaults to ".") wait_for_install_complete() { local dir="${1:-.}" # Fall back to openshift-install if KUBECONFIG/kubectl not available if [[ -z "${KUBECONFIG}" ]] || ! command -v kubectl &>/dev/null; then echo "WARNING: KUBECONFIG not set or kubectl unavailable, falling back to openshift-install" "${OKD}/openshift-install" wait-for install-complete --dir="${dir}" 2>&1 | \ grep -v "^INFO:" | grep -v "^time=" || true return 0 fi export KUBECONFIG echo -e "${BLUE}Polling cluster operators for stability (timeout: 120m)...${NC}" local timeout=$((120 * 60)) local interval=60 local elapsed=0 while (( elapsed < timeout )); do local result result=$(kubectl get co --no-headers 2>/dev/null | awk ' BEGIN { stable=0; unstable=0 } { progressing=$4 degraded=$5 if (progressing == "False" && degraded == "False") stable++ else unstable++ } END { if (stable > 0 && unstable == 0) print "STABLE" else if (stable > 0) printf "WAITING:%d\n", unstable else print "NO-OPERATORS" } ') if [[ "${result}" == "STABLE" ]]; then echo "All cluster operators are stable." return 0 fi if [[ "${result}" == WAITING:* ]]; then local count="${result#WAITING:}" local minutes=$((elapsed / 60)) echo " [${minutes}m/$((timeout / 60))m] ${count} operator(s) still not stable..." else echo " [$((elapsed / 60))m/$((timeout / 60))m] Waiting for operators..." fi sleep "${interval}" elapsed=$((elapsed + interval)) done # Final check before failing result=$(kubectl get co --no-headers 2>/dev/null | awk ' BEGIN { c=0 } { if ($4 != "False" || $5 != "False") c++ } END { print c } ') if [[ "${result}" -eq 0 ]] 2>/dev/null; then echo "Cluster operators stabilized (final check passed)." return 0 fi echo "ERROR: Timeout waiting for cluster operators. ${result:-unknown} operator(s) still not stable." return 1 } ansible() { ansible-playbook -i ansible/inventory --ask-become-pass ansible/servers.yaml --ask-pass \ -e 'ansible_python_interpreter=/usr/bin/python3' } stateful_workload_stop() { # kubectl patch cronjobs -n netbox netbox-housekeeping -p '{"spec" : {"suspend" : true }}' kubectl patch cronjobs -n immich immich-cron -p '{"spec" : {"suspend" : true }}' kubectl patch cronjobs -n nextcloud nextcloud-preview -p '{"spec" : {"suspend" : true }}' kubectl patch cronjobs -n nextcloud nextcloud-rsync -p '{"spec" : {"suspend" : true }}' kubectl patch cronjobs -n nextcloud nextcloud-cron -p '{"spec" : {"suspend" : true }}' kubectl scale --replicas=0 -n openshift-monitoring deployment/cluster-monitoring-operator kubectl scale --replicas=0 -n openshift-monitoring deployment/prometheus-operator kubectl scale --replicas=0 -n openshift-monitoring statefulset/alertmanager-main kubectl scale --replicas=0 -n openshift-monitoring statefulset/prometheus-k8s kubectl scale --replicas=0 -n openshift-user-workload-monitoring deployment/prometheus-operator kubectl scale --replicas=0 -n openshift-user-workload-monitoring statefulset/alertmanager-user-workload kubectl scale --replicas=0 -n openshift-user-workload-monitoring statefulset/prometheus-user-workload kubectl scale --replicas=0 -n openshift-user-workload-monitoring statefulset/thanos-ruler-user-workload kubectl scale --replicas=0 -n bitwarden statefulset/bitwarden kubectl scale --replicas=0 -n gitea deployment/gitea kubectl scale --replicas=0 -n grafana deployment/grafana kubectl scale --replicas=0 -n heimdall statefulset/heimdall kubectl scale --replicas=0 -n homeassistant statefulset/homeassistant # kubectl scale --replicas=0 -n loki statefulset/loki kubectl scale --replicas=0 -n nextcloud deployment/nextcloud kubectl scale --replicas=0 -n immich statefulset/immich kubectl scale --replicas=0 -n prometheus statefulset/prometheus kubectl scale --replicas=0 -n prometheus statefulset/thanos-store-gateway kubectl scale --replicas=0 -n prometheus statefulset/prometheus-truenas kubectl scale --replicas=0 -n prometheus statefulset/thanos-truenas-store-gateway kubectl scale --replicas=0 -n uptime-kuma statefulset/uptime-kuma kubectl scale --replicas=0 -n vault statefulset/vault kubectl scale --replicas=0 -n pihole statefulset/pihole kubectl scale --replicas=0 -n technitium-dns statefulset/technitium-dns kubectl scale --replicas=0 -n external-dns deployment/external-dns-microshift kubectl scale --replicas=0 -n external-dns deployment/external-dns-microshift-vlan3 kubectl scale --replicas=0 -n external-dns deployment/external-dns-okd kubectl scale --replicas=0 -n external-dns deployment/external-dns-okd-vlan3 kubectl scale --replicas=0 -n ntp deployment/ntp-rootless kubectl scale --replicas=0 -n cloudflare-ddns deployment/cloudflare-ddns kubectl scale --replicas=0 -n quay deployment/quay-operator-tng kubectl scale --replicas=0 -n quay deployment/quay-quay-app kubectl scale --replicas=0 -n quay deployment/quay-clair-app kubectl scale --replicas=0 -n quay deployment/quay-quay-mirror # kubectl scale --replicas=0 -n cockroach-operator-system deployments/cockroach-operator-manager # kubectl scale --replicas=0 -n zitadel statefulset/crdb kubectl scale --replicas=0 -n zitadel deployment/zitadel # kubectl scale --replicas=0 -n mongodb-operator deployments/mongodb-kubernetes-operator # kubectl scale --replicas=0 -n unifi-network-application statefulset/unifi-network-application # kubectl scale --replicas=0 -n unifi-network-application statefulset/mongo-unifi-network-application kubectl annotate cluster -n awx awx --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n quay clair --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n finance-tracker finance --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n gitea gitea --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n grafana grafana --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n homeassistant homeassistant --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n immich immich --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n netbox netbox --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n nextcloud-fpm nextcloud --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n quay quay --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n analytics-for-spotify spotify --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n analytics-for-spotify-ext spotify --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n stackrox stackrox --overwrite cnpg.io/hibernation=on kubectl annotate cluster -n zitadel zitadel --overwrite cnpg.io/hibernation=on kubectl scale --replicas=0 -n argocd deployment/argocd-operator-controller-manager kubectl scale --replicas=0 -n argocd statefulset/argocd-application-controller kubectl scale --replicas=0 -n argocd deployment/argocd-notifications-controller kubectl scale --replicas=0 -n argocd deployment/argocd-repo-server kubectl scale --replicas=0 -n argocd deployment/argocd-dex-server kubectl scale --replicas=0 -n argocd deployment/argocd-server kubectl scale --replicas=0 -n argocd-apps statefulset/argocd-apps-application-controller kubectl scale --replicas=0 -n argocd-apps deployment/argocd-apps-dex-server kubectl scale --replicas=0 -n argocd-apps deployment/argocd-apps-repo-server kubectl scale --replicas=0 -n argocd-apps deployment/argocd-apps-server kubectl scale --replicas=0 -n stackrox deployment/central kubectl scale --replicas=0 -n stackrox deployment/scanner-db kubectl scale --replicas=0 -n dragonfly-operator-system deployment/dragonfly-operator-controller-manager kubectl scale --replicas=0 -n nextcloud statefulset/nextcloud-dragonfly kubectl scale --replicas=0 -n gitea statefulset/gitea-dragonfly kubectl scale --replicas=0 -n quay statefulset/quay-dragonfly # kubectl scale --replicas=0 -n netbox statefulset/netbox-dragonfly kubectl scale --replicas=0 -n loki-operator deployment/loki-operator-controller-manager kubectl scale --replicas=0 -n openshift-netobserv-loki statefulset/netobserv-compactor kubectl scale --replicas=0 -n openshift-netobserv-loki statefulset/netobserv-index-gateway kubectl scale --replicas=0 -n openshift-netobserv-loki statefulset/netobserv-ingester kubectl scale --replicas=0 -n openshift-netobserv-loki deployment/netobserv-distributor kubectl scale --replicas=0 -n openshift-netobserv-loki deployment/netobserv-gateway kubectl scale --replicas=0 -n openshift-netobserv-loki deployment/netobserv-querier kubectl scale --replicas=0 -n openshift-netobserv-loki deployment/netobserv-query-frontend kubectl scale --replicas=0 -n openshift-logging statefulset/logging-loki-compactor kubectl scale --replicas=0 -n openshift-logging statefulset/logging-loki-index-gateway kubectl scale --replicas=0 -n openshift-logging statefulset/logging-loki-ingester kubectl scale --replicas=0 -n openshift-logging deployment/logging-loki-distributor kubectl scale --replicas=0 -n openshift-logging deployment/logging-loki-gateway kubectl scale --replicas=0 -n openshift-logging deployment/logging-loki-querier kubectl scale --replicas=0 -n openshift-logging deployment/logging-loki-query-frontend # kubectl scale --replicas=0 -n awx deployment/awx-operator-controller-manager # kubectl scale --replicas=0 -n awx deployment/awx-task # kubectl scale --replicas=0 -n awx deployment/awx-web # kubectl scale --replicas=0 -n netbox deployment/netbox # kubectl scale --replicas=0 -n netbox deployment/netbox-worker kubectl delete jobs -A --all kubectl delete pipelineruns -A --all } stateful_workload_start_pre() { # kubectl scale --replicas=1 -n cockroach-operator-system deployments/cockroach-operator-manager # kubectl scale --replicas=3 -n zitadel statefulset/crdb # kubectl scale --replicas=1 -n mongodb-operator deployments/mongodb-kubernetes-operator # kubectl scale --replicas=3 -n unifi-network-application statefulset/mongo-unifi-network-application # kubectl scale --replicas=1 -n loki statefulset/loki kubectl scale --replicas=1 -n prometheus statefulset/prometheus kubectl scale --replicas=1 -n prometheus statefulset/thanos-store-gateway kubectl scale --replicas=1 -n quay deployment/quay-operator-tng kubectl annotate cluster -n awx awx --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n quay clair --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n finance-tracker finance --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n gitea gitea --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n grafana grafana --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n homeassistant homeassistant --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n immich immich --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n netbox netbox --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n nextcloud-fpm nextcloud --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n quay quay --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n analytics-for-spotify spotify --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n analytics-for-spotify-ext spotify --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n stackrox stackrox --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n zitadel zitadel --overwrite cnpg.io/hibernation=off kubectl scale --replicas=1 -n pihole statefulset/pihole kubectl scale --replicas=1 -n technitium-dns statefulset/technitium-dns kubectl scale --replicas=2 -n ntp deployment/ntp-rootless } stateful_workload_start() { kubectl scale --replicas=1 -n pihole statefulset/pihole kubectl scale --replicas=1 -n technitium-dns statefulset/technitium-dns kubectl scale --replicas=2 -n external-dns deployment/external-dns-microshift kubectl scale --replicas=2 -n external-dns deployment/external-dns-microshift-vlan3 kubectl scale --replicas=2 -n external-dns deployment/external-dns-okd kubectl scale --replicas=2 -n external-dns deployment/external-dns-okd-vlan3 kubectl scale --replicas=2 -n cloudflare-ddns deployment/cloudflare-ddns # kubectl patch cronjobs -n netbox netbox-housekeeping -p '{"spec" : {"suspend" : false }}' kubectl patch cronjobs -n immich immich-cron -p '{"spec" : {"suspend" : false }}' kubectl patch cronjobs -n nextcloud nextcloud-preview -p '{"spec" : {"suspend" : false }}' kubectl patch cronjobs -n nextcloud nextcloud-rsync -p '{"spec" : {"suspend" : false }}' kubectl patch cronjobs -n nextcloud nextcloud-cron -p '{"spec" : {"suspend" : false }}' kubectl scale --replicas=1 -n openshift-monitoring deployment/cluster-monitoring-operator kubectl scale --replicas=1 -n dragonfly-operator-system deployment/dragonfly-operator-controller-manager kubectl scale --replicas=2 -n nextcloud statefulset/nextcloud-dragonfly kubectl scale --replicas=2 -n gitea statefulset/gitea-dragonfly kubectl scale --replicas=2 -n quay statefulset/quay-dragonfly # kubectl scale --replicas=2 -n netbox statefulset/netbox-dragonfly # kubectl scale --replicas=1 -n cockroach-operator-system deployments/cockroach-operator-manager # kubectl scale --replicas=3 -n zitadel statefulset/crdb kubectl scale --replicas=2 -n zitadel deployment/zitadel # kubectl scale --replicas=1 -n mongodb-operator deployments/mongodb-kubernetes-operator # kubectl scale --replicas=3 -n unifi-network-application statefulset/mongo-unifi-network-application # kubectl scale --replicas=1 -n unifi-network-application statefulset/unifi-network-application kubectl scale --replicas=1 -n bitwarden statefulset/bitwarden kubectl scale --replicas=2 -n gitea deployment/gitea kubectl scale --replicas=2 -n grafana deployment/grafana kubectl scale --replicas=1 -n heimdall statefulset/heimdall kubectl scale --replicas=1 -n homeassistant statefulset/homeassistant kubectl scale --replicas=1 -n loki statefulset/loki kubectl scale --replicas=2 -n nextcloud deployment/nextcloud kubectl scale --replicas=1 -n immich statefulset/immich kubectl scale --replicas=1 -n prometheus statefulset/prometheus kubectl scale --replicas=1 -n prometheus statefulset/thanos-store-gateway kubectl scale --replicas=1 -n prometheus statefulset/prometheus-truenas kubectl scale --replicas=1 -n prometheus statefulset/thanos-truenas-store-gateway kubectl scale --replicas=1 -n uptime-kuma statefulset/uptime-kuma kubectl scale --replicas=3 -n vault statefulset/vault kubectl scale --replicas=1 -n quay deployment/quay-operator-tng kubectl scale --replicas=2 -n quay deployment/quay-quay-app kubectl scale --replicas=2 -n quay deployment/quay-clair-app kubectl scale --replicas=2 -n quay deployment/quay-quay-mirror kubectl annotate cluster -n awx awx --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n quay clair --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n finance-tracker finance --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n gitea gitea --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n grafana grafana --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n homeassistant homeassistant --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n immich immich --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n netbox netbox --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n nextcloud-fpm nextcloud --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n quay quay --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n analytics-for-spotify spotify --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n analytics-for-spotify-ext spotify --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n stackrox stackrox --overwrite cnpg.io/hibernation=off kubectl annotate cluster -n zitadel zitadel --overwrite cnpg.io/hibernation=off kubectl scale --replicas=1 -n argocd deployment/argocd-operator-controller-manager kubectl scale --replicas=1 -n argocd statefulset/argocd-application-controller kubectl scale --replicas=1 -n argocd statefulset/argocd-application-controller kubectl scale --replicas=1 -n argocd deployment/argocd-repo-server kubectl scale --replicas=1 -n argocd deployment/argocd-dex-server kubectl scale --replicas=1 -n argocd deployment/argocd-server kubectl scale --replicas=1 -n argocd-apps statefulset/argocd-apps-application-controller kubectl scale --replicas=1 -n argocd-apps deployment/argocd-apps-dex-server kubectl scale --replicas=1 -n argocd-apps deployment/argocd-apps-repo-server kubectl scale --replicas=1 -n argocd-apps deployment/argocd-apps-server kubectl scale --replicas=1 -n stackrox deployment/central kubectl scale --replicas=1 -n stackrox deployment/scanner-db kubectl scale --replicas=1 -n loki-operator deployment/loki-operator-controller-manager # kubectl scale --replicas=1 -n awx deployment/awx-operator-controller-manager # kubectl scale --replicas=1 -n awx deployment/netbox # kubectl scale --replicas=1 -n awx deployment/netbox-worker # echo -e "\nkubectl exec -it vault-0 -n vault -- vault operator unseal --tls-skip-verify" } kvm-infra() { ssh 10.0.0.110 virt-install \ --noautoconsole \ --graphics vnc \ --name=k3s-server-2 \ --os-variant=debian10 \ --vcpus sockets=1,cores=1,threads=2 \ --ram=1792 \ --disk "/mnt/storage/vm/k3s-server-2".img,,format=raw,size=25 \ --network bridge=br0,mac="10:00:00:00:01:02" \ --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ \ --extra-args="\ auto=true priority=critical vga=normal hostname=k3s-server-2 \ url=http://10.0.0.5:7071/preseed.cfg" virt-install \ --noautoconsole \ --graphics vnc \ --name=k3s-worker-1 \ --os-variant=debian10 \ --vcpus sockets=1,cores=2,threads=2 \ --ram=10240 \ --disk "/mnt/storage/vm/k3s-worker-1".img,,format=raw,size=125 \ --network bridge=br0,mac="10:00:00:00:01:11" \ --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ \ --extra-args="\ auto=true priority=critical vga=normal hostname=k3s-worker-1 \ url=http://10.0.0.5:7071/preseed.cfg" } kvm() { ssh 10.101.1.6 export LIBVIRT_DEFAULT_URI=qemu:///system virt-install \ --noautoconsole \ --graphics vnc \ --name=k3s-server-3 \ --os-variant=debian10 \ --vcpus sockets=1,cores=1,threads=2 \ --ram=1792 \ --disk "/mnt/storage/vm/k3s-server-3".img,,format=raw,size=25 \ --network bridge=br0,mac="10:00:00:00:01:03" \ --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ \ --extra-args="\ auto=true priority=critical vga=normal hostname=k3s-server-3 \ url=http://10.0.0.5:7071/preseed.cfg" virt-install \ --noautoconsole \ --graphics vnc \ --name=k3s-worker-2 \ --os-variant=debian10 \ --vcpus sockets=1,cores=2,threads=2 \ --ram=4352 \ --disk "/mnt/storage/vm/k3s-worker-2".img,,format=raw,size=125 \ --network bridge=br0,mac="10:00:00:00:01:12" \ --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ \ --extra-args="\ auto=true priority=critical vga=normal hostname=k3s-worker-2 \ url=http://10.0.0.5:7071/preseed.cfg" } label_nodes() { # Taint kubectl taint node k3s-server-1 node-role.kubernetes.io/control-plane:NoSchedule --overwrite kubectl taint node k3s-server-2 node-role.kubernetes.io/control-plane:NoSchedule --overwrite kubectl taint node k3s-server-3 node-role.kubernetes.io/control-plane:NoSchedule --overwrite kubectl taint node k3s-server-2 node-role.kubernetes.io/control-plane:NoSchedule --overwrite kubectl taint node k3s-server-3 node-role.kubernetes.io/control-plane:NoSchedule --overwrite # Role Label kubectl label node k3s-worker-1 node-role.kubernetes.io/infra=true --overwrite kubectl label node k3s-worker-1 node-role.kubernetes.io/worker=true --overwrite kubectl label node k3s-worker-2 node-role.kubernetes.io/worker=true --overwrite # Zone Labels kubectl label node k3s-server-1 topology.kubernetes.io/zone=bare-metal --overwrite kubectl label node k3s-server-2 topology.kubernetes.io/zone=infra-kvm --overwrite kubectl label node k3s-server-3 topology.kubernetes.io/zone=kvm --overwrite kubectl label node k3s-worker-1 topology.kubernetes.io/zone=infra-kvm --overwrite kubectl label node k3s-worker-2 topology.kubernetes.io/zone=kvm --overwrite # Longhorn Label kubectl label node k3s-server-1 node.longhorn.io/create-default-disk=true --overwrite kubectl label node k3s-worker-1 node.longhorn.io/create-default-disk=true --overwrite kubectl label node k3s-worker-2 node.longhorn.io/create-default-disk=true --overwrite # #Drain DaemonSets Temporarily off a Node # kubectl taint node k3s-server- node-role.kubernetes.io/control-plane:NoExecute # kubectl taint node k3s-server- node-role.kubernetes.io/control-plane:NoExecute- } k3s_setup() { # Configure PFsense / HAProxy / DHCP / DNS # Servers ansible-playbook -i ansible/inventory --ask-become-pass ansible/servers.yaml --ask-pass # Reboot All Kubernetes Nodes && For KVM Nodes Update DHCP Server With New Mac Address # Install VMs bash kvm_k3s.bash kvm-infra bash kvm_k3s.bash kvm bash kvm_k3s.bash ansible # Reboot VMs # Install K3s # First Time Setup export K3S_TOKEN="" export RESERVED="--kubelet-arg system-reserved=cpu=250m,memory=500Mi --kubelet-arg kube-reserved=cpu=250m,memory=500Mi" curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --cluster-init --tls-san 10.0.0.100 --tls-san k3s..com ${RESERVED}" \ INSTALL_K3S_CHANNEL=v1.23 sh - # Servers export K3S_TOKEN="" export RESERVED="--kubelet-arg system-reserved=cpu=250m,memory=500Mi --kubelet-arg kube-reserved=cpu=250m,memory=500Mi" curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --server https://10.0.0.100:6443 --disable traefik ${RESERVED}" \ K3S_URL=https://10.0.0.100:6443 INSTALL_K3S_CHANNEL=v1.23 sh - # Agents/Workers export K3S_TOKEN="" export RESERVED="--kubelet-arg system-reserved=cpu=250m,memory=250Mi --kubelet-arg kube-reserved=cpu=250m,memory=250Mi" curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="${RESERVED}" \ K3S_URL=https://10.0.0.100:6443 INSTALL_K3S_CHANNEL=v1.23 sh - # Label Nodes bash kvm_k3s label_nodes } k3s_image_cleanup() { export IP_LIST="5 102 103 111 112" for IP in ${IP_LIST}; do ssh -t arthur@10.0.0."${IP}" "sudo k3s crictl rmi --prune" done } # Sandbox Functions # SANDBOX HAPROXY: /machineConfigs/sandbox/var/etc/haproxy # Setup DHCP Network # virsh net-edit default # # # # # # # # virsh net-destroy default && virsh net-start default export START_IP=11 export PASSWORD=${PASSWORD:-} stop_cluster() { echo -e "\n${BLUE}Stopping Cluster:${NC}" for NODE in ${NODES}; do if virsh destroy "${PREFIX}${NODE}"; then echo "Shutting Down:${PREFIX}${NODE}" fi done } start_cluster() { echo -e "\n${BLUE}Starting Cluster:${NC}" for NODE in ${NODES}; do virsh start "${PREFIX}${NODE}" echo "Started ${PREFIX}${NODE}" done } reboot_cluster() { echo -e "\n${BLUE}Rebooting Cluster:${NC}" for NODE in ${NODES}; do virsh reboot "${PREFIX}${NODE}" echo "Started ${PREFIX}${NODE}" done IP=${START_IP} for NODE in ${NODES}; do finished="0" while [[ "${finished}" = "0" ]]; do sleep 1 if nc -z 10.10.10."${IP}" 22; then echo "${PREFIX}${NODE} is up." finished=1 fi done ((IP++)) done } delete_cluster() { stop_cluster echo -e "\n${BLUE}Deleting Cluster:${NC}" for NODE in ${NODES}; do echo "${NODE}" if virsh undefine "${PREFIX}${NODE}" --remove-all-storage; then echo "Deleting: ${PREFIX}${NODE}" fi done } ansible_sandbox() { echo -e "\n${BLUE}Running Ansible Playbooks:${NC}" if [[ -z "${PASSWORD}" ]]; then echo -n Password: read -r -s PASSWORD echo "" fi IP=${START_IP} # Create Ansible Inventory File echo "[servers]" >/tmp/inventory for NODE in ${NODES}; do echo "10.10.10.${IP}" >>/tmp/inventory ((IP++)) done # Run Playbooks On All Machines ansible-playbook -i /tmp/inventory ansible/servers.yaml --extra-vars \ "ansible_become_pass=${PASSWORD} ansible_ssh_pass=${PASSWORD}" } label_vms() { load_kubeconfig echo -e "\n${BLUE}Labeling Nodes:${NC}" for NODE in ${NODES}; do if [[ "${NODE}" =~ "master" || "${NODE}" =~ "server" ]]; then kubectl label nodes "${PREFIX}${NODE}" node-type=master --overwrite kubectl taint node "${PREFIX}${NODE}" node-role.kubernetes.io/control-plane:NoSchedule --overwrite elif [[ "${NODE}" =~ "worker" || "${NODE}" =~ "agent" ]]; then kubectl label node "${PREFIX}${NODE}" node-role.kubernetes.io/worker=true --overwrite ZONE="EVEN" if ((${NODE##*-} % 2)); then ZONE="ODD" fi kubectl label node "${PREFIX}${NODE}" topology.kubernetes.io/zone="${ZONE}" --overwrite kubectl label node "${PREFIX}${NODE}" node.longhorn.io/create-default-disk=true --overwrite fi done } load_kubeconfig() { echo -e "\n${BLUE}Loading KubeConfig:${NC}" export KUBECONFIG=/mnt/storage/vm/${PREFIX}/${PREFIX}.yaml } uninstall_k3s() { echo -e "\n\n${BLUE}Uninstall k3s:${NC}" if [[ -z "${PASSWORD}" ]]; then echo -n Password: read -r -s PASSWORD echo "" fi IP=${START_IP} for NODE in ${NODES}; do echo "${PREFIX}${NODE}" if [[ "${NODE}" =~ "master" || "${NODE}" =~ "server" ]]; then sshpass -p "${PASSWORD}" ssh -t 10.10.10."${IP}" "echo ${PASSWORD} | sudo -S /usr/local/bin/k3s-uninstall.sh" elif [[ "${NODE}" =~ "worker" || "${NODE}" =~ "agent" ]]; then sshpass -p "${PASSWORD}" ssh -t 10.10.10."${IP}" "echo ${PASSWORD} | sudo -S /usr/local/bin/k3s-agent-uninstall.sh" fi ((IP++)) done } install_k3s() { # Install & Setup k3s echo -e "\n\n${BLUE}Install & Setup k3s:${NC}" if [[ -z "${PASSWORD}" ]]; then echo -n Password: read -r -s PASSWORD echo "" fi K3S="echo ${PASSWORD} | sudo -S ls; curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC='" K3S_RESERVED="--kubelet-arg system-reserved=cpu=125m,memory=250Mi --kubelet-arg kube-reserved=cpu=125m,memory=250Mi'" K3S_CONFIG="K3S_TOKEN=${K3S_TOKEN} INSTALL_K3S_CHANNEL=v1.23 sh -" INSTALL="${K3S} server --cluster-init --disable traefik ${K3S_RESERVED} ${K3S_CONFIG}" SERVER=" ${K3S} server --server https://10.10.10.1:6443 --disable traefik ${K3S_RESERVED} ${K3S_CONFIG}" WORKER=" ${K3S} ${K3S_RESERVED} K3S_URL=https://10.10.10.1:6443 ${K3S_CONFIG}" IP=${START_IP} for NODE in ${NODES}; do KUBE=0 if [[ "${NODE}" =~ "master" || "${NODE}" =~ "server" ]]; then if [[ ${IP} -eq ${START_IP} ]]; then CONFIG=${INSTALL} KUBE=1 else CONFIG=${SERVER} fi elif [[ "${NODE}" =~ "worker" || "${NODE}" =~ "agent" ]]; then CONFIG=${WORKER} fi echo "${PREFIX}${NODE}" sshpass -p "${PASSWORD}" ssh -t 10.10.10."${IP}" "${CONFIG}" if [[ ${KUBE} -eq 1 ]]; then CMD="echo ${PASSWORD} | sudo -S cp /etc/rancher/k3s/k3s.yaml /tmp; sudo chmod 777 /tmp/k3s.yaml" sshpass -p "${PASSWORD}" ssh -t 10.10.10."${START_IP}" "${CMD}" sshpass -p "${PASSWORD}" scp 10.10.10."${START_IP}":/tmp/k3s.yaml "/mnt/storage/vm/${PREFIX}/${PREFIX}.yaml" sed -i "s,127.0.0.1,10.10.10.1,g" "/mnt/storage/vm/${PREFIX}/${PREFIX}.yaml" fi ((IP++)) done echo -e "\n\n${BLUE}Install Complete!${NC}" echo "export KUBECONFIG=/mnt/storage/vm/${PREFIX}/${PREFIX}.yaml" echo "${PREFIX} k3s Install Complete!" } install_addons() { # Tweak Settings from Physical HomeLab load_kubeconfig echo -e "\n${BLUE}Installing Cluster Addons:${NC}" echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-} if [[ -z "${URL}" ]]; then echo -n URL: read -r -s URL echo "" fi echo "${URL}" rm -rf /tmp/kubernetes cp -r kubernetes /tmp/ echo -e "\n${BLUE}Traefik:${NC}" kubectl apply -f /tmp/kubernetes/traefik/traefik-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/traefik/traefik-dashboard-traefik.yaml rm -f /tmp/kubernetes/traefik/cluster-wildcard-certificate.yaml kubectl apply -f /tmp/kubernetes/traefik/traefik-crd.yaml kubectl apply -f /tmp/kubernetes/traefik echo -e "\n${BLUE}Kube Eagle:${NC}" kubectl apply -f /tmp/kubernetes/kube-eagle echo -e "\n${BLUE}Kubernetes Dashboard:${NC}" kubectl apply -f /tmp/kubernetes/kubernetes-dashboard/kubernetes-dashboard-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/kubernetes-dashboard/kubernetes-dashboard-traefik.yaml kubectl apply -f /tmp/kubernetes/kubernetes-dashboard echo -e "\n${BLUE}Longhorn:${NC}" kubectl apply -f /tmp/kubernetes/longhorn/longhorn-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/longhorn/longhorn-traefik.yaml kubectl apply -f /tmp/kubernetes/longhorn kubectl patch daemonset -n longhorn-system longhorn-manager --type=json -p='[{"op": "remove", "path": "/spec/template/spec/tolerations"}]' echo -e "\n${BLUE}Waiting for Longhorn to Boot:${NC}" local lh_count=0 lh_count=$(kubectl get pods -n longhorn-system | grep -cv Running) || { echo "Failed to get Longhorn pods count" >&2; return 1; } while [[ "${lh_count}" -ne 1 ]]; do sleep 1 done echo -e "\n${BLUE}Prometheus:${NC}" kubectl apply -f /tmp/kubernetes/prometheus/prometheus-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/prometheus/prometheus-traefik.yaml kubectl apply -f /tmp/kubernetes/prometheus echo -e "\n${BLUE}Grafana:${NC}" kubectl apply -f /tmp/kubernetes/grafana/grafana-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/grafana/grafana-traefik.yaml kubectl apply -f /tmp/kubernetes/grafana kubectl patch deployment -n grafana grafana --type=json -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/env"}]' echo -e "\n${BLUE}Grafana Loki:${NC}" kubectl apply -f /tmp/kubernetes/grafana-loki echo -e "\n${BLUE}Grafana Promtail:${NC}" kubectl apply -f /tmp/kubernetes/grafana-promtail echo -e "\n\n${BLUE}Addons Installed!${NC}" } install_addons_optional() { load_kubeconfig echo -e "\n${BLUE}Heimdall:${NC}" kubectl apply -f /tmp/kubernetes/heimdall/heimdall-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/heimdall/heimdall-traefik.yaml sed -i "s/replicas: 1/replicas: 0/g" /tmp/kubernetes/heimdall/heimdall-statefulSet.yaml kubectl apply -f /tmp/kubernetes/heimdall kubectl patch statefulset -n heimdall heimdall --type=json -p='[{"op": "remove", "path": "/spec/template/spec/affinity"}]' kubectl patch statefulset -n heimdall heimdall --type=json -p='[{"op": "remove", "path": "/spec/template/spec/tolerations"}]' kubectl scale --replicas=1 statefulSet/heimdall -n heimdall echo -e "\n${BLUE}Uptime Kuma:${NC}" kubectl apply -f /tmp/kubernetes/uptime-kuma/uptime-kuma-namespace.yaml sed -i "s//${URL}/g" /tmp/kubernetes/uptime-kuma/uptime-kuma-traefik.yaml sed -i "s/replicas: 1/replicas: 0/g" /tmp/kubernetes/uptime-kuma/uptime-kuma-statefulSet.yaml kubectl apply -f /tmp/kubernetes/uptime-kuma kubectl patch statefulset -n uptime-kuma uptime-kuma --type=json -p='[{"op": "remove", "path": "/spec/template/spec/affinity"}]' kubectl patch statefulset -n uptime-kuma uptime-kuma --type=json -p='[{"op": "remove", "path": "/spec/template/spec/tolerations"}]' kubectl scale --replicas=1 statefulSet/uptime-kuma -n uptime-kuma } get_dashboard_secret() { load_kubeconfig echo -e "\n${BLUE}Kubernetes Dashboard Secret:${NC}" # shellcheck disable=SC2046 local sa_name="" sa_name=$(kubectl get serviceaccount admin-user -n kubernetes-dashboard -o jsonpath="{.secrets[0].name}") || { echo "Failed to get service account secret name" >&2; return 1; } kubectl get secret -n kubernetes-dashboard \ "${sa_name}" \ -o jsonpath="{.data.token}" | base64 --decode } preseed_server() { echo -e "\n${BLUE}Loading Preseed Server:${NC}" echo -n Password: read -r -s PASSWORD echo "" # Startup Preseed Web Server mkdir -p /tmp/preseed/ cp machineConfigs/preseed.cfg /tmp/preseed//preseed.cfg mkpasswd -m md5 "${PASSWORD}" local preseed_hash="" preseed_hash=$(mkpasswd -s -m md5 "${PASSWORD}") || { echo "Failed to generate password hash" >&2; return 1; } sed -i "s,#d-i passwd/user-password-crypted password ,d-i passwd/user-password-crypted password ${preseed_hash},g" "/tmp/preseed/preseed.cfg" python3 -m http.server --directory /tmp/preseed/ } install_cluster() { echo -e "\n${BLUE}Installing Cluster:${NC}" echo -n Password: read -r -s PASSWORD echo "" # IP Counter IP=${START_IP} # Delete Cluster If Exists delete_cluster mkdir -p "/mnt/storage/vm/${PREFIX}" for NODE in ${NODES}; do echo -e "\n\n${BLUE}Creating: ${PREFIX}${NODE}${NC}" if [[ "${NODE}" =~ "master" || "${NODE}" =~ "server" ]]; then CPU=3 MEMORY=4096 DISK=15 elif [[ "${NODE}" =~ "worker" || "${NODE}" =~ "agent" ]]; then CPU=6 MEMORY=8192 DISK=30 fi # Install VM #--debug \ #--wait=-1 \ # shellcheck disable=SC2140 virt-install \ --noautoconsole \ --graphics vnc \ --name="${PREFIX}${NODE}" \ --os-variant=debian11 \ --vcpus sockets=1,cores=1,threads="${CPU}" \ --ram="${MEMORY}" \ --disk "/mnt/storage/vm/${PREFIX}/${PREFIX}${NODE}".img,size="${DISK}" \ --network network=default,model=virtio,mac="10:10:00:00:00:${IP}" \ --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ \ --extra-args="\ auto=true priority=critical vga=normal hostname=${PREFIX}${NODE} \ url=http://10.10.10.1:8000/preseed.cfg" # https://crysol.org/recipe/2012-12-25/virtual-machine-unattended-debian-installations-with-libvirt-and-d-i-preseeding.html ((IP++)) done # Wait for Machines to Finish Installing OS, Then Boot Machines echo -e "\n\n${BLUE}Booting Nodes:${NC}" echo "Waiting for Installation to Finish..." echo "http://localhost:9090/machines" for NODE in ${NODES}; do # https://serverfault.com/a/386867 finished="0" while [[ "${finished}" = "0" ]]; do sleep 5 local running=0 running=$(virsh list --all | grep "running" | grep "${PREFIX}${NODE}" | wc -c) || { echo "Failed to check VM ${NODE} status" >&2; return 1; } if [[ "${running}" -eq 0 ]]; then echo "Starting vm ${PREFIX}${NODE}" sleep 1 virsh start "${PREFIX}${NODE}" finished=1 fi done done # Add Machines to Known Hosts echo -e "\n\n${BLUE}Add SSH Known Hosts:${NC}" IP=${START_IP} for NODE in ${NODES}; do finished="0" while [[ "${finished}" = "0" ]]; do sleep 1 if nc -z 10.10.10."${IP}" 22; then ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "10.10.10.${IP}" ssh-keyscan 10.10.10."${IP}" >>"${HOME}/.ssh/known_hosts" finished=1 fi done ((IP++)) done # Run Playbooks On All Machines ansible_sandbox reboot_cluster } install_k3s_cluster() { export PREFIX="sk3s" # Sandbox k3s export K3S_TOKEN=${PREFIX} # Sandbox k3s export NODES="-master-0 -master-1 -master-2 -worker-0 -worker-1 -worker-2 -worker-3" install_cluster # Install & Setup k3s install_k3s export KUBECONFIG="/mnt/storage/vm/${PREFIX}/${PREFIX}.yaml" local node_count=0 node_count=$(kubectl get nodes | wc -l) || { echo "Failed to get node count" >&2; return 1; } local expected=0 expected=$(echo "${NODES}" | wc -w) || { echo "Failed to count expected nodes" >&2; return 1; } while [[ "${node_count}" -le "${expected}" ]]; do sleep 1 done # Label Nodes label_vms # Install Addons install_addons install_addons_optional get_dashboard_secret echo " " echo "export KUBECONFIG=/mnt/storage/vm/${PREFIX}/${PREFIX}.yaml" echo "${PREFIX} k3s Install Complete!" } monitor_okd_virt() { export HOMELAB="${PWD}" export OKD=/tmp/okd export KUBECONFIG="${OKD}/okd/auth/kubeconfig" kubectl config set-cluster okd --insecure-skip-tls-verify=true "${OKD}/openshift-install" agent wait-for bootstrap-complete --dir "${OKD}/okd/" wait_for_install_complete "${OKD}/okd/" } delete_okd_virt() { export HOMELAB="${PWD}" export OKD=/tmp/okd # TODO, Automate Finding Which IPs to Remove ssh-keygen -R 10.103.10.101 ssh-keygen -R 10.103.10.102 ssh-keygen -R 10.103.10.103 echo -e "\n\n${BLUE}Delete All Existing Data:${NC}" rm -rf \ "${OKD}/oc" \ "${OKD}/kubectl" \ "${OKD}/openshift-install" \ "${OKD}/okd" # "${HOME}/.cache/agent" \ # ${OKD}/openshift-install-linux* \ # ${OKD}/openshift-client-linux* \ echo -e "\n\n${BLUE}Deleting VMs:${NC}" kubectl delete -n okd-virt vm --all --ignore-not-found 2>/dev/null kubectl delete -n okd-virt virtualmachineinstance --all --ignore-not-found 2>/dev/null kubectl delete -n okd-virt datavolume --all --ignore-not-found 2>/dev/null kubectl delete -f "${HOMELAB}/sandbox/kubevirt/okd/vm" --ignore-not-found 2>/dev/null } delete_okd_bm() { export HOMELAB="${PWD}" export OKD=/tmp/okd echo -e "\n\n${BLUE}Delete All Existing Data:${NC}" rm -rf \ "${OKD}/oc" \ "${OKD}/kubectl" \ "${OKD}/openshift-install" \ "${OKD}/okd" # "${HOME}/.cache/agent" \ # ${OKD}/openshift-install-linux* \ # ${OKD}/openshift-client-linux* \ } okd_bm_worker_shutdown() { export KUBECONFIG="${KUBECONFIG:-${HOME}/.kube/okd}" for WORKER in worker-1 worker-2 worker-3; do echo -e "\n${BLUE}Scaling down ${WORKER}:${NC}" kubectl patch machineset "okd-4ww5p-${WORKER}" -n openshift-machine-api --type='merge' -p '{"spec":{"replicas":0}}' done } okd_bm_worker_startup() { export KUBECONFIG="${KUBECONFIG:-${HOME}/.kube/okd}" for WORKER in worker-1 worker-2 worker-3; do echo -e "\n${BLUE}Scaling up ${WORKER}:${NC}" kubectl patch machineset "okd-4ww5p-${WORKER}" -n openshift-machine-api --type='merge' -p '{"spec":{"replicas":1}}' done } generate_node_iso() { local arg="${1:-}" local HOMELAB="${HOMELAB:-${PWD}}" if [[ -z "${arg}" || "${arg}" == "all" ]]; then local yaml_files=("${HOMELAB}"/okd/node-config/*.yaml) elif [[ "${arg}" == "workers" ]]; then local yaml_files=("${HOMELAB}"/okd/node-config/worker-*.yaml) elif [[ -f "${arg}" ]]; then local yaml_files=("${arg}") else echo "Error: Invalid argument '${arg}'. Usage: generate_node_iso | workers | all" >&2 return 1 fi if [[ ${#yaml_files[@]} -eq 0 ]]; then echo "Error: No YAML files found for '${arg}'" >&2 return 1 fi local dest_dir="${HOMELAB}/notes/iso" mkdir -p "${dest_dir}" export KUBECONFIG=/home/arthur/.kube/okd for node_yaml in "${yaml_files[@]}"; do if [[ ! -f "${node_yaml}" ]]; then continue fi echo -e "\n${BLUE}Generating node ISO from: ${node_yaml}${NC}" local node_name="" node_name=$(grep -m1 'hostname:' "${node_yaml}" | sed -E 's/.*hostname:[[:space:]]*//; s/[[:space:]]*$//') if [[ -z "${node_name}" ]]; then echo " WARNING: Could not extract hostname from '${node_yaml}', skipping" >&2 continue fi echo " Node name: ${node_name}" local node_ip="" node_ip=$(grep -m1 'ip:' "${node_yaml}" | sed -E 's/.*ip:[[:space:]]*//; s/[[:space:]]*$//') if [[ -z "${node_ip}" ]]; then echo " WARNING: Could not extract IP from '${node_yaml}', scp output will be incomplete" >&2 fi local tmp_dir tmp_dir=$(mktemp -d) cp "${node_yaml}" "${tmp_dir}/nodes-config.yaml" echo " Node IP: ${node_ip:-?}" if ! oc adm node-image create --dir="${tmp_dir}" -o="${node_name}.iso"; then echo "Error: oc adm node-image create failed for ${node_yaml}" >&2 rm -rf "${tmp_dir}" continue fi mv "${tmp_dir}/${node_name}.iso" "${dest_dir}/${node_name}.iso" rm -rf "${tmp_dir}" echo -e "${BLUE}ISO generated: ${dest_dir}/${node_name}.iso${NC}" # Print scp command if [[ -n "${node_ip}" ]]; then echo " scp arthur@10.101.11.7:/home/arthur/Projects/HomeLab/notes/iso/${node_name}.iso ./" fi done # Bulk pull command — run from your laptop echo "" echo " # Pull entire ISO folder from this machine:" echo " scp -r arthur@10.101.11.7:/home/arthur/Projects/HomeLab/notes/iso/ ./" } # ============================================================================== # MicroShift BootC Image Pull # ============================================================================== # Builds the microshift BootC image on the VM and outputs the scp command to # pull the image tarball to the local laptop. Follows the same pattern as # generate_node_iso but outputs image pull commands instead. # # Usage: # main.bash microshift-bootc-pull # Pull from default VM (10.101.11.7) # # This function: # 1. Builds the image (make image BUILD_MODE=release) # 2. Saves the image to a tarball # 3. Outputs the scp command to pull the tarball to your laptop # ============================================================================== microshift-bootc-pull() { local arg="${1:-}" local HOMELAB="${HOMELAB:-${PWD}}" local BUILD_MODE="${BUILD_MODE:-release}" local IMAGE_TAG # IMAGE_NAME used below for podman tag / save # Get version from version.txt if available local CLONE_DIR="/tmp/microshift-main" if [[ -f "${CLONE_DIR}/_output/rpmbuild/RPMS/version.txt" ]]; then IMAGE_TAG=$(tr '-' '_' < "${CLONE_DIR}/_output/rpmbuild/RPMS/version.txt") else IMAGE_TAG="latest" fi echo -e "\n${BLUE}MicroShift BootC Pull Instructions:${NC}" echo " BUILD_MODE=${BUILD_MODE}" echo " IMAGE_TAG=${IMAGE_TAG}" echo "" echo " # Build image on VM (if not already built):" echo " ssh arthur@10.101.11.7 'cd /home/arthur/Projects/HomeLab/containers/microshift-bootc && make image BUILD_MODE=${BUILD_MODE}'" echo "" echo " # Tag and save image on VM:" echo " ssh arthur@10.101.11.7 'cd /home/arthur/Projects/HomeLab/containers/microshift-bootc && podman tag localhost/microshift-bootc:latest localhost/microshift-bootc:${IMAGE_TAG}'" echo " ssh arthur@10.101.11.7 'cd /home/arthur/Projects/HomeLab/containers/microshift-bootc && podman save -o microshift-bootc-${IMAGE_TAG}.tar localhost/microshift-bootc:${IMAGE_TAG}'" echo "" echo " # Pull image tarball to your laptop:" echo " scp arthur@10.101.11.7:/home/arthur/Projects/HomeLab/containers/microshift-bootc/microshift-bootc-${IMAGE_TAG}.tar ." echo " podman load -i microshift-bootc-${IMAGE_TAG}.tar" echo "" echo " # Cleanup on VM:" echo " ssh arthur@10.101.11.7 'rm -f /home/arthur/Projects/HomeLab/containers/microshift-bootc/microshift-bootc-${IMAGE_TAG}.tar'" echo "" echo " # Or pull just the ISO (recommended):" echo " scp arthur@10.101.11.7:/home/arthur/Projects/HomeLab/containers/microshift-bootc/output/microshift.iso ./" } install_okd_prep() { sudo mount -o remount,size=8G,noatime /tmp sudo setquota -u "$(whoami)" 0 0 0 0 /tmp || true export XDG_CACHE_HOME="/tmp/cache" mkdir -p "${XDG_CACHE_HOME}" export AVP_TYPE=vault export AVP_AUTH_TYPE=token if [[ -z "${VAULT_TOKEN}" ]]; then export VAULT_TOKEN VAULT_TOKEN=$(vault login --tls-skip-verify -address="${URL}" -method=userpass -token-only username=arthur) || { echo "Failed to login to vault" >&2; return 1; } fi if [[ -z "${URL}" ]]; then echo -n URL: read -r -s URL echo "" fi echo "${URL}" echo -e "\n\n${BLUE}Get Registry URL:${NC}" export REGISTRY REGISTRY=${REGISTRY:-registry.arthurvardevanyan.com} if [[ -z "${REGISTRY}" ]]; then echo -n REGISTRY: read -r -s REGISTRY echo "" fi echo "${REGISTRY}" echo -e "\n\n${BLUE}Download Dependencies:${NC}" export OKD_VERSION=${OKD_VERSION:-""} # Auto-detect OCP vs OKD: OKD versions always contain "okd" if [[ -n "${OKD_VERSION}" ]] && [[ "${OKD_VERSION}" != *"okd"* ]]; then OCP=true fi export OCP=${OCP:-false} if [[ "${OCP}" == "true" ]]; then # OCP Official Releases: https://amd64.ocp.releases.ci.openshift.org/ # Channels: 4-stable, 4-dev-preview export OCP_CHANNEL=${OCP_CHANNEL:-4-stable} if [[ -z "${OKD_VERSION}" ]]; then OKD_VERSION=$(curl -s "https://amd64.ocp.releases.ci.openshift.org/api/v1/releasestream/${OCP_CHANNEL}/latest" | jq -r '.name') fi echo "${OKD_VERSION}" export OKD_URL="quay.io/openshift-release-dev/ocp-release:${OKD_VERSION}-x86_64" else # OKD Releases: https://amd64.origin.releases.ci.openshift.org/ if [[ -z "${OKD_VERSION}" ]]; then export OKD_CHANNEL=${OKD_CHANNEL:-5-scos-stable} # https://github.com/JaimeMagiera/oct/blob/3968059ca79d9b60245aaff659533f6090c9a722/helpers/okd-query-releases.sh#L137 OKD_VERSION=$(curl -s "https://amd64.origin.releases.ci.openshift.org/releasestream/${OKD_CHANNEL}" | grep "Accepted" -B 1 | awk 'sub(/.*release\/ */,""){f=1} f{if ( sub(/ *".*/,"") ) f=0; print}' | head -n 1) fi echo "${OKD_VERSION}" if [[ ${OKD_VERSION} == *"scos"* ]]; then export OKD_URL="quay.io/okd/scos-release:${OKD_VERSION}" export OKD_URL_CI="registry.ci.openshift.org/origin/release-scos:${OKD_VERSION}" else export OKD_URL="quay.io/openshift/okd:${OKD_VERSION}" export OKD_URL_CI="registry.ci.openshift.org/origin/release:${OKD_VERSION}" fi fi mkdir -p "${OKD}" if [[ ! -f "${OKD}/openshift-install-linux-${OKD_VERSION}.tar.gz" ]]; then echo "Required tools not found. Downloading..." # Clean up tarballs and binaries from any previous version find "${OKD}" -maxdepth 1 -name 'openshift-install-linux-4*.tar.gz' -delete find "${OKD}" -maxdepth 1 -name 'openshift-client-linux-4*.tar.gz' -delete rm -f "${OKD}/oc" "${OKD}/openshift-install" "${OKD}/kubectl" if ! oc adm release extract --tools "${OKD_URL}" --to="${OKD}/"; then if [[ "${OCP}" != "true" ]]; then echo -e "${BLUE}Trying CI Registry:${NC}" oc adm release extract --tools "${OKD_URL_CI}" --to="${OKD}/" else echo "ERROR: Failed to extract tools from ${OKD_URL}" >&2 exit 1 fi fi else echo "Tools already present. Skipping download." fi tar xvzf "${OKD}/openshift-install-linux-${OKD_VERSION}.tar.gz" -C "${OKD}" tar xvzf "${OKD}/openshift-client-linux-${OKD_VERSION}.tar.gz" -C "${OKD}" mkdir -p "${OKD}/vm" echo -e "\n\n${BLUE}Create Config Files:${NC}" # Create okd directory of openshift-install files mkdir -p "${OKD}"/okd # Copy the install-config.yaml export INSTALL_CONFIG_DIR=${INSTALL_CONFIG_DIR:-"${HOMELAB}/okd/install-config.yaml"} cp "${INSTALL_CONFIG_DIR}" "${OKD}/okd/" PULL_SECRET=$(vault kv get -field=auth secret/docker | jq -c) SSH=$(cat "${HOME}"/.ssh/id_ed25519.pub) sed -i "s//${SSH}/g" "${OKD}/okd/install-config.yaml" sed -i "s//${URL}/g" "${OKD}/okd/install-config.yaml" sed -i "s//${REGISTRY}/g" "${OKD}/okd/install-config.yaml" sed -i "s//${TF_VAR_control_plane_count:-${CONTROL_PLANE_COUNT}}/g" "${OKD}/okd/install-config.yaml" sed -i "s//${TF_VAR_worker_count:-${WORKER_COUNT}}/g" "${OKD}/okd/install-config.yaml" local pull_secret="" pull_secret=$(printf '%s\n' "${PULL_SECRET}" | tr -d '\n') PULL_SECRET="${pull_secret}" python3 -c " import json, os, sys path = sys.argv[1] with open(path) as f: content = f.read() content = content.replace('pullSecret: |\n ', 'pullSecret: ' + json.dumps(os.environ['PULL_SECRET']), 1) with open(path, 'w') as f: f.write(content) " "${OKD}/okd/install-config.yaml" cp "${OKD}/okd/install-config.yaml" "${OKD}/okd/install-config_backup.yaml" # export OPENSHIFT_INSTALL_OS_IMAGE_OVERRIDE=${OPENSHIFT_INSTALL_OS_IMAGE_OVERRIDE:-"https://mirror.openshift.com/pub/openshift-v4/x86_64/dependencies/rhcos/pre-release/latest/rhcos-live-iso.x86_64.iso"} } install_okd_virt() { delete_okd_virt export HOMELAB="${PWD}" export OKD=/tmp/okd export CONTROL_PLANE_COUNT export WORKER_COUNT CONTROL_PLANE_COUNT=${CONTROL_PLANE_COUNT:-3} WORKER_COUNT=${WORKER_COUNT:-0} echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-virt.arthurvardevanyan.com} export INSTALL_CONFIG_DIR="${HOMELAB}/sandbox/kubevirt/okd/configs/install-config.yaml" install_okd_prep cp "${HOMELAB}/sandbox/kubevirt/okd/configs/agent-config.yaml" "${OKD}/okd/" cp "${HOMELAB}/sandbox/kubevirt/okd/configs/containerfile" "${OKD}/okd/containerfile" "${OKD}/openshift-install" agent create cluster-manifests --dir "${OKD}/okd/" mkdir -p "${OKD}/okd/openshift" cp "${HOMELAB}/sandbox/kubevirt/okd/configs/ovn.yaml" "${OKD}/okd/openshift/cluster-network-03-config.yml" cp "${HOMELAB}/sandbox/kubevirt/okd/configs/boot-disk.yaml" "${OKD}/okd/openshift/boot-disk.yaml" cp "${HOMELAB}/sandbox/kubevirt/okd/configs/secondary-disk.yaml" "${OKD}/okd/openshift/secondary-disk.yaml" "${OKD}/openshift-install" agent create image --dir "${OKD}/okd/" podman build -f "${OKD}/okd/containerfile" -t "${REGISTRY}/homelab/okd-virt:latest" podman push "${REGISTRY}/homelab/okd-virt:latest" podman rmi -f "${REGISTRY}/homelab/okd-virt:latest" kubectl apply -f "${HOMELAB}/sandbox/kubevirt/okd/vm" export KUBECONFIG="${OKD}/okd/auth/kubeconfig" kubectl config set-cluster okd --insecure-skip-tls-verify=true "${OKD}/openshift-install" agent wait-for bootstrap-complete --dir "${OKD}/okd/" # "${OKD}/oc" apply -f "${HOMELAB}/okd/okd-configuration/overlays/sandbox/ingress-controller.yaml" wait_for_install_complete "${OKD}/okd/" kubectl label node sandbox-1 topology.kubernetes.io/zone=1 --overwrite kubectl label node sandbox-2 topology.kubernetes.io/zone=2 --overwrite kubectl label node sandbox-3 topology.kubernetes.io/zone=3 --overwrite } install_okd_sno() { delete_okd_bm export HOMELAB="${PWD}" export OKD=/tmp/okd export CONTROL_PLANE_COUNT export WORKER_COUNT CONTROL_PLANE_COUNT=${CONTROL_PLANE_COUNT:-1} WORKER_COUNT=${WORKER_COUNT:-0} echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-sno.arthurvardevanyan.com} install_okd_prep cp "${HOMELAB}/sandbox/sno/agent-config.yaml" "${OKD}/okd/" "${OKD}/openshift-install" agent create cluster-manifests --dir "${OKD}/okd/" "${OKD}/openshift-install" agent create image --dir "${OKD}/okd/" export KUBECONFIG="${OKD}/okd/auth/kubeconfig" "${OKD}/openshift-install" agent wait-for bootstrap-complete --dir "${OKD}/okd/" # "${OKD}/oc" apply -f "${HOMELAB}/okd/okd-configuration/overlays/sandbox/ingress-controller.yaml" wait_for_install_complete "${OKD}/okd/" } install_okd_bm() { delete_okd_bm export HOMELAB="${PWD}" export OKD=/tmp/okd export CONTROL_PLANE_COUNT export WORKER_COUNT CONTROL_PLANE_COUNT=${CONTROL_PLANE_COUNT:-3} WORKER_COUNT=${WORKER_COUNT:-0} echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-homelab.arthurvardevanyan.com} install_okd_prep cp "${HOMELAB}/okd/agent-config.yaml" "${OKD}/okd/" "${OKD}/openshift-install" agent create cluster-manifests --dir "${OKD}/okd/" mkdir -p "${OKD}/okd/openshift" cp "${HOMELAB}/okd/ovn.yaml" "${OKD}/okd/openshift/cluster-network-03-config.yml" cp "${HOMELAB}/okd/disks/boot-disk.yaml" "${OKD}/okd/openshift/boot-disk.yaml" cp "${HOMELAB}/okd/disks/secondary-disk.yaml" "${OKD}/okd/openshift/secondary-disk.yaml" "${OKD}/openshift-install" agent create image --dir "${OKD}/okd/" export KUBECONFIG="${OKD}/okd/auth/kubeconfig" "${OKD}/openshift-install" agent wait-for bootstrap-complete --dir "${OKD}/okd/" # "${OKD}/oc" apply -f "${HOMELAB}/okd/okd-configuration/overlays/sandbox/ingress-controller.yaml" wait_for_install_complete "${OKD}/okd/" kubectl label node server-1 topology.kubernetes.io/zone=1 --overwrite kubectl label node server-2 topology.kubernetes.io/zone=2 --overwrite kubectl label node server-3 topology.kubernetes.io/zone=3 --overwrite kubectl label node worker-1 topology.kubernetes.io/zone=1 --overwrite kubectl label node worker-2 topology.kubernetes.io/zone=2 --overwrite kubectl label node worker-3 topology.kubernetes.io/zone=3 --overwrite } delete_okd() { export HOME=/home/arthur export HOMELAB="${PWD}" export OKD=/mnt/storage/okd echo -e "\n\n${BLUE}Delete OKD Install:${NC}" echo -e "\n\n${BLUE}Delete Bootstrap:${NC}" cd "${HOMELAB}/terraform/sandbox/cluster" terraform init export TF_VAR_base_volume_id TF_VAR_base_volume_id=$(terraform output -raw base_volume_id) export TF_VAR_pool TF_VAR_pool=$(terraform output -raw pool) cd "${HOMELAB}/terraform/sandbox/bootstrap" terraform init terraform destroy -auto-approve echo -e "\n\n${BLUE}Delete Workers:${NC}" cd "${HOMELAB}/terraform/sandbox/workers" terraform init terraform destroy -auto-approve echo -e "\n\n${BLUE}Delete libvirt and Masters:${NC}" cd "${HOMELAB}/terraform/sandbox/cluster" terraform destroy -auto-approve if swapon --show | grep -q "${SWAP_PATH}"; then swapoff /home/swapfile.img fi echo -e "\n\n${BLUE}Delete All Existing Data:${NC}" rm -rf \ "${OKD}"/openshift-install-linux* \ "${OKD}"/openshift-client-linux* \ "${OKD}"/oc \ "${OKD}"/kubectl \ "${OKD}"/openshift-install \ "${OKD}"/fedora-coreos-* \ "${OKD}"/*.qcow2 \ "${OKD}"/okd \ "${OKD}" cd "${HOMELAB}" } delete_okd_agent() { export HOME=/home/arthur export HOMELAB="${PWD}" export OKD=/mnt/storage/okd # TODO, Automate Finding Which IPs to Remove # TODO Stop Running as Sudo sudo -H -u arthur ssh-keygen -R 10.10.10.10 sudo -H -u arthur ssh-keygen -R 10.10.10.11 sudo -H -u arthur ssh-keygen -R 10.10.10.12 echo -e "\n\n${BLUE}Delete Installation:${NC}" cd "${HOMELAB}/terraform/agent" terraform destroy -auto-approve echo -e "\n\n${BLUE}Delete All Existing Data:${NC}" rm -rf \ "${OKD}"/openshift-install-linux* \ "${OKD}"/openshift-client-linux* \ "${OKD}"/oc \ "${OKD}"/kubectl \ "${OKD}"/openshift-install \ "${OKD}"/fedora-coreos-* \ "${OKD}"/*.qcow2 \ "${OKD}"/okd if swapon --show | grep -q "${SWAP_PATH}"; then swapoff /home/swapfile.img fi cd "${HOMELAB}" } install_okd_libvirt_prep() { # https://blog.maumene.org/2020/11/18/OKD-or-OpenShit-in-one-box.html # ssh -i ~/.ssh/id_ed25519 core@10.10.10.10 # journalctl -b -f -u kubelet.service -u ciro.service # TODO: Fix Permissions: # sudo vi /etc/libvirt/qemu.conf # user = "root" # group = "root" # sudo systemctl restart libvirtd export HOMELAB="${PWD}" export HOME=/home/arthur export OKD=/mnt/storage/okd export KUBECONFIG="${OKD}/okd/auth/kubeconfig" export PREFIX="" export TF_VAR_control_plane_count=3 # Expand Swap Size on Host Computer if ! test -f "${SWAP_PATH}"; then dd if=/dev/zero of="${SWAP_PATH}" bs=45056 count=1M mkswap "${SWAP_PATH}" fi if ! (swapon --show | grep -q "${SWAP_PATH}"); then swapon "${SWAP_PATH}" fi systemctl start haproxy systemctl stop firewalld } install_okd() { # Delete Cluster If Exists delete_okd export TF_VAR_worker_count=4 install_okd_libvirt_prep echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-sandbox.arthurvardevanyan.com} install_okd_prep # Create the ignition files "${OKD}/openshift-install" create ignition-configs --dir="${OKD}/okd" chown -R arthur:arthur "${OKD}" chmod 777 -R "${OKD}" echo -e "\n\n${BLUE}Start OKD Install:${NC}" echo -e "\n\n${BLUE}Setup libvirt and Masters:${NC}" cd "${HOMELAB}/terraform/sandbox/cluster" mkdir -p "${OKD}/terraform/cluster" terraform init terraform apply -auto-approve export TF_VAR_base_volume_id TF_VAR_base_volume_id=$(terraform output -raw base_volume_id) export TF_VAR_pool TF_VAR_pool=$(terraform output -raw pool) echo -e "\n\n${BLUE}Initialize Bootstrap:${NC}" cd "${HOMELAB}/terraform/sandbox/bootstrap" mkdir -p "${OKD}/terraform/bootstrap" terraform init terraform apply -auto-approve echo -e "\n\n${BLUE}Waiting For Bootstrap:${NC}" "${OKD}/openshift-install" --dir="${OKD}/okd" wait-for bootstrap-complete --log-level debug echo -e "\n\n${BLUE}Destroy Bootstrap:${NC}" cd "${HOMELAB}/terraform/sandbox/bootstrap" mkdir -p "${OKD}/terraform/bootstrap" terraform destroy -auto-approve echo -e "\n\n${BLUE}Initialize Workers:${NC}" cd "${HOMELAB}/terraform/sandbox/workers" mkdir -p "${OKD}/terraform/workers" terraform init terraform apply -auto-approve echo -e "\n\n${BLUE}Wait for Install To Complete:${NC}" #${OKD}/oc apply -f "${HOMELAB}/okd/okd-configuration/overlays/sandbox/ingress-controller.yaml" wait_for_install_complete "${OKD}/okd/" "${OKD}/oc" apply -f "${HOMELAB}/okd/okd-configuration/base/operator-hub.yaml" "${OKD}/oc" apply -f "${HOMELAB}/okd/okd-configuration/base/operators" echo -e "\n\n${BLUE}Setup Image Mirroring:${NC}" #sed 's/AllowContactingSource/NeverContactSource/' "${HOMELAB}"/okd/okd-configuration/base/image-mirror-set.yaml | kubectl apply -f - kubectl apply -f "${HOMELAB}"/okd/okd-configuration/base/image-mirror-set.yaml echo -e "\n\n${BLUE}Install Complete:${NC}" echo "export KUBECONFIG=${OKD}/okd/auth/kubeconfig" local kadmin_pass="" kadmin_pass=$(cat "${OKD}/okd/auth/kubeadmin-password") || { echo "Failed to read kubeadmin password" >&2; return 1; } echo "Kubeadmin Password: ${kadmin_pass}" #single_server } install_okd_agent() { # Delete Cluster If Exists delete_okd_agent export TF_VAR_worker_count=0 install_okd_libvirt_prep echo -e "\n\n${BLUE}Get URL:${NC}" export URL URL=${URL:-sandbox.arthurvardevanyan.com} install_okd_prep cp "${HOMELAB}/sandbox/libvirt/configs/agent-config.yaml" "${OKD}/okd/" "${OKD}/openshift-install" agent create cluster-manifests --dir "${OKD}/okd/" "${OKD}/openshift-install" agent create image --dir "${OKD}/okd/" chown -R arthur:arthur "${OKD}" chmod 777 -R "${OKD}" echo -e "\n\n${BLUE}Start OKD Install:${NC}" echo -e "\n\n${BLUE}Setup libvirt and Masters:${NC}" cd "${HOMELAB}/terraform/agent" mkdir -p "${OKD}/terraform/agent" terraform init terraform apply -auto-approve export KUBECONFIG="${OKD}/okd/auth/kubeconfig" "${OKD}/openshift-install" agent wait-for bootstrap-complete --dir "${OKD}/okd/" # "${OKD}/oc" apply -f "${HOMELAB}/okd/okd-configuration/overlays/sandbox/ingress-controller.yaml" wait_for_install_complete "${OKD}/okd/" } single_server() { echo -e "\n\n${BLUE}Single Server Resource Adjustments:${NC}" export KUBECONFIG="${OKD}/okd/auth/kubeconfig" "${OKD}/oc" scale --replicas=1 ingresscontroller/default -n openshift-ingress-operator "${OKD}/oc" scale --replicas=1 deployment.apps/console -n openshift-console "${OKD}/oc" scale --replicas=1 deployment.apps/downloads -n openshift-console "${OKD}/oc" scale --replicas=1 deployment.apps/oauth-openshift -n openshift-authentication "${OKD}/oc" scale --replicas=1 deployment.apps/packageserver -n openshift-operator-lifecycle-manager "${OKD}/oc" scale --replicas=1 deployment.apps/prometheus-adapter -n openshift-monitoring "${OKD}/oc" scale --replicas=1 deployment.apps/thanos-querier -n openshift-monitoring "${OKD}/oc" scale --replicas=1 statefulset.apps/prometheus-k8s -n openshift-monitoring "${OKD}/oc" scale --replicas=1 statefulset.apps/alertmanager-main -n openshift-monitoring } install_addons_okd_virt() { # Tweak Settings from Physical HomeLab export OKD=/tmp/okd export KUBECONFIG="${OKD}/okd/auth/kubeconfig" export HOMELAB="${PWD}" kubectl config set-cluster okd --insecure-skip-tls-verify=true echo -e "\n${BLUE}Installing Cluster Addons:${NC}" echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-virt.arthurvardevanyan.com} if [[ -z "${URL}" ]]; then echo -n URL: read -r -s URL echo "" fi echo "${URL}" export AVP_TYPE=vault export AVP_AUTH_TYPE=token if [[ -z "${VAULT_TOKEN}" ]]; then export VAULT_TOKEN VAULT_TOKEN=$(vault login --tls-skip-verify -address="${URL}" -method=userpass -token-only username=arthur) || { echo "Failed to login to vault" >&2; return 1; } fi kubectl config set-cluster okd --insecure-skip-tls-verify=true kubectl label node sandbox-1 topology.kubernetes.io/zone=1 --overwrite kubectl label node sandbox-2 topology.kubernetes.io/zone=2 --overwrite kubectl label node sandbox-3 topology.kubernetes.io/zone=3 --overwrite echo -e "\n${BLUE}Pull Secret:${NC}" argocd-vault-plugin generate "${HOMELAB}"/okd/okd-configuration/base/pull-secret.yaml | kubectl apply -f - sleep 30s local mcp_master_status="" mcp_master_status=$(kubectl get mcp master -o yaml | yq '.status.conditions[] | select(.type == "Updating") | .status') || { echo "Failed to get MCP master status" >&2; return 1; } while [[ "${mcp_master_status}" == "True" ]]; do echo "Waiting for MCPs" sleep 30s done oc patch --type=merge --patch='{"spec":{"paused":true}}' machineconfigpool/master echo -e "\n${BLUE}OperatorHub:${NC}" kubectl apply -f "${HOMELAB}"/okd/okd-configuration/base/operator-hub.yaml sleep 30s echo -e "\n${BLUE}Waiting for MarketPlace Pods to Boot:${NC}" local mp_count=0 mp_count=$(kubectl get pods -n openshift-marketplace | grep -v "Completed" | grep -cv Running) || { echo "Failed to get Marketplace pods count" >&2; return 1; } while [[ "${mp_count}" -ne 1 ]]; do sleep 1 done echo -e "\n${BLUE}Cert Manager:${NC}" local _cm _cm=0 while [[ ${_cm} -lt 5 ]]; do if kubectl kustomize "${HOMELAB}"/kubernetes/cert-manager/overlays/okd-sandbox | argocd-vault-plugin generate - | kubectl apply -f -; then break fi _cm=$((_cm + 1)) sleep $((5 ** _cm)) done echo -e "\n${BLUE}Nmstate:${NC}" kubectl create ns openshift-nmstate kubectl kustomize "${HOMELAB}"/kubernetes/nmstate/overlays/sandbox | kubectl apply -f - echo -e "\n${BLUE}OKD Configuration:${NC}" kubectl kustomize "${HOMELAB}"/okd/okd-configuration/overlays/sandbox | argocd-vault-plugin generate - | kubectl apply -f - kubectl kustomize kubernetes/nmstate/overlays/sandbox | kubectl apply -f - echo -e "\n${BLUE}Waiting for API Certificate to Generate:${NC}" local api_cert_ready="" api_cert_ready=$(kubectl get certificate -n openshift-config api-certificate -o yaml | yq '.status.conditions[] | select(.type == "Ready") | .status') || { echo "Failed to get API certificate ready status" >&2; return 1; } while [[ "${api_cert_ready}" == "False" ]]; do echo "Waiting for API Certificate to Generate" sleep 30 done echo -e "\n${BLUE}Waiting for Ingress Certificate to Generate:${NC}" local ingress_cert_ready="" ingress_cert_ready=$(kubectl get certificate -n openshift-ingress ingress-certificate -o yaml | yq '.status.conditions[] | select(.type == "Ready") | .status') || { echo "Failed to get Ingress certificate ready status" >&2; return 1; } while [[ "${ingress_cert_ready}" == "False" ]]; do echo "Waiting for Ingress Certificate to Generate" sleep 30 done sleep 120 oc patch --type=merge --patch='{"spec":{"paused":false}}' machineconfigpool/master oc patch --type=merge --patch='{"spec":{"paused":false}}' machineconfigpool/worker echo -e "\n\n${BLUE}Addons Installed!${NC}" } install_addons_okd() { # Tweak Settings from Physical HomeLab export OKD=/mnt/storage/okd export KUBECONFIG="${OKD}/okd/auth/kubeconfig" export HOMELAB="${PWD}" echo -e "\n${BLUE}Installing Cluster Addons:${NC}" echo -e "\n\n${BLUE}Get URL:${NC}" URL=${URL:-arthurvardevanyan.com} if [[ -z "${URL}" ]]; then echo -n URL: read -r -s URL echo "" fi echo "${URL}" export AVP_TYPE=vault export AVP_AUTH_TYPE=token if [[ -z "${VAULT_TOKEN}" ]]; then export VAULT_TOKEN VAULT_TOKEN=$(vault login --tls-skip-verify -address="${URL}" -method=userpass -token-only username=arthur) fi sleep 15s local mcp_worker_status="" mcp_worker_status=$(kubectl get mcp worker -o yaml | yq '.status.conditions[] | select(.type == "Updating") | .status') || { echo "Failed to get MCP worker status" >&2; return 1; } while [[ "${mcp_worker_status}" == "True" ]]; do echo "Waiting for MCPs" sleep 30s done oc debug node/worker-0 -t -- chroot /host sudo mkfs.ext4 -L longhorn /dev/vdb oc debug node/worker-1 -t -- chroot /host sudo mkfs.ext4 -L longhorn /dev/vdb oc debug node/worker-2 -t -- chroot /host sudo mkfs.ext4 -L longhorn /dev/vdb oc debug node/worker-3 -t -- chroot /host sudo mkfs.ext4 -L longhorn /dev/vdb kubectl label node worker-0 topology.kubernetes.io/zone="even" --overwrite kubectl label node worker-1 topology.kubernetes.io/zone="odd" --overwrite kubectl label node worker-2 topology.kubernetes.io/zone="even" --overwrite kubectl label node worker-3 topology.kubernetes.io/zone="odd" --overwrite || { echo "Warning: kubectl label failed on worker-3" >&2; } kubectl annotate node "worker-0" node.longhorn.io/default-disks-config='[{"path":"/var/mnt/longhorn","allowScheduling":true}]' --overwrite kubectl annotate node "worker-1" node.longhorn.io/default-disks-config='[{"path":"/var/mnt/longhorn","allowScheduling":true}]' --overwrite kubectl annotate node "worker-2" node.longhorn.io/default-disks-config='[{"path":"/var/mnt/longhorn","allowScheduling":true}]' --overwrite kubectl annotate node "worker-3" node.longhorn.io/default-disks-config='[{"path":"/var/mnt/longhorn","allowScheduling":true}]' --overwrite || { echo "Warning: kubectl annotate failed on worker-3" >&2; } kubectl label node "worker-0" node.longhorn.io/create-default-disk=config --overwrite kubectl label node "worker-1" node.longhorn.io/create-default-disk=config --overwrite kubectl label node "worker-2" node.longhorn.io/create-default-disk=config --overwrite kubectl label node "worker-3" node.longhorn.io/create-default-disk=config --overwrite || { echo "Warning: kubectl label failed on worker-3" >&2; } kubectl label node "worker-0" node-role.kubernetes.io/infra="" --overwrite kubectl label node "worker-1" node-role.kubernetes.io/infra="" --overwrite kubectl label node "worker-2" node-role.kubernetes.io/infra="" --overwrite kubectl label node "worker-3" node-role.kubernetes.io/infra="" --overwrite || { echo "Warning: kubectl label failed on worker-3" >&2; } kubectl apply -f "${HOMELAB}"/okd/okd-configuration/base/mcp.yaml yq '.spec.config.systemd.units[1].enabled=false' "${HOMELAB}"/okd/okd-configuration/overlays/sandbox/longhorn-mc.yaml | kubectl apply -f - sleep 15s local mcp_infra_status="" mcp_infra_status=$(kubectl get mcp infra -o yaml | yq '.status.conditions[] | select(.type == "Updating") | .status') || { echo "Failed to get MCP infra status" >&2; return 1; } while [[ "${mcp_infra_status}" == "True" ]]; do echo "Waiting for MCPs" sleep 30s done echo -e "\n${BLUE}Longhorn:${NC}" local _lh _lh=0 while [[ ${_lh} -lt 5 ]]; do if kubectl kustomize "${HOMELAB}"/kubernetes/longhorn/overlays/okd-sandbox | argocd-vault-plugin generate - | kubectl apply -f -; then break fi _lh=$((_lh + 1)) sleep $((5 ** _lh)) done echo -e "\n${BLUE}Waiting for Longhorn to Boot:${NC}" local lh_count2=0 lh_count2=$(kubectl get pods -n longhorn-system | grep -cv Running) || { echo "Failed to get Longhorn pods count" >&2; return 1; } while [[ "${lh_count2}" -ne 1 ]]; do sleep 1 done echo -e "\n${BLUE}OKD Monitoring:${NC}" local _om _om=0 while [[ ${_om} -lt 5 ]]; do local _okm _okm=$(kubectl kustomize "${HOMELAB}"/okd/openshift-monitoring/overlays/sandbox | argocd-vault-plugin generate - | kubectl apply -f -) || { echo "Failed to apply monitoring" >&2; return 1; } if [[ "${_okm}" == "0" ]]; then break fi _om=$((_om + 1)) sleep $((5 ** _om)) done echo -e "\n${BLUE}Cert Manager:${NC}" local _cm _cm=0 while [[ ${_cm} -lt 5 ]]; do if kubectl kustomize "${HOMELAB}"/kubernetes/cert-manager/overlays/okd-sandbox | argocd-vault-plugin generate - | kubectl apply -f -; then break fi _cm=$((_cm + 1)) sleep $((5 ** _cm)) done echo -e "\n${BLUE}OKD Configuration:${NC}" local _oc _oc=0 while [[ ${_oc} -lt 5 ]]; do if kubectl kustomize "${HOMELAB}"/okd/okd-configuration/overlays/sandbox | argocd-vault-plugin generate - | kubectl apply -f -; then break fi _oc=$((_oc + 1)) sleep $((5 ** _oc)) done oc patch --type=merge --patch='{"spec":{"paused":true}}' machineconfigpool/master oc patch --type=merge --patch='{"spec":{"paused":true}}' machineconfigpool/worker oc patch --type=merge --patch='{"spec":{"paused":true}}' machineconfigpool/infra echo -e "\n${BLUE}Waiting for API Certificate to Generate:${NC}" local api_cert_status="" api_cert_status=$(kubectl get certificate -n openshift-config api-certificate -o yaml | yq '.status.conditions[] | select(.type == "Ready") | .status') || { echo "Failed to get API certificate status" >&2; return 1; } while [[ "${api_cert_status}" == "False" ]]; do echo "Waiting for API Certificate to Generate" sleep 30 done echo -e "\n${BLUE}Waiting for Ingress Certificate to Generate:${NC}" local ingress_cert_status="" ingress_cert_status=$(kubectl get certificate -n openshift-ingress ingress-certificate -o yaml | yq '.status.conditions[] | select(.type == "Ready") | .status') || { echo "Failed to get Ingress certificate status" >&2; return 1; } while [[ "${ingress_cert_status}" == "False" ]]; do echo "Waiting for Ingress Certificate to Generate" sleep 30 done sleep 120 oc patch --type=merge --patch='{"spec":{"paused":false}}' machineconfigpool/master oc patch --type=merge --patch='{"spec":{"paused":false}}' machineconfigpool/worker oc patch --type=merge --patch='{"spec":{"paused":false}}' machineconfigpool/infra echo -e "\n\n${BLUE}Addons Installed!${NC}" } approve_csr() { echo -e "\n\n${BLUE}Approving CSRs:${NC}" while true; do for csr in $(oc get csr 2>/dev/null | grep -w 'Pending' | awk '{print $1}'); do echo -n ' --> Approving CSR: ' oc adm certificate approve "${csr}" 2>/dev/null || { echo "Warning: CSR ${csr} not pending or already approved" >&2; } done sleep 5 done } update_install_plans() { echo -e "\n${BLUE}Checking for pending InstallPlans:${NC}" PENDING=$(kubectl get installplan -A -o jsonpath='{range .items[?(@.spec.approved==false)]}{.metadata.namespace}{"\t"}{.spec.clusterServiceVersionNames[0]}{"\n"}{end}') if [[ -z "${PENDING}" ]]; then echo "No pending InstallPlans found." return 0 fi while IFS=$'\t' read -r NAMESPACE CSV; do [[ -z "${NAMESPACE}" ]] && continue echo -e "\nPending: ${CSV} in ${NAMESPACE}" # Find the subscription file matching this namespace SUB_FILE=$(grep -rl "namespace: ${NAMESPACE}$" --include="subscription.yaml" kubernetes/ 2>/dev/null | head -1) if [[ -z "${SUB_FILE}" ]]; then echo " WARNING: No subscription file found for namespace '${NAMESPACE}'" continue fi # Get current startingCSV from the file CURRENT_CSV=$(grep 'startingCSV:' "${SUB_FILE}" | sed 's/.*startingCSV:[[:space:]]*//' | tr -d '"' | tr -d "'" | awk '{print $1}') if [[ "${CURRENT_CSV}" == "${CSV}" ]]; then echo " Already up to date: ${SUB_FILE}" continue fi echo " Updating ${SUB_FILE}" echo " ${CURRENT_CSV} -> ${CSV}" sed -i "s|startingCSV:.*|startingCSV: ${CSV}|" "${SUB_FILE}" done <<< "${PENDING}" echo -e "\n${BLUE}Done updating subscription files!${NC}" } approve_install_plans() { echo -e "\n${BLUE}Approving pending InstallPlans:${NC}" PENDING=$(kubectl get installplan -A -o jsonpath='{range .items[?(@.spec.approved==false)]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.clusterServiceVersionNames[0]}{"\n"}{end}') if [[ -z "${PENDING}" ]]; then echo "No pending InstallPlans found." return 0 fi while IFS=$'\t' read -r NAMESPACE NAME CSV; do [[ -z "${NAMESPACE}" ]] && continue # Strip hidden characters (CR, whitespace) NAMESPACE=$(printf '%s' "${NAMESPACE}" | tr -d '\r' | xargs) CSV=$(printf '%s' "${CSV}" | tr -d '\r' | xargs) echo " Approving ${NAME} (${CSV}) in ${NAMESPACE}" kubectl patch installplan "${NAME}" -n "${NAMESPACE}" --type merge --patch '{"spec":{"approved":true}}' # Find and update the subscription file matching this namespace SUB_FILE=$(grep -rl "namespace: ${NAMESPACE}" --include="subscription.yaml" kubernetes/ 2>/dev/null | head -1) if [[ -z "${SUB_FILE}" ]]; then echo " WARNING: No subscription file found for namespace '${NAMESPACE}'" continue fi # Get current startingCSV from the file CURRENT_CSV=$(grep 'startingCSV:' "${SUB_FILE}" | sed 's/.*startingCSV:[[:space:]]*//' | tr -d '"' | tr -d "'" | tr -d '\r' | awk '{print $1}') if [[ "${CURRENT_CSV}" == "${CSV}" ]]; then echo " Already up to date: ${SUB_FILE} (${CSV})" else echo " Updating ${SUB_FILE}" echo " ${CURRENT_CSV} -> ${CSV}" sed -i "s|startingCSV:.*|startingCSV: ${CSV}|" "${SUB_FILE}" fi done <<< "${PENDING}" echo -e "\n${BLUE}Done approving InstallPlans!${NC}" } "$@"