The NodeConfig custom resource
NodeConfig is a namespaced custom resource (group keel.tlspu.io, version
v1, short name nc). Each NodeConfig describes one periodic task that Keel
applies on every matching node.
Schema
apiVersion: keel.tlspu.io/v1
kind: NodeConfig
metadata:
name: <name>
namespace: <namespace>
spec:
type: <string> # required; selects the handler
interval: <duration> # optional; e.g. "1h", "10m"; defaults to --default-interval
params: # optional; arbitrary key/value strings
<key>: <value> # values are templated against facts
services: # optional; templated systemd service names
- <service>
nodeSelector: # optional; Kubernetes LabelSelector
matchLabels:
node-role: worker
watchConfigMaps: # optional; triggers in the NodeConfig namespace
- name: application-config
spec.type
Required. Selects the handler that interprets the params. Built-in values:
file, package. The optional admission webhook rejects unknown types; without it, unknown types
are reported in status as errors. See
adding-a-type.md to add more.
spec.interval
Optional. How often the task runs, e.g. 30m, 6h, 24h. When unset, the
operator's --default-interval (10 minutes) is used. Durations use Go syntax (no d suffix). Values must be positive.
Successful runs repeat on the interval; failed runs use controller backoff.
Spec edits and watched ConfigMap changes bypass the interval.
spec.params
Optional map of arbitrary string values. Values are resolved through Go templating against the facts store before being passed to the handler. Unknown template references fail the run and are reported in status. See facts and templates for available fields and helpers.
spec.services
Optional list of systemd service names, rendered using the same facts and helpers
as params. Rendering and unit-name checks finish before applying the handler.
Use literal names rather than paths, options or wildcard patterns. They are
restarted when the handler reports a change (e.g. a file's contents differed),
or when retrying a pending restart from an earlier run. systemctl daemon-reload
is run before the restarts. Adding a service to an otherwise unchanged
configuration does not itself trigger a restart. See handlers
for partial failure behavior.
spec.nodeSelector
Optional Kubernetes LabelSelector, supporting matchLabels and
matchExpressions. Omitted or empty selects all nodes. Selection uses the
current Node labels; local Node changes enqueue configs for reevaluation.
Excluded nodes do not apply configurations or remove previously applied state,
and their last status entry can remain from an earlier matching run. Newly
matching nodes run according to their last successful interval and current spec.
spec.watchConfigMaps
Optional list of {name: ...} references to ConfigMaps in the same namespace.
Create, update, delete and recreation events enqueue referring NodeConfigs. A
hash of referenced names, UIDs and resource versions is persisted per node, so a
changed trigger bypasses a successful run's remaining interval, including after
agent restarts. Missing ConfigMaps are valid and represented in the hash. A
ConfigMap is a trigger only: its data is not injected into facts or parameters.
For example, an external controller can update an annotation on the watched
ConfigMap when a package repository or other external source changes.
Types
file
Maintain a file's contents on the host.
| Param | Required | Description |
|---|---|---|
filename |
yes | Absolute path of the file to maintain |
contents |
yes | Desired contents (may be empty) |
mode |
no | Permission bits in octal, e.g. "0644" (default 0644 when writing content) |
The file is written atomically. When content and mode already match, no change
is reported and nothing is rewritten. Without mode, existing permissions are
left alone only when contents already match; a content replacement uses 0644.
Specify mode explicitly when permissions matter. Special bits
(setuid/setgid/sticky) in mode are ignored. See handlers for
ownership, symlinks and replacement limitations.
package
Ensure a package is installed on the host.
| Param | Required | Description |
|---|---|---|
package_name |
yes | Name of the package |
version |
no | Exact version to ensure installed |
The package manager is detected from the host /etc/os-release
(apt/dnf/apk/zypper/pacman). If the package is already installed (and the
version matches), no change is reported. Omitting version ensures presence
without upgrading an installed package. See handlers for exact
OS detection, backend commands and version-pinning limitations.
Status
status:
observedGeneration: 3
nodes:
node-01:
lastRunTime: "2026-10-15T12:00:00Z"
lastResult: success # success | error
changed: true
observedGeneration: 3
triggerDigest: "<sha256>" # present when ConfigMaps are watched
restartPending: false # true when a service restart needs retrying
message: configuration applied, services restarted
status.nodes is keyed by node name, because every node runs its own instance
and writes only its own entry (via a per-node JSON merge patch, so pods never
clobber each other). The first result and meaningful outcome, generation or
trigger changes are persisted; identical steady-state no-op results do not
rewrite status. Per-node observedGeneration controls spec-change scheduling; the top-level
value is informational and may reflect a different node. triggerDigest tracks
external ConfigMap changes. lastRunTime is the most recently persisted run,
which may precede successful no-op runs suppressed from status.
lastResult is success or error;
message explains the outcome (e.g. an error message, or whether services were
restarted). If a service restart fails after a change, restartPending is set
and the restart is retried on the next run.
Lifecycle
Deleting a NodeConfig stops future reconciliation after agents observe deletion. It does not revert files, uninstall packages, restore services or cancel a host operation already in progress. Excluding a node through the selector also leaves host state in place. There is no suspend field or built-in absent state. See the user guide for change and removal workflows.
Example
apiVersion: keel.tlspu.io/v1
kind: NodeConfig
metadata:
name: install-htop
namespace: tak-os
spec:
type: package
interval: 6h
params:
package_name: htop
