MCPcopy Index your code
hub / github.com/cozystack/talm

github.com/cozystack/talm @v0.32.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.32.0 ↗ · + Follow
1,436 symbols 4,902 edges 131 files 1,220 documented · 85%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Talm

Manage Talos the GitOps Way!

Talm is just like Helm, but for Talos Linux

Features

While developing Talm, we aimed to achieve the following goals:

  • Automatic Discovery: In a bare-metal environment, each server may vary slightly in aspects such as disks and network interfaces. Talm enables discovery of node information, which is then used to generate patches.

  • Ease of Customization: You can customize templates to create your unique configuration based on your environment. The templates use the standard Go templates syntax, enhanced with widely-known Helm templating logic.

  • GitOps Friendly: The patches generated do not contain sensitive data, allowing them to be stored in Git in an unencrypted, open format. For scenarios requiring complete configurations, the --full option allows the obtain a complete config that can be used for matchbox and other solutions.

  • Simplicity of Use: You no longer need to pass connection options for each specific server; they are saved along with the templating results into a separate file. Per-node configuration sits in a single modeline-annotated nodes/<name>.yaml; the first -f file is the anchor and any subsequent -f files stack onto its rendered config as side-patches (see "Apply with side-patches" below).

  • Compatibility with talosctl: We strive to maintain compatibility with the upstream project in patches and configurations. The configurations you obtain can be used with the official tools like talosctl and Omni.

Installation

Homebrew

For macOS and Linux users, the recommended way to install talm is with Homebrew.

brew install talm

Binary

Download binary from Github releases page

Or use simple script to install it:

curl -sSL https://github.com/cozystack/talm/raw/refs/heads/main/hack/install.sh | sh -s

Windows

Windows is supported. Download the talm-windows-*.zip archive from the releases page and extract talm.exe. On Windows, template paths passed to the -t / --template flag accept either \ or / separators, so -t templates\controlplane.yaml and -t templates/controlplane.yaml are equivalent. Other path flags (--talosconfig, -f / --file) are delegated to the underlying OS file loader and follow standard Windows path rules.

Getting Started

Create new project

mkdir newcluster
cd newcluster
talm init -p cozystack -N myawesomecluster

talm init refuses to run when the current directory is inside an existing talm project (it would otherwise walk up and partially overwrite the parent). To create a project under the current directory anyway — e.g. a sub-project nested inside another talm project — pass --root . explicitly. To re-initialise the parent itself, run talm init from the parent directory.

To pin a specific Talos installer image at init time (e.g. a Talos Factory image with extensions), pass --image:

talm init -p cozystack -N myawesomecluster --image factory.talos.dev/installer/<sha256>:<version>

--image rewrites the top-level image: field in the preset's values.yaml before write. The flag is honored on initial init only — for an existing project, edit values.yaml directly. The cozystack preset declares image:; the generic preset does not, so --image --preset generic is rejected up front.

To set the Kubernetes control-plane URL at init time, pass --cluster-endpoint:

talm init -p cozystack -N myawesomecluster --cluster-endpoint https://vip.example.test:6443

--cluster-endpoint writes the URL into values.yaml::endpoint, which the chart renders into cluster.controlPlane.endpoint of every node's MachineConfig (the URL kubelet and kube-proxy dial). The flag is honored on initial init only — for an existing project, edit values.yaml directly.

--endpoints and --cluster-endpoint address different concepts: --endpoints (plural, list) populates the talosconfig context for the talosctl client; --cluster-endpoint (singular, full URL) populates the Kubernetes control-plane address inside the chart. When --endpoints is given a single value, init auto-derives values.yaml::endpoint as https://<that>:6443 — the single-target case is unambiguous. Multi-endpoint inputs never auto-derive (picking one node would silently couple cluster availability to it); the operator must pass --cluster-endpoint explicitly or fill values.yaml::endpoint later. The init flow prints a hint at the end when the field is left empty.

Edit values.yaml to set your cluster's control-plane endpoint if neither flag set it. This is the URL every node's kubelet and kube-proxy will dial. The chart leaves it empty by default so a missed override fails loudly instead of silently embedding a placeholder.

Endpoint / floatingIP combinations:

  • cozystack VIP setup: set endpoint and floatingIP together to the same IP — single shared VIP.
  • single-node cluster: set endpoint to the node's routable IP and leave floatingIP blank.
  • multi-node with external load balancer: set endpoint to the LB URL and leave floatingIP blank.

When vipLink is left empty the chart picks the link automatically using a two-step rule:

  1. Longest-prefix match across configurable links. If floatingIP falls inside the CIDR of any address on a configurable link (physical NIC, bond, VLAN, bridge), the most specific subnet wins. This handles the Hetzner-style topology where a public NIC carries the default route and a VLAN child carries the private cluster subnet — the VIP lands on the VLAN child.
  2. Fallback to the IPv4-default-gateway-bearing link. Used when no configurable link's CIDR contains the floatingIP — typical for upstream-routable VIPs that arrive via the default route.

Addresses on links the chart does not emit a per-link document for (Wireguard, kernel-managed loopback, slave NICs of a bond, anything outside the configurable set) are skipped — a VIP pinned there would have no surrounding network document.

Set vipLink explicitly when the target link does not yet exist on the live system at first apply (typically a VLAN sub-interface). The chart pins Layer2VIPConfig.link to it directly and emits the document even on a fresh node where discovery has not yet populated the addresses table. The chart does not auto-emit a LinkConfig or VLANConfig for the override link; the operator is responsible for ensuring the link comes up, typically by adding a LinkConfig or VLANConfig for that link to the per-node body overlay alongside vipLink.

Subnet-selector fields (kubelet.validSubnets, etcd.advertisedSubnets) are derived automatically from the node's default-gateway-bearing link, so no override is needed unless you have a multi-homed node that requires a specific subnet pinned.

Boot Talos Linux node, let's say it has address 192.0.2.4. Then:

# values.yaml (single-node example matching the 192.0.2.4 node below)
endpoint: "https://192.0.2.4:6443"
floatingIP: ""

Gather node information:

talm --nodes 192.0.2.4 -e 192.0.2.4 template -t templates/controlplane.yaml -i > nodes/node1.yaml

Edit nodes/node1.yaml file:

# talm: nodes=["192.0.2.4"], endpoints=["192.0.2.4"], templates=["templates/controlplane.yaml"]
machine:
    network:
        # -- Discovered interfaces:
        # enx9c6b0047066c:
        #   name: enp193s0f0
        #   mac:9c:6b:00:47:06:6c
        #   bus:0000:c1:00.0
        #   driver:bnxt_en
        #   vendor: Broadcom Inc. and subsidiaries
        #   product: BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller)
        # enx9c6b0047066d:
        #   name: enp193s0f1
        #   mac:9c:6b:00:47:06:6d
        #   bus:0000:c1:00.1
        #   driver:bnxt_en
        #   vendor: Broadcom Inc. and subsidiaries
        #   product: BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller)
        interfaces:
            - interface: enx9c6b0047066c
              addresses:
                - 192.0.2.4/26
              routes:
                - network: 0.0.0.0/0
                  gateway: 192.0.2.1
        nameservers:
            - 8.8.8.8
            - 8.8.4.4
    install:
        # -- Discovered disks:
        # /dev/nvme0n1:
        #    model: SAMSUNG MZQL21T9HCJR-00A07
        #    serial: S64GNE0RB00153
        #    wwid: eui.3634473052b001530025384500000001
        #    size: 1.75 TB
        # /dev/nvme1n1:
        #    model: SAMSUNG MZQL21T9HCJR-00A07
        #    serial: S64GNE0R811820
        #    wwid: eui.36344730528118200025384500000001
        #    size: 1.75 TB
        disk: /dev/nvme0n1
    type: controlplane
cluster:
    clusterName: talm
    controlPlane:
        endpoint: https://192.0.2.4:6443

Note: output format depends on Talos version.

Selected via Chart.yaml (templateOptions.talosVersion) or --talos-version:

  • Talos < v1.12 — single YAML document with machine.network and machine.registries sections (as shown above).
  • Talos >= v1.12 — multi-document format with separate typed documents instead of the deprecated monolithic fields.

For v1.12+ multi-doc output, one document is emitted per configurable link on the node, plus a fixed pair on every render:

  • HostnameConfig and ResolverConfig — always emitted.
  • LinkConfig — physical NICs.
  • BondConfig — bond masters. Bond slaves are filtered out so they do not collide with the master's document.
  • VLANConfig — VLAN sub-interfaces.
  • BridgeConfig — bridges, symmetric to BondConfig for bonds. Ports discovered via spec.slaveKind == "bridge" + spec.masterIndex; STP / VLAN-filtering settings reach the output when the bridge controller reports them on spec.bridgeMaster.
  • Layer2VIPConfig — controlplane nodes when floatingIP is set.
  • RegistryMirrorConfig — cozystack chart only.

Per-link emission rules:

  • The link carrying the IPv4 default route gets the routes.gateway entry on its document; every other link is emitted gateway-less. Applies uniformly to LinkConfig, BondConfig, VLANConfig, BridgeConfig.
  • Both IPv4 and IPv6 global-scope addresses on a link are surfaced.
  • The operator-declared floatingIP is stripped from per-link addresses so the VIP currently held by a leader does not leak into the static document.

Multi-NIC nodes therefore produce one document per NIC, not one document total.

Version compatibility (templateOptions.talosVersion / --talos-version). This setting must match the Talos version actually running on the target node — i.e. the maintenance ISO/PXE the node booted from for apply -i, or the installed Talos for an authenticated apply. It is not the same as install.image, which only controls what gets written to disk after a successful apply. When the configured contract is newer than the running binary, machinery injects fields (e.g. machine.install.grubUseUKICmdline from v1.12) that the running parser does not know, and the apply fails on the node side with failed to parse config: unknown keys found during decoding: .... talm apply runs a best-effort pre-flight check against the running version and prints a warning: pre-flight: ... line with a hint when it detects this mismatch; if the warning is missed, the same hint is appended to the apply error. Either reboot the node into a maintenance image that matches the configured contract, or lower templateOptions.talosVersion / --talos-version to match what is running.

Apply-time safety gates. talm apply and talm upgrade run additional gates around each operation:

  1. Declared-resource existence (--skip-resource-validation opt-out, default on). Before sending the config to the node, the gate walks the rendered MachineConfig, extracts every reference to a host-side resource (network links from v1.12 multi-doc — LinkConfig.name, BondConfig.links[], VLANConfig.parent, BridgeConfig.links[], Layer2VIPConfig.link, HCloudVIPConfig.link, DHCPv4Config.name / DHCPv6Config.name / EthernetConfig.name; v1.11 legacy machine.network.interfaces[].interface; install disk via machine.install.disk literal or machine.install.diskSelector; UserVolumeConfig.provisioning.diskSelector), and verifies each against the node's COSI LinkStatus/Disk snapshots. A reference that doesn't resolve fails the apply with a [blocker] line listing the available names so the typo or migration miss is fixable from the values without re-running discovery. Disk selectors must match at least one (non-readonly, non-CDROM, non-virtual) disk — zero matches block, multiple matches warn (install picks the first). Virtual-link-creator documents (BondConfig.name, VLANConfig.name, BridgeConfig.name, WireguardConfig.name, DummyLinkConfig.name, LinkAliasConfig.name) are intentionally NOT validated against existing links — those .name fields describe new virtual links the apply is creating, not references to pre-existing host resources. The gate also runs a syntactic net-addr walker against StaticHostConfig.name (must parse as an IP literal — the name field on this kind doubles as the IP the hostnames map to), NetworkRuleConfig.ingress[].subnet and .except (per-entry CIDR), and WireguardConfig.peers[].endpoint (host:port; empty / absent endpoint is a listener-only peer, NOT a finding). Out of scope today: machine.disks[].device (extra-disk partitioning); track in a follow-up if you need it. Pass --skip-resource-validation for recovery into a maintenance image with mismatched hardware or pre-staging values for hardware that isn't installed yet.

  2. Pre-apply drift preview (--skip-drift-preview opt-out, default on). Reads the node's current MachineConfig via COSI and prints a +/-/~/= diff of what's about to

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

String
called by 124
pkg/applycheck/diff.go
MergeFileAsPatch
called by 40
pkg/engine/engine.go
Render
called by 34
pkg/engine/helm/engine.go
fileExists
called by 32
pkg/commands/init.go
loadValues
called by 26
pkg/engine/engine.go
EncryptSecretsFile
called by 25
pkg/age/age.go
classifyLookupError
called by 24
pkg/engine/lookup_classify.go
Render
called by 22
pkg/engine/helm/engine.go

Shape

Function 1,379
Struct 21
Method 19
FuncType 10
TypeAlias 7

Languages

Go100%

Modules by API surface

pkg/engine/render_test.go121 symbols
pkg/engine/contract_network_multidoc_test.go61 symbols
pkg/engine/engine.go44 symbols
pkg/engine/contract_machine_test.go41 symbols
pkg/commands/apply.go36 symbols
pkg/age/age.go36 symbols
pkg/commands/preflight_apply_safety_test.go35 symbols
pkg/engine/helm/engine_test.go34 symbols
pkg/engine/contract_cluster_test.go33 symbols
pkg/commands/apply_test.go31 symbols
pkg/commands/contract_root_dispatch_test.go29 symbols
pkg/engine/engine_test.go28 symbols

For agents

$ claude mcp add talm \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact