From 6b1fc54c0df288081cbaef253163f0df1012ebe7 Mon Sep 17 00:00:00 2001 From: Konstantin Grachev Date: Wed, 16 Sep 2026 23:12:48 +0300 Subject: [PATCH] chore: tidy up :broom: --- .../e2e/cilium-network-policy.yaml | 12 - .github/template-tests/e2e/cluster.sh | 291 ------------ .github/template-tests/e2e/cluster.toml.tmpl | 22 - .github/template-tests/e2e/network.yaml.tmpl | 42 -- .github/template-tests/e2e/node.toml.tmpl | 7 - .../template-tests/e2e/sops-secret.yaml.tmpl | 8 - .github/template-tests/e2e/talos-cluster.yaml | 21 - .../template-tests/invalid/bad-bgp-asn.toml | 34 -- .../invalid/bad-mac-address.toml | 29 -- .../template-tests/invalid/bad-repo-url.toml | 30 -- .../template-tests/invalid/bad-vlan-tag.toml | 30 -- .../invalid/duplicate-gateway-addrs.toml | 29 -- .../invalid/duplicate-node-names.toml | 37 -- .../invalid/gateway-node-collision.toml | 29 -- .../invalid/missing-dns-token.toml | 29 -- .../invalid/missing-external-gateway.toml | 28 -- .../invalid/missing-known-hosts.toml | 30 -- .../invalid/missing-schematic.toml | 28 -- .../invalid/nested-cidr-overlap.toml | 30 -- .../invalid/node-addr-outside-cidr.toml | 29 -- .../invalid/node-uses-gateway-addr.toml | 30 -- .../invalid/non-canonical-cidr.toml | 30 -- .../invalid/overlapping-cidrs.toml | 29 -- .../template-tests/invalid/partial-bgp.toml | 34 -- .../invalid/reserved-node-name.toml | 29 -- .../template-tests/invalid/tiny-svc-cidr.toml | 33 -- .../invalid/tunnel-without-dns.toml | 32 -- .github/template-tests/valid/direct.toml | 38 -- .github/template-tests/valid/internal.toml | 34 -- .../valid/multi-controller.toml | 42 -- .github/template-tests/valid/no-webhook.toml | 36 -- .github/template-tests/valid/private.toml | 39 -- .github/template-tests/valid/public.toml | 59 --- .github/template-tests/valid/selfhosted.toml | 39 -- .github/template-tests/valid/single-node.toml | 26 -- .github/workflows/template-e2e-cluster.yaml | 103 ----- .github/workflows/template-e2e.yaml | 176 ------- .github/workflows/template-release.yaml | 58 --- .mise/conf.d/template.toml | 5 - .mise/mise.lock | 73 --- cluster.sample.toml | 292 ------------ cluster.schema.json | 433 ------------------ justfile | 15 - makejinja.toml | 19 - pyproject.toml | 16 - template/config/.sops.yaml.j2 | 12 - .../config/bootstrap/deploy-key.sops.yaml.j2 | 15 - .../config/bootstrap/helmfile/apps.yaml.j2 | 60 --- .../config/bootstrap/helmfile/crds.yaml.j2 | 37 -- .../config/bootstrap/helmfile/default.yaml.j2 | 8 - .../helmfile/templates/release.yaml.gotmpl.j2 | 3 - .../helmfile/templates/values.yaml.gotmpl.j2 | 1 - template/config/bootstrap/mod.just | 105 ----- .../config/bootstrap/sops-age.sops.yaml.j2 | 8 - .../cert-manager/app/clusterissuer.yaml.j2 | 52 --- .../cert-manager/app/helmrelease.yaml.j2 | 20 - .../cert-manager/app/kustomization.yaml.j2 | 10 - .../cert-manager/app/ocirepository.yaml.j2 | 13 - .../cert-manager/app/secret.sops.yaml.j2 | 9 - .../apps/cert-manager/cert-manager/ks.yaml.j2 | 30 -- .../apps/cert-manager/kustomization.yaml.j2 | 11 - .../apps/cert-manager/namespace.yaml.j2 | 7 - .../apps/default/echo/app/helmrelease.yaml.j2 | 31 -- .../default/echo/app/kustomization.yaml.j2 | 6 - .../default/echo/app/ocirepository.yaml.j2 | 13 - .../kubernetes/apps/default/echo/ks.yaml.j2 | 19 - .../apps/default/kustomization.yaml.j2 | 11 - .../kubernetes/apps/default/namespace.yaml.j2 | 7 - .../flux-instance/app/helmrelease.yaml.j2 | 136 ------ .../flux-instance/app/httproute.yaml.j2 | 22 - .../flux-instance/app/kustomization.yaml.j2 | 11 - .../flux-instance/app/ocirepository.yaml.j2 | 13 - .../flux-instance/app/receiver.yaml.j2 | 21 - .../flux-instance/app/secret.sops.yaml.j2 | 9 - .../apps/flux-system/flux-instance/ks.yaml.j2 | 21 - .../flux-operator/app/helmrelease.yaml.j2 | 13 - .../flux-operator/app/kustomization.yaml.j2 | 6 - .../flux-operator/app/ocirepository.yaml.j2 | 13 - .../apps/flux-system/flux-operator/ks.yaml.j2 | 19 - .../apps/flux-system/kustomization.yaml.j2 | 12 - .../apps/flux-system/namespace.yaml.j2 | 7 - .../cilium/app/helmrelease.yaml.j2 | 91 ---- .../cilium/app/kustomization.yaml.j2 | 7 - .../kube-system/cilium/app/networks.yaml.j2 | 71 --- .../cilium/app/ocirepository.yaml.j2 | 13 - .../apps/kube-system/cilium/ks.yaml.j2 | 19 - .../coredns/app/helmrelease.yaml.j2 | 69 --- .../coredns/app/kustomization.yaml.j2 | 6 - .../coredns/app/ocirepository.yaml.j2 | 13 - .../apps/kube-system/coredns/ks.yaml.j2 | 19 - .../apps/kube-system/kustomization.yaml.j2 | 17 - .../metrics-server/app/helmrelease.yaml.j2 | 21 - .../metrics-server/app/kustomization.yaml.j2 | 6 - .../metrics-server/app/ocirepository.yaml.j2 | 13 - .../kube-system/metrics-server/ks.yaml.j2 | 19 - .../apps/kube-system/namespace.yaml.j2 | 7 - .../reloader/app/helmrelease.yaml.j2 | 17 - .../reloader/app/kustomization.yaml.j2 | 6 - .../reloader/app/ocirepository.yaml.j2 | 13 - .../apps/kube-system/reloader/ks.yaml.j2 | 19 - .../spegel/app/helmrelease.yaml.j2 | 21 - .../spegel/app/kustomization.yaml.j2 | 8 - .../spegel/app/ocirepository.yaml.j2 | 15 - .../apps/kube-system/spegel/ks.yaml.j2 | 21 - .../cloudflare-dns/app/helmrelease.yaml.j2 | 37 -- .../cloudflare-dns/app/kustomization.yaml.j2 | 9 - .../cloudflare-dns/app/ocirepository.yaml.j2 | 15 - .../cloudflare-dns/app/secret.sops.yaml.j2 | 9 - .../apps/network/cloudflare-dns/ks.yaml.j2 | 21 - .../cloudflare-tunnel/app/dnsendpoint.yaml.j2 | 12 - .../cloudflare-tunnel/app/helmrelease.yaml.j2 | 86 ---- .../app/kustomization.yaml.j2 | 10 - .../app/ocirepository.yaml.j2 | 15 - .../cloudflare-tunnel/app/secret.sops.yaml.j2 | 9 - .../apps/network/cloudflare-tunnel/ks.yaml.j2 | 21 - .../envoy-gateway/app/certificate.yaml.j2 | 18 - .../network/envoy-gateway/app/envoy.yaml.j2 | 174 ------- .../envoy-gateway/app/helmrelease.yaml.j2 | 20 - .../envoy-gateway/app/kustomization.yaml.j2 | 9 - .../envoy-gateway/app/ocirepository.yaml.j2 | 13 - .../envoy-gateway/app/podmonitor.yaml.j2 | 18 - .../apps/network/envoy-gateway/ks.yaml.j2 | 19 - .../k8s-gateway/app/helmrelease.yaml.j2 | 21 - .../k8s-gateway/app/kustomization.yaml.j2 | 6 - .../k8s-gateway/app/ocirepository.yaml.j2 | 13 - .../apps/network/k8s-gateway/ks.yaml.j2 | 19 - .../apps/network/kustomization.yaml.j2 | 18 - .../kubernetes/apps/network/namespace.yaml.j2 | 7 - .../sops/cluster-secrets.sops.yaml.j2 | 7 - .../components/sops/kustomization.yaml.j2 | 5 - .../config/kubernetes/flux/cluster/ks.yaml.j2 | 57 --- template/config/kubernetes/mod.just | 10 - template/config/talos/README.md.j2 | 20 - .../config/talos/all/00-install.yaml.tpl.j2 | 9 - .../config/talos/all/01-hostname.yaml.tpl.j2 | 4 - template/config/talos/all/10-cluster.yaml.j2 | 10 - .../talos/all/20-network-links.yaml.tpl.j2 | 41 -- template/config/talos/all/21-network.yaml.j2 | 8 - template/config/talos/all/22-time.yaml.j2 | 7 - template/config/talos/all/30-kubelet.yaml.j2 | 16 - template/config/talos/all/40-sysctls.yaml.j2 | 12 - template/config/talos/all/50-files.yaml.j2 | 8 - .../talos/all/60-encryption.yaml.tpl.j2 | 20 - .../talos/all/61-kernel-modules.yaml.tpl.j2 | 6 - template/config/talos/all/70-security.yaml.j2 | 7 - .../config/talos/all/71-filesystem.yaml.j2 | 9 - .../talos/control-plane/00-cluster.yaml.j2 | 62 --- template/config/talos/mod.just | 64 --- template/config/talos/topf.yaml.j2 | 33 -- template/mod.just | 182 -------- template/overrides/readme.partial.yaml.j2 | 5 - template/resources/kubeconform.sh | 50 -- template/scripts/plugin.py | 150 ------ template/scripts/test_validate.py | 173 ------- template/scripts/validate.py | 402 ---------------- uv.lock | 365 --------------- 156 files changed, 6158 deletions(-) delete mode 100644 .github/template-tests/e2e/cilium-network-policy.yaml delete mode 100755 .github/template-tests/e2e/cluster.sh delete mode 100644 .github/template-tests/e2e/cluster.toml.tmpl delete mode 100644 .github/template-tests/e2e/network.yaml.tmpl delete mode 100644 .github/template-tests/e2e/node.toml.tmpl delete mode 100644 .github/template-tests/e2e/sops-secret.yaml.tmpl delete mode 100644 .github/template-tests/e2e/talos-cluster.yaml delete mode 100644 .github/template-tests/invalid/bad-bgp-asn.toml delete mode 100644 .github/template-tests/invalid/bad-mac-address.toml delete mode 100644 .github/template-tests/invalid/bad-repo-url.toml delete mode 100644 .github/template-tests/invalid/bad-vlan-tag.toml delete mode 100644 .github/template-tests/invalid/duplicate-gateway-addrs.toml delete mode 100644 .github/template-tests/invalid/duplicate-node-names.toml delete mode 100644 .github/template-tests/invalid/gateway-node-collision.toml delete mode 100644 .github/template-tests/invalid/missing-dns-token.toml delete mode 100644 .github/template-tests/invalid/missing-external-gateway.toml delete mode 100644 .github/template-tests/invalid/missing-known-hosts.toml delete mode 100644 .github/template-tests/invalid/missing-schematic.toml delete mode 100644 .github/template-tests/invalid/nested-cidr-overlap.toml delete mode 100644 .github/template-tests/invalid/node-addr-outside-cidr.toml delete mode 100644 .github/template-tests/invalid/node-uses-gateway-addr.toml delete mode 100644 .github/template-tests/invalid/non-canonical-cidr.toml delete mode 100644 .github/template-tests/invalid/overlapping-cidrs.toml delete mode 100644 .github/template-tests/invalid/partial-bgp.toml delete mode 100644 .github/template-tests/invalid/reserved-node-name.toml delete mode 100644 .github/template-tests/invalid/tiny-svc-cidr.toml delete mode 100644 .github/template-tests/invalid/tunnel-without-dns.toml delete mode 100644 .github/template-tests/valid/direct.toml delete mode 100644 .github/template-tests/valid/internal.toml delete mode 100644 .github/template-tests/valid/multi-controller.toml delete mode 100644 .github/template-tests/valid/no-webhook.toml delete mode 100644 .github/template-tests/valid/private.toml delete mode 100644 .github/template-tests/valid/public.toml delete mode 100644 .github/template-tests/valid/selfhosted.toml delete mode 100644 .github/template-tests/valid/single-node.toml delete mode 100644 .github/workflows/template-e2e-cluster.yaml delete mode 100644 .github/workflows/template-e2e.yaml delete mode 100644 .github/workflows/template-release.yaml delete mode 100644 .mise/conf.d/template.toml delete mode 100644 cluster.sample.toml delete mode 100644 cluster.schema.json delete mode 100644 makejinja.toml delete mode 100644 pyproject.toml delete mode 100644 template/config/.sops.yaml.j2 delete mode 100644 template/config/bootstrap/deploy-key.sops.yaml.j2 delete mode 100644 template/config/bootstrap/helmfile/apps.yaml.j2 delete mode 100644 template/config/bootstrap/helmfile/crds.yaml.j2 delete mode 100644 template/config/bootstrap/helmfile/default.yaml.j2 delete mode 100644 template/config/bootstrap/helmfile/templates/release.yaml.gotmpl.j2 delete mode 100644 template/config/bootstrap/helmfile/templates/values.yaml.gotmpl.j2 delete mode 100644 template/config/bootstrap/mod.just delete mode 100644 template/config/bootstrap/sops-age.sops.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/cert-manager/app/clusterissuer.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/cert-manager/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/cert-manager/app/secret.sops.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/cert-manager/namespace.yaml.j2 delete mode 100644 template/config/kubernetes/apps/default/echo/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/default/echo/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/default/echo/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/default/echo/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/default/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/default/namespace.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/app/httproute.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/app/receiver.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/app/secret.sops.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-instance/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-operator/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-operator/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-operator/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/flux-operator/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/flux-system/namespace.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/cilium/app/networks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/cilium/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/cilium/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/coredns/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/coredns/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/metrics-server/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/namespace.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/reloader/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/reloader/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/spegel/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/kube-system/spegel/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-dns/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-dns/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-dns/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-dns/app/secret.sops.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-dns/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-tunnel/app/dnsendpoint.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-tunnel/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-tunnel/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-tunnel/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-tunnel/app/secret.sops.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/cloudflare-tunnel/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/app/certificate.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/app/envoy.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/app/podmonitor.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/envoy-gateway/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/k8s-gateway/app/ocirepository.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/apps/network/namespace.yaml.j2 delete mode 100644 template/config/kubernetes/components/sops/cluster-secrets.sops.yaml.j2 delete mode 100644 template/config/kubernetes/components/sops/kustomization.yaml.j2 delete mode 100644 template/config/kubernetes/flux/cluster/ks.yaml.j2 delete mode 100644 template/config/kubernetes/mod.just delete mode 100644 template/config/talos/README.md.j2 delete mode 100644 template/config/talos/all/00-install.yaml.tpl.j2 delete mode 100644 template/config/talos/all/01-hostname.yaml.tpl.j2 delete mode 100644 template/config/talos/all/10-cluster.yaml.j2 delete mode 100644 template/config/talos/all/20-network-links.yaml.tpl.j2 delete mode 100644 template/config/talos/all/21-network.yaml.j2 delete mode 100644 template/config/talos/all/22-time.yaml.j2 delete mode 100644 template/config/talos/all/30-kubelet.yaml.j2 delete mode 100644 template/config/talos/all/40-sysctls.yaml.j2 delete mode 100644 template/config/talos/all/50-files.yaml.j2 delete mode 100644 template/config/talos/all/60-encryption.yaml.tpl.j2 delete mode 100644 template/config/talos/all/61-kernel-modules.yaml.tpl.j2 delete mode 100644 template/config/talos/all/70-security.yaml.j2 delete mode 100644 template/config/talos/all/71-filesystem.yaml.j2 delete mode 100644 template/config/talos/control-plane/00-cluster.yaml.j2 delete mode 100644 template/config/talos/mod.just delete mode 100644 template/config/talos/topf.yaml.j2 delete mode 100644 template/mod.just delete mode 100644 template/overrides/readme.partial.yaml.j2 delete mode 100644 template/resources/kubeconform.sh delete mode 100644 template/scripts/plugin.py delete mode 100644 template/scripts/test_validate.py delete mode 100644 template/scripts/validate.py delete mode 100644 uv.lock diff --git a/.github/template-tests/e2e/cilium-network-policy.yaml b/.github/template-tests/e2e/cilium-network-policy.yaml deleted file mode 100644 index c3e02bf..0000000 --- a/.github/template-tests/e2e/cilium-network-policy.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: cilium.io/v2 -kind: CiliumNetworkPolicy -metadata: - name: e2e-deny-server - namespace: default -spec: - endpointSelector: - matchLabels: - app: e2e-network-server - ingress: - - {} diff --git a/.github/template-tests/e2e/cluster.sh b/.github/template-tests/e2e/cluster.sh deleted file mode 100755 index 601f637..0000000 --- a/.github/template-tests/e2e/cluster.sh +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env bash -# Full-fidelity bootstrap e2e: takes maintenance-mode Talos VMs, discovers -# their hardware the same way the README instructs users to, writes a -# cluster.toml from the discovered facts, and runs the template's real -# bootstrap flow against them. -# -# Two provisioning paths share this test body: -# - CI: talosctl-cluster-action boots the nodes (talos-cluster.yaml) and -# passes E2E_CONTROLPLANE_IPS / E2E_WORKER_IPS / E2E_CIDR; the action's -# post step destroys them. -# - Local: run with no env set; the script boots and destroys the cluster -# itself. Requires Docker, /dev/kvm, passwordless sudo, qemu-system-x86, -# and the repo's mise toolchain on PATH. -# -# Renders into the working tree like any configure run. -set -euo pipefail - -NAME="${E2E_NAME:-template-e2e}" -MODE="${1:-all}" -E2E_DIR=".github/template-tests/e2e" -CIDR="${E2E_CIDR:-10.9.0.0/24}" -PREFIX="${CIDR%/*}" -PREFIX="${PREFIX%.*}" -TALOSCTL="$(command -v talosctl)" -# The Image Factory vanilla schematic, matching the ISO the nodes boot from. -SCHEMATIC="376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -if [ -n "${E2E_CONTROLPLANE_IPS:-}" ]; then - PROVISIONED=true - IFS=',' read -r -a CONTROLPLANES <<< "$E2E_CONTROLPLANE_IPS" - IFS=',' read -r -a WORKERS <<< "${E2E_WORKER_IPS:-}" -else - PROVISIONED=false - CONTROLPLANES=("$PREFIX.2") - WORKERS=("$PREFIX.3") -fi -NODES=("${CONTROLPLANES[@]}" "${WORKERS[@]}") -# The VMs reach the host at the gateway address; the rendered workspace is -# served from there over git smart HTTP so Flux can sync it. -GIT_HOST="${E2E_GATEWAY:-$PREFIX.1}" -GIT_PORT=8418 -GIT_SERVER_CONTAINER="" - -# The provisioner runs under sudo and writes state relative to its cwd and -# TALOSCONFIG, so both are pointed at a scratch dir to keep root-owned files -# out of the repo. -if [ -n "${E2E_STATE:-}" ]; then - STATE="$E2E_STATE" - STATE_OWNED=false -else - STATE="$(mktemp -d)" - STATE_OWNED=true -fi -mkdir -p "$STATE" -GIT_PUSH_URL="http://127.0.0.1:$GIT_PORT/repo.git" - -cleanup() { - rc=$? - if [ "$rc" -ne 0 ]; then - echo "==> e2e failed (rc=$rc), collecting diagnostics" - kubectl get pods --all-namespaces 2>/dev/null || true - kubectl get gitrepositories,kustomizations,helmreleases --all-namespaces 2>/dev/null || true - kubectl get events --all-namespaces --sort-by=.lastTimestamp 2>/dev/null | tail -30 || true - [ -n "$GIT_SERVER_CONTAINER" ] && docker logs --tail 5 "$GIT_SERVER_CONTAINER" 2>/dev/null || true - for ip in "${NODES[@]}"; do - talosctl -n "$ip" dmesg 2>/dev/null | tail -20 || true - done - fi - if [ "$MODE" = all ]; then - [ -n "$GIT_SERVER_CONTAINER" ] && docker stop "$GIT_SERVER_CONTAINER" >/dev/null 2>&1 || true - fi - if [ "$MODE" = all ] && [ "$PROVISIONED" = false ]; then - (cd "$STATE" && sudo -E env TALOSCONFIG="$STATE/talosconfig" \ - "$TALOSCTL" cluster destroy --name "$NAME" --provisioner qemu >/dev/null 2>&1) || true - fi - if [ "$MODE" = all ] && [ "$STATE_OWNED" = true ]; then - sudo rm -rf "$STATE" || true - fi - exit "$rc" -} -trap cleanup EXIT - -start_local_git_server() { - GIT_SERVER_CONTAINER="$NAME-git" - docker run --detach --rm --name "$GIT_SERVER_CONTAINER" \ - --publish "$GIT_PORT:23232" \ - --env SOFT_SERVE_GIT_ENABLED=false \ - --env SOFT_SERVE_LFS_ENABLED=false \ - --env SOFT_SERVE_SSH_LISTEN_ADDR=127.0.0.1:23231 \ - --env SOFT_SERVE_STATS_ENABLED=false \ - --entrypoint /bin/sh \ - ghcr.io/charmbracelet/soft-serve:v0.11.6 \ - -c 'set -eu; ssh-keygen -q -t ed25519 -N "" -f /tmp/admin; export SOFT_SERVE_INITIAL_ADMIN_KEYS="$(cat /tmp/admin.pub)"; /usr/local/bin/soft serve & pid=$!; until ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost settings anon-access read-write; do sleep 1; done; ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost repo create repo; wait "$pid"' \ - >/dev/null - deadline=$((SECONDS + 60)) - until git ls-remote "$GIT_PUSH_URL" >/dev/null 2>&1; do - if (( SECONDS >= deadline )); then - just log fatal "Soft Serve is not reachable" - fi - sleep 1 - done -} - -prepare() { -# In CI the action itself waits for every node's maintenance API before -# returning, so the poll here covers only the local path, where cluster -# create returns as soon as the VMs launch. -if [ "$PROVISIONED" = false ]; then - echo "==> booting maintenance-mode nodes" - (cd "$STATE" && sudo -E env TALOSCONFIG="$STATE/talosconfig" \ - "$TALOSCTL" cluster create qemu --name "$NAME" --presets iso,maintenance \ - --controlplanes 1 --workers 1 --cidr "$CIDR" \ - --memory-controlplanes 4GiB --memory-workers 3GiB) - - echo "==> waiting for the maintenance API" - for ip in "${NODES[@]}"; do - until talosctl -n "$ip" get links --insecure >/dev/null 2>&1; do sleep 5; done - done -fi - -echo "==> discovering node hardware" -declare -A MACS DISKS -for ip in "${NODES[@]}"; do - MACS[$ip]="$(talosctl -n "$ip" get links --insecure -o json \ - | jq -r 'select(.spec.type == "ether" and .spec.operationalState == "up" and (.metadata.id | startswith("bond") | not)) | .spec.hardwareAddr' | head -1)" - DISKS[$ip]="/dev/$(talosctl -n "$ip" get disks --insecure -o json \ - | jq -r 'select(.spec.readonly == false and (.metadata.id | startswith("loop") | not)) | .metadata.id' | head -1)" - echo " $ip mac=${MACS[$ip]} disk=${DISKS[$ip]}" -done - -echo "==> generating cluster.toml" -export E2E_CIDR="$CIDR" -export E2E_GATEWAY="${E2E_GATEWAY:-$PREFIX.1}" -export E2E_GIT_HOST="$GIT_HOST" -export E2E_GIT_PORT="$GIT_PORT" -export E2E_PREFIX="$PREFIX" -export E2E_SCHEMATIC="$SCHEMATIC" -envsubst '${E2E_CIDR} ${E2E_GATEWAY} ${E2E_GIT_HOST} ${E2E_GIT_PORT} ${E2E_PREFIX} ${E2E_SCHEMATIC}' \ - < "$E2E_DIR/cluster.toml.tmpl" > cluster.toml -index=0 -for ip in "${NODES[@]}"; do - controller=false - for cp in "${CONTROLPLANES[@]}"; do [ "$ip" = "$cp" ] && controller=true; done - export E2E_NODE_NAME="e2e-$index" - export E2E_NODE_ADDRESS="$ip" - export E2E_NODE_CONTROLLER="$controller" - export E2E_NODE_DISK="${DISKS[$ip]}" - export E2E_NODE_MAC="${MACS[$ip]}" - envsubst '${E2E_NODE_ADDRESS} ${E2E_NODE_CONTROLLER} ${E2E_NODE_DISK} ${E2E_NODE_MAC} ${E2E_NODE_NAME}' \ - < "$E2E_DIR/node.toml.tmpl" >> cluster.toml - index=$((index + 1)) -done - -echo "==> configure" -just init -just configure - -# Flux's FluxInstance only reports Ready once its Git sync succeeds, so the -# rendered kubernetes/ tree is committed to a bare repo and served to the -# cluster — the same push-then-bootstrap flow the README walks users through. -echo "==> publishing rendered repo" -mkdir -p "$STATE/gitwork" -cp -r kubernetes "$STATE/gitwork/" -git -C "$STATE/gitwork" init --quiet --initial-branch main -git -C "$STATE/gitwork" add --all -git -C "$STATE/gitwork" -c user.name=e2e -c user.email=e2e@cluster.local \ - commit --quiet --message "rendered workspace" -git -C "$STATE/gitwork" push --quiet "$GIT_PUSH_URL" main -} - -assert_cluster_health() { -echo "==> asserting cluster health" -kubectl wait nodes --all --for=condition=Ready --timeout=10m -for ns in kube-system cert-manager flux-system; do - kubectl wait pods --namespace "$ns" --all --for=condition=Ready --timeout=10m -done - -echo "==> asserting flux reconciliation" -kubectl wait fluxinstance/flux --namespace flux-system --for=condition=Ready --timeout=10m -kubectl wait gitrepositories --all --all-namespaces --for=condition=Ready --timeout=5m -kubectl wait kustomizations --all --all-namespaces --for=condition=Ready --timeout=10m -kubectl wait helmreleases --all --all-namespaces --for=condition=Ready --timeout=10m -} - -foundation() { -deadline=$((SECONDS + 60)) -until git ls-remote "http://$GIT_HOST:$GIT_PORT/repo.git" >/dev/null 2>&1; do - if (( SECONDS >= deadline )); then - just log fatal "Rendered repository server is not reachable" - fi - sleep 1 -done -echo "==> bootstrap talos" -just bootstrap talos - -echo "==> bootstrap apps" -just bootstrap apps -assert_cluster_health - -echo "==> asserting bootstrap idempotency" -just configure -just bootstrap talos -just bootstrap apps -assert_cluster_health -} - -flux_sops() { -echo "==> asserting Flux SOPS decryption" -SOPS_SECRET="$STATE/gitwork/kubernetes/apps/default/e2e-sops.sops.yaml" -export E2E_SOPS_VALUE=flux-decrypted -envsubst '${E2E_SOPS_VALUE}' < "$E2E_DIR/sops-secret.yaml.tmpl" > "$SOPS_SECRET" -sops encrypt --filename-override kubernetes/apps/default/e2e-sops.sops.yaml \ - --in-place "$SOPS_SECRET" -yq --inplace '.resources += ["./e2e-sops.sops.yaml"]' \ - "$STATE/gitwork/kubernetes/apps/default/kustomization.yaml" -git -C "$STATE/gitwork" add --all -git -C "$STATE/gitwork" -c user.name=e2e -c user.email=e2e@cluster.local \ - commit --quiet --message "test Flux SOPS decryption" -git -C "$STATE/gitwork" push --quiet "$GIT_PUSH_URL" main -flux reconcile kustomization cluster-apps --with-source --timeout=10m -test "$(kubectl get secret e2e-sops --namespace default \ - --output jsonpath='{.data.value}' | base64 --decode)" = "flux-decrypted" -} - -networking() { -echo "==> asserting pod networking and DNS" -export E2E_CONTROLPLANE_NODE="$(kubectl get nodes \ - --selector=node-role.kubernetes.io/control-plane \ - --output jsonpath='{.items[0].metadata.name}')" -export E2E_WORKER_NODE="$(kubectl get nodes \ - --selector='!node-role.kubernetes.io/control-plane' \ - --output jsonpath='{.items[0].metadata.name}')" -NETWORK_CONFIG="$STATE/network.yaml" -envsubst '${E2E_CONTROLPLANE_NODE} ${E2E_WORKER_NODE}' \ - < "$E2E_DIR/network.yaml.tmpl" > "$NETWORK_CONFIG" -kubectl apply --filename "$NETWORK_CONFIG" -kubectl wait pods/e2e-network-server pods/e2e-network-client \ - --namespace default --for=condition=Ready --timeout=5m -SERVER_IP="$(kubectl get pod e2e-network-server --namespace default \ - --output jsonpath='{.status.podIP}')" -kubectl exec --namespace default e2e-network-client -- \ - /agnhost connect --timeout=10s "$SERVER_IP:8080" -kubectl exec --namespace default e2e-network-client -- \ - /agnhost connect --timeout=10s e2e-network-server.default.svc.cluster.local:8080 -kubectl exec --namespace default e2e-network-client -- \ - /agnhost connect --timeout=10s github.com:443 -kubectl apply --filename "$E2E_DIR/cilium-network-policy.yaml" -deadline=$((SECONDS + 60)) -while kubectl exec --namespace default e2e-network-client -- \ - /agnhost connect --timeout=2s "$SERVER_IP:8080" &>/dev/null; do - if (( SECONDS >= deadline )); then - just log fatal "CiliumNetworkPolicy did not block pod traffic" - fi - sleep 2 -done -kubectl delete ciliumnetworkpolicy e2e-deny-server --namespace default -deadline=$((SECONDS + 60)) -until kubectl exec --namespace default e2e-network-client -- \ - /agnhost connect --timeout=2s "$SERVER_IP:8080" &>/dev/null; do - if (( SECONDS >= deadline )); then - just log fatal "Pod traffic did not recover after removing CiliumNetworkPolicy" - fi - sleep 2 -done -} - -summary() { -kubectl get nodes --output wide -kubectl get kustomizations,helmreleases --all-namespaces -echo "==> e2e bootstrap succeeded" -} - -case "$MODE" in - prepare) prepare ;; - foundation) foundation ;; - flux-sops) flux_sops ;; - networking) networking ;; - summary) summary ;; - all) - start_local_git_server - prepare - foundation - flux_sops - networking - summary - ;; - *) - echo "usage: $0 {prepare|foundation|flux-sops|networking|summary|all}" >&2 - exit 2 - ;; -esac diff --git a/.github/template-tests/e2e/cluster.toml.tmpl b/.github/template-tests/e2e/cluster.toml.tmpl deleted file mode 100644 index 387724a..0000000 --- a/.github/template-tests/e2e/cluster.toml.tmpl +++ /dev/null @@ -1,22 +0,0 @@ -[network] -node_cidr = "${E2E_CIDR}" -default_gateway = "${E2E_GATEWAY}" - -[kubernetes.api] -addr = "${E2E_PREFIX}.100" - -[gateways] -internal = "${E2E_PREFIX}.101" -dns = "${E2E_PREFIX}.102" - -[domain] -name = "e2e.example.com" - -[dns] -provider = "none" - -[repository] -url = "http://${E2E_GIT_HOST}:${E2E_GIT_PORT}/repo.git" - -[talos] -schematic_id = "${E2E_SCHEMATIC}" diff --git a/.github/template-tests/e2e/network.yaml.tmpl b/.github/template-tests/e2e/network.yaml.tmpl deleted file mode 100644 index 07a862c..0000000 --- a/.github/template-tests/e2e/network.yaml.tmpl +++ /dev/null @@ -1,42 +0,0 @@ ---- -apiVersion: v1 -kind: Pod -metadata: - name: e2e-network-server - namespace: default - labels: - app: e2e-network-server -spec: - nodeName: "${E2E_WORKER_NODE}" - containers: - - name: server - image: registry.k8s.io/e2e-test-images/agnhost:2.66.0 - args: ["netexec", "--http-port=8080"] - ports: - - name: http - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - name: e2e-network-server - namespace: default -spec: - selector: - app: e2e-network-server - ports: - - name: http - port: 8080 - targetPort: http ---- -apiVersion: v1 -kind: Pod -metadata: - name: e2e-network-client - namespace: default -spec: - nodeName: "${E2E_CONTROLPLANE_NODE}" - containers: - - name: client - image: registry.k8s.io/e2e-test-images/agnhost:2.66.0 - args: ["pause"] diff --git a/.github/template-tests/e2e/node.toml.tmpl b/.github/template-tests/e2e/node.toml.tmpl deleted file mode 100644 index 3ebf9e2..0000000 --- a/.github/template-tests/e2e/node.toml.tmpl +++ /dev/null @@ -1,7 +0,0 @@ - -[[nodes]] -name = "${E2E_NODE_NAME}" -address = "${E2E_NODE_ADDRESS}" -controller = ${E2E_NODE_CONTROLLER} -disk = "${E2E_NODE_DISK}" -mac_addr = "${E2E_NODE_MAC}" diff --git a/.github/template-tests/e2e/sops-secret.yaml.tmpl b/.github/template-tests/e2e/sops-secret.yaml.tmpl deleted file mode 100644 index 0dec0ec..0000000 --- a/.github/template-tests/e2e/sops-secret.yaml.tmpl +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - name: e2e-sops - namespace: default -stringData: - value: "${E2E_SOPS_VALUE}" diff --git a/.github/template-tests/e2e/talos-cluster.yaml b/.github/template-tests/e2e/talos-cluster.yaml deleted file mode 100644 index 2ad1566..0000000 --- a/.github/template-tests/e2e/talos-cluster.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/home-operations/talosctl-cluster-action/main/schema/talos-cluster.json -# Maintenance-mode nodes for the bootstrap e2e: the action boots and destroys -# them, and cluster.sh exercises the template's real bootstrap flow against -# the unconfigured nodes. -apiVersion: v1alpha1 -kind: TalosCluster -metadata: - name: template-e2e -spec: - controlplanes: - count: 1 - memory: 4GiB - workers: - count: 1 - memory: 3GiB - network: - cidr: 10.9.0.0/24 - qemu: - presets: [iso, maintenance] - disks: [virtio:10GiB] diff --git a/.github/template-tests/invalid/bad-bgp-asn.toml b/.github/template-tests/invalid/bad-bgp-asn.toml deleted file mode 100644 index abd68d8..0000000 --- a/.github/template-tests/invalid/bad-bgp-asn.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Negative fixture: BGP router ASN above the 32-bit ASN range. -# Expected to be rejected by _router_asn_in_range. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[cilium.bgp] -router_addr = "10.10.1.1" -router_asn = "4294967296" -node_asn = "64514" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/bad-mac-address.toml b/.github/template-tests/invalid/bad-mac-address.toml deleted file mode 100644 index d623202..0000000 --- a/.github/template-tests/invalid/bad-mac-address.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: malformed MAC address (uppercase + missing colons). -# Expected to be rejected by the #Node.mac_addr regex constraint. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "AABBCCDDEEFF" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/bad-repo-url.toml b/.github/template-tests/invalid/bad-repo-url.toml deleted file mode 100644 index 2ab4a7e..0000000 --- a/.github/template-tests/invalid/bad-repo-url.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Negative fixture: scp-style git URL ("git@host:owner/repo.git") instead of -# the canonical https:// or ssh://git@ form. -# Expected to be rejected by the repository.url pattern. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "git@github.com:onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/bad-vlan-tag.toml b/.github/template-tests/invalid/bad-vlan-tag.toml deleted file mode 100644 index 846a794..0000000 --- a/.github/template-tests/invalid/bad-vlan-tag.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Negative fixture: vlan_tag outside the valid 1-4094 range. -# Expected to be rejected by _vlan_tag_in_range. -[network] -node_cidr = "10.10.10.0/24" -vlan_tag = "5000" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/duplicate-gateway-addrs.toml b/.github/template-tests/invalid/duplicate-gateway-addrs.toml deleted file mode 100644 index 2f0be1a..0000000 --- a/.github/template-tests/invalid/duplicate-gateway-addrs.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: gateways.internal == gateways.dns. -# Expected to be rejected by `_addrs_check` (list.UniqueItems). -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.252" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/duplicate-node-names.toml b/.github/template-tests/invalid/duplicate-node-names.toml deleted file mode 100644 index 4836923..0000000 --- a/.github/template-tests/invalid/duplicate-node-names.toml +++ /dev/null @@ -1,37 +0,0 @@ -# Negative fixture: two nodes with the same name. -# Expected to be rejected by `_node_name_check` (list.UniqueItems). -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/gateway-node-collision.toml b/.github/template-tests/invalid/gateway-node-collision.toml deleted file mode 100644 index c068e0c..0000000 --- a/.github/template-tests/invalid/gateway-node-collision.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: a node reuses the internal gateway VIP. -# Expected to be rejected by _addr_uniqueness_check. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.252" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/missing-dns-token.toml b/.github/template-tests/invalid/missing-dns-token.toml deleted file mode 100644 index 352d923..0000000 --- a/.github/template-tests/invalid/missing-dns-token.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: dns.provider "cloudflare" (the default) without a token. -# Expected to be rejected by the Dns validator. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[domain] -name = "example.com" - -[dns] -provider = "cloudflare" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/missing-external-gateway.toml b/.github/template-tests/invalid/missing-external-gateway.toml deleted file mode 100644 index 1b996e3..0000000 --- a/.github/template-tests/invalid/missing-external-gateway.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Negative fixture: cloudflare-tunnel ingress without gateways.external. -# Expected to be rejected by the Config cross-check. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/missing-known-hosts.toml b/.github/template-tests/invalid/missing-known-hosts.toml deleted file mode 100644 index 2fe0595..0000000 --- a/.github/template-tests/invalid/missing-known-hosts.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Negative fixture: ssh:// URL to a host without bundled SSH host keys -# (github.com/gitlab.com/codeberg.org) and no repository.known_hosts set. -# Expected to be rejected by the conditional known_hosts constraint. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "ssh://git@git.example.com/k8s/home-ops.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/missing-schematic.toml b/.github/template-tests/invalid/missing-schematic.toml deleted file mode 100644 index fb08541..0000000 --- a/.github/template-tests/invalid/missing-schematic.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Negative fixture: a node without schematic_id and no [talos] default. -# Expected to be rejected by the schematic resolution in Config. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" diff --git a/.github/template-tests/invalid/nested-cidr-overlap.toml b/.github/template-tests/invalid/nested-cidr-overlap.toml deleted file mode 100644 index fa8a61e..0000000 --- a/.github/template-tests/invalid/nested-cidr-overlap.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Negative fixture: node_cidr is nested inside the default pod_cidr -# (10.42.0.0/16) without being string-equal to it. -# Expected to be rejected by _cidr_overlap_check. -[network] -node_cidr = "10.42.128.0/17" - -[kubernetes.api] -addr = "10.42.128.254" - -[gateways] -internal = "10.42.128.252" -dns = "10.42.128.253" -external = "10.42.128.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.42.128.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/node-addr-outside-cidr.toml b/.github/template-tests/invalid/node-addr-outside-cidr.toml deleted file mode 100644 index c161ffa..0000000 --- a/.github/template-tests/invalid/node-addr-outside-cidr.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: node address is not inside network.node_cidr. -# Expected to be rejected by _node_addrs_in_node_cidr. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "192.168.1.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/node-uses-gateway-addr.toml b/.github/template-tests/invalid/node-uses-gateway-addr.toml deleted file mode 100644 index 28dd9bd..0000000 --- a/.github/template-tests/invalid/node-uses-gateway-addr.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Negative fixture: a node claims the default gateway address (defaults to -# the first IP in node_cidr). -# Expected to be rejected by _addr_uniqueness_check. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.1" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/non-canonical-cidr.toml b/.github/template-tests/invalid/non-canonical-cidr.toml deleted file mode 100644 index a94acbc..0000000 --- a/.github/template-tests/invalid/non-canonical-cidr.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Negative fixture: node_cidr written with host bits set instead of the -# network address. -# Expected to be rejected by _cidr_canonical_check. -[network] -node_cidr = "10.10.10.5/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/overlapping-cidrs.toml b/.github/template-tests/invalid/overlapping-cidrs.toml deleted file mode 100644 index ec3f1e3..0000000 --- a/.github/template-tests/invalid/overlapping-cidrs.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: network.node_cidr overlaps the default kubernetes.pod_cidr (10.42.0.0/16). -# Expected to be rejected by `_cidrs_check` (list.UniqueItems). -[network] -node_cidr = "10.42.0.0/16" - -[kubernetes.api] -addr = "10.42.0.254" - -[gateways] -internal = "10.42.0.252" -dns = "10.42.0.253" -external = "10.42.0.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.42.0.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/partial-bgp.toml b/.github/template-tests/invalid/partial-bgp.toml deleted file mode 100644 index f9a5bd4..0000000 --- a/.github/template-tests/invalid/partial-bgp.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Negative fixture: two of the three BGP fields set; previously this -# silently left BGP disabled. -# Expected to be rejected by the Bgp all-or-nothing check. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[cilium.bgp] -router_addr = "10.10.1.1" -router_asn = "64513" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/reserved-node-name.toml b/.github/template-tests/invalid/reserved-node-name.toml deleted file mode 100644 index a961742..0000000 --- a/.github/template-tests/invalid/reserved-node-name.toml +++ /dev/null @@ -1,29 +0,0 @@ -# Negative fixture: a node name uses the reserved word "controller". -# Expected to be rejected by the #Node.name regex constraint. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "controller" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/tiny-svc-cidr.toml b/.github/template-tests/invalid/tiny-svc-cidr.toml deleted file mode 100644 index 54c641a..0000000 --- a/.github/template-tests/invalid/tiny-svc-cidr.toml +++ /dev/null @@ -1,33 +0,0 @@ -# Negative fixture: svc_cidr too small to contain the derived CoreDNS -# address (10th IP). -# Expected to be rejected by _coredns_addr_in_svc_cidr. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes] -svc_cidr = "10.43.0.0/29" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/invalid/tunnel-without-dns.toml b/.github/template-tests/invalid/tunnel-without-dns.toml deleted file mode 100644 index 93fd88f..0000000 --- a/.github/template-tests/invalid/tunnel-without-dns.toml +++ /dev/null @@ -1,32 +0,0 @@ -# Negative fixture: cloudflare-tunnel ingress with dns.provider "none". -# Expected to be rejected by the Config cross-check. -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[domain] -name = "example.com" - -[dns] -provider = "none" - -[ingress] -mode = "cloudflare-tunnel" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/valid/direct.toml b/.github/template-tests/valid/direct.toml deleted file mode 100644 index 0ab192d..0000000 --- a/.github/template-tests/valid/direct.toml +++ /dev/null @@ -1,38 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[ingress] -mode = "direct" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/valid/internal.toml b/.github/template-tests/valid/internal.toml deleted file mode 100644 index e3e33d3..0000000 --- a/.github/template-tests/valid/internal.toml +++ /dev/null @@ -1,34 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" - -[domain] -name = "example.com" - -[dns] -provider = "none" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/valid/multi-controller.toml b/.github/template-tests/valid/multi-controller.toml deleted file mode 100644 index 71eb652..0000000 --- a/.github/template-tests/valid/multi-controller.toml +++ /dev/null @@ -1,42 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" - -[repository] -url = "ssh://git@github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -provider = "none" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-2" -address = "10.10.10.102" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:02" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/valid/no-webhook.toml b/.github/template-tests/valid/no-webhook.toml deleted file mode 100644 index a907294..0000000 --- a/.github/template-tests/valid/no-webhook.toml +++ /dev/null @@ -1,36 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" -webhook_provider = "none" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/valid/private.toml b/.github/template-tests/valid/private.toml deleted file mode 100644 index b862618..0000000 --- a/.github/template-tests/valid/private.toml +++ /dev/null @@ -1,39 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "ssh://git@github.com/onedr0p/cluster-template.git" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" -mtu = 1500 -secureboot = true -encrypt_disk = true -kernel_modules = ["nvidia", "nvidia_uvm"] diff --git a/.github/template-tests/valid/public.toml b/.github/template-tests/valid/public.toml deleted file mode 100644 index 8c427c7..0000000 --- a/.github/template-tests/valid/public.toml +++ /dev/null @@ -1,59 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" -default_gateway = "10.10.10.1" -vlan_tag = "100" -dns_servers = ["1.1.1.1"] -ntp_servers = ["162.159.200.123"] - -[kubernetes] -pod_cidr = "10.42.0.0/16" -svc_cidr = "10.43.0.0/16" - -[kubernetes.api] -addr = "10.10.10.254" -tls_sans = ["example.com"] - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" -branch = "main" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[cilium] -loadbalancer_mode = "dsr" - -[cilium.bgp] -router_addr = "10.10.1.1" -router_asn = "64513" -node_asn = "64514" - -[talos] -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" -mtu = 1500 -secureboot = true -encrypt_disk = true -kernel_modules = ["nvidia", "nvidia_uvm"] diff --git a/.github/template-tests/valid/selfhosted.toml b/.github/template-tests/valid/selfhosted.toml deleted file mode 100644 index 0176450..0000000 --- a/.github/template-tests/valid/selfhosted.toml +++ /dev/null @@ -1,39 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" -external = "10.10.10.251" - -[repository] -url = "ssh://git@git.example.com/k8s/home-ops.git" -webhook_provider = "generic-hmac" -known_hosts = """ -git.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl -""" - -[domain] -name = "example.com" - -[dns] -token = "fake" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" - -[[nodes]] -name = "k8s-1" -address = "10.10.10.101" -controller = false -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:01" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/template-tests/valid/single-node.toml b/.github/template-tests/valid/single-node.toml deleted file mode 100644 index bda61ef..0000000 --- a/.github/template-tests/valid/single-node.toml +++ /dev/null @@ -1,26 +0,0 @@ -[network] -node_cidr = "10.10.10.0/24" - -[kubernetes.api] -addr = "10.10.10.254" - -[gateways] -internal = "10.10.10.252" -dns = "10.10.10.253" - -[domain] -name = "example.com" - -[dns] -provider = "none" - -[repository] -url = "https://github.com/onedr0p/cluster-template.git" - -[[nodes]] -name = "k8s-0" -address = "10.10.10.100" -controller = true -disk = "/dev/sdfake" -mac_addr = "00:00:00:00:00:00" -schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" diff --git a/.github/workflows/template-e2e-cluster.yaml b/.github/workflows/template-e2e-cluster.yaml deleted file mode 100644 index f22f9a9..0000000 --- a/.github/workflows/template-e2e-cluster.yaml +++ /dev/null @@ -1,103 +0,0 @@ ---- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json -name: "E2E Cluster" - -on: - workflow_dispatch: - pull_request: - branches: ["main"] - schedule: - - cron: "30 5 * * *" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: {} - -jobs: - bootstrap: - if: ${{ github.repository == 'onedr0p/cluster-template' }} - name: bootstrap (qemu) - runs-on: ubuntu-latest - permissions: - contents: read - services: - git: - image: ghcr.io/charmbracelet/soft-serve:v0.12.2 - env: - SOFT_SERVE_GIT_ENABLED: "false" - SOFT_SERVE_LFS_ENABLED: "false" - SOFT_SERVE_SSH_LISTEN_ADDR: "127.0.0.1:23231" - SOFT_SERVE_STATS_ENABLED: "false" - ports: - - 8418:23232 - entrypoint: /bin/sh - command: >- - -c "set -eu; ssh-keygen -q -t ed25519 -N '' -f /tmp/admin; - export SOFT_SERVE_INITIAL_ADMIN_KEYS=$(cat /tmp/admin.pub); - /usr/local/bin/soft serve & pid=$!; - until ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost settings anon-access read-write; do sleep 1; done; - ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost repo create repo; - wait $pid" - options: >- - --health-cmd "git ls-remote http://localhost:23232/repo.git" - --health-interval 2s - --health-timeout 2s - --health-retries 30 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Install QEMU - run: | - sudo apt-get update - sudo apt-get install --yes --no-install-recommends gettext-base qemu-system-x86 qemu-utils ovmf - - - name: Setup mise - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - - name: Boot maintenance-mode nodes - id: cluster - uses: home-operations/talosctl-cluster-action@fb6a31bf5de43218acc80d2e23958a16eee7380c # v0.2.2 - with: - config: ./.github/template-tests/e2e/talos-cluster.yaml - cache: true - - - name: Export cluster environment - env: - CONTROLPLANE_IPS: "${{ steps.cluster.outputs.controlplane-ips }}" - GATEWAY: "${{ steps.cluster.outputs.gateway }}" - WORKER_IPS: "${{ steps.cluster.outputs.worker-ips }}" - run: | - echo "E2E_CONTROLPLANE_IPS=$CONTROLPLANE_IPS" >> "$GITHUB_ENV" - echo "E2E_WORKER_IPS=$WORKER_IPS" >> "$GITHUB_ENV" - echo "E2E_GATEWAY=$GATEWAY" >> "$GITHUB_ENV" - echo "E2E_CIDR=10.9.0.0/24" >> "$GITHUB_ENV" - echo "E2E_STATE=$RUNNER_TEMP/template-e2e" >> "$GITHUB_ENV" - - - name: Prepare cluster - run: bash ./.github/template-tests/e2e/cluster.sh prepare - - - name: Build healthy cluster foundation - run: bash ./.github/template-tests/e2e/cluster.sh foundation - - - name: Test Flux SOPS - id: flux-sops - run: bash ./.github/template-tests/e2e/cluster.sh flux-sops - background: true - - - name: Test networking - id: networking - run: bash ./.github/template-tests/e2e/cluster.sh networking - background: true - - - name: Wait for E2E tests - wait: [flux-sops, networking] - - - name: Summarize cluster - run: bash ./.github/template-tests/e2e/cluster.sh summary diff --git a/.github/workflows/template-e2e.yaml b/.github/workflows/template-e2e.yaml deleted file mode 100644 index 097ad5c..0000000 --- a/.github/workflows/template-e2e.yaml +++ /dev/null @@ -1,176 +0,0 @@ ---- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json -name: "E2E" - -on: - workflow_dispatch: - push: - branches: - - main - pull_request: - branches: - - main - -concurrency: - group: ${{ github.workflow }}-${{ github.event.number || github.ref }} - cancel-in-progress: true - -permissions: {} - -jobs: - validate-invalid: - if: ${{ github.repository == 'onedr0p/cluster-template' }} - name: reject-invalid (${{ matrix.fixture }}) - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - fail-fast: false - matrix: - fixture: - - overlapping-cidrs - - nested-cidr-overlap - - non-canonical-cidr - - tiny-svc-cidr - - duplicate-gateway-addrs - - duplicate-node-names - - reserved-node-name - - bad-mac-address - - bad-repo-url - - missing-known-hosts - - node-addr-outside-cidr - - node-uses-gateway-addr - - gateway-node-collision - - bad-vlan-tag - - bad-bgp-asn - - missing-dns-token - - tunnel-without-dns - - missing-external-gateway - - missing-schematic - - partial-bgp - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Setup mise - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - - name: Assert schema rejects ${{ matrix.fixture }}.toml - run: | - fixture=./.github/template-tests/invalid/${{ matrix.fixture }}.toml - if uv run --quiet --locked --no-dev ./template/scripts/validate.py "$fixture" >/dev/null 2>&1; then - echo "::error::schema accepted invalid fixture ${{ matrix.fixture }} (expected rejection)" - exit 1 - fi - echo "schema correctly rejected ${{ matrix.fixture }}" - # Also surface the actual error message in the log for debuggability. - uv run --quiet --locked --no-dev ./template/scripts/validate.py "$fixture" || true - - validator-tests: - if: ${{ github.repository == 'onedr0p/cluster-template' }} - name: validator-tests - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Setup mise - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - - name: Run validator tests - run: uv run --quiet --locked pytest ./template/scripts/test_validate.py - - validate-valid: - if: ${{ github.repository == 'onedr0p/cluster-template' }} - name: accept-valid (${{ matrix.fixture }}) - runs-on: ubuntu-latest - permissions: - contents: read - strategy: - fail-fast: false - matrix: - fixture: - - public - - private - - selfhosted - - no-webhook - - internal - - direct - - single-node - - multi-controller - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Setup mise - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - with: - experimental: true - install_args: --locked - - - name: Run init recipe - run: just init - - - name: Prepare files - run: | - cp ./.github/template-tests/valid/${{ matrix.fixture }}.toml cluster.toml - echo '{"AccountTag":"fake","TunnelSecret":"fake","TunnelID":"fake"}' > cloudflare-tunnel.json - touch kubeconfig - - - name: Assert cluster.toml passes the JSON Schema - run: taplo check --schema "file://$PWD/cluster.schema.json" ./cluster.toml - - - name: Run configure recipe - run: just configure - - # Rendered output must already match the format-yaml pre-commit hook, - # otherwise every `just configure` shows up as formatting churn. - - name: Assert rendered output is formatted - run: oxfmt --check ./.sops.yaml ./bootstrap ./kubernetes ./talos - - - name: Install flate - uses: home-operations/flate/action@631b76b69c4e58c6f4d1cb01e23616fa61aebafa # v0.6.5 - with: - base: "" - - - name: Run flate test - run: flate test all -p ./kubernetes/flux/cluster - - - name: Render bootstrap helmfile charts - run: just template test-helmfile - - - name: Dry run bootstrap talos recipe - run: just --dry-run bootstrap talos - - - name: Create talos secret - run: just bootstrap talos-secret - - - name: Render talos configs - run: just talos render - - - name: Validate talos configs - run: | - for config in ./talos/rendered/*.yaml; do - talosctl validate --config "$config" --mode metal - done - - - name: Dry run bootstrap apps recipe - run: just --dry-run bootstrap apps - - - name: Run reset recipe - run: yes | just template reset - - - name: Run tidy recipe - run: yes | just template tidy diff --git a/.github/workflows/template-release.yaml b/.github/workflows/template-release.yaml deleted file mode 100644 index abb455e..0000000 --- a/.github/workflows/template-release.yaml +++ /dev/null @@ -1,58 +0,0 @@ ---- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json -name: "Release" - -on: - workflow_dispatch: - schedule: - - cron: 0 0 1 * * - -permissions: {} - -jobs: - release: - name: Release - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Get Previous Release Tag and Determine Next Tag - id: determine-next-tag - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - result-encoding: string - script: | - const { data: releases } = await github.rest.repos.listReleases({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 1, - }); - - let previousTag = "0.0.0"; // Default if no previous release exists - if (releases.length > 0) { - previousTag = releases[0].tag_name; - } - - const [previousMajor, previousMinor, previousPatch] = previousTag.split('.').map(Number); - const currentYear = new Date().getFullYear(); - const currentMonth = new Date().getMonth() + 1; // Months are 0-indexed in JavaScript - - const nextMajorMinor = `${currentYear}.${currentMonth}`; - let nextPatch; - - if (`${previousMajor}.${previousMinor}` === nextMajorMinor) { - console.log("Month release already exists for the year. Incrementing patch number by 1."); - nextPatch = previousPatch + 1; - } else { - console.log("Month release does not exist for the year. Starting with patch number 0."); - nextPatch = 0; - } - - return `${nextMajorMinor}.${nextPatch}`; - - - name: Create Release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ steps.determine-next-tag.outputs.result }} - run: gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --generate-notes diff --git a/.mise/conf.d/template.toml b/.mise/conf.d/template.toml deleted file mode 100644 index fa07c43..0000000 --- a/.mise/conf.d/template.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Tools only the template step needs; `just template tidy` archives this file. -[tools] -uv = "0.12.13" -sd = "1.1.0" -taplo = "0.10.0" diff --git a/.mise/mise.lock b/.mise/mise.lock index 25d4661..5aeb9c6 100644 --- a/.mise/mise.lock +++ b/.mise/mise.lock @@ -384,30 +384,6 @@ url = "https://nodejs.org/dist/v24.21.0/node-v24.21.0-darwin-x64.tar.gz" version = "0.67.0" backend = "npm:oxfmt" -[[tools.sd]] -version = "1.1.0" -backend = "aqua:chmln/sd" - -[tools.sd."platforms.linux-arm64"] -checksum = "sha256:ec8c93c0533ff21f4851d11566808d4082544baf063d9b96ea77c27e98b7cd99" -url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-aarch64-unknown-linux-musl.tar.gz" -url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325535" - -[tools.sd."platforms.linux-x64"] -checksum = "sha256:3613eca74cd686739bb5a6d68319aa56c747e7315274d02323a2ca2b1c5d82d2" -url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-x86_64-unknown-linux-gnu.tar.gz" -url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325296" - -[tools.sd."platforms.macos-arm64"] -checksum = "sha256:4bd3c09226376ca0a1d69589c91e86276fae36c5fbaaee669afce583f6682030" -url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-aarch64-apple-darwin.tar.gz" -url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325451" - -[tools.sd."platforms.macos-x64"] -checksum = "sha256:1fca1e9c91813a8aac6821063c923107ba0f66a83309e095edcd3b202f67f97e" -url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-x86_64-apple-darwin.tar.gz" -url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325430" - [[tools.sops]] version = "3.13.3" backend = "aqua:getsops/sops" @@ -472,55 +448,6 @@ url = "https://github.com/siderolabs/talos/releases/download/v1.14.0/talosctl-da url_api = "https://api.github.com/repos/siderolabs/talos/releases/assets/542359004" provenance = "cosign" -[[tools.taplo]] -version = "0.10.0" -backend = "aqua:tamasfe/taplo" - -[tools.taplo."platforms.linux-arm64"] -url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-linux-aarch64.gz" -url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257322597" - -[tools.taplo."platforms.linux-x64"] -checksum = "blake3:4871fab0e60275a1eb46e7190726e144f56c9a9527f59b0d1da5a042baead8e2" -url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-linux-x86_64.gz" -url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257322600" - -[tools.taplo."platforms.macos-arm64"] -url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-darwin-aarch64.gz" -url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257323110" - -[tools.taplo."platforms.macos-x64"] -url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-darwin-x86_64.gz" -url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257323116" - -[[tools.uv]] -version = "0.12.13" -backend = "aqua:astral-sh/uv" - -[tools.uv."platforms.linux-arm64"] -checksum = "sha256:2eaa5d94f5db7b3a1a092156b9420459e42ab0217d917fe74a876309cef9b5e9" -url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-aarch64-unknown-linux-gnu.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670186" -provenance = "github-attestations" - -[tools.uv."platforms.linux-x64"] -checksum = "sha256:745765a3b6e360ad76743599ae5c42e9278c7edf8bbff9fc76d05bf2623a04dd" -url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-x86_64-unknown-linux-gnu.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670375" -provenance = "github-attestations" - -[tools.uv."platforms.macos-arm64"] -checksum = "sha256:7e6ddb9316acc00f2296c82ff4d99977870ee34b2f0ddcae9444d714db9364ed" -url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-aarch64-apple-darwin.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670160" -provenance = "github-attestations" - -[tools.uv."platforms.macos-x64"] -checksum = "sha256:5e287ef61cb6a9b61b3a83fef124fd143e400468a7dac794230147a810e17119" -url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-x86_64-apple-darwin.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670348" -provenance = "github-attestations" - [[tools.yq]] version = "4.53.6" backend = "aqua:mikefarah/yq" diff --git a/cluster.sample.toml b/cluster.sample.toml deleted file mode 100644 index 7cb749f..0000000 --- a/cluster.sample.toml +++ /dev/null @@ -1,292 +0,0 @@ -#:schema ./cluster.schema.json -# ============================================================================= -# Physical LAN that your Talos nodes live on. Defines the address space -# used for node IPs, the gateway/DNS/NTP servers nodes will use, and an -# optional VLAN tag for switch ports that aren't natively tagged. -# ============================================================================= -[network] - -# The CIDR block your nodes' IPs come from. Every node's `address`, the Kube -# API VIP, and the gateway VIPs (internal/dns/external) must all sit inside -# this range. -# REQUIRED. Example: "192.168.1.0/24" -node_cidr = "" - -# Upstream DNS servers Talos nodes use for name resolution. Defaults to -# Cloudflare (1.1.1.1 / 1.0.0.1). Override if you run an internal resolver -# (Pi-hole, Unbound, AdGuard) or want a different public provider. -# OPTIONAL. Default: ["1.1.1.1", "1.0.0.1"] - -# dns_servers = ["1.1.1.1", "1.0.0.1"] - -# Upstream NTP servers. Defaults to Cloudflare's anycast NTP. Most homelabs -# don't need to change this. -# OPTIONAL. Default: ["162.159.200.1", "162.159.200.123"] - -# ntp_servers = ["162.159.200.1", "162.159.200.123"] - -# Default gateway IP that nodes use to reach the rest of your LAN/WAN. -# Defaults to the first usable host in node_cidr (e.g. 192.168.1.1 for -# 192.168.1.0/24), which is correct for most home routers. Override if your -# router lives at a non-standard address inside the subnet. -# OPTIONAL. Default: first IP in node_cidr - -# default_gateway = "" - -# 802.1Q VLAN tag to apply to the Talos node interface. Only set this if -# your switch ports are configured as trunks (passing tagged traffic to the -# nodes); access ports already untag VLAN traffic. Must be 1-4094. -# REF: https://www.talos.dev/latest/advanced/advanced-networking/#vlans -# OPTIONAL. - -# vlan_tag = "" - - -# ============================================================================= -# Cluster-internal control plane and overlay networks. The pod and service -# CIDRs are in-cluster only — they don't have to be routable on your LAN -# and never appear on the wire outside the nodes. -# ============================================================================= -[kubernetes] - -# CIDR Cilium hands out to pods. /16 gives ~64K pod IPs, which is well beyond -# what a homelab needs but matches the upstream default. Must NOT overlap -# with node_cidr or svc_cidr. -# OPTIONAL. Default: "10.42.0.0/16" - -# pod_cidr = "10.42.0.0/16" - -# CIDR for ClusterIP services (the virtual IPs `kubectl get svc` shows). -# Same /16 reasoning as pod_cidr. Must NOT overlap with node_cidr or -# pod_cidr. -# OPTIONAL. Default: "10.43.0.0/16" - -# svc_cidr = "10.43.0.0/16" - -# ClusterIP for the CoreDNS Service. Must be inside svc_cidr. -# OPTIONAL. Default: the 10th IP in svc_cidr - -# coredns_addr = "" - -[kubernetes.api] - -# Virtual IP for the Kubernetes API server. kubectl, flux, and every other -# client connect here on port 6443. Must be an unused IP inside -# network.node_cidr — kube-vip floats it across controller nodes. -# REQUIRED. -addr = "" - -# Additional Subject Alternative Names to put on the Kube API cert. Useful -# if you want to call the API by hostname (e.g. via a CNAME or local -# /etc/hosts entry) instead of the raw IP. -# OPTIONAL. Example: ["mycluster.example.com"] - -# tls_sans = ["mycluster.example.com"] - - -# ============================================================================= -# LoadBalancer IPs that Cilium hands out to the cluster's edge gateways. -# Each must be an unused address inside network.node_cidr, and all four -# (these three plus kubernetes.api.addr) must be distinct. -# ============================================================================= -[gateways] - -# IP for the `envoy-internal` gateway — used by HTTPRoutes intended for -# private/in-network access only. Most apps use this gateway by default. -# REQUIRED. -internal = "" - -# IP for `k8s_gateway`, which serves DNS for cluster-managed hostnames. -# Point your home DNS server's conditional forwarder for domain.name -# at this IP to enable split-DNS resolution from your LAN. -# REQUIRED. -dns = "" - -# IP for the `envoy-external` gateway — sits behind the ingress path (e.g. -# the cloudflared tunnel) and handles traffic exposed to the public -# internet. HTTPRoutes that reference this gateway become reachable via -# your public domain. -# REQUIRED unless ingress.mode is "none" (internal-only cluster). -external = "" - - -# ============================================================================= -# The Git repo Flux will sync from. This is the single source of truth -# for everything in your cluster — once bootstrapped, changes are made by -# pushing to this repo. Any Git host works: GitHub, GitLab, Gitea, -# Forgejo, Codeberg or self-hosted. -# ============================================================================= -[repository] - -# Full clone URL of the repository this cluster will pull from. -# Must be the repo you cloned this template into. -# Use `https://` if the repo is publicly readable (or `http://` for a -# LAN-local git server). Use `ssh://git@` if it is -# private: the template then wires up a deploy key (`deploy.key.pub`) so -# Flux can clone over SSH; see the README for the extra setup step. -# REQUIRED. Examples: -# "https://github.com/onedr0p/home-ops.git" -# "ssh://git@gitlab.com/onedr0p/home-ops.git" -# "ssh://git@git.example.com/k8s/home-ops.git" -url = "" - -# Branch Flux watches. Changes pushed to this branch get reconciled into the -# cluster. -# OPTIONAL. Default: "main" - -# branch = "main" - -# Webhook payload format the Flux webhook Receiver verifies, so pushes are -# reconciled instantly. Gitea and Forgejo emulate GitHub webhooks, so keep -# "github" for them. Use "generic-hmac" for anything else that signs with -# HMAC; see https://fluxcd.io/flux/components/notification/receivers/ -# Use "none" to skip the webhook entirely (e.g. your Git host cannot reach -# the cluster); Flux then only polls on an interval. -# OPTIONAL. Default: "github". -# Allowed: "github" | "gitlab" | "generic-hmac" | "none" - -# webhook_provider = "github" - -# SSH host keys for your Git host (`ssh-keyscan -t ed25519,ecdsa,rsa ` -# output). Only used with `ssh://` URLs. Keys for github.com, gitlab.com and -# codeberg.org are bundled; REQUIRED for any other host. -# OPTIONAL. Example: -# known_hosts = """ -# git.example.com ssh-ed25519 AAAA... -# """ - -# known_hosts = "" - - -# ============================================================================= -# The domain your cluster's hostnames live under. Used for every rendered -# hostname (echo, flux-webhook, internal split DNS) and the wildcard -# certificate, regardless of DNS provider. -# ============================================================================= -[domain] - -# REQUIRED. Example: "example.com" -name = "" - - -# ============================================================================= -# Public DNS authority and certificate issuance. With "cloudflare", -# external-dns publishes records automatically and cert-manager issues a -# Let's Encrypt wildcard via ACME DNS-01. With "none", nothing is -# published and the wildcard certificate is issued by an in-cluster -# self-signed CA instead (internal-only cluster). -# ============================================================================= -[dns] - -# OPTIONAL. Default: "cloudflare". Allowed: "cloudflare" | "none" - -# provider = "cloudflare" - -# Cloudflare API token (NOT the global API key) with `Zone - DNS - Edit` and -# `Account - Cloudflare Tunnel - Read` permissions, scoped to the zone -# above. See the README for token creation steps. -# REQUIRED when provider is "cloudflare"; must be empty otherwise. -token = "" - - -# ============================================================================= -# How the public internet reaches the cluster's external gateway. With -# "cloudflare-tunnel", cloudflared connects outbound so no ports are -# forwarded (requires dns.provider = "cloudflare" and -# cloudflare-tunnel.json). With "direct", you forward TCP 443 (and -# optionally 80) on your router to gateways.external and point an -# `external.` DNS record at your WAN address (A record or DDNS -# CNAME) yourself; per-app records are still published automatically. -# With "none", nothing is exposed and apps are only reachable on your -# LAN via the internal gateway. -# ============================================================================= -[ingress] - -# OPTIONAL. Default: "cloudflare-tunnel" when dns.provider is "cloudflare", -# otherwise "none". Allowed: "cloudflare-tunnel" | "direct" | "none" - -# mode = "cloudflare-tunnel" - - -# ============================================================================= -# CNI configuration. Defaults are sane for most homelab setups; touch -# this section only if you need BGP peering or a different LB mode. -# ============================================================================= -[cilium] - -# How Cilium's load balancer handles return traffic. `dsr` (Direct Server -# Return) preserves the client IP and is faster, but requires a switch -# fabric that won't drop asymmetric flows. `snat` masquerades the client -# and is the safe default for unknown topologies. -# REF: https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/ -# OPTIONAL. Default: "dsr". Allowed: "dsr" | "snat" - -# loadbalancer_mode = "dsr" - -# Cilium BGP peering — advertises Service IPs to your upstream router so -# LoadBalancer addresses become reachable from anywhere on your LAN -# (rather than only via L2 ARP). Set ALL THREE fields below to enable; -# leaving any blank disables BGP entirely. -# REF: https://docs.cilium.io/en/latest/network/bgp-control-plane/bgp-control-plane/ -[cilium.bgp] - -# IP of your BGP-speaking router. The cluster peers with it from each node. -# OPTIONAL. Example: "192.168.1.1" - -# router_addr = "" - -# ASN your router uses for BGP. Anything in the private range (64512-65534) -# is fine if you're not peering with the public internet. -# OPTIONAL. Example: "64513" - -# router_asn = "" - -# ASN the cluster's nodes use for BGP. Pick a different value than -# router_asn so peering is eBGP rather than iBGP. -# OPTIONAL. Example: "64514" - -# node_asn = "" - - -# ============================================================================= -# Talos Image Factory settings shared by all nodes. -# ============================================================================= -[talos] - -# Default schematic for every node that doesn't set its own schematic_id. -# The 64-character hex string from your build at https://factory.talos.dev/ -# OPTIONAL if every node sets schematic_id itself. -schematic_id = "" - - -# ============================================================================= -# One [[nodes]] table per physical machine or VM in the cluster. At least -# one controller (controller=true) is required; worker nodes are optional. -# For HA, use 3 controllers. -# -# Discover hardware details from a node already booted into Talos -# maintenance mode: -# talosctl get disks -n --insecure -# talosctl get links -n --insecure -# Schematic ID is the 64-character hex string from your build at: -# https://factory.talos.dev/ -# -# The block below is a template — copy it once per node, uncomment, and -# fill in the values. -# ============================================================================= - -# [[nodes]] -# name = "k8s-0" # Hostname; must match [a-z0-9-]+ (not "global"/"controller"/"worker"). -# address = "192.168.1.10" # Static IP; must be inside network.node_cidr. -# controller = true # true = control-plane (etcd + API server), false = worker. -# disk = "/dev/nvme0n1" # Block device or /dev/disk/by-id/... symlink to install Talos onto. -# mac_addr = "aa:bb:cc:dd:ee:ff" # Primary NIC MAC. -# -# # Optional when [talos] sets a cluster-wide default: -# schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" # 64-hex from factory.talos.dev. -# -# # Optional advanced fields (each independently uncommentable): -# mtu = 1500 # Set only for jumbo frames / non-1500 MTUs (1450-9000). -# secureboot = false # UEFI SecureBoot — requires a SecureBoot-enabled schematic. -# encrypt_disk = false # TPM-bound full-disk encryption. -# kernel_modules = ["nvidia", "nvidia_uvm"] # Only for schematics shipping matching extensions. diff --git a/cluster.schema.json b/cluster.schema.json deleted file mode 100644 index f2b92c5..0000000 --- a/cluster.schema.json +++ /dev/null @@ -1,433 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "Api": { - "additionalProperties": false, - "properties": { - "addr": { - "format": "ipv4", - "title": "Addr", - "type": "string" - }, - "tls_sans": { - "anyOf": [ - { - "items": { - "$ref": "#/$defs/Fqdn" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Tls Sans" - } - }, - "required": ["addr"], - "title": "Api", - "type": "object" - }, - "Asn": { - "type": "string" - }, - "Bgp": { - "additionalProperties": false, - "properties": { - "router_addr": { - "anyOf": [ - { - "format": "ipv4", - "type": "string" - }, - { - "const": "", - "type": "string" - } - ], - "default": "", - "title": "Router Addr" - }, - "router_asn": { - "$ref": "#/$defs/Asn", - "default": "" - }, - "node_asn": { - "$ref": "#/$defs/Asn", - "default": "" - } - }, - "title": "Bgp", - "type": "object" - }, - "Cidr": { - "format": "ipv4network", - "type": "string" - }, - "Cilium": { - "additionalProperties": false, - "properties": { - "loadbalancer_mode": { - "default": "dsr", - "enum": ["dsr", "snat"], - "title": "Loadbalancer Mode", - "type": "string" - }, - "bgp": { - "$ref": "#/$defs/Bgp", - "default": { - "router_addr": "", - "router_asn": "", - "node_asn": "" - } - } - }, - "title": "Cilium", - "type": "object" - }, - "Dns": { - "additionalProperties": false, - "properties": { - "provider": { - "default": "cloudflare", - "enum": ["cloudflare", "none"], - "title": "Provider", - "type": "string" - }, - "token": { - "default": "", - "title": "Token", - "type": "string" - } - }, - "title": "Dns", - "type": "object" - }, - "Domain": { - "additionalProperties": false, - "properties": { - "name": { - "$ref": "#/$defs/Fqdn" - } - }, - "required": ["name"], - "title": "Domain", - "type": "object" - }, - "Fqdn": { - "pattern": "^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}$", - "type": "string" - }, - "Gateways": { - "additionalProperties": false, - "properties": { - "internal": { - "format": "ipv4", - "title": "Internal", - "type": "string" - }, - "dns": { - "format": "ipv4", - "title": "Dns", - "type": "string" - }, - "external": { - "anyOf": [ - { - "format": "ipv4", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "External" - } - }, - "required": ["internal", "dns"], - "title": "Gateways", - "type": "object" - }, - "Ingress": { - "additionalProperties": false, - "properties": { - "mode": { - "default": "cloudflare-tunnel", - "enum": ["cloudflare-tunnel", "direct", "none"], - "title": "Mode", - "type": "string" - } - }, - "title": "Ingress", - "type": "object" - }, - "Kubernetes": { - "additionalProperties": false, - "properties": { - "pod_cidr": { - "$ref": "#/$defs/Cidr", - "default": "10.42.0.0/16" - }, - "svc_cidr": { - "$ref": "#/$defs/Cidr", - "default": "10.43.0.0/16" - }, - "coredns_addr": { - "format": "ipv4", - "title": "Coredns Addr", - "type": "string" - }, - "api": { - "$ref": "#/$defs/Api" - } - }, - "required": ["api"], - "title": "Kubernetes", - "type": "object" - }, - "Network": { - "additionalProperties": false, - "properties": { - "node_cidr": { - "$ref": "#/$defs/Cidr" - }, - "dns_servers": { - "default": ["1.1.1.1", "1.0.0.1"], - "items": { - "format": "ipv4", - "type": "string" - }, - "title": "Dns Servers", - "type": "array" - }, - "ntp_servers": { - "default": ["162.159.200.1", "162.159.200.123"], - "items": { - "format": "ipv4", - "type": "string" - }, - "title": "Ntp Servers", - "type": "array" - }, - "default_gateway": { - "format": "ipv4", - "title": "Default Gateway", - "type": "string" - }, - "vlan_tag": { - "anyOf": [ - { - "pattern": "^[0-9]+$", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Vlan Tag" - } - }, - "required": ["node_cidr"], - "title": "Network", - "type": "object" - }, - "Node": { - "additionalProperties": false, - "properties": { - "name": { - "pattern": "^[a-z0-9][a-z0-9\\-]{0,61}[a-z0-9]$|^[a-z0-9]$", - "title": "Name", - "type": "string" - }, - "address": { - "format": "ipv4", - "title": "Address", - "type": "string" - }, - "controller": { - "title": "Controller", - "type": "boolean" - }, - "disk": { - "title": "Disk", - "type": "string" - }, - "mac_addr": { - "pattern": "^([0-9a-f]{2}:){5}[0-9a-f]{2}$", - "title": "Mac Addr", - "type": "string" - }, - "schematic_id": { - "anyOf": [ - { - "pattern": "^[a-z0-9]{64}$", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Schematic Id" - }, - "mtu": { - "default": 1500, - "maximum": 9000, - "minimum": 1450, - "title": "Mtu", - "type": "integer" - }, - "secureboot": { - "default": false, - "title": "Secureboot", - "type": "boolean" - }, - "encrypt_disk": { - "default": false, - "title": "Encrypt Disk", - "type": "boolean" - }, - "kernel_modules": { - "default": [], - "items": { - "type": "string" - }, - "title": "Kernel Modules", - "type": "array" - } - }, - "required": ["name", "address", "controller", "disk", "mac_addr"], - "title": "Node", - "type": "object" - }, - "Repository": { - "additionalProperties": false, - "properties": { - "url": { - "pattern": "^(https?://|ssh://git@)[^/]+/.+$", - "title": "Url", - "type": "string" - }, - "branch": { - "default": "main", - "minLength": 1, - "title": "Branch", - "type": "string" - }, - "webhook_provider": { - "default": "github", - "enum": ["github", "gitlab", "generic-hmac", "none"], - "title": "Webhook Provider", - "type": "string" - }, - "known_hosts": { - "default": "", - "title": "Known Hosts", - "type": "string" - } - }, - "required": ["url"], - "title": "Repository", - "type": "object" - }, - "Spegel": { - "additionalProperties": false, - "properties": { - "enabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Enabled" - } - }, - "title": "Spegel", - "type": "object" - }, - "Talos": { - "additionalProperties": false, - "properties": { - "schematic_id": { - "anyOf": [ - { - "pattern": "^[a-z0-9]{64}$", - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Schematic Id" - } - }, - "title": "Talos", - "type": "object" - } - }, - "additionalProperties": false, - "properties": { - "network": { - "$ref": "#/$defs/Network" - }, - "kubernetes": { - "$ref": "#/$defs/Kubernetes" - }, - "gateways": { - "$ref": "#/$defs/Gateways" - }, - "repository": { - "$ref": "#/$defs/Repository" - }, - "domain": { - "$ref": "#/$defs/Domain" - }, - "dns": { - "$ref": "#/$defs/Dns" - }, - "ingress": { - "$ref": "#/$defs/Ingress" - }, - "cilium": { - "$ref": "#/$defs/Cilium", - "default": { - "loadbalancer_mode": "dsr", - "bgp": { - "node_asn": "", - "router_addr": "", - "router_asn": "" - } - } - }, - "talos": { - "$ref": "#/$defs/Talos", - "default": { - "schematic_id": null - } - }, - "spegel": { - "$ref": "#/$defs/Spegel", - "default": { - "enabled": null - } - }, - "nodes": { - "items": { - "$ref": "#/$defs/Node" - }, - "title": "Nodes", - "type": "array" - } - }, - "required": ["network", "kubernetes", "gateways", "repository", "domain", "dns", "nodes"], - "title": "cluster.toml", - "type": "object" -} diff --git a/justfile b/justfile index 254358d..897c1d3 100644 --- a/justfile +++ b/justfile @@ -17,18 +17,3 @@ mod? talos 'talos' [private] log lvl msg *args: gum log -t rfc3339 -s -l "{{ lvl }}" "{{ msg }}" {{ args }} - -# === template === - -[group('template')] -mod template 'template' - -[doc('Render and validate configuration files')] -[group('template')] -configure: - just template configure - -[doc('Initialize configuration files (cluster.toml, age key, deploy key, webhook token)')] -[group('template')] -init: - just template init diff --git a/makejinja.toml b/makejinja.toml deleted file mode 100644 index d429f26..0000000 --- a/makejinja.toml +++ /dev/null @@ -1,19 +0,0 @@ -[makejinja] -inputs = ["./template/overrides","./template/config"] -output = "./" -exclude_patterns = ["*.partial.yaml.j2"] -data = ["./cluster.toml"] -import_paths = ["./template/scripts"] -loaders = ["plugin:Plugin"] -jinja_suffix = ".j2" -copy_metadata = true -force = true -undefined = "strict" - -[makejinja.delimiter] -block_start = "#%" -block_end = "%#" -comment_start = "#|" -comment_end = "#|" -variable_start = "#{" -variable_end = "}#" diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 4e55c6f..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,16 +0,0 @@ -[project] -name = "cluster-template" -version = "0.0.0" -requires-python = ">=3.14" -dependencies = [ - "makejinja==2.8.3", - "pydantic==2.13.5", -] - -[dependency-groups] -dev = [ - "pytest>=8", -] - -[tool.uv] -package = false diff --git a/template/config/.sops.yaml.j2 b/template/config/.sops.yaml.j2 deleted file mode 100644 index bf44c2c..0000000 --- a/template/config/.sops.yaml.j2 +++ /dev/null @@ -1,12 +0,0 @@ ---- -creation_rules: - - path_regex: talos/.*\.sops\.ya?ml - mac_only_encrypted: true - age: "#{ age_key('public') }#" - - path_regex: (bootstrap|kubernetes)/.*\.sops\.ya?ml - encrypted_regex: "^(data|stringData)$" - mac_only_encrypted: true - age: "#{ age_key('public') }#" -stores: - yaml: - indent: 2 diff --git a/template/config/bootstrap/deploy-key.sops.yaml.j2 b/template/config/bootstrap/deploy-key.sops.yaml.j2 deleted file mode 100644 index a5e4888..0000000 --- a/template/config/bootstrap/deploy-key.sops.yaml.j2 +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - name: deploy-key - namespace: flux-system -stringData: - identity: | - #% filter indent(width=4, first=False) %# - #{ deploy_key() }# - #% endfilter %# - known_hosts: | - #% filter indent(width=4, first=False) %# - #{ repository.known_hosts }# - #% endfilter %# diff --git a/template/config/bootstrap/helmfile/apps.yaml.j2 b/template/config/bootstrap/helmfile/apps.yaml.j2 deleted file mode 100644 index ae02b9d..0000000 --- a/template/config/bootstrap/helmfile/apps.yaml.j2 +++ /dev/null @@ -1,60 +0,0 @@ ---- -# yaml-language-server: $schema=https://json.schemastore.org/helmfile - -# Bootstraps core applications that provide a minimal runtime base for the -# cluster. These releases are installed first so the cluster has the resources -# Flux needs before its own reconciliation begins. -# -# After this bootstrap phase, Flux is ready to take over management of the -# application stack and continue reconciling downstream state. - -helmDefaults: - cleanupOnFail: true - forceConflicts: true - wait: true - waitForJobs: true - -bases: - - default.yaml - -releases: - - name: cilium - namespace: kube-system - inherit: - - template: default - - - name: coredns - namespace: kube-system - inherit: - - template: default - needs: ["kube-system/cilium"] - #% if spegel.enabled %# - - - name: spegel - namespace: kube-system - inherit: - - template: default - needs: ["kube-system/coredns"] - #% endif %# - - - name: cert-manager - namespace: cert-manager - inherit: - - template: default - #% if spegel.enabled %# - needs: ["kube-system/spegel"] - #% else %# - needs: ["kube-system/coredns"] - #% endif %# - - - name: flux-operator - namespace: flux-system - inherit: - - template: default - needs: ["cert-manager/cert-manager"] - - - name: flux-instance - namespace: flux-system - inherit: - - template: default - needs: ["flux-system/flux-operator"] diff --git a/template/config/bootstrap/helmfile/crds.yaml.j2 b/template/config/bootstrap/helmfile/crds.yaml.j2 deleted file mode 100644 index 91dd445..0000000 --- a/template/config/bootstrap/helmfile/crds.yaml.j2 +++ /dev/null @@ -1,37 +0,0 @@ ---- -# yaml-language-server: $schema=https://json.schemastore.org/helmfile - -# Bootstraps cluster-wide Custom Resource Definitions (CRDs) by extracting them -# from upstream Helm charts and applying them directly with kubectl. The releases -# below are never reconciled with helmfile apply or helmfile sync — only their -# CRDs are rendered (via --include-crds) and piped to the cluster. -# -# Installing CRDs out-of-band ensures they exist before Flux begins reconciling -# workloads that reference them, avoiding the need for dependsOn chains on nearly -# every Kustomization that consumes a CRD-backed resource. - -helmDefaults: - args: - - --include-crds - - --no-hooks - -bases: - - default.yaml - -releases: - #% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# - - name: cloudflare-dns - namespace: network - inherit: - - template: default - - #% endif %# - - name: envoy-gateway - namespace: network - inherit: - - template: default - - - name: prometheus-operator-crds - namespace: observability - chart: oci://ghcr.io/prometheus-community/charts/prometheus-operator-crds - version: 29.0.0 diff --git a/template/config/bootstrap/helmfile/default.yaml.j2 b/template/config/bootstrap/helmfile/default.yaml.j2 deleted file mode 100644 index 575ff5d..0000000 --- a/template/config/bootstrap/helmfile/default.yaml.j2 +++ /dev/null @@ -1,8 +0,0 @@ ---- -# yaml-language-server: $schema=https://json.schemastore.org/helmfile -templates: - default: - chart: '{{ (fromYaml (tpl (readFile "./templates/release.yaml.gotmpl") .)).chart }}' - version: '{{ (fromYaml (tpl (readFile "./templates/release.yaml.gotmpl") .)).version }}' - values: - - ./templates/values.yaml.gotmpl diff --git a/template/config/bootstrap/helmfile/templates/release.yaml.gotmpl.j2 b/template/config/bootstrap/helmfile/templates/release.yaml.gotmpl.j2 deleted file mode 100644 index 00cb5eb..0000000 --- a/template/config/bootstrap/helmfile/templates/release.yaml.gotmpl.j2 +++ /dev/null @@ -1,3 +0,0 @@ -{{- $oci := fromYaml (readFile (printf "../../kubernetes/apps/%s/%s/app/ocirepository.yaml" .Release.Namespace .Release.Name)) -}} -chart: {{ $oci.spec.url }} -version: {{ $oci.spec.ref.tag }} diff --git a/template/config/bootstrap/helmfile/templates/values.yaml.gotmpl.j2 b/template/config/bootstrap/helmfile/templates/values.yaml.gotmpl.j2 deleted file mode 100644 index 2a046fa..0000000 --- a/template/config/bootstrap/helmfile/templates/values.yaml.gotmpl.j2 +++ /dev/null @@ -1 +0,0 @@ -{{ (fromYaml (readFile (printf "../../../kubernetes/apps/%s/%s/app/helmrelease.yaml" .Release.Namespace .Release.Name))).spec.values | toYaml }} diff --git a/template/config/bootstrap/mod.just b/template/config/bootstrap/mod.just deleted file mode 100644 index 81d4106..0000000 --- a/template/config/bootstrap/mod.just +++ /dev/null @@ -1,105 +0,0 @@ -set no-exit-message -set quiet -set shell := ['bash', '-euo', 'pipefail', '-c'] -set script-interpreter := ['bash', '-euo', 'pipefail'] -set default-list -set default-script - -kubernetes_dir := justfile_dir() + '/kubernetes' - -[doc('Bootstrap the Talos cluster')] -[group('bootstrap')] -talos: talos-secret talos-apply talos-talosconfig talos-kubeconfig - -[doc('Bootstrap apps into the Talos cluster')] -[group('bootstrap')] -apps: apps-ready apps-namespaces apps-secrets apps-crds apps-helm - just log info "Cluster is bootstrapped — Flux will start syncing the Git repository" - -# No sops call or existence guard is needed: the bundle is stored already -# encrypted with the repo's age recipient, and existing bundles are left -# untouched. -[private] -[working-directory('../talos')] -talos-secret: - just log info "Generating secrets" stage "{{ recipe_name() }}" - topf secrets --confirm=false > /dev/null - -[private] -[working-directory('../talos')] -talos-apply: - just log info "Applying talos config and bootstrapping" stage "{{ recipe_name() }}" - topf apply --auto-bootstrap --confirm=false - -[private] -[working-directory('../talos')] -talos-talosconfig: - just log info "Generating talosconfig" stage "{{ recipe_name() }}" - topf talosconfig > talosconfig - -# topf issues short-lived admin certs by default; 8760h keeps the -# kubeconfig usable long-term. -[private] -[working-directory('../talos')] -talos-kubeconfig: - just log info "Fetching kubeconfig" stage "{{ recipe_name() }}" - topf kubeconfig --validity 8760h > "{{ justfile_dir() }}/kubeconfig" - -[private] -apps-crds: - just log info "Applying CRDs" stage "{{ recipe_name() }}" - if ! helmfile --file "{{ source_directory() }}/helmfile/crds.yaml" template --quiet | yq eval-all --exit-status 'select(.kind == "CustomResourceDefinition")' | kubectl apply --server-side --force-conflicts --filename -; then - just log fatal "Failed to apply crds" - fi - -[private] -apps-helm: - just log info "Syncing helmfile" stage "{{ recipe_name() }}" - if ! helmfile --file "{{ source_directory() }}/helmfile/apps.yaml" sync --hide-notes; then - just log fatal "Failed to sync helmfile" - fi - -[private] -apps-namespaces: - just log info "Applying namespaces for apps" stage "{{ recipe_name() }}" - for app in "{{ kubernetes_dir }}/apps"/*/; do - ns="$(basename "$app")" - if kubectl create namespace "$ns" --dry-run=client -o yaml \ - | kubectl apply --server-side --filename - &>/dev/null; then - just log info "Namespace applied" namespace "$ns" - else - just log fatal "Failed to apply namespace" namespace "$ns" - fi - done - -[private] -apps-secrets: - just log info "Applying secrets for apps" stage "{{ recipe_name() }}" - for secret in \ - "{{ source_directory() }}/deploy-key.sops.yaml" \ - "{{ source_directory() }}/sops-age.sops.yaml" \ - "{{ kubernetes_dir }}/components/sops/cluster-secrets.sops.yaml" - do - name="$(basename "$secret" .sops.yaml)" - if sops decrypt "$secret" \ - | kubectl --namespace flux-system apply --server-side --filename - &>/dev/null; then - just log info "Secret applied" resource "$name" - else - just log fatal "Failed to apply secret" resource "$name" - fi - done - -# Wait until nodes register as Ready=False. They only become Ready=True once the CNI is healthy. -[private] -apps-ready: - just log info "Waiting for nodes to register as Ready=False" stage "{{ recipe_name() }}" - if ! kubectl wait nodes --for=condition=Ready=True --all --timeout=10s &>/dev/null; then - deadline=$((SECONDS + 600)) - until kubectl wait nodes --for=condition=Ready=False --all --timeout=10s &>/dev/null; do - if (( SECONDS >= deadline )); then - just log fatal "Timed out waiting for nodes to register" - fi - just log info "Nodes not available, waiting for nodes to be available. Retrying in 5 seconds..." - sleep 5 - done - fi diff --git a/template/config/bootstrap/sops-age.sops.yaml.j2 b/template/config/bootstrap/sops-age.sops.yaml.j2 deleted file mode 100644 index f555b4b..0000000 --- a/template/config/bootstrap/sops-age.sops.yaml.j2 +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - name: sops-age - namespace: flux-system -stringData: - age.agekey: "#{ age_key('private') }#" diff --git a/template/config/kubernetes/apps/cert-manager/cert-manager/app/clusterissuer.yaml.j2 b/template/config/kubernetes/apps/cert-manager/cert-manager/app/clusterissuer.yaml.j2 deleted file mode 100644 index 66f1b1d..0000000 --- a/template/config/kubernetes/apps/cert-manager/cert-manager/app/clusterissuer.yaml.j2 +++ /dev/null @@ -1,52 +0,0 @@ -#% if dns.provider == 'cloudflare' %# ---- -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: letsencrypt-production -spec: - acme: - privateKeySecretRef: - name: letsencrypt-production - profile: shortlived - server: https://acme-v02.api.letsencrypt.org/directory - solvers: - - dns01: - cloudflare: - apiTokenSecretRef: - name: cert-manager-secret - key: api-token - selector: - dnsZones: ["${SECRET_DOMAIN}"] -#% else %# ---- -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: selfsigned -spec: - selfSigned: {} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: internal-ca -spec: - isCA: true - commonName: internal-ca - secretName: internal-ca - privateKey: - algorithm: ECDSA - size: 256 - issuerRef: - name: selfsigned - kind: ClusterIssuer ---- -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: internal-ca -spec: - ca: - secretName: internal-ca -#% endif %# diff --git a/template/config/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 deleted file mode 100644 index bd038a3..0000000 --- a/template/config/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,20 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: cert-manager -spec: - chartRef: - kind: OCIRepository - name: cert-manager - interval: 1h - values: - crds: - enabled: true - replicaCount: #{ 2 if nodes | length > 1 else 1 }# - dns01RecursiveNameservers: https://1.1.1.1:443/dns-query,https://1.0.0.1:443/dns-query - dns01RecursiveNameserversOnly: true - prometheus: - enabled: true - servicemonitor: - enabled: true diff --git a/template/config/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 deleted file mode 100644 index 16806ed..0000000 --- a/template/config/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./clusterissuer.yaml - - ./helmrelease.yaml - - ./ocirepository.yaml - #% if dns.provider == 'cloudflare' %# - - ./secret.sops.yaml - #% endif %# diff --git a/template/config/kubernetes/apps/cert-manager/cert-manager/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/cert-manager/cert-manager/app/ocirepository.yaml.j2 deleted file mode 100644 index 55a808f..0000000 --- a/template/config/kubernetes/apps/cert-manager/cert-manager/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: cert-manager -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: v1.21.2 - url: oci://quay.io/jetstack/charts/cert-manager diff --git a/template/config/kubernetes/apps/cert-manager/cert-manager/app/secret.sops.yaml.j2 b/template/config/kubernetes/apps/cert-manager/cert-manager/app/secret.sops.yaml.j2 deleted file mode 100644 index 990f771..0000000 --- a/template/config/kubernetes/apps/cert-manager/cert-manager/app/secret.sops.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -#% if dns.provider == 'cloudflare' %# ---- -apiVersion: v1 -kind: Secret -metadata: - name: cert-manager-secret -stringData: - api-token: "#{ dns.token }#" -#% endif %# diff --git a/template/config/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 b/template/config/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 deleted file mode 100644 index ba0c807..0000000 --- a/template/config/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 +++ /dev/null @@ -1,30 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: cert-manager -spec: - healthChecks: - - apiVersion: helm.toolkit.fluxcd.io/v2 - kind: HelmRelease - name: cert-manager - namespace: cert-manager - - apiVersion: cert-manager.io/v1 - kind: ClusterIssuer - name: #{ cluster_issuer }# - healthCheckExprs: - - apiVersion: cert-manager.io/v1 - kind: ClusterIssuer - current: status.conditions.exists(e, e.type == 'Ready' && e.status == 'True') - interval: 1h - path: ./kubernetes/apps/cert-manager/cert-manager/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: cert-manager diff --git a/template/config/kubernetes/apps/cert-manager/kustomization.yaml.j2 b/template/config/kubernetes/apps/cert-manager/kustomization.yaml.j2 deleted file mode 100644 index e24fb0b..0000000 --- a/template/config/kubernetes/apps/cert-manager/kustomization.yaml.j2 +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: cert-manager - -components: - - ../../components/sops - -resources: - - ./namespace.yaml - - ./cert-manager/ks.yaml diff --git a/template/config/kubernetes/apps/cert-manager/namespace.yaml.j2 b/template/config/kubernetes/apps/cert-manager/namespace.yaml.j2 deleted file mode 100644 index 6e34540..0000000 --- a/template/config/kubernetes/apps/cert-manager/namespace.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: cert-manager - annotations: - kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/template/config/kubernetes/apps/default/echo/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/default/echo/app/helmrelease.yaml.j2 deleted file mode 100644 index c9abbc7..0000000 --- a/template/config/kubernetes/apps/default/echo/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,31 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: echo -spec: - chartRef: - kind: OCIRepository - name: echo - interval: 1h - values: - replicaCount: #{ 2 if nodes | length > 1 else 1 }# - config: - kubernetes: true - trustedProxies: - - "#{ kubernetes.pod_cidr }#" - httpRoute: - enabled: true - hostnames: - - "{{ .Release.Name }}.${SECRET_DOMAIN}" - parentRefs: - - name: envoy-#{ 'external' if ingress.mode != 'none' else 'internal' }# - namespace: network - monitoring: - serviceMonitor: - enabled: true - resources: - requests: - cpu: 10m - limits: - memory: 64Mi diff --git a/template/config/kubernetes/apps/default/echo/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/default/echo/app/kustomization.yaml.j2 deleted file mode 100644 index 2ccd5a8..0000000 --- a/template/config/kubernetes/apps/default/echo/app/kustomization.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml diff --git a/template/config/kubernetes/apps/default/echo/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/default/echo/app/ocirepository.yaml.j2 deleted file mode 100644 index 22e6205..0000000 --- a/template/config/kubernetes/apps/default/echo/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: echo -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 0.2.5 - url: oci://ghcr.io/home-operations/charts/echo diff --git a/template/config/kubernetes/apps/default/echo/ks.yaml.j2 b/template/config/kubernetes/apps/default/echo/ks.yaml.j2 deleted file mode 100644 index f942a81..0000000 --- a/template/config/kubernetes/apps/default/echo/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: echo -spec: - interval: 1h - path: ./kubernetes/apps/default/echo/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: default - wait: false diff --git a/template/config/kubernetes/apps/default/kustomization.yaml.j2 b/template/config/kubernetes/apps/default/kustomization.yaml.j2 deleted file mode 100644 index d7271b1..0000000 --- a/template/config/kubernetes/apps/default/kustomization.yaml.j2 +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: default - -components: - - ../../components/sops - -resources: - - ./namespace.yaml - - ./echo/ks.yaml diff --git a/template/config/kubernetes/apps/default/namespace.yaml.j2 b/template/config/kubernetes/apps/default/namespace.yaml.j2 deleted file mode 100644 index e3f8a49..0000000 --- a/template/config/kubernetes/apps/default/namespace.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: default - annotations: - kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/app/helmrelease.yaml.j2 deleted file mode 100644 index bfd7588..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,136 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: flux-instance -spec: - chartRef: - kind: OCIRepository - name: flux-instance - interval: 1h - values: - commonAnnotations: - fluxcd.controlplane.io/reconcileArtifactEvery: 1h - instance: - cluster: - networkPolicy: false - components: - - source-controller - - kustomize-controller - - helm-controller - - notification-controller - sync: - kind: GitRepository - url: "#{ repository.url }#" - #% if repository.url.startswith('ssh://') %# - pullSecret: deploy-key - #% endif %# - ref: "refs/heads/#{ repository.branch }#" - path: kubernetes/flux/cluster - commonMetadata: - labels: - app.kubernetes.io/name: flux - kustomize: - patches: - - # Increase the number of workers - patch: | - - op: add - path: /spec/template/spec/containers/0/args/- - value: --concurrent=10 - - op: add - path: /spec/template/spec/containers/0/args/- - value: --requeue-dependency=5s - target: - kind: Deployment - name: (kustomize-controller|helm-controller|source-controller) - - # Increase the memory limits - patch: | - apiVersion: apps/v1 - kind: Deployment - metadata: - name: all - spec: - template: - spec: - containers: - - name: manager - resources: - limits: - memory: 1Gi - target: - kind: Deployment - name: (kustomize-controller|helm-controller|source-controller) - - # Enable in-memory kustomize builds - patch: | - - op: add - path: /spec/template/spec/containers/0/args/- - value: --concurrent=20 - - op: replace - path: /spec/template/spec/volumes/0 - value: - name: temp - emptyDir: - medium: Memory - target: - kind: Deployment - name: kustomize-controller - - # Enable Helm repositories caching - patch: | - - op: add - path: /spec/template/spec/containers/0/args/- - value: --helm-cache-max-size=10 - - op: add - path: /spec/template/spec/containers/0/args/- - value: --helm-cache-ttl=60m - - op: add - path: /spec/template/spec/containers/0/args/- - value: --helm-cache-purge-interval=5m - target: - kind: Deployment - name: source-controller - - # Flux near OOM detection for Helm - patch: | - - op: add - path: /spec/template/spec/containers/0/args/- - value: --feature-gates=OOMWatch=true - - op: add - path: /spec/template/spec/containers/0/args/- - value: --oom-watch-memory-threshold=95 - - op: add - path: /spec/template/spec/containers/0/args/- - value: --oom-watch-interval=500ms - target: - kind: Deployment - name: helm-controller - - # Disable chart digest tracking - patch: | - - op: add - path: /spec/template/spec/containers/0/args/- - value: --feature-gates=DisableChartDigestTracking=true - target: - kind: Deployment - name: helm-controller - - # Controller-level SOPS decryption - patch: | - - op: add - path: /spec/template/spec/containers/0/args/- - value: --sops-age-secret=sops-age - target: - kind: Deployment - name: kustomize-controller - - # Watch configmaps and secrets attached to HelmReleases and Kustomizations - patch: |- - - op: add - path: /spec/template/spec/containers/0/args/- - value: --watch-configs-label-selector=owner!=helm - target: - kind: Deployment - name: (helm-controller|kustomize-controller) - - # Cancel health checks on new Kustomizations revisions - patch: |- - - op: add - path: /spec/template/spec/containers/0/args/- - value: --feature-gates=CancelHealthCheckOnNewRevision=true - target: - kind: Deployment - name: kustomize-controller diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/app/httproute.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/app/httproute.yaml.j2 deleted file mode 100644 index 2685606..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/app/httproute.yaml.j2 +++ /dev/null @@ -1,22 +0,0 @@ -#% if repository.webhook_provider != 'none' %# ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: flux-webhook -spec: - hostnames: ["flux-webhook.${SECRET_DOMAIN}"] - parentRefs: - - name: envoy-#{ 'external' if ingress.mode != 'none' else 'internal' }# - namespace: network - sectionName: https - rules: - - backendRefs: - - name: webhook-receiver - namespace: flux-system - port: 80 - matches: - - path: - type: PathPrefix - value: /hook/ -#% endif %# diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/app/kustomization.yaml.j2 deleted file mode 100644 index b2fa265..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/app/kustomization.yaml.j2 +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml - #% if repository.webhook_provider != 'none' %# - - ./secret.sops.yaml - - ./httproute.yaml - - ./receiver.yaml - #% endif %# diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/app/ocirepository.yaml.j2 deleted file mode 100644 index 8aa1733..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: flux-instance -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 0.60.0 - url: oci://ghcr.io/controlplaneio-fluxcd/charts/flux-instance diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/app/receiver.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/app/receiver.yaml.j2 deleted file mode 100644 index 58866ac..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/app/receiver.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ -#% if repository.webhook_provider != 'none' %# ---- -apiVersion: notification.toolkit.fluxcd.io/v1 -kind: Receiver -metadata: - name: flux-webhook -spec: - type: #{ repository.webhook_provider }# - events: ["ping", "push"] - secretRef: - name: flux-webhook-token - resources: - - apiVersion: source.toolkit.fluxcd.io/v1 - kind: GitRepository - name: flux-system - namespace: flux-system - - apiVersion: kustomize.toolkit.fluxcd.io/v1 - kind: Kustomization - name: flux-system - namespace: flux-system -#% endif %# diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/app/secret.sops.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/app/secret.sops.yaml.j2 deleted file mode 100644 index bbdb8cd..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/app/secret.sops.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -#% if repository.webhook_provider != 'none' %# ---- -apiVersion: v1 -kind: Secret -metadata: - name: flux-webhook-token -stringData: - token: "#{ webhook_token() }#" -#% endif %# diff --git a/template/config/kubernetes/apps/flux-system/flux-instance/ks.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-instance/ks.yaml.j2 deleted file mode 100644 index 1a35461..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-instance/ks.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: flux-instance -spec: - dependsOn: - - name: flux-operator - interval: 1h - path: ./kubernetes/apps/flux-system/flux-instance/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: flux-system - wait: false diff --git a/template/config/kubernetes/apps/flux-system/flux-operator/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-operator/app/helmrelease.yaml.j2 deleted file mode 100644 index 08fba38..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-operator/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: flux-operator -spec: - chartRef: - kind: OCIRepository - name: flux-operator - interval: 1h - values: - serviceMonitor: - create: true diff --git a/template/config/kubernetes/apps/flux-system/flux-operator/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-operator/app/kustomization.yaml.j2 deleted file mode 100644 index 2ccd5a8..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-operator/app/kustomization.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml diff --git a/template/config/kubernetes/apps/flux-system/flux-operator/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-operator/app/ocirepository.yaml.j2 deleted file mode 100644 index dc939db..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-operator/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: flux-operator -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 0.60.0 - url: oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator diff --git a/template/config/kubernetes/apps/flux-system/flux-operator/ks.yaml.j2 b/template/config/kubernetes/apps/flux-system/flux-operator/ks.yaml.j2 deleted file mode 100644 index 2e39824..0000000 --- a/template/config/kubernetes/apps/flux-system/flux-operator/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: flux-operator -spec: - interval: 1h - path: ./kubernetes/apps/flux-system/flux-operator/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: flux-system - wait: true diff --git a/template/config/kubernetes/apps/flux-system/kustomization.yaml.j2 b/template/config/kubernetes/apps/flux-system/kustomization.yaml.j2 deleted file mode 100644 index 4b0dd43..0000000 --- a/template/config/kubernetes/apps/flux-system/kustomization.yaml.j2 +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: flux-system - -components: - - ../../components/sops - -resources: - - ./namespace.yaml - - ./flux-instance/ks.yaml - - ./flux-operator/ks.yaml diff --git a/template/config/kubernetes/apps/flux-system/namespace.yaml.j2 b/template/config/kubernetes/apps/flux-system/namespace.yaml.j2 deleted file mode 100644 index 120760a..0000000 --- a/template/config/kubernetes/apps/flux-system/namespace.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: flux-system - annotations: - kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/template/config/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 deleted file mode 100644 index c21ffae..0000000 --- a/template/config/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,91 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: cilium -spec: - chartRef: - kind: OCIRepository - name: cilium - interval: 1h - values: - autoDirectNodeRoutes: true - bpf: - masquerade: true - # Ref: https://github.com/siderolabs/talos/issues/10002 - hostLegacyRouting: true - #% if cilium_bgp_enabled %# - bgpControlPlane: - enabled: true - #% endif %# - cni: - # Required for pairing with Multus CNI - exclusive: false - cgroup: - automount: - enabled: false - hostRoot: /sys/fs/cgroup - # The stable bond name is defined in talos/all/20-network-links.yaml.tpl - devices: bond0+ - dashboards: - enabled: true - endpointRoutes: - enabled: true - envoy: - enabled: false - gatewayAPI: - enabled: false - hubble: - enabled: false - ipam: - mode: kubernetes - ipv4NativeRoutingCIDR: "#{ kubernetes.pod_cidr }#" - k8sServiceHost: 127.0.0.1 - k8sServicePort: 7445 - kubeProxyReplacement: true - kubeProxyReplacementHealthzBindAddr: 0.0.0.0:10256 - l2announcements: - enabled: true - loadBalancer: - algorithm: maglev - mode: "#{ cilium.loadbalancer_mode }#" - localRedirectPolicies: - enabled: true - operator: - dashboards: - enabled: true - prometheus: - enabled: true - serviceMonitor: - enabled: true - replicas: #{ 2 if nodes | length > 1 else 1 }# - rollOutPods: true - prometheus: - enabled: true - serviceMonitor: - enabled: true - trustCRDsExist: true - rollOutCiliumPods: true - routingMode: native - securityContext: - capabilities: - ciliumAgent: - - CHOWN - - KILL - - NET_ADMIN - - NET_RAW - - IPC_LOCK - - SYS_ADMIN - - SYS_RESOURCE - - PERFMON - - BPF - - DAC_OVERRIDE - - FOWNER - - SETGID - - SETUID - cleanCiliumState: - - NET_ADMIN - - SYS_ADMIN - - SYS_RESOURCE - socketLB: - enabled: true diff --git a/template/config/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 deleted file mode 100644 index 791e352..0000000 --- a/template/config/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml - - ./networks.yaml diff --git a/template/config/kubernetes/apps/kube-system/cilium/app/networks.yaml.j2 b/template/config/kubernetes/apps/kube-system/cilium/app/networks.yaml.j2 deleted file mode 100644 index 6a8d330..0000000 --- a/template/config/kubernetes/apps/kube-system/cilium/app/networks.yaml.j2 +++ /dev/null @@ -1,71 +0,0 @@ ---- -apiVersion: cilium.io/v2alpha1 -kind: CiliumLoadBalancerIPPool -metadata: - name: pool -spec: - allowFirstLastIPs: "No" - blocks: - - cidr: "#{ network.node_cidr }#" ---- -apiVersion: cilium.io/v2alpha1 -kind: CiliumL2AnnouncementPolicy -metadata: - name: l2-policy -spec: - loadBalancerIPs: true - # NOTE: interfaces might need to be set if you have more than one active NIC on your hosts - # interfaces: - # - ^eno[0-9]+ - # - ^eth[0-9]+ - nodeSelector: - matchLabels: - kubernetes.io/os: linux -#% if cilium_bgp_enabled %# ---- -apiVersion: cilium.io/v2alpha1 -kind: CiliumBGPAdvertisement -metadata: - name: bgp-advertisement-config - labels: - advertise: bgp -spec: - advertisements: - - advertisementType: Service - service: - addresses: - - LoadBalancerIP - selector: - matchExpressions: - - { key: somekey, operator: NotIn, values: ["never-used-value"] } ---- -apiVersion: cilium.io/v2alpha1 -kind: CiliumBGPPeerConfig -metadata: - name: bgp-peer-config-v4 -spec: - families: - - afi: ipv4 - safi: unicast - advertisements: - matchLabels: - advertise: bgp ---- -apiVersion: cilium.io/v2alpha1 -kind: CiliumBGPClusterConfig -metadata: - name: bgp-cluster-config -spec: - nodeSelector: - matchLabels: - kubernetes.io/os: linux - bgpInstances: - - name: instance-#{ cilium.bgp.node_asn }# - localASN: #{ cilium.bgp.node_asn }# - peers: - - name: peer-#{ cilium.bgp.router_asn }#-v4 - peerASN: #{ cilium.bgp.router_asn }# - peerAddress: #{ cilium.bgp.router_addr }# - peerConfigRef: - name: bgp-peer-config-v4 -#% endif %# diff --git a/template/config/kubernetes/apps/kube-system/cilium/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/kube-system/cilium/app/ocirepository.yaml.j2 deleted file mode 100644 index b95952e..0000000 --- a/template/config/kubernetes/apps/kube-system/cilium/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: cilium -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 1.20.1 - url: oci://quay.io/cilium/charts/cilium diff --git a/template/config/kubernetes/apps/kube-system/cilium/ks.yaml.j2 b/template/config/kubernetes/apps/kube-system/cilium/ks.yaml.j2 deleted file mode 100644 index ea0a835..0000000 --- a/template/config/kubernetes/apps/kube-system/cilium/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: cilium -spec: - interval: 1h - path: ./kubernetes/apps/kube-system/cilium/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: kube-system - wait: false diff --git a/template/config/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 deleted file mode 100644 index 3406a2c..0000000 --- a/template/config/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,69 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: coredns -spec: - chartRef: - kind: OCIRepository - name: coredns - interval: 1h - values: - fullnameOverride: coredns - image: - repository: mirror.gcr.io/coredns/coredns - k8sAppLabelOverride: kube-dns - serviceAccount: - create: true - service: - name: kube-dns - clusterIP: "#{ kubernetes.coredns_addr }#" - replicaCount: #{ 2 if controller_count > 1 else 1 }# - priorityClassName: system-cluster-critical - servers: - - zones: - - zone: . - scheme: dns:// - use_tcp: true - port: 53 - plugins: - - name: errors - - name: health - configBlock: |- - lameduck 5s - - name: ready - - name: kubernetes - parameters: cluster.local in-addr.arpa ip6.arpa - configBlock: |- - pods verified - fallthrough in-addr.arpa ip6.arpa - - name: autopath - parameters: "@kubernetes" - - name: forward - parameters: . /etc/resolv.conf - - name: cache - configBlock: |- - prefetch 20 - serve_stale - servfail 0 - - name: loop - - name: reload - - name: loadbalance - - name: prometheus - parameters: 0.0.0.0:9153 - - name: log - configBlock: |- - class error - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists - tolerations: - - key: CriticalAddonsOnly - operator: Exists - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule diff --git a/template/config/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 deleted file mode 100644 index 2ccd5a8..0000000 --- a/template/config/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml diff --git a/template/config/kubernetes/apps/kube-system/coredns/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/kube-system/coredns/app/ocirepository.yaml.j2 deleted file mode 100644 index 21e7231..0000000 --- a/template/config/kubernetes/apps/kube-system/coredns/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: coredns -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - url: oci://ghcr.io/coredns/charts/coredns - ref: - tag: 1.47.1 diff --git a/template/config/kubernetes/apps/kube-system/coredns/ks.yaml.j2 b/template/config/kubernetes/apps/kube-system/coredns/ks.yaml.j2 deleted file mode 100644 index 5ac2a87..0000000 --- a/template/config/kubernetes/apps/kube-system/coredns/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: coredns -spec: - interval: 1h - path: ./kubernetes/apps/kube-system/coredns/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: kube-system - wait: false diff --git a/template/config/kubernetes/apps/kube-system/kustomization.yaml.j2 b/template/config/kubernetes/apps/kube-system/kustomization.yaml.j2 deleted file mode 100644 index a1d78a9..0000000 --- a/template/config/kubernetes/apps/kube-system/kustomization.yaml.j2 +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: kube-system - -components: - - ../../components/sops - -resources: - - ./namespace.yaml - - ./cilium/ks.yaml - - ./coredns/ks.yaml - - ./metrics-server/ks.yaml - - ./reloader/ks.yaml - #% if spegel.enabled %# - - ./spegel/ks.yaml - #% endif %# diff --git a/template/config/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 deleted file mode 100644 index befc886..0000000 --- a/template/config/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: metrics-server -spec: - chartRef: - kind: OCIRepository - name: metrics-server - interval: 1h - values: - args: - - --kubelet-insecure-tls - - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - - --kubelet-use-node-status-port - - --metric-resolution=10s - - --kubelet-request-timeout=2s - metrics: - enabled: true - serviceMonitor: - enabled: true diff --git a/template/config/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 deleted file mode 100644 index 2ccd5a8..0000000 --- a/template/config/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml diff --git a/template/config/kubernetes/apps/kube-system/metrics-server/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/kube-system/metrics-server/app/ocirepository.yaml.j2 deleted file mode 100644 index cf8351d..0000000 --- a/template/config/kubernetes/apps/kube-system/metrics-server/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: metrics-server -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 3.14.0 - url: oci://ghcr.io/home-operations/charts-mirror/metrics-server diff --git a/template/config/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 b/template/config/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 deleted file mode 100644 index 60042b7..0000000 --- a/template/config/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: metrics-server -spec: - interval: 1h - path: ./kubernetes/apps/kube-system/metrics-server/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: kube-system - wait: false diff --git a/template/config/kubernetes/apps/kube-system/namespace.yaml.j2 b/template/config/kubernetes/apps/kube-system/namespace.yaml.j2 deleted file mode 100644 index 7f6e64f..0000000 --- a/template/config/kubernetes/apps/kube-system/namespace.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: kube-system - annotations: - kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/template/config/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 deleted file mode 100644 index 100b095..0000000 --- a/template/config/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: reloader -spec: - chartRef: - kind: OCIRepository - name: reloader - interval: 1h - values: - fullnameOverride: reloader - reloader: - readOnlyRootFileSystem: true - podMonitor: - enabled: true - namespace: "{{ .Release.Namespace }}" diff --git a/template/config/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 deleted file mode 100644 index 2ccd5a8..0000000 --- a/template/config/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml diff --git a/template/config/kubernetes/apps/kube-system/reloader/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/kube-system/reloader/app/ocirepository.yaml.j2 deleted file mode 100644 index 98a2f4d..0000000 --- a/template/config/kubernetes/apps/kube-system/reloader/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: reloader -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 2.2.17 - url: oci://ghcr.io/stakater/charts/reloader diff --git a/template/config/kubernetes/apps/kube-system/reloader/ks.yaml.j2 b/template/config/kubernetes/apps/kube-system/reloader/ks.yaml.j2 deleted file mode 100644 index 5f5c65a..0000000 --- a/template/config/kubernetes/apps/kube-system/reloader/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: reloader -spec: - interval: 1h - path: ./kubernetes/apps/kube-system/reloader/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: kube-system - wait: false diff --git a/template/config/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 deleted file mode 100644 index fb1cfa4..0000000 --- a/template/config/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ -#% if spegel.enabled %# ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: spegel -spec: - chartRef: - kind: OCIRepository - name: spegel - interval: 1h - values: - spegel: - containerdSock: /run/containerd/containerd.sock - containerdRegistryConfigPath: /etc/cri/conf.d/hosts - service: - registry: - hostPort: 29999 - serviceMonitor: - enabled: true -#% endif %# diff --git a/template/config/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 deleted file mode 100644 index f61cc97..0000000 --- a/template/config/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 +++ /dev/null @@ -1,8 +0,0 @@ -#% if spegel.enabled %# ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml -#% endif %# diff --git a/template/config/kubernetes/apps/kube-system/spegel/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/kube-system/spegel/app/ocirepository.yaml.j2 deleted file mode 100644 index 867a7f9..0000000 --- a/template/config/kubernetes/apps/kube-system/spegel/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,15 +0,0 @@ -#% if spegel.enabled %# ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: spegel -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 0.7.4 - url: oci://ghcr.io/spegel-org/helm-charts/spegel -#% endif %# diff --git a/template/config/kubernetes/apps/kube-system/spegel/ks.yaml.j2 b/template/config/kubernetes/apps/kube-system/spegel/ks.yaml.j2 deleted file mode 100644 index 4158fb1..0000000 --- a/template/config/kubernetes/apps/kube-system/spegel/ks.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ -#% if spegel.enabled %# ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: spegel -spec: - interval: 1h - path: ./kubernetes/apps/kube-system/spegel/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: kube-system - wait: false -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-dns/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-dns/app/helmrelease.yaml.j2 deleted file mode 100644 index 19714a4..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-dns/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,37 +0,0 @@ -#% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: &app cloudflare-dns -spec: - chartRef: - kind: OCIRepository - name: cloudflare-dns - interval: 1h - values: - fullnameOverride: *app - provider: cloudflare - env: - - name: CF_API_TOKEN - valueFrom: - secretKeyRef: - name: &secret cloudflare-dns-secret - key: api-token - extraArgs: - - --cloudflare-dns-records-per-page=1000 - - --cloudflare-proxied - - --crd-source-apiversion=externaldns.k8s.io/v1alpha1 - - --crd-source-kind=DNSEndpoint - - --gateway-name=envoy-external - triggerLoopOnEvent: true - policy: sync - sources: ["crd", "gateway-httproute"] - txtPrefix: k8s. - txtOwnerId: default - domainFilters: ["${SECRET_DOMAIN}"] - serviceMonitor: - enabled: true - podAnnotations: - secret.reloader.stakater.com/reload: *secret -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-dns/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-dns/app/kustomization.yaml.j2 deleted file mode 100644 index 6aa29bb..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-dns/app/kustomization.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -#% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./secret.sops.yaml - - ./helmrelease.yaml - - ./ocirepository.yaml -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-dns/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-dns/app/ocirepository.yaml.j2 deleted file mode 100644 index 126d418..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-dns/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,15 +0,0 @@ -#% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: cloudflare-dns -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 1.22.0 - url: oci://ghcr.io/home-operations/charts-mirror/external-dns -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-dns/app/secret.sops.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-dns/app/secret.sops.yaml.j2 deleted file mode 100644 index 1e95e0a..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-dns/app/secret.sops.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -#% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# ---- -apiVersion: v1 -kind: Secret -metadata: - name: cloudflare-dns-secret -stringData: - api-token: "#{ dns.token }#" -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-dns/ks.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-dns/ks.yaml.j2 deleted file mode 100644 index 56c3c2f..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-dns/ks.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ -#% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: cloudflare-dns -spec: - interval: 1h - path: ./kubernetes/apps/network/cloudflare-dns/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: network - wait: true -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/dnsendpoint.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-tunnel/app/dnsendpoint.yaml.j2 deleted file mode 100644 index 0e0683e..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/dnsendpoint.yaml.j2 +++ /dev/null @@ -1,12 +0,0 @@ -#% if ingress.mode == 'cloudflare-tunnel' %# ---- -apiVersion: externaldns.k8s.io/v1alpha1 -kind: DNSEndpoint -metadata: - name: cloudflare-tunnel -spec: - endpoints: - - dnsName: "external.${SECRET_DOMAIN}" - recordType: CNAME - targets: ["#{ cloudflare_tunnel_id() }#.cfargotunnel.com"] -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-tunnel/app/helmrelease.yaml.j2 deleted file mode 100644 index ec8d1a2..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,86 +0,0 @@ -#% if ingress.mode == 'cloudflare-tunnel' %# ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: cloudflare-tunnel -spec: - chartRef: - kind: OCIRepository - name: cloudflare-tunnel - interval: 1h - values: - controllers: - cloudflare-tunnel: - strategy: RollingUpdate - annotations: - reloader.stakater.com/auto: "true" - containers: - app: - image: - repository: docker.io/cloudflare/cloudflared - tag: 2026.9.1 - env: - NO_AUTOUPDATE: true - TUNNEL_METRICS: 0.0.0.0:8080 - TUNNEL_POST_QUANTUM: true # disable when using http2 - TUNNEL_TRANSPORT_PROTOCOL: quic # or http2 - envFrom: - - secretRef: - name: cloudflare-tunnel-secret - args: ["tunnel", "run"] - probes: - liveness: &probes - enabled: true - custom: true - spec: - httpGet: - path: /ready - port: &port 8080 - initialDelaySeconds: 0 - periodSeconds: 10 - timeoutSeconds: 1 - failureThreshold: 3 - readiness: *probes - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: { drop: ["ALL"] } - resources: - requests: - cpu: 10m - limits: - memory: 256Mi - defaultPodOptions: - securityContext: - runAsNonRoot: true - runAsUser: 65534 - runAsGroup: 65534 - service: - app: - ports: - http: - port: *port - serviceMonitor: - app: - endpoints: - - port: http - configMaps: - config: - data: - config.yaml: |- - ingress: - - hostname: "*.${SECRET_DOMAIN}" - originRequest: - http2Origin: true - originServerName: external.${SECRET_DOMAIN} - service: https://envoy-external.{{ .Release.Namespace }}.svc.cluster.local:443 - - service: http_status:404 - persistence: - config-file: - type: configMap - identifier: config - globalMounts: - - path: /etc/cloudflared/config.yaml - subPath: config.yaml -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-tunnel/app/kustomization.yaml.j2 deleted file mode 100644 index 4defe8e..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/kustomization.yaml.j2 +++ /dev/null @@ -1,10 +0,0 @@ -#% if ingress.mode == 'cloudflare-tunnel' %# ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./dnsendpoint.yaml - - ./secret.sops.yaml - - ./helmrelease.yaml - - ./ocirepository.yaml -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-tunnel/app/ocirepository.yaml.j2 deleted file mode 100644 index 915c541..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,15 +0,0 @@ -#% if ingress.mode == 'cloudflare-tunnel' %# ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: cloudflare-tunnel -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 5.1.0 - url: oci://ghcr.io/bjw-s-labs/helm/app-template -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/secret.sops.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-tunnel/app/secret.sops.yaml.j2 deleted file mode 100644 index 7b1088f..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-tunnel/app/secret.sops.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -#% if ingress.mode == 'cloudflare-tunnel' %# ---- -apiVersion: v1 -kind: Secret -metadata: - name: cloudflare-tunnel-secret -stringData: - TUNNEL_TOKEN: "#{ cloudflare_tunnel_secret() }#" -#% endif %# diff --git a/template/config/kubernetes/apps/network/cloudflare-tunnel/ks.yaml.j2 b/template/config/kubernetes/apps/network/cloudflare-tunnel/ks.yaml.j2 deleted file mode 100644 index eb2dfa9..0000000 --- a/template/config/kubernetes/apps/network/cloudflare-tunnel/ks.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ -#% if ingress.mode == 'cloudflare-tunnel' %# ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: cloudflare-tunnel -spec: - interval: 1h - path: ./kubernetes/apps/network/cloudflare-tunnel/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: network - wait: false -#% endif %# diff --git a/template/config/kubernetes/apps/network/envoy-gateway/app/certificate.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/app/certificate.yaml.j2 deleted file mode 100644 index 2c6b27b..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/app/certificate.yaml.j2 +++ /dev/null @@ -1,18 +0,0 @@ ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: "${SECRET_DOMAIN/./-}-production" -spec: - dnsNames: - - "${SECRET_DOMAIN}" - - "*.${SECRET_DOMAIN}" - duration: 160h - issuerRef: - name: #{ cluster_issuer }# - kind: ClusterIssuer - privateKey: - algorithm: ECDSA - secretName: "${SECRET_DOMAIN/./-}-production-tls" - usages: - - digital signature diff --git a/template/config/kubernetes/apps/network/envoy-gateway/app/envoy.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/app/envoy.yaml.j2 deleted file mode 100644 index 134217f..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/app/envoy.yaml.j2 +++ /dev/null @@ -1,174 +0,0 @@ ---- -apiVersion: gateway.envoyproxy.io/v1alpha1 -kind: EnvoyProxy -metadata: - name: envoy -spec: - logging: - level: - default: info - provider: - type: Kubernetes - kubernetes: - envoyDeployment: - replicas: #{ 2 if nodes | length > 1 else 1 }# - container: - imageRepository: mirror.gcr.io/envoyproxy/envoy - resources: - requests: - cpu: 100m - limits: - memory: 1Gi - envoyService: - externalTrafficPolicy: Cluster - shutdown: - drainTimeout: 180s - telemetry: - metrics: - prometheus: - compression: - type: Zstd ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: GatewayClass -metadata: - name: envoy -spec: - controllerName: gateway.envoyproxy.io/gatewayclass-controller - parametersRef: - group: gateway.envoyproxy.io - kind: EnvoyProxy - name: envoy - namespace: network -#% if ingress.mode != 'none' %# ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: envoy-external - annotations: - external-dns.kubernetes.io/target: external.${SECRET_DOMAIN} -spec: - gatewayClassName: envoy - infrastructure: - annotations: - external-dns.kubernetes.io/hostname: external.${SECRET_DOMAIN} - lbipam.cilium.io/ips: "#{ gateways.external }#" - listeners: - - name: http - protocol: HTTP - port: 80 - allowedRoutes: - namespaces: - from: Same - - name: https - protocol: HTTPS - port: 443 - allowedRoutes: - namespaces: - from: All - tls: - certificateRefs: - - kind: Secret - name: ${SECRET_DOMAIN/./-}-production-tls -#% endif %# ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: envoy-internal - annotations: - external-dns.kubernetes.io/target: internal.${SECRET_DOMAIN} -spec: - gatewayClassName: envoy - infrastructure: - annotations: - external-dns.kubernetes.io/hostname: internal.${SECRET_DOMAIN} - lbipam.cilium.io/ips: "#{ gateways.internal }#" - listeners: - - name: http - protocol: HTTP - port: 80 - allowedRoutes: - namespaces: - from: Same - - name: https - protocol: HTTPS - port: 443 - allowedRoutes: - namespaces: - from: All - tls: - certificateRefs: - - kind: Secret - name: ${SECRET_DOMAIN/./-}-production-tls ---- -apiVersion: gateway.envoyproxy.io/v1alpha1 -kind: BackendTrafficPolicy -metadata: - name: envoy -spec: - compressor: - - type: Zstd - zstd: {} - - type: Brotli - brotli: {} - - type: Gzip - gzip: {} - retry: - numRetries: 2 - retryOn: - triggers: - - reset - targetSelectors: - - group: gateway.networking.k8s.io - kind: Gateway - tcpKeepalive: {} - timeout: - http: - requestTimeout: 0s ---- -apiVersion: gateway.envoyproxy.io/v1alpha1 -kind: ClientTrafficPolicy -metadata: - name: envoy -spec: - clientIPDetection: - xForwardedFor: - trustedCIDRs: - - "#{ kubernetes.pod_cidr }#" - http2: - onInvalidMessage: TerminateStream - http3: {} - targetSelectors: - - group: gateway.networking.k8s.io - kind: Gateway - tcpKeepalive: {} - tls: - minVersion: "1.2" - alpnProtocols: - - h2 - - http/1.1 ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: https-redirect - annotations: - external-dns.kubernetes.io/controller: none -spec: - parentRefs: - #% if ingress.mode != 'none' %# - - name: envoy-external - namespace: network - sectionName: http - #% endif %# - - name: envoy-internal - namespace: network - sectionName: http - rules: - - filters: - - type: RequestRedirect - requestRedirect: - scheme: https - statusCode: 301 diff --git a/template/config/kubernetes/apps/network/envoy-gateway/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/app/helmrelease.yaml.j2 deleted file mode 100644 index 55f3dea..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,20 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: envoy-gateway -spec: - chartRef: - kind: OCIRepository - name: envoy-gateway - interval: 1h - values: - global: - imageRegistry: mirror.gcr.io - config: - envoyGateway: - provider: - type: Kubernetes - kubernetes: - deploy: - type: GatewayNamespace diff --git a/template/config/kubernetes/apps/network/envoy-gateway/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/app/kustomization.yaml.j2 deleted file mode 100644 index 545ef59..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/app/kustomization.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./certificate.yaml - - ./envoy.yaml - - ./helmrelease.yaml - - ./ocirepository.yaml - - ./podmonitor.yaml diff --git a/template/config/kubernetes/apps/network/envoy-gateway/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/app/ocirepository.yaml.j2 deleted file mode 100644 index de028ab..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: envoy-gateway -spec: - interval: 15m - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 1.9.1 - url: oci://mirror.gcr.io/envoyproxy/gateway-helm diff --git a/template/config/kubernetes/apps/network/envoy-gateway/app/podmonitor.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/app/podmonitor.yaml.j2 deleted file mode 100644 index b199aa8..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/app/podmonitor.yaml.j2 +++ /dev/null @@ -1,18 +0,0 @@ ---- -apiVersion: monitoring.coreos.com/v1 -kind: PodMonitor -metadata: - name: envoy-proxy -spec: - jobLabel: envoy-proxy - namespaceSelector: - matchNames: - - network - podMetricsEndpoints: - - port: metrics - path: /stats/prometheus - honorLabels: true - selector: - matchLabels: - app.kubernetes.io/component: proxy - app.kubernetes.io/name: envoy diff --git a/template/config/kubernetes/apps/network/envoy-gateway/ks.yaml.j2 b/template/config/kubernetes/apps/network/envoy-gateway/ks.yaml.j2 deleted file mode 100644 index 78a3ce9..0000000 --- a/template/config/kubernetes/apps/network/envoy-gateway/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: envoy-gateway -spec: - interval: 1h - path: ./kubernetes/apps/network/envoy-gateway/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: network - wait: false diff --git a/template/config/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 b/template/config/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 deleted file mode 100644 index b3c6a32..0000000 --- a/template/config/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 +++ /dev/null @@ -1,21 +0,0 @@ ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: k8s-gateway -spec: - chartRef: - kind: OCIRepository - name: k8s-gateway - interval: 1h - values: - fullnameOverride: k8s-gateway - domain: "${SECRET_DOMAIN}" - ttl: 1 - service: - type: LoadBalancer - port: 53 - annotations: - lbipam.cilium.io/ips: "#{ gateways.dns }#" - externalTrafficPolicy: Cluster - watchedResources: ["HTTPRoute", "Service"] diff --git a/template/config/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 b/template/config/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 deleted file mode 100644 index 2ccd5a8..0000000 --- a/template/config/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./helmrelease.yaml - - ./ocirepository.yaml diff --git a/template/config/kubernetes/apps/network/k8s-gateway/app/ocirepository.yaml.j2 b/template/config/kubernetes/apps/network/k8s-gateway/app/ocirepository.yaml.j2 deleted file mode 100644 index 29e381d..0000000 --- a/template/config/kubernetes/apps/network/k8s-gateway/app/ocirepository.yaml.j2 +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: OCIRepository -metadata: - name: k8s-gateway -spec: - interval: 1h - layerSelector: - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip - operation: copy - ref: - tag: 3.7.3 - url: oci://codeberg.org/k8s-gateway/charts/k8s-gateway diff --git a/template/config/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 b/template/config/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 deleted file mode 100644 index 844476f..0000000 --- a/template/config/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: k8s-gateway -spec: - interval: 1h - path: ./kubernetes/apps/network/k8s-gateway/app - postBuild: - substituteFrom: - - name: cluster-secrets - kind: Secret - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - targetNamespace: network - wait: false diff --git a/template/config/kubernetes/apps/network/kustomization.yaml.j2 b/template/config/kubernetes/apps/network/kustomization.yaml.j2 deleted file mode 100644 index dd63d5f..0000000 --- a/template/config/kubernetes/apps/network/kustomization.yaml.j2 +++ /dev/null @@ -1,18 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: network - -components: - - ../../components/sops - -resources: - - ./namespace.yaml - #% if dns.provider == 'cloudflare' and ingress.mode != 'none' %# - - ./cloudflare-dns/ks.yaml - #% endif %# - #% if ingress.mode == 'cloudflare-tunnel' %# - - ./cloudflare-tunnel/ks.yaml - #% endif %# - - ./envoy-gateway/ks.yaml - - ./k8s-gateway/ks.yaml diff --git a/template/config/kubernetes/apps/network/namespace.yaml.j2 b/template/config/kubernetes/apps/network/namespace.yaml.j2 deleted file mode 100644 index 17866ba..0000000 --- a/template/config/kubernetes/apps/network/namespace.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: network - annotations: - kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/template/config/kubernetes/components/sops/cluster-secrets.sops.yaml.j2 b/template/config/kubernetes/components/sops/cluster-secrets.sops.yaml.j2 deleted file mode 100644 index 5c7e4c4..0000000 --- a/template/config/kubernetes/components/sops/cluster-secrets.sops.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - name: cluster-secrets -stringData: - SECRET_DOMAIN: "#{ domain.name }#" diff --git a/template/config/kubernetes/components/sops/kustomization.yaml.j2 b/template/config/kubernetes/components/sops/kustomization.yaml.j2 deleted file mode 100644 index 1948b44..0000000 --- a/template/config/kubernetes/components/sops/kustomization.yaml.j2 +++ /dev/null @@ -1,5 +0,0 @@ ---- -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component -resources: - - ./cluster-secrets.sops.yaml diff --git a/template/config/kubernetes/flux/cluster/ks.yaml.j2 b/template/config/kubernetes/flux/cluster/ks.yaml.j2 deleted file mode 100644 index 77d76e8..0000000 --- a/template/config/kubernetes/flux/cluster/ks.yaml.j2 +++ /dev/null @@ -1,57 +0,0 @@ ---- -apiVersion: kustomize.toolkit.fluxcd.io/v1 -kind: Kustomization -metadata: - name: cluster-apps - namespace: flux-system -spec: - decryption: - provider: sops - deletionPolicy: WaitForTermination - interval: 1h - path: ./kubernetes/apps - prune: true - sourceRef: - kind: GitRepository - name: flux-system - namespace: flux-system - wait: false - patches: - - # Add Kustomization defaults for all child Kustomizations - patch: |- - apiVersion: kustomize.toolkit.fluxcd.io/v1 - kind: Kustomization - metadata: - name: _ - spec: - decryption: - provider: sops - deletionPolicy: WaitForTermination - patches: - - patch: |- - apiVersion: helm.toolkit.fluxcd.io/v2 - kind: HelmRelease - metadata: - name: _ - spec: - install: - crds: CreateReplace - strategy: - name: RetryOnFailure - rollback: - cleanupOnFail: true - recreate: true - upgrade: - cleanupOnFail: true - crds: CreateReplace - strategy: - name: RemediateOnFailure - remediation: - remediateLastFailure: true - retries: 2 - target: - group: helm.toolkit.fluxcd.io - kind: HelmRelease - target: - group: kustomize.toolkit.fluxcd.io - kind: Kustomization diff --git a/template/config/kubernetes/mod.just b/template/config/kubernetes/mod.just deleted file mode 100644 index b1de2b1..0000000 --- a/template/config/kubernetes/mod.just +++ /dev/null @@ -1,10 +0,0 @@ -set quiet -set shell := ['bash', '-euo', 'pipefail', '-c'] -set script-interpreter := ['bash', '-euo', 'pipefail'] -set default-list -set default-script - -[doc('Force Flux to pull in changes from your Git repository')] -[group('kube')] -reconcile: - flux --namespace flux-system reconcile kustomization flux-system --with-source diff --git a/template/config/talos/README.md.j2 b/template/config/talos/README.md.j2 deleted file mode 100644 index b0359f7..0000000 --- a/template/config/talos/README.md.j2 +++ /dev/null @@ -1,20 +0,0 @@ -# Talos Patching - -Machine configs are assembled by [topf](https://postfinance.github.io/topf/) from -`topf.yaml` plus the strategic merge patches in this directory. - - - -## Patch Directories - -Patches merge in this order, alphabetically within each directory, with later -patches taking precedence: - -- `all/`: applied to every node -- `control-plane/`: applied to control-plane nodes -- `worker/`: applied to worker nodes -- `node/${hostname}/`: applied to the node with the specified name - -Files ending in `.yaml.tpl` are Go-templated per node; see the -[topf configuration model](https://postfinance.github.io/topf/main/configuration-model/) -for the available template variables. diff --git a/template/config/talos/all/00-install.yaml.tpl.j2 b/template/config/talos/all/00-install.yaml.tpl.j2 deleted file mode 100644 index 1a38a86..0000000 --- a/template/config/talos/all/00-install.yaml.tpl.j2 +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1alpha1 -kind: UnattendedInstallConfig -provisioning: - diskSelector: - {{- if .Node.Data.installDisk }} - match: disk.dev_path == "{{ .Node.Data.installDisk }}" || "{{ .Node.Data.installDisk }}" in disk.symlinks - {{- else }} - match: disk.serial == "{{ .Node.Data.installDiskSerial }}" - {{- end }} diff --git a/template/config/talos/all/01-hostname.yaml.tpl.j2 b/template/config/talos/all/01-hostname.yaml.tpl.j2 deleted file mode 100644 index a6ac043..0000000 --- a/template/config/talos/all/01-hostname.yaml.tpl.j2 +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1alpha1 -kind: HostnameConfig -auto: "off" -hostname: "{{ .Node.Host }}" diff --git a/template/config/talos/all/10-cluster.yaml.j2 b/template/config/talos/all/10-cluster.yaml.j2 deleted file mode 100644 index 4a9292f..0000000 --- a/template/config/talos/all/10-cluster.yaml.j2 +++ /dev/null @@ -1,10 +0,0 @@ -machine: - certSANs: - #% for item in cert_sans %# - - "#{ item }#" - #% endfor %# ---- -apiVersion: v1alpha1 -kind: KubeNetworkConfig -podSubnets: ["#{ kubernetes.pod_cidr }#"] -serviceSubnets: ["#{ kubernetes.svc_cidr }#"] diff --git a/template/config/talos/all/20-network-links.yaml.tpl.j2 b/template/config/talos/all/20-network-links.yaml.tpl.j2 deleted file mode 100644 index aa81fc4..0000000 --- a/template/config/talos/all/20-network-links.yaml.tpl.j2 +++ /dev/null @@ -1,41 +0,0 @@ -#| Node networking as Talos 1.13+ typed network documents. The MAC-matched - link is enslaved to a single-link active-backup bond so every consumer - (Cilium devices, VLANs, the VIP, metrics) sees a stable interface name, - bond0, regardless of kernel NIC naming; a second NIC can join the bond - later without renaming anything. The bond name is also referenced by - `devices` in the cilium HelmRelease. #| -#% set link = 'bond0.' ~ network.vlan_tag if network.vlan_tag else 'bond0' %# ---- -apiVersion: v1alpha1 -kind: LinkAliasConfig -name: ethSel0 -selector: - match: glob("{{ .Node.Data.macAddr }}", mac(link.hardware_addr)) ---- -apiVersion: v1alpha1 -kind: BondConfig -name: bond0 -links: - - ethSel0 -bondMode: active-backup -mtu: {{ .Node.Data.mtu }} -#% if network.vlan_tag %# ---- -apiVersion: v1alpha1 -kind: VLANConfig -name: #{ link }# -parent: bond0 -vlanID: #{ network.vlan_tag }# -mtu: {{ .Node.Data.mtu }} -#% endif %# -addresses: - - address: "{{ .Node.IP }}/#{ network.node_cidr.split('/') | last }#" -routes: - - gateway: "#{ network.default_gateway }#" -{{- if eq .Node.Role "control-plane" }} ---- -apiVersion: v1alpha1 -kind: Layer2VIPConfig -link: #{ link }# -name: "#{ kubernetes.api.addr }#" -{{- end }} diff --git a/template/config/talos/all/21-network.yaml.j2 b/template/config/talos/all/21-network.yaml.j2 deleted file mode 100644 index adbf330..0000000 --- a/template/config/talos/all/21-network.yaml.j2 +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1alpha1 -kind: ResolverConfig -nameservers: - #% for item in network.dns_servers %# - - address: #{ item }# - #% endfor %# -searchDomains: - disableDefault: true diff --git a/template/config/talos/all/22-time.yaml.j2 b/template/config/talos/all/22-time.yaml.j2 deleted file mode 100644 index b34f2f2..0000000 --- a/template/config/talos/all/22-time.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1alpha1 -kind: TimeSyncConfig -ntp: - servers: - #% for item in network.ntp_servers %# - - #{ item }# - #% endfor %# diff --git a/template/config/talos/all/30-kubelet.yaml.j2 b/template/config/talos/all/30-kubelet.yaml.j2 deleted file mode 100644 index 38c8e06..0000000 --- a/template/config/talos/all/30-kubelet.yaml.j2 +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1alpha1 -kind: KubeletConfig -config: - crashLoopBackOff: - maxContainerRestartPeriod: 60s - imageMaximumGCAge: 168h - maxParallelImagePulls: 3 - serializeImagePulls: false - shutdownGracePeriod: 90s - shutdownGracePeriodCriticalPods: 60s ---- -apiVersion: v1alpha1 -kind: KubeNodeConfig -nodeIP: - validSubnets: - - #{ network.node_cidr }# diff --git a/template/config/talos/all/40-sysctls.yaml.j2 b/template/config/talos/all/40-sysctls.yaml.j2 deleted file mode 100644 index 4bda500..0000000 --- a/template/config/talos/all/40-sysctls.yaml.j2 +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1alpha1 -kind: SysctlConfig -params: - fs.inotify.max_user_watches: "1048576" # Watchdog - fs.inotify.max_user_instances: "8192" # Watchdog - net.core.rmem_max: "7500000" # Cloudflared | QUIC - net.core.wmem_max: "7500000" # Cloudflared | QUIC - net.ipv4.neigh.default.gc_thresh1: "4096" # Prevent ARP cache overflows - net.ipv4.neigh.default.gc_thresh2: "8192" # Prevent ARP cache overflows - net.ipv4.neigh.default.gc_thresh3: "16384" # Prevent ARP cache overflows - net.ipv4.tcp_slow_start_after_idle: "0" # Preserve congestion window after idle - user.max_user_namespaces: "11255" # User Namespaces diff --git a/template/config/talos/all/50-files.yaml.j2 b/template/config/talos/all/50-files.yaml.j2 deleted file mode 100644 index 7208111..0000000 --- a/template/config/talos/all/50-files.yaml.j2 +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1alpha1 -kind: CRICustomizationConfig -name: containerd -content: | - [plugins."io.containerd.cri.v1.images"] - discard_unpacked_layers = false - [plugins."io.containerd.cri.v1.runtime"] - device_ownership_from_security_context = true diff --git a/template/config/talos/all/60-encryption.yaml.tpl.j2 b/template/config/talos/all/60-encryption.yaml.tpl.j2 deleted file mode 100644 index c478f5b..0000000 --- a/template/config/talos/all/60-encryption.yaml.tpl.j2 +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Node.Data.encryptDisk }} -# Encrypt system disk with TPM -apiVersion: v1alpha1 -kind: VolumeConfig -name: STATE -encryption: - provider: luks2 - keys: - - slot: 0 - tpm: {} ---- -apiVersion: v1alpha1 -kind: VolumeConfig -name: EPHEMERAL -encryption: - provider: luks2 - keys: - - slot: 0 - tpm: {} -{{- end }} diff --git a/template/config/talos/all/61-kernel-modules.yaml.tpl.j2 b/template/config/talos/all/61-kernel-modules.yaml.tpl.j2 deleted file mode 100644 index 5e39729..0000000 --- a/template/config/talos/all/61-kernel-modules.yaml.tpl.j2 +++ /dev/null @@ -1,6 +0,0 @@ -{{- range .Node.Data.kernelModules }} ---- -apiVersion: v1alpha1 -kind: KernelModuleConfig -name: {{ . }} -{{- end }} diff --git a/template/config/talos/all/70-security.yaml.j2 b/template/config/talos/all/70-security.yaml.j2 deleted file mode 100644 index bc5dfb2..0000000 --- a/template/config/talos/all/70-security.yaml.j2 +++ /dev/null @@ -1,7 +0,0 @@ -# Talos 1.14 defaults to running kubelet and pods in a sandboxed PID/mount -# namespace, which breaks workloads that rely on hostPID or host mounts -# (node-exporter, GPU plugins, in-tree iSCSI). Set to true to opt in; on -# encrypted disks expect a second reboot the first time it is enabled. -apiVersion: v1alpha1 -kind: SecurityProfileConfig -workloadIsolation: false diff --git a/template/config/talos/all/71-filesystem.yaml.j2 b/template/config/talos/all/71-filesystem.yaml.j2 deleted file mode 100644 index e55ba22..0000000 --- a/template/config/talos/all/71-filesystem.yaml.j2 +++ /dev/null @@ -1,9 +0,0 @@ -# Weekly online maintenance for XFS volumes: trim discards unused blocks -# (SSDs, thin provisioning) and scrub checks filesystem metadata. -apiVersion: v1alpha1 -kind: FilesystemTrimConfig -interval: 168h0m0s ---- -apiVersion: v1alpha1 -kind: FilesystemScrubConfig -interval: 168h0m0s diff --git a/template/config/talos/control-plane/00-cluster.yaml.j2 b/template/config/talos/control-plane/00-cluster.yaml.j2 deleted file mode 100644 index fdd4d04..0000000 --- a/template/config/talos/control-plane/00-cluster.yaml.j2 +++ /dev/null @@ -1,62 +0,0 @@ -cluster: - etcd: - extraArgs: - listen-metrics-urls: http://0.0.0.0:2381 - advertisedSubnets: - - #{ network.node_cidr }# ---- -# Allow scheduling on control-plane nodes -apiVersion: v1alpha1 -kind: KubeNodeConfig -taints: - node-role.kubernetes.io/control-plane: - $patch: delete ---- -apiVersion: v1alpha1 -kind: KubeAdmissionControlConfig -name: PodSecurity -$patch: delete ---- -apiVersion: v1alpha1 -kind: KubeAPIServerConfig -certExtraSANs: - #% for item in cert_sans %# - - "#{ item }#" - #% endfor %# -extraArgs: - # https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/ - enable-aggregator-routing: "true" ---- -apiVersion: v1alpha1 -kind: KubeControllerManagerConfig -extraArgs: - bind-address: 0.0.0.0 ---- -apiVersion: v1alpha1 -kind: KubeCoreDNSConfig -enabled: false ---- -# Disable built-in CNI and kube-proxy to use Cilium -apiVersion: v1alpha1 -kind: KubeFlannelCNIConfig -$patch: delete ---- -apiVersion: v1alpha1 -kind: KubeProxyConfig -enabled: false ---- -apiVersion: v1alpha1 -kind: KubeSchedulerConfig -extraArgs: - bind-address: 0.0.0.0 -config: - profiles: - - schedulerName: default-scheduler - pluginConfig: - - name: PodTopologySpread - args: - defaultingType: List - defaultConstraints: - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway diff --git a/template/config/talos/mod.just b/template/config/talos/mod.just deleted file mode 100644 index 64b6b5a..0000000 --- a/template/config/talos/mod.just +++ /dev/null @@ -1,64 +0,0 @@ -set quiet -set shell := ['bash', '-euo', 'pipefail', '-c'] -set script-interpreter := ['bash', '-euo', 'pipefail'] -set default-list -set default-script - -# Day-2 apply/upgrade recipes rely on topf's built-in diff-and-confirm -# prompt; only the destructive reset recipes disable it in favour of just's -# own confirmation. - -[doc('Apply Talos config to all nodes (shows a diff and asks first)')] -[group('talos')] -apply: - topf apply - -[arg('mode', pattern='auto|reboot|no-reboot|staged|try')] -[doc('Apply Talos config to a node (shows a diff and asks first)')] -[group('talos')] -apply-node node mode='auto': - topf apply --nodes-filter "^{{ node }}$" --mode "{{ mode }}" - -[doc('Show pending config changes without applying them')] -[group('talos')] -diff: - topf apply --dry-run - -[doc('List all nodes and their current state')] -[group('talos')] -nodes: - topf nodes - -[doc('Render Talos machine configs to ./rendered')] -[group('talos')] -render: - topf render --output ./rendered - -[confirm("This will destroy your cluster and reset all nodes to maintenance mode — continue? [y/N]")] -[doc('Reset all nodes back to maintenance mode (DESTRUCTIVE)')] -[group('talos')] -reset: - topf reset --confirm=false - -[confirm("This will reset node " + node + " to maintenance mode — continue? [y/N]")] -[doc('Reset a single node back to maintenance mode (DESTRUCTIVE)')] -[group('talos')] -reset-node node: - topf reset --nodes-filter "^{{ node }}$" --confirm=false - -# topf intentionally does not manage Kubernetes upgrades -[doc('Upgrade Kubernetes')] -[group('talos')] -upgrade-k8s: - talosctl --nodes "$(yq '[.nodes[] | select(.role == "control-plane")][0].ip' topf.yaml)" \ - upgrade-k8s --to "$(yq '.kubernetesVersion' topf.yaml)" - -[doc('Upgrade Talos on all nodes, one at a time (asks first)')] -[group('talos')] -upgrade: - topf upgrade - -[doc('Upgrade Talos on a single node (asks first)')] -[group('talos')] -upgrade-node node: - topf upgrade --nodes-filter "^{{ node }}$" diff --git a/template/config/talos/topf.yaml.j2 b/template/config/talos/topf.yaml.j2 deleted file mode 100644 index 16f684f..0000000 --- a/template/config/talos/topf.yaml.j2 +++ /dev/null @@ -1,33 +0,0 @@ ---- -clusterName: kubernetes -clusterEndpoint: https://#{ kubernetes.api.addr }#:6443 - -# renovate: datasource=github-releases depName=siderolabs/talos -talosVersion: v1.14.0 -# renovate: datasource=docker depName=ghcr.io/siderolabs/kubelet -kubernetesVersion: v1.37.0 - -secretsPath: secrets.sops.yaml - -nodes: - #% for item in nodes %# - - host: "#{ item.name }#" - ip: "#{ item.address }#" - role: #{ 'control-plane' if item.controller else 'worker' }# - schematicId: "#{ item.schematic_id }#" - #% if item.secureboot %# - secureboot: true - #% endif %# - data: - #% if item.disk.startswith('/') %# - installDisk: "#{ item.disk }#" - installDiskSerial: "" - #% else %# - installDisk: "" - installDiskSerial: "#{ item.disk }#" - #% endif %# - macAddr: "#{ item.mac_addr | lower }#" - mtu: #{ item.mtu }# - encryptDisk: #{ item.encrypt_disk | string | lower }# - kernelModules: [#{ item.kernel_modules | join(', ') }#] - #% endfor %# diff --git a/template/mod.just b/template/mod.just deleted file mode 100644 index c05cd0e..0000000 --- a/template/mod.just +++ /dev/null @@ -1,182 +0,0 @@ -set quiet -set default-list -set default-script -set shell := ['bash', '-euo', 'pipefail', '-c'] -set script-interpreter := ['bash', '-euo', 'pipefail'] -set working-directory := '..' - -bootstrap_dir := justfile_dir() + '/bootstrap' -kubernetes_dir := justfile_dir() + '/kubernetes' -talos_dir := justfile_dir() + '/talos' -private_dir := justfile_dir() + '/.private' - -template_dir := justfile_dir() + '/template' -template_resources := template_dir + '/resources' -makejinja_config := justfile_dir() + '/makejinja.toml' -mise_fragment := justfile_dir() + '/.mise/conf.d/template.toml' - -config_file := justfile_dir() + '/cluster.toml' -schema_file := justfile_dir() + '/cluster.schema.json' -sample_config_file := justfile_dir() + '/cluster.sample.toml' -validate_script := template_dir + '/scripts/validate.py' -deploy_key := justfile_dir() + '/deploy.key' -webhook_token_file := justfile_dir() + '/flux-webhook-token.txt' -cloudflare_tunnel := justfile_dir() + '/cloudflare-tunnel.json' - -[doc('Render and validate configuration files')] -[group('template')] -configure: render encrypt-secrets validate-kubernetes validate-talos - -[doc('Check that all prerequisite files exist and cluster.toml validates against the schema')] -[group('template')] -[no-exit-message] -doctor: - rc=0 - just log info "just version" version "{{ just_version() }}" - just template doctor-check "cluster.toml" "{{ config_file }}" || rc=1 - just template doctor-check "cluster.sample.toml" "{{ sample_config_file }}" || rc=1 - config_json="$(just template config-json 2>/dev/null || true)" - if [ "$(jq -r '.ingress.mode' <<< "$config_json" 2>/dev/null)" = "cloudflare-tunnel" ]; then - just template doctor-check "cloudflare-tunnel.json" "{{ cloudflare_tunnel }}" || rc=1 - fi - just template doctor-check "age.key" "$SOPS_AGE_KEY_FILE" || rc=1 - just template doctor-check "deploy.key" "{{ deploy_key }}" || rc=1 - just template doctor-check "flux-webhook-token.txt" "{{ webhook_token_file }}" || rc=1 - just template doctor-check "template/" "{{ template_dir }}" || rc=1 - just template doctor-check "makejinja.toml" "{{ makejinja_config }}" || rc=1 - just template doctor-check "validate.py" "{{ validate_script }}" || rc=1 - if [ -f "{{ config_file }}" ] && [ -f "{{ validate_script }}" ]; then - if [ -n "$config_json" ]; then - just log info "ok" check schema - else - just log error "fail" check schema - just log info "diagnose with: uv run --locked --no-dev {{ validate_script }} {{ config_file }}" - rc=1 - fi - fi - [ "$rc" = 0 ] && just log info "all good" - exit "$rc" - -[doc('Initialize configuration files (cluster.toml, age key, deploy key, webhook token)')] -[group('template')] -init: - [ -f "{{ config_file }}" ] || cp "{{ sample_config_file }}" "{{ config_file }}" - [ -f "$SOPS_AGE_KEY_FILE" ] || age-keygen -pq --output "$SOPS_AGE_KEY_FILE" - [ -f "{{ deploy_key }}" ] || ssh-keygen -t ed25519 -C "deploy-key" -f "{{ deploy_key }}" -q -P "" - [ -f "{{ webhook_token_file }}" ] || openssl rand -hex 16 > "{{ webhook_token_file }}" - -# Editors resolve the #:schema directive in cluster.toml against this file; -# the validator tests fail when it drifts from the pydantic model. -[doc('Regenerate the cluster.toml JSON Schema from the validator')] -[group('template')] -schema: - uv run --quiet --locked --no-dev "{{ validate_script }}" --schema > "{{ schema_file }}" - oxfmt "{{ schema_file }}" > /dev/null - -[confirm("Remove all templated files and directories — continue? [y/N]")] -[doc('Remove rendered files (bootstrap/, kubernetes/, talos/, .sops.yaml)')] -[group('template')] -reset: - rm -rf "{{ bootstrap_dir }}" "{{ kubernetes_dir }}" "{{ talos_dir }}" "{{ justfile_dir() }}/.sops.yaml" - -# tidy-archive moves template/ (this module's source) away, so it runs as a -# subsequent dependency: the tidy script itself must spawn while the module -# directory still exists. -[confirm("All template related config will be archived — continue? [y/N]")] -[doc('Archive all template tooling under .private// (one-way, run after configure works)')] -[group('template')] -tidy: tidy-preconditions tidy-strip && tidy-archive - -# Validated, defaulted cluster config as JSON on stdout; fails when the -# config is invalid. -[private] -config-json: - uv run --quiet --locked --no-dev "{{ validate_script }}" "{{ config_file }}" - -[no-exit-message] -[private] -doctor-check label path: - if [ -e "{{ path }}" ]; then - just log info "ok" file "{{ label }}" - else - just log error "missing" file "{{ label }}" - exit 1 - fi - -[private] -encrypt-secrets: - find "{{ bootstrap_dir }}" "{{ kubernetes_dir }}" "{{ talos_dir }}" -type f -name '*.sops.*' -print0 \ - | while IFS= read -r -d '' f; do - if ! status="$(sops filestatus "$f" | jq -r '.encrypted')"; then - just log fatal "could not read encryption status" file "$f" - fi - case "$status" in - false) sops encrypt --in-place "$f" ;; - true) : ;; - *) just log fatal "could not determine encryption status" file "$f" ;; - esac - done - -[private] -render: - PYTHONDONTWRITEBYTECODE=1 uv run --locked --no-dev makejinja - -[private] -tidy-archive: - TIDY_FOLDER="{{ private_dir }}/$(date +%s)" - mkdir -p "$TIDY_FOLDER" - rm -rf "{{ justfile_dir() }}/.github/template-tests" "{{ justfile_dir() }}/.github/workflows"/template-*.yaml - mv \ - "{{ template_dir }}" \ - "{{ makejinja_config }}" \ - "{{ mise_fragment }}" \ - "{{ schema_file }}" \ - "{{ config_file }}" \ - "{{ sample_config_file }}" \ - "{{ justfile_dir() }}/pyproject.toml" \ - "{{ justfile_dir() }}/uv.lock" \ - "$TIDY_FOLDER/" - rmdir "$(dirname "{{ mise_fragment }}")" 2>/dev/null || true - rm -rf "{{ justfile_dir() }}/.venv" - -[private] -tidy-preconditions: - test -d "{{ template_dir }}" - test -d "{{ justfile_dir() }}/.github/template-tests" - test -f "{{ makejinja_config }}" - test -f "{{ config_file }}" - test -f "{{ sample_config_file }}" - test -f "{{ justfile_dir() }}/pyproject.toml" - test -f "{{ justfile_dir() }}/uv.lock" - test -f "{{ justfile_dir() }}/justfile" - test -f "{{ mise_fragment }}" - test -f "{{ schema_file }}" - test -f "{{ justfile_dir() }}/.renovaterc.json5" - compgen -G "{{ justfile_dir() }}/.github/workflows/template-*.yaml" > /dev/null - -[private] -tidy-strip: - sd '\..\.j2' '' "{{ justfile_dir() }}/.renovaterc.json5" - sd -A '(?ms)^# === template ===$.*' '' "{{ justfile_dir() }}/justfile" - -# Renders every bootstrap chart with the rendered values, catching chart or -# values drift that dry runs cannot; needs network access to pull charts. -[doc('Render the bootstrap helmfile charts against the rendered values')] -[group('template')] -test-helmfile: - helmfile --file "{{ bootstrap_dir }}/helmfile/apps.yaml" template --quiet > /dev/null - just log info "bootstrap charts rendered cleanly" - -[private] -validate-kubernetes: - bash "{{ template_resources }}/kubeconform.sh" "{{ kubernetes_dir }}" - -# Rendering needs the secrets bundle; topf generates and sops-encrypts one -# on first run (--confirm=false so it never prompts mid-pipeline). -[private] -[working-directory('talos')] -validate-talos: - topf render --confirm=false --output "$(mktemp -d)" >/dev/null - if ! sops filestatus secrets.sops.yaml | jq --exit-status '.encrypted == true' >/dev/null; then - just log fatal "Talos secrets bundle is not encrypted" - fi diff --git a/template/overrides/readme.partial.yaml.j2 b/template/overrides/readme.partial.yaml.j2 deleted file mode 100644 index b73f753..0000000 --- a/template/overrides/readme.partial.yaml.j2 +++ /dev/null @@ -1,5 +0,0 @@ -#| Place user jinja template overrides in this file's directory |# -#| Docs: https://mirkolenz.github.io/makejinja/makejinja.html |# -#| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/makejinja.toml |# -#| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/input1/not-empty.yaml.jinja |# -#| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/input2/not-empty.yaml.jinja |# diff --git a/template/resources/kubeconform.sh b/template/resources/kubeconform.sh deleted file mode 100644 index 821e569..0000000 --- a/template/resources/kubeconform.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -KUBERNETES_DIR=$1 - -[[ -z "${KUBERNETES_DIR}" ]] && echo "Kubernetes location not specified" && exit 1 - -kustomize_args=("--load-restrictor=LoadRestrictionsNone") -kustomize_config="kustomization.yaml" -kubeconform_args=( - "-strict" - "-ignore-missing-schemas" - "-skip" - "Gateway,HTTPRoute,Secret" - "-schema-location" - "default" - "-schema-location" - "https://k8s-schemas.home-operations.com/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" - "-verbose" -) - -echo "=== Validating standalone manifests in ${KUBERNETES_DIR}/flux ===" -find "${KUBERNETES_DIR}/flux" -maxdepth 1 -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file; -do - kubeconform "${kubeconform_args[@]}" "${file}" - if [[ ${PIPESTATUS[0]} != 0 ]]; then - exit 1 - fi -done - -echo "=== Validating kustomizations in ${KUBERNETES_DIR}/flux ===" -find "${KUBERNETES_DIR}/flux" -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file; -do - echo "=== Validating kustomizations in ${file/%$kustomize_config} ===" - kustomize build "${file/%$kustomize_config}" "${kustomize_args[@]}" | kubeconform "${kubeconform_args[@]}" - if [[ ${PIPESTATUS[0]} != 0 ]]; then - exit 1 - fi -done - -echo "=== Validating kustomizations in ${KUBERNETES_DIR}/apps ===" -find "${KUBERNETES_DIR}/apps" -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file; -do - echo "=== Validating kustomizations in ${file/%$kustomize_config} ===" - kustomize build "${file/%$kustomize_config}" "${kustomize_args[@]}" | kubeconform "${kubeconform_args[@]}" - if [[ ${PIPESTATUS[0]} != 0 ]]; then - exit 1 - fi -done diff --git a/template/scripts/plugin.py b/template/scripts/plugin.py deleted file mode 100644 index 40341eb..0000000 --- a/template/scripts/plugin.py +++ /dev/null @@ -1,150 +0,0 @@ -from pathlib import Path -from typing import Any - -import base64 -import json -import makejinja -import re -import validate - - -# Return the stripped contents of file_path, rejecting a missing or empty file -def _read_stripped(file_path: str) -> str: - try: - content = Path(file_path).read_text().strip() - except FileNotFoundError: - raise FileNotFoundError(f"File not found: {file_path}") from None - if not content: - raise ValueError(f"{file_path} is empty") - return content - - -# Return the parsed contents of a JSON file -def _read_json(file_path: str) -> dict[str, Any]: - try: - return json.loads(_read_stripped(file_path)) - except json.JSONDecodeError: - raise ValueError(f"Could not decode JSON file: {file_path}") from None - - -# Return the age public or private key from age.key -def age_key(key_type: str, file_path: str = 'age.key') -> str: - file_content = _read_stripped(file_path) - if key_type == 'public': - # Matches both classic (age1...) and post-quantum (age1pq1...) recipients - key_match = re.search(r"# public key: (age1[\w]+)", file_content) - if not key_match: - raise ValueError("Could not find public key in the age key file.") - return key_match.group(1) - elif key_type == 'private': - # (?:PQ-)? matches post-quantum identities (AGE-SECRET-KEY-PQ-1...) as well as classic ones - key_match = re.search(r"(AGE-SECRET-KEY-(?:PQ-)?1[\w]+)", file_content) - if not key_match: - raise ValueError("Could not find private key in the age key file.") - return key_match.group(1) - else: - raise ValueError("Invalid key type. Use 'public' or 'private'.") - - -# Return cloudflare tunnel fields from cloudflare-tunnel.json -def cloudflare_tunnel_id(file_path: str = 'cloudflare-tunnel.json') -> str: - data = _read_json(file_path) - tunnel_id = data.get("TunnelID") - if tunnel_id is None: - raise KeyError(f"Missing 'TunnelID' key in {file_path}") - if not tunnel_id: - raise ValueError(f"'TunnelID' is empty in {file_path}") - return tunnel_id - - -# Return cloudflare tunnel fields from cloudflare-tunnel.json in TUNNEL_TOKEN format -def cloudflare_tunnel_secret(file_path: str = 'cloudflare-tunnel.json') -> str: - data = _read_json(file_path) - for field in ("AccountTag", "TunnelID", "TunnelSecret"): - if field not in data: - raise KeyError(f"Missing '{field}' key in {file_path}") - if not data[field]: - raise ValueError(f"'{field}' is empty in {file_path}") - transformed_data = { - "a": data["AccountTag"], - "t": data["TunnelID"], - "s": data["TunnelSecret"] - } - json_string = json.dumps(transformed_data, separators=(',', ':')) - return base64.b64encode(json_string.encode('utf-8')).decode('utf-8') - - -# Return the Flux deploy key from deploy.key -def deploy_key(file_path: str = 'deploy.key') -> str: - return _read_stripped(file_path) - - -# Return the Flux webhook token from flux-webhook-token.txt -def webhook_token(file_path: str = 'flux-webhook-token.txt') -> str: - return _read_stripped(file_path) - - -CONFIG_FILE = 'cluster.toml' - -# SSH host keys as published by each provider. Must cover every host in -# KNOWN_SSH_HOSTS in validate.py; any other host requires the user to set -# repository.known_hosts in cluster.toml. -KNOWN_HOSTS = { - 'github.com': ( - 'github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n' - 'github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n' - 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=' - ), - 'gitlab.com': ( - 'gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf\n' - 'gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=\n' - 'gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9' - ), - 'codeberg.org': ( - 'codeberg.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIVIC02vnjFyL+I4RHfvIGNtOgJMe769VTF1VR4EB3ZB\n' - 'codeberg.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2pDxWr18SoiDJCGZ5LmxPygTlPu+cCKSkpqkvCyQzl5xmIMeKNdfdBpfbCGDPoZQghePzFZkKJNR/v9Win3Sc=\n' - 'codeberg.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hZi7K1/2E2uBX8gwPRJAHvRAob+3Sn+y2hxiEhN0buv1igjYFTgFO2qQD8vLfU/HT/P/rqvEeTvaDfY1y/vcvQ8+YuUYyTwE2UaVU5aJv89y6PEZBYycaJCPdGIfZlLMmjilh/Sk8IWSEK6dQr+g686lu5cSWrFW60ixWpHpEVB26eRWin3lKYWSQGMwwKv4LwmW3ouqqs4Z4vsqRFqXJ/eCi3yhpT+nOjljXvZKiYTpYajqUC48IHAxTWugrKe1vXWOPxVXXMQEPsaIRc2hpK+v1LmfB7GnEGvF1UAKnEZbUuiD9PBEeD5a1MZQIzcoPWCrTxipEpuXQ5Tni4mN' - ), -} - - -# makejinja adds import_paths (this directory) to sys.path, and both -# makejinja and pydantic come from the uv project environment, so the -# validator runs in-process. -def validate_config() -> dict[str, Any]: - try: - return validate.load(CONFIG_FILE) - except validate.ConfigError as e: - raise RuntimeError(f"config validation failed:\n{e}") from None - - -class Plugin(makejinja.plugin.Plugin): - def __init__(self, data: dict[str, Any]): - self._data = data - - - def data(self) -> makejinja.plugin.Data: - data = validate_config() - if ( - data['ingress']['mode'] == 'cloudflare-tunnel' - and not Path('cloudflare-tunnel.json').is_file() - ): - raise RuntimeError('cloudflare-tunnel.json not found — see README') - # The deploy key secret always pins every bundled provider host key - # (entries are matched per-hostname, so unused ones are inert); - # user-supplied entries for self-hosted git servers are appended. - repository = data['repository'] - repository['known_hosts'] = '\n'.join( - [*KNOWN_HOSTS.values(), repository['known_hosts']] - ).strip() - return data - - - def functions(self) -> makejinja.plugin.Functions: - return [ - age_key, - cloudflare_tunnel_id, - cloudflare_tunnel_secret, - deploy_key, - webhook_token - ] diff --git a/template/scripts/test_validate.py b/template/scripts/test_validate.py deleted file mode 100644 index 490c395..0000000 --- a/template/scripts/test_validate.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Unit tests for the cluster.toml validator. - -Run from the repo root: - uv run --locked pytest template/scripts/test_validate.py -q -""" - -from pathlib import Path - -import json -import sys -import tomllib - -import pytest - -sys.path.insert(0, str(Path(__file__).parent)) - -from pydantic import ValidationError # noqa: E402 -from validate import Config, ConfigError, format_errors, load, schema # noqa: E402 - -REPO_ROOT = Path(__file__).parents[2] -VALID = sorted((REPO_ROOT / ".github/template-tests/valid").glob("*.toml")) -INVALID = sorted((REPO_ROOT / ".github/template-tests/invalid").glob("*.toml")) - - -def config_from(fixture: str, **overrides) -> dict: - raw = tomllib.loads((REPO_ROOT / ".github/template-tests/valid" / fixture).read_text()) - for dotted, value in overrides.items(): - target = raw - *parents, leaf = dotted.split(".") - for key in parents: - target = target.setdefault(key, {}) - if value is None: - target.pop(leaf, None) - else: - target[leaf] = value - return raw - - -@pytest.mark.parametrize("fixture", VALID, ids=lambda p: p.stem) -def test_valid_fixture_accepted(fixture: Path): - load(str(fixture)) - - -@pytest.mark.parametrize("fixture", INVALID, ids=lambda p: p.stem) -def test_invalid_fixture_rejected(fixture: Path): - with pytest.raises(ConfigError): - load(str(fixture)) - - -def _load_raw(raw: dict) -> Config: - try: - return Config.model_validate(raw) - except ValidationError as e: - raise ConfigError(format_errors(e)) from None - - -def test_host_bits_error_suggests_network_address(): - raw = config_from("public.toml", **{"network.node_cidr": "10.10.10.5/24"}) - with pytest.raises(ConfigError, match=r"did you mean 10\.10\.10\.0/24"): - _load_raw(raw) - - -def test_duplicate_address_names_both_owners(): - raw = config_from("public.toml") - raw["nodes"][0]["address"] = raw["gateways"]["internal"] - with pytest.raises(ConfigError, match=r"gateways\.internal and nodes\[0\]\.address"): - _load_raw(raw) - - -def test_default_gateway_derived_from_node_cidr(): - raw = config_from("private.toml") - assert "default_gateway" not in raw["network"] - data = _load_raw(raw).model_dump(mode="json") - assert data["network"]["default_gateway"] == "10.10.10.1" - - -def test_coredns_addr_default_and_override(): - raw = config_from("private.toml") - assert _load_raw(raw).model_dump(mode="json")["kubernetes"]["coredns_addr"] == "10.43.0.10" - raw = config_from("private.toml", **{"kubernetes.coredns_addr": "10.43.0.53"}) - assert _load_raw(raw).model_dump(mode="json")["kubernetes"]["coredns_addr"] == "10.43.0.53" - raw = config_from("private.toml", **{"kubernetes.coredns_addr": "192.168.9.9"}) - with pytest.raises(ConfigError, match="not inside svc_cidr"): - _load_raw(raw) - - -def test_spegel_enabled_follows_node_count(): - two_nodes = config_from("private.toml") - assert _load_raw(two_nodes).spegel.enabled is True - one_node = config_from("private.toml") - one_node["nodes"] = one_node["nodes"][:1] - assert _load_raw(one_node).spegel.enabled is False - empty_section = config_from("private.toml", spegel={}) - assert _load_raw(empty_section).spegel.enabled is True - explicit = config_from("private.toml", **{"spegel.enabled": False}) - assert _load_raw(explicit).spegel.enabled is False - - -def test_controller_count_ignores_workers(): - raw = config_from("private.toml") - assert [n["controller"] for n in raw["nodes"]] == [True, False] - assert _load_raw(raw).controller_count == 1 - raw["nodes"][1]["controller"] = True - assert _load_raw(raw).controller_count == 2 - - -def test_derived_fields_are_not_settable(): - raw = config_from("public.toml", cilium_bgp_enabled=True) - with pytest.raises(ConfigError, match="cilium_bgp_enabled"): - _load_raw(raw) - raw = config_from("public.toml", controller_count=3) - with pytest.raises(ConfigError, match="controller_count"): - _load_raw(raw) - - -def test_cert_sans_single_source(): - raw = config_from("public.toml") - assert _load_raw(raw).cert_sans == ["127.0.0.1", "10.10.10.254", "example.com"] - raw = config_from("private.toml") - assert _load_raw(raw).cert_sans == ["127.0.0.1", "10.10.10.254"] - - -def test_ingress_mode_follows_dns_provider(): - cloudflare = config_from("public.toml", ingress=None) - assert _load_raw(cloudflare).ingress.mode == "cloudflare-tunnel" - internal = config_from("internal.toml") - assert (REPO_ROOT / ".github/template-tests/valid/internal.toml").exists() - assert "ingress" not in internal - assert _load_raw(internal).ingress.mode == "none" - - -def test_direct_mode_requires_cloudflare_dns(): - raw = config_from("internal.toml", **{"ingress.mode": "direct"}) - with pytest.raises(ConfigError, match="requires dns.provider 'cloudflare'"): - _load_raw(raw) - - -def test_schematic_id_inherits_from_talos_section(): - raw = config_from("public.toml") - cfg = _load_raw(raw) - assert cfg.nodes[0].schematic_id == cfg.talos.schematic_id - assert cfg.nodes[1].schematic_id is not None - - -def test_partial_bgp_rejected(): - raw = config_from("private.toml", **{"cilium.bgp.router_addr": "10.10.1.1", "cilium.bgp.router_asn": "64513"}) - with pytest.raises(ConfigError, match="partially configured"): - _load_raw(raw) - - -def test_node_defaults_exported(): - data = _load_raw(config_from("private.toml")).model_dump(mode="json") - node = data["nodes"][0] - assert node["mtu"] == 1500 - assert node["secureboot"] is False - assert node["kernel_modules"] == [] - - -def test_gateways_may_leave_node_cidr_only_with_bgp(): - with_bgp = config_from("public.toml", **{"gateways.external": "192.168.50.1"}) - _load_raw(with_bgp) # public.toml enables BGP - without_bgp = config_from("private.toml", **{"gateways.external": "192.168.50.1"}) - with pytest.raises(ConfigError, match="required unless BGP is enabled"): - _load_raw(without_bgp) - - -def test_schema_file_matches_model(): - committed = json.loads((REPO_ROOT / "cluster.schema.json").read_text()) - assert committed == schema(), "cluster.schema.json is stale: run `just template schema`" - - -def test_schema_omits_computed_fields(): - assert "cluster_issuer" not in schema()["properties"] diff --git a/template/scripts/validate.py b/template/scripts/validate.py deleted file mode 100644 index adb0098..0000000 --- a/template/scripts/validate.py +++ /dev/null @@ -1,402 +0,0 @@ -"""Validate cluster.toml, apply defaults, and emit the config as JSON. - -Standalone usage (doctor, CI): uv run --locked --no-dev template/scripts/validate.py [cluster.toml] -Schema export (just template schema): uv run --locked --no-dev template/scripts/validate.py --schema -In-process usage (makejinja plugin): from validate import load - -Exits non-zero with one human-readable error per line on stderr when the -config is invalid. -""" - -from ipaddress import IPv4Address, IPv4Network -from pathlib import Path -from typing import Annotated, Any, Literal, Self - -import json -import re -import sys -import tomllib - -from pydantic import ( - AfterValidator, - BaseModel, - BeforeValidator, - ConfigDict, - Field, - ValidationError, - computed_field, - model_validator, -) - -# Git hosts whose SSH host keys are bundled with the template; ssh:// URLs -# pointing anywhere else must provide repository.known_hosts. -KNOWN_SSH_HOSTS = ["github.com", "gitlab.com", "codeberg.org"] - -REPO_URL_PATTERN = r"^(https?://|ssh://git@)[^/]+/.+$" -FQDN_PATTERN = r"^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$" - - -def _network(value: Any) -> Any: - """Parse a CIDR, requiring network-address form (no host bits set).""" - if not isinstance(value, str): - return value - try: - return IPv4Network(value) - except ValueError: - try: - fixed = IPv4Network(value, strict=False) - except ValueError: - raise ValueError(f"{value!r} is not a valid IPv4 CIDR") from None - raise ValueError( - f"{value!r} has host bits set; did you mean {fixed}?" - ) from None - - -def _asn(value: str) -> str: - if value == "": - return value - if not re.fullmatch(r"[0-9]+", value): - raise ValueError(f"{value!r} must be a decimal ASN") - if not 1 <= int(value) <= 4294967295: - raise ValueError(f"{value!r} must be in the range 1-4294967295") - return value - - -type Cidr = Annotated[IPv4Network, BeforeValidator(_network)] -type Asn = Annotated[str, AfterValidator(_asn)] -type Fqdn = Annotated[str, Field(pattern=FQDN_PATTERN)] - - -class Model(BaseModel): - model_config = ConfigDict(extra="forbid") - - -class Network(Model): - node_cidr: Cidr - dns_servers: list[IPv4Address] = [IPv4Address("1.1.1.1"), IPv4Address("1.0.0.1")] - ntp_servers: list[IPv4Address] = [IPv4Address("162.159.200.1"), IPv4Address("162.159.200.123")] - # The first IP in node_cidr unless set explicitly. - default_gateway: IPv4Address = Field( - default_factory=lambda data: data["node_cidr"].network_address + 1 - ) - vlan_tag: str | None = Field(default=None, pattern=r"^[0-9]+$") - - @model_validator(mode="after") - def check(self) -> Self: - if self.default_gateway not in self.node_cidr: - raise ValueError( - f"default_gateway {self.default_gateway} is not inside node_cidr {self.node_cidr}" - ) - if self.vlan_tag is not None and not 1 <= int(self.vlan_tag) <= 4094: - raise ValueError(f"vlan_tag {self.vlan_tag} must be in the range 1-4094") - return self - - -class Api(Model): - addr: IPv4Address - tls_sans: list[Fqdn] | None = None - - -class Kubernetes(Model): - pod_cidr: Cidr = IPv4Network("10.42.0.0/16") - svc_cidr: Cidr = IPv4Network("10.43.0.0/16") - # The 10th IP in svc_cidr unless set explicitly. - coredns_addr: IPv4Address = Field( - default_factory=lambda data: data["svc_cidr"].network_address + 10 - ) - api: Api - - @model_validator(mode="after") - def check(self) -> Self: - if self.coredns_addr not in self.svc_cidr: - raise ValueError( - f"coredns_addr {self.coredns_addr} is not inside svc_cidr {self.svc_cidr}" - ) - return self - - -class Gateways(Model): - internal: IPv4Address - dns: IPv4Address - # Required when ingress.mode is not "none". - external: IPv4Address | None = None - - -class Repository(Model): - url: str = Field(pattern=REPO_URL_PATTERN) - branch: str = Field(default="main", min_length=1) - webhook_provider: Literal["github", "gitlab", "generic-hmac", "none"] = "github" - known_hosts: str = "" - - @model_validator(mode="after") - def check(self) -> Self: - if self.url.startswith("ssh://"): - host = self.url.removeprefix("ssh://git@").split("/", 1)[0].split(":", 1)[0] - if host not in KNOWN_SSH_HOSTS and not self.known_hosts: - raise ValueError( - f"known_hosts is required for ssh:// URLs to {host!r} " - f"(host keys are only bundled for {', '.join(KNOWN_SSH_HOSTS)})" - ) - return self - - -class Domain(Model): - name: Fqdn - - -class Dns(Model): - provider: Literal["cloudflare", "none"] = "cloudflare" - token: str = "" - - @model_validator(mode="after") - def check(self) -> Self: - if self.provider == "cloudflare" and not self.token: - raise ValueError("token is required when dns.provider is 'cloudflare'") - if self.provider == "none" and self.token: - raise ValueError("token must be empty when dns.provider is 'none'") - return self - - -class Ingress(Model): - mode: Literal["cloudflare-tunnel", "direct", "none"] = "cloudflare-tunnel" - - -class Bgp(Model): - router_addr: IPv4Address | Literal[""] = "" - router_asn: Asn = "" - node_asn: Asn = "" - - @model_validator(mode="after") - def check(self) -> Self: - unset = [name for name in ("router_addr", "router_asn", "node_asn") if getattr(self, name) == ""] - if unset and len(unset) < 3: - raise ValueError( - "bgp is partially configured: set router_addr, router_asn and " - f"node_asn together (missing: {', '.join(unset)})" - ) - return self - - -class Talos(Model): - # Default Image Factory schematic for nodes that don't set their own. - schematic_id: str | None = Field(default=None, pattern=r"^[a-z0-9]{64}$") - - -class Spegel(Model): - # True when the cluster has more than one node, unless set explicitly. - enabled: bool | None = None - - -class Cilium(Model): - loadbalancer_mode: Literal["dsr", "snat"] = "dsr" - bgp: Bgp = Bgp() - - -class Node(Model): - name: str = Field(pattern=r"^[a-z0-9][a-z0-9\-]{0,61}[a-z0-9]$|^[a-z0-9]$") - address: IPv4Address - controller: bool - disk: str - mac_addr: str = Field(pattern=r"^([0-9a-f]{2}:){5}[0-9a-f]{2}$") - # Falls back to talos.schematic_id when unset. - schematic_id: str | None = Field(default=None, pattern=r"^[a-z0-9]{64}$") - mtu: int = Field(default=1500, ge=1450, le=9000) - secureboot: bool = False - encrypt_disk: bool = False - kernel_modules: list[str] = [] - - @model_validator(mode="after") - def check(self) -> Self: - if self.name in ("global", "controller", "worker"): - raise ValueError(f"node name {self.name!r} is reserved") - return self - - -class Config(Model): - model_config = ConfigDict(extra="forbid", title="cluster.toml") - - network: Network - kubernetes: Kubernetes - gateways: Gateways - repository: Repository - domain: Domain - dns: Dns - # Defaults to "cloudflare-tunnel" when dns.provider is "cloudflare", - # otherwise "none". - ingress: Ingress = Field( - default_factory=lambda data: Ingress( - mode="cloudflare-tunnel" if data["dns"].provider == "cloudflare" else "none" - ) - ) - cilium: Cilium = Cilium() - talos: Talos = Talos() - spegel: Spegel = Spegel() - nodes: list[Node] - - @computed_field - @property - def cilium_bgp_enabled(self) -> bool: - bgp = self.cilium.bgp - return bgp.router_addr != "" and bgp.router_asn != "" and bgp.node_asn != "" - - # Replica counts for control-plane-only workloads key off this rather - # than len(nodes); a cluster can have many workers but one controller. - @computed_field - @property - def controller_count(self) -> int: - return sum(1 for node in self.nodes if node.controller) - - @computed_field - @property - def cluster_issuer(self) -> str: - if self.dns.provider == "cloudflare": - return "letsencrypt-production" - return "internal-ca" - - # Single source for the machine and apiServer certificate SAN lists, - # which live in separate patch files. - @computed_field - @property - def cert_sans(self) -> list[str]: - return ["127.0.0.1", str(self.kubernetes.api.addr), *(self.kubernetes.api.tls_sans or [])] - - @model_validator(mode="after") - def check(self) -> Self: - if self.spegel.enabled is None: - self.spegel.enabled = len(self.nodes) > 1 - for i, node in enumerate(self.nodes): - if node.schematic_id is None: - node.schematic_id = self.talos.schematic_id - if node.schematic_id is None: - raise ValueError( - f"nodes[{i}].schematic_id is required: set it on the node " - "or set a cluster-wide default in [talos]" - ) - if self.ingress.mode != "none" and self.dns.provider != "cloudflare": - raise ValueError( - f"ingress.mode {self.ingress.mode!r} requires dns.provider 'cloudflare'" - ) - if self.ingress.mode != "none" and self.gateways.external is None: - raise ValueError( - f"gateways.external is required when ingress.mode is {self.ingress.mode!r}" - ) - - cidrs = { - "network.node_cidr": self.network.node_cidr, - "kubernetes.pod_cidr": self.kubernetes.pod_cidr, - "kubernetes.svc_cidr": self.kubernetes.svc_cidr, - } - names = list(cidrs) - for i, a in enumerate(names): - for b in names[i + 1:]: - if cidrs[a].overlaps(cidrs[b]): - raise ValueError(f"{a} {cidrs[a]} overlaps {b} {cidrs[b]}") - - addresses = { - "kubernetes.api.addr": self.kubernetes.api.addr, - "gateways.internal": self.gateways.internal, - "gateways.dns": self.gateways.dns, - "network.default_gateway": self.network.default_gateway, - } | {f"nodes[{i}].address": n.address for i, n in enumerate(self.nodes)} - if self.gateways.external is not None: - addresses["gateways.external"] = self.gateways.external - seen: dict[IPv4Address, str] = {} - for owner, addr in addresses.items(): - if addr in seen: - raise ValueError(f"address {addr} is used by both {seen[addr]} and {owner}") - seen[addr] = owner - - node_cidr = self.network.node_cidr - for i, node in enumerate(self.nodes): - if node.address not in node_cidr: - raise ValueError( - f"nodes[{i}].address {node.address} is not inside node_cidr {node_cidr}" - ) - if self.kubernetes.api.addr not in node_cidr: - raise ValueError( - f"kubernetes.api.addr {self.kubernetes.api.addr} is not inside node_cidr {node_cidr}" - ) - # Without BGP the gateway VIPs are announced over L2 and must live in - # the node network. - if not self.cilium_bgp_enabled: - for name in ("internal", "dns", "external"): - addr = getattr(self.gateways, name) - if addr is not None and addr not in node_cidr: - raise ValueError( - f"gateways.{name} {addr} is not inside node_cidr {node_cidr} " - "(required unless BGP is enabled)" - ) - - for field, label in (("name", "name"), ("mac_addr", "MAC address")): - values: dict[str, int] = {} - for i, node in enumerate(self.nodes): - value = getattr(node, field) - if value in values: - raise ValueError( - f"duplicate node {label} {value!r} on nodes[{values[value]}] and nodes[{i}]" - ) - values[value] = i - return self - - -def format_errors(error: ValidationError) -> str: - lines = [] - for err in error.errors(): - loc = ".".join(str(part) for part in err["loc"]) - msg = err["msg"].removeprefix("Value error, ") - lines.append(f"{loc}: {msg}" if loc else msg) - return "\n".join(lines) - - -class ConfigError(Exception): - pass - - -# Validate config_file and return the defaulted config as a plain dict. -# Raises ConfigError with a human-readable message. -def load(config_file: str = "cluster.toml") -> dict[str, Any]: - path = Path(config_file) - try: - raw = tomllib.loads(path.read_text()) - except FileNotFoundError: - raise ConfigError(f"{path}: file not found") from None - except tomllib.TOMLDecodeError as e: - raise ConfigError(f"{path}: invalid TOML: {e}") from None - - try: - config = Config.model_validate(raw) - except ValidationError as e: - raise ConfigError(format_errors(e)) from None - - # Unset optionals stay in the dump as None rather than being dropped: - # makejinja renders with StrictUndefined, so a template testing - # network.vlan_tag needs the key to exist. - return config.model_dump(mode="json") - - -# JSON Schema for editor completion and validation of cluster.toml (taplo's -# #:schema directive). Cross-field rules and data-aware defaults only exist in -# the model validators, so the schema is an editing aid, not the gate. -def schema() -> dict[str, Any]: - return { - "$schema": "https://json-schema.org/draft/2020-12/schema", - **Config.model_json_schema(), - } - - -def main() -> int: - if sys.argv[1:] == ["--schema"]: - json.dump(schema(), sys.stdout, indent=2) - sys.stdout.write("\n") - return 0 - try: - data = load(sys.argv[1] if len(sys.argv) > 1 else "cluster.toml") - except ConfigError as e: - print(e, file=sys.stderr) - return 1 - json.dump(data, sys.stdout, indent=2) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/uv.lock b/uv.lock deleted file mode 100644 index ed5f922..0000000 --- a/uv.lock +++ /dev/null @@ -1,365 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.14" - -[[package]] -name = "annotated-types" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, -] - -[[package]] -name = "attrs" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, -] - -[[package]] -name = "cattrs" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" }, -] - -[[package]] -name = "click" -version = "8.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, -] - -[[package]] -name = "cluster-template" -version = "0.0.0" -source = { virtual = "." } -dependencies = [ - { name = "makejinja" }, - { name = "pydantic" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, -] - -[package.metadata] -requires-dist = [ - { name = "makejinja", specifier = "==2.8.3" }, - { name = "pydantic", specifier = "==2.13.5" }, -] - -[package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=8" }] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "frozendict" -version = "2.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/b2/2a3d1374b7780999d3184e171e25439a8358c47b481f68be883c14086b4c/frozendict-2.4.7.tar.gz", hash = "sha256:e478fb2a1391a56c8a6e10cc97c4a9002b410ecd1ac28c18d780661762e271bd", size = 317082, upload-time = "2025-11-11T22:40:14.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/74/f94141b38a51a553efef7f510fc213894161ae49b88bffd037f8d2a7cb2f/frozendict-2.4.7-py3-none-any.whl", hash = "sha256:972af65924ea25cf5b4d9326d549e69a9a4918d8a76a9d3a7cd174d98b237550", size = 16264, upload-time = "2025-11-11T22:40:12.836Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "makejinja" -version = "2.8.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozendict" }, - { name = "jinja2" }, - { name = "pyyaml" }, - { name = "rich-click" }, - { name = "typed-settings", extra = ["attrs", "cattrs", "click"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/94/313ff9b192ee37fad7c7b65bfd62888a7e3d7dfe394e93a7a986bc8d3710/makejinja-2.8.3.tar.gz", hash = "sha256:3462e7718f4deba02beecbcb4a77b5f60c1df32391c5f9f5e9fc9985320b3236", size = 13238, upload-time = "2026-09-07T11:10:03.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/fe/ff26698949c66d86888a0ae2605c2a94c599527cfaf83306718efaaca0b5/makejinja-2.8.3-py3-none-any.whl", hash = "sha256:564e5191fc75e7d8b83fb4d478f89ef6bc7a0395ae296ee56eb9b58a91de43fa", size = 15519, upload-time = "2026-09-07T11:10:01.845Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "packaging" -version = "26.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pydantic" -version = "2.13.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, - { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, - { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, - { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, - { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, - { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, - { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, - { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, - { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, - { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, - { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, - { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, - { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, - { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, - { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, - { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, - { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, - { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, - { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, - { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, - { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, - { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, - { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, -] - -[[package]] -name = "pygments" -version = "2.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, -] - -[[package]] -name = "pytest" -version = "9.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - -[[package]] -name = "rich" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, -] - -[[package]] -name = "rich-click" -version = "1.9.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f7/ea/21e4867ea0ef881ffd4c0550fc21a061435e50d6324bcd034396633cbc18/rich_click-1.9.8.tar.gz", hash = "sha256:4008f921da88b5d91646c134ec881c1500e5a6b3f093e90e8f29400e09608371", size = 75363, upload-time = "2026-05-28T19:54:59.144Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/97/a87901aef6b7e7e4a34c6dd6cc17dca8594a592ef9d9dd765fca2b7facf7/rich_click-1.9.8-py3-none-any.whl", hash = "sha256:12873865396e6927835d4eabb1cc3996edcd65b7ac9b2391a29eca4f335a2f93", size = 72189, upload-time = "2026-05-28T19:54:57.867Z" }, -] - -[[package]] -name = "typed-settings" -version = "25.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/19/a8155d9f411ed7cb4a5f510a6c37ac09ea6065542c597c23d0a88ec2fbed/typed_settings-25.3.0.tar.gz", hash = "sha256:865eb52c3184f467705645a1e58db9d719e08b9d7957448a2ad8cbbc22ed21a6", size = 3492969, upload-time = "2025-11-29T22:10:25.768Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/9e/d01ef2bed8e995bbdeeb5307a571729a10545de4a6f8f06d9175fc7a1978/typed_settings-25.3.0-py3-none-any.whl", hash = "sha256:8fe578c84ae2e44f6e8bdde256d2449fbff45f47dca3c4092aeee03900efc12c", size = 63962, upload-time = "2025-11-29T22:10:23.77Z" }, -] - -[package.optional-dependencies] -attrs = [ - { name = "attrs" }, -] -cattrs = [ - { name = "cattrs" }, -] -click = [ - { name = "click" }, -] - -[[package]] -name = "typing-extensions" -version = "4.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, -]