Initial commit
Label Sync / Label Sync (push) Failing after 1m7s
E2E / reject-invalid (bad-bgp-asn) (push) Skipped
E2E / reject-invalid (bad-mac-address) (push) Skipped
E2E / reject-invalid (bad-repo-url) (push) Skipped
E2E / reject-invalid (bad-vlan-tag) (push) Skipped
E2E / reject-invalid (duplicate-gateway-addrs) (push) Skipped
E2E / reject-invalid (duplicate-node-names) (push) Skipped
E2E / reject-invalid (gateway-node-collision) (push) Skipped
E2E / reject-invalid (missing-dns-token) (push) Skipped
E2E / reject-invalid (nested-cidr-overlap) (push) Skipped
E2E / reject-invalid (node-addr-outside-cidr) (push) Skipped
E2E / reject-invalid (tunnel-without-dns) (push) Skipped
E2E / accept-valid (selfhosted) (push) Skipped
E2E / reject-invalid (missing-known-hosts) (push) Skipped
E2E / reject-invalid (missing-schematic) (push) Skipped
E2E / reject-invalid (partial-bgp) (push) Skipped
E2E / accept-valid (internal) (push) Skipped
E2E / reject-invalid (missing-external-gateway) (push) Skipped
E2E / reject-invalid (node-uses-gateway-addr) (push) Skipped
E2E / reject-invalid (non-canonical-cidr) (push) Skipped
E2E / accept-valid (private) (push) Skipped
E2E / accept-valid (single-node) (push) Skipped
E2E / reject-invalid (overlapping-cidrs) (push) Skipped
E2E / reject-invalid (reserved-node-name) (push) Skipped
E2E / reject-invalid (tiny-svc-cidr) (push) Skipped
E2E / validator-tests (push) Skipped
E2E / accept-valid (direct) (push) Skipped
E2E / accept-valid (multi-controller) (push) Skipped
E2E / accept-valid (no-webhook) (push) Skipped
E2E / accept-valid (public) (push) Skipped

This commit is contained in:
2026-09-15 22:21:43 +03:00
committed by GitHub
commit e502e0ff76
171 changed files with 7520 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
; https://editorconfig.org/
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.cue]
indent_style = tab
indent_size = 4
[*.md]
indent_size = 4
trim_trailing_whitespace = false
[*.sh]
indent_size = 4
+10
View File
@@ -0,0 +1,10 @@
* text=auto eol=lf
*.env linguist-detectable linguist-language=SHELL
*.json linguist-detectable linguist-language=JSON
*.json5 linguist-detectable linguist-language=JSON5
*.md linguist-detectable linguist-language=MARKDOWN
*.sh linguist-detectable linguist-language=SHELL
*.toml linguist-detectable linguist-language=TOML
*.yml linguist-detectable linguist-language=YAML
*.yaml linguist-detectable linguist-language=YAML
*.yaml.j2 linguist-detectable linguist-language=YAML
+9
View File
@@ -0,0 +1,9 @@
---
- name: type/digest
color: ffeC19
- name: type/patch
color: ffeC19
- name: type/minor
color: ff9800
- name: type/major
color: f6412d
+5
View File
@@ -0,0 +1,5 @@
changelog:
exclude:
authors:
- github-actions
- renovate
@@ -0,0 +1,12 @@
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: e2e-deny-server
namespace: default
spec:
endpointSelector:
matchLabels:
app: e2e-network-server
ingress:
- {}
+291
View File
@@ -0,0 +1,291 @@
#!/usr/bin/env bash
# Full-fidelity bootstrap e2e: takes maintenance-mode Talos VMs, discovers
# their hardware the same way the README instructs users to, writes a
# cluster.toml from the discovered facts, and runs the template's real
# bootstrap flow against them.
#
# Two provisioning paths share this test body:
# - CI: talosctl-cluster-action boots the nodes (talos-cluster.yaml) and
# passes E2E_CONTROLPLANE_IPS / E2E_WORKER_IPS / E2E_CIDR; the action's
# post step destroys them.
# - Local: run with no env set; the script boots and destroys the cluster
# itself. Requires Docker, /dev/kvm, passwordless sudo, qemu-system-x86,
# and the repo's mise toolchain on PATH.
#
# Renders into the working tree like any configure run.
set -euo pipefail
NAME="${E2E_NAME:-template-e2e}"
MODE="${1:-all}"
E2E_DIR=".github/template-tests/e2e"
CIDR="${E2E_CIDR:-10.9.0.0/24}"
PREFIX="${CIDR%/*}"
PREFIX="${PREFIX%.*}"
TALOSCTL="$(command -v talosctl)"
# The Image Factory vanilla schematic, matching the ISO the nodes boot from.
SCHEMATIC="376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
if [ -n "${E2E_CONTROLPLANE_IPS:-}" ]; then
PROVISIONED=true
IFS=',' read -r -a CONTROLPLANES <<< "$E2E_CONTROLPLANE_IPS"
IFS=',' read -r -a WORKERS <<< "${E2E_WORKER_IPS:-}"
else
PROVISIONED=false
CONTROLPLANES=("$PREFIX.2")
WORKERS=("$PREFIX.3")
fi
NODES=("${CONTROLPLANES[@]}" "${WORKERS[@]}")
# The VMs reach the host at the gateway address; the rendered workspace is
# served from there over git smart HTTP so Flux can sync it.
GIT_HOST="${E2E_GATEWAY:-$PREFIX.1}"
GIT_PORT=8418
GIT_SERVER_CONTAINER=""
# The provisioner runs under sudo and writes state relative to its cwd and
# TALOSCONFIG, so both are pointed at a scratch dir to keep root-owned files
# out of the repo.
if [ -n "${E2E_STATE:-}" ]; then
STATE="$E2E_STATE"
STATE_OWNED=false
else
STATE="$(mktemp -d)"
STATE_OWNED=true
fi
mkdir -p "$STATE"
GIT_PUSH_URL="http://127.0.0.1:$GIT_PORT/repo.git"
cleanup() {
rc=$?
if [ "$rc" -ne 0 ]; then
echo "==> e2e failed (rc=$rc), collecting diagnostics"
kubectl get pods --all-namespaces 2>/dev/null || true
kubectl get gitrepositories,kustomizations,helmreleases --all-namespaces 2>/dev/null || true
kubectl get events --all-namespaces --sort-by=.lastTimestamp 2>/dev/null | tail -30 || true
[ -n "$GIT_SERVER_CONTAINER" ] && docker logs --tail 5 "$GIT_SERVER_CONTAINER" 2>/dev/null || true
for ip in "${NODES[@]}"; do
talosctl -n "$ip" dmesg 2>/dev/null | tail -20 || true
done
fi
if [ "$MODE" = all ]; then
[ -n "$GIT_SERVER_CONTAINER" ] && docker stop "$GIT_SERVER_CONTAINER" >/dev/null 2>&1 || true
fi
if [ "$MODE" = all ] && [ "$PROVISIONED" = false ]; then
(cd "$STATE" && sudo -E env TALOSCONFIG="$STATE/talosconfig" \
"$TALOSCTL" cluster destroy --name "$NAME" --provisioner qemu >/dev/null 2>&1) || true
fi
if [ "$MODE" = all ] && [ "$STATE_OWNED" = true ]; then
sudo rm -rf "$STATE" || true
fi
exit "$rc"
}
trap cleanup EXIT
start_local_git_server() {
GIT_SERVER_CONTAINER="$NAME-git"
docker run --detach --rm --name "$GIT_SERVER_CONTAINER" \
--publish "$GIT_PORT:23232" \
--env SOFT_SERVE_GIT_ENABLED=false \
--env SOFT_SERVE_LFS_ENABLED=false \
--env SOFT_SERVE_SSH_LISTEN_ADDR=127.0.0.1:23231 \
--env SOFT_SERVE_STATS_ENABLED=false \
--entrypoint /bin/sh \
ghcr.io/charmbracelet/soft-serve:v0.11.6 \
-c 'set -eu; ssh-keygen -q -t ed25519 -N "" -f /tmp/admin; export SOFT_SERVE_INITIAL_ADMIN_KEYS="$(cat /tmp/admin.pub)"; /usr/local/bin/soft serve & pid=$!; until ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost settings anon-access read-write; do sleep 1; done; ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost repo create repo; wait "$pid"' \
>/dev/null
deadline=$((SECONDS + 60))
until git ls-remote "$GIT_PUSH_URL" >/dev/null 2>&1; do
if (( SECONDS >= deadline )); then
just log fatal "Soft Serve is not reachable"
fi
sleep 1
done
}
prepare() {
# In CI the action itself waits for every node's maintenance API before
# returning, so the poll here covers only the local path, where cluster
# create returns as soon as the VMs launch.
if [ "$PROVISIONED" = false ]; then
echo "==> booting maintenance-mode nodes"
(cd "$STATE" && sudo -E env TALOSCONFIG="$STATE/talosconfig" \
"$TALOSCTL" cluster create qemu --name "$NAME" --presets iso,maintenance \
--controlplanes 1 --workers 1 --cidr "$CIDR" \
--memory-controlplanes 4GiB --memory-workers 3GiB)
echo "==> waiting for the maintenance API"
for ip in "${NODES[@]}"; do
until talosctl -n "$ip" get links --insecure >/dev/null 2>&1; do sleep 5; done
done
fi
echo "==> discovering node hardware"
declare -A MACS DISKS
for ip in "${NODES[@]}"; do
MACS[$ip]="$(talosctl -n "$ip" get links --insecure -o json \
| jq -r 'select(.spec.type == "ether" and .spec.operationalState == "up" and (.metadata.id | startswith("bond") | not)) | .spec.hardwareAddr' | head -1)"
DISKS[$ip]="/dev/$(talosctl -n "$ip" get disks --insecure -o json \
| jq -r 'select(.spec.readonly == false and (.metadata.id | startswith("loop") | not)) | .metadata.id' | head -1)"
echo " $ip mac=${MACS[$ip]} disk=${DISKS[$ip]}"
done
echo "==> generating cluster.toml"
export E2E_CIDR="$CIDR"
export E2E_GATEWAY="${E2E_GATEWAY:-$PREFIX.1}"
export E2E_GIT_HOST="$GIT_HOST"
export E2E_GIT_PORT="$GIT_PORT"
export E2E_PREFIX="$PREFIX"
export E2E_SCHEMATIC="$SCHEMATIC"
envsubst '${E2E_CIDR} ${E2E_GATEWAY} ${E2E_GIT_HOST} ${E2E_GIT_PORT} ${E2E_PREFIX} ${E2E_SCHEMATIC}' \
< "$E2E_DIR/cluster.toml.tmpl" > cluster.toml
index=0
for ip in "${NODES[@]}"; do
controller=false
for cp in "${CONTROLPLANES[@]}"; do [ "$ip" = "$cp" ] && controller=true; done
export E2E_NODE_NAME="e2e-$index"
export E2E_NODE_ADDRESS="$ip"
export E2E_NODE_CONTROLLER="$controller"
export E2E_NODE_DISK="${DISKS[$ip]}"
export E2E_NODE_MAC="${MACS[$ip]}"
envsubst '${E2E_NODE_ADDRESS} ${E2E_NODE_CONTROLLER} ${E2E_NODE_DISK} ${E2E_NODE_MAC} ${E2E_NODE_NAME}' \
< "$E2E_DIR/node.toml.tmpl" >> cluster.toml
index=$((index + 1))
done
echo "==> configure"
just init
just configure
# Flux's FluxInstance only reports Ready once its Git sync succeeds, so the
# rendered kubernetes/ tree is committed to a bare repo and served to the
# cluster — the same push-then-bootstrap flow the README walks users through.
echo "==> publishing rendered repo"
mkdir -p "$STATE/gitwork"
cp -r kubernetes "$STATE/gitwork/"
git -C "$STATE/gitwork" init --quiet --initial-branch main
git -C "$STATE/gitwork" add --all
git -C "$STATE/gitwork" -c user.name=e2e -c user.email=e2e@cluster.local \
commit --quiet --message "rendered workspace"
git -C "$STATE/gitwork" push --quiet "$GIT_PUSH_URL" main
}
assert_cluster_health() {
echo "==> asserting cluster health"
kubectl wait nodes --all --for=condition=Ready --timeout=10m
for ns in kube-system cert-manager flux-system; do
kubectl wait pods --namespace "$ns" --all --for=condition=Ready --timeout=10m
done
echo "==> asserting flux reconciliation"
kubectl wait fluxinstance/flux --namespace flux-system --for=condition=Ready --timeout=10m
kubectl wait gitrepositories --all --all-namespaces --for=condition=Ready --timeout=5m
kubectl wait kustomizations --all --all-namespaces --for=condition=Ready --timeout=10m
kubectl wait helmreleases --all --all-namespaces --for=condition=Ready --timeout=10m
}
foundation() {
deadline=$((SECONDS + 60))
until git ls-remote "http://$GIT_HOST:$GIT_PORT/repo.git" >/dev/null 2>&1; do
if (( SECONDS >= deadline )); then
just log fatal "Rendered repository server is not reachable"
fi
sleep 1
done
echo "==> bootstrap talos"
just bootstrap talos
echo "==> bootstrap apps"
just bootstrap apps
assert_cluster_health
echo "==> asserting bootstrap idempotency"
just configure
just bootstrap talos
just bootstrap apps
assert_cluster_health
}
flux_sops() {
echo "==> asserting Flux SOPS decryption"
SOPS_SECRET="$STATE/gitwork/kubernetes/apps/default/e2e-sops.sops.yaml"
export E2E_SOPS_VALUE=flux-decrypted
envsubst '${E2E_SOPS_VALUE}' < "$E2E_DIR/sops-secret.yaml.tmpl" > "$SOPS_SECRET"
sops encrypt --filename-override kubernetes/apps/default/e2e-sops.sops.yaml \
--in-place "$SOPS_SECRET"
yq --inplace '.resources += ["./e2e-sops.sops.yaml"]' \
"$STATE/gitwork/kubernetes/apps/default/kustomization.yaml"
git -C "$STATE/gitwork" add --all
git -C "$STATE/gitwork" -c user.name=e2e -c user.email=e2e@cluster.local \
commit --quiet --message "test Flux SOPS decryption"
git -C "$STATE/gitwork" push --quiet "$GIT_PUSH_URL" main
flux reconcile kustomization cluster-apps --with-source --timeout=10m
test "$(kubectl get secret e2e-sops --namespace default \
--output jsonpath='{.data.value}' | base64 --decode)" = "flux-decrypted"
}
networking() {
echo "==> asserting pod networking and DNS"
export E2E_CONTROLPLANE_NODE="$(kubectl get nodes \
--selector=node-role.kubernetes.io/control-plane \
--output jsonpath='{.items[0].metadata.name}')"
export E2E_WORKER_NODE="$(kubectl get nodes \
--selector='!node-role.kubernetes.io/control-plane' \
--output jsonpath='{.items[0].metadata.name}')"
NETWORK_CONFIG="$STATE/network.yaml"
envsubst '${E2E_CONTROLPLANE_NODE} ${E2E_WORKER_NODE}' \
< "$E2E_DIR/network.yaml.tmpl" > "$NETWORK_CONFIG"
kubectl apply --filename "$NETWORK_CONFIG"
kubectl wait pods/e2e-network-server pods/e2e-network-client \
--namespace default --for=condition=Ready --timeout=5m
SERVER_IP="$(kubectl get pod e2e-network-server --namespace default \
--output jsonpath='{.status.podIP}')"
kubectl exec --namespace default e2e-network-client -- \
/agnhost connect --timeout=10s "$SERVER_IP:8080"
kubectl exec --namespace default e2e-network-client -- \
/agnhost connect --timeout=10s e2e-network-server.default.svc.cluster.local:8080
kubectl exec --namespace default e2e-network-client -- \
/agnhost connect --timeout=10s github.com:443
kubectl apply --filename "$E2E_DIR/cilium-network-policy.yaml"
deadline=$((SECONDS + 60))
while kubectl exec --namespace default e2e-network-client -- \
/agnhost connect --timeout=2s "$SERVER_IP:8080" &>/dev/null; do
if (( SECONDS >= deadline )); then
just log fatal "CiliumNetworkPolicy did not block pod traffic"
fi
sleep 2
done
kubectl delete ciliumnetworkpolicy e2e-deny-server --namespace default
deadline=$((SECONDS + 60))
until kubectl exec --namespace default e2e-network-client -- \
/agnhost connect --timeout=2s "$SERVER_IP:8080" &>/dev/null; do
if (( SECONDS >= deadline )); then
just log fatal "Pod traffic did not recover after removing CiliumNetworkPolicy"
fi
sleep 2
done
}
summary() {
kubectl get nodes --output wide
kubectl get kustomizations,helmreleases --all-namespaces
echo "==> e2e bootstrap succeeded"
}
case "$MODE" in
prepare) prepare ;;
foundation) foundation ;;
flux-sops) flux_sops ;;
networking) networking ;;
summary) summary ;;
all)
start_local_git_server
prepare
foundation
flux_sops
networking
summary
;;
*)
echo "usage: $0 {prepare|foundation|flux-sops|networking|summary|all}" >&2
exit 2
;;
esac
@@ -0,0 +1,22 @@
[network]
node_cidr = "${E2E_CIDR}"
default_gateway = "${E2E_GATEWAY}"
[kubernetes.api]
addr = "${E2E_PREFIX}.100"
[gateways]
internal = "${E2E_PREFIX}.101"
dns = "${E2E_PREFIX}.102"
[domain]
name = "e2e.example.com"
[dns]
provider = "none"
[repository]
url = "http://${E2E_GIT_HOST}:${E2E_GIT_PORT}/repo.git"
[talos]
schematic_id = "${E2E_SCHEMATIC}"
@@ -0,0 +1,42 @@
---
apiVersion: v1
kind: Pod
metadata:
name: e2e-network-server
namespace: default
labels:
app: e2e-network-server
spec:
nodeName: "${E2E_WORKER_NODE}"
containers:
- name: server
image: registry.k8s.io/e2e-test-images/agnhost:2.66.0
args: ["netexec", "--http-port=8080"]
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: e2e-network-server
namespace: default
spec:
selector:
app: e2e-network-server
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Pod
metadata:
name: e2e-network-client
namespace: default
spec:
nodeName: "${E2E_CONTROLPLANE_NODE}"
containers:
- name: client
image: registry.k8s.io/e2e-test-images/agnhost:2.66.0
args: ["pause"]
@@ -0,0 +1,7 @@
[[nodes]]
name = "${E2E_NODE_NAME}"
address = "${E2E_NODE_ADDRESS}"
controller = ${E2E_NODE_CONTROLLER}
disk = "${E2E_NODE_DISK}"
mac_addr = "${E2E_NODE_MAC}"
@@ -0,0 +1,8 @@
---
apiVersion: v1
kind: Secret
metadata:
name: e2e-sops
namespace: default
stringData:
value: "${E2E_SOPS_VALUE}"
@@ -0,0 +1,21 @@
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/home-operations/talosctl-cluster-action/main/schema/talos-cluster.json
# Maintenance-mode nodes for the bootstrap e2e: the action boots and destroys
# them, and cluster.sh exercises the template's real bootstrap flow against
# the unconfigured nodes.
apiVersion: v1alpha1
kind: TalosCluster
metadata:
name: template-e2e
spec:
controlplanes:
count: 1
memory: 4GiB
workers:
count: 1
memory: 3GiB
network:
cidr: 10.9.0.0/24
qemu:
presets: [iso, maintenance]
disks: [virtio:10GiB]
@@ -0,0 +1,34 @@
# Negative fixture: BGP router ASN above the 32-bit ASN range.
# Expected to be rejected by _router_asn_in_range.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[cilium.bgp]
router_addr = "10.10.1.1"
router_asn = "4294967296"
node_asn = "64514"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: malformed MAC address (uppercase + missing colons).
# Expected to be rejected by the #Node.mac_addr regex constraint.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "AABBCCDDEEFF"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,30 @@
# Negative fixture: scp-style git URL ("git@host:owner/repo.git") instead of
# the canonical https:// or ssh://git@ form.
# Expected to be rejected by the repository.url pattern.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "git@github.com:onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,30 @@
# Negative fixture: vlan_tag outside the valid 1-4094 range.
# Expected to be rejected by _vlan_tag_in_range.
[network]
node_cidr = "10.10.10.0/24"
vlan_tag = "5000"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: gateways.internal == gateways.dns.
# Expected to be rejected by `_addrs_check` (list.UniqueItems).
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.252"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,37 @@
# Negative fixture: two nodes with the same name.
# Expected to be rejected by `_node_name_check` (list.UniqueItems).
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-0"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: a node reuses the internal gateway VIP.
# Expected to be rejected by _addr_uniqueness_check.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.252"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: dns.provider "cloudflare" (the default) without a token.
# Expected to be rejected by the Dns validator.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[domain]
name = "example.com"
[dns]
provider = "cloudflare"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,28 @@
# Negative fixture: cloudflare-tunnel ingress without gateways.external.
# Expected to be rejected by the Config cross-check.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
[domain]
name = "example.com"
[dns]
token = "fake"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,30 @@
# Negative fixture: ssh:// URL to a host without bundled SSH host keys
# (github.com/gitlab.com/codeberg.org) and no repository.known_hosts set.
# Expected to be rejected by the conditional known_hosts constraint.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "ssh://git@git.example.com/k8s/home-ops.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,28 @@
# Negative fixture: a node without schematic_id and no [talos] default.
# Expected to be rejected by the schematic resolution in Config.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[domain]
name = "example.com"
[dns]
token = "fake"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
@@ -0,0 +1,30 @@
# Negative fixture: node_cidr is nested inside the default pod_cidr
# (10.42.0.0/16) without being string-equal to it.
# Expected to be rejected by _cidr_overlap_check.
[network]
node_cidr = "10.42.128.0/17"
[kubernetes.api]
addr = "10.42.128.254"
[gateways]
internal = "10.42.128.252"
dns = "10.42.128.253"
external = "10.42.128.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.42.128.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: node address is not inside network.node_cidr.
# Expected to be rejected by _node_addrs_in_node_cidr.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "192.168.1.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,30 @@
# Negative fixture: a node claims the default gateway address (defaults to
# the first IP in node_cidr).
# Expected to be rejected by _addr_uniqueness_check.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.1"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,30 @@
# Negative fixture: node_cidr written with host bits set instead of the
# network address.
# Expected to be rejected by _cidr_canonical_check.
[network]
node_cidr = "10.10.10.5/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: network.node_cidr overlaps the default kubernetes.pod_cidr (10.42.0.0/16).
# Expected to be rejected by `_cidrs_check` (list.UniqueItems).
[network]
node_cidr = "10.42.0.0/16"
[kubernetes.api]
addr = "10.42.0.254"
[gateways]
internal = "10.42.0.252"
dns = "10.42.0.253"
external = "10.42.0.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.42.0.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,34 @@
# Negative fixture: two of the three BGP fields set; previously this
# silently left BGP disabled.
# Expected to be rejected by the Bgp all-or-nothing check.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[domain]
name = "example.com"
[dns]
token = "fake"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[cilium.bgp]
router_addr = "10.10.1.1"
router_asn = "64513"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,29 @@
# Negative fixture: a node name uses the reserved word "controller".
# Expected to be rejected by the #Node.name regex constraint.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "controller"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,33 @@
# Negative fixture: svc_cidr too small to contain the derived CoreDNS
# address (10th IP).
# Expected to be rejected by _coredns_addr_in_svc_cidr.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes]
svc_cidr = "10.43.0.0/29"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,32 @@
# Negative fixture: cloudflare-tunnel ingress with dns.provider "none".
# Expected to be rejected by the Config cross-check.
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[domain]
name = "example.com"
[dns]
provider = "none"
[ingress]
mode = "cloudflare-tunnel"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
+38
View File
@@ -0,0 +1,38 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[domain]
name = "example.com"
[dns]
token = "fake"
[ingress]
mode = "direct"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,34 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
[domain]
name = "example.com"
[dns]
provider = "none"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,42 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
[repository]
url = "ssh://git@github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
provider = "none"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-2"
address = "10.10.10.102"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:02"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,36 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
webhook_provider = "none"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
+39
View File
@@ -0,0 +1,39 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "ssh://git@github.com/onedr0p/cluster-template.git"
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
mtu = 1500
secureboot = true
encrypt_disk = true
kernel_modules = ["nvidia", "nvidia_uvm"]
+59
View File
@@ -0,0 +1,59 @@
[network]
node_cidr = "10.10.10.0/24"
default_gateway = "10.10.10.1"
vlan_tag = "100"
dns_servers = ["1.1.1.1"]
ntp_servers = ["162.159.200.123"]
[kubernetes]
pod_cidr = "10.42.0.0/16"
svc_cidr = "10.43.0.0/16"
[kubernetes.api]
addr = "10.10.10.254"
tls_sans = ["example.com"]
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
branch = "main"
[domain]
name = "example.com"
[dns]
token = "fake"
[cilium]
loadbalancer_mode = "dsr"
[cilium.bgp]
router_addr = "10.10.1.1"
router_asn = "64513"
node_asn = "64514"
[talos]
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
mtu = 1500
secureboot = true
encrypt_disk = true
kernel_modules = ["nvidia", "nvidia_uvm"]
@@ -0,0 +1,39 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
external = "10.10.10.251"
[repository]
url = "ssh://git@git.example.com/k8s/home-ops.git"
webhook_provider = "generic-hmac"
known_hosts = """
git.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
"""
[domain]
name = "example.com"
[dns]
token = "fake"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
[[nodes]]
name = "k8s-1"
address = "10.10.10.101"
controller = false
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:01"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
@@ -0,0 +1,26 @@
[network]
node_cidr = "10.10.10.0/24"
[kubernetes.api]
addr = "10.10.10.254"
[gateways]
internal = "10.10.10.252"
dns = "10.10.10.253"
[domain]
name = "example.com"
[dns]
provider = "none"
[repository]
url = "https://github.com/onedr0p/cluster-template.git"
[[nodes]]
name = "k8s-0"
address = "10.10.10.100"
controller = true
disk = "/dev/sdfake"
mac_addr = "00:00:00:00:00:00"
schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"
+61
View File
@@ -0,0 +1,61 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: "Flate"
on:
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
filter:
name: Flate - Filter
runs-on: ubuntu-latest
outputs:
changed-files: ${{ steps.changed-files.outputs.changed_files }}
steps:
- name: Get Changed Files
id: changed-files
uses: bjw-s-labs/action-changed-files@a9a36fb08ce06db9b02fbd8026cc2c0945eb9841 # v0.6.0
with:
patterns: kubernetes/**/*
flate:
if: ${{ needs.filter.outputs.changed-files != '[]' }}
needs: filter
name: Flate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
- name: Install Flate
uses: home-operations/flate/action@631b76b69c4e58c6f4d1cb01e23616fa61aebafa # v0.6.5
- name: Run Flate
id: flate
run: flate test all -p ./kubernetes/flux/cluster
success:
if: ${{ !cancelled() }}
needs: flate
name: Flate - Success
runs-on: ubuntu-latest
steps:
- name: Any jobs failed?
if: ${{ contains(needs.*.result, 'failure') }}
run: exit 1
- name: All jobs passed or skipped?
if: ${{ !(contains(needs.*.result, 'failure')) }}
run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}"
+32
View File
@@ -0,0 +1,32 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: "Label Sync"
on:
workflow_dispatch:
push:
branches:
- main
paths:
- .github/labels.yaml
permissions: {}
jobs:
label-sync:
name: Label Sync
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Sync Labels
uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
with:
config-file: .github/labels.yaml
delete-other-labels: true
+103
View File
@@ -0,0 +1,103 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: "E2E Cluster"
on:
workflow_dispatch:
pull_request:
branches: ["main"]
schedule:
- cron: "30 5 * * *"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
bootstrap:
if: ${{ github.repository == 'onedr0p/cluster-template' }}
name: bootstrap (qemu)
runs-on: ubuntu-latest
permissions:
contents: read
services:
git:
image: ghcr.io/charmbracelet/soft-serve:v0.12.2
env:
SOFT_SERVE_GIT_ENABLED: "false"
SOFT_SERVE_LFS_ENABLED: "false"
SOFT_SERVE_SSH_LISTEN_ADDR: "127.0.0.1:23231"
SOFT_SERVE_STATS_ENABLED: "false"
ports:
- 8418:23232
entrypoint: /bin/sh
command: >-
-c "set -eu; ssh-keygen -q -t ed25519 -N '' -f /tmp/admin;
export SOFT_SERVE_INITIAL_ADMIN_KEYS=$(cat /tmp/admin.pub);
/usr/local/bin/soft serve & pid=$!;
until ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost settings anon-access read-write; do sleep 1; done;
ssh -q -i /tmp/admin -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -p 23231 localhost repo create repo;
wait $pid"
options: >-
--health-cmd "git ls-remote http://localhost:23232/repo.git"
--health-interval 2s
--health-timeout 2s
--health-retries 30
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install QEMU
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends gettext-base qemu-system-x86 qemu-utils ovmf
- name: Setup mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Boot maintenance-mode nodes
id: cluster
uses: home-operations/talosctl-cluster-action@fb6a31bf5de43218acc80d2e23958a16eee7380c # v0.2.2
with:
config: ./.github/template-tests/e2e/talos-cluster.yaml
cache: true
- name: Export cluster environment
env:
CONTROLPLANE_IPS: "${{ steps.cluster.outputs.controlplane-ips }}"
GATEWAY: "${{ steps.cluster.outputs.gateway }}"
WORKER_IPS: "${{ steps.cluster.outputs.worker-ips }}"
run: |
echo "E2E_CONTROLPLANE_IPS=$CONTROLPLANE_IPS" >> "$GITHUB_ENV"
echo "E2E_WORKER_IPS=$WORKER_IPS" >> "$GITHUB_ENV"
echo "E2E_GATEWAY=$GATEWAY" >> "$GITHUB_ENV"
echo "E2E_CIDR=10.9.0.0/24" >> "$GITHUB_ENV"
echo "E2E_STATE=$RUNNER_TEMP/template-e2e" >> "$GITHUB_ENV"
- name: Prepare cluster
run: bash ./.github/template-tests/e2e/cluster.sh prepare
- name: Build healthy cluster foundation
run: bash ./.github/template-tests/e2e/cluster.sh foundation
- name: Test Flux SOPS
id: flux-sops
run: bash ./.github/template-tests/e2e/cluster.sh flux-sops
background: true
- name: Test networking
id: networking
run: bash ./.github/template-tests/e2e/cluster.sh networking
background: true
- name: Wait for E2E tests
wait: [flux-sops, networking]
- name: Summarize cluster
run: bash ./.github/template-tests/e2e/cluster.sh summary
+176
View File
@@ -0,0 +1,176 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: "E2E"
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
validate-invalid:
if: ${{ github.repository == 'onedr0p/cluster-template' }}
name: reject-invalid (${{ matrix.fixture }})
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
fixture:
- overlapping-cidrs
- nested-cidr-overlap
- non-canonical-cidr
- tiny-svc-cidr
- duplicate-gateway-addrs
- duplicate-node-names
- reserved-node-name
- bad-mac-address
- bad-repo-url
- missing-known-hosts
- node-addr-outside-cidr
- node-uses-gateway-addr
- gateway-node-collision
- bad-vlan-tag
- bad-bgp-asn
- missing-dns-token
- tunnel-without-dns
- missing-external-gateway
- missing-schematic
- partial-bgp
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Assert schema rejects ${{ matrix.fixture }}.toml
run: |
fixture=./.github/template-tests/invalid/${{ matrix.fixture }}.toml
if uv run --quiet --locked --no-dev ./template/scripts/validate.py "$fixture" >/dev/null 2>&1; then
echo "::error::schema accepted invalid fixture ${{ matrix.fixture }} (expected rejection)"
exit 1
fi
echo "schema correctly rejected ${{ matrix.fixture }}"
# Also surface the actual error message in the log for debuggability.
uv run --quiet --locked --no-dev ./template/scripts/validate.py "$fixture" || true
validator-tests:
if: ${{ github.repository == 'onedr0p/cluster-template' }}
name: validator-tests
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Run validator tests
run: uv run --quiet --locked pytest ./template/scripts/test_validate.py
validate-valid:
if: ${{ github.repository == 'onedr0p/cluster-template' }}
name: accept-valid (${{ matrix.fixture }})
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
fixture:
- public
- private
- selfhosted
- no-webhook
- internal
- direct
- single-node
- multi-controller
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup mise
uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
with:
experimental: true
install_args: --locked
- name: Run init recipe
run: just init
- name: Prepare files
run: |
cp ./.github/template-tests/valid/${{ matrix.fixture }}.toml cluster.toml
echo '{"AccountTag":"fake","TunnelSecret":"fake","TunnelID":"fake"}' > cloudflare-tunnel.json
touch kubeconfig
- name: Assert cluster.toml passes the JSON Schema
run: taplo check --schema "file://$PWD/cluster.schema.json" ./cluster.toml
- name: Run configure recipe
run: just configure
# Rendered output must already match the format-yaml pre-commit hook,
# otherwise every `just configure` shows up as formatting churn.
- name: Assert rendered output is formatted
run: oxfmt --check ./.sops.yaml ./bootstrap ./kubernetes ./talos
- name: Install flate
uses: home-operations/flate/action@631b76b69c4e58c6f4d1cb01e23616fa61aebafa # v0.6.5
with:
base: ""
- name: Run flate test
run: flate test all -p ./kubernetes/flux/cluster
- name: Render bootstrap helmfile charts
run: just template test-helmfile
- name: Dry run bootstrap talos recipe
run: just --dry-run bootstrap talos
- name: Create talos secret
run: just bootstrap talos-secret
- name: Render talos configs
run: just talos render
- name: Validate talos configs
run: |
for config in ./talos/rendered/*.yaml; do
talosctl validate --config "$config" --mode metal
done
- name: Dry run bootstrap apps recipe
run: just --dry-run bootstrap apps
- name: Run reset recipe
run: yes | just template reset
- name: Run tidy recipe
run: yes | just template tidy
+58
View File
@@ -0,0 +1,58 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: "Release"
on:
workflow_dispatch:
schedule:
- cron: 0 0 1 * *
permissions: {}
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Get Previous Release Tag and Determine Next Tag
id: determine-next-tag
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 1,
});
let previousTag = "0.0.0"; // Default if no previous release exists
if (releases.length > 0) {
previousTag = releases[0].tag_name;
}
const [previousMajor, previousMinor, previousPatch] = previousTag.split('.').map(Number);
const currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth() + 1; // Months are 0-indexed in JavaScript
const nextMajorMinor = `${currentYear}.${currentMonth}`;
let nextPatch;
if (`${previousMajor}.${previousMinor}` === nextMajorMinor) {
console.log("Month release already exists for the year. Incrementing patch number by 1.");
nextPatch = previousPatch + 1;
} else {
console.log("Month release does not exist for the year. Starting with patch number 0.");
nextPatch = 0;
}
return `${nextMajorMinor}.${nextPatch}`;
- name: Create Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.determine-next-tag.outputs.result }}
run: gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --generate-notes
+25
View File
@@ -0,0 +1,25 @@
# Secrets
*.pub
*.key
*.decrypted~*.yaml
/age.key
/cloudflare-tunnel.json
/deploy.key
/deploy.key.pub
/flux-webhook-token.txt
# Template config files
/cluster.toml
# Kubernetes
kubeconfig
talosconfig
# Python
__pycache__/
*.py[cod]
# Misc.
.claude/
.private/
.venv/
.DS_Store
Thumbs.db
/talos/output/
/talos/rendered/
+36
View File
@@ -0,0 +1,36 @@
[pre-commit]
parallel = true
skip = ["merge", "rebase"]
[pre-commit.commands.format-just]
glob = ["justfile", "*.just", ".justfile"]
run = 'for f in {staged_files}; do just --justfile "$f" --fmt; done'
stage_fixed = true
[pre-commit.commands.format-mise]
glob = [".mise.toml", ".mise/config.toml", ".mise/conf.d/*.toml"]
run = "mise fmt"
stage_fixed = true
[pre-commit.commands.format-json]
glob = ["*.json", "*.json5", "*.jsonc"]
run = "oxfmt {staged_files}"
stage_fixed = true
[pre-commit.commands.format-markdown]
glob = ["*.md", "*.markdown", "*.mdx"]
run = "oxfmt {staged_files}"
stage_fixed = true
[pre-commit.commands.format-yaml]
glob = ["*.yaml", "*.yml"]
run = "oxfmt {staged_files}"
stage_fixed = true
[pre-commit.commands.mise-lock]
glob = [".mise.toml", ".mise/config.toml", ".mise/conf.d/*.toml"]
run = "mise lock && git add .mise/mise.lock"
[pre-commit.commands.zizmor]
glob = [".github/workflows/*.yaml", ".github/actions/**/action.yaml"]
run = "zizmor --offline {staged_files}"
+5
View File
@@ -0,0 +1,5 @@
# Tools only the template step needs; `just template tidy` archives this file.
[tools]
uv = "0.12.13"
sd = "1.1.0"
taplo = "0.10.0"
+35
View File
@@ -0,0 +1,35 @@
min_version = "2026.7.0"
[env]
KUBECONFIG = "{{config_root}}/kubeconfig"
SOPS_CONFIG = "{{config_root}}/.sops.yaml"
SOPS_AGE_KEY_FILE = "{{config_root}}/age.key"
TALOSCONFIG = "{{config_root}}/talos/talosconfig"
[tools]
age = "1.3.2"
cloudflared = "2026.9.1"
flux2 = "2.9.5"
gh = "2.100.0"
gum = "2.0.1"
helm = "4.3.0"
helmfile = "1.7.4"
jq = "1.8.2"
just = "1.58.0"
kubeconform = "0.8.0"
kubectl = "1.37.0"
kustomize = "5.8.1"
lefthook = "2.1.12"
node = "24.21.0"
oxfmt = "0.67.0"
sops = "3.13.3"
talosctl = "1.14.0"
"github:postfinance/topf" = "v0.6.0"
yq = "4.53.6"
zizmor = "1.30.1"
[settings]
lockfile_platforms = ["linux-x64", "linux-arm64", "macos-arm64", "macos-x64"]
[hooks]
postinstall = "lefthook install"
+577
View File
@@ -0,0 +1,577 @@
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
[[tools.age]]
version = "1.3.2"
backend = "aqua:FiloSottile/age"
[tools.age."platforms.linux-arm64"]
checksum = "sha256:6b8dc4333c53a5a57c9e5834e3a48f92605d7154014cd07269ff3327db5d37f4"
url = "https://github.com/FiloSottile/age/releases/download/v1.3.2/age-v1.3.2-linux-arm64.tar.gz"
url_api = "https://api.github.com/repos/FiloSottile/age/releases/assets/535541932"
provenance = "github-attestations"
[tools.age."platforms.linux-x64"]
checksum = "sha256:cbe24006683f8eb669266162894b9a522a1af52f2665fbc63a4bb032ed26ac10"
url = "https://github.com/FiloSottile/age/releases/download/v1.3.2/age-v1.3.2-linux-amd64.tar.gz"
url_api = "https://api.github.com/repos/FiloSottile/age/releases/assets/535541903"
provenance = "github-attestations"
[tools.age."platforms.macos-arm64"]
checksum = "sha256:e2020b073c44f692685a24d6abc378817eb81ffaaf49fd0531ef8565f767f2f5"
url = "https://github.com/FiloSottile/age/releases/download/v1.3.2/age-v1.3.2-darwin-arm64.tar.gz"
url_api = "https://api.github.com/repos/FiloSottile/age/releases/assets/535541897"
provenance = "github-attestations"
[tools.age."platforms.macos-x64"]
checksum = "sha256:1d1e4bc66e1427edad7739ae7616157de0e79db8b6d2a1497d7d9925fb06a539"
url = "https://github.com/FiloSottile/age/releases/download/v1.3.2/age-v1.3.2-darwin-amd64.tar.gz"
url_api = "https://api.github.com/repos/FiloSottile/age/releases/assets/535541887"
provenance = "github-attestations"
[[tools.cloudflared]]
version = "2026.9.1"
backend = "aqua:cloudflare/cloudflared"
[tools.cloudflared."platforms.linux-arm64"]
checksum = "sha256:3d97437c71848bd8df68041e12436b484a661d95073ea1937f01a845ce88faa3"
url = "https://github.com/cloudflare/cloudflared/releases/download/2026.9.1/cloudflared-linux-arm64"
url_api = "https://api.github.com/repos/cloudflare/cloudflared/releases/assets/557330419"
[tools.cloudflared."platforms.linux-x64"]
checksum = "sha256:03f1f25d1cc93b9ad6c60569d44060bc4f17ed97075760ed8cfca4b12dcd68cc"
url = "https://github.com/cloudflare/cloudflared/releases/download/2026.9.1/cloudflared-linux-amd64"
url_api = "https://api.github.com/repos/cloudflare/cloudflared/releases/assets/557330171"
[tools.cloudflared."platforms.macos-arm64"]
checksum = "sha256:c27ab8fd0aa489449e3d201eb02f957ef460a13b613662928b1b23394bf1bcfe"
url = "https://github.com/cloudflare/cloudflared/releases/download/2026.9.1/cloudflared-darwin-arm64.tgz"
url_api = "https://api.github.com/repos/cloudflare/cloudflared/releases/assets/557329375"
[tools.cloudflared."platforms.macos-x64"]
checksum = "sha256:ff0d3b51d5ff70eceef89d6b32145fee985018a2174596a5dbe405e2766e2ac4"
url = "https://github.com/cloudflare/cloudflared/releases/download/2026.9.1/cloudflared-darwin-amd64.tgz"
url_api = "https://api.github.com/repos/cloudflare/cloudflared/releases/assets/557330132"
[[tools.flux2]]
version = "2.9.5"
backend = "aqua:fluxcd/flux2"
[tools.flux2."platforms.linux-arm64"]
checksum = "sha256:f3e159af616ec0b9bd0a405c2185cf09d06b74652c1de3c7f377e8166826651a"
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/flux_2.9.5_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/fluxcd/flux2/releases/assets/538282890"
[tools.flux2."platforms.linux-arm64".provenance.slsa]
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/provenance.intoto.jsonl"
[tools.flux2."platforms.linux-x64"]
checksum = "sha256:b853df82adfd7736f580692f9f734473d571606307139f8fd20c2a80dd1ff473"
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/flux_2.9.5_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/fluxcd/flux2/releases/assets/538282891"
[tools.flux2."platforms.linux-x64".provenance.slsa]
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/provenance.intoto.jsonl"
[tools.flux2."platforms.macos-arm64"]
checksum = "sha256:2869ef7151a6f1b27e6b5d2a6804f3ef23c7bdaa06a74e00d3fe5bfc646547fd"
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/flux_2.9.5_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/fluxcd/flux2/releases/assets/538282876"
[tools.flux2."platforms.macos-arm64".provenance.slsa]
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/provenance.intoto.jsonl"
[tools.flux2."platforms.macos-x64"]
checksum = "sha256:5748583cf5da035ca2d751190d2c15f5f656d305c166ec28a312a2f3b6799e31"
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/flux_2.9.5_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/fluxcd/flux2/releases/assets/538282892"
[tools.flux2."platforms.macos-x64".provenance.slsa]
url = "https://github.com/fluxcd/flux2/releases/download/v2.9.5/provenance.intoto.jsonl"
[[tools.gh]]
version = "2.100.0"
backend = "aqua:cli/cli"
[tools.gh."platforms.linux-arm64"]
checksum = "sha256:ea4e7a581a32ccad6cc7923cb1576ac5859ba4b9a16ab22eb8f8a96e78e2e961"
url = "https://github.com/cli/cli/releases/download/v2.100.0/gh_2.100.0_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/cli/cli/releases/assets/542974279"
provenance = "github-attestations"
[tools.gh."platforms.linux-x64"]
checksum = "sha256:e4d4bb4498e8d007abe545b6568926793ace1b6447da598294a610018cb164be"
url = "https://github.com/cli/cli/releases/download/v2.100.0/gh_2.100.0_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/cli/cli/releases/assets/542974264"
provenance = "github-attestations"
[tools.gh."platforms.macos-arm64"]
checksum = "sha256:45f9a62da2f6e641a7fad57e2ce39656dfd7ef331372d80a2a2aed65abb01642"
url = "https://github.com/cli/cli/releases/download/v2.100.0/gh_2.100.0_macOS_arm64.zip"
url_api = "https://api.github.com/repos/cli/cli/releases/assets/542974295"
provenance = "github-attestations"
[tools.gh."platforms.macos-x64"]
checksum = "sha256:fcd7799e85eb575f3c7d2b1679bfbfedaefa1269d4bc7d096b51e10939b4812b"
url = "https://github.com/cli/cli/releases/download/v2.100.0/gh_2.100.0_macOS_amd64.zip"
url_api = "https://api.github.com/repos/cli/cli/releases/assets/542974293"
provenance = "github-attestations"
[[tools."github:postfinance/topf"]]
version = "0.6.0"
backend = "github:postfinance/topf"
[tools."github:postfinance/topf"."platforms.linux-arm64"]
checksum = "sha256:7e5d4bf21f07ba91b83c4653eb65dd3cf5ee67aa8fa4131ca403b38791476367"
url = "https://github.com/postfinance/topf/releases/download/v0.6.0/topf_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/postfinance/topf/releases/assets/542955869"
[tools."github:postfinance/topf"."platforms.linux-x64"]
checksum = "sha256:458df4b25f4181a31ed361c0592194f7eb9e6e7b13e7096f8745453afdeaadfb"
url = "https://github.com/postfinance/topf/releases/download/v0.6.0/topf_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/postfinance/topf/releases/assets/542955866"
[tools."github:postfinance/topf"."platforms.macos-arm64"]
checksum = "sha256:48b22175d61eadba0c287411c34a90f73dbaa716fd792b7b28625d3edbf870e2"
url = "https://github.com/postfinance/topf/releases/download/v0.6.0/topf_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/postfinance/topf/releases/assets/542955870"
[tools."github:postfinance/topf"."platforms.macos-x64"]
checksum = "sha256:b4c76fb5985c2e0c73d6a365a5ae7a45f97f9d61cb502a01b1ee82e63da0b1cd"
url = "https://github.com/postfinance/topf/releases/download/v0.6.0/topf_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/postfinance/topf/releases/assets/542955865"
[[tools.gum]]
version = "2.0.1"
backend = "aqua:charmbracelet/gum"
[tools.gum."platforms.linux-arm64"]
checksum = "sha256:6998202a8fea27bb2007f69e44ec5dcb4cff5268c62d995de857eee0e2cd52cb"
url = "https://github.com/charmbracelet/gum/releases/download/v2.0.1/gum_2.0.1_Linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/charmbracelet/gum/releases/assets/556562997"
provenance = "cosign"
[tools.gum."platforms.linux-x64"]
checksum = "sha256:4dfe4547f960813864c803b3617aa64427fa32ca566707fde949e08975297c48"
url = "https://github.com/charmbracelet/gum/releases/download/v2.0.1/gum_2.0.1_Linux_x86_64.tar.gz"
url_api = "https://api.github.com/repos/charmbracelet/gum/releases/assets/556563001"
provenance = "cosign"
[tools.gum."platforms.macos-arm64"]
checksum = "sha256:994662daab6fcfe9dcfc57d87ca42bcae2948d86fa864f9010a3848fa9bb7d6e"
url = "https://github.com/charmbracelet/gum/releases/download/v2.0.1/gum_2.0.1_Darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/charmbracelet/gum/releases/assets/556563007"
provenance = "cosign"
[tools.gum."platforms.macos-x64"]
checksum = "sha256:4d125b60fbaa28ef1674bb16f3859ed4e813acb027a79d24603b598ea4a0b71f"
url = "https://github.com/charmbracelet/gum/releases/download/v2.0.1/gum_2.0.1_Darwin_x86_64.tar.gz"
url_api = "https://api.github.com/repos/charmbracelet/gum/releases/assets/556562991"
provenance = "cosign"
[[tools.helm]]
version = "4.3.0"
backend = "aqua:helm/helm"
[tools.helm."platforms.linux-arm64"]
checksum = "sha256:31c5794dd55c66a51e6b7d2e2ac7a114ae8b1de41ff1d9ba51748ac973b06a08"
url = "https://get.helm.sh/helm-v4.3.0-linux-arm64.tar.gz"
[tools.helm."platforms.linux-x64"]
checksum = "sha256:86584a54def73570558f66f5111cc53dfed56689637ae32c1201205d494f54fb"
url = "https://get.helm.sh/helm-v4.3.0-linux-amd64.tar.gz"
[tools.helm."platforms.macos-arm64"]
checksum = "sha256:d3870437e1e95b67f8edbde964156c84a26503f560821d40c542441658934fba"
url = "https://get.helm.sh/helm-v4.3.0-darwin-arm64.tar.gz"
[tools.helm."platforms.macos-x64"]
checksum = "sha256:347a784877e0e20eac865e8d1c36a80f6bb0861d6f29abd34defb6570ef95d92"
url = "https://get.helm.sh/helm-v4.3.0-darwin-amd64.tar.gz"
[[tools.helmfile]]
version = "1.7.4"
backend = "aqua:helmfile/helmfile"
[tools.helmfile."platforms.linux-arm64"]
checksum = "sha256:0292f57a4638a21e775b0ce8bde37ee3fdd65dbbdbe0e5b9a06718f1dd04c7fa"
url = "https://github.com/helmfile/helmfile/releases/download/v1.7.4/helmfile_1.7.4_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/helmfile/helmfile/releases/assets/516813268"
[tools.helmfile."platforms.linux-x64"]
checksum = "sha256:f96ef0a015df06b29d7f38bf0ca08821018ae25eb96bf2c7abd3affa1b84e112"
url = "https://github.com/helmfile/helmfile/releases/download/v1.7.4/helmfile_1.7.4_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/helmfile/helmfile/releases/assets/516813296"
[tools.helmfile."platforms.macos-arm64"]
checksum = "sha256:e1490d371fecc1f2d9aae914ef34058f3cb2363c9ee63350b37a85c3994d71d9"
url = "https://github.com/helmfile/helmfile/releases/download/v1.7.4/helmfile_1.7.4_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/helmfile/helmfile/releases/assets/516813297"
[tools.helmfile."platforms.macos-x64"]
checksum = "sha256:7a0951fcc5bb991d7ea3a0c80e35754eb3981dcadba19795edbec7cba2518ae2"
url = "https://github.com/helmfile/helmfile/releases/download/v1.7.4/helmfile_1.7.4_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/helmfile/helmfile/releases/assets/516813301"
[[tools.jq]]
version = "1.8.2"
backend = "aqua:jqlang/jq"
[tools.jq."platforms.linux-arm64"]
checksum = "sha256:8b85c817833814ddca00a144c33705546355afccf0cf39b188f3cdb48b852309"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-arm64"
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012756"
provenance = "github-attestations"
[tools.jq."platforms.linux-x64"]
checksum = "sha256:b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64"
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012752"
provenance = "github-attestations"
[tools.jq."platforms.macos-arm64"]
checksum = "sha256:2d75340ba57a4b4b4c8708a21c2dc8e958a48aaa8bba13b27f77f6e4c0eca07e"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-macos-arm64"
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012783"
provenance = "github-attestations"
[tools.jq."platforms.macos-x64"]
checksum = "sha256:e94b266e3c26690550006abe63152b782280f4e14374accdf04cbde844f00bc0"
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-macos-amd64"
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012782"
provenance = "github-attestations"
[[tools.just]]
version = "1.58.0"
backend = "aqua:casey/just"
[tools.just."platforms.linux-arm64"]
checksum = "sha256:748237128c4c40cbdabc65e841d05ceba13cc23a91eaba395495894c1d9764df"
url = "https://github.com/casey/just/releases/download/1.58.0/just-1.58.0-aarch64-unknown-linux-musl.tar.gz"
url_api = "https://api.github.com/repos/casey/just/releases/assets/500510099"
[tools.just."platforms.linux-x64"]
checksum = "sha256:4a5cc2f53e6f0f8c59092a6cc38291eb729d46a7dd95d3ae582008881b84931d"
url = "https://github.com/casey/just/releases/download/1.58.0/just-1.58.0-x86_64-unknown-linux-musl.tar.gz"
url_api = "https://api.github.com/repos/casey/just/releases/assets/500509978"
[tools.just."platforms.macos-arm64"]
checksum = "sha256:50ae3e996c974a0bf32ea7d10f495070df33f1b43e0616b2769e3d4821ed8f48"
url = "https://github.com/casey/just/releases/download/1.58.0/just-1.58.0-aarch64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/casey/just/releases/assets/500509965"
[tools.just."platforms.macos-x64"]
checksum = "sha256:9a09cfef66aaa79da58203970103a0684307716caaabd3e9844cacc4dc0f4023"
url = "https://github.com/casey/just/releases/download/1.58.0/just-1.58.0-x86_64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/casey/just/releases/assets/500510084"
[[tools.kubeconform]]
version = "0.8.0"
backend = "aqua:yannh/kubeconform"
[tools.kubeconform."platforms.linux-arm64"]
checksum = "sha256:1f53fc8e81258197a35e8603054162a5af1de8c5af13746c71ab680d9534ed87"
url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-arm64.tar.gz"
url_api = "https://api.github.com/repos/yannh/kubeconform/releases/assets/438612685"
[tools.kubeconform."platforms.linux-x64"]
checksum = "sha256:9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883"
url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-amd64.tar.gz"
url_api = "https://api.github.com/repos/yannh/kubeconform/releases/assets/438612666"
[tools.kubeconform."platforms.macos-arm64"]
checksum = "sha256:f84f4dfbebf4a6b0b230385fa065a39ea35e02608c2b50d025dcf64775a69d67"
url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-darwin-arm64.tar.gz"
url_api = "https://api.github.com/repos/yannh/kubeconform/releases/assets/438612680"
[tools.kubeconform."platforms.macos-x64"]
checksum = "sha256:71dbc87ac9f24099a62b93570e65aa06312ba6ac8aea63b7f86e9d999edf5a92"
url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-darwin-amd64.tar.gz"
url_api = "https://api.github.com/repos/yannh/kubeconform/releases/assets/438612679"
[[tools.kubectl]]
version = "1.37.0"
backend = "aqua:kubernetes/kubernetes/kubectl"
[tools.kubectl."platforms.linux-arm64"]
checksum = "sha256:922df28df248cc00a9e025f947704f1d1482de64ece54cfe57e61f19eaf1eef3"
url = "https://dl.k8s.io/v1.37.0/bin/linux/arm64/kubectl"
[tools.kubectl."platforms.linux-x64"]
checksum = "sha256:6129359f4e1f3848a5572ccb0b26cf28b8ca08cef38c95a765b2f64a2c961a2f"
url = "https://dl.k8s.io/v1.37.0/bin/linux/amd64/kubectl"
[tools.kubectl."platforms.macos-arm64"]
checksum = "sha256:583beedaebe422e71d3f1a96acef8b1fef86ea2f09a45ad01aa6c9ce287c1380"
url = "https://dl.k8s.io/v1.37.0/bin/darwin/arm64/kubectl"
[tools.kubectl."platforms.macos-x64"]
checksum = "sha256:d5276c0f4fde77fc446070290f345944a7f1fda153df6b960e5fde93b7a9bccd"
url = "https://dl.k8s.io/v1.37.0/bin/darwin/amd64/kubectl"
[[tools.kustomize]]
version = "5.8.1"
backend = "aqua:kubernetes-sigs/kustomize"
[tools.kustomize."platforms.linux-arm64"]
checksum = "sha256:0953ea3e476f66d6ddfcd911d750f5167b9365aa9491b2326398e289fef2c142"
url = "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v5.8.1/kustomize_v5.8.1_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/kubernetes-sigs/kustomize/releases/assets/353160971"
[tools.kustomize."platforms.linux-x64"]
checksum = "sha256:029a7f0f4e1932c52a0476cf02a0fd855c0bb85694b82c338fc648dcb53a819d"
url = "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v5.8.1/kustomize_v5.8.1_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/kubernetes-sigs/kustomize/releases/assets/353160972"
[tools.kustomize."platforms.macos-arm64"]
checksum = "sha256:8886f8a78474e608cc81234f729fda188a9767da23e28925802f00ece2bab288"
url = "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v5.8.1/kustomize_v5.8.1_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/kubernetes-sigs/kustomize/releases/assets/353160975"
[tools.kustomize."platforms.macos-x64"]
checksum = "sha256:ee7cf0c1e3592aa7bb66ba82b359933a95e7f2e0b36e5f53ed0a4535b017f2f8"
url = "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v5.8.1/kustomize_v5.8.1_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/kubernetes-sigs/kustomize/releases/assets/353160974"
[[tools.lefthook]]
version = "2.1.12"
backend = "aqua:evilmartians/lefthook"
[tools.lefthook."platforms.linux-arm64"]
checksum = "sha256:d96ac16753f6d3d69b92098621c2a2427522a9c2c3e78cd7c9b4a9790b0b8f17"
url = "https://github.com/evilmartians/lefthook/releases/download/v2.1.12/lefthook_2.1.12_Linux_aarch64.gz"
url_api = "https://api.github.com/repos/evilmartians/lefthook/releases/assets/533627819"
provenance = "github-attestations"
[tools.lefthook."platforms.linux-x64"]
checksum = "sha256:dad908593c859d139b886c14913a44401d95288aa7642d9bdf6f3bd36bd788ff"
url = "https://github.com/evilmartians/lefthook/releases/download/v2.1.12/lefthook_2.1.12_Linux_x86_64.gz"
url_api = "https://api.github.com/repos/evilmartians/lefthook/releases/assets/533627780"
provenance = "github-attestations"
[tools.lefthook."platforms.macos-arm64"]
checksum = "sha256:f23eac328ca50c7b775dc24dc275cdeacb65e16cf8bd1745d18e3c563f704f94"
url = "https://github.com/evilmartians/lefthook/releases/download/v2.1.12/lefthook_2.1.12_MacOS_arm64.gz"
url_api = "https://api.github.com/repos/evilmartians/lefthook/releases/assets/533627758"
provenance = "github-attestations"
[tools.lefthook."platforms.macos-x64"]
checksum = "sha256:83c8591d338b1b789944480d9effe2807adc903cf27e4a16ab013e39d7723c31"
url = "https://github.com/evilmartians/lefthook/releases/download/v2.1.12/lefthook_2.1.12_MacOS_x86_64.gz"
url_api = "https://api.github.com/repos/evilmartians/lefthook/releases/assets/533627775"
provenance = "github-attestations"
[[tools.node]]
version = "24.21.0"
backend = "core:node"
[tools.node."platforms.linux-arm64"]
checksum = "sha256:724282c3b43aec998aa9527380465b45d229e021b58035f5f4f63095eabfe5d5"
url = "https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-arm64.tar.gz"
[tools.node."platforms.linux-x64"]
checksum = "sha256:6e1db87ef58b8819e5d5402eff1536491b18edd8eb7bee5ef7897876e88dc5ff"
url = "https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.gz"
[tools.node."platforms.macos-arm64"]
checksum = "sha256:bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057"
url = "https://nodejs.org/dist/v24.21.0/node-v24.21.0-darwin-arm64.tar.gz"
[tools.node."platforms.macos-x64"]
checksum = "sha256:1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097"
url = "https://nodejs.org/dist/v24.21.0/node-v24.21.0-darwin-x64.tar.gz"
[[tools.oxfmt]]
version = "0.67.0"
backend = "npm:oxfmt"
[[tools.sd]]
version = "1.1.0"
backend = "aqua:chmln/sd"
[tools.sd."platforms.linux-arm64"]
checksum = "sha256:ec8c93c0533ff21f4851d11566808d4082544baf063d9b96ea77c27e98b7cd99"
url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-aarch64-unknown-linux-musl.tar.gz"
url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325535"
[tools.sd."platforms.linux-x64"]
checksum = "sha256:3613eca74cd686739bb5a6d68319aa56c747e7315274d02323a2ca2b1c5d82d2"
url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-x86_64-unknown-linux-gnu.tar.gz"
url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325296"
[tools.sd."platforms.macos-arm64"]
checksum = "sha256:4bd3c09226376ca0a1d69589c91e86276fae36c5fbaaee669afce583f6682030"
url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-aarch64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325451"
[tools.sd."platforms.macos-x64"]
checksum = "sha256:1fca1e9c91813a8aac6821063c923107ba0f66a83309e095edcd3b202f67f97e"
url = "https://github.com/chmln/sd/releases/download/v1.1.0/sd-v1.1.0-x86_64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/chmln/sd/releases/assets/362325430"
[[tools.sops]]
version = "3.13.3"
backend = "aqua:getsops/sops"
[tools.sops."platforms.linux-arm64"]
checksum = "sha256:53b0abacd38ef1b12a66d6c100956691b9cefce018d91f81e73ddf7438b94d77"
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.linux.arm64"
url_api = "https://api.github.com/repos/getsops/sops/releases/assets/486808229"
[tools.sops."platforms.linux-arm64".provenance.slsa]
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.intoto.jsonl"
[tools.sops."platforms.linux-x64"]
checksum = "sha256:e5bec3346a873ae91d871550f3e698c1aad962aff462a080e40f25fde17fef6b"
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.linux.amd64"
url_api = "https://api.github.com/repos/getsops/sops/releases/assets/486808201"
[tools.sops."platforms.linux-x64".provenance.slsa]
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.intoto.jsonl"
[tools.sops."platforms.macos-arm64"]
checksum = "sha256:b97c0d434aab577dc40310e8d22ff9e45eef4c80638ab978daae9b4681c59286"
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.darwin.arm64"
url_api = "https://api.github.com/repos/getsops/sops/releases/assets/486808198"
[tools.sops."platforms.macos-arm64".provenance.slsa]
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.intoto.jsonl"
[tools.sops."platforms.macos-x64"]
checksum = "sha256:42162d5cef10b74fcf80a045a70e658d7ce6e63d6ea1be6f347e44015714468d"
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.darwin.amd64"
url_api = "https://api.github.com/repos/getsops/sops/releases/assets/486808228"
[tools.sops."platforms.macos-x64".provenance.slsa]
url = "https://github.com/getsops/sops/releases/download/v3.13.3/sops-v3.13.3.intoto.jsonl"
[[tools.talosctl]]
version = "1.14.0"
backend = "aqua:siderolabs/talos"
[tools.talosctl."platforms.linux-arm64"]
checksum = "sha256:19615e1d0eb222de86ec2f1487e7d6e74f5171a9038e73aeacde8cc647e3d9e0"
url = "https://github.com/siderolabs/talos/releases/download/v1.14.0/talosctl-linux-arm64"
url_api = "https://api.github.com/repos/siderolabs/talos/releases/assets/542359013"
provenance = "cosign"
[tools.talosctl."platforms.linux-x64"]
checksum = "sha256:2c147c4a99d124c95bd5c190fe054e0b3c93495f2243fd652ebd423adb8377c7"
url = "https://github.com/siderolabs/talos/releases/download/v1.14.0/talosctl-linux-amd64"
url_api = "https://api.github.com/repos/siderolabs/talos/releases/assets/542359015"
provenance = "cosign"
[tools.talosctl."platforms.macos-arm64"]
checksum = "sha256:f0c65a0e970b6f23cf0160e432e7496ba93957a49f369780d4e53888ed66ef46"
url = "https://github.com/siderolabs/talos/releases/download/v1.14.0/talosctl-darwin-arm64"
url_api = "https://api.github.com/repos/siderolabs/talos/releases/assets/542359001"
provenance = "cosign"
[tools.talosctl."platforms.macos-x64"]
checksum = "sha256:6563baa43774ef5c351e0d9f2fd720941c482da01fe3aed53ee9644b552453c5"
url = "https://github.com/siderolabs/talos/releases/download/v1.14.0/talosctl-darwin-amd64"
url_api = "https://api.github.com/repos/siderolabs/talos/releases/assets/542359004"
provenance = "cosign"
[[tools.taplo]]
version = "0.10.0"
backend = "aqua:tamasfe/taplo"
[tools.taplo."platforms.linux-arm64"]
url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-linux-aarch64.gz"
url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257322597"
[tools.taplo."platforms.linux-x64"]
url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-linux-x86_64.gz"
url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257322600"
[tools.taplo."platforms.macos-arm64"]
url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-darwin-aarch64.gz"
url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257323110"
[tools.taplo."platforms.macos-x64"]
url = "https://github.com/tamasfe/taplo/releases/download/0.10.0/taplo-darwin-x86_64.gz"
url_api = "https://api.github.com/repos/tamasfe/taplo/releases/assets/257323116"
[[tools.uv]]
version = "0.12.13"
backend = "aqua:astral-sh/uv"
[tools.uv."platforms.linux-arm64"]
checksum = "sha256:2eaa5d94f5db7b3a1a092156b9420459e42ab0217d917fe74a876309cef9b5e9"
url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-aarch64-unknown-linux-gnu.tar.gz"
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670186"
provenance = "github-attestations"
[tools.uv."platforms.linux-x64"]
checksum = "sha256:745765a3b6e360ad76743599ae5c42e9278c7edf8bbff9fc76d05bf2623a04dd"
url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-x86_64-unknown-linux-gnu.tar.gz"
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670375"
provenance = "github-attestations"
[tools.uv."platforms.macos-arm64"]
checksum = "sha256:7e6ddb9316acc00f2296c82ff4d99977870ee34b2f0ddcae9444d714db9364ed"
url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-aarch64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670160"
provenance = "github-attestations"
[tools.uv."platforms.macos-x64"]
checksum = "sha256:5e287ef61cb6a9b61b3a83fef124fd143e400468a7dac794230147a810e17119"
url = "https://github.com/astral-sh/uv/releases/download/0.12.13/uv-x86_64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/555670348"
provenance = "github-attestations"
[[tools.yq]]
version = "4.53.6"
backend = "aqua:mikefarah/yq"
[tools.yq."platforms.linux-arm64"]
checksum = "sha256:88a1016bc1d657375a35864e4f44b6f333df8ff97b559f51bba0adcb2169df09"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.6/yq_linux_arm64"
url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/522028007"
provenance = "cosign"
[tools.yq."platforms.linux-x64"]
checksum = "sha256:c5f056448f973ae7d39b5401949648a78f2dc1947d6a8eb65be60d5c504b9385"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.6/yq_linux_amd64"
url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/522028022"
provenance = "cosign"
[tools.yq."platforms.macos-arm64"]
checksum = "sha256:cceb0b8d71ea5294334121f8429f33f92b920e7217d904a2f9f35443968ac424"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.6/yq_darwin_arm64"
url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/522028033"
provenance = "cosign"
[tools.yq."platforms.macos-x64"]
checksum = "sha256:caa513cb04f3804b34d4752f0e0d7904fecb9e7cf1d34081289f83259319a7f6"
url = "https://github.com/mikefarah/yq/releases/download/v4.53.6/yq_darwin_amd64"
url_api = "https://api.github.com/repos/mikefarah/yq/releases/assets/522028031"
provenance = "cosign"
[[tools.zizmor]]
version = "1.30.1"
backend = "aqua:zizmorcore/zizmor"
[tools.zizmor."platforms.linux-arm64"]
checksum = "sha256:7ff1dce33bdd18fd2a4affe63bdd47efcccca97b2cec1c1863ec26e9e2647540"
url = "https://github.com/zizmorcore/zizmor/releases/download/v1.30.1/zizmor-aarch64-unknown-linux-gnu.tar.gz"
url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/552067643"
provenance = "github-attestations"
[tools.zizmor."platforms.linux-x64"]
checksum = "sha256:e65324f4430c2717591937edcec90ccbefaf14c174f8ec9415e03ca875b46e1a"
url = "https://github.com/zizmorcore/zizmor/releases/download/v1.30.1/zizmor-x86_64-unknown-linux-gnu.tar.gz"
url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/552067642"
provenance = "github-attestations"
[tools.zizmor."platforms.macos-arm64"]
checksum = "sha256:e28d22b087f9ebb8d99da6e740d348c930f559961c7c3f12badda54f882195a2"
url = "https://github.com/zizmorcore/zizmor/releases/download/v1.30.1/zizmor-aarch64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/552067640"
provenance = "github-attestations"
[tools.zizmor."platforms.macos-x64"]
checksum = "sha256:10e6b18b11ea07e515a16f0f0518c7b07527bc9977c1fd5698181ce7f3554202"
url = "https://github.com/zizmorcore/zizmor/releases/download/v1.30.1/zizmor-x86_64-apple-darwin.tar.gz"
url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/552067641"
provenance = "github-attestations"
+3
View File
@@ -0,0 +1,3 @@
{
"printWidth": 100
}
+79
View File
@@ -0,0 +1,79 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
extends: ["github>home-operations/renovate-presets#8.1.0"],
schedule: ["every weekend"],
lockFileMaintenance: {
enabled: true,
schedule: ["before 3am on the first day of the month"],
commitMessageExtra: "({{manager}})",
additionalBranchPrefix: "{{manager}}-",
},
mise: {
managerFilePatterns: ["/^\\.mise/conf\\.d/[^/]+\\.toml$/"],
},
packageRules: [
{
description: "GitHub Actions Group",
matchManagers: ["github-actions"],
groupName: "github-actions",
minimumReleaseAge: "3 days",
},
{
description: "Mise Tools Group",
matchManagers: ["mise"],
groupName: "mise tools",
minimumReleaseAge: "3 days",
},
{
description: "Talos Group",
groupName: "talos",
matchPackageNames: ["siderolabs/talos"],
group: {
commitMessageTopic: "{{{groupName}}} group",
},
minimumGroupSize: 2,
},
{
description: "Flux Operator Group",
groupName: "flux-operator",
matchDatasources: ["docker"],
matchPackageNames: ["/flux-operator/", "/flux-instance/"],
group: {
commitMessageTopic: "{{{groupName}}} group",
},
minimumGroupSize: 2,
},
{
matchUpdateTypes: ["major"],
addLabels: ["type/major"],
},
{
matchUpdateTypes: ["minor"],
addLabels: ["type/minor"],
},
{
matchUpdateTypes: ["patch"],
addLabels: ["type/patch"],
},
{
matchUpdateTypes: ["digest"],
addLabels: ["type/digest"],
},
{
matchDatasources: ["docker"],
addLabels: ["renovate/container"],
},
{
matchDatasources: ["helm"],
addLabels: ["renovate/helm"],
},
{
matchManagers: ["github-actions"],
addLabels: ["renovate/github-action"],
},
{
matchDatasources: ["github-releases"],
addLabels: ["renovate/github-release"],
},
],
}
+9
View File
@@ -0,0 +1,9 @@
{
"recommendations": [
"blueglassblock.better-json5",
"irongeek.vscode-env",
"redhat.vscode-yaml",
"signageos.signageos-vscode-sops",
"hverlin.mise-vscode"
]
}
+18
View File
@@ -0,0 +1,18 @@
{
"editor.bracketPairColorization.enabled": true,
"files.associations": {
"**/*.json5": "json5"
},
"files.trimTrailingWhitespace": true,
"sops.defaults.ageKeyFile": "age.key",
"vs-kubernetes": {
"vs-kubernetes.kubeconfig": "./kubeconfig",
"vs-kubernetes.knownKubeconfigs": [
"./kubeconfig"
]
},
"yaml.schemaStore.enable": true,
"yaml.schemas": {
"kubernetes": "./kubernetes/**/*.yaml"
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 onedr0p
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+474
View File
@@ -0,0 +1,474 @@
# ⛵ Cluster Template
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 my personal [home-ops](https://github.com/onedr0p/home-ops) repository, providing a practical starting point for anyone interested in managing their own Kubernetes environment.
At its core, this project leverages [makejinja](https://github.com/mirkolenz/makejinja), a powerful tool for rendering templates. By reading the [cluster.toml](./cluster.sample.toml) configuration file—validated and defaulted by [pydantic](https://docs.pydantic.dev/)—Makejinja generates the necessary configurations to deploy a Kubernetes cluster with the following features:
- Easy configuration through a single TOML file.
- Compatibility with home setups, whether on physical hardware or VMs.
- A modular and extensible approach to cluster deployment and management.
With this approach, you'll gain a solid foundation to build and manage your Kubernetes cluster efficiently.
## ✨ Features
A Kubernetes cluster deployed with [Talos Linux](https://github.com/siderolabs/talos) and an opinionated implementation of [Flux](https://github.com/fluxcd/flux2) syncing from the Git provider of your choice (GitHub, GitLab, Gitea, Forgejo, Codeberg or self-hosted), [sops](https://github.com/getsops/sops) to manage secrets and [cloudflared](https://github.com/cloudflare/cloudflared) to access applications external to your local network.
- **Required:** Some knowledge of [Containers](https://opencontainers.org/), [YAML](https://noyaml.com/), [Git](https://git-scm.com/), and a **domain**. Exposing apps to the public internet requires a **Cloudflare account**; internal-only clusters don't.
- **Included components:** [flux](https://github.com/fluxcd/flux2), [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).
**Other features include:**
- Dev env managed w/ [mise](https://mise.jdx.dev/)
- Workflow automation w/ [GitHub Actions](https://github.com/features/actions)
- Dependency automation w/ [Renovate](https://www.mend.io/renovate)
- Flux `HelmRelease` and `Kustomization` diffs w/ [flate](https://github.com/home-operations/flate)
Does this sound cool to you? If so, continue to read on! 👇
## 🚀 Let's Go!
There are **6 stages** outlined below for completing this project, make sure you follow the stages in order.
### Stage 1: Hardware Configuration
For a **stable** and **high-availability** production Kubernetes cluster, hardware selection is critical. NVMe/SSDs are strongly preferred over HDDs, and **Bare Metal is strongly recommended** over virtualized platforms like Proxmox.
Using **enterprise NVMe or SATA SSDs on Bare Metal** (even used drives) provides the most reliable performance and rock-solid stability. Consumer **NVMe or SATA SSDs**, on the other hand, carry risks such as latency spikes, corruption, and fsync delays, particularly in multi-node setups.
**Proxmox with enterprise drives can work** for testing or carefully tuned production clusters, but it introduces additional layers of potential I/O contention — especially if consumer drives are used. Any **replicated storage** (e.g., Rook-Ceph, Longhorn) should always use **dedicated disks separate from control plane and etcd nodes** to ensure reliability. Worker nodes are more flexible, but risky configurations should still be avoided for stateful workloads to maintain cluster stability.
These guidelines provide a strong baseline, but there are always exceptions and nuances. The best way to ensure your hardware configuration works is to **test it thoroughly and benchmark performance** under realistic workloads.
### Stage 2: Machine Preparation
> [!IMPORTANT]
> 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**.
>
> **Minimum system requirements**
>
> | Role | Cores | Memory | System Disk |
> | -------------- | ----- | ------ | -------------- |
> | Control/Worker | 4 | 16GB | 256GB SSD/NVMe |
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. Depending on your CPU start with the Intel/AMD system extensions (`i915`, `intel-ucode` & `mei` **or** `amdgpu` & `amd-ucode`), you can always add system extensions after Talos is installed and working.
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.
3. Flash the Talos ISO or RAW image to a USB drive and boot from it on your nodes.
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.)
```sh
nmap -Pn -n -p 50000 192.168.1.0/24 -vv | grep 'Discovered'
```
### Stage 3: Local Workstation
> [!TIP]
> It is recommended to set the visibility of your repository to `Public` so you can easily request help if you get stuck.
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 use the [GitHub CLI](https://cli.github.com/) ...
```sh
export REPONAME="home-ops"
gh repo create $REPONAME --template onedr0p/cluster-template --public --clone
cd $REPONAME
```
📍 _**Not using GitHub?** Any Git provider works (GitLab, Gitea, Forgejo, Codeberg or self-hosted). Create an empty repository on your provider, download this template with `git clone --depth 1 https://github.com/onedr0p/cluster-template`, re-initialize it with `git init` and push it to your repository._
2. **Install** the [Mise CLI](https://mise.jdx.dev/getting-started.html#installing-mise-cli) on your local workstation.
3. **Activate** Mise in your shell by following the [activation guide](https://mise.jdx.dev/getting-started.html#activate-mise).
4. Use `mise` to install the **required** CLI tools:
```sh
mise trust
mise install
```
📍 _**Having trouble installing the tools?** Try unsetting the `GITHUB_TOKEN` env var and then run these commands again_
📍 _**Platforms:** `.mise/mise.lock` pins tool downloads for the platforms listed under `lockfile_platforms` in `.mise/config.toml`: Linux and macOS on amd64 and arm64 (`linux-x64`, `linux-arm64`, `macos-x64`, `macos-arm64`). If you also need musl (e.g. Alpine) or Windows, add the platform to that list (`linux-x64-musl`, `linux-arm64-musl`, `windows-x64`), run `mise lock`, and commit both files. Your own platform is always locked, even when it is not in the list._
5. Logout of the GitHub Container Registry as this may cause authorization problems in future steps when using the public registry:
```sh
docker logout ghcr.io
helm registry logout ghcr.io
```
### Stage 4: Cloudflare configuration
> [!TIP]
> **Internal-only cluster?** Set `provider = "none"` under `[dns]` in `cluster.toml` and skip this stage entirely: no Cloudflare account, API token, or `cloudflare-tunnel.json` is needed. Nothing is exposed to the internet, apps are reachable on your LAN via the internal gateway, and the wildcard certificate is issued by an in-cluster self-signed CA instead of Let's Encrypt.
> [!WARNING]
> If any of the commands fail with `command not found` or `unknown command` it means `mise` is either not installed, activated or it could be configured incorrectly.
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.
- Click the blue `Use template` button for the `Edit zone DNS` template.
- Name your token `kubernetes`
- Under `Permissions`, click `+ Add More` and add permissions `Zone - DNS - Edit` and `Account - Cloudflare Tunnel - Read`
- Limit the permissions to a specific account and/or zone resources and then click `Continue to Summary` and then `Create Token`.
- **Save this token somewhere safe**, you will need it later on.
2. Create the Cloudflare Tunnel:
```sh
cloudflared tunnel login
cloudflared tunnel create --credentials-file cloudflare-tunnel.json kubernetes
```
📍 _**Prefer port-forwarding over a tunnel?** Set `mode = "direct"` under `[ingress]` in `cluster.toml` and skip this step: no `cloudflare-tunnel.json` is needed. Instead, forward TCP 443 (and optionally 80) on your router to the `gateways.external` IP, and create an `external.<domain>` DNS record yourself pointing at your WAN address (an A record, or a CNAME to a DDNS hostname). Per-app records are still published automatically._
### Stage 5: Cluster configuration
1. Generate the config files from the sample files:
```sh
just init
```
2. Fill out the `cluster.toml` configuration file using the comments in it as a guide. Editors with TOML schema support (VS Code's Even Better TOML, taplo in Neovim) pick up the `#:schema` directive at the top of the file and provide completion and inline validation.
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.
```sh
just configure
```
4. Push your changes to git:
📍 _**Verify** all the `./bootstrap/**/*.sops.*`, `./kubernetes/**/*.sops.*` and `./talos/secrets.sops.yaml` files are **encrypted** with SOPS_
```sh
git add -A
git commit -m "chore: initial commit :rocket:"
git push
```
> [!TIP]
> Using a **private repository** (an `ssh://` URL in `cluster.toml`)? Make sure to paste the public key from `deploy.key.pub` into the deploy keys section of your repository settings (GitHub: `Settings/Deploy keys`, GitLab: `Settings/Repository/Deploy keys`, Gitea/Forgejo: `Settings/Deploy keys`). This will make sure Flux has read/write access to your repository.
### Stage 6: Bootstrap Talos, Kubernetes, and Flux
> [!WARNING]
> 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 normal.** If this step gets interrupted, e.g. by pressing <kbd>Ctrl</kbd> + <kbd>C</kbd>, you likely will need to [reset the cluster](#-reset) before trying again
1. Install Talos:
```sh
just bootstrap talos
```
2. Install cilium, coredns, spegel, flux and sync the cluster to the repository state:
```sh
just bootstrap apps
```
3. Watch the rollout of your cluster happen:
```sh
kubectl get pods --all-namespaces --watch
```
## 📣 Post installation
### ✅ Verifications
1. Check the status of Cilium:
```sh
kubectl -n kube-system exec ds/cilium --container cilium-agent -- cilium status
```
2. Check the status of Flux and if the Flux resources are up-to-date and in a ready state:
📍 _Run `just kube reconcile` to force Flux to sync your Git repository state_
```sh
flux check
flux get sources git flux-system
flux get ks -A
flux get hr -A
```
3. Check TCP connectivity to both the internal and external gateways:
📍 _The variables are only placeholders, replace them with your actual values_
```sh
nmap -Pn -n -p 443 ${gateways_internal} ${gateways_external} -vv
```
4. Check you can resolve DNS for `echo`, this should resolve to `${gateways_external}`:
📍 _The variables are only placeholders, replace them with your actual values_
```sh
dig @${gateways_dns} echo.${cloudflare_domain}
```
5. Check the status of your wildcard `Certificate`:
```sh
kubectl -n network describe certificates
```
### 🌐 Public DNS
> [!TIP]
> 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.
The `external-dns` application created in the `network` namespace will handle creating public DNS records. By default, `echo` and the `flux-webhook` 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`.
### 🏠 Home DNS
> [!TIP]
> 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.
`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 `${gateways_dns}` instead of the upstream DNS server(s) it normally uses. This is a form of **split DNS** (aka split-horizon DNS / conditional forwarding).
_... Nothing working? That is expected, this is DNS after all!_
### 🪝 Git Webhook
By default Flux will periodically check your git repository for changes. In-order to have Flux reconcile on `git push` you must configure your Git provider to send `push` events to Flux.
📍 _Don't want a webhook, or your Git provider can't reach the cluster? Set `webhook_provider = "none"` in `cluster.toml` and skip this section; Flux will keep polling on an interval._
1. Obtain the webhook path:
📍 _Hook id and path should look like `/hook/12ebd1e363c641dc3c2e430ecf3cee2b3c7a5ac9e1234506f6f5f3ce1230e123`_
```sh
kubectl -n flux-system get receiver flux-webhook --output=jsonpath='{.status.webhookPath}'
```
2. Piece together the full URL with the webhook path appended:
```text
https://flux-webhook.${cloudflare_domain}/hook/12ebd1e363c641dc3c2e430ecf3cee2b3c7a5ac9e1234506f6f5f3ce1230e123
```
3. Navigate to your repository settings and add a webhook with that URL and the secret token from `flux-webhook-token.txt`:
- **GitHub**: under "Settings/Webhooks" press the "Add webhook" button. Fill in the webhook URL, paste the token as the secret, Content type: `application/json`, Events: Choose Just the push event, and save.
- **GitLab**: under "Settings/Webhooks" fill in the webhook URL, paste the token as the secret token, check the push events trigger, and save. Also set `webhook_provider = "gitlab"` in `cluster.toml`.
- **Gitea/Forgejo**: under "Settings/Webhooks" add a **Gitea/Forgejo** webhook with the webhook URL, method `POST`, content type `application/json`, paste the token as the secret, trigger on push events, and save. Keep the default `webhook_provider = "github"` since these providers emulate GitHub webhooks.
## 💥 Reset
> [!CAUTION]
> **Resetting** the cluster **multiple times in a short period of time** could lead to being **rate limited by DockerHub or Let's Encrypt**.
There might be a situation where you want to destroy your Kubernetes cluster. The following command will reset your nodes back to maintenance mode.
```sh
just talos reset
```
## 🛠️ Talos and Kubernetes Maintenance
### ⚙️ Updating Talos node configuration
> [!TIP]
> Ensure you have updated `topf.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.
```sh
# Preview the rendered machine configs (optional)
just talos render
# Apply the config to the node
just talos apply-node <node>
# e.g. just talos apply-node k8s-0
```
### ⬆️ Updating Talos and Kubernetes versions
> [!TIP]
> Ensure the `talosVersion` and `kubernetesVersion` in `topf.yaml` are up-to-date with the version you wish to upgrade to.
```sh
# Upgrade talos on a node
just talos upgrade-node <node>
# e.g. just talos upgrade-node k8s-0
```
```sh
# Upgrade cluster to a newer Kubernetes version
just talos upgrade-k8s
```
### Adding a node to your cluster
At some point you might want to expand your cluster to run more workloads and/or improve the reliability of your cluster. Keep in mind it is recommended to have an **odd number** of control plane nodes for quorum reasons.
You don't need to re-bootstrap the cluster to add new nodes. Follow these steps:
1. **Prepare the new node**: Review the [Stage 2: Machine Preparation](#stage-2-machine-preparation) section and boot your new node into maintenance mode.
2. **Get the node information**: While the node is in maintenance mode, retrieve the disk and MAC address information needed for configuration:
```sh
talosctl get disks -n <ip> --insecure
talosctl get links -n <ip> --insecure
```
3. **Update the configuration**: Read the documentation for [topf](https://postfinance.github.io/topf/) and extend `topf.yaml` (and any `node/<hostname>/` patches) manually with the new node information (including the disk and MAC address from step 2).
4. **Apply the configuration**:
```sh
# Preview the rendered machine configs (optional)
just talos render
# Apply the configuration to the node
just talos apply-node <node>
# e.g. just talos apply-node k8s-3
```
The node should join the cluster automatically and workloads will be scheduled once they report as ready.
## 🤖 Renovate
[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 Flux to apply the update to your cluster.
To enable Renovate on GitHub, click the 'Configure' button over at their [Github app page](https://github.com/apps/renovate) and select your repository. On other Git providers you can [self-host Renovate](https://docs.renovatebot.com/getting-started/running/#self-hosting-renovate); note that fetching the shared preset in `.renovaterc.json5` requires a `GITHUB_COM_TOKEN`. 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.
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.
## 🐛 Debugging
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.
1. Check if the Flux resources are up-to-date and in a ready state:
📍 _Run `just kube reconcile` to force Flux to sync your Git repository state_
```sh
flux get sources git -A
flux get ks -A
flux get hr -A
```
2. Do you see the pod of the workload you are debugging:
```sh
kubectl -n <namespace> get pods -o wide
```
3. Check the logs of the pod if it's there:
```sh
kubectl -n <namespace> logs <pod-name> -f
```
4. If a resource exists, try to describe it to see what problems it might have:
```sh
kubectl -n <namespace> describe <resource> <name>
```
5. Check the namespace events:
```sh
kubectl -n <namespace> get events --sort-by='.metadata.creationTimestamp'
```
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.
## 🧹 Tidy up
Once your cluster is fully configured and you no longer need to run `just configure`, it's a good idea to clean up the repository by removing the [template](./template) 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.
1. Tidy up your repository:
```sh
just template tidy
```
2. Push your changes to git:
```sh
git add -A
git commit -m "chore: tidy up :broom:"
git push
```
## ❔ What's next
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.
Below are some optional considerations you may want to explore.
### DNS
The template uses [k8s_gateway](https://github.com/k8s-gateway/k8s_gateway) to provide DNS for your applications, consider exploring [external-dns](https://github.com/kubernetes-sigs/external-dns) as an alternative.
External-DNS offers broad support for various DNS providers, including but not limited to:
- [Pi-hole](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/pihole.md)
- [UniFi](https://github.com/kashalls/external-dns-unifi-webhook)
- [Adguard Home](https://github.com/muhlba91/external-dns-provider-adguard)
- [Bind](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/rfc2136.md)
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.
### Secrets
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.
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.
### Storage
If your workloads require persistent storage with features like replication or connectivity to NFS, SMB, or iSCSI servers, there are several projects worth exploring:
- [rook-ceph](https://github.com/rook/rook) / [longhorn](https://github.com/longhorn/longhorn) / [openebs](https://github.com/openebs/openebs)
- [democratic-csi](https://github.com/democratic-csi/democratic-csi)
- [csi-driver-nfs](https://github.com/kubernetes-csi/csi-driver-nfs) / [csi-driver-smb](https://github.com/kubernetes-csi/csi-driver-smb)
- [synology-csi](https://github.com/SynologyOpenSource/synology-csi)
- [truenas-csi](https://github.com/truenas/truenas-csi) / [tns-csi](https://github.com/fenio/tns-csi)
These tools offer a variety of solutions to meet your persistent storage needs, whether youre using cloud-native or self-hosted infrastructures.
### Community Repositories
Community member [@whazor](https://github.com/whazor) created [Kubesearch](https://kubesearch.dev) to allow searching Flux HelmReleases across Github and Gitlab repositories with the `kubesearch` topic.
## 🙋 Support
### Community
- Make a post in this repository's GitHub [Discussions](https://github.com/onedr0p/cluster-template/discussions).
- Start a thread in the `#support` or `#cluster-template` channels in the [Home Operations](https://discord.gg/home-operations) Discord server.
## 📺 Media
Check out these videos below. If you find them helpful, a like and subscribe goes a long way!
<a href="https://youtube.com/watch?v=aeUKOpeoiUs">
<img src="https://github.com/user-attachments/assets/2dab1c6f-7b27-4b94-a7ad-a6d9c5b17c78" alt="Youtube Video" width="300">
</a>
&nbsp;&nbsp;
<a href="https://youtube.com/watch?v=hoi2GzvJUXM">
<img src="https://github.com/user-attachments/assets/5b939b90-0019-4515-b90c-321ffe7448cf" alt="Youtube Video" width="300">
</a>
## 🙌 Related Projects
If this repo is too hot to handle or too cold to hold check out these following projects.
- [ajaykumar4/cluster-template](https://github.com/ajaykumar4/cluster-template) - _A template for deploying a Talos Kubernetes cluster including Argo for GitOps_
- [mitchross/k3s-argocd-starter](https://github.com/mitchross/k3s-argocd-starter) - starter kit for k3s, argocd
- [ricsanfre/pi-cluster](https://github.com/ricsanfre/pi-cluster) - _Pi Kubernetes Cluster. Homelab kubernetes cluster automated with Ansible and FluxCD_
- [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._
## 🤝 Thanks
Big shout out to all the contributors, sponsors and everyone else who has helped on this project.
+292
View File
@@ -0,0 +1,292 @@
#:schema ./cluster.schema.json
# =============================================================================
# Physical LAN that your Talos nodes live on. Defines the address space
# used for node IPs, the gateway/DNS/NTP servers nodes will use, and an
# optional VLAN tag for switch ports that aren't natively tagged.
# =============================================================================
[network]
# The CIDR block your nodes' IPs come from. Every node's `address`, the Kube
# API VIP, and the gateway VIPs (internal/dns/external) must all sit inside
# this range.
# REQUIRED. Example: "192.168.1.0/24"
node_cidr = ""
# Upstream DNS servers Talos nodes use for name resolution. Defaults to
# Cloudflare (1.1.1.1 / 1.0.0.1). Override if you run an internal resolver
# (Pi-hole, Unbound, AdGuard) or want a different public provider.
# OPTIONAL. Default: ["1.1.1.1", "1.0.0.1"]
# dns_servers = ["1.1.1.1", "1.0.0.1"]
# Upstream NTP servers. Defaults to Cloudflare's anycast NTP. Most homelabs
# don't need to change this.
# OPTIONAL. Default: ["162.159.200.1", "162.159.200.123"]
# ntp_servers = ["162.159.200.1", "162.159.200.123"]
# Default gateway IP that nodes use to reach the rest of your LAN/WAN.
# Defaults to the first usable host in node_cidr (e.g. 192.168.1.1 for
# 192.168.1.0/24), which is correct for most home routers. Override if your
# router lives at a non-standard address inside the subnet.
# OPTIONAL. Default: first IP in node_cidr
# default_gateway = ""
# 802.1Q VLAN tag to apply to the Talos node interface. Only set this if
# your switch ports are configured as trunks (passing tagged traffic to the
# nodes); access ports already untag VLAN traffic. Must be 1-4094.
# REF: https://www.talos.dev/latest/advanced/advanced-networking/#vlans
# OPTIONAL.
# vlan_tag = ""
# =============================================================================
# Cluster-internal control plane and overlay networks. The pod and service
# CIDRs are in-cluster only — they don't have to be routable on your LAN
# and never appear on the wire outside the nodes.
# =============================================================================
[kubernetes]
# CIDR Cilium hands out to pods. /16 gives ~64K pod IPs, which is well beyond
# what a homelab needs but matches the upstream default. Must NOT overlap
# with node_cidr or svc_cidr.
# OPTIONAL. Default: "10.42.0.0/16"
# pod_cidr = "10.42.0.0/16"
# CIDR for ClusterIP services (the virtual IPs `kubectl get svc` shows).
# Same /16 reasoning as pod_cidr. Must NOT overlap with node_cidr or
# pod_cidr.
# OPTIONAL. Default: "10.43.0.0/16"
# svc_cidr = "10.43.0.0/16"
# ClusterIP for the CoreDNS Service. Must be inside svc_cidr.
# OPTIONAL. Default: the 10th IP in svc_cidr
# coredns_addr = ""
[kubernetes.api]
# Virtual IP for the Kubernetes API server. kubectl, flux, and every other
# client connect here on port 6443. Must be an unused IP inside
# network.node_cidr — kube-vip floats it across controller nodes.
# REQUIRED.
addr = ""
# Additional Subject Alternative Names to put on the Kube API cert. Useful
# if you want to call the API by hostname (e.g. via a CNAME or local
# /etc/hosts entry) instead of the raw IP.
# OPTIONAL. Example: ["mycluster.example.com"]
# tls_sans = ["mycluster.example.com"]
# =============================================================================
# LoadBalancer IPs that Cilium hands out to the cluster's edge gateways.
# Each must be an unused address inside network.node_cidr, and all four
# (these three plus kubernetes.api.addr) must be distinct.
# =============================================================================
[gateways]
# IP for the `envoy-internal` gateway — used by HTTPRoutes intended for
# private/in-network access only. Most apps use this gateway by default.
# REQUIRED.
internal = ""
# IP for `k8s_gateway`, which serves DNS for cluster-managed hostnames.
# Point your home DNS server's conditional forwarder for domain.name
# at this IP to enable split-DNS resolution from your LAN.
# REQUIRED.
dns = ""
# IP for the `envoy-external` gateway — sits behind the ingress path (e.g.
# the cloudflared tunnel) and handles traffic exposed to the public
# internet. HTTPRoutes that reference this gateway become reachable via
# your public domain.
# REQUIRED unless ingress.mode is "none" (internal-only cluster).
external = ""
# =============================================================================
# The Git repo Flux will sync from. This is the single source of truth
# for everything in your cluster — once bootstrapped, changes are made by
# pushing to this repo. Any Git host works: GitHub, GitLab, Gitea,
# Forgejo, Codeberg or self-hosted.
# =============================================================================
[repository]
# Full clone URL of the repository this cluster will pull from.
# Must be the repo you cloned this template into.
# Use `https://` if the repo is publicly readable (or `http://` for a
# LAN-local git server). Use `ssh://git@` if it is
# private: the template then wires up a deploy key (`deploy.key.pub`) so
# Flux can clone over SSH; see the README for the extra setup step.
# REQUIRED. Examples:
# "https://github.com/onedr0p/home-ops.git"
# "ssh://git@gitlab.com/onedr0p/home-ops.git"
# "ssh://git@git.example.com/k8s/home-ops.git"
url = ""
# Branch Flux watches. Changes pushed to this branch get reconciled into the
# cluster.
# OPTIONAL. Default: "main"
# branch = "main"
# Webhook payload format the Flux webhook Receiver verifies, so pushes are
# reconciled instantly. Gitea and Forgejo emulate GitHub webhooks, so keep
# "github" for them. Use "generic-hmac" for anything else that signs with
# HMAC; see https://fluxcd.io/flux/components/notification/receivers/
# Use "none" to skip the webhook entirely (e.g. your Git host cannot reach
# the cluster); Flux then only polls on an interval.
# OPTIONAL. Default: "github".
# Allowed: "github" | "gitlab" | "generic-hmac" | "none"
# webhook_provider = "github"
# SSH host keys for your Git host (`ssh-keyscan -t ed25519,ecdsa,rsa <host>`
# output). Only used with `ssh://` URLs. Keys for github.com, gitlab.com and
# codeberg.org are bundled; REQUIRED for any other host.
# OPTIONAL. Example:
# known_hosts = """
# git.example.com ssh-ed25519 AAAA...
# """
# known_hosts = ""
# =============================================================================
# The domain your cluster's hostnames live under. Used for every rendered
# hostname (echo, flux-webhook, internal split DNS) and the wildcard
# certificate, regardless of DNS provider.
# =============================================================================
[domain]
# REQUIRED. Example: "example.com"
name = ""
# =============================================================================
# Public DNS authority and certificate issuance. With "cloudflare",
# external-dns publishes records automatically and cert-manager issues a
# Let's Encrypt wildcard via ACME DNS-01. With "none", nothing is
# published and the wildcard certificate is issued by an in-cluster
# self-signed CA instead (internal-only cluster).
# =============================================================================
[dns]
# OPTIONAL. Default: "cloudflare". Allowed: "cloudflare" | "none"
# provider = "cloudflare"
# Cloudflare API token (NOT the global API key) with `Zone - DNS - Edit` and
# `Account - Cloudflare Tunnel - Read` permissions, scoped to the zone
# above. See the README for token creation steps.
# REQUIRED when provider is "cloudflare"; must be empty otherwise.
token = ""
# =============================================================================
# How the public internet reaches the cluster's external gateway. With
# "cloudflare-tunnel", cloudflared connects outbound so no ports are
# forwarded (requires dns.provider = "cloudflare" and
# cloudflare-tunnel.json). With "direct", you forward TCP 443 (and
# optionally 80) on your router to gateways.external and point an
# `external.<domain>` DNS record at your WAN address (A record or DDNS
# CNAME) yourself; per-app records are still published automatically.
# With "none", nothing is exposed and apps are only reachable on your
# LAN via the internal gateway.
# =============================================================================
[ingress]
# OPTIONAL. Default: "cloudflare-tunnel" when dns.provider is "cloudflare",
# otherwise "none". Allowed: "cloudflare-tunnel" | "direct" | "none"
# mode = "cloudflare-tunnel"
# =============================================================================
# CNI configuration. Defaults are sane for most homelab setups; touch
# this section only if you need BGP peering or a different LB mode.
# =============================================================================
[cilium]
# How Cilium's load balancer handles return traffic. `dsr` (Direct Server
# Return) preserves the client IP and is faster, but requires a switch
# fabric that won't drop asymmetric flows. `snat` masquerades the client
# and is the safe default for unknown topologies.
# REF: https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/
# OPTIONAL. Default: "dsr". Allowed: "dsr" | "snat"
# loadbalancer_mode = "dsr"
# Cilium BGP peering — advertises Service IPs to your upstream router so
# LoadBalancer addresses become reachable from anywhere on your LAN
# (rather than only via L2 ARP). Set ALL THREE fields below to enable;
# leaving any blank disables BGP entirely.
# REF: https://docs.cilium.io/en/latest/network/bgp-control-plane/bgp-control-plane/
[cilium.bgp]
# IP of your BGP-speaking router. The cluster peers with it from each node.
# OPTIONAL. Example: "192.168.1.1"
# router_addr = ""
# ASN your router uses for BGP. Anything in the private range (64512-65534)
# is fine if you're not peering with the public internet.
# OPTIONAL. Example: "64513"
# router_asn = ""
# ASN the cluster's nodes use for BGP. Pick a different value than
# router_asn so peering is eBGP rather than iBGP.
# OPTIONAL. Example: "64514"
# node_asn = ""
# =============================================================================
# Talos Image Factory settings shared by all nodes.
# =============================================================================
[talos]
# Default schematic for every node that doesn't set its own schematic_id.
# The 64-character hex string from your build at https://factory.talos.dev/
# OPTIONAL if every node sets schematic_id itself.
schematic_id = ""
# =============================================================================
# One [[nodes]] table per physical machine or VM in the cluster. At least
# one controller (controller=true) is required; worker nodes are optional.
# For HA, use 3 controllers.
#
# Discover hardware details from a node already booted into Talos
# maintenance mode:
# talosctl get disks -n <node-ip> --insecure
# talosctl get links -n <node-ip> --insecure
# Schematic ID is the 64-character hex string from your build at:
# https://factory.talos.dev/
#
# The block below is a template — copy it once per node, uncomment, and
# fill in the values.
# =============================================================================
# [[nodes]]
# name = "k8s-0" # Hostname; must match [a-z0-9-]+ (not "global"/"controller"/"worker").
# address = "192.168.1.10" # Static IP; must be inside network.node_cidr.
# controller = true # true = control-plane (etcd + API server), false = worker.
# disk = "/dev/nvme0n1" # Block device or /dev/disk/by-id/... symlink to install Talos onto.
# mac_addr = "aa:bb:cc:dd:ee:ff" # Primary NIC MAC.
#
# # Optional when [talos] sets a cluster-wide default:
# schematic_id = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" # 64-hex from factory.talos.dev.
#
# # Optional advanced fields (each independently uncommentable):
# mtu = 1500 # Set only for jumbo frames / non-1500 MTUs (1450-9000).
# secureboot = false # UEFI SecureBoot — requires a SecureBoot-enabled schematic.
# encrypt_disk = false # TPM-bound full-disk encryption.
# kernel_modules = ["nvidia", "nvidia_uvm"] # Only for schematics shipping matching extensions.
+433
View File
@@ -0,0 +1,433 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Api": {
"additionalProperties": false,
"properties": {
"addr": {
"format": "ipv4",
"title": "Addr",
"type": "string"
},
"tls_sans": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/Fqdn"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Tls Sans"
}
},
"required": ["addr"],
"title": "Api",
"type": "object"
},
"Asn": {
"type": "string"
},
"Bgp": {
"additionalProperties": false,
"properties": {
"router_addr": {
"anyOf": [
{
"format": "ipv4",
"type": "string"
},
{
"const": "",
"type": "string"
}
],
"default": "",
"title": "Router Addr"
},
"router_asn": {
"$ref": "#/$defs/Asn",
"default": ""
},
"node_asn": {
"$ref": "#/$defs/Asn",
"default": ""
}
},
"title": "Bgp",
"type": "object"
},
"Cidr": {
"format": "ipv4network",
"type": "string"
},
"Cilium": {
"additionalProperties": false,
"properties": {
"loadbalancer_mode": {
"default": "dsr",
"enum": ["dsr", "snat"],
"title": "Loadbalancer Mode",
"type": "string"
},
"bgp": {
"$ref": "#/$defs/Bgp",
"default": {
"router_addr": "",
"router_asn": "",
"node_asn": ""
}
}
},
"title": "Cilium",
"type": "object"
},
"Dns": {
"additionalProperties": false,
"properties": {
"provider": {
"default": "cloudflare",
"enum": ["cloudflare", "none"],
"title": "Provider",
"type": "string"
},
"token": {
"default": "",
"title": "Token",
"type": "string"
}
},
"title": "Dns",
"type": "object"
},
"Domain": {
"additionalProperties": false,
"properties": {
"name": {
"$ref": "#/$defs/Fqdn"
}
},
"required": ["name"],
"title": "Domain",
"type": "object"
},
"Fqdn": {
"pattern": "^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}$",
"type": "string"
},
"Gateways": {
"additionalProperties": false,
"properties": {
"internal": {
"format": "ipv4",
"title": "Internal",
"type": "string"
},
"dns": {
"format": "ipv4",
"title": "Dns",
"type": "string"
},
"external": {
"anyOf": [
{
"format": "ipv4",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "External"
}
},
"required": ["internal", "dns"],
"title": "Gateways",
"type": "object"
},
"Ingress": {
"additionalProperties": false,
"properties": {
"mode": {
"default": "cloudflare-tunnel",
"enum": ["cloudflare-tunnel", "direct", "none"],
"title": "Mode",
"type": "string"
}
},
"title": "Ingress",
"type": "object"
},
"Kubernetes": {
"additionalProperties": false,
"properties": {
"pod_cidr": {
"$ref": "#/$defs/Cidr",
"default": "10.42.0.0/16"
},
"svc_cidr": {
"$ref": "#/$defs/Cidr",
"default": "10.43.0.0/16"
},
"coredns_addr": {
"format": "ipv4",
"title": "Coredns Addr",
"type": "string"
},
"api": {
"$ref": "#/$defs/Api"
}
},
"required": ["api"],
"title": "Kubernetes",
"type": "object"
},
"Network": {
"additionalProperties": false,
"properties": {
"node_cidr": {
"$ref": "#/$defs/Cidr"
},
"dns_servers": {
"default": ["1.1.1.1", "1.0.0.1"],
"items": {
"format": "ipv4",
"type": "string"
},
"title": "Dns Servers",
"type": "array"
},
"ntp_servers": {
"default": ["162.159.200.1", "162.159.200.123"],
"items": {
"format": "ipv4",
"type": "string"
},
"title": "Ntp Servers",
"type": "array"
},
"default_gateway": {
"format": "ipv4",
"title": "Default Gateway",
"type": "string"
},
"vlan_tag": {
"anyOf": [
{
"pattern": "^[0-9]+$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Vlan Tag"
}
},
"required": ["node_cidr"],
"title": "Network",
"type": "object"
},
"Node": {
"additionalProperties": false,
"properties": {
"name": {
"pattern": "^[a-z0-9][a-z0-9\\-]{0,61}[a-z0-9]$|^[a-z0-9]$",
"title": "Name",
"type": "string"
},
"address": {
"format": "ipv4",
"title": "Address",
"type": "string"
},
"controller": {
"title": "Controller",
"type": "boolean"
},
"disk": {
"title": "Disk",
"type": "string"
},
"mac_addr": {
"pattern": "^([0-9a-f]{2}:){5}[0-9a-f]{2}$",
"title": "Mac Addr",
"type": "string"
},
"schematic_id": {
"anyOf": [
{
"pattern": "^[a-z0-9]{64}$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Schematic Id"
},
"mtu": {
"default": 1500,
"maximum": 9000,
"minimum": 1450,
"title": "Mtu",
"type": "integer"
},
"secureboot": {
"default": false,
"title": "Secureboot",
"type": "boolean"
},
"encrypt_disk": {
"default": false,
"title": "Encrypt Disk",
"type": "boolean"
},
"kernel_modules": {
"default": [],
"items": {
"type": "string"
},
"title": "Kernel Modules",
"type": "array"
}
},
"required": ["name", "address", "controller", "disk", "mac_addr"],
"title": "Node",
"type": "object"
},
"Repository": {
"additionalProperties": false,
"properties": {
"url": {
"pattern": "^(https?://|ssh://git@)[^/]+/.+$",
"title": "Url",
"type": "string"
},
"branch": {
"default": "main",
"minLength": 1,
"title": "Branch",
"type": "string"
},
"webhook_provider": {
"default": "github",
"enum": ["github", "gitlab", "generic-hmac", "none"],
"title": "Webhook Provider",
"type": "string"
},
"known_hosts": {
"default": "",
"title": "Known Hosts",
"type": "string"
}
},
"required": ["url"],
"title": "Repository",
"type": "object"
},
"Spegel": {
"additionalProperties": false,
"properties": {
"enabled": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Enabled"
}
},
"title": "Spegel",
"type": "object"
},
"Talos": {
"additionalProperties": false,
"properties": {
"schematic_id": {
"anyOf": [
{
"pattern": "^[a-z0-9]{64}$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Schematic Id"
}
},
"title": "Talos",
"type": "object"
}
},
"additionalProperties": false,
"properties": {
"network": {
"$ref": "#/$defs/Network"
},
"kubernetes": {
"$ref": "#/$defs/Kubernetes"
},
"gateways": {
"$ref": "#/$defs/Gateways"
},
"repository": {
"$ref": "#/$defs/Repository"
},
"domain": {
"$ref": "#/$defs/Domain"
},
"dns": {
"$ref": "#/$defs/Dns"
},
"ingress": {
"$ref": "#/$defs/Ingress"
},
"cilium": {
"$ref": "#/$defs/Cilium",
"default": {
"loadbalancer_mode": "dsr",
"bgp": {
"node_asn": "",
"router_addr": "",
"router_asn": ""
}
}
},
"talos": {
"$ref": "#/$defs/Talos",
"default": {
"schematic_id": null
}
},
"spegel": {
"$ref": "#/$defs/Spegel",
"default": {
"enabled": null
}
},
"nodes": {
"items": {
"$ref": "#/$defs/Node"
},
"title": "Nodes",
"type": "array"
}
},
"required": ["network", "kubernetes", "gateways", "repository", "domain", "dns", "nodes"],
"title": "cluster.toml",
"type": "object"
}
+34
View File
@@ -0,0 +1,34 @@
set quiet
set minimum-version := '1.55.1'
set default-list
set default-script
set shell := ['bash', '-euo', 'pipefail', '-c']
set script-interpreter := ['bash', '-euo', 'pipefail']
[group('bootstrap')]
mod? bootstrap 'bootstrap'
[group('kubernetes')]
mod? kube 'kubernetes'
[group('talos')]
mod? talos 'talos'
[private]
log lvl msg *args:
gum log -t rfc3339 -s -l "{{ lvl }}" "{{ msg }}" {{ args }}
# === template ===
[group('template')]
mod template 'template'
[doc('Render and validate configuration files')]
[group('template')]
configure:
just template configure
[doc('Initialize configuration files (cluster.toml, age key, deploy key, webhook token)')]
[group('template')]
init:
just template init
+19
View File
@@ -0,0 +1,19 @@
[makejinja]
inputs = ["./template/overrides","./template/config"]
output = "./"
exclude_patterns = ["*.partial.yaml.j2"]
data = ["./cluster.toml"]
import_paths = ["./template/scripts"]
loaders = ["plugin:Plugin"]
jinja_suffix = ".j2"
copy_metadata = true
force = true
undefined = "strict"
[makejinja.delimiter]
block_start = "#%"
block_end = "%#"
comment_start = "#|"
comment_end = "#|"
variable_start = "#{"
variable_end = "}#"
+16
View File
@@ -0,0 +1,16 @@
[project]
name = "cluster-template"
version = "0.0.0"
requires-python = ">=3.14"
dependencies = [
"makejinja==2.8.3",
"pydantic==2.13.5",
]
[dependency-groups]
dev = [
"pytest>=8",
]
[tool.uv]
package = false
+12
View File
@@ -0,0 +1,12 @@
---
creation_rules:
- path_regex: talos/.*\.sops\.ya?ml
mac_only_encrypted: true
age: "#{ age_key('public') }#"
- path_regex: (bootstrap|kubernetes)/.*\.sops\.ya?ml
encrypted_regex: "^(data|stringData)$"
mac_only_encrypted: true
age: "#{ age_key('public') }#"
stores:
yaml:
indent: 2
@@ -0,0 +1,15 @@
---
apiVersion: v1
kind: Secret
metadata:
name: deploy-key
namespace: flux-system
stringData:
identity: |
#% filter indent(width=4, first=False) %#
#{ deploy_key() }#
#% endfilter %#
known_hosts: |
#% filter indent(width=4, first=False) %#
#{ repository.known_hosts }#
#% endfilter %#
@@ -0,0 +1,60 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/helmfile
# Bootstraps core applications that provide a minimal runtime base for the
# cluster. These releases are installed first so the cluster has the resources
# Flux needs before its own reconciliation begins.
#
# After this bootstrap phase, Flux is ready to take over management of the
# application stack and continue reconciling downstream state.
helmDefaults:
cleanupOnFail: true
forceConflicts: true
wait: true
waitForJobs: true
bases:
- default.yaml
releases:
- name: cilium
namespace: kube-system
inherit:
- template: default
- name: coredns
namespace: kube-system
inherit:
- template: default
needs: ["kube-system/cilium"]
#% if spegel.enabled %#
- name: spegel
namespace: kube-system
inherit:
- template: default
needs: ["kube-system/coredns"]
#% endif %#
- name: cert-manager
namespace: cert-manager
inherit:
- template: default
#% if spegel.enabled %#
needs: ["kube-system/spegel"]
#% else %#
needs: ["kube-system/coredns"]
#% endif %#
- name: flux-operator
namespace: flux-system
inherit:
- template: default
needs: ["cert-manager/cert-manager"]
- name: flux-instance
namespace: flux-system
inherit:
- template: default
needs: ["flux-system/flux-operator"]
@@ -0,0 +1,37 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/helmfile
# Bootstraps cluster-wide Custom Resource Definitions (CRDs) by extracting them
# from upstream Helm charts and applying them directly with kubectl. The releases
# below are never reconciled with helmfile apply or helmfile sync — only their
# CRDs are rendered (via --include-crds) and piped to the cluster.
#
# Installing CRDs out-of-band ensures they exist before Flux begins reconciling
# workloads that reference them, avoiding the need for dependsOn chains on nearly
# every Kustomization that consumes a CRD-backed resource.
helmDefaults:
args:
- --include-crds
- --no-hooks
bases:
- default.yaml
releases:
#% if dns.provider == 'cloudflare' and ingress.mode != 'none' %#
- name: cloudflare-dns
namespace: network
inherit:
- template: default
#% endif %#
- name: envoy-gateway
namespace: network
inherit:
- template: default
- name: prometheus-operator-crds
namespace: observability
chart: oci://ghcr.io/prometheus-community/charts/prometheus-operator-crds
version: 29.0.0
@@ -0,0 +1,8 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/helmfile
templates:
default:
chart: '{{ (fromYaml (tpl (readFile "./templates/release.yaml.gotmpl") .)).chart }}'
version: '{{ (fromYaml (tpl (readFile "./templates/release.yaml.gotmpl") .)).version }}'
values:
- ./templates/values.yaml.gotmpl
@@ -0,0 +1,3 @@
{{- $oci := fromYaml (readFile (printf "../../kubernetes/apps/%s/%s/app/ocirepository.yaml" .Release.Namespace .Release.Name)) -}}
chart: {{ $oci.spec.url }}
version: {{ $oci.spec.ref.tag }}
@@ -0,0 +1 @@
{{ (fromYaml (readFile (printf "../../../kubernetes/apps/%s/%s/app/helmrelease.yaml" .Release.Namespace .Release.Name))).spec.values | toYaml }}
+105
View File
@@ -0,0 +1,105 @@
set no-exit-message
set quiet
set shell := ['bash', '-euo', 'pipefail', '-c']
set script-interpreter := ['bash', '-euo', 'pipefail']
set default-list
set default-script
kubernetes_dir := justfile_dir() + '/kubernetes'
[doc('Bootstrap the Talos cluster')]
[group('bootstrap')]
talos: talos-secret talos-apply talos-talosconfig talos-kubeconfig
[doc('Bootstrap apps into the Talos cluster')]
[group('bootstrap')]
apps: apps-ready apps-namespaces apps-secrets apps-crds apps-helm
just log info "Cluster is bootstrapped — Flux will start syncing the Git repository"
# No sops call or existence guard is needed: the bundle is stored already
# encrypted with the repo's age recipient, and existing bundles are left
# untouched.
[private]
[working-directory('../talos')]
talos-secret:
just log info "Generating secrets" stage "{{ recipe_name() }}"
topf secrets --confirm=false > /dev/null
[private]
[working-directory('../talos')]
talos-apply:
just log info "Applying talos config and bootstrapping" stage "{{ recipe_name() }}"
topf apply --auto-bootstrap --confirm=false
[private]
[working-directory('../talos')]
talos-talosconfig:
just log info "Generating talosconfig" stage "{{ recipe_name() }}"
topf talosconfig > talosconfig
# topf issues short-lived admin certs by default; 8760h keeps the
# kubeconfig usable long-term.
[private]
[working-directory('../talos')]
talos-kubeconfig:
just log info "Fetching kubeconfig" stage "{{ recipe_name() }}"
topf kubeconfig --validity 8760h > "{{ justfile_dir() }}/kubeconfig"
[private]
apps-crds:
just log info "Applying CRDs" stage "{{ recipe_name() }}"
if ! helmfile --file "{{ source_directory() }}/helmfile/crds.yaml" template --quiet | yq eval-all --exit-status 'select(.kind == "CustomResourceDefinition")' | kubectl apply --server-side --force-conflicts --filename -; then
just log fatal "Failed to apply crds"
fi
[private]
apps-helm:
just log info "Syncing helmfile" stage "{{ recipe_name() }}"
if ! helmfile --file "{{ source_directory() }}/helmfile/apps.yaml" sync --hide-notes; then
just log fatal "Failed to sync helmfile"
fi
[private]
apps-namespaces:
just log info "Applying namespaces for apps" stage "{{ recipe_name() }}"
for app in "{{ kubernetes_dir }}/apps"/*/; do
ns="$(basename "$app")"
if kubectl create namespace "$ns" --dry-run=client -o yaml \
| kubectl apply --server-side --filename - &>/dev/null; then
just log info "Namespace applied" namespace "$ns"
else
just log fatal "Failed to apply namespace" namespace "$ns"
fi
done
[private]
apps-secrets:
just log info "Applying secrets for apps" stage "{{ recipe_name() }}"
for secret in \
"{{ source_directory() }}/deploy-key.sops.yaml" \
"{{ source_directory() }}/sops-age.sops.yaml" \
"{{ kubernetes_dir }}/components/sops/cluster-secrets.sops.yaml"
do
name="$(basename "$secret" .sops.yaml)"
if sops decrypt "$secret" \
| kubectl --namespace flux-system apply --server-side --filename - &>/dev/null; then
just log info "Secret applied" resource "$name"
else
just log fatal "Failed to apply secret" resource "$name"
fi
done
# Wait until nodes register as Ready=False. They only become Ready=True once the CNI is healthy.
[private]
apps-ready:
just log info "Waiting for nodes to register as Ready=False" stage "{{ recipe_name() }}"
if ! kubectl wait nodes --for=condition=Ready=True --all --timeout=10s &>/dev/null; then
deadline=$((SECONDS + 600))
until kubectl wait nodes --for=condition=Ready=False --all --timeout=10s &>/dev/null; do
if (( SECONDS >= deadline )); then
just log fatal "Timed out waiting for nodes to register"
fi
just log info "Nodes not available, waiting for nodes to be available. Retrying in 5 seconds..."
sleep 5
done
fi
@@ -0,0 +1,8 @@
---
apiVersion: v1
kind: Secret
metadata:
name: sops-age
namespace: flux-system
stringData:
age.agekey: "#{ age_key('private') }#"
@@ -0,0 +1,52 @@
#% if dns.provider == 'cloudflare' %#
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
privateKeySecretRef:
name: letsencrypt-production
profile: shortlived
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cert-manager-secret
key: api-token
selector:
dnsZones: ["${SECRET_DOMAIN}"]
#% else %#
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-ca
spec:
isCA: true
commonName: internal-ca
secretName: internal-ca
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned
kind: ClusterIssuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca
spec:
ca:
secretName: internal-ca
#% endif %#
@@ -0,0 +1,20 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cert-manager
spec:
chartRef:
kind: OCIRepository
name: cert-manager
interval: 1h
values:
crds:
enabled: true
replicaCount: #{ 2 if nodes | length > 1 else 1 }#
dns01RecursiveNameservers: https://1.1.1.1:443/dns-query,https://1.0.0.1:443/dns-query
dns01RecursiveNameserversOnly: true
prometheus:
enabled: true
servicemonitor:
enabled: true
@@ -0,0 +1,10 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./clusterissuer.yaml
- ./helmrelease.yaml
- ./ocirepository.yaml
#% if dns.provider == 'cloudflare' %#
- ./secret.sops.yaml
#% endif %#
@@ -0,0 +1,13 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: cert-manager
spec:
interval: 15m
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: v1.21.2
url: oci://quay.io/jetstack/charts/cert-manager
@@ -0,0 +1,9 @@
#% if dns.provider == 'cloudflare' %#
---
apiVersion: v1
kind: Secret
metadata:
name: cert-manager-secret
stringData:
api-token: "#{ dns.token }#"
#% endif %#
@@ -0,0 +1,30 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: cert-manager
spec:
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
name: cert-manager
namespace: cert-manager
- apiVersion: cert-manager.io/v1
kind: ClusterIssuer
name: #{ cluster_issuer }#
healthCheckExprs:
- apiVersion: cert-manager.io/v1
kind: ClusterIssuer
current: status.conditions.exists(e, e.type == 'Ready' && e.status == 'True')
interval: 1h
path: ./kubernetes/apps/cert-manager/cert-manager/app
postBuild:
substituteFrom:
- name: cluster-secrets
kind: Secret
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
targetNamespace: cert-manager
@@ -0,0 +1,11 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cert-manager
components:
- ../../components/sops
resources:
- ./namespace.yaml
- ./cert-manager/ks.yaml
@@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
@@ -0,0 +1,31 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: echo
spec:
chartRef:
kind: OCIRepository
name: echo
interval: 1h
values:
replicaCount: #{ 2 if nodes | length > 1 else 1 }#
config:
kubernetes: true
trustedProxies:
- "#{ kubernetes.pod_cidr }#"
httpRoute:
enabled: true
hostnames:
- "{{ .Release.Name }}.${SECRET_DOMAIN}"
parentRefs:
- name: envoy-#{ 'external' if ingress.mode != 'none' else 'internal' }#
namespace: network
monitoring:
serviceMonitor:
enabled: true
resources:
requests:
cpu: 10m
limits:
memory: 64Mi
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./helmrelease.yaml
- ./ocirepository.yaml
@@ -0,0 +1,13 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: echo
spec:
interval: 15m
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: 0.2.5
url: oci://ghcr.io/home-operations/charts/echo
@@ -0,0 +1,19 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: echo
spec:
interval: 1h
path: ./kubernetes/apps/default/echo/app
postBuild:
substituteFrom:
- name: cluster-secrets
kind: Secret
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
targetNamespace: default
wait: false
@@ -0,0 +1,11 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: default
components:
- ../../components/sops
resources:
- ./namespace.yaml
- ./echo/ks.yaml
@@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: default
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
@@ -0,0 +1,136 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: flux-instance
spec:
chartRef:
kind: OCIRepository
name: flux-instance
interval: 1h
values:
commonAnnotations:
fluxcd.controlplane.io/reconcileArtifactEvery: 1h
instance:
cluster:
networkPolicy: false
components:
- source-controller
- kustomize-controller
- helm-controller
- notification-controller
sync:
kind: GitRepository
url: "#{ repository.url }#"
#% if repository.url.startswith('ssh://') %#
pullSecret: deploy-key
#% endif %#
ref: "refs/heads/#{ repository.branch }#"
path: kubernetes/flux/cluster
commonMetadata:
labels:
app.kubernetes.io/name: flux
kustomize:
patches:
- # Increase the number of workers
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --concurrent=10
- op: add
path: /spec/template/spec/containers/0/args/-
value: --requeue-dependency=5s
target:
kind: Deployment
name: (kustomize-controller|helm-controller|source-controller)
- # Increase the memory limits
patch: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: all
spec:
template:
spec:
containers:
- name: manager
resources:
limits:
memory: 1Gi
target:
kind: Deployment
name: (kustomize-controller|helm-controller|source-controller)
- # Enable in-memory kustomize builds
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --concurrent=20
- op: replace
path: /spec/template/spec/volumes/0
value:
name: temp
emptyDir:
medium: Memory
target:
kind: Deployment
name: kustomize-controller
- # Enable Helm repositories caching
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --helm-cache-max-size=10
- op: add
path: /spec/template/spec/containers/0/args/-
value: --helm-cache-ttl=60m
- op: add
path: /spec/template/spec/containers/0/args/-
value: --helm-cache-purge-interval=5m
target:
kind: Deployment
name: source-controller
- # Flux near OOM detection for Helm
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --feature-gates=OOMWatch=true
- op: add
path: /spec/template/spec/containers/0/args/-
value: --oom-watch-memory-threshold=95
- op: add
path: /spec/template/spec/containers/0/args/-
value: --oom-watch-interval=500ms
target:
kind: Deployment
name: helm-controller
- # Disable chart digest tracking
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --feature-gates=DisableChartDigestTracking=true
target:
kind: Deployment
name: helm-controller
- # Controller-level SOPS decryption
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --sops-age-secret=sops-age
target:
kind: Deployment
name: kustomize-controller
- # Watch configmaps and secrets attached to HelmReleases and Kustomizations
patch: |-
- op: add
path: /spec/template/spec/containers/0/args/-
value: --watch-configs-label-selector=owner!=helm
target:
kind: Deployment
name: (helm-controller|kustomize-controller)
- # Cancel health checks on new Kustomizations revisions
patch: |-
- op: add
path: /spec/template/spec/containers/0/args/-
value: --feature-gates=CancelHealthCheckOnNewRevision=true
target:
kind: Deployment
name: kustomize-controller
@@ -0,0 +1,22 @@
#% if repository.webhook_provider != 'none' %#
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: flux-webhook
spec:
hostnames: ["flux-webhook.${SECRET_DOMAIN}"]
parentRefs:
- name: envoy-#{ 'external' if ingress.mode != 'none' else 'internal' }#
namespace: network
sectionName: https
rules:
- backendRefs:
- name: webhook-receiver
namespace: flux-system
port: 80
matches:
- path:
type: PathPrefix
value: /hook/
#% endif %#
@@ -0,0 +1,11 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./helmrelease.yaml
- ./ocirepository.yaml
#% if repository.webhook_provider != 'none' %#
- ./secret.sops.yaml
- ./httproute.yaml
- ./receiver.yaml
#% endif %#
@@ -0,0 +1,13 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: flux-instance
spec:
interval: 15m
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: 0.60.0
url: oci://ghcr.io/controlplaneio-fluxcd/charts/flux-instance
@@ -0,0 +1,21 @@
#% if repository.webhook_provider != 'none' %#
---
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: flux-webhook
spec:
type: #{ repository.webhook_provider }#
events: ["ping", "push"]
secretRef:
name: flux-webhook-token
resources:
- apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
name: flux-system
namespace: flux-system
- apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
name: flux-system
namespace: flux-system
#% endif %#
@@ -0,0 +1,9 @@
#% if repository.webhook_provider != 'none' %#
---
apiVersion: v1
kind: Secret
metadata:
name: flux-webhook-token
stringData:
token: "#{ webhook_token() }#"
#% endif %#
@@ -0,0 +1,21 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flux-instance
spec:
dependsOn:
- name: flux-operator
interval: 1h
path: ./kubernetes/apps/flux-system/flux-instance/app
postBuild:
substituteFrom:
- name: cluster-secrets
kind: Secret
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
targetNamespace: flux-system
wait: false
@@ -0,0 +1,13 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: flux-operator
spec:
chartRef:
kind: OCIRepository
name: flux-operator
interval: 1h
values:
serviceMonitor:
create: true
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./helmrelease.yaml
- ./ocirepository.yaml
@@ -0,0 +1,13 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: flux-operator
spec:
interval: 15m
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: 0.60.0
url: oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator
@@ -0,0 +1,19 @@
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flux-operator
spec:
interval: 1h
path: ./kubernetes/apps/flux-system/flux-operator/app
postBuild:
substituteFrom:
- name: cluster-secrets
kind: Secret
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
targetNamespace: flux-system
wait: true
@@ -0,0 +1,12 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: flux-system
components:
- ../../components/sops
resources:
- ./namespace.yaml
- ./flux-instance/ks.yaml
- ./flux-operator/ks.yaml
@@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
annotations:
kustomize.toolkit.fluxcd.io/prune: disabled
@@ -0,0 +1,91 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cilium
spec:
chartRef:
kind: OCIRepository
name: cilium
interval: 1h
values:
autoDirectNodeRoutes: true
bpf:
masquerade: true
# Ref: https://github.com/siderolabs/talos/issues/10002
hostLegacyRouting: true
#% if cilium_bgp_enabled %#
bgpControlPlane:
enabled: true
#% endif %#
cni:
# Required for pairing with Multus CNI
exclusive: false
cgroup:
automount:
enabled: false
hostRoot: /sys/fs/cgroup
# The stable bond name is defined in talos/all/20-network-links.yaml.tpl
devices: bond0+
dashboards:
enabled: true
endpointRoutes:
enabled: true
envoy:
enabled: false
gatewayAPI:
enabled: false
hubble:
enabled: false
ipam:
mode: kubernetes
ipv4NativeRoutingCIDR: "#{ kubernetes.pod_cidr }#"
k8sServiceHost: 127.0.0.1
k8sServicePort: 7445
kubeProxyReplacement: true
kubeProxyReplacementHealthzBindAddr: 0.0.0.0:10256
l2announcements:
enabled: true
loadBalancer:
algorithm: maglev
mode: "#{ cilium.loadbalancer_mode }#"
localRedirectPolicies:
enabled: true
operator:
dashboards:
enabled: true
prometheus:
enabled: true
serviceMonitor:
enabled: true
replicas: #{ 2 if nodes | length > 1 else 1 }#
rollOutPods: true
prometheus:
enabled: true
serviceMonitor:
enabled: true
trustCRDsExist: true
rollOutCiliumPods: true
routingMode: native
securityContext:
capabilities:
ciliumAgent:
- CHOWN
- KILL
- NET_ADMIN
- NET_RAW
- IPC_LOCK
- SYS_ADMIN
- SYS_RESOURCE
- PERFMON
- BPF
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
cleanCiliumState:
- NET_ADMIN
- SYS_ADMIN
- SYS_RESOURCE
socketLB:
enabled: true
@@ -0,0 +1,7 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./helmrelease.yaml
- ./ocirepository.yaml
- ./networks.yaml
@@ -0,0 +1,71 @@
---
apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
name: pool
spec:
allowFirstLastIPs: "No"
blocks:
- cidr: "#{ network.node_cidr }#"
---
apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
name: l2-policy
spec:
loadBalancerIPs: true
# NOTE: interfaces might need to be set if you have more than one active NIC on your hosts
# interfaces:
# - ^eno[0-9]+
# - ^eth[0-9]+
nodeSelector:
matchLabels:
kubernetes.io/os: linux
#% if cilium_bgp_enabled %#
---
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisement-config
labels:
advertise: bgp
spec:
advertisements:
- advertisementType: Service
service:
addresses:
- LoadBalancerIP
selector:
matchExpressions:
- { key: somekey, operator: NotIn, values: ["never-used-value"] }
---
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeerConfig
metadata:
name: bgp-peer-config-v4
spec:
families:
- afi: ipv4
safi: unicast
advertisements:
matchLabels:
advertise: bgp
---
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPClusterConfig
metadata:
name: bgp-cluster-config
spec:
nodeSelector:
matchLabels:
kubernetes.io/os: linux
bgpInstances:
- name: instance-#{ cilium.bgp.node_asn }#
localASN: #{ cilium.bgp.node_asn }#
peers:
- name: peer-#{ cilium.bgp.router_asn }#-v4
peerASN: #{ cilium.bgp.router_asn }#
peerAddress: #{ cilium.bgp.router_addr }#
peerConfigRef:
name: bgp-peer-config-v4
#% endif %#
@@ -0,0 +1,13 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: cilium
spec:
interval: 15m
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: 1.20.1
url: oci://quay.io/cilium/charts/cilium

Some files were not shown because too many files have changed in this diff Show More