{
  "markdown": "# talos-platform-apps\n\n[![Talos Linux](https://img.shields.io/badge/Talos%20Linux-1.13.0-ff7300?style=flat-square)](https://www.talos.dev/)\n[![Kubernetes](https://img.shields.io/badge/Kubernetes-1.36.0-326ce5?style=flat-square&logo=kubernetes)](https://kubernetes.io/)\n[![Cilium](https://img.shields.io/badge/Cilium-1.19.3-F8C517?style=flat-square&logo=cilium)](https://cilium.io/)\n[![Gateway API](https://img.shields.io/badge/Gateway%20API-v1.2-326CE5?style=flat-square&logo=kubernetes)](https://gateway-api.sigs.k8s.io/)\n[![Helm](https://img.shields.io/badge/Helm-v3-0F1689?style=flat-square&logo=helm)](https://helm.sh/)\n[![cosign](https://img.shields.io/badge/cosign-2.4%2B-2E7D9A?style=flat-square&logo=sigstore)](https://github.com/sigstore/cosign)\n[![ORAS](https://img.shields.io/badge/ORAS-1.2%2B-1E3F66?style=flat-square)](https://oras.land/)\n[![Conftest](https://img.shields.io/badge/Conftest-OPA%20Rego-7D4698?style=flat-square&logo=openpolicyagent)](https://www.conftest.dev/)\n[![ArgoCD](https://img.shields.io/badge/ArgoCD-7.7-EF7B4D?style=flat-square&logo=argo)](https://argo-cd.readthedocs.io/)\n[![cert-manager](https://img.shields.io/badge/cert--manager-1.17-0A6E32?style=flat-square)](https://cert-manager.io/)\n[![kind](https://img.shields.io/badge/kind-local%20K8s-326CE5?style=flat-square&logo=kubernetes)](https://kind.sigs.k8s.io/)\n[![mkcert](https://img.shields.io/badge/mkcert-Local%20TLS-1F305F?style=flat-square)](https://github.com/FiloSottile/mkcert)\n[![Devbox](https://img.shields.io/badge/Devbox-Nix--based-31135a?style=flat-square)](https://www.jetify.com/devbox/)\n[![direnv](https://img.shields.io/badge/direnv-2.36%2B-FFD400?style=flat-square)](https://direnv.net/)\n[![Taskfile](https://img.shields.io/badge/Taskfile-v3-29BEB0?style=flat-square&logo=Task)](https://taskfile.dev/)\n[![GitHub Actions](https://img.shields.io/badge/CI-GitHub%20Actions-2088FF?style=flat-square&logo=githubactions&logoColor=white)](https://github.com/features/actions)\n\nOCI sub-layers of the Devoba Talos platform: `automation`, `databases`, `identity`, `lifecycle`, `network`, `observability`, `registry`, `secrets`, `storage-block`, and `storage-objects`. Pre-rendered manifests, cosign-signed (keyless); SLSA v1 provenance and CycloneDX SBOM attestations are planned (phase 2+, see `task attest`). Consumed by consumer-cluster repos.\n\n## Purpose\n\nThis repo is the **central platform catalog** of the Devoba Talos platform: **everything that is not substrate** (does not belong in `talos-platform-base`) lives here as independently versioned, signed OCI artifacts — Helm charts + values + optional custom manifests, pre-rendered in CI into final manifests. **Consumer-cluster repos draw from the catalog** by referencing exactly the OCI components they need (by tag / Argo `targetRevision`, not by Helm render at apply time). Division of labor: **Base = substrate, Apps = catalog, Consumer = composition** — whatever is not substrate belongs in the catalog, never in Base.\n\nRationale: deterministic, reviewable deployment artifacts with cryptographic supply-chain verification. A cluster update is a tag bump in the consumer configuration. See [ADR-0009](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0009-platform-layer-model.md).\n\n## Sub-layer and component overview\n\nOCI distribution is **per component** (ADR-0009, revision 2026-05-26). The sub-layer remains a directory grouping and tag namespace.\n\n| Sub-layer | Components | Backlog issue |\n|---|---|---|\n| [`automation`](sub-layers/automation/) | renovate, velero | #16 |\n| [`databases`](sub-layers/databases/) | cnpg | #15 |\n| [`identity`](sub-layers/identity/) | dex | #47 |\n| [`lifecycle`](sub-layers/lifecycle/) | crossplane, ipxe, providers, compositions | #12 |\n| [`network`](sub-layers/network/) | multus-cni-crds | #48 |\n| [`observability`](sub-layers/observability/) | prometheus-operator, loki, mimir, tempo, alloy, grafana | #17 |\n| [`registry`](sub-layers/registry/) | harbor | #14 |\n| [`secrets`](sub-layers/secrets/) | external-secrets, clustersecretstore-defaults | #15a |\n| [`storage-block`](sub-layers/storage-block/) | democratic-csi, synology-csi | #50 |\n| [`storage-objects`](sub-layers/storage-objects/) | garage, garage-buckets | #13 |\n\nEach sub-layer has a `README.md` with a component table including sync-wave order. Each component has its own `README.md` + `compatibility.yaml` with a `requires` block (component dependencies, including cross-sub-layer ones such as `databases/cnpg` for Harbor).\n\n## Local Setup\n\nThe dev environment runs entirely on **Devbox** (Nix-based) + **direnv**. Tool versions are pinned per repo and on `PATH` automatically after `cd` — no global `brew install` needed.\n\n### Prerequisites\n\n| Tool | Version | Installation note |\n|---|---|---|\n| **Devbox** | ≥ 0.16 | `curl -fsSL https://get.jetify.com/devbox \\| bash` |\n| **direnv** | ≥ 2.36 | macOS: `brew install direnv`; Linux: distro package. Hook it into your shell (see [direnv.net/docs/hook.html](https://direnv.net/docs/hook.html)) |\n| **git** | ≥ 2.40 | already installed |\n\n### Setup\n\n```bash\ngit clone git@github.com:devobagmbh/talos-platform-apps.git\ncd talos-platform-apps\ndirenv allow\nlefthook install   # activate the Git pre-commit / commit-msg hooks (lint, signing, secrets, scope)\n```\n\n`direnv allow` triggers the `.envrc`, which activates Devbox. On first invocation Devbox installs all tools (`helm`, `kubectl`, `cosign`, `oras`, `syft`, `go-task`, `yq`, `jq`, `sops`, `age`) into a reproducible Nix store. Subsequent `cd`s into the repo switch the environment automatically.\n\n`lefthook install` wires the local Git hooks (`.git/hooks/`) — **required once per clone** so the pre-commit gates (including the commit-signing check below) actually run; without it an unsigned or non-conforming commit is caught only later on the server.\n\n### Commit signing (required for merge)\n\n`main` enforces **signed commits** (branch protection `required_signatures`). An unsigned commit makes a PR `mergeStateStatus: BLOCKED` even when review and checks are green. The fix is to **sign the commit** — never to admin-override the gate. (This is **git commit signing**, distinct from the **cosign OCI artifact signing** done by CI — see [Render / sign / publish workflow](#render--sign--publish-workflow).)\n\nConfigure signing **once, globally** on your machine. Commit signing is a per-developer, per-machine identity setting — a global config then signs commits in **every** repository, **every** fresh clone, and **every** git worktree automatically, so there is no per-repo or per-clone step to remember:\n\n```bash\ngit config --global gpg.format ssh\ngit config --global user.signingkey ~/.ssh/id_ed25519.pub   # your PUBLIC key (.pub); adjust if your key has a different name\ngit config --global commit.gpgsign true\n```\n\n> **Note:** `user.signingkey` points at the **public** key (`.pub`), not the private key. Global `commit.gpgsign true` enables signing in **all** your repositories — if you already sign other projects (e.g. with GPG), run the three commands with `--local` inside this clone instead, so your machine-wide `gpg.format`/key are not overwritten.\n\nFor the green **Verified** badge three conditions MUST additionally hold on GitHub (these are per-account and cannot be scripted from a repo):\n\n1. The **same** public key is registered as a **Signing key** (Settings → SSH and GPG keys → *New SSH key* → key type **Signing Key**) — a separate entry from the Authentication key, even with identical key material.\n2. Your committer email is a **verified** email on that same account.\n3. If the key is **passphrase-protected**, it is loaded into the ssh-agent (`ssh-add --apple-use-keychain <key>` on macOS) — otherwise non-interactive / agent-driven commits fail to sign and are rejected.\n\n**Verify** via the GitHub **Verified** badge on a pushed commit (the authoritative check). The local `git log --show-signature -1` additionally needs a configured `gpg.ssh.allowedSignersFile` to print `Good \"git\" signature`; without it a correctly-signed commit shows as unverifiable locally even though GitHub accepts it.\n\n### Tools provided by Devbox\n\nSee `devbox.json`. Versions are pinned in `devbox.lock` as needed — updates happen in a controlled manner via `devbox update`.\n\n### Tasks (instead of make)\n\n`go-task` replaces make. Tasks are declared in `Taskfile.yml`. Example targets:\n\n```bash\ntask render:one -- lifecycle/crossplane        # render one component to rendered/manifest.yaml\ntask sign       -- lifecycle/crossplane v0.1.0 # cosign sign of the published OCI tag\ntask attest     -- lifecycle/crossplane v0.1.0 # SBOM + SLSA provenance (deferred stub, phase 2+)\ntask publish    -- lifecycle/crossplane v0.1.0 # render → package → push → sign in one go\ntask ci                                        # local reproduction of the GHA pipeline\n```\n\n### Local live testing (Talos + ArgoCD)\n\nFor end-to-end tests of individual sub-layers (render → OCI push → Argo sync → apply) there is a prod-conformant **Talos** cluster (docker provisioner) — the same substrate as the consumer clusters (Talos nodes, Cilium CNI, Gateway API, kube-proxy off, KubePrism) — with a local OCI registry behind `registry.localhost.direct` (mkcert TLS):\n\n```bash\ntask local:up                                  # Talos + Cilium + Gateway + ArgoCD + registry bridge\ntask local:publish -- lifecycle/crossplane 0.0.0-dev  # push the component into the local registry\ntask local:apply   -- lifecycle 0.0.0-dev      # create the sub-layer's Argo Applications\ntask local:argo:ui                             # open https://argocd.localhost.direct\ntask local:down                                # tear everything down\n```\n\nFull architecture, endpoints, component details, and troubleshooting: [`local/README.md`](local/README.md).\n\n### CI\n\nThe production pipeline runs on **GitHub Actions** (workflows under `.github/workflows/`). Triggers: PRs (render + lint, no push) and tag push `<sub-layer>/<component>-vX.Y.Z` (render + OCI push + cosign sign + verify). Tags are normally cut by release-please (see § Release automation); cosign signing is keyless via the GHA OIDC identity (`oci-publish.yml@refs/tags/...`).\n\n**Three binding CI rules** for this and all other platform repos:\n\n1. **Devbox cache active**: every job uses `jetify-com/devbox-install-action` with `enable-cache: true`. Tool versions come exclusively from `devbox.json`/`devbox.lock` — no separate `actions/setup-go`/`-helm`/`-kubectl` steps. This makes the CI environment byte-identical to the workstation, and builds are cache-warm after the first run.\n2. **Locally reproducible**: every single task in `Taskfile.yml` runs on the workstation exactly as in CI. Before `git push` the full pipeline is replayed locally (`task ci`). No GHA-specific code in tasks — outer logic (OIDC, tag detection, matrix) stays in the workflow.\n3. **Pipeline = thin task caller**: workflow steps only call `task <name>`. No inline `helm template`/`oras push`/`cosign sign` commands in the YAML. Whoever wants to change pipeline behavior changes the task — workflow diffs stay minimal and easy to review.\n\n## Render / sign / publish workflow\n\n```\nHelm chart + values\n        │\n        ▼\n helm template\n        │\n        ▼\n rendered/manifest.yaml\n        │\n        ▼\n task package → <component>-<tag>.tar.gz   (single OCI layer, tar of the Kustomize base: kustomization.yaml + manifest.yaml)\n        │\n        ▼\noras push ghcr.io/devobagmbh/talos-platform-apps/<sub-layer>/<component>:<tag>\n        │\n        ▼\n cosign sign --yes\n        │\n        ▼\n cosign verify   (keyless; identity oci-publish.yml@refs/tags/...)\n\n # phase 2+ (deferred — NOT wired in oci-publish.yml today; see `task attest`):\n #   syft → CycloneDX SBOM → cosign attest\n #   slsa-github-generator → provenance → cosign attest\n```\n\n### Release automation (release-please)\n\nReleases are automated per component — you do **not** hand-cut tags in the normal flow:\n\n1. Merge a `feat`/`fix` commit that touches exactly one `sub-layers/<sl>/components/<c>/` (the lefthook `lint:commit-scope` gate enforces one component per commit, because release-please maps commits to components by path).\n2. `release-please.yml` opens (or updates) a **per-component release PR** that bumps the version and updates the changelog.\n3. Merging that release PR cuts the tag `<sub-layer>/<component>-vX.Y.Z`. The tag is created with a **GitHub App installation token** (not the built-in `GITHUB_TOKEN`, whose events do not cascade to other workflows), so the tag-push triggers `oci-publish.yml`, which renders → pushes → signs → verifies the component. The cosign signing identity stays `oci-publish.yml@refs/tags/...` — unchanged for `task verify` and for consumer-side Kyverno verification.\n\nThe per-component version SoT is [`.release-please-manifest.json`](.release-please-manifest.json); [`release-please-config.json`](release-please-config.json) enumerates every component (not-yet-implemented stubs carry `initial-version: 0.1.0`); every `components/` directory is a release package — a *stack* like `kube-prometheus-stack` is a composition documented in its sub-layer README, not a `components/` directory.\n\n**One-time setup (org admin).** release-please cuts tags via a GitHub App token (short-lived, minted per run — no long-lived PAT). Create a GitHub App in `devobagmbh` with repo permissions **Contents: Read & Write**, **Pull requests: Read & Write**, **Issues: Read & Write**; **install it on this repository only** (not org-wide — limits the App key's blast radius); then set repo variable `RELEASE_PLEASE_APP_ID` and repo secret `RELEASE_PLEASE_APP_PRIVATE_KEY`. Until `RELEASE_PLEASE_APP_ID` is set the `release-please` job is **skipped** (neutral, no failing check); manual tag push remains available for releases in the meantime.\n\n**Manual / backfill tag** — a hand-pushed `<sub-layer>/<component>-vX.Y.Z` tag also triggers `oci-publish.yml` directly (first-time backfill or a hotfix). Caveat: push **at most three tags per `git push`** — tags beyond the third in a single push raise no workflow run at all (silently), so batch larger backfills.\n\n**Adoption cutover (pre-existing components).** `bootstrap-sha` in `release-please-config.json` marks where release-please starts managing this repo. Component changes merged *before* that SHA are **not** auto-released by the first run — release-please anchors on its own last release PR (or `bootstrap-sha`), never on the manually backfilled tags, and the manifest already records each component's last published version. Pre-cutover unreleased changes are therefore published via the **manual-backfill path above** when a component next warrants a version (`bootstrap-sha`/`last-release-sha` are top-level-only — there is no per-package config alternative). The pending pre-cutover backfill is tracked in issue #259.\n\n**Recovering a stuck release** — if a release PR merged but no OCI artifact appeared, the cut tag did not reach `oci-publish.yml`. Check the [Actions tab](../../actions/workflows/oci-publish.yml) for a run on that tag:\n\n- A run exists but **failed** (e.g. a transient upstream Helm-repo error during render) → re-run it from the Actions UI (*Re-run failed jobs*). No tag change needed.\n- **No run** exists (the tag event never fired) → re-push the tag to re-raise the event (`oci-publish.yml` has no `workflow_dispatch`, so a tag re-push is the only trigger). Re-push the tag **at the commit it already points at** (the release PR's merge commit), never at `HEAD`, or a different tree is published — capture that commit *before* deleting the tag, and confirm it is the intended release commit (not a moved or tampered ref) before re-signing:\n\n  ```bash\n  rel_sha=$(git rev-list -n 1 <sub-layer>/<component>-vX.Y.Z)  # commit the tag points at — capture FIRST\n  git push origin :refs/tags/<sub-layer>/<component>-vX.Y.Z    # delete the remote tag\n  git tag -fs <sub-layer>/<component>-vX.Y.Z \"$rel_sha\"        # re-create at the same commit (signed)\n  git push origin <sub-layer>/<component>-vX.Y.Z               # re-push → triggers oci-publish.yml\n  ```\n\n  While the tag is deleted, a consumer Argo app pinning it by `targetRevision` sees a transient gap until the re-push lands.\n\n**Rotating the App key** — generate a new private key in the GitHub App's settings, update the repo secret `RELEASE_PLEASE_APP_PRIVATE_KEY` with the new PEM, then delete the old key in the App settings. `RELEASE_PLEASE_APP_ID` is unchanged. Old keys stay valid until deleted, so there is no downtime window.\n\n## Conventions\n\n- **Per-component versioning**: SemVer per component (`<sub-layer>/<component>-vMAJ.MIN.PATCH`). Each component has an independent lifecycle (managed by release-please).\n- **OCI paths**: `ghcr.io/devobagmbh/talos-platform-apps/<sub-layer>/<component>:<tag>` as the manifest, same path for SBOM/provenance attestations.\n- **Signing**: cosign keyless (OIDC via the GitHub Actions workflow identity). Verification in consumer clusters via the Kyverno ClusterPolicy `image-verify-platform-oci` (see [Issue #21](https://github.com/devobagmbh/talos-platform-docs/issues/21)).\n- **Value separation**: cluster-specific Helm values stay in the consumer-cluster repos. This layer holds defaults and shared values.\n- **Language**: English throughout — code, comments, READMEs, and docs (platform policy 2026-06-03). Code and Helm values follow upstream conventions (English).\n- **Tools**: all dev-relevant binaries come from Devbox — direct `brew install <tool>` is forbidden to avoid version drift.\n- **Consumer composition**: consumer-cluster repos (layer 3) reference the OCI components by tag / Argo `targetRevision` and compose their cluster configuration from them. Which subset a consumer uses lives in the respective consumer repo, not here.\n\n## Related docs\n\n- [ADR-0009 — Platform-Layer-Model](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0009-platform-layer-model.md)\n- [ADR-0012 — Platform-Registry-Proxy (Harbor)](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0012-platform-registry-proxy.md)\n- [ADR-0013 — In-cluster registry (Harbor on both clusters)](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0013-in-cluster-registry.md)\n- [ADR-0015 — Monitoring architecture (LGTM-A)](https://github.com/devobagmbh/talos-platform-docs/blob/main/adr/0015-monitoring-architecture.md)\n",
  "bytes": 18395,
  "sha": "dbefa8764c5eec411dfebedda7ef313d7d05ae3ef1c5665e3d5c46d35583b0b7",
  "repo_slug": "devobagmbh/talos-platform-apps",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_devobagmbh_talos_platform_apps_knowledge_df78f8c5/readme"
}