├── .shellcheckrc
├── .github
├── release.yaml
├── workflows
│ ├── labeler.yaml
│ ├── label-sync.yaml
│ ├── e2e.yaml
│ └── release.yaml
├── tests
│ ├── nodes.yaml
│ ├── private.yaml
│ └── public.yaml
├── labels.yaml
└── labeler.yaml
├── templates
├── config
│ ├── kubernetes
│ │ ├── apps
│ │ │ ├── cert-manager
│ │ │ │ └── cert-manager
│ │ │ │ │ ├── issuers
│ │ │ │ │ ├── .mjfilter.py
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ ├── secret.sops.yaml.j2
│ │ │ │ │ ├── secret-generator.yaml.j2
│ │ │ │ │ └── clusterissuers.sops.yaml.j2
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ └── values.yaml.j2
│ │ │ ├── kube-system
│ │ │ │ ├── cilium
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ ├── config
│ │ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ │ └── networks.yaml.j2
│ │ │ │ │ └── values.yaml.j2
│ │ │ │ ├── reloader
│ │ │ │ │ └── values.yaml.j2
│ │ │ │ ├── metrics-server
│ │ │ │ │ └── values.yaml.j2
│ │ │ │ ├── spegel
│ │ │ │ │ └── values.yaml.j2
│ │ │ │ └── coredns
│ │ │ │ │ └── values.yaml.j2
│ │ │ ├── network
│ │ │ │ ├── envoy-gateway
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ └── config
│ │ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ │ ├── secret-generator.yaml.j2
│ │ │ │ │ │ ├── certificate.sops.yaml.j2
│ │ │ │ │ │ ├── podmonitor.yaml.j2
│ │ │ │ │ │ └── envoy.sops.yaml.j2
│ │ │ │ ├── cloudflare-dns
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ ├── config
│ │ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ │ ├── secret.sops.yaml.j2
│ │ │ │ │ │ └── secret-generator.yaml.j2
│ │ │ │ │ └── values.sops.yaml.j2
│ │ │ │ ├── cloudflare-tunnel
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ ├── config
│ │ │ │ │ │ ├── secret.sops.yaml.j2
│ │ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ │ ├── dnsendpoint.sops.yaml.j2
│ │ │ │ │ │ ├── secret-generator.yaml.j2
│ │ │ │ │ │ └── config.sops.yaml.j2
│ │ │ │ │ └── values.yaml.j2
│ │ │ │ └── k8s-gateway
│ │ │ │ │ └── values.sops.yaml.j2
│ │ │ ├── argo-system
│ │ │ │ └── argo-cd
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ ├── config
│ │ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ │ └── http-route.yaml.j2
│ │ │ │ │ ├── values.sops.yaml.j2
│ │ │ │ │ └── values.yaml.j2
│ │ │ └── default
│ │ │ │ └── echo
│ │ │ │ └── values.sops.yaml.j2
│ │ ├── argo
│ │ │ ├── repositories
│ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ ├── secret-generator.yaml.j2
│ │ │ │ └── github.sops.yaml.j2
│ │ │ ├── settings
│ │ │ │ ├── kustomization.yaml.j2
│ │ │ │ └── cluster-settings.yaml.j2
│ │ │ └── apps
│ │ │ │ ├── network
│ │ │ │ ├── envoy-gateway.yaml.j2
│ │ │ │ ├── k8s-gateway.yaml.j2
│ │ │ │ ├── cloudflare-tunnel.yaml.j2
│ │ │ │ └── cloudflare-dns.yaml.j2
│ │ │ │ ├── default
│ │ │ │ └── echo.yaml.j2
│ │ │ │ ├── kube-system
│ │ │ │ ├── coredns.yaml.j2
│ │ │ │ ├── reloader.yaml.j2
│ │ │ │ ├── spegel.yaml.j2
│ │ │ │ ├── metrics-server.yaml.j2
│ │ │ │ └── cilium.yaml.j2
│ │ │ │ ├── cert-manager
│ │ │ │ └── cert-manager.yaml.j2
│ │ │ │ └── argo-system
│ │ │ │ └── argo-cd.yaml.j2
│ │ └── components
│ │ │ └── common
│ │ │ ├── helm-secrets-private-keys.sops.yaml.j2
│ │ │ ├── settings.yaml.j2
│ │ │ ├── repositories.yaml.j2
│ │ │ └── apps.yaml.j2
│ ├── talos
│ │ ├── patches
│ │ │ ├── global
│ │ │ │ ├── machine-kubelet.yaml.j2
│ │ │ │ ├── machine-time.yaml.j2
│ │ │ │ ├── machine-network.yaml.j2
│ │ │ │ ├── machine-files.yaml.j2
│ │ │ │ └── machine-sysctls.yaml.j2
│ │ │ ├── controller
│ │ │ │ └── cluster.yaml.j2
│ │ │ └── README.md.j2
│ │ ├── talenv.yaml.j2
│ │ └── talconfig.yaml.j2
│ ├── .sops.yaml.j2
│ └── bootstrap
│ │ └── helmfile.d
│ │ ├── 00-crds.yaml.j2
│ │ └── 01-apps.yaml.j2
├── overrides
│ └── readme.partial.yaml.j2
└── scripts
│ └── plugin.py
├── .vscode
├── extensions.json
└── settings.json
├── .gitignore
├── .editorconfig
├── .gitattributes
├── makejinja.toml
├── .taskfiles
├── template
│ ├── resources
│ │ ├── nodes.schema.cue
│ │ ├── cluster.schema.cue
│ │ └── kubeconform.sh
│ └── Taskfile.yaml
├── bootstrap
│ └── Taskfile.yaml
└── talos
│ └── Taskfile.yaml
├── Taskfile.yaml
├── nodes.sample.yaml
├── LICENSE
├── .mise.toml
├── scripts
├── lib
│ └── common.sh
└── bootstrap-apps.sh
├── cluster.sample.yaml
├── .renovaterc.json5
└── README.md
/.shellcheckrc:
--------------------------------------------------------------------------------
1 | disable=SC1091
2 | disable=SC2155
3 |
--------------------------------------------------------------------------------
/.github/release.yaml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | authors:
4 | - github-actions
5 | - renovate
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/issuers/.mjfilter.py:
--------------------------------------------------------------------------------
1 | main = lambda data: data.get("cloudflare", {}).get("enabled", False) == True
2 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/cilium/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./config
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/envoy-gateway/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./config
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/argo-system/argo-cd/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./config
6 |
7 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./issuers
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-dns/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./config
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./config
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/repositories/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | generators:
5 | - ./secret-generator.yaml
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/settings/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./cluster-settings.yaml
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/argo-system/argo-cd/config/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./http-route.yaml
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/cilium/config/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | resources:
5 | - ./networks.yaml
6 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "blueglassblock.better-json5",
4 | "irongeek.vscode-env",
5 | "redhat.vscode-yaml",
6 | "signageos.signageos-vscode-sops"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-dns/config/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | generators:
5 | - ./secret-generator.yaml
6 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/issuers/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | generators:
5 | - ./secret-generator.yaml
6 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/global/machine-kubelet.yaml.j2:
--------------------------------------------------------------------------------
1 | machine:
2 | kubelet:
3 | extraConfig:
4 | serializeImagePulls: false
5 | nodeIP:
6 | validSubnets:
7 | - #{ node_cidr }#
8 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/global/machine-time.yaml.j2:
--------------------------------------------------------------------------------
1 | machine:
2 | time:
3 | disabled: false
4 | servers:
5 | #% for item in node_ntp_servers %#
6 | - #{ item }#
7 | #% endfor %#
8 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-dns/config/secret.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: cloudflare-dns-secret
6 | stringData:
7 | api-token: "#{ cloudflare_token }#"
8 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: cert-manager-secret
6 | stringData:
7 | api-token: "#{ cloudflare_token }#"
8 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/global/machine-network.yaml.j2:
--------------------------------------------------------------------------------
1 | machine:
2 | network:
3 | disableSearchDomain: true
4 | nameservers:
5 | #% for item in node_dns_servers %#
6 | - #{ item }#
7 | #% endfor %#
8 |
--------------------------------------------------------------------------------
/templates/config/talos/talenv.yaml.j2:
--------------------------------------------------------------------------------
1 | # renovate: datasource=docker depName=ghcr.io/siderolabs/installer
2 | talosVersion: v1.11.5
3 | # renovate: datasource=docker depName=ghcr.io/siderolabs/kubelet
4 | kubernetesVersion: v1.34.2
5 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/reloader/values.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | fullnameOverride: reloader
3 | reloader:
4 | readOnlyRootFileSystem: true
5 | podMonitor:
6 | enabled: true
7 | namespace: "{{ .Release.Namespace }}"
8 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/components/common/helm-secrets-private-keys.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: helm-secrets-private-keys
6 | stringData:
7 | key.txt: |
8 | #{ age_key('private') }#
9 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/config/secret.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: cloudflare-tunnel-secret
6 | stringData:
7 | TUNNEL_TOKEN: "#{ cloudflare_tunnel_secret() }#"
8 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/envoy-gateway/config/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 |
5 | generators:
6 | - ./secret-generator.yaml
7 |
8 | resources:
9 | - ./podmonitor.yaml
10 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/config/kustomization.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kustomize.config.k8s.io/v1beta1
3 | kind: Kustomization
4 | generators:
5 | - ./secret-generator.yaml
6 |
7 | generatorOptions:
8 | disableNameSuffixHash: true
9 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/argo-system/argo-cd/values.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | fullnameOverride: "argocd"
2 | configs:
3 | secret:
4 | argocdServerAdminPassword: #{ argo_password }#
5 | argocdServerAdminPasswordMtime: #{ current_datetime() }#
6 | githubSecret: #{ github_push_token() }#
7 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/repositories/secret-generator.yaml.j2:
--------------------------------------------------------------------------------
1 | apiVersion: viaduct.ai/v1
2 | kind: ksops
3 | metadata:
4 | name: repositories-secret-generator
5 | annotations:
6 | config.kubernetes.io/function: |
7 | exec:
8 | path: ksops
9 | files:
10 | - ./github.sops.yaml
11 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-dns/config/secret-generator.yaml.j2:
--------------------------------------------------------------------------------
1 | apiVersion: viaduct.ai/v1
2 | kind: ksops
3 | metadata:
4 | name: cloudflare-dns-secret-generator
5 | annotations:
6 | config.kubernetes.io/function: |
7 | exec:
8 | path: ksops
9 | files:
10 | - ./secret.sops.yaml
11 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/values.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | crds:
3 | enabled: true
4 | replicaCount: 1
5 | dns01RecursiveNameservers: https://1.1.1.1:443/dns-query,https://1.0.0.1:443/dns-query
6 | dns01RecursiveNameserversOnly: true
7 | prometheus:
8 | enabled: true
9 | servicemonitor:
10 | enabled: true
11 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/envoy-gateway/config/secret-generator.yaml.j2:
--------------------------------------------------------------------------------
1 | apiVersion: viaduct.ai/v1
2 | kind: ksops
3 | metadata:
4 | name: envoy-gateway-secret-generator
5 | annotations:
6 | config.kubernetes.io/function: |
7 | exec:
8 | path: ksops
9 | files:
10 | - ./certificate.sops.yaml
11 | - ./envoy.sops.yaml
12 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/issuers/secret-generator.yaml.j2:
--------------------------------------------------------------------------------
1 | apiVersion: viaduct.ai/v1
2 | kind: ksops
3 | metadata:
4 | name: cert-manager-secret-generator
5 | annotations:
6 | config.kubernetes.io/function: |
7 | exec:
8 | path: ksops
9 | files:
10 | - ./secret.sops.yaml
11 | - ./clusterissuers.sops.yaml
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Secrets
2 | *.pub
3 | *.key
4 | *.decrypted~*.yaml
5 | /age.key
6 | /cloudflare-tunnel.json
7 | /github-deploy.key
8 | /github-deploy.key.pub
9 | /github-push-token.txt
10 | # Template config files
11 | /cluster.yaml
12 | /nodes.yaml
13 | # Kubernetes
14 | kubeconfig
15 | talosconfig
16 | # Misc.
17 | .private/
18 | .task/
19 | .venv/
20 | .DS_Store
21 | Thumbs.db
22 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/metrics-server/values.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | args:
3 | - --kubelet-insecure-tls
4 | - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
5 | - --kubelet-use-node-status-port
6 | - --metric-resolution=10s
7 | - --kubelet-request-timeout=2s
8 | metrics:
9 | enabled: true
10 | serviceMonitor:
11 | enabled: true
12 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/spegel/values.yaml.j2:
--------------------------------------------------------------------------------
1 | #% if spegel_enabled %#
2 | ---
3 | spegel:
4 | containerdSock: /run/containerd/containerd.sock
5 | containerdRegistryConfigPath: /etc/cri/conf.d/hosts
6 | service:
7 | registry:
8 | hostPort: 29999
9 | serviceMonitor:
10 | enabled: true
11 | grafanaDashboard:
12 | enabled: true
13 | #% endif %#
14 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/config/dnsendpoint.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: externaldns.k8s.io/v1alpha1
3 | kind: DNSEndpoint
4 | metadata:
5 | name: cloudflare-tunnel
6 | spec:
7 | endpoints:
8 | - dnsName: "external.#{ cloudflare_domain }#"
9 | recordType: CNAME
10 | targets: ["#{ cloudflare_tunnel_id() }#.cfargotunnel.com"]
11 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/k8s-gateway/values.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | fullnameOverride: k8s-gateway
3 | domain: "#{ cloudflare_domain }#"
4 | ttl: 1
5 | service:
6 | type: LoadBalancer
7 | port: 53
8 | annotations:
9 | io.cilium/lb-ipam-ips: "#{ cluster_dns_gateway_addr }#"
10 | externalTrafficPolicy: Cluster
11 | watchedResources: ["HTTPRoute", "Service"]
12 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/global/machine-files.yaml.j2:
--------------------------------------------------------------------------------
1 | machine:
2 | files:
3 | - op: create
4 | path: /etc/cri/conf.d/20-customization.part
5 | content: |
6 | [plugins."io.containerd.cri.v1.images"]
7 | discard_unpacked_layers = false
8 | [plugins."io.containerd.cri.v1.runtime"]
9 | device_ownership_from_security_context = true
10 |
--------------------------------------------------------------------------------
/templates/config/.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | creation_rules:
3 | - path_regex: talos/.*\.sops\.ya?ml
4 | mac_only_encrypted: true
5 | age: "#{ age_key('public') }#"
6 | - path_regex: (bootstrap|kubernetes|secret)/.*\.sops\.ya?ml
7 | encrypted_regex: "^(data|stringData)$"
8 | mac_only_encrypted: true
9 | age: "#{ age_key('public') }#"
10 | stores:
11 | yaml:
12 | indent: 2
13 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/config/secret-generator.yaml.j2:
--------------------------------------------------------------------------------
1 | apiVersion: viaduct.ai/v1
2 | kind: ksops
3 | metadata:
4 | name: cloudflare-tunnel-secret-generator
5 | annotations:
6 | config.kubernetes.io/function: |
7 | exec:
8 | path: ksops
9 | files:
10 | - ./secret.sops.yaml
11 | - ./dnsendpoint.sops.yaml
12 | - ./config.sops.yaml
13 |
14 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/argo-system/argo-cd/config/http-route.yaml.j2:
--------------------------------------------------------------------------------
1 | apiVersion: gateway.networking.k8s.io/v1
2 | kind: HTTPRoute
3 | metadata:
4 | name: argo
5 | spec:
6 | parentRefs:
7 | - name: envoy-external
8 | namespace: kube-system
9 | hostnames: ["argo.#{ cloudflare_domain }#"]
10 | rules:
11 | - backendRefs:
12 | - name: argocd-server
13 | port: 80
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; https://editorconfig.org/
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.cue]
14 | indent_style = tab
15 | indent_size = 4
16 |
17 | [*.md]
18 | indent_size = 4
19 | trim_trailing_whitespace = false
20 |
21 | [*.sh]
22 | indent_size = 4
23 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/settings/cluster-settings.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: AppProject
4 | metadata:
5 | name: kubernetes
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | destinations:
11 | - name: "*"
12 | namespace: "*"
13 | server: "*"
14 | sourceRepos:
15 | - "*"
16 | clusterResourceWhitelist:
17 | - group: "*"
18 | kind: "*"
19 |
--------------------------------------------------------------------------------
/templates/overrides/readme.partial.yaml.j2:
--------------------------------------------------------------------------------
1 | #| Place user jinja template overrides in this file's directory |#
2 | #| Docs: https://mirkolenz.github.io/makejinja/makejinja.html |#
3 | #| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/makejinja.toml |#
4 | #| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/input1/not-empty.yaml.jinja |#
5 | #| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/input2/not-empty.yaml.jinja |#
6 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Labeler"
3 |
4 | on:
5 | workflow_dispatch:
6 | pull_request_target:
7 | branches: ["main"]
8 |
9 | jobs:
10 | labeler:
11 | name: Labeler
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: write
15 | pull-requests: write
16 | steps:
17 | - name: Labeler
18 | uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
19 | with:
20 | configuration-path: .github/labeler.yaml
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | *.env linguist-detectable linguist-language=SHELL
3 | *.json linguist-detectable linguist-language=JSON
4 | *.json5 linguist-detectable linguist-language=JSON5
5 | *.md linguist-detectable linguist-language=MARKDOWN
6 | *.sh linguist-detectable linguist-language=SHELL
7 | *.toml linguist-detectable linguist-language=TOML
8 | *.yml linguist-detectable linguist-language=YAML
9 | *.yaml linguist-detectable linguist-language=YAML
10 | *.yaml.j2 linguist-detectable linguist-language=YAML
11 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/envoy-gateway/config/certificate.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: cert-manager.io/v1
3 | kind: Certificate
4 | metadata:
5 | name: "#{ cloudflare_domain.replace('.', '-') }#-production"
6 | spec:
7 | secretName: "#{ cloudflare_domain.replace('.', '-') }#-production-tls"
8 | issuerRef:
9 | name: letsencrypt-production
10 | kind: ClusterIssuer
11 | commonName: "#{ cloudflare_domain }#"
12 | dnsNames: ["#{ cloudflare_domain }#", "*.#{ cloudflare_domain }#"]
13 |
14 |
15 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/envoy-gateway/config/podmonitor.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: monitoring.coreos.com/v1
3 | kind: PodMonitor
4 | metadata:
5 | name: envoy-proxy
6 | spec:
7 | jobLabel: envoy-proxy
8 | namespaceSelector:
9 | matchNames:
10 | - network
11 | podMetricsEndpoints:
12 | - port: metrics
13 | path: /stats/prometheus
14 | honorLabels: true
15 | selector:
16 | matchLabels:
17 | app.kubernetes.io/component: proxy
18 | app.kubernetes.io/name: envoy
19 |
--------------------------------------------------------------------------------
/makejinja.toml:
--------------------------------------------------------------------------------
1 | [makejinja]
2 | inputs = ["./templates/overrides","./templates/config"]
3 | output = "./"
4 | exclude_patterns = ["*.partial.yaml.j2"]
5 | data = ["./cluster.yaml", "./nodes.yaml"]
6 | import_paths = ["./templates/scripts"]
7 | loaders = ["plugin:Plugin"]
8 | jinja_suffix = ".j2"
9 | copy_metadata = true
10 | force = true
11 | undefined = "chainable"
12 |
13 | [makejinja.delimiter]
14 | block_start = "#%"
15 | block_end = "%#"
16 | comment_start = "#|"
17 | comment_end = "#|"
18 | variable_start = "#{"
19 | variable_end = "}#"
20 |
--------------------------------------------------------------------------------
/.github/tests/nodes.yaml:
--------------------------------------------------------------------------------
1 | nodes:
2 | - name: k8s-0
3 | address: 10.10.10.100
4 | controller: true
5 | disk: /dev/sdfake
6 | mac_addr: 00:00:00:00:00:00
7 | schematic_id: "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
8 | - name: k8s-1
9 | address: 10.10.10.101
10 | controller: false
11 | disk: /dev/sdfake
12 | mac_addr: 00:00:00:00:00:01
13 | schematic_id: "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
14 | mtu: 1500
15 | secureboot: true
16 | encrypt_disk: true
17 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.bracketPairColorization.enabled": true,
3 | "files.associations": {
4 | "**/*.json5": "json5"
5 | },
6 | "files.trimTrailingWhitespace": true,
7 | "sops.defaults.ageKeyFile": "age.key",
8 | "vs-kubernetes": {
9 | "vs-kubernetes.kubeconfig": "./kubeconfig",
10 | "vs-kubernetes.knownKubeconfigs": [
11 | "./kubeconfig"
12 | ]
13 | },
14 | "yaml.schemaStore.enable": true,
15 | "yaml.schemas": {
16 | "kubernetes": "./kubernetes/**/*.yaml"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/config/config.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: cloudflare-tunnel-configmap
6 | data:
7 | config.yaml: |
8 | ---
9 | originRequest:
10 | originServerName: "external.#{ cloudflare_domain }#"
11 |
12 | ingress:
13 | - hostname: "#{ cloudflare_domain }#"
14 | service: &svc https://envoy-external.kube-system.svc.cluster.local
15 | - hostname: "*.#{ cloudflare_domain }#"
16 | service: *svc
17 | - service: http_status:404
18 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/cert-manager/cert-manager/issuers/clusterissuers.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: cert-manager.io/v1
3 | kind: ClusterIssuer
4 | metadata:
5 | name: letsencrypt-production
6 | spec:
7 | acme:
8 | server: https://acme-v02.api.letsencrypt.org/directory
9 | privateKeySecretRef:
10 | name: letsencrypt-production
11 | solvers:
12 | - dns01:
13 | cloudflare:
14 | apiTokenSecretRef:
15 | name: cert-manager-secret
16 | key: api-token
17 | selector:
18 | dnsZones: ["#{ cloudflare_domain }#"]
19 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/global/machine-sysctls.yaml.j2:
--------------------------------------------------------------------------------
1 | machine:
2 | sysctls:
3 | fs.inotify.max_user_watches: "1048576" # Watchdog
4 | fs.inotify.max_user_instances: "8192" # Watchdog
5 | net.core.rmem_max: "7500000" # Cloudflared | QUIC
6 | net.core.wmem_max: "7500000" # Cloudflared | QUIC
7 | net.ipv4.neigh.default.gc_thresh1: "4096" # Prevent ARP cache overflows
8 | net.ipv4.neigh.default.gc_thresh2: "8192" # Prevent ARP cache overflows
9 | net.ipv4.neigh.default.gc_thresh3: "16384" # Prevent ARP cache overflows
10 | user.max_user_namespaces: "11255" # User Namespaces
11 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/repositories/github.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: github
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '1'
9 | labels:
10 | argocd.argoproj.io/secret-type: repository
11 | stringData:
12 | type: git
13 | #% if repository_visibility == 'private' %#
14 | url: "ssh://git@github.com:#{ repository_name }#.git"
15 | sshPrivateKey: |
16 | #% filter indent(width=4, first=False) %#
17 | #{ github_deploy_key() }#
18 | #%- endfilter %#
19 | #% else %#
20 | url: "https://github.com/#{ repository_name }#.git"
21 | #% endif %#
22 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/controller/cluster.yaml.j2:
--------------------------------------------------------------------------------
1 | cluster:
2 | allowSchedulingOnControlPlanes: true
3 | apiServer:
4 | admissionControl:
5 | $$patch: delete
6 | extraArgs:
7 | # https://kubernetes.io/docs/tasks/extend-kubernetes/configure-aggregation-layer/
8 | enable-aggregator-routing: true
9 | controllerManager:
10 | extraArgs:
11 | bind-address: 0.0.0.0
12 | coreDNS:
13 | disabled: true
14 | etcd:
15 | extraArgs:
16 | listen-metrics-urls: http://0.0.0.0:2381
17 | advertisedSubnets:
18 | - #{ node_cidr }#
19 | proxy:
20 | disabled: true
21 | scheduler:
22 | extraArgs:
23 | bind-address: 0.0.0.0
24 |
--------------------------------------------------------------------------------
/.github/workflows/label-sync.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Label Sync"
3 |
4 | on:
5 | workflow_dispatch:
6 | push:
7 | branches: ["main"]
8 | paths: [".github/labels.yaml"]
9 |
10 | jobs:
11 | label-sync:
12 | name: Label Sync
13 | runs-on: ubuntu-latest
14 | permissions:
15 | contents: write
16 | pull-requests: write
17 | issues: write
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
21 |
22 | - name: Sync Labels
23 | uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
24 | with:
25 | config-file: .github/labels.yaml
26 | delete-other-labels: true
27 |
--------------------------------------------------------------------------------
/.github/tests/private.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | node_cidr: "10.10.10.0/24"
3 | # node_default_gateway: ""
4 | # node_vlan_tag:
5 | # cluster_pod_cidr: ""
6 | # cluster_svc_cidr: ""
7 | # node_dns_servers: []
8 | # node_ntp_servers: []
9 | cluster_api_addr: "10.10.10.254"
10 | # cluster_api_tls_sans: []
11 | cluster_gateway_addr: "10.10.10.252"
12 | cluster_dns_gateway_addr: "10.10.10.253"
13 | repository_name: "ajaykumar4/cluster-template"
14 | # repository_branch: ""
15 | repository_visibility: "private"
16 | cloudflare_domain: "example.com"
17 | cloudflare_token: "fake"
18 | cloudflare_gateway_addr: "10.10.10.251"
19 | # cilium_bgp_router_addr: ""
20 | # cilium_bgp_router_asn: ""
21 | # cilium_bgp_node_asn: ""
22 | # cilium_loadbalancer_mode: ""
23 | argo_password: "argo"
24 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-dns/values.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | fullnameOverride: cloudflare-dns
3 | provider: cloudflare
4 | env:
5 | - name: CF_API_TOKEN
6 | valueFrom:
7 | secretKeyRef:
8 | name: &secret cloudflare-dns-secret
9 | key: api-token
10 | extraArgs:
11 | - --cloudflare-dns-records-per-page=1000
12 | - --cloudflare-proxied
13 | - --crd-source-apiversion=externaldns.k8s.io/v1alpha1
14 | - --crd-source-kind=DNSEndpoint
15 | - --gateway-name=envoy-external
16 | triggerLoopOnEvent: true
17 | policy: sync
18 | sources: ["crd", "gateway-httproute"]
19 | txtPrefix: k8s.
20 | txtOwnerId: default
21 | domainFilters: ["#{ cloudflare_domain }#"]
22 | serviceMonitor:
23 | enabled: true
24 | podAnnotations:
25 | secret.reloader.stakater.com/reload: *secret
26 |
--------------------------------------------------------------------------------
/.taskfiles/template/resources/nodes.schema.cue:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "net"
5 | "list"
6 | )
7 |
8 | #Config: {
9 | nodes: [...#Node]
10 | _nodes_check: {
11 | name: list.UniqueItems() & [for item in nodes {item.name}]
12 | address: list.UniqueItems() & [for item in nodes {item.address}]
13 | mac_addr: list.UniqueItems() & [for item in nodes {item.mac_addr}]
14 | }
15 | }
16 |
17 | #Node: {
18 | name: =~"^[a-z0-9][a-z0-9\\-]{0,61}[a-z0-9]$|^[a-z0-9]$" & !="global" & !="controller" & !="worker"
19 | address: net.IPv4
20 | controller: bool
21 | disk: string
22 | mac_addr: =~"^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$"
23 | schematic_id: =~"^[a-z0-9]{64}$"
24 | mtu?: >=1450 & <=9000
25 | secureboot?: bool
26 | encrypt_disk?: bool
27 | }
28 |
29 | #Config
30 |
--------------------------------------------------------------------------------
/templates/config/talos/patches/README.md.j2:
--------------------------------------------------------------------------------
1 | # Talos Patching
2 |
3 | This directory contains Kustomization patches that are added to the talhelper configuration file.
4 |
5 |
6 |
7 | ## Patch Directories
8 |
9 | Under this `patches` directory, there are several sub-directories that can contain patches that are added to the talhelper configuration file.
10 | Each directory is optional and therefore might not created by default.
11 |
12 | - `global/`: patches that are applied to both the controller and worker configurations
13 | - `controller/`: patches that are applied to the controller configurations
14 | - `worker/`: patches that are applied to the worker configurations
15 | - `${node-hostname}/`: patches that are applied to the node with the specified name
16 |
--------------------------------------------------------------------------------
/.github/tests/public.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | node_cidr: "10.10.10.0/24"
3 | node_default_gateway: "10.10.10.1"
4 | node_vlan_tag: "100"
5 | cluster_pod_cidr: "10.42.0.0/16"
6 | cluster_svc_cidr: "10.43.0.0/16"
7 | node_dns_servers: ["1.1.1.1"]
8 | node_ntp_servers: ["162.159.200.123"]
9 | cluster_api_addr: "10.10.10.254"
10 | cluster_api_tls_sans: ["example.com"]
11 | cluster_gateway_addr: "10.10.10.252"
12 | cluster_dns_gateway_addr: "10.10.10.253"
13 | repository_name: "ajaykumar4/cluster-template"
14 | repository_branch: "main"
15 | repository_visibility: "public"
16 | cloudflare_domain: "example.com"
17 | cloudflare_token: "fake"
18 | cloudflare_gateway_addr: "10.10.10.251"
19 | cilium_loadbalancer_mode: "dsr"
20 | cilium_bgp_router_addr: "10.10.1.1"
21 | cilium_bgp_router_asn: "64513"
22 | cilium_bgp_node_asn: "64514"
23 | argo_password: "argo"
24 |
--------------------------------------------------------------------------------
/templates/config/bootstrap/helmfile.d/00-crds.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # This helmfile is for extracting and installing Custom Resource Definitions (CRDs) from Helm charts.
4 | # It is not intended to be used with helmfile apply or helmfile sync.
5 |
6 | helmDefaults:
7 | args:
8 | - --include-crds
9 | - --no-hooks
10 | postRenderer: bash
11 | postRendererArgs:
12 | - -c
13 | - yq eval-all --exit-status 'select(.kind == "CustomResourceDefinition")'
14 |
15 | releases:
16 | - name: cloudflare-dns
17 | namespace: network
18 | chart: oci://ghcr.io/home-operations/charts-mirror/external-dns
19 | version: 1.19.0
20 |
21 | - name: envoy-gateway
22 | namespace: network
23 | chart: oci://mirror.gcr.io/envoyproxy/gateway-helm
24 | version: v1.6.0
25 |
26 | - name: kube-prometheus-stack
27 | namespace: observability
28 | chart: oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack
29 | version: 79.8.2
30 |
--------------------------------------------------------------------------------
/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3'
3 |
4 | set: [pipefail]
5 | shopt: [globstar]
6 |
7 | vars:
8 | BOOTSTRAP_DIR: '{{.ROOT_DIR}}/bootstrap'
9 | KUBERNETES_DIR: '{{.ROOT_DIR}}/kubernetes'
10 | SCRIPTS_DIR: '{{.ROOT_DIR}}/scripts'
11 | TALOS_DIR: '{{.ROOT_DIR}}/talos'
12 | PRIVATE_DIR: '{{.ROOT_DIR}}/.private'
13 | TALOSCONFIG: '{{.ROOT_DIR}}/talos/clusterconfig/talosconfig'
14 |
15 | env:
16 | KUBECONFIG: '{{.ROOT_DIR}}/kubeconfig'
17 | SOPS_AGE_KEY_FILE: '{{.ROOT_DIR}}/age.key'
18 | TALOSCONFIG: '{{.TALOSCONFIG}}'
19 |
20 | includes:
21 | bootstrap: .taskfiles/bootstrap
22 | talos: .taskfiles/talos
23 | template: .taskfiles/template
24 |
25 | tasks:
26 |
27 | default: task --list
28 |
29 | reconcile:
30 | desc: Force Argo to pull in changes from your Git repository
31 | cmd: argocd app list --project kubernetes -o name | xargs -I {} argocd app wait {} --sync --timeout 300
32 | preconditions:
33 | - test -f {{.KUBECONFIG}}
34 | - which argocd
35 |
--------------------------------------------------------------------------------
/nodes.sample.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | nodes: []
3 | # - name: "" # (REQUIRED) Name of the node (must match [a-z0-9-\]+)
4 | # address: "" # (REQUIRED) IP address of the node (must be in the node_cidr)
5 | # controller: true # (REQUIRED) Set to true if this is a controller node
6 | # disk: "" # (REQUIRED) Device path or serial number of the disk for this node (talosctl get disks -n --insecure)
7 | # mac_addr: "" # (REQUIRED) MAC address of the NIC for this node (talosctl get links -n --insecure)
8 | # schematic_id: "" # (REQUIRED) Schematic ID from https://factory.talos.dev/
9 | # mtu: 1500 # (ADVANCED/OPTIONAL) MTU for the NIC. DEFAULT: 1500
10 | # secureboot: false # (ADVANCED/OPTIONAL) SecureBoot mode on UEFI platforms. Ref: https://www.talos.dev/latest/talos-guides/install/bare-metal-platforms/secureboot
11 | # encrypt_disk: false # (ADVANCED/OPTIONAL) TPM-based disk encryption. Ref: https://www.talos.dev/latest/talos-guides/install/bare-metal-platforms/secureboot
12 | # ...
13 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/components/common/settings.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: projects
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '1'
9 | spec:
10 | project: default
11 | source:
12 | repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/argo/settings
14 | targetRevision: #{ repository_branch }#
15 | destination:
16 | name: in-cluster
17 | namespace: argo-system
18 | syncPolicy:
19 | automated:
20 | allowEmpty: true
21 | prune: true
22 | selfHeal: true
23 | retry:
24 | limit: 1
25 | backoff:
26 | duration: 10s
27 | factor: 2
28 | maxDuration: 3m
29 | syncOptions:
30 | - CreateNamespace=true
31 | - ApplyOutOfSyncOnly=true
32 | - ServerSideApply=true
33 | - PruneLast=true
34 | - RespectIgnoreDifferences=true
35 | ignoreDifferences:
36 | - group: argoproj.io
37 | kind: Application
38 | jsonPointers:
39 | - /spec/syncPolicy
40 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/components/common/repositories.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: repositories
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '1'
9 | spec:
10 | project: default
11 | source:
12 | repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/argo/repositories
14 | targetRevision: #{ repository_branch }#
15 | destination:
16 | name: in-cluster
17 | namespace: argo-system
18 | syncPolicy:
19 | automated:
20 | allowEmpty: true
21 | prune: true
22 | selfHeal: true
23 | retry:
24 | limit: 1
25 | backoff:
26 | duration: 10s
27 | factor: 2
28 | maxDuration: 3m
29 | syncOptions:
30 | - CreateNamespace=true
31 | - ApplyOutOfSyncOnly=true
32 | - ServerSideApply=true
33 | - PruneLast=true
34 | - RespectIgnoreDifferences=true
35 | ignoreDifferences:
36 | - group: argoproj.io
37 | kind: Application
38 | jsonPointers:
39 | - /spec/syncPolicy
40 |
--------------------------------------------------------------------------------
/.github/labels.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Areas
3 | - name: area/bootstrap
4 | color: "0e8a16"
5 | - name: area/docs
6 | color: "0e8a16"
7 | - name: area/github
8 | color: "0e8a16"
9 | - name: area/kubernetes
10 | color: "0e8a16"
11 | - name: area/mise
12 | color: "0e8a16"
13 | - name: area/renovate
14 | color: "0e8a16"
15 | - name: area/scripts
16 | color: "0e8a16"
17 | - name: area/talos
18 | color: "0e8a16"
19 | - name: area/templates
20 | color: "0e8a16"
21 | - name: area/taskfile
22 | color: "0e8a16"
23 | # Renovate Types
24 | - name: renovate/container
25 | color: "027fa0"
26 | - name: renovate/github-action
27 | color: "027fa0"
28 | - name: renovate/grafana-dashboard
29 | color: "027fa0"
30 | - name: renovate/github-release
31 | color: "027fa0"
32 | - name: renovate/helm
33 | color: "027fa0"
34 | # Semantic Types
35 | - name: type/digest
36 | color: "ffeC19"
37 | - name: type/patch
38 | color: "ffeC19"
39 | - name: type/minor
40 | color: "ff9800"
41 | - name: type/major
42 | color: "f6412d"
43 | # Uncategorized
44 | - name: community
45 | color: "370fb2"
46 | - name: hold
47 | color: "ee0701"
48 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/components/common/apps.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: apps
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '1'
9 | spec:
10 | project: default
11 | source:
12 | repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/argo/apps
14 | targetRevision: #{ repository_branch }#
15 | directory:
16 | recurse: true
17 | destination:
18 | name: in-cluster
19 | namespace: argo-system
20 | syncPolicy:
21 | automated:
22 | allowEmpty: true
23 | prune: true
24 | selfHeal: true
25 | retry:
26 | limit: 1
27 | backoff:
28 | duration: 10s
29 | factor: 2
30 | maxDuration: 3m
31 | syncOptions:
32 | - CreateNamespace=true
33 | - ApplyOutOfSyncOnly=true
34 | - ServerSideApply=true
35 | - PruneLast=true
36 | - RespectIgnoreDifferences=true
37 | ignoreDifferences:
38 | - group: argoproj.io
39 | kind: Application
40 | jsonPointers:
41 | - /spec/syncPolicy
42 |
--------------------------------------------------------------------------------
/.github/labeler.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | area/bootstrap:
3 | - changed-files:
4 | - any-glob-to-any-file:
5 | - bootstrap/**/*
6 | area/docs:
7 | - changed-files:
8 | - any-glob-to-any-file:
9 | - README.md
10 | area/github:
11 | - changed-files:
12 | - any-glob-to-any-file:
13 | - .github/**/*
14 | area/kubernetes:
15 | - changed-files:
16 | - any-glob-to-any-file:
17 | - kubernetes/**/*
18 | area/mise:
19 | - changed-files:
20 | - any-glob-to-any-file:
21 | - .mise.toml
22 | area/renovate:
23 | - changed-files:
24 | - any-glob-to-any-file:
25 | - .renovate/**/*
26 | - .renovaterc.json5
27 | area/scripts:
28 | - changed-files:
29 | - any-glob-to-any-file:
30 | - scripts/**/*
31 | area/talos:
32 | - changed-files:
33 | - any-glob-to-any-file:
34 | - talos/**/*
35 | area/taskfile:
36 | - changed-files:
37 | - any-glob-to-any-file:
38 | - .taskfiles/**/*
39 | - Taskfile.yaml
40 | area/templates:
41 | - changed-files:
42 | - any-glob-to-any-file:
43 | - templates/**/*
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 ajaykumar4
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/network/envoy-gateway.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: envoy-gateway
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/network/envoy-gateway
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: mirror.gcr.io/envoyproxy
17 | chart: gateway-helm
18 | targetRevision: v1.6.0
19 | helm:
20 | releaseName: envoy-gateway
21 | destination:
22 | name: in-cluster
23 | namespace: network
24 | syncPolicy:
25 | automated:
26 | allowEmpty: true
27 | prune: true
28 | selfHeal: true
29 | retry:
30 | limit: 1
31 | backoff:
32 | duration: 10s
33 | factor: 2
34 | maxDuration: 3m
35 | syncOptions:
36 | - CreateNamespace=true
37 | - ApplyOutOfSyncOnly=true
38 | - ServerSideApply=true
39 | - PruneLast=true
40 | - RespectIgnoreDifferences=true
41 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/default/echo.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: echo
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/default/echo
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: ghcr.io/bjw-s-labs/helm
17 | chart: app-template
18 | targetRevision: 4.4.0
19 | helm:
20 | releaseName: echo
21 | valueFiles:
22 | - $repo/kubernetes/apps/default/echo/values.sops.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: default
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/kube-system/coredns.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: coredns
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/kube-system/coredns
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: ghcr.io/coredns/charts
17 | chart: coredns
18 | targetRevision: 1.45.0
19 | helm:
20 | releaseName: coredns
21 | valueFiles:
22 | - $repo/kubernetes/apps/kube-system/coredns/values.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: kube-system
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/kube-system/reloader.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: reloader
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/kube-system/reloader
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: ghcr.io/stakater/charts
17 | chart: reloader
18 | targetRevision: 2.2.5
19 | helm:
20 | releaseName: reloader
21 | valueFiles:
22 | - $repo/kubernetes/apps/kube-system/reloader/values.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: kube-system
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/network/k8s-gateway.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: k8s-gateway
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/network/k8s-gateway
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: https://ori-edge.github.io/k8s_gateway
17 | chart: k8s-gateway
18 | targetRevision: 2.4.0
19 | helm:
20 | releaseName: k8s-gateway
21 | valueFiles:
22 | - $repo/kubernetes/apps/network/k8s-gateway/values.sops.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: network
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/network/cloudflare-tunnel.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: cloudflare-tunnel
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/network/cloudflare-tunnel
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: ghcr.io/bjw-s/helm
17 | chart: app-template
18 | targetRevision: 3.7.3
19 | helm:
20 | releaseName: cloudflare-tunnel
21 | valueFiles:
22 | - $repo/kubernetes/apps/network/cloudflare-tunnel/values.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: network
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/cert-manager/cert-manager.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: cert-manager
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/cert-manager/cert-manager
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: https://charts.jetstack.io
17 | chart: cert-manager
18 | targetRevision: v1.19.1
19 | helm:
20 | releaseName: cert-manager
21 | valueFiles:
22 | - $repo/kubernetes/apps/cert-manager/cert-manager/values.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: cert-manager
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/kube-system/spegel.yaml.j2:
--------------------------------------------------------------------------------
1 | #% if spegel_enabled %#
2 | ---
3 | apiVersion: argoproj.io/v1alpha1
4 | kind: Application
5 | metadata:
6 | name: spegel
7 | namespace: argo-system
8 | annotations:
9 | argocd.argoproj.io/sync-wave: '0'
10 | spec:
11 | project: kubernetes
12 | sources:
13 | - repoURL: "https://github.com/#{ repository_name }#.git"
14 | path: kubernetes/apps/kube-system/spegel
15 | targetRevision: #{ repository_branch }#
16 | ref: repo
17 | - repoURL: ghcr.io/spegel-org/helm-charts
18 | chart: spegel
19 | targetRevision: 0.5.1
20 | helm:
21 | releaseName: spegel
22 | valueFiles:
23 | - $repo/kubernetes/apps/kube-system/spegel/values.yaml
24 | destination:
25 | name: in-cluster
26 | namespace: kube-system
27 | syncPolicy:
28 | automated:
29 | allowEmpty: true
30 | prune: true
31 | selfHeal: true
32 | retry:
33 | limit: 1
34 | backoff:
35 | duration: 10s
36 | factor: 2
37 | maxDuration: 3m
38 | syncOptions:
39 | - CreateNamespace=true
40 | - ApplyOutOfSyncOnly=true
41 | - ServerSideApply=true
42 | - PruneLast=true
43 | - RespectIgnoreDifferences=true
44 | #% endif %#
45 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/network/cloudflare-dns.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: cloudflare-dns
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/network/cloudflare-dns
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: https://kubernetes-sigs.github.io/external-dns
17 | chart: external-dns
18 | targetRevision: 1.19.0
19 | helm:
20 | releaseName: cloudflare-dns
21 | valueFiles:
22 | - $repo/kubernetes/apps/network/cloudflare-dns/values.sops.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: network
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/kube-system/metrics-server.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: metrics-server
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/kube-system/metrics-server
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: https://kubernetes-sigs.github.io/metrics-server
17 | chart: metrics-server
18 | targetRevision: 3.13.0
19 | helm:
20 | releaseName: metrics-server
21 | valueFiles:
22 | - $repo/kubernetes/apps/kube-system/metrics-server/values.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: kube-system
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 |
--------------------------------------------------------------------------------
/.taskfiles/bootstrap/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3'
3 |
4 | tasks:
5 |
6 | talos:
7 | desc: Bootstrap the Talos cluster
8 | dir: '{{.TALOS_DIR}}'
9 | cmds:
10 | - '[ -f talsecret.sops.yaml ] || talhelper gensecret | sops --filename-override talos/talsecret.sops.yaml --encrypt /dev/stdin > talsecret.sops.yaml'
11 | - talhelper genconfig
12 | - talhelper gencommand apply --extra-flags="--insecure" | bash
13 | - until talhelper gencommand bootstrap | bash; do sleep 10; done
14 | - until talhelper gencommand kubeconfig --extra-flags="{{.ROOT_DIR}} --force" | bash; do sleep 10; done
15 | preconditions:
16 | - test -f {{.ROOT_DIR}}/.sops.yaml
17 | - test -f {{.SOPS_AGE_KEY_FILE}}
18 | - test -f {{.TALOS_DIR}}/talconfig.yaml
19 | - which talhelper talosctl sops
20 |
21 | apps:
22 | desc: Bootstrap apps into the Talos cluster
23 | cmd: bash {{.SCRIPTS_DIR}}/bootstrap-apps.sh
24 | preconditions:
25 | - msg: Unsupported bash version, run `brew install bash` to upgrade
26 | sh: '{{if eq OS "darwin"}}test -f /opt/homebrew/bin/bash || test -f /usr/local/bin/bash{{end}}'
27 | - test -f {{.KUBECONFIG}}
28 | - test -f {{.ROOT_DIR}}/.sops.yaml
29 | - test -f {{.SCRIPTS_DIR}}/bootstrap-apps.sh
30 | - test -f {{.SOPS_AGE_KEY_FILE}}
31 |
--------------------------------------------------------------------------------
/.taskfiles/template/resources/cluster.schema.cue:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "net"
5 | )
6 |
7 | #Config: {
8 | node_cidr: net.IPCIDR & !=cluster_pod_cidr & !=cluster_svc_cidr
9 | node_dns_servers?: [...net.IPv4]
10 | node_ntp_servers?: [...net.IPv4]
11 | node_default_gateway?: net.IPv4 & !=""
12 | node_vlan_tag?: string & !=""
13 | cluster_pod_cidr: *"10.42.0.0/16" | net.IPCIDR & !=node_cidr & !=cluster_svc_cidr
14 | cluster_svc_cidr: *"10.43.0.0/16" | net.IPCIDR & !=node_cidr & !=cluster_pod_cidr
15 | cluster_api_addr: net.IPv4
16 | cluster_api_tls_sans?: [...net.FQDN]
17 | cluster_gateway_addr: net.IPv4 & !=cluster_api_addr & !=cluster_dns_gateway_addr & !=cloudflare_gateway_addr
18 | cluster_dns_gateway_addr: net.IPv4 & !=cluster_api_addr & !=cluster_gateway_addr & !=cloudflare_gateway_addr
19 | repository_name: string
20 | repository_branch?: string & !=""
21 | repository_visibility?: *"public" | "private"
22 | cloudflare_domain: net.FQDN
23 | cloudflare_token: string
24 | cloudflare_gateway_addr: net.IPv4 & !=cluster_api_addr & !=cluster_gateway_addr & !=cluster_dns_gateway_addr
25 | cilium_bgp_router_addr?: net.IPv4 & !=""
26 | cilium_bgp_router_asn?: string & !=""
27 | cilium_bgp_node_asn?: string & !=""
28 | cilium_loadbalancer_mode?: *"dsr" | "snat"
29 | argo_password: string
30 | }
31 |
32 | #Config
33 |
--------------------------------------------------------------------------------
/.mise.toml:
--------------------------------------------------------------------------------
1 | [env]
2 | _.python.venv = { path = "{{config_root}}/.venv", create = true }
3 | KUBECONFIG = "{{config_root}}/kubeconfig"
4 | SOPS_AGE_KEY_FILE = "{{config_root}}/age.key"
5 | TALOSCONFIG = "{{config_root}}/talos/clusterconfig/talosconfig"
6 |
7 | [tasks.deps]
8 | description = "Install Helm dependencies"
9 | run = [
10 | "helm plugin list | grep -q 'secrets' && helm plugin update secrets || helm plugin install https://github.com/jkroepke/helm-secrets",
11 | "helm plugin list | grep -q 'diff' && helm plugin update diff || helm plugin install https://github.com/databus23/helm-diff"
12 | ]
13 |
14 | [tools]
15 | "python" = "3.14.2"
16 | "pipx:makejinja" = "2.8.2"
17 | "aqua:argoproj/argo-cd" = "3.2.2"
18 | "aqua:budimanjojo/talhelper" = "3.0.43"
19 | "aqua:cilium/cilium-cli" = "0.18.9"
20 | "aqua:cli/cli" = "2.83.2"
21 | "aqua:cloudflare/cloudflared" = "2025.11.1"
22 | "aqua:cue-lang/cue" = "0.15.1"
23 | "aqua:FiloSottile/age" = "1.2.1"
24 | "aqua:getsops/sops" = "3.11.0"
25 | "aqua:go-task/task" = "3.46.3"
26 | "aqua:helm/helm" = "4.0.4"
27 | "aqua:helmfile/helmfile" = "1.2.3"
28 | "aqua:jqlang/jq" = "1.7.1"
29 | "aqua:kubernetes-sigs/kustomize" = "5.6.0"
30 | "aqua:kubernetes/kubectl" = "1.33.1"
31 | "aqua:mikefarah/yq" = "4.50.1"
32 | "aqua:siderolabs/talos" = "1.11.6"
33 | "aqua:yannh/kubeconform" = "0.7.0"
34 | "asdf:janpieper/asdf-ksops" = "4.3.3"
35 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/argo-system/argo-cd.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: argo
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | labels:
10 | argocd.argoproj.io/instance: argo
11 | spec:
12 | project: kubernetes
13 | sources:
14 | - repoURL: "https://github.com/#{ repository_name }#.git"
15 | path: kubernetes/apps/argo-system/argo-cd
16 | targetRevision: #{ repository_branch }#
17 | ref: repo
18 | - repoURL: ghcr.io/argoproj/argo-helm
19 | chart: argo-cd
20 | targetRevision: 9.1.3
21 | helm:
22 | releaseName: argo-cd
23 | valueFiles:
24 | - $repo/kubernetes/apps/argo-system/argo-cd/values.yaml
25 | - $repo/kubernetes/apps/argo-system/argo-cd/values.sops.yaml
26 | destination:
27 | name: in-cluster
28 | namespace: argo-system
29 | syncPolicy:
30 | automated:
31 | allowEmpty: true
32 | prune: true
33 | selfHeal: true
34 | retry:
35 | limit: 1
36 | backoff:
37 | duration: 10s
38 | factor: 2
39 | maxDuration: 3m
40 | syncOptions:
41 | - CreateNamespace=true
42 | - ApplyOutOfSyncOnly=true
43 | - ServerSideApply=true
44 | - PruneLast=true
45 | - RespectIgnoreDifferences=true
46 |
--------------------------------------------------------------------------------
/templates/config/bootstrap/helmfile.d/01-apps.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | helmDefaults:
4 | cleanupOnFail: true
5 | wait: true
6 | waitForJobs: true
7 |
8 | releases:
9 | - name: cilium
10 | namespace: kube-system
11 | chart: oci://ghcr.io/home-operations/charts-mirror/cilium
12 | version: 1.18.4
13 | values: ['../../kubernetes/apps/kube-system/cilium/values.yaml']
14 |
15 | - name: coredns
16 | namespace: kube-system
17 | chart: oci://ghcr.io/coredns/charts/coredns
18 | version: 1.45.0
19 | values: ['../../kubernetes/apps/kube-system/coredns/values.yaml']
20 |
21 | #% if spegel_enabled %#
22 | - name: spegel
23 | namespace: kube-system
24 | chart: oci://ghcr.io/spegel-org/helm-charts/spegel
25 | version: 0.5.1
26 | values: ['../../kubernetes/apps/kube-system/spegel/values.yaml']
27 | needs: ['kube-system/coredns']
28 | #% endif %#
29 |
30 | - name: cert-manager
31 | namespace: cert-manager
32 | chart: oci://quay.io/jetstack/charts/cert-manager
33 | version: v1.19.1
34 | values: ['../../kubernetes/apps/cert-manager/cert-manager/values.yaml']
35 | #% if spegel_enabled %#
36 | needs: ['kube-system/spegel']
37 | #% else %#
38 | needs: ['kube-system/coredns']
39 | #% endif %#
40 |
41 | - name: argo-cd
42 | namespace: argo-system
43 | chart: oci://ghcr.io/argoproj/argo-helm/argo-cd
44 | version: 9.1.4
45 | values: ['../../kubernetes/apps/argo-system/argo-cd/values.yaml']
46 | secrets: ['../../kubernetes/apps/argo-system/argo-cd/values.sops.yaml']
47 |
48 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/coredns/values.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | fullnameOverride: coredns
3 | image:
4 | repository: mirror.gcr.io/coredns/coredns
5 | k8sAppLabelOverride: kube-dns
6 | serviceAccount:
7 | create: true
8 | service:
9 | name: kube-dns
10 | clusterIP: "#{ cluster_svc_cidr | nthhost(10) }#"
11 | replicaCount: 2
12 | servers:
13 | - zones:
14 | - zone: .
15 | scheme: dns://
16 | use_tcp: true
17 | port: 53
18 | plugins:
19 | - name: errors
20 | - name: health
21 | configBlock: |-
22 | lameduck 5s
23 | - name: ready
24 | - name: kubernetes
25 | parameters: cluster.local in-addr.arpa ip6.arpa
26 | configBlock: |-
27 | pods verified
28 | fallthrough in-addr.arpa ip6.arpa
29 | - name: autopath
30 | parameters: "@kubernetes"
31 | - name: forward
32 | parameters: . /etc/resolv.conf
33 | - name: cache
34 | configBlock: |-
35 | prefetch 20
36 | serve_stale
37 | - name: loop
38 | - name: reload
39 | - name: loadbalance
40 | - name: prometheus
41 | parameters: 0.0.0.0:9153
42 | - name: log
43 | configBlock: |-
44 | class error
45 | affinity:
46 | nodeAffinity:
47 | requiredDuringSchedulingIgnoredDuringExecution:
48 | nodeSelectorTerms:
49 | - matchExpressions:
50 | - key: node-role.kubernetes.io/control-plane
51 | operator: Exists
52 | tolerations:
53 | - key: CriticalAddonsOnly
54 | operator: Exists
55 | - key: node-role.kubernetes.io/control-plane
56 | operator: Exists
57 | effect: NoSchedule
58 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/default/echo/values.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | controllers:
2 | echo:
3 | strategy: RollingUpdate
4 | containers:
5 | app:
6 | image:
7 | repository: ghcr.io/mendhak/http-https-echo
8 | tag: 37
9 | env:
10 | HTTP_PORT: &port 8080
11 | LOG_WITHOUT_NEWLINE: true
12 | LOG_IGNORE_PATH: /healthz
13 | PROMETHEUS_ENABLED: true
14 | probes:
15 | liveness: &probes
16 | enabled: true
17 | custom: true
18 | spec:
19 | httpGet:
20 | path: /healthz
21 | port: *port
22 | initialDelaySeconds: 0
23 | periodSeconds: 10
24 | timeoutSeconds: 1
25 | failureThreshold: 3
26 | readiness: *probes
27 | securityContext:
28 | allowPrivilegeEscalation: false
29 | readOnlyRootFilesystem: true
30 | capabilities: { drop: ["ALL"] }
31 | resources:
32 | requests:
33 | cpu: 10m
34 | limits:
35 | memory: 64Mi
36 | defaultPodOptions:
37 | securityContext:
38 | runAsNonRoot: true
39 | runAsUser: 65534
40 | runAsGroup: 65534
41 | service:
42 | app:
43 | controller: echo
44 | ports:
45 | http:
46 | port: *port
47 | serviceMonitor:
48 | app:
49 | serviceName: echo
50 | endpoints:
51 | - port: http
52 | route:
53 | app:
54 | hostnames: ["echo.#{ cloudflare_domain }#"]
55 | parentRefs:
56 | - name: envoy-external
57 | namespace: kube-system
58 | sectionName: https
59 | rules:
60 | - backendRefs:
61 | - identifier: app
62 | port: *port
63 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/argo/apps/kube-system/cilium.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: argoproj.io/v1alpha1
3 | kind: Application
4 | metadata:
5 | name: cilium
6 | namespace: argo-system
7 | annotations:
8 | argocd.argoproj.io/sync-wave: '0'
9 | spec:
10 | project: kubernetes
11 | sources:
12 | - repoURL: "https://github.com/#{ repository_name }#.git"
13 | path: kubernetes/apps/kube-system/cilium
14 | targetRevision: #{ repository_branch }#
15 | ref: repo
16 | - repoURL: https://helm.cilium.io
17 | chart: cilium
18 | targetRevision: 1.18.4
19 | helm:
20 | releaseName: cilium
21 | valueFiles:
22 | - $repo/kubernetes/apps/kube-system/cilium/values.yaml
23 | destination:
24 | name: in-cluster
25 | namespace: kube-system
26 | syncPolicy:
27 | automated:
28 | allowEmpty: true
29 | prune: true
30 | selfHeal: true
31 | retry:
32 | limit: 1
33 | backoff:
34 | duration: 10s
35 | factor: 2
36 | maxDuration: 3m
37 | syncOptions:
38 | - CreateNamespace=true
39 | - ApplyOutOfSyncOnly=true
40 | - ServerSideApply=true
41 | - PruneLast=true
42 | - RespectIgnoreDifferences=true
43 | ignoreDifferences:
44 | - group: ""
45 | kind: ConfigMap
46 | name: hubble-ca-cert
47 | jsonPointers:
48 | - /data/ca.crt
49 | - group: ""
50 | kind: Secret
51 | name: hubble-relay-client-certs
52 | jsonPointers:
53 | - /data/ca.crt
54 | - /data/tls.crt
55 | - /data/tls.key
56 | - group: ""
57 | kind: Secret
58 | name: hubble-server-certs
59 | jsonPointers:
60 | - /data/ca.crt
61 | - /data/tls.crt
62 | - /data/tls.key
63 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/cilium/config/networks.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: cilium.io/v2alpha1
3 | kind: CiliumLoadBalancerIPPool
4 | metadata:
5 | name: pool
6 | spec:
7 | allowFirstLastIPs: "No"
8 | blocks:
9 | - cidr: "#{ node_cidr }#"
10 | ---
11 | apiVersion: cilium.io/v2alpha1
12 | kind: CiliumL2AnnouncementPolicy
13 | metadata:
14 | name: l2-policy
15 | spec:
16 | loadBalancerIPs: true
17 | # NOTE: interfaces might need to be set if you have more than one active NIC on your hosts
18 | # interfaces:
19 | # - ^eno[0-9]+
20 | # - ^eth[0-9]+
21 | nodeSelector:
22 | matchLabels:
23 | kubernetes.io/os: linux
24 | #% if cilium_bgp_enabled %#
25 | ---
26 | apiVersion: cilium.io/v2alpha1
27 | kind: CiliumBGPAdvertisement
28 | metadata:
29 | name: bgp-advertisement-config
30 | labels:
31 | advertise: bgp
32 | spec:
33 | advertisements:
34 | - advertisementType: Service
35 | service:
36 | addresses:
37 | - LoadBalancerIP
38 | selector:
39 | matchExpressions:
40 | - { key: somekey, operator: NotIn, values: ["never-used-value"] }
41 | ---
42 | apiVersion: cilium.io/v2alpha1
43 | kind: CiliumBGPPeerConfig
44 | metadata:
45 | name: bgp-peer-config-v4
46 | spec:
47 | families:
48 | - afi: ipv4
49 | safi: unicast
50 | advertisements:
51 | matchLabels:
52 | advertise: bgp
53 | ---
54 | apiVersion: cilium.io/v2alpha1
55 | kind: CiliumBGPClusterConfig
56 | metadata:
57 | name: bgp-cluster-config
58 | spec:
59 | nodeSelector:
60 | matchLabels:
61 | kubernetes.io/os: linux
62 | bgpInstances:
63 | - name: instance-#{ cilium_bgp_node_asn }#
64 | localASN: #{ cilium_bgp_node_asn }#
65 | peers:
66 | - name: peer-#{ cilium_bgp_router_asn }#-v4
67 | peerASN: #{ cilium_bgp_router_asn }#
68 | peerAddress: #{ cilium_bgp_router_addr }#
69 | peerConfigRef:
70 | name: bgp-peer-config-v4
71 | #% endif %#
72 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/cloudflare-tunnel/values.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | controllers:
3 | cloudflare-tunnel:
4 | strategy: RollingUpdate
5 | annotations:
6 | reloader.stakater.com/auto: "true"
7 | containers:
8 | app:
9 | image:
10 | repository: docker.io/cloudflare/cloudflared
11 | tag: 2025.11.1
12 | env:
13 | NO_AUTOUPDATE: true
14 | TUNNEL_METRICS: 0.0.0.0:8080
15 | TUNNEL_POST_QUANTUM: true # disable when using http2
16 | TUNNEL_TRANSPORT_PROTOCOL: quic # or http2
17 | envFrom:
18 | - secretRef:
19 | name: cloudflare-tunnel-secret
20 | args: ["tunnel", "run"]
21 | probes:
22 | liveness: &probes
23 | enabled: true
24 | custom: true
25 | spec:
26 | httpGet:
27 | path: /ready
28 | port: &port 8080
29 | initialDelaySeconds: 0
30 | periodSeconds: 10
31 | timeoutSeconds: 1
32 | failureThreshold: 3
33 | readiness: *probes
34 | securityContext:
35 | allowPrivilegeEscalation: false
36 | readOnlyRootFilesystem: true
37 | capabilities: { drop: ["ALL"] }
38 | resources:
39 | requests:
40 | cpu: 10m
41 | limits:
42 | memory: 256Mi
43 | defaultPodOptions:
44 | securityContext:
45 | runAsNonRoot: true
46 | runAsUser: 65534
47 | runAsGroup: 65534
48 | service:
49 | app:
50 | controller: cloudflare-tunnel
51 | ports:
52 | http:
53 | port: *port
54 | serviceMonitor:
55 | app:
56 | serviceName: cloudflare-tunnel
57 | endpoints:
58 | - port: http
59 | persistence:
60 | config-file:
61 | type: configMap
62 | name: cloudflare-tunnel-configmap
63 | globalMounts:
64 | - path: /etc/cloudflared/config.yaml
65 | subPath: config.yaml
66 | readOnly: true
67 |
--------------------------------------------------------------------------------
/.taskfiles/template/resources/kubeconform.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | KUBERNETES_DIR=$1
6 |
7 | [[ -z "${KUBERNETES_DIR}" ]] && echo "Kubernetes location not specified" && exit 1
8 |
9 | kustomize_args=(
10 | "--load-restrictor=LoadRestrictionsNone"
11 | "--enable-helm"
12 | "--enable-alpha-plugins"
13 | "--enable-exec"
14 | )
15 | kustomize_config="kustomization.yaml"
16 | kubeconform_args=(
17 | "-strict"
18 | "-ignore-missing-schemas"
19 | "-skip"
20 | "Gateway,HTTPRoute,Secret"
21 | "-schema-location"
22 | "default"
23 | "-schema-location"
24 | "https://kubernetes-schemas.pages.dev/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json"
25 | "-verbose"
26 | )
27 |
28 | echo "=== Validating standalone manifests in ${KUBERNETES_DIR}/argo ==="
29 | find "${KUBERNETES_DIR}/argo" -maxdepth 1 -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file;
30 | do
31 | kubeconform "${kubeconform_args[@]}" "${file}"
32 | if [[ ${PIPESTATUS[0]} != 0 ]]; then
33 | exit 1
34 | fi
35 | done
36 |
37 | echo "=== Validating kustomizations in ${KUBERNETES_DIR}/argo ==="
38 | find "${KUBERNETES_DIR}/argo" -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file;
39 | do
40 | echo "=== Validating kustomizations in ${file/%$kustomize_config} ==="
41 | kustomize build "${file/%$kustomize_config}" "${kustomize_args[@]}" | kubeconform "${kubeconform_args[@]}"
42 | if [[ ${PIPESTATUS[0]} != 0 ]]; then
43 | exit 1
44 | fi
45 | done
46 |
47 | echo "=== Validating kustomizations in ${KUBERNETES_DIR}/apps ==="
48 | find "${KUBERNETES_DIR}/apps" -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file;
49 | do
50 | echo "=== Validating kustomizations in ${file/%$kustomize_config} ==="
51 | kustomize build "${file/%$kustomize_config}" "${kustomize_args[@]}" | kubeconform "${kubeconform_args[@]}"
52 | if [[ ${PIPESTATUS[0]} != 0 ]]; then
53 | exit 1
54 | fi
55 | done
56 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/kube-system/cilium/values.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | autoDirectNodeRoutes: true
3 | bpf:
4 | masquerade: true
5 | # Ref: https://github.com/siderolabs/talos/issues/10002
6 | hostLegacyRouting: true
7 | #% if cilium_bgp_enabled %#
8 | bgpControlPlane:
9 | enabled: true
10 | #% endif %#
11 | cni:
12 | # Required for pairing with Multus CNI
13 | exclusive: false
14 | cgroup:
15 | automount:
16 | enabled: false
17 | hostRoot: /sys/fs/cgroup
18 | # NOTE: devices might need to be set if you have more than one active NIC on your hosts
19 | # devices: eno+ eth+
20 | dashboards:
21 | enabled: true
22 | endpointRoutes:
23 | enabled: true
24 | envoy:
25 | rollOutPods: true
26 | prometheus:
27 | serviceMonitor:
28 | enabled: true
29 | gatewayAPI:
30 | enabled: true
31 | hubble:
32 | enabled: false
33 | ipam:
34 | mode: kubernetes
35 | ipv4NativeRoutingCIDR: "#{ cluster_pod_cidr }#"
36 | k8sServiceHost: 127.0.0.1
37 | k8sServicePort: 7445
38 | kubeProxyReplacement: true
39 | kubeProxyReplacementHealthzBindAddr: 0.0.0.0:10256
40 | l2announcements:
41 | enabled: true
42 | loadBalancer:
43 | algorithm: maglev
44 | mode: "#{ cilium_loadbalancer_mode }#"
45 | localRedirectPolicy: true
46 | operator:
47 | dashboards:
48 | enabled: true
49 | prometheus:
50 | enabled: true
51 | serviceMonitor:
52 | enabled: true
53 | replicas: 1
54 | rollOutPods: true
55 | prometheus:
56 | enabled: true
57 | serviceMonitor:
58 | enabled: true
59 | trustCRDsExist: true
60 | rollOutCiliumPods: true
61 | routingMode: native
62 | securityContext:
63 | capabilities:
64 | ciliumAgent:
65 | - CHOWN
66 | - KILL
67 | - NET_ADMIN
68 | - NET_RAW
69 | - IPC_LOCK
70 | - SYS_ADMIN
71 | - SYS_RESOURCE
72 | - PERFMON
73 | - BPF
74 | - DAC_OVERRIDE
75 | - FOWNER
76 | - SETGID
77 | - SETUID
78 | cleanCiliumState:
79 | - NET_ADMIN
80 | - SYS_ADMIN
81 | - SYS_RESOURCE
82 | socketLB:
83 | hostNamespaceOnly: true
84 |
--------------------------------------------------------------------------------
/.github/workflows/e2e.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "e2e"
3 |
4 | on:
5 | workflow_dispatch:
6 | pull_request:
7 | branches: ["main"]
8 | paths-ignore:
9 | - kubernetes/**
10 |
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
13 | cancel-in-progress: true
14 |
15 | jobs:
16 | configure:
17 | if: ${{ github.repository == 'ajaykumar4/cluster-template' }}
18 | name: configure
19 | runs-on: ubuntu-latest
20 | permissions:
21 | contents: write
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | config-files:
26 | - public
27 | - private
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
31 |
32 | - name: Setup mise
33 | uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3.5.1
34 | env:
35 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
36 | with:
37 | cache: false
38 |
39 | - name: Run init task
40 | run: task init
41 |
42 | - name: Prepare files
43 | run: |
44 | cp ./.github/tests/${{ matrix.config-files }}.yaml cluster.yaml
45 | cp ./.github/tests/nodes.yaml nodes.yaml
46 | echo '{"AccountTag":"fake","TunnelSecret":"fake","TunnelID":"fake"}' > cloudflare-tunnel.json
47 | touch kubeconfig
48 |
49 | - name: Run configure task
50 | run: task configure --yes
51 |
52 | - name: Run generate talconfig task
53 | run: |
54 | FILENAME=talos/talsecret.sops.yaml
55 | talhelper gensecret | sops --filename-override $FILENAME --encrypt /dev/stdin > $FILENAME
56 | task talos:generate-config
57 |
58 | - name: Dry run bootstrap talos task
59 | run: task bootstrap:talos --dry
60 |
61 | - name: Dry run bootstrap apps task
62 | run: task bootstrap:apps --dry
63 |
64 | - name: Run reset task
65 | run: task template:reset --yes
66 |
67 | - name: Run cleanup task
68 | run: task template:tidy --yes
69 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Release"
3 |
4 | on:
5 | workflow_dispatch:
6 | schedule:
7 | - cron: "0 0 1 * *" # 1st of every month at midnight
8 |
9 | jobs:
10 | release:
11 | name: Release
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: write
15 | steps:
16 | - name: Get Previous Release Tag and Determine Next Tag
17 | id: determine-next-tag
18 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
19 | with:
20 | github-token: "${{ secrets.GITHUB_TOKEN }}"
21 | result-encoding: string
22 | script: |
23 | const { data: releases } = await github.rest.repos.listReleases({
24 | owner: context.repo.owner,
25 | repo: context.repo.repo,
26 | per_page: 1,
27 | });
28 |
29 | let previousTag = "0.0.0"; // Default if no previous release exists
30 | if (releases.length > 0) {
31 | previousTag = releases[0].tag_name;
32 | }
33 |
34 | const [previousMajor, previousMinor, previousPatch] = previousTag.split('.').map(Number);
35 | const currentYear = new Date().getFullYear();
36 | const currentMonth = new Date().getMonth() + 1; // Months are 0-indexed in JavaScript
37 |
38 | const nextMajorMinor = `${currentYear}.${currentMonth}`;
39 | let nextPatch;
40 |
41 | if (`${previousMajor}.${previousMinor}` === nextMajorMinor) {
42 | console.log("Month release already exists for the year. Incrementing patch number by 1.");
43 | nextPatch = previousPatch + 1;
44 | } else {
45 | console.log("Month release does not exist for the year. Starting with patch number 0.");
46 | nextPatch = 0;
47 | }
48 |
49 | return `${nextMajorMinor}.${nextPatch}`;
50 |
51 | - name: Create Release
52 | uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
53 | with:
54 | generateReleaseNotes: true
55 | tag: "${{ steps.determine-next-tag.outputs.result }}"
56 | token: "${{ secrets.GITHUB_TOKEN }}"
57 |
--------------------------------------------------------------------------------
/.taskfiles/talos/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3'
3 |
4 | tasks:
5 |
6 | generate-config:
7 | desc: Generate Talos configuration
8 | dir: '{{.TALOS_DIR}}'
9 | cmd: talhelper genconfig
10 | preconditions:
11 | - test -f {{.TALOS_DIR}}/talconfig.yaml
12 | - test -f {{.ROOT_DIR}}/.sops.yaml
13 | - test -f {{.SOPS_AGE_KEY_FILE}}
14 | - which talhelper
15 |
16 | apply-node:
17 | desc: Apply Talos config to a node [IP=required]
18 | dir: '{{.TALOS_DIR}}'
19 | cmd: talhelper gencommand apply --node {{.IP}} --extra-flags '--mode={{.MODE}}' | bash
20 | vars:
21 | MODE: '{{.MODE | default "auto"}}'
22 | requires:
23 | vars: [IP]
24 | preconditions:
25 | - talosctl --nodes {{.IP}} get machineconfig
26 | - talosctl config info
27 | - test -f {{.TALOSCONFIG}}
28 | - which talhelper talosctl yq
29 |
30 | upgrade-node:
31 | desc: Upgrade Talos on a single node [IP=required]
32 | dir: '{{.TALOS_DIR}}'
33 | cmd: talhelper gencommand upgrade --node {{.IP}} --extra-flags "--image='{{.TALOS_IMAGE}}:{{.TALOS_VERSION}}' --timeout=10m" | bash
34 | vars:
35 | TALOS_IMAGE:
36 | sh: yq '.nodes[] | select(.ipAddress == "{{.IP}}") | .talosImageURL' {{.TALOS_DIR}}/talconfig.yaml
37 | TALOS_VERSION:
38 | sh: yq '.talosVersion' {{.TALOS_DIR}}/talenv.yaml
39 | requires:
40 | vars: [IP]
41 | preconditions:
42 | - talosctl --nodes {{.IP}} get machineconfig
43 | - talosctl config info
44 | - test -f {{.TALOSCONFIG}}
45 | - which kubectl talhelper talosctl yq
46 |
47 | upgrade-k8s:
48 | desc: Upgrade Kubernetes
49 | dir: '{{.TALOS_DIR}}'
50 | cmd: talhelper gencommand upgrade-k8s --extra-flags "--to '{{.KUBERNETES_VERSION}}'" | bash
51 | vars:
52 | KUBERNETES_VERSION:
53 | sh: yq '.kubernetesVersion' {{.TALOS_DIR}}/talenv.yaml
54 | preconditions:
55 | - talosctl config info
56 | - test -f {{.TALOSCONFIG}}
57 | - which talhelper talosctl yq
58 |
59 | reset:
60 | desc: Resets nodes back to maintenance mode
61 | dir: '{{.TALOS_DIR}}'
62 | prompt: This will destroy your cluster and reset the nodes back to maintenance mode... continue?
63 | cmd: talhelper gencommand reset --extra-flags="--reboot {{- if eq .CLI_FORCE false }} --system-labels-to-wipe STATE --system-labels-to-wipe EPHEMERAL{{ end }} --graceful=false --wait=false" | bash
64 | preconditions:
65 | - which talhelper
66 |
--------------------------------------------------------------------------------
/scripts/lib/common.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -Eeuo pipefail
3 |
4 | # Log messages with different levels
5 | function log() {
6 | local level="${1:-info}"
7 | shift
8 |
9 | # Define log levels with their priorities
10 | local -A level_priority=(
11 | [debug]=1
12 | [info]=2
13 | [warn]=3
14 | [error]=4
15 | )
16 |
17 | # Get the current log level's priority
18 | local current_priority=${level_priority[$level]:-2} # Default to "info" priority
19 |
20 | # Get the configured log level from the environment, default to "info"
21 | local configured_level=${LOG_LEVEL:-info}
22 | local configured_priority=${level_priority[$configured_level]:-2}
23 |
24 | # Skip log messages below the configured log level
25 | if ((current_priority < configured_priority)); then
26 | return
27 | fi
28 |
29 | # Define log colors
30 | local -A colors=(
31 | [debug]="\033[1m\033[38;5;63m" # Blue
32 | [info]="\033[1m\033[38;5;87m" # Cyan
33 | [warn]="\033[1m\033[38;5;192m" # Yellow
34 | [error]="\033[1m\033[38;5;198m" # Red
35 | )
36 |
37 | # Fallback to "info" if the color for the given level is not defined
38 | local color="${colors[$level]:-${colors[info]}}"
39 | local msg="$1"
40 | shift
41 |
42 | # Prepare additional data
43 | local data=
44 | if [[ $# -gt 0 ]]; then
45 | for item in "$@"; do
46 | if [[ "${item}" == *=* ]]; then
47 | data+="\033[1m\033[38;5;236m${item%%=*}=\033[0m\"${item#*=}\" "
48 | else
49 | data+="${item} "
50 | fi
51 | done
52 | fi
53 |
54 | # Determine output stream based on log level
55 | local output_stream="/dev/stdout"
56 | if [[ "$level" == "error" ]]; then
57 | output_stream="/dev/stderr"
58 | fi
59 |
60 | # Print the log message
61 | printf "%s %b%s%b %s %b\n" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
62 | "${color}" "${level^^}" "\033[0m" "${msg}" "${data}" >"${output_stream}"
63 |
64 | # Exit if the log level is error
65 | if [[ "$level" == "error" ]]; then
66 | exit 1
67 | fi
68 | }
69 |
70 | # Check if required environment variables are set
71 | function check_env() {
72 | local envs=("${@}")
73 | local missing=()
74 | local values=()
75 |
76 | for env in "${envs[@]}"; do
77 | if [[ -z "${!env-}" ]]; then
78 | missing+=("${env}")
79 | else
80 | values+=("${env}=${!env}")
81 | fi
82 | done
83 |
84 | if [ ${#missing[@]} -ne 0 ]; then
85 | log error "Missing required env variables" "envs=${missing[*]}"
86 | fi
87 |
88 | log debug "Env variables are set" "envs=${values[*]}"
89 | }
90 |
91 | # Check if required CLI tools are installed
92 | function check_cli() {
93 | local deps=("${@}")
94 | local missing=()
95 |
96 | for dep in "${deps[@]}"; do
97 | if ! command -v "${dep}" &>/dev/null; then
98 | missing+=("${dep}")
99 | fi
100 | done
101 |
102 | if [ ${#missing[@]} -ne 0 ]; then
103 | log error "Missing required deps" "deps=${missing[*]}"
104 | fi
105 |
106 | log debug "Deps are installed" "deps=${deps[*]}"
107 | }
108 |
--------------------------------------------------------------------------------
/cluster.sample.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # -- The network CIDR for the nodes.
3 | # (REQUIRED) / (e.g. 192.168.1.0/24)
4 | node_cidr: ""
5 |
6 | # -- DNS servers to use for the cluster.
7 | # (OPTIONAL) / (DEFAULT: ["1.1.1.1", "1.0.0.1"]) / (Cloudflare DNS)
8 | # node_dns_servers: []
9 |
10 | # -- NTP servers to use for the cluster.
11 | # (OPTIONAL) / (DEFAULT: ["162.159.200.1", "162.159.200.123"]) / (Cloudflare NTP)
12 | # node_ntp_servers: []
13 |
14 | # -- The default gateway for the nodes.
15 | # (OPTIONAL) / (DEFAULT: the first IP in the node_cidr)
16 | # node_default_gateway: ""
17 |
18 | # -- Attach a vlan tag to the Talos nodes. Not needed if ports on your switch are tagged or you are not using VLANs.
19 | # (OPTIONAL) / (REF: https://www.talos.dev/latest/advanced/advanced-networking/#vlans)
20 | # node_vlan_tag: ""
21 |
22 | # -- The IP address of the Kube API.
23 | # (REQUIRED) / (NOTE: Choose an unused IP in node_cidr)
24 | cluster_api_addr: ""
25 |
26 | # -- Additional SANs to add to the Kube API cert. This is useful if you want to call the Kube API by hostname rather than IP
27 | # (OPTIONAL) / (e.g. ["mycluster.example.com"])
28 | # cluster_api_tls_sans: []
29 |
30 | # -- The pod CIDR for the cluster, this must NOT overlap with any existing networks and should be a /16 (64K IPs).
31 | # (OPTIONAL) / (DEFAULT: "10.42.0.0/16")
32 | # cluster_pod_cidr: ""
33 |
34 | # -- The service CIDR for the cluster, this must NOT overlap with any existing networks and should be a /16 (64K IPs).
35 | # (OPTIONAL) / (DEFAULT: "10.43.0.0/16")
36 | # cluster_svc_cidr: ""
37 |
38 | # -- The Load balancer IP for k8s_gateway, this provides DNS to all your gateways when split DNS is configured on your internal DNS server (Dnsmasq, Pi-hole, etc)
39 | # (REQUIRED) / (NOTE: Choose an unused IP in node_cidr)
40 | cluster_dns_gateway_addr: ""
41 |
42 | # -- The Load balancer IP for the internal gateway
43 | # (REQUIRED) / (NOTE: Choose an unused IP in node_cidr)
44 | cluster_gateway_addr: ""
45 |
46 | # -- GitHub repository
47 | # (REQUIRED) / (e.g. "onedr0p/cluster-template")
48 | repository_name: ""
49 |
50 | # -- GitHub repository branch
51 | # (OPTIONAL) / (DEFAULT: "main")
52 | # repository_branch: ""
53 |
54 | # -- Repository visibility (public or private)
55 | # (OPTIONAL) / (DEFAULT: "public") / (NOTE: See the README for information when set private)
56 | # repository_visibility: ""
57 |
58 | # -- Domain you wish to use from your Cloudflare account
59 | # (REQUIRED) / (e.g. "example.com")
60 | cloudflare_domain: ""
61 |
62 | # -- API Token for Cloudflare with the 'Zone:DNS:Edit' and 'Account:Cloudflare Tunnel:Read' permissions
63 | # (REQUIRED) (NOTE: See the README for information on creating this)
64 | cloudflare_token: ""
65 |
66 | # -- The Load balancer IP for the external gateway
67 | # (REQUIRED) / (NOTE: Choose an unused IP in node_cidr)
68 | cloudflare_gateway_addr: ""
69 |
70 | # -- The load balancer mode for cilium.
71 | # (OPTIONAL) / (DEFAULT: "dsr") / (NOTE: accepted values are 'dsr' or 'snat') / (REF: https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/)
72 | # cilium_loadbalancer_mode: ""
73 |
74 | # -- The IP address of the BGP router, to keep things simple, node network will be used for BGP peering.
75 | # (OPTIONAL) / (e.g. "192.168.1.1") / (REF: https://docs.cilium.io/en/latest/network/bgp-control-plane/bgp-control-plane/)
76 | # cilium_bgp_router_addr: ""
77 |
78 | # -- The BGP router ASN
79 | # (OPTIONAL) / (e.g. "64513")
80 | # cilium_bgp_router_asn: ""
81 |
82 | # -- The BGP node ASN
83 | # (OPTIONAL) / (e.g. "64514")
84 | # cilium_bgp_node_asn: ""
85 |
86 | # -- Argo CD Admin password to login
87 | # (REQUIRED) / (NOTE: `htpasswd -nbBC 10 "" $ARGO_PWD | tr -d ':\n' | sed 's/$2y/$2a/'`)
88 | argo_password: ""
89 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/argo-system/argo-cd/values.yaml.j2:
--------------------------------------------------------------------------------
1 | fullnameOverride: "argocd"
2 | crds:
3 | install: true
4 | # -- Keep CRDs on chart uninstall
5 | keep: false
6 | configs:
7 | params:
8 | server.insecure: true
9 | controller.diff.server.side: true
10 | cm:
11 | statusbadge.enabled: true
12 | kustomize.buildOptions: "--enable-alpha-plugins --enable-exec"
13 | helm.valuesFileSchemes: >-
14 | secrets+gpg-import, secrets+gpg-import-kubernetes,
15 | secrets+age-import, secrets+age-import-kubernetes,
16 | secrets, secrets+literal,
17 | https
18 | resource.exclusions: |
19 | - apiGroups:
20 | - cilium.io
21 | kinds:
22 | - CiliumIdentity
23 | clusters:
24 | - "*"
25 | controller:
26 | replicas: 2
27 | metrics:
28 | enabled: true
29 | applicationLabels:
30 | enabled: true
31 | serviceMonitor: &service-monitor
32 | enabled: true
33 | additionalLabels:
34 | release: kube-prometheus-stack
35 | dex:
36 | replicas: 2
37 | metrics:
38 | enabled: true
39 | serviceMonitor: *service-monitor
40 | redis:
41 | metrics:
42 | enabled: true
43 | serviceMonitor: *service-monitor
44 | server:
45 | replicas: 2
46 | allowAnyNamespace: true
47 | metrics:
48 | enabled: true
49 | serviceMonitor: *service-monitor
50 | notifications:
51 | replicas: 2
52 | metrics:
53 | enabled: true
54 | serviceMonitor: *service-monitor
55 | repoServer:
56 | replicas: 2
57 | serviceAccount:
58 | create: true
59 | name: argocd-repo-server
60 | rbac:
61 | - apiGroups:
62 | - ""
63 | resources:
64 | - secrets
65 | verbs:
66 | - get
67 | env:
68 | - name: HELM_PLUGINS
69 | value: /custom-tools/helm-plugins/
70 | - name: HELM_SECRETS_CURL_PATH
71 | value: /custom-tools/curl
72 | - name: HELM_SECRETS_SOPS_PATH
73 | value: /custom-tools/sops
74 | - name: HELM_SECRETS_VALS_PATH
75 | value: /custom-tools/vals
76 | - name: HELM_SECRETS_KUBECTL_PATH
77 | value: /custom-tools/kubectl
78 | - name: HELM_SECRETS_BACKEND
79 | value: sops
80 | # https://github.com/jkroepke/helm-secrets/wiki/Security-in-shared-environments
81 | - name: HELM_SECRETS_VALUES_ALLOW_SYMLINKS
82 | value: "false"
83 | - name: HELM_SECRETS_VALUES_ALLOW_ABSOLUTE_PATH
84 | value: "true"
85 | - name: HELM_SECRETS_VALUES_ALLOW_PATH_TRAVERSAL
86 | value: "false"
87 | - name: HELM_SECRETS_WRAPPER_ENABLED
88 | value: "true"
89 | - name: HELM_SECRETS_DECRYPT_SECRETS_IN_TMP_DIR
90 | value: "true"
91 | - name: HELM_SECRETS_HELM_PATH
92 | value: /usr/local/bin/helm
93 | - name: SOPS_AGE_KEY_FILE # For age
94 | value: /helm-secrets-private-keys/key.txt
95 | volumes:
96 | - name: custom-tools
97 | emptyDir: {}
98 | # kubectl create secret generic helm-secrets-private-keys --from-file=key.asc=assets/gpg/private2.gpg
99 | - name: helm-secrets-private-keys
100 | secret:
101 | secretName: helm-secrets-private-keys
102 | volumeMounts:
103 | - mountPath: /custom-tools
104 | name: custom-tools
105 | - mountPath: /usr/local/sbin/helm
106 | subPath: helm
107 | name: custom-tools
108 | - mountPath: /usr/local/bin/kustomize
109 | name: custom-tools
110 | subPath: kustomize
111 | - mountPath: /usr/local/bin/ksops
112 | name: custom-tools
113 | subPath: ksops
114 | - mountPath: /helm-secrets-private-keys/
115 | name: helm-secrets-private-keys
116 | initContainers:
117 | - name: gitops-tools
118 | image: ghcr.io/ajaykumar4/gitops-tools:2025.11.0
119 | imagePullPolicy: Always
120 | command: [sh, -ec]
121 | args:
122 | - |
123 | mkdir -p /custom-tools/
124 | cp -rf /gitops-tools/* /custom-tools
125 | chmod +x /custom-tools/*
126 | volumeMounts:
127 | - mountPath: /custom-tools
128 | name: custom-tools
129 |
--------------------------------------------------------------------------------
/templates/config/talos/talconfig.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | clusterName: kubernetes
3 |
4 | talosVersion: "${talosVersion}"
5 | kubernetesVersion: "${kubernetesVersion}"
6 |
7 | endpoint: https://#{ cluster_api_addr }#:6443
8 | additionalApiServerCertSans: &sans
9 | - "127.0.0.1"
10 | - "#{ cluster_api_addr }#"
11 | #% for item in cluster_api_tls_sans %#
12 | - "#{ item }#"
13 | #% endfor %#
14 | additionalMachineCertSans: *sans
15 |
16 | clusterPodNets: ["#{ cluster_pod_cidr }#"]
17 | clusterSvcNets: ["#{ cluster_svc_cidr }#"]
18 |
19 | # Disable built-in CNI to use Cilium
20 | cniConfig:
21 | name: none
22 |
23 | nodes:
24 | #% for item in nodes %#
25 | - hostname: "#{ item.name }#"
26 | ipAddress: "#{ item.address }#"
27 | #% if item.disk.startswith('/') %#
28 | installDisk: "#{ item.disk }#"
29 | #% else %#
30 | installDiskSelector:
31 | serial: "#{ item.disk }#"
32 | #% endif %#
33 | machineSpec:
34 | secureboot: #{ (true if item.secureboot else false) | string | lower }#
35 | talosImageURL: factory.talos.dev/installer#{ "-secureboot" if item.secureboot | default(false, true) }#/#{ item.schematic_id }#
36 | controlPlane: #{ (item.controller) | string | lower }#
37 | networkInterfaces:
38 | - deviceSelector:
39 | hardwareAddr: "#{ item.mac_addr | lower }#"
40 | #% if node_vlan_tag %#
41 | vlans:
42 | - vlanId: #{ node_vlan_tag }#
43 | addresses:
44 | - "#{ item.address }#/#{ node_cidr.split('/') | last }#"
45 | mtu: #{ item.mtu | default(1500, true) }#
46 | routes:
47 | - network: "0.0.0.0/0"
48 | gateway: "#{ node_default_gateway }#"
49 | #% if item.controller %#
50 | vip:
51 | ip: "#{ cluster_api_addr }#"
52 | #% endif %#
53 | #% else %#
54 | dhcp: false
55 | addresses:
56 | - "#{ item.address }#/#{ node_cidr.split('/') | last }#"
57 | routes:
58 | - network: "0.0.0.0/0"
59 | gateway: "#{ node_default_gateway }#"
60 | mtu: #{ item.mtu | default(1500, true) }#
61 | #% if item.controller %#
62 | vip:
63 | ip: "#{ cluster_api_addr }#"
64 | #% endif %#
65 | #% endif %#
66 | #% if talos_patches('%s' % (item.name)) | length == 0 %#
67 | #% if item.encrypt_disk | default(false, true) %#
68 | patches:
69 | - # Encrypt system disk with TPM
70 | |-
71 | machine:
72 | systemDiskEncryption:
73 | state:
74 | provider: luks2
75 | keys:
76 | - slot: 0
77 | tpm: {}
78 | ephemeral:
79 | provider: luks2
80 | keys:
81 | - slot: 0
82 | tpm: {}
83 | #% endif %#
84 | #% else %#
85 | #% for file in talos_patches('%s' % (item.name)) %#
86 | #% if loop.index == 1 %#
87 | patches:
88 | #% if item.encrypt_disk | default(false, true) %#
89 | - |-
90 | machine:
91 | systemDiskEncryption:
92 | state:
93 | provider: luks2
94 | keys:
95 | - slot: 0
96 | tpm: {}
97 | ephemeral:
98 | provider: luks2
99 | keys:
100 | - slot: 0
101 | tpm: {}
102 | #% endif %#
103 | #% endif %#
104 | - "@./patches/#{ item.name }#/#{ file | basename }#"
105 | #% endfor %#
106 | #% endif %#
107 | #% endfor %#
108 |
109 | #% for file in talos_patches('global') %#
110 | #% if loop.index == 1 %#
111 | # Global patches
112 | patches:
113 | #% endif %#
114 | - "@./patches/global/#{ file | basename }#"
115 | #% endfor %#
116 |
117 | #% for file in talos_patches('controller') %#
118 | #% if loop.index == 1 %#
119 | # Controller patches
120 | controlPlane:
121 | patches:
122 | #% endif %#
123 | - "@./patches/controller/#{ file | basename }#"
124 | #% endfor %#
125 |
126 | #% if (nodes | selectattr('controller', 'equalto', False) | list | length) and (talos_patches('worker') | length) %#
127 | #% for file in talos_patches('worker') %#
128 | #% if loop.index == 1 %#
129 | # Worker patches
130 | worker:
131 | patches:
132 | #% endif %#
133 | - "@./patches/worker/#{ file | basename }#"
134 | #% endfor %#
135 | #% endif %#
136 |
--------------------------------------------------------------------------------
/templates/config/kubernetes/apps/network/envoy-gateway/config/envoy.sops.yaml.j2:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: gateway.envoyproxy.io/v1alpha1
3 | kind: EnvoyProxy
4 | metadata:
5 | name: envoy
6 | spec:
7 | logging:
8 | level:
9 | default: info
10 | provider:
11 | type: Kubernetes
12 | kubernetes:
13 | envoyDeployment:
14 | replicas: 2
15 | container:
16 | resources:
17 | requests:
18 | cpu: 100m
19 | limits:
20 | memory: 1Gi
21 | envoyService:
22 | externalTrafficPolicy: Cluster
23 | shutdown:
24 | drainTimeout: 180s
25 | telemetry:
26 | metrics:
27 | prometheus:
28 | compression:
29 | type: Gzip
30 | ---
31 | apiVersion: gateway.networking.k8s.io/v1
32 | kind: GatewayClass
33 | metadata:
34 | name: envoy
35 | spec:
36 | controllerName: gateway.envoyproxy.io/gatewayclass-controller
37 | parametersRef:
38 | group: gateway.envoyproxy.io
39 | kind: EnvoyProxy
40 | name: envoy
41 | namespace: network
42 | ---
43 | apiVersion: gateway.networking.k8s.io/v1
44 | kind: Gateway
45 | metadata:
46 | name: envoy-external
47 | annotations:
48 | external-dns.alpha.kubernetes.io/target: "external.#{ cloudflare_domain }#"
49 | spec:
50 | gatewayClassName: envoy
51 | infrastructure:
52 | annotations:
53 | external-dns.alpha.kubernetes.io/hostname: "external.#{ cloudflare_domain }#"
54 | lbipam.cilium.io/ips: "#{ cloudflare_gateway_addr }#"
55 | listeners:
56 | - name: http
57 | protocol: HTTP
58 | port: 80
59 | allowedRoutes:
60 | namespaces:
61 | from: Same
62 | - name: https
63 | protocol: HTTPS
64 | port: 443
65 | allowedRoutes:
66 | namespaces:
67 | from: All
68 | tls:
69 | certificateRefs:
70 | - kind: Secret
71 | name: "#{ cloudflare_domain.replace('.', '-') }#-production-tls"
72 | ---
73 | apiVersion: gateway.networking.k8s.io/v1
74 | kind: Gateway
75 | metadata:
76 | name: envoy-internal
77 | annotations:
78 | external-dns.alpha.kubernetes.io/target: "internal.#{ cloudflare_domain }#"
79 | spec:
80 | gatewayClassName: envoy
81 | infrastructure:
82 | annotations:
83 | external-dns.alpha.kubernetes.io/hostname: "internal.#{ cloudflare_domain }#"
84 | lbipam.cilium.io/ips: "#{ cluster_gateway_addr }#"
85 | listeners:
86 | - name: http
87 | protocol: HTTP
88 | port: 80
89 | allowedRoutes:
90 | namespaces:
91 | from: Same
92 | - name: https
93 | protocol: HTTPS
94 | port: 443
95 | allowedRoutes:
96 | namespaces:
97 | from: All
98 | tls:
99 | certificateRefs:
100 | - kind: Secret
101 | name: "#{ cloudflare_domain.replace('.', '-') }#-production-tls"
102 | ---
103 | apiVersion: gateway.envoyproxy.io/v1alpha1
104 | kind: BackendTrafficPolicy
105 | metadata:
106 | name: envoy
107 | spec:
108 | compression:
109 | - type: Brotli
110 | - type: Gzip
111 | connection:
112 | bufferLimit: 8Mi
113 | targetSelectors:
114 | - group: gateway.networking.k8s.io
115 | kind: Gateway
116 | tcpKeepalive: {}
117 | timeout:
118 | http:
119 | requestTimeout: 0s
120 | ---
121 | apiVersion: gateway.envoyproxy.io/v1alpha1
122 | kind: ClientTrafficPolicy
123 | metadata:
124 | name: envoy
125 | spec:
126 | clientIPDetection:
127 | xForwardedFor:
128 | numTrustedHops: 1
129 | connection:
130 | bufferLimit: 4Mi
131 | maxAcceptPerSocketEvent: 0
132 | http2:
133 | initialStreamWindowSize: 512Ki
134 | initialConnectionWindowSize: 8Mi
135 | onInvalidMessage: TerminateStream
136 | http3: {}
137 | targetSelectors:
138 | - group: gateway.networking.k8s.io
139 | kind: Gateway
140 | tcpKeepalive: {}
141 | timeout:
142 | http:
143 | requestReceivedTimeout: 0s
144 | tls:
145 | minVersion: "1.2"
146 | alpnProtocols:
147 | - h2
148 | - http/1.1
149 | ---
150 | apiVersion: gateway.networking.k8s.io/v1
151 | kind: HTTPRoute
152 | metadata:
153 | name: https-redirect
154 | annotations:
155 | external-dns.alpha.kubernetes.io/controller: none
156 | spec:
157 | parentRefs:
158 | - name: envoy-external
159 | namespace: network
160 | sectionName: http
161 | - name: envoy-internal
162 | namespace: network
163 | sectionName: http
164 | rules:
165 | - filters:
166 | - type: RequestRedirect
167 | requestRedirect:
168 | scheme: https
169 | statusCode: 301
170 |
--------------------------------------------------------------------------------
/.renovaterc.json5:
--------------------------------------------------------------------------------
1 | {
2 | $schema: "https://docs.renovatebot.com/renovate-schema.json",
3 | extends: [
4 | "config:recommended",
5 | "docker:enableMajor",
6 | "helpers:pinGitHubActionDigests",
7 | ":automergeBranch",
8 | ":dependencyDashboard",
9 | ":disableRateLimiting",
10 | ":semanticCommits",
11 | ],
12 | dependencyDashboard: true,
13 | dependencyDashboardTitle: "Renovate Dashboard 🤖",
14 | schedule: ["every weekend"],
15 | ignorePaths: ["**/*.sops.*"],
16 | argocd: {
17 | managerFilePatterns: ["(^|/)kubernetes/.+\\.ya?ml(?:\\.j2)?$"],
18 | },
19 | helmfile: {
20 | managerFilePatterns: [
21 | "/(^|/)helmfile\\.ya?ml?(?:\\.j2)?$/",
22 | "/(^|/)helmfile\\.d/.+\\.ya?ml?(?:\\.j2)?$/",
23 | ],
24 | },
25 | "helm-values": {
26 | "managerFilePatterns": ["/(^|/)kubernetes/.+\\/values\\.ya?ml(?:\\.j2)?$/"]
27 | },
28 | kubernetes: {
29 | managerFilePatterns: ["(^|/)kubernetes/.+\\.ya?ml(?:\\.j2)?$"],
30 | },
31 | kustomize: {
32 | managerFilePatterns: ["/^kustomization\\.ya?ml(?:\\.j2)?$/"],
33 | },
34 | packageRules: [
35 | {
36 | description: "Override Helmfile Dependency Name",
37 | matchDatasources: ["docker"],
38 | matchManagers: ["helmfile"],
39 | overrideDepName: "{{packageName}}",
40 | },
41 | {
42 | description: "Argo Operator Group",
43 | groupName: "argocd",
44 | matchDatasources: ["docker"],
45 | matchPackageNames: ["/argocd/"],
46 | group: {
47 | commitMessageTopic: "{{{groupName}}} group",
48 | },
49 | minimumGroupSize: 2,
50 | },
51 | {
52 | description: "Auto-merge GitHub Actions",
53 | matchManagers: ["github-actions"],
54 | automerge: true,
55 | automergeType: "branch",
56 | matchUpdateTypes: ["minor", "patch", "digest"],
57 | minimumReleaseAge: "3 days",
58 | ignoreTests: true,
59 | },
60 | {
61 | description: "Auto-merge Mise Tools",
62 | matchManagers: ["mise"],
63 | automerge: true,
64 | automergeType: "branch",
65 | matchUpdateTypes: ["minor", "patch"],
66 | ignoreTests: true,
67 | },
68 | {
69 | matchUpdateTypes: ["major"],
70 | semanticCommitType: "feat",
71 | commitMessagePrefix: "{{semanticCommitType}}({{semanticCommitScope}})!:",
72 | commitMessageExtra: "( {{currentVersion}} ➔ {{newVersion}} )",
73 | },
74 | {
75 | matchUpdateTypes: ["minor"],
76 | semanticCommitType: "feat",
77 | commitMessageExtra: "( {{currentVersion}} ➔ {{newVersion}} )",
78 | },
79 | {
80 | matchUpdateTypes: ["patch"],
81 | semanticCommitType: "fix",
82 | commitMessageExtra: "( {{currentVersion}} ➔ {{newVersion}} )",
83 | },
84 | {
85 | matchUpdateTypes: ["digest"],
86 | semanticCommitType: "chore",
87 | commitMessageExtra: "( {{currentDigestShort}} ➔ {{newDigestShort}} )",
88 | },
89 | {
90 | matchDatasources: ["docker"],
91 | semanticCommitScope: "container",
92 | commitMessageTopic: "image {{depName}}",
93 | },
94 | {
95 | matchDatasources: ["helm"],
96 | semanticCommitScope: "helm",
97 | commitMessageTopic: "chart {{depName}}",
98 | },
99 | {
100 | matchManagers: ["github-actions"],
101 | semanticCommitType: "ci",
102 | semanticCommitScope: "github-action",
103 | commitMessageTopic: "action {{depName}}",
104 | },
105 | {
106 | matchDatasources: ["github-releases"],
107 | semanticCommitScope: "github-release",
108 | commitMessageTopic: "release {{depName}}",
109 | },
110 | {
111 | matchManagers: ["mise"],
112 | semanticCommitScope: "mise",
113 | commitMessageTopic: "tool {{depName}}",
114 | },
115 | {
116 | matchUpdateTypes: ["major"],
117 | labels: ["type/major"],
118 | },
119 | {
120 | matchUpdateTypes: ["minor"],
121 | labels: ["type/minor"],
122 | },
123 | {
124 | matchUpdateTypes: ["patch"],
125 | labels: ["type/patch"],
126 | },
127 | {
128 | matchDatasources: ["docker"],
129 | addLabels: ["renovate/container"],
130 | },
131 | {
132 | matchDatasources: ["helm"],
133 | addLabels: ["renovate/helm"],
134 | },
135 | {
136 | matchManagers: ["github-actions"],
137 | addLabels: ["renovate/github-action"],
138 | },
139 | {
140 | matchDatasources: ["github-releases"],
141 | addLabels: ["renovate/github-release"],
142 | },
143 | ],
144 | customManagers: [
145 | {
146 | description: "Process annotated dependencies",
147 | customType: "regex",
148 | managerFilePatterns: [
149 | "/(^|/).+\\.env(?:\\.j2)?$/",
150 | "/(^|/).+\\.sh(?:\\.j2)?$/",
151 | "/(^|/).+\\.ya?ml(?:\\.j2)?$/",
152 | ],
153 | matchStrings: [
154 | // # renovate: datasource=github-releases depName=k3s-io/k3s
155 | // k3s_release_version: &version v1.29.0+k3s1
156 | // # renovate: datasource=helm depName=cilium repository=https://helm.cilium.io
157 | // version: 1.15.1
158 | // # renovate: datasource=docker depName=ghcr.io/siderolabs/kubelet
159 | // KUBERNETES_VERSION=v1.31.1
160 | "datasource=(?\\S+) depName=(?\\S+)( repository=(?\\S+))?\\n.+(:\\s|=)(&\\S+\\s)?(?\\S+)",
161 | // # renovate: datasource=docker depName=ghcr.io/prometheus-operator/prometheus-operator
162 | // https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/v0.80.0/example/prometheus-operator-crd/monitoring.coreos.com_alertmanagerconfigs.yaml
163 | "datasource=(?\\S+) depName=(?\\S+)\\n.+/(?(v|\\d)[^/]+)",
164 | ],
165 | datasourceTemplate: "{{#if datasource}}{{{datasource}}}{{else}}github-releases{{/if}}",
166 | },
167 | ],
168 | }
169 |
--------------------------------------------------------------------------------
/scripts/bootstrap-apps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -Eeuo pipefail
3 |
4 | source "$(dirname "${0}")/lib/common.sh"
5 |
6 | export LOG_LEVEL="debug"
7 | export ROOT_DIR="$(git rev-parse --show-toplevel)"
8 |
9 | # Talos requires the nodes to be 'Ready=False' before applying resources
10 | function wait_for_nodes() {
11 | log debug "Waiting for nodes to be available"
12 |
13 | # Skip waiting if all nodes are 'Ready=True'
14 | if kubectl wait nodes --for=condition=Ready=True --all --timeout=10s &>/dev/null; then
15 | log info "Nodes are available and ready, skipping wait for nodes"
16 | return
17 | fi
18 |
19 | # Wait for all nodes to be 'Ready=False'
20 | until kubectl wait nodes --for=condition=Ready=False --all --timeout=10s &>/dev/null; do
21 | log info "Nodes are not available, waiting for nodes to be available. Retrying in 10 seconds..."
22 | sleep 10
23 | done
24 | }
25 |
26 | # Namespaces to be applied before the SOPS secrets are installed
27 | function apply_namespaces() {
28 | log debug "Applying namespaces"
29 |
30 | local -r apps_dir="${ROOT_DIR}/kubernetes/apps"
31 |
32 | if [[ ! -d "${apps_dir}" ]]; then
33 | log error "Directory does not exist" "directory=${apps_dir}"
34 | fi
35 |
36 | for app in "${apps_dir}"/*/; do
37 | namespace=$(basename "${app}")
38 |
39 | # Check if the namespace resources are up-to-date
40 | if kubectl get namespace "${namespace}" &>/dev/null; then
41 | log info "Namespace resource is up-to-date" "resource=${namespace}"
42 | continue
43 | fi
44 |
45 | # Apply the namespace resources
46 | if kubectl create namespace "${namespace}" --dry-run=client --output=yaml \
47 | | kubectl apply --server-side --filename - &>/dev/null;
48 | then
49 | log info "Namespace resource applied" "resource=${namespace}"
50 | else
51 | log error "Failed to apply namespace resource" "resource=${namespace}"
52 | fi
53 | done
54 | }
55 |
56 | # SOPS secrets to be applied before the helmfile charts are installed
57 | function apply_sops_secrets() {
58 | log debug "Applying secrets"
59 |
60 | local -r secrets=(
61 | "${ROOT_DIR}/kubernetes/components/common/helm-secrets-private-keys.sops.yaml"
62 | )
63 |
64 | for secret in "${secrets[@]}"; do
65 | if [ ! -f "${secret}" ]; then
66 | log warn "File does not exist" "file=${secret}"
67 | continue
68 | fi
69 |
70 | # Check if the secret resources are up-to-date
71 | if sops exec-file "${secret}" "kubectl --namespace argo-system diff --filename {}" &>/dev/null; then
72 | log info "Secret resource is up-to-date" "resource=$(basename "${secret}" ".sops.yaml")"
73 | continue
74 | fi
75 |
76 | # Apply secret resources
77 | if sops exec-file "${secret}" "kubectl --namespace argo-system apply --server-side --filename {}" &>/dev/null; then
78 | log info "Secret resource applied successfully" "resource=$(basename "${secret}" ".sops.yaml")"
79 | else
80 | log error "Failed to apply secret resource" "resource=$(basename "${secret}" ".sops.yaml")"
81 | fi
82 | done
83 | }
84 |
85 | # CRDs to be applied before the helmfile charts are installed
86 | function apply_crds() {
87 | log debug "Applying CRDs"
88 |
89 | local -r helmfile_file="${ROOT_DIR}/bootstrap/helmfile.d/00-crds.yaml"
90 |
91 | if [[ ! -f "${helmfile_file}" ]]; then
92 | log fatal "File does not exist" "file" "${helmfile_file}"
93 | fi
94 |
95 | if ! crds=$(helmfile --file "${helmfile_file}" template --quiet) || [[ -z "${crds}" ]]; then
96 | log fatal "Failed to render CRDs from Helmfile" "file" "${helmfile_file}"
97 | fi
98 |
99 | if echo "${crds}" | kubectl diff --filename - &>/dev/null; then
100 | log info "CRDs are up-to-date"
101 | return
102 | fi
103 |
104 | if ! echo "${crds}" | kubectl apply --server-side --filename - &>/dev/null; then
105 | log fatal "Failed to apply crds from Helmfile" "file" "${helmfile_file}"
106 | fi
107 |
108 | log info "CRDs applied successfully"
109 | }
110 |
111 | # Sync Helm releases
112 | function sync_helm_releases() {
113 | log debug "Syncing Helm releases"
114 |
115 | local -r helmfile_file="${ROOT_DIR}/bootstrap/helmfile.d/01-apps.yaml"
116 |
117 | if [[ ! -f "${helmfile_file}" ]]; then
118 | log error "File does not exist" "file=${helmfile_file}"
119 | fi
120 |
121 | if ! helmfile --file "${helmfile_file}" sync --hide-notes; then
122 | log error "Failed to sync Helm releases"
123 | fi
124 |
125 | log info "Helm releases synced successfully"
126 | }
127 |
128 | # Sync Argo Applications
129 | function sync_argo_apps() {
130 | log debug "Sync Argo Applications"
131 |
132 | local -r bootstrappingmaps=(
133 | "${ROOT_DIR}/kubernetes/components/common/apps.yaml"
134 | "${ROOT_DIR}/kubernetes/components/common/repositories.yaml"
135 | "${ROOT_DIR}/kubernetes/components/common/settings.yaml"
136 | )
137 |
138 | for bootstrappingmap in "${bootstrappingmaps[@]}"; do
139 | if [ ! -f "${bootstrappingmap}" ]; then
140 | log warn "File does not exist" file "${bootstrappingmap}"
141 | continue
142 | fi
143 |
144 | # Check if the bootstrappingmap resources are up-to-date
145 | if kubectl --namespace argo-system diff --filename "${bootstrappingmap}" &>/dev/null; then
146 | log info "bootstrappingmap resource is up-to-date" "resource=$(basename "${bootstrappingmap}" ".yaml")"
147 | continue
148 | fi
149 |
150 | # Apply bootstrappingmap resources
151 | if kubectl --namespace argo-system apply --server-side --filename "${bootstrappingmap}" &>/dev/null; then
152 | log info "bootstrappingmap resource applied successfully" "resource=$(basename "${bootstrappingmap}" ".yaml")"
153 | else
154 | log error "Failed to apply bootstrappingmap resource" "resource=$(basename "${bootstrappingmap}" ".yaml")"
155 | fi
156 | done
157 | }
158 |
159 | function main() {
160 | check_env KUBECONFIG TALOSCONFIG
161 | check_cli helmfile kubectl kustomize sops talhelper yq
162 |
163 | # Apply resources and Helm releases
164 | wait_for_nodes
165 | apply_namespaces
166 | apply_sops_secrets
167 | apply_crds
168 | sync_helm_releases
169 | sync_argo_apps
170 |
171 | log info "Congrats! The cluster is bootstrapped and Argo is syncing the Git repository"
172 | }
173 |
174 | main "$@"
175 |
--------------------------------------------------------------------------------
/.taskfiles/template/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3'
3 |
4 | vars:
5 | MAKEJINJA_CONFIG_FILE: '{{.ROOT_DIR}}/makejinja.toml'
6 | TEMPLATE_DIR: '{{.ROOT_DIR}}/templates'
7 | TEMPLATE_RESOURCES_DIR: '{{.ROOT_DIR}}/.taskfiles/template/resources'
8 | TEMPLATE_CONFIG_FILE: '{{.ROOT_DIR}}/cluster.yaml'
9 | TEMPLATE_NODE_CONFIG_FILE: '{{.ROOT_DIR}}/nodes.yaml'
10 |
11 | tasks:
12 |
13 | :init:
14 | desc: Initialize configuration files
15 | cmds:
16 | - task: generate-template-config
17 | - task: generate-age-key
18 | - task: generate-deploy-key
19 | - task: generate-push-token
20 |
21 | generate-template-config:
22 | internal: true
23 | cmds:
24 | - mv {{.TEMPLATE_CONFIG_FILE | replace ".yaml" ".sample.yaml"}} {{.TEMPLATE_CONFIG_FILE}}
25 | - mv {{.TEMPLATE_NODE_CONFIG_FILE | replace ".yaml" ".sample.yaml"}} {{.TEMPLATE_NODE_CONFIG_FILE}}
26 | status:
27 | - test -f {{.TEMPLATE_CONFIG_FILE}}
28 | - test -f {{.TEMPLATE_NODE_CONFIG_FILE}}
29 |
30 | generate-age-key:
31 | internal: true
32 | cmd: age-keygen --output {{.SOPS_AGE_KEY_FILE}}
33 | status:
34 | - test -f {{.SOPS_AGE_KEY_FILE}}
35 | preconditions:
36 | - which age-keygen
37 |
38 | generate-deploy-key:
39 | internal: true
40 | cmd: ssh-keygen -t ed25519 -C "deploy-key" -f {{.ROOT_DIR}}/github-deploy.key -q -P ""
41 | status:
42 | - test -f {{.ROOT_DIR}}/github-deploy.key
43 | preconditions:
44 | - which ssh-keygen
45 |
46 | generate-push-token:
47 | internal: true
48 | cmd: python -c "import secrets; print(secrets.token_hex(16))" > {{.ROOT_DIR}}/github-push-token.txt
49 | status:
50 | - test -f {{.ROOT_DIR}}/github-push-token.txt
51 |
52 | :configure:
53 | desc: Render and validate configuration files
54 | prompt: Any conflicting files in the kubernetes directory will be overwritten... continue?
55 | cmds:
56 | - task: validate-schemas
57 | - task: render-configs
58 | - task: encrypt-secrets
59 | - task: validate-kubernetes-config
60 | - task: validate-talos-config
61 | preconditions:
62 | - msg: An existing Age key interferes with the age key in this repository, rename or delete ~/.config/sops/age/keys.txt
63 | sh: '! test -f ~/.config/sops/age/keys.txt'
64 | - msg: File cluster.yaml not found, did you run `task init`?
65 | sh: test -f {{.TEMPLATE_CONFIG_FILE}}
66 | - msg: File nodes.yaml not found, did you run `task init`?
67 | sh: test -f {{.TEMPLATE_NODE_CONFIG_FILE}}
68 | - msg: File cloudflare-tunnel.json not found, see the README for information on creating it.
69 | sh: test -f {{.ROOT_DIR}}/cloudflare-tunnel.json
70 |
71 | validate-schemas:
72 | internal: true
73 | cmds:
74 | - cue vet {{.TEMPLATE_CONFIG_FILE}} {{.TEMPLATE_RESOURCES_DIR}}/cluster.schema.cue
75 | - cue vet {{.TEMPLATE_NODE_CONFIG_FILE}} {{.TEMPLATE_RESOURCES_DIR}}/nodes.schema.cue
76 | preconditions:
77 | - test -f {{.TEMPLATE_RESOURCES_DIR}}/cluster.schema.cue
78 | - test -f {{.TEMPLATE_RESOURCES_DIR}}/nodes.schema.cue
79 | - which cue
80 |
81 | render-configs:
82 | internal: true
83 | cmd: makejinja
84 | env:
85 | PYTHONDONTWRITEBYTECODE: '1'
86 | preconditions:
87 | - test -f {{.TEMPLATE_DIR}}/scripts/plugin.py
88 | - test -f {{.MAKEJINJA_CONFIG_FILE}}
89 | - which makejinja
90 |
91 | encrypt-secrets:
92 | internal: true
93 | cmds:
94 | - for: { var: SECRET_FILES }
95 | cmd: |
96 | if [ $(sops filestatus "{{.ITEM}}" | jq ".encrypted") == "false" ]; then
97 | sops --encrypt --in-place "{{.ITEM}}"
98 | fi
99 | vars:
100 | SECRET_FILES:
101 | sh: find "{{.BOOTSTRAP_DIR}}" "{{.KUBERNETES_DIR}}" "{{.TALOS_DIR}}" -type f -name "*.sops.*" -print
102 | preconditions:
103 | - test -f {{.SOPS_AGE_KEY_FILE}}
104 | - test -f {{.ROOT_DIR}}/.sops.yaml
105 | - which jq sops
106 |
107 | validate-kubernetes-config:
108 | internal: true
109 | cmd: bash {{.TEMPLATE_RESOURCES_DIR}}/kubeconform.sh {{.KUBERNETES_DIR}}
110 | preconditions:
111 | - test -f {{.TEMPLATE_RESOURCES_DIR}}/kubeconform.sh
112 | - which kubeconform
113 |
114 | validate-talos-config:
115 | internal: true
116 | dir: '{{.TALOS_DIR}}'
117 | cmd: talhelper validate talconfig {{.TALOS_DIR}}/talconfig.yaml
118 | preconditions:
119 | - test -f {{.TALOS_DIR}}/talconfig.yaml
120 | - which talhelper
121 |
122 | debug:
123 | desc: Gather common resources in your cluster
124 | cmds:
125 | - for:
126 | matrix:
127 | RESOURCE: [certificates, certificaterequests, gitrepositories, helmrepositories, helmreleases, httproutes, kustomizations, nodes, pods]
128 | cmd: kubectl get --all-namespaces {{.ITEM.RESOURCE}}
129 | preconditions:
130 | - test -f {{.KUBECONFIG}}
131 | - which kubectl
132 |
133 | tidy:
134 | desc: Archive template related files and directories
135 | prompt: All files and directories related to the templating process will be archived... continue?
136 | cmds:
137 | - mkdir -p {{.TIDY_FOLDER}}
138 | - rm -rf {{.ROOT_DIR}}/.github/tests
139 | - rm -rf {{.ROOT_DIR}}/.github/workflows/e2e.yaml
140 | - rm -rf {{.ROOT_DIR}}/.github/workflows/mise.yaml
141 | - rm -rf {{.ROOT_DIR}}/.github/workflows/release.yaml
142 | - |
143 | {{.SED}} -i 's/(..\.j2)\?//g' {{.ROOT_DIR}}/.renovaterc.json5
144 | - mv {{.TEMPLATE_DIR}} {{.TIDY_FOLDER}}/templates
145 | - mv {{.MAKEJINJA_CONFIG_FILE}} {{.TIDY_FOLDER}}/makejinja.toml
146 | - mv {{.TEMPLATE_CONFIG_FILE}} {{.TIDY_FOLDER}}/cluster.yaml
147 | - mv {{.TEMPLATE_NODE_CONFIG_FILE}} {{.TIDY_FOLDER}}/nodes.yaml
148 | - |
149 | {{.SED}} -i '/template:/d' {{.ROOT_DIR}}/Taskfile.yaml
150 | - mv {{.ROOT_DIR}}/.taskfiles/template {{.TIDY_FOLDER}}/.taskfiles/
151 | vars:
152 | TIDY_FOLDER: '{{.PRIVATE_DIR}}/{{now | unixEpoch}}'
153 | SED:
154 | sh: which gsed || which sed
155 | preconditions:
156 | - msg: Unsupported sed version, run `brew install gsed` to upgrade
157 | sh: '{{if eq OS "darwin"}}test -f /opt/homebrew/bin/gsed || test -f /usr/local/bin/gsed{{end}}'
158 | - test -d {{.ROOT_DIR}}/.taskfiles/template
159 | - test -d {{.TEMPLATE_DIR}}
160 | - test -f {{.MAKEJINJA_CONFIG_FILE}}
161 | - test -f {{.ROOT_DIR}}/.renovaterc.json5
162 |
163 | reset:
164 | desc: Remove templated files and directories
165 | prompt: Remove all templated files and directories... continue?
166 | cmds:
167 | - rm -rf {{.BOOTSTRAP_DIR}}
168 | - rm -rf {{.KUBERNETES_DIR}}
169 | - rm -rf {{.TALOS_DIR}}
170 | - rm -rf {{.ROOT_DIR}}/.sops.yaml
171 |
--------------------------------------------------------------------------------
/templates/scripts/plugin.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import Any
3 | from datetime import datetime
4 |
5 | import base64
6 | import ipaddress
7 | import makejinja
8 | import re
9 | import json
10 |
11 |
12 | # Return the filename of a path without the j2 extension
13 | def basename(value: str) -> str:
14 | return Path(value).stem
15 |
16 |
17 | # Return the nth host in a CIDR range
18 | def nthhost(value: str, query: int) -> str:
19 | try:
20 | network = ipaddress.ip_network(value, strict=False)
21 | if 0 <= query < network.num_addresses:
22 | return str(network[query])
23 | except ValueError:
24 | pass
25 | return False
26 |
27 |
28 | # Return the age public or private key from age.key
29 | def age_key(key_type: str, file_path: str = 'age.key') -> str:
30 | try:
31 | with open(file_path, 'r') as file:
32 | file_content = file.read().strip()
33 | if key_type == 'public':
34 | key_match = re.search(r"# public key: (age1[\w]+)", file_content)
35 | if not key_match:
36 | raise ValueError("Could not find public key in the age key file.")
37 | return key_match.group(1)
38 | elif key_type == 'private':
39 | key_match = re.search(r"(AGE-SECRET-KEY-[\w]+)", file_content)
40 | if not key_match:
41 | raise ValueError("Could not find private key in the age key file.")
42 | return key_match.group(1)
43 | else:
44 | raise ValueError("Invalid key type. Use 'public' or 'private'.")
45 | except FileNotFoundError:
46 | raise FileNotFoundError(f"File not found: {file_path}")
47 | except Exception as e:
48 | raise RuntimeError(f"Unexpected error while processing {file_path}: {e}")
49 |
50 |
51 | # Return cloudflare tunnel fields from cloudflare-tunnel.json
52 | def cloudflare_tunnel_id(file_path: str = 'cloudflare-tunnel.json') -> str:
53 | try:
54 | with open(file_path, 'r') as file:
55 | data = json.load(file)
56 | tunnel_id = data.get("TunnelID")
57 | if tunnel_id is None:
58 | raise KeyError(f"Missing 'TunnelID' key in {file_path}")
59 | return tunnel_id
60 |
61 | except FileNotFoundError:
62 | raise FileNotFoundError(f"File not found: {file_path}")
63 | except json.JSONDecodeError:
64 | raise ValueError(f"Could not decode JSON file: {file_path}")
65 | except KeyError as e:
66 | raise KeyError(f"Error in JSON structure: {e}")
67 | except Exception as e:
68 | raise RuntimeError(f"Unexpected error while processing {file_path}: {e}")
69 |
70 |
71 | # Return cloudflare tunnel fields from cloudflare-tunnel.json in TUNNEL_TOKEN format
72 | def cloudflare_tunnel_secret(file_path: str = 'cloudflare-tunnel.json') -> str:
73 | try:
74 | with open(file_path, 'r') as file:
75 | data = json.load(file)
76 | transformed_data = {
77 | "a": data["AccountTag"],
78 | "t": data["TunnelID"],
79 | "s": data["TunnelSecret"]
80 | }
81 | json_string = json.dumps(transformed_data, separators=(',', ':'))
82 | return base64.b64encode(json_string.encode('utf-8')).decode('utf-8')
83 |
84 | except FileNotFoundError:
85 | raise FileNotFoundError(f"File not found: {file_path}")
86 | except json.JSONDecodeError:
87 | raise ValueError(f"Could not decode JSON file: {file_path}")
88 | except KeyError as e:
89 | raise KeyError(f"Missing key in JSON file {file_path}: {e}")
90 | except Exception as e:
91 | raise RuntimeError(f"Unexpected error while processing {file_path}: {e}")
92 |
93 |
94 | # Return the GitHub deploy key from github-deploy.key
95 | def github_deploy_key(file_path: str = 'github-deploy.key') -> str:
96 | try:
97 | with open(file_path, 'r') as file:
98 | return file.read().strip()
99 | except FileNotFoundError:
100 | raise FileNotFoundError(f"File not found: {file_path}")
101 | except Exception as e:
102 | raise RuntimeError(f"Unexpected error while reading {file_path}: {e}")
103 |
104 |
105 | # Return the Argo / GitHub push token from github-push-token.txt
106 | def github_push_token(file_path: str = 'github-push-token.txt') -> str:
107 | try:
108 | with open(file_path, 'r') as file:
109 | return file.read().strip()
110 | except FileNotFoundError:
111 | raise FileNotFoundError(f"File not found: {file_path}")
112 | except Exception as e:
113 | raise RuntimeError(f"Unexpected error while reading {file_path}: {e}")
114 |
115 |
116 | # Return a list of files in the talos patches directory
117 | def talos_patches(value: str) -> list[str]:
118 | path = Path(f'templates/config/talos/patches/{value}')
119 | if not path.is_dir():
120 | return []
121 | return [str(f) for f in sorted(path.glob('*.yaml.j2')) if f.is_file()]
122 |
123 |
124 | # Return current datetime
125 | def current_datetime() -> str:
126 | return datetime.now().strftime('%Y-%m-%dT%H:%M:%S%Z')
127 |
128 |
129 | class Plugin(makejinja.plugin.Plugin):
130 | def __init__(self, data: dict[str, Any]):
131 | self._data = data
132 |
133 |
134 | def data(self) -> makejinja.plugin.Data:
135 | data = self._data
136 |
137 | # Set default values for optional fields
138 | data.setdefault('node_default_gateway', nthhost(data.get('node_cidr'), 1))
139 | data.setdefault('node_dns_servers', ['1.1.1.1', '1.0.0.1'])
140 | data.setdefault('node_ntp_servers', ['162.159.200.1', '162.159.200.123'])
141 | data.setdefault('cluster_pod_cidr', '10.42.0.0/16')
142 | data.setdefault('cluster_svc_cidr', '10.43.0.0/16')
143 | data.setdefault('repository_branch', 'main')
144 | data.setdefault('repository_visibility', 'public')
145 | data.setdefault('cilium_loadbalancer_mode', 'dsr')
146 |
147 | # If all BGP keys are set, enable BGP
148 | bgp_keys = ['cilium_bgp_router_addr', 'cilium_bgp_router_asn', 'cilium_bgp_node_asn']
149 | bgp_enabled = all(data.get(key) for key in bgp_keys)
150 | data.setdefault('cilium_bgp_enabled', bgp_enabled)
151 |
152 | # If there is more than one node, enable spegel
153 | spegel_enabled = len(data.get('nodes')) > 1
154 | data.setdefault('spegel_enabled', spegel_enabled)
155 |
156 | return data
157 |
158 |
159 | def filters(self) -> makejinja.plugin.Filters:
160 | return [
161 | basename,
162 | nthhost
163 | ]
164 |
165 |
166 | def functions(self) -> makejinja.plugin.Functions:
167 | return [
168 | age_key,
169 | cloudflare_tunnel_id,
170 | cloudflare_tunnel_secret,
171 | github_deploy_key,
172 | github_push_token,
173 | talos_patches,
174 | current_datetime
175 | ]
176 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⛵ Cluster Template
2 |
3 | Welcome to my template designed for deploying a single Kubernetes cluster. Whether you're setting up a cluster at home on bare-metal or virtual machines (VMs), this project aims to simplify the process and make Kubernetes more accessible. This template is inspired by onedr0p [cluster-template](https://github.com/onedr0p/cluster-template) repository, providing a practical starting point for anyone interested in managing their own Kubernetes environment.
4 |
5 | At its core, this project leverages [makejinja](https://github.com/mirkolenz/makejinja), a powerful tool for rendering templates. By reading configuration files—such as [cluster.yaml](./cluster.sample.yaml) and [nodes.yaml](./nodes.sample.yaml)—Makejinja generates the necessary configurations to deploy a Kubernetes cluster with the following features:
6 |
7 | - Easy configuration through YAML files.
8 | - Compatibility with home setups, whether on physical hardware or VMs.
9 | - A modular and extensible approach to cluster deployment and management.
10 |
11 | With this approach, you'll gain a solid foundation to build and manage your Kubernetes cluster efficiently.
12 |
13 | ## ✨ Features
14 |
15 | A Kubernetes cluster deployed with [Talos Linux](https://github.com/siderolabs/talos) and an opinionated implementation of [Argo](https://github.com/argoproj/argo-cd) using [GitHub](https://github.com/) as the Git provider, [sops](https://github.com/getsops/sops) to manage secrets and [cloudflared](https://github.com/cloudflare/cloudflared) to access applications external to your local network.
16 |
17 | - **Required:** Some knowledge of [Containers](https://opencontainers.org/), [YAML](https://noyaml.com/), [Git](https://git-scm.com/), and a **Cloudflare account** with a **domain**.
18 | - **Included components:** [argo](https://github.com/argoproj/argo-cd), [cilium](https://github.com/cilium/cilium), [cert-manager](https://github.com/cert-manager/cert-manager), [spegel](https://github.com/spegel-org/spegel), [reloader](https://github.com/stakater/Reloader), [envoy-gateway](https://github.com/envoyproxy/gateway), [external-dns](https://github.com/kubernetes-sigs/external-dns) and [cloudflared](https://github.com/cloudflare/cloudflared).
19 |
20 | **Other features include:**
21 |
22 | - Dev env managed w/ [mise](https://mise.jdx.dev/)
23 | - Workflow automation w/ [GitHub Actions](https://github.com/features/actions)
24 | - Dependency automation w/ [Renovate](https://www.mend.io/renovate)
25 |
26 | Does this sound cool to you? If so, continue to read on! 👇
27 |
28 | ## 🚀 Let's Go!
29 |
30 | There are **5 stages** outlined below for completing this project, make sure you follow the stages in order.
31 |
32 | ### Stage 1: Machine Preparation
33 |
34 | > [!IMPORTANT]
35 | > If you have **3 or more nodes** it is recommended to make 3 of them controller nodes for a highly available control plane. This project configures **all nodes** to be able to run workloads. **Worker nodes** are therefore **optional**.
36 | >
37 | > **Minimum system requirements**
38 | > | Role | Cores | Memory | System Disk |
39 | > |---------|----------|---------------|---------------------------|
40 | > | Control/Worker | 4 | 16GB | 256GB SSD/NVMe |
41 |
42 | 1. Head over to the [Talos Linux Image Factory](https://factory.talos.dev) and follow the instructions. Be sure to only choose the **bare-minimum system extensions** as some might require additional configuration and prevent Talos from booting without it. You can always add system extensions after Talos is installed and working.
43 |
44 | 2. This will eventually lead you to download a Talos Linux ISO (or for SBCs a RAW) image. Make sure to note the **schematic ID** you will need this later on.
45 |
46 | 3. Flash the Talos ISO or RAW image to a USB drive and boot from it on your nodes.
47 |
48 | 4. Verify with `nmap` that your nodes are available on the network. (Replace `192.168.1.0/24` with the network your nodes are on.)
49 |
50 | ```sh
51 | nmap -Pn -n -p 50000 192.168.1.0/24 -vv | grep 'Discovered'
52 | ```
53 |
54 | ### Stage 2: Local Workstation
55 |
56 | > [!TIP]
57 | > It is recommended to set the visibility of your repository to `Public` so you can easily request help if you get stuck.
58 |
59 | 1. Create a new repository by clicking the green `Use this template` button at the top of this page, then clone the new repo you just created and `cd` into it. Alternatively you can us the [GitHub CLI](https://cli.github.com/) ...
60 |
61 | ```sh
62 | export REPONAME="home-ops"
63 | gh repo create $REPONAME --template ajaykumar4/cluster-template --disable-wiki --public --clone && cd $REPONAME
64 | ```
65 |
66 | 2. **Install** the [Mise CLI](https://mise.jdx.dev/getting-started.html#installing-mise-cli) on your workstation.
67 |
68 | 3. **Activate** Mise in your shell by following the [activation guide](https://mise.jdx.dev/getting-started.html#activate-mise).
69 |
70 | 4. Use `mise` to install the **required** CLI tools:
71 |
72 | ```sh
73 | mise trust
74 | pip install pipx
75 | mise install
76 | mise run deps
77 | ```
78 |
79 | 📍 _**Having trouble installing the tools?** Try unsetting the `GITHUB_TOKEN` env var and then run these commands again_
80 |
81 | 📍 _**Having trouble compiling Python?** Try running `mise settings python.compile=0` and then run these commands again_
82 |
83 | 5. Logout of GitHub Container Registry (GHCR) as this may cause authorization problems when using the public registry:
84 |
85 | ```sh
86 | docker logout ghcr.io
87 | helm registry logout ghcr.io
88 | ```
89 |
90 | ### Stage 3: Cloudflare configuration
91 |
92 | > [!WARNING]
93 | > If any of the commands fail with `command not found` or `unknown command` it means `mise` is either not install or configured incorrectly.
94 |
95 | 1. Create a Cloudflare API token for use with cloudflared and external-dns by reviewing the official [documentation](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) and following the instructions below.
96 |
97 | - Click the blue `Use template` button for the `Edit zone DNS` template.
98 | - Name your token `kubernetes`
99 | - Under `Permissions`, click `+ Add More` and add permissions `Zone - DNS - Edit` and `Account - Cloudflare Tunnel - Read`
100 | - Limit the permissions to a specific account and/or zone resources and then click `Continue to Summary` and then `Create Token`.
101 | - **Save this token somewhere safe**, you will need it later on.
102 |
103 | 2. Create the Cloudflare Tunnel:
104 |
105 | ```sh
106 | cloudflared tunnel login
107 | cloudflared tunnel create --credentials-file cloudflare-tunnel.json kubernetes
108 | ```
109 |
110 | ### Stage 4: Cluster configuration
111 |
112 | 1. Generate the config files from the sample files:
113 |
114 | ```sh
115 | task init
116 | ```
117 |
118 | 2. Fill out `cluster.yaml` and `nodes.yaml` configuration files using the comments in those file as a guide.
119 |
120 | 3. Template out the kubernetes and talos configuration files, if any issues come up be sure to read the error and adjust your config files accordingly.
121 |
122 | ```sh
123 | task configure
124 | ```
125 |
126 | 4. Push your changes to git:
127 |
128 | 📍 _**Verify** all the `./kubernetes/**/*.sops.*` files are **encrypted** with SOPS_
129 |
130 | ```sh
131 | git add -A
132 | git commit -m "chore: initial commit :rocket:"
133 | git push
134 | ```
135 |
136 | > [!TIP]
137 | > Using a **private repository**? Make sure to paste the public key from `github-deploy.key.pub` into the deploy keys section of your GitHub repository settings. This will make sure Argo has read/write access to your repository.
138 |
139 | ### Stage 5: Bootstrap Talos, Kubernetes, and Argo
140 |
141 | > [!WARNING]
142 | > It might take a while for the cluster to be setup (10+ minutes is normal). During which time you will see a variety of error messages like: "couldn't get current server API group list," "error: no matching resources found", etc. 'Ready' will remain "False" as no CNI is deployed yet. **This is a normal.** If this step gets interrupted, e.g. by pressing Ctrl + C, you likely will need to [reset the cluster](#-reset) before trying again
143 |
144 | 1. Install Talos:
145 |
146 | ```sh
147 | task bootstrap:talos
148 | ```
149 |
150 | 2. Push your changes to git:
151 |
152 | ```sh
153 | git add -A
154 | git commit -m "chore: add talhelper encrypted secret :lock:"
155 | git push
156 | ```
157 |
158 | 3. Install cilium, coredns, spegel, argo and sync the cluster to the repository state:
159 |
160 | ```sh
161 | task bootstrap:apps
162 | ```
163 |
164 | 4. Watch the rollout of your cluster happen:
165 |
166 | ```sh
167 | kubectl get pods --all-namespaces --watch
168 | ```
169 |
170 | ## 📣 Post installation
171 |
172 | ### ✅ Verifications
173 |
174 | 1. Check the status of Cilium:
175 |
176 | ```sh
177 | cilium status
178 | ```
179 |
180 | 2. Check the status of Argo and if the Argo resources are up-to-date and in a ready state:
181 |
182 | 📍 _Run `task reconcile` to force Argo to sync your Git repository state_
183 |
184 | ```sh
185 | argocd login argo.${cloudflare_domain} --username admin --password ${argo_password} --insecure
186 | argocd cluster list
187 | argocd repo list --output wide
188 | argocd app list -A --output wide
189 | ```
190 |
191 | 3. Check TCP connectivity to both the internal and external gateways:
192 |
193 | 📍 _The variables are only placeholders, replace them with your actual values_
194 |
195 | ```sh
196 | nmap -Pn -n -p 443 ${cluster_gateway_addr} ${cloudflare_gateway_addr} -vv
197 | ```
198 |
199 | 4. Check you can resolve DNS for `echo`, this should resolve to `${cloudflare_gateway_addr}`:
200 |
201 | 📍 _The variables are only placeholders, replace them with your actual values_
202 |
203 | ```sh
204 | dig @${cluster_dns_gateway_addr} echo.${cloudflare_domain}
205 | ```
206 |
207 | 5. Check the status of your wildcard `Certificate`:
208 |
209 | ```sh
210 | kubectl -n cert-manager describe certificates
211 | ```
212 | ### 🌐 Public DNS
213 |
214 | > [!TIP]
215 | > Use the `envoy-external` gateway on `HTTPRoutes` to make applications public to the internet. These are also accessible on your private network once you set up split DNS.
216 |
217 | The `external-dns` application created in the `network` namespace will handle creating public DNS records. By default, `echo` and the `argo` are the only subdomains reachable from the public internet. In order to make additional applications public you must **set the correct gateway** like in the HelmRelease for `echo`.
218 |
219 | ### 🏠 Home DNS
220 |
221 | > [!TIP]
222 | > Use the `envoy-internal` gateway on `HTTPRoutes` to make applications private to your network. If you're having trouble with internal DNS resolution check out [this](https://github.com/onedr0p/cluster-template/discussions/719) GitHub discussion.
223 |
224 | `k8s_gateway` will provide DNS resolution to external Kubernetes resources (i.e. points of entry to the cluster) from any device that uses your home DNS server. For this to work, your home DNS server must be configured to forward DNS queries for `${cloudflare_domain}` to `${cluster_dns_gateway_addr}` instead of the upstream DNS server(s) it normally uses. This is a form of **split DNS** (aka split-horizon DNS / conditional forwarding).
225 |
226 | _... Nothing working? That is expected, this is DNS after all!_
227 |
228 | ### 🪝 Github Webhook
229 |
230 | By default Argo will periodically check your git repository for changes. In-order to have Argo reconcile on `git push` you must configure Github to send `push` events to Argo.
231 |
232 | 1. Piece together the full URL with the webhook path appended:
233 |
234 | ```text
235 | https://argo.${cloudflare_domain}/api/webhook
236 | ```
237 |
238 | 3. Navigate to the settings of your repository on Github, under "Settings/Webhooks" press the "Add webhook" button. Fill in the webhook URL and your token from `github-push-token.txt`, Content type: `application/json`, Events: Choose Just the push event, and save.
239 |
240 | ## 💥 Reset
241 |
242 | > [!CAUTION]
243 | > **Resetting** the cluster **multiple times in a short period of time** could lead to being **rate limited by DockerHub or Let's Encrypt**.
244 |
245 | There might be a situation where you want to destroy your Kubernetes cluster. The following command will reset your nodes back to maintenance mode.
246 |
247 | ```sh
248 | task talos:reset
249 | ```
250 |
251 | ## 🛠️ Talos and Kubernetes Maintenance
252 |
253 | ### ⚙️ Updating Talos node configuration
254 |
255 | > [!TIP]
256 | > Ensure you have updated `talconfig.yaml` and any patches with your updated configuration. In some cases you **not only need to apply the configuration but also upgrade talos** to apply new configuration.
257 |
258 | ```sh
259 | # (Re)generate the Talos config
260 | task talos:generate-config
261 | # Apply the config to the node
262 | task talos:apply-node IP=? MODE=?
263 | # e.g. task talos:apply-node IP=10.10.10.10 MODE=auto
264 | ```
265 |
266 | ### ⬆️ Updating Talos and Kubernetes versions
267 |
268 | > [!TIP]
269 | > Ensure the `talosVersion` and `kubernetesVersion` in `talenv.yaml` are up-to-date with the version you wish to upgrade to.
270 |
271 | ```sh
272 | # Upgrade node to a newer Talos version
273 | task talos:upgrade-node IP=?
274 | # e.g. task talos:upgrade-node IP=10.10.10.10
275 | ```
276 |
277 | ```sh
278 | # Upgrade cluster to a newer Kubernetes version
279 | task talos:upgrade-k8s
280 | # e.g. task talos:upgrade-k8s
281 | ```
282 |
283 | ## 🤖 Renovate
284 |
285 | [Renovate](https://www.mend.io/renovate) is a tool that automates dependency management. It is designed to scan your repository around the clock and open PRs for out-of-date dependencies it finds. Common dependencies it can discover are Helm charts, container images, GitHub Actions and more! In most cases merging a PR will cause Argo to apply the update to your cluster.
286 |
287 | To enable Renovate, click the 'Configure' button over at their [Github app page](https://github.com/apps/renovate) and select your repository. Renovate creates a "Dependency Dashboard" as an issue in your repository, giving an overview of the status of all updates. The dashboard has interactive checkboxes that let you do things like advance scheduling or reattempt update PRs you closed without merging.
288 |
289 | The base Renovate configuration in your repository can be viewed at [.renovaterc.json5](.renovaterc.json5). By default it is scheduled to be active with PRs every weekend, but you can [change the schedule to anything you want](https://docs.renovatebot.com/presets-schedule), or remove it if you want Renovate to open PRs immediately.
290 |
291 | ## 🐛 Debugging
292 |
293 | Below is a general guide on trying to debug an issue with an resource or application. For example, if a workload/resource is not showing up or a pod has started but in a `CrashLoopBackOff` or `Pending` state. These steps do not include a way to fix the problem as the problem could be one of many different things.
294 |
295 | 1. Check if the Argo resources are up-to-date and in a ready state:
296 |
297 | 📍 _Run `task reconcile` to force Argo to sync your Git repository state_
298 |
299 | ```sh
300 | argocd repo list --output wide
301 | argocd app list -A --output wide
302 | ```
303 |
304 | 2. Do you see the pod of the workload you are debugging:
305 |
306 | ```sh
307 | kubectl -n get pods -o wide
308 | ```
309 |
310 | 3. Check the logs of the pod if its there:
311 |
312 | ```sh
313 | kubectl -n logs -f
314 | ```
315 |
316 | 4. If a resource exists try to describe it to see what problems it might have:
317 |
318 | ```sh
319 | kubectl -n describe
320 | ```
321 |
322 | 5. Check the namespace events:
323 |
324 | ```sh
325 | kubectl -n get events --sort-by='.metadata.creationTimestamp'
326 | ```
327 |
328 | Resolving problems that you have could take some tweaking of your YAML manifests in order to get things working, other times it could be a external factor like permissions on a NFS server. If you are unable to figure out your problem see the support sections below.
329 |
330 | ## 🧹 Tidy up
331 |
332 | Once your cluster is fully configured and you no longer need to run `task configure`, it's a good idea to clean up the repository by removing the [templates](./templates) directory and any files related to the templating process. This will help eliminate unnecessary clutter from the upstream template repository and resolve any "duplicate registry" warnings from Renovate.
333 |
334 | 1. Tidy up your repository:
335 |
336 | ```sh
337 | task template:tidy
338 | ```
339 |
340 | 2. Push your changes to git:
341 |
342 | ```sh
343 | git add -A
344 | git commit -m "chore: tidy up :broom:"
345 | git push
346 | ```
347 |
348 | ## ❔ What's next
349 |
350 | There's a lot to absorb here, especially if you're new to these tools. Take some time to familiarize yourself with the tooling and understand how all the components interconnect. Dive into the documentation of the various tools included — they are a valuable resource. This shouldn't be a production environment yet, so embrace the freedom to experiment. Move fast, break things intentionally, and challenge yourself to fix them.
351 |
352 | Below are some optional considerations you may want to explore.
353 |
354 | ### DNS
355 |
356 | The template uses [k8s_gateway](https://github.com/ori-edge/k8s_gateway) to provide DNS for your applications, consider exploring [external-dns](https://github.com/kubernetes-sigs/external-dns) as an alternative.
357 |
358 | External-DNS offers broad support for various DNS providers, including but not limited to:
359 |
360 | - [Pi-hole](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/pihole.md)
361 | - [UniFi](https://github.com/kashalls/external-dns-unifi-webhook)
362 | - [Adguard Home](https://github.com/muhlba91/external-dns-provider-adguard)
363 | - [Bind](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/rfc2136.md)
364 |
365 | This flexibility allows you to integrate seamlessly with a range of DNS solutions to suit your environment and offload DNS from your cluster to your router, or external device.
366 |
367 | ### Secrets
368 |
369 | SOPs is an excellent tool for managing secrets in a GitOps workflow. However, it can become cumbersome when rotating secrets or maintaining a single source of truth for secret items.
370 |
371 | For a more streamlined approach to those issues, consider [External Secrets](https://external-secrets.io/latest/). This tool allows you to move away from SOPs and leverage an external provider for managing your secrets. External Secrets supports a wide range of providers, from cloud-based solutions to self-hosted options.
372 |
373 | ### Storage
374 |
375 | If your workloads require persistent storage with features like replication or connectivity to NFS, SMB, or iSCSI servers, there are several projects worth exploring:
376 |
377 | - [rook-ceph](https://github.com/rook/rook)
378 | - [longhorn](https://github.com/longhorn/longhorn)
379 | - [openebs](https://github.com/openebs/openebs)
380 | - [democratic-csi](https://github.com/democratic-csi/democratic-csi)
381 | - [csi-driver-nfs](https://github.com/kubernetes-csi/csi-driver-nfs)
382 | - [csi-driver-smb](https://github.com/kubernetes-csi/csi-driver-smb)
383 | - [synology-csi](https://github.com/SynologyOpenSource/synology-csi)
384 |
385 | These tools offer a variety of solutions to meet your persistent storage needs, whether you’re using cloud-native or self-hosted infrastructures.
386 |
387 | ### Community Repositories
388 |
389 | Community member [@whazor](https://github.com/whazor) created [Kubesearch](https://kubesearch.dev) to allow searching Argo Helm Releases across Github and Gitlab repositories with the `kubesearch` topic.
390 |
391 | ## 🙋 Support
392 |
393 | ### Community
394 |
395 | - Make a post in this repository's Github [Discussions](https://github.com/ajaykumar4/cluster-template/discussions).
396 | - Start a thread in the `#support` or `#cluster-template` channels in the [Home Operations](https://discord.gg/home-operations) Discord server.
397 |
398 | ## 🙌 Related Projects
399 |
400 | If this repo is too hot to handle or too cold to hold check out these following projects.
401 |
402 | - [onedr0p/cluster-template](https://github.com/onedr0p/cluster-template) - _A template for deploying a Talos Kubernetes cluster including Flux for GitOps_
403 | - [khuedoan/homelab](https://github.com/khuedoan/homelab) - _Fully automated homelab from empty disk to running services with a single command._
404 | - [mitchross/k3s-argocd-starter](https://github.com/mitchross/k3s-argocd-starter) - starter kit for k3s, argocd
405 | - [ricsanfre/pi-cluster](https://github.com/ricsanfre/pi-cluster) - _Pi Kubernetes Cluster. Homelab kubernetes cluster automated with Ansible and FluxCD_
406 | - [techno-tim/k3s-ansible](https://github.com/techno-tim/k3s-ansible) - _The easiest way to bootstrap a self-hosted High Availability Kubernetes cluster. A fully automated HA k3s etcd install with kube-vip, MetalLB, and more. Build. Destroy. Repeat._
407 |
408 | ## ⭐ Stargazers
409 |
410 |
421 |
422 | ## 🤝 Thanks
423 |
424 | Big shout out to [onedr0p](https://github.com/onedr0p)
425 |
--------------------------------------------------------------------------------