#!/usr/bin/env bash set -euo pipefail # ─── Configuration ─────────────────────────────────────────────────────────── K3S_VERSION="v1.31.4+k3s1" LONGHORN_VERSION="1.7.2" LONGHORN_REPO="https://charts.longhorn.io" # ─── Helper ────────────────────────────────────────────────────────────────── info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } error() { printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2; exit 1; } usage() { cat < Commands: server Install k3s server (manager node) agent Install k3s agent (worker node) longhorn Install Longhorn via Helm sops-keygen Generate an age keypair for SOPS apply-secrets Decrypt and apply SOPS secrets to the cluster deploy Helm-install all charts all Run: server → longhorn → sops-keygen → apply-secrets → deploy EOF } # ─── k3s server ────────────────────────────────────────────────────────────── cmd_server() { info "Installing k3s server ${K3S_VERSION} …" curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="${K3S_VERSION}" sh -s - server \ --write-kubeconfig-mode 644 info "Waiting for node to be Ready …" until kubectl get node "$(hostname)" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q True; do sleep 2 done info "Server node is Ready." local token token=$(cat /var/lib/rancher/k3s/server/node-token) local ip ip=$(hostname -I | awk '{print $1}') info "Worker join command:" echo " curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=\"${K3S_VERSION}\" K3S_URL=\"https://${ip}:6443\" K3S_TOKEN=\"${token}\" sh -" } # ─── k3s agent ─────────────────────────────────────────────────────────────── cmd_agent() { if [[ -z "${K3S_URL:-}" || -z "${K3S_TOKEN:-}" ]]; then error "Set K3S_URL and K3S_TOKEN environment variables first.\n K3S_URL=https://:6443 K3S_TOKEN= $0 agent" fi info "Installing k3s agent ${K3S_VERSION} …" curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="${K3S_VERSION}" K3S_URL="${K3S_URL}" K3S_TOKEN="${K3S_TOKEN}" sh - info "Agent installed. It will appear as a node shortly." } # ─── Longhorn ──────────────────────────────────────────────────────────────── cmd_longhorn() { info "Installing Longhorn ${LONGHORN_VERSION} via Helm …" helm repo add longhorn "${LONGHORN_REPO}" 2>/dev/null || true helm repo update longhorn kubectl create namespace longhorn-system 2>/dev/null || true helm upgrade --install longhorn longhorn/longhorn \ --namespace longhorn-system \ --version "${LONGHORN_VERSION}" \ --set defaultSettings.defaultReplicaCount=2 \ --wait info "Longhorn installed." } # ─── SOPS / age ───────────────────────────────────────────────────────────── cmd_sops_keygen() { local keydir="/etc/sops/age" local keyfile="${keydir}/keys.txt" if [[ -f "${keyfile}" ]]; then warn "Age key already exists at ${keyfile} — skipping generation." else info "Generating age keypair …" sudo mkdir -p "${keydir}" age-keygen -o "${keyfile}" 2>&1 sudo chmod 600 "${keyfile}" info "Key written to ${keyfile}" fi info "Public key (put this in .sops.yaml):" grep 'public key' "${keyfile}" | awk '{print $NF}' } cmd_apply_secrets() { local secrets_dir secrets_dir="$(cd "$(dirname "$0")/.." && pwd)/secrets" info "Decrypting and applying secrets …" local decrypted decrypted="$(sops -d "${secrets_dir}/secrets.enc.yaml")" local ns for ns in $(printf '%s\n' "${decrypted}" | grep '^\s*namespace:' | awk '{print $2}' | sort -u); do kubectl create namespace "${ns}" 2>/dev/null || true done printf '%s\n' "${decrypted}" | kubectl apply -f - info "Secrets applied." } # ─── Deploy all charts ────────────────────────────────────────────────────── cmd_deploy() { local charts_dir charts_dir="$(cd "$(dirname "$0")/.." && pwd)/charts" local -a chart_order=(traefik-config media paperless mealie dashboards utils headlamp) local -A chart_ns=( [traefik-config]=kube-system [media]=media [paperless]=paperless [mealie]=apps [dashboards]=apps [utils]=apps [headlamp]=apps ) for chart in "${chart_order[@]}"; do local ns="${chart_ns[$chart]}" info "Deploying ${chart} → namespace ${ns} …" kubectl create namespace "${ns}" 2>/dev/null || true helm upgrade --install "${chart}" "${charts_dir}/${chart}" \ --namespace "${ns}" \ --wait --timeout 5m done info "All charts deployed." } # ─── Main ──────────────────────────────────────────────────────────────────── [[ $# -lt 1 ]] && { usage; exit 1; } case "$1" in server) cmd_server ;; agent) cmd_agent ;; longhorn) cmd_longhorn ;; sops-keygen) cmd_sops_keygen ;; apply-secrets) cmd_apply_secrets ;; deploy) cmd_deploy ;; all) cmd_server; cmd_longhorn; cmd_sops_keygen; cmd_apply_secrets; cmd_deploy ;; *) usage; exit 1 ;; esac