From 34732771dc035a525fda308ae39751ad5413b2ff Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Mon, 16 Mar 2026 21:04:56 +0000 Subject: [PATCH 01/11] feat: add talos-image-update dispatch handler --- .github/workflows/talos-image-update.yml | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/talos-image-update.yml diff --git a/.github/workflows/talos-image-update.yml b/.github/workflows/talos-image-update.yml new file mode 100644 index 0000000..550c41e --- /dev/null +++ b/.github/workflows/talos-image-update.yml @@ -0,0 +1,41 @@ +name: Update Talos image + +on: + repository_dispatch: + types: [talos-image-update] + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + + - name: Update omni/talos-image.yaml + env: + TALOS_VERSION: ${{ github.event.client_payload.talos_version }} + run: | + sed -i "s|^talos_version:.*|talos_version: ${TALOS_VERSION}|" omni/talos-image.yaml + sed -i "s|^updated_at:.*|updated_at: \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"|" omni/talos-image.yaml + + - name: Open pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TALOS_VERSION: ${{ github.event.client_payload.talos_version }} + run: | + BRANCH="talos-image-update/${TALOS_VERSION}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "${BRANCH}" + git add omni/talos-image.yaml + git diff --cached --quiet && echo "No changes" && exit 0 + git commit -m "chore: update Talos installer image to ${TALOS_VERSION}" + git push origin "${BRANCH}" + gh pr create \ + --title "chore: update Talos installer image to ${TALOS_VERSION}" \ + --body "Auto-generated by talos-images build workflow." \ + --base main \ + --head "${BRANCH}" From ba4a0eec0c23cfae80a5703f5511bd6af910c21d Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Mon, 16 Mar 2026 21:28:40 +0000 Subject: [PATCH 02/11] fix(ci): bump actions/checkout to v6 --- .github/workflows/talos-image-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/talos-image-update.yml b/.github/workflows/talos-image-update.yml index 550c41e..2a9ddb6 100644 --- a/.github/workflows/talos-image-update.yml +++ b/.github/workflows/talos-image-update.yml @@ -12,7 +12,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Update omni/talos-image.yaml env: From 525037ebd0e7d03382cea3662df27eb625bb6366 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Mon, 16 Mar 2026 21:58:44 +0000 Subject: [PATCH 03/11] fix: remove broken kubeconform pre-commit hook, add markdownlint config --- .markdownlint.yaml | 1 + .pre-commit-config.yaml | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) create mode 100644 .markdownlint.yaml diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..ea3cc5e --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1 @@ +MD013: false # line-length: URLs and long paths make this impractical diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9b4985b..4683c6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,14 +10,6 @@ repos: hooks: - id: gitleaks - - repo: https://github.com/yannh/kubeconform - rev: v0.6.7 - hooks: - - id: kubeconform - args: - - -strict - - -ignore-missing-schemas - - -summary - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.44.0 From 397dd97d1c08e746547c8f9b6d341fba32be0b74 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:02:36 +0000 Subject: [PATCH 04/11] chore: update Talos installer image to v1.12.5 (#2) Co-authored-by: github-actions[bot] --- omni/talos-image.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/omni/talos-image.yaml b/omni/talos-image.yaml index 9d8cc88..8c83fe0 100644 --- a/omni/talos-image.yaml +++ b/omni/talos-image.yaml @@ -1,6 +1,6 @@ # Auto-updated by talos-images workflow — do not edit manually -talos_version: v1.9.0 +talos_version: v1.12.5 kubernetes_version: v1.32.3 schematic_id: "" oci_image_ocid: "" -updated_at: "" +updated_at: "2026-03-16T21:19:36Z" From 9e3a80d78a1b61396c0c277c73ff406917f1831c Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Mon, 16 Mar 2026 22:03:14 +0000 Subject: [PATCH 05/11] fix(ci): bump actions/checkout to v6, setup-node to v6 with Node 24 --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51cc282..db0e438 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: name: YAML lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: pip install yamllint --quiet - run: yamllint -c .yamllint.yml . @@ -22,7 +22,7 @@ jobs: name: Secrets detection runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install gitleaks @@ -38,7 +38,7 @@ jobs: name: Kubernetes manifest validation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install kubeconform run: | @@ -58,7 +58,7 @@ jobs: name: Helm lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Helm uses: azure/setup-helm@v4 @@ -98,7 +98,7 @@ jobs: name: API deprecation check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Read target Kubernetes version id: k8s @@ -130,9 +130,9 @@ jobs: name: Markdown lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - node-version: "20" + node-version: "24" - run: npm install -g markdownlint-cli2 - run: markdownlint-cli2 "**/*.md" "#node_modules" From 478e2a0f5d58bb8f9e266c7615d0e78a519f9ab8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 19 Mar 2026 23:42:51 +0000 Subject: [PATCH 06/11] chore: Talos upgrade to v1.12.6 (k8s v1.35.2) --- omni/machine-classes/control-plane.yaml | 2 +- omni/machine-classes/worker.yaml | 2 +- omni/talos-image.yaml | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/omni/machine-classes/control-plane.yaml b/omni/machine-classes/control-plane.yaml index 31071a0..be89d52 100644 --- a/omni/machine-classes/control-plane.yaml +++ b/omni/machine-classes/control-plane.yaml @@ -4,7 +4,7 @@ metadata: id: oci-cp spec: - installImage: ghcr.io/syscode-labs/talos-images/installer:v1.9.0 + installImage: ghcr.io/syscode-labs/talos-images/installer:v1.12.6 matchLabels: # Applied manually in Omni UI after OCI nodes connect via SideroLink. # omnictl machine set-labels role=oci-cp diff --git a/omni/machine-classes/worker.yaml b/omni/machine-classes/worker.yaml index ebdfc54..036b144 100644 --- a/omni/machine-classes/worker.yaml +++ b/omni/machine-classes/worker.yaml @@ -4,7 +4,7 @@ metadata: id: oci-worker spec: - installImage: ghcr.io/syscode-labs/talos-images/installer:v1.9.0 + installImage: ghcr.io/syscode-labs/talos-images/installer:v1.12.6 matchLabels: # Applied manually in Omni UI after OCI nodes connect via SideroLink. # omnictl machine set-labels role=oci-worker diff --git a/omni/talos-image.yaml b/omni/talos-image.yaml index 8c83fe0..fe9efb7 100644 --- a/omni/talos-image.yaml +++ b/omni/talos-image.yaml @@ -1,6 +1,6 @@ # Auto-updated by talos-images workflow — do not edit manually -talos_version: v1.12.5 -kubernetes_version: v1.32.3 -schematic_id: "" -oci_image_ocid: "" -updated_at: "2026-03-16T21:19:36Z" +talos_version: v1.12.6 +kubernetes_version: v1.35.2 +schematic_id: 4a0d65c669d46663f377e7161e50cfd570c401f26fd9e7bda34a0216b6f1922b +oci_image_ocid: ocid1.image.oc1.uk-london-1.aaaaaaaapwpetlngenkj4j5hu6g4ddkppcgobk4t4pwj5hgdzmwwardgik7a +updated_at: 2026-03-19T23:42:51Z From f043168e6ddb2f4ff76471c54c68c0680d5f7354 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri <33348729+syscod3@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:38:16 +0100 Subject: [PATCH 07/11] chore: Talos upgrade to v1.13.5 (#9) * chore: Talos upgrade to v1.13.5 (k8s v1.36.2) * chore: Talos upgrade to v1.13.5 (k8s v1.36.2) --------- Co-authored-by: github-actions[bot] --- omni/machine-classes/control-plane.yaml | 2 +- omni/machine-classes/worker.yaml | 2 +- omni/talos-image.yaml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/omni/machine-classes/control-plane.yaml b/omni/machine-classes/control-plane.yaml index be89d52..e48c50e 100644 --- a/omni/machine-classes/control-plane.yaml +++ b/omni/machine-classes/control-plane.yaml @@ -4,7 +4,7 @@ metadata: id: oci-cp spec: - installImage: ghcr.io/syscode-labs/talos-images/installer:v1.12.6 + installImage: ghcr.io/syscode-labs/talos-images/installer:v1.13.5 matchLabels: # Applied manually in Omni UI after OCI nodes connect via SideroLink. # omnictl machine set-labels role=oci-cp diff --git a/omni/machine-classes/worker.yaml b/omni/machine-classes/worker.yaml index 036b144..2ee0294 100644 --- a/omni/machine-classes/worker.yaml +++ b/omni/machine-classes/worker.yaml @@ -4,7 +4,7 @@ metadata: id: oci-worker spec: - installImage: ghcr.io/syscode-labs/talos-images/installer:v1.12.6 + installImage: ghcr.io/syscode-labs/talos-images/installer:v1.13.5 matchLabels: # Applied manually in Omni UI after OCI nodes connect via SideroLink. # omnictl machine set-labels role=oci-worker diff --git a/omni/talos-image.yaml b/omni/talos-image.yaml index fe9efb7..5e77ee7 100644 --- a/omni/talos-image.yaml +++ b/omni/talos-image.yaml @@ -1,6 +1,6 @@ # Auto-updated by talos-images workflow — do not edit manually -talos_version: v1.12.6 -kubernetes_version: v1.35.2 +talos_version: v1.13.5 +kubernetes_version: v1.36.2 schematic_id: 4a0d65c669d46663f377e7161e50cfd570c401f26fd9e7bda34a0216b6f1922b -oci_image_ocid: ocid1.image.oc1.uk-london-1.aaaaaaaapwpetlngenkj4j5hu6g4ddkppcgobk4t4pwj5hgdzmwwardgik7a -updated_at: 2026-03-19T23:42:51Z +oci_image_ocid: ocid1.image.oc1.uk-london-1.aaaaaaaadheaqve6ikpx5nqn637zmaudgq6dt67tnv7z2t7dnju5tyu6l2ta +updated_at: 2026-07-05T05:59:40Z From c5432790c16ea99dd8d6ef4256a1a9cd499221e5 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri <33348729+syscod3@users.noreply.github.com> Date: Sat, 11 Jul 2026 00:38:25 +0100 Subject: [PATCH 08/11] chore(omni): align cluster template talos.version with the installer (#10) The talos-images upgrade workflow rewrites omni/talos-image.yaml and omni/machine-classes/*.yaml, but never omni/cluster-templates/talos.yaml. The template has therefore drifted: it pinned Talos v1.12.5 while the machine classes install v1.12.6, and it is the file Omni actually applies. Move it to v1.13.5 to match PR #9. kubernetes.version is left at v1.32.3 on purpose and annotated: it is human-owned, and Talos 1.13.x still supports it. --- omni/cluster-templates/talos.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/omni/cluster-templates/talos.yaml b/omni/cluster-templates/talos.yaml index 28e4538..0b1b255 100644 --- a/omni/cluster-templates/talos.yaml +++ b/omni/cluster-templates/talos.yaml @@ -3,9 +3,13 @@ name: oci-talos labels: environment: oci kubernetes: + # Not touched by the talos-images upgrade workflow — bump deliberately, one + # minor at a time. Talos 1.13.x supports k8s 1.31–1.36 (SupportedKubernetesVersions=6 + # counting back from its 1.36.2 default), so this is valid as-is. version: v1.32.3 talos: - version: v1.12.5 + # Keep in step with installImage in omni/machine-classes/*.yaml. + version: v1.13.5 patches: - file: ../patches/cni-none.yaml - file: ../patches/disable-kube-proxy.yaml From 9766cbed6144c97c3c4aed0b46abd617312777e9 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Sat, 11 Jul 2026 13:54:05 +0100 Subject: [PATCH 09/11] feat(omni): ARGO_MODE toggle for in-cluster vs hub Argo topology generate-manifests.sh now takes ARGO_MODE=in-cluster|hub. Default in-cluster keeps today's behaviour (Argo + root App-of-Apps bootstrapped inside the cluster). ARGO_MODE=hub emits only the Cilium inline manifest and omits the two argocd blocks, so the cluster boots with no in-cluster Argo and is managed by a central hub Argo (on the Omni VM k8s) as a spoke. Script-only: the committed inline-manifests.yaml is unchanged until someone regenerates with a chosen mode. --- omni/scripts/generate-manifests.sh | 41 ++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/omni/scripts/generate-manifests.sh b/omni/scripts/generate-manifests.sh index df68ec9..58b1ea1 100755 --- a/omni/scripts/generate-manifests.sh +++ b/omni/scripts/generate-manifests.sh @@ -8,6 +8,16 @@ set -euo pipefail CILIUM_VERSION="${CILIUM_VERSION:-1.17.2}" ARGOCD_VERSION="${ARGOCD_VERSION:-v2.14.4}" +# Argo topology selector. +# in-cluster : bootstrap Argo CD + root App-of-Apps inside this cluster (default). +# hub : omit in-cluster Argo — a central hub Argo (on the Omni VM k8s) manages +# this cluster as a spoke. Cilium is always in-cluster (it is the CNI). +ARGO_MODE="${ARGO_MODE:-in-cluster}" +case "$ARGO_MODE" in + in-cluster|hub) ;; + *) echo "ARGO_MODE must be 'in-cluster' or 'hub', got '${ARGO_MODE}'" >&2; exit 1 ;; +esac + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PATCH_FILE="${SCRIPT_DIR}/../patches/inline-manifests.yaml" TMPDIR=$(mktemp -d) @@ -29,23 +39,26 @@ helm template cilium cilium/cilium \ --set hubble.ui.enabled=true \ > "${TMPDIR}/cilium.yaml" -echo "==> Downloading Argo CD ${ARGOCD_VERSION}..." -curl -sL "https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml" \ - > "${TMPDIR}/argocd.yaml" +if [ "$ARGO_MODE" = "in-cluster" ]; then + echo "==> Downloading Argo CD ${ARGOCD_VERSION}..." + curl -sL "https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml" \ + > "${TMPDIR}/argocd.yaml" + ARGOCD_CONTENT=$(sed 's/^/ /' "${TMPDIR}/argocd.yaml") +fi CILIUM_CONTENT=$(sed 's/^/ /' "${TMPDIR}/cilium.yaml") -ARGOCD_CONTENT=$(sed 's/^/ /' "${TMPDIR}/argocd.yaml") -echo "==> Writing ${PATCH_FILE}..." +echo "==> Writing ${PATCH_FILE} (ARGO_MODE=${ARGO_MODE})..." cat > "${PATCH_FILE}" <> "${PATCH_FILE}" <> "${PATCH_FILE}" < Done. Commit omni/patches/inline-manifests.yaml." +echo "==> Done (ARGO_MODE=${ARGO_MODE}). Commit omni/patches/inline-manifests.yaml." From d4616c38f9c5eb8a35a5d61cb147c51a51abc092 Mon Sep 17 00:00:00 2001 From: Giovanni Ferri Date: Sat, 11 Jul 2026 14:04:36 +0100 Subject: [PATCH 10/11] feat(omni): default ARGO_MODE to hub (homelab central Argo) --- omni/scripts/generate-manifests.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/omni/scripts/generate-manifests.sh b/omni/scripts/generate-manifests.sh index 58b1ea1..de990fb 100755 --- a/omni/scripts/generate-manifests.sh +++ b/omni/scripts/generate-manifests.sh @@ -9,10 +9,11 @@ CILIUM_VERSION="${CILIUM_VERSION:-1.17.2}" ARGOCD_VERSION="${ARGOCD_VERSION:-v2.14.4}" # Argo topology selector. -# in-cluster : bootstrap Argo CD + root App-of-Apps inside this cluster (default). # hub : omit in-cluster Argo — a central hub Argo (on the Omni VM k8s) manages -# this cluster as a spoke. Cilium is always in-cluster (it is the CNI). -ARGO_MODE="${ARGO_MODE:-in-cluster}" +# this cluster as a spoke (default for this homelab). +# in-cluster : bootstrap Argo CD + root App-of-Apps inside this cluster. +# Cilium is always in-cluster (it is the CNI). +ARGO_MODE="${ARGO_MODE:-hub}" case "$ARGO_MODE" in in-cluster|hub) ;; *) echo "ARGO_MODE must be 'in-cluster' or 'hub', got '${ARGO_MODE}'" >&2; exit 1 ;; @@ -54,8 +55,8 @@ cat > "${PATCH_FILE}" < Date: Sat, 11 Jul 2026 18:46:30 +0100 Subject: [PATCH 11/11] feat(omni): hub mode ships argocd-manager SA/token for spoke registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In ARGO_MODE=hub, emit the argocd-manager ServiceAccount, cluster-admin binding, and a named (non-expiring) token Secret as inline manifests, so a hub-mode spoke is registration-ready at first boot. The one manual step (read token → sops → commit) is documented in the hub registration runbook; the inline comment points there rather than restating it. --- omni/scripts/generate-manifests.sh | 41 +++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/omni/scripts/generate-manifests.sh b/omni/scripts/generate-manifests.sh index de990fb..f53e38b 100755 --- a/omni/scripts/generate-manifests.sh +++ b/omni/scripts/generate-manifests.sh @@ -99,11 +99,44 @@ ${ARGOCD_CONTENT} selfHeal: true EOF else -cat >> "${PATCH_FILE}" <> "${PATCH_FILE}" <<'EOF' - # ARGO_MODE=hub — in-cluster Argo intentionally omitted. - # A central hub Argo (on the Omni VM k8s) manages this cluster as a spoke; register it - # there with an Argo cluster secret pointing at this cluster's API over Tailscale. + # ARGO_MODE=hub — no in-cluster Argo. A central hub Argo (on the Omni VM k8s) manages + # this cluster as a spoke. This block ships the ServiceAccount the hub authenticates as, + # so the token exists from first boot. Registration (read token → sops → commit) is the + # single manual step, documented once in: + # syscode-ai-internal-plans/projects/imp/docs/plans/2026-07-11-argo-hub-spoke-registration-runbook.md + - name: argocd-manager + contents: | + apiVersion: v1 + kind: ServiceAccount + metadata: + name: argocd-manager + namespace: kube-system + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: argocd-manager + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin + subjects: + - kind: ServiceAccount + name: argocd-manager + namespace: kube-system + --- + # Non-expiring legacy SA token (auto-token Secrets removed in k8s 1.24). Deliberate: + # a bound short-lived token can't be pre-committed for GitOps registration. + apiVersion: v1 + kind: Secret + metadata: + name: argocd-manager-token + namespace: kube-system + annotations: + kubernetes.io/service-account.name: argocd-manager + type: kubernetes.io/service-account-token EOF fi