[2.0.0] - 2026-08-16
Removed
- Five unused helm variables:
helm_target_groups,helm_target_host,
helm_kubectl_context,helm_validation_timeoutandhelm_log_levelare
gone fromroles/helm/defaults/main.ymland
roles/helm/meta/argument_specs.yml. No task file in the role ever read
them, so setting them changed nothing and produced no warning.
helm_target_groupsandhelm_target_hostwere especially misleading: they
suggested the role filters its own target hosts, which it does not — host
selection belongs in the play (hosts:). Validation behaviour is unaffected:
helm_validation_retriesandhelm_validation_delayare read by
roles/helm/tasks/charts.yml,helm_validation_enabledby
roles/helm/tasks/prerequisites.yml, and together they cover the validation
flow.
Added
-
apt_version_pinfilter plugin: resolves a version prefix to the full
version string apt reports, replacing four hand-rolled Jinja regex copies in
the docker anddocker_compose_v2roles. Documented in
plugins/filter/DOCUMENTATION.ymland covered by
tests/unit/plugins/filter/test_apt_filters.py. -
Configurable fleet bundle readiness wait:
fleet_bundle_wait(true)
andfleet_bundle_wait_timeout(300) inroles/fleet/defaults/main.yml,
both overridable per bundle viawait/wait_timeouton the item and
declared inmeta/argument_specs.yml. The "Manage Fleet Bundles" task
previously applied each bundle with a hardcoded wait forReady, so a serial
loop over many bundles stacked up the 300s timeout per item. Stable bundles
can now be applied fire-and-forget and let Fleet reconcile them
asynchronously; the registered result shape is unchanged. -
k3s container log rotation defaults:
k3s_container_log_max_size
(10Mi) andk3s_container_log_max_files(5) now have defaults in
defaults/main.ymland entries inmeta/argument_specs.yml. Both were
previously only read by anis definedguard in the agent config template,
so out of the box kubelet never receivedcontainer-log-max-size/
container-log-max-filesand container logs grew unbounded. The guards stay
in place, so settingk3s_container_log_max_sizeto""or
k3s_container_log_max_filesto0still omits the respective flag. -
k3s hardening variables: eight new variables defined in both
defaults/main.ymlandmeta/argument_specs.yml—
k3s_secrets_encryption,k3s_protect_kernel_defaults,
k3s_audit_log_enabled,k3s_audit_log_maxage,k3s_anonymous_auth,
k3s_node_restriction,k3s_kubelet_read_only_port_disabled,
k3s_audit_log_levelandk3s_pod_security_admission_profile. All are gated
byk3s_security_hardening, which previously only covered SELinux/AppArmor. -
k3s audit policy: renders
audit-policy.yamland passes
audit-policy-fileto the kube-apiserver when audit logging is enabled.
Without a policy file the apiserver matches no events and the audit log stays
empty. Drops high-volume node/proxy and health-endpoint traffic, caps secrets
and configmaps atMetadata, and logs everything else at
k3s_audit_log_level. -
k3s Pod Security Admission: optional cluster-wide default profile via
k3s_pod_security_admission_profile(empty by default, so no behaviour
change). Renderspsa-config.yamlwithkube-systemexempt and wires it as
admission-control-config-file. -
k3s molecule hardening assertions:
verify.ymlchecks the rendered
config for the hardening flags,k3s secrets-encrypt statusforEnabled,
the credential directory for0700, and that a user-supplied
kube-apiserver-argsurvives the hardening merge. -
Molecule coverage for the five previously-untested roles, each with a
defaultscenario underroles/<role>/molecule/default/and a matching
molecule-<role>job wired intopull-request.yml(qemu/KVM driver,
Ubuntu 22.04 cloud image, pinned toci-ansible-molecule.yml@2026-06-18). -
docker_login molecule — full converge: a
prepare.ymlinstalls Docker via
thedockerrole and starts a throwaway localregistry:2container,
converge logs intolocalhost:5000, and verify asserts the registry appears
underauthsin the Docker client config. -
docker_compose_v2 molecule — full converge:
prepare.ymlinstalls Docker,
converge deploys a minimalnginxcompose project, and verify asserts the
compose plugin, the rendered project file, the systemd unit and a running
container. -
helm, fleet, tailscale molecule — full converge against an
ephemeral k3s cluster. Each scenario gained aprepare.ymlthat installs
arillso.container.k3sin the same QEMU VM and adds whatever upstream
controller the role needs, soconverge,idempotenceandverifyare now
part of thetest_sequence. No external cluster and no repository secret is
involved, which keeps the jobs working on fork pull requests. These scenarios
assert that the roles emit schema-valid resources the API accepts; they do not
assert that the upstream controllers act on them. -
helm molecule exercises the whole deployment chain: the role applies a
HelmChart CR, the k3s Helm controller turns it into an install Job, and verify
asserts the CR, the completed Job and the running Pod. -
fleet molecule installs standalone Fleet (
fleet-crd, thenfleet) and
asserts that the GitRepo and Bundle CRs the role emits are accepted by the
fleet.cattle.ioAPI with the expected spec and labels. Workspaces
(Rancher-onlymanagement.cattle.io/v3), registration tokens (the controller
rotates the secret) and clusters (need a second cluster's kubeconfig) stay out
of the fixture. -
tailscale molecule installs the operator chart with dummy OAuth
credentials and without waiting for the Pod, which registers the
tailscale.comCRDs without a real tailnet, then asserts the ProxyGroup the
role creates.
Fixed
-
docker version pin never matched on the Debian family: apt matches an
=
pin against the full version string and does not expand globs in it, so the
shippeddocker_versiondefault (docker-ce=28.5.2) was unusable on Debian
and Ubuntu — apt reports the version with an epoch and a distro release
suffix.install_docker_debian.ymlandinstall_docker_ubuntu.ymlnow
resolve the full version string on the host before pinning. Every existing
docker scenario blankeddocker_version, so the shipped default had never
been exercised; a newpinnedmolecule scenario runs the role without that
override and asserts the installed server version equals the pin. -
docker_compose_v2 pin was built in one form for three package managers:
apt needed the full version string it never got, and dnf does not know the
pkg=versionsyntax at all, so every pinned run failed on a role that
advertises Debian, Ubuntu and EL 9. The pin form now lives in
per-distributionvars/files, and the apt string is resolved on the host
through the newapt_version_pinfilter. -
helm applied charts to only one host per play:
charts.yml,
prerequisites.ymlandrepositories.ymlcarriedrun_once, but each host
the role targets is its own k3s cluster, so the HelmChart CRs reached a single
host and left the other clusters empty. Removingrun_oncealso unmasks the
kubeconfig autodetection, which promised the first existing candidate but let
the loop overwrite it down to the last; it now selects the first match in one
pass. -
helm_kubeconfig_pathwas undocumented and unvalidated: the role read it
in 13 task lines, including askubeconfigfor everykubernetes.corecall,
but declared it in neitherdefaults/main.ymlnormeta/argument_specs.yml.
Both now declare it. The default stays empty, sinceprerequisites.ymlonly
autodetects the path while the value is unset or empty. -
lefthook could not lint without Galaxy access: the
ansible-linthook
resolved the collections declared in the molecule requirements against
galaxy.ansible.com, so a machine without Galaxy egress could not commit any
file under the hook's glob and had to reach for--no-verify, which drops the
gitleaks hook too. The hook now lints offline. -
Renovate could not refresh the pinned nginx digest: the
image:lines in
roles/docker_compose_v2/molecule/default/converge.ymlandverify.yml
carried a# renovate: datasource=docker depName=nginxcomment, which the
comment manager from the sharedrenovate-basepreset read as
currentValue: nginx:alpine. The resulting request for the
library/nginx:nginx:alpinemanifest returned400 DIGEST_INVALIDand every
Renovate run reported "Could not determine new digest for update" as a
repository problem, leaving the digest frozen. The comments are gone and
.github/renovate.jsongained acustomManagerthat parses theimage:
line itself, sodepNameisnginx,currentValueisalpineand the
digest updates again. The match requires an@sha256:suffix, which keeps
tag-only test images such asregistry:2indocker_loginuntracked, while
the tag itself is optional so a digest-only pin (image: nginx@sha256:…) is
still tracked rather than silently frozen. Excluding/from the tag keeps a
port-qualified registry (registry.example.com:5000/nginx) insidedepName
instead of splitting it at the port. -
k3s AppArmor profile survived being disabled: setting
k3s_apparmor_profiletofalseonly stopped
roles/k3s/tasks/security.ymlfrom writing/etc/apparmor.d/k3s; a copy
left from an earlier run stayed on disk and was reloaded by
apparmor.serviceon every boot, so turning the variable off never took
effect on hosts that once had it on. The role now mirrors the cri-containerd
disable path — it unloads the profile withapparmor_parser -R, removes the
file and clears a hand-placed/etc/apparmor.d/disable/k3ssymlink. With the
profile still loaded, container processes ran under stacked child profiles,
sokubectl execfailed with an AppArmoroperation not permittederror and
pods hung in Terminating withFailedKillPod. The load task also read
k3s_apparmor_profile | default(true)where the deploy task read
default(false); both now usedefault(false). -
k3s server nodes ignored container log rotation: the
container-log-max-sizeandcontainer-log-max-fileskubelet args were only
built byagent-config.yaml.j2, so a server node never rotated container
logs even with the variables set.server-config.yaml.j2now builds the same
two args, andmolecule/default/verify.ymlasserts both reach the rendered
config. -
k3s server config template used the pre-migration fact spelling: the
ansible_factsmigration left
templates/etc/rancher/k3s/server-config.yaml.j2onansible_local.k3s,
while the sister templateagent-config.yaml.j2in the same role had already
moved toansible_facts['ansible_local']['k3s']. Both templates read the same
fact in two different spellings. No behaviour change:ansible_localis
exempt from theINJECT_FACTS_AS_VARSdeprecation and is always promoted to
the top level, so the old spelling still resolved. A sweep for
ansible_local.acrossroles/confirms this was the last remaining
occurrence. -
k3s environment variables were never rendered:
k3s_environment_varswas
fully wired — documented indefaults/main.yml, declared as adictin
meta/argument_specs.yml, consumed by
templates/etc/systemd/system/k3s.service.env.j2and loaded by the unit via
EnvironmentFile=— but the task that renders the file was commented out in
roles/k3s/tasks/utilities.yml. SettingHTTP_PROXY/HTTPS_PROXY, the
documented use case, produced no error and no file: the leading-in
EnvironmentFile=-makes systemd treat the missing file as fine, so k3s ran
without proxy settings and failed image pulls silently. The task is active
again and notifiesRestart k3sinstead of the stale lowercase handler name
restart k3s, which no longer exists. Becauseutilities.ymlis included
after the role has already flushed its handlers and waited for the API server,
the task flushes handlers itself and waits for the server to come back, so a
following play never meets a restarting cluster.verify.ymlasserts the file
is rendered0600and carries the value set throughk3s_environment_vars,
matching it withgrepinstead of reading the file into a variable. -
k3s environment file broke idempotence: the environment template rendered
K3S_TOKENwheneverk3s_tokenwas set. On a fresh node the variable is
empty during the first run and only read back once the datastore exists, so
the line appeared on the second run and rewrote the file every time. The
template no longer renders the token;server-config.yaml.j2already writes
it intoconfig.yaml, which the unit loads throughK3S_CONFIG_FILE, so the
join credential now lives in one file instead of two.verify.ymlasserts the
environment file carries noK3S_TOKEN=line. -
fleet GitRepo and Bundle sent empty values for unset options: both tasks
sent every optional field with an empty default ('',[],false) instead
of omitting it, and patched instead of applying. The API drops empty values
and writes its own defaults (such asspec.targetCustomizationMode), so the
applied object never matched the stored one. Optional fields are now omitted
when unset and both tasks useapply: true, which compares against the
fields the role owns. This narrows the diff but does not make the applies
idempotent yet — a second run still bumps the resource'sgeneration, so the
fleet molecule scenario runs without theidempotencestep for now. -
fleet Bundle sent fields the API rejects:
spec.helm.timeout,
spec.helm.timeoutForceDeleteandspec.yodaModeare not part of the Fleet
Bundle schema and were discarded with anunknown fieldwarning on every
apply. All three are removed from the task and fromargument_specs.yml. -
k3s secrets encryption was silently off:
server-config.yaml.j2
referencedk3s_secrets_encryption,k3s_protect_kernel_defaultsand
k3s_audit_log_enabled, but none of them were defined indefaults/main.yml
orargument_specs.yml. Every default deployment therefore ran without
secrets encryption whilek3s_security_hardening: truesuggested otherwise. -
k3s dead permission tasks: the kubeconfig, token-file and credential
directory permission tasks were commented out, makingk3s_kubeconfig_mode,
k3s_kubeconfig_ownerandk3s_kubeconfig_groupdead configuration. All
three are restored with astatpre-check, sincesecurity.ymlruns before
the installation. The orphanedstaton the credential directory has its
consumer back. -
k3s bash completion never ran: both completion tasks in
roles/k3s/tasks/utilities.ymlwere gated onk3s_completion_test, a
variable that was never registered anywhere in the collection, so the
condition was always false and the tasks were silently skipped on every run
despitek3s_create_bash_completiondefaulting totrue. The completion
output is now generated by a dedicated probe task per binary and written with
ansible.builtin.copy, which replaces thechanged_when: trueshell
redirects and reportschangedonly when the file content actually differs.
verify.ymlasserts that both completion files exist and are non-empty. -
k3s dead security blocks removed: the commented-out audit-policy task
referencedtemplates/etc/kubernetes/audit-policy.yaml.j2, which does not
exist in the repository, and the commented-out debug summary duplicated the
active debug task in the same file. -
k3s
cluster-initdrift on re-runs: the first server rewrote
config.yamland restarted k3s on every run after the first.cluster-init
was derived fromk3s_server_init, which followsshould_initand requires
the datastore to be absent — so the flag was written on the initial run and
dropped on every later one.cluster-initis a bootstrap flag that k3s only
evaluates while creating the datastore, so it is now kept for as long as this
node runs the cluster. Secondary servers and agents keep writingserver:as
before. -
fleet async applies could never run:
bundles.ymlandclusters.yml
fired the Bundle and Cluster applies withasync/poll: 0, but
kubernetes.core.k8sships an action plugin wrapper that never opts into
async, soActionBase.run()rejects the task before the module executes
("This action (kubernetes.core.k8s) does not support async"). Only
check_modeever reached the synchronous fallback, so a real run failed
outright. Both resources now apply synchronously; thefleet_async_enabled,
fleet_async_timeout,fleet_async_retriesandfleet_async_delay
variables are gone with the path they configured. -
fleet sent
keepFailHistoryas an integer: the Bundle spec rendered
spec.correctDrift.keepFailHistorythrough| intand the argument spec
declared itint("Number of failed attempts to keep"), but the Fleet CRD
types it as a boolean, so the API rejected every Bundle carrying a
correct_driftblock with a 422. Both now usebool. -
fleet applied Bundles and one namespace without a kubeconfig: the Bundle
apply and the ClusterRegistrationToken namespace task omitted the
kubeconfigparameter every other task in the role passes, so they fell back
to the module default and failed with "Could not create API client: Invalid
kube-config file" wherever the k3s kubeconfig is not the ambient default. -
fleet Bundle targets ignored the documented spelling:
bundles.yml
passedtargetsto the API verbatim while the argument spec documents (and
validates) snake_case keys, so a spec-conformantcluster_selectorreached
the API unconverted and a workingclusterSelectorfailed validation.
Bundles now run targets throughfleet_transform_targets, the same filter
gitrepos.ymlalready used. -
helm chart deploys are excluded from the idempotence check: the k3s Helm
controller owns the HelmChart it reconciles and writes back to the spec, so
the applied resource never matches and the task reportschangedon every
run. The molecule scenario skips it viamolecule-idempotence-notestrather
than masking the result withchanged_when: false. -
helm role failed on a host with an empty apt cache: the
packagesentry
point ofarillso.system.packagesnever refreshes the package cache and its
install task pinsupdate_cache: false, sopython3-kuberneteswas reported
as unavailable on a freshly provisioned host. The role now refreshes the apt
cache before installing its Python dependency. -
k3s config drift on re-runs: a server rewrote
config.yamland restarted
k3s on every run after the first. On the initial run the cluster database does
not exist yet, so the role initialises the cluster andk3s_tokenstays empty
— the rendered config carries notoken. From the second run on, the token
file exists, the role reads it back and writes it into the config, which
changes the file and notifiesRestart k3s. A token read from the node's own
server/node-tokenis redundant inconfig.yamland is now left out of the
server config. A token fetched from another host stays in place, so secondary
servers and agents can still join. -
docker_compose_v2 molecule idempotence: the
defaultscenario failed on
the launch task withIdempotence test failed.nginx:alpineis an OCI image
index with 16 manifests, and compose stores the resolved image ID in the
container'scom.docker.compose.imagelabel. Once the tag resolved to a
different ID than the stored one, compose recreated the container on every
run — the config hash still matched on both sides, so only the image label
differed. The converge image is now pinned by digest. -
Secret masking: tasks that read, render or transport credentials ran
withoutno_log: true, so the values were printed with-vor in the task
result. The k3s cluster join token (slurp+set_factin
roles/k3s/tasks/main.yml), the k3s config and registries templates, the
Fleet GitRepo auth secret and the FleetClusterRegistrationTokenlookup,
and thedocker_loginregistry login are now all masked. Thek3s_token
anddocker_login_passwordoptions inargument_specs.ymlcarryno_log
as well, matching the already-maskedk3s_etcd_s3_*keys. -
fleet argument_specs: the main entry point used six separate
<<:merge
keys in one mapping, whichruamel.yaml(used by ansible-lint) rejects as
duplicate keys. Consolidated into a single YAML 1.1 list-form merge
(<<: [*a, *b, ...]); the merged result is byte-for-key identical. -
omit defaults:
docker_loginandfleetdeclared optional parameters
with a stringdefault: "{{ omit }}", which fails argument-spec validation on
Ansible 2.18+ for non-string types. Removed the string-omit defaults; the
docker_logintasks now resolve those values with| default(omit). -
docker role: the Docker prune service/timer setup called
arillso.system.systemd_unit, a role that no longer exists in
arillso.system(replaced by thesystemdrole with asystemd_units
list interface), so the role failed with "role
'arillso.system.systemd_unit' was not found". Rewrite the prune units in
thesystemd_unitsformat and callarillso.system.systemd. -
docker role: add the missing
log-optsoption to thedocker_daemon
argument spec.log-optsis a validdaemon.jsonkey but was absent from
the spec, so passing it (e.g. log rotationmax-size/max-file) failed
argument validation. -
docker role — hardening and merge documentation: new
## Hardening
section in the role README covering theno-new-privilegesopt-out and the
userns-remapopt-in with its costs, plus a Quick Start that explains the
merge semantics. The log-rotation troubleshooting entry now states that
max-size/max-filearejson-fileoptions and are ignored under the
defaultjournalddriver, which limits size viaSystemMaxUseinstead. -
docker role — merge coverage in tests: the molecule
verify.ymlasserts
that role defaults survive a user-provideddocker_daemonand that nested
log-optsmerge per sub-key; the integration target asserts the pure
default path, which no test previously covered.
Changed
- Role-internal registers now carry a role prefix: every register in
roles/helmandroles/k3swas renamed to_<role>_<name>, so
role-internal state no longer leaks into the play namespace. This covers
both the 12 registers that carried no role prefix at all (cluster_token,
kubeconfig_check,facts_check→_k3s_cluster_token,
_helm_kubeconfig_check,_k3s_facts_check) and the 16 that already had
one but no leading underscore (helm_repo_check,k3s_release_checksum,
k3s_selinux_restorecon→_helm_repo_check,_k3s_release_checksum,
_k3s_selinux_restorecon). The second group was already lint-clean;
renaming it keeps a single style inside the two roles. The molecule verify
playbooks fordockerandk3suse the plain<role>_<name>form, since
play-level variables are not role-internal.k3s_token,k3s_token_file
andk3s_token_sourceare the role interface and are unchanged. ansible-lintruns the production profile:.ansible-lintgains
profile: productionandstrict: true, and drops
var-naming[no-role-prefix]fromskip_list, which turns the prefix
convention into an enforced rule.package-lateststays skipped.- Unified the declared Ansible floor at
2.18: the collection declared its
minimum Ansible version in nine places with three different values. Galaxy
already enforcesrequires_ansible: ">=2.18.0"frommeta/runtime.ymlat
install time, whileREADME.mdpromised>= 2.15and thehelm/fleet
roles claimed2.9.README.mdnow states>= 2.18and all seven role
min_ansible_versionvalues are"2.18". For README readers this is a
documented tightening, even though Galaxy has been enforcing it all along.
2.9was unsupportable regardless, since thecommunity.dockerdependency
declared ingalaxy.ymlrequires>=3.4.11, which does not run on it. - BREAKING — the fleet async variables are gone:
fleet_async_enabled,
fleet_async_timeout,fleet_async_retriesandfleet_async_delayare
removed. They configured an async apply path that Ansible refuses to run for
kubernetes.core.k8s, so setting them never had the documented effect.
Playbooks passing them need to drop the variables; Bundle and Cluster applies
now always run synchronously. - BREAKING — k3s hardening now applies by default:
k3s_secrets_encryption,
k3s_anonymous_auth,k3s_node_restrictionand
k3s_kubelet_read_only_port_disabledtake effect on existing clusters running
default variables. Secrets encryption restarts K3s (no data loss);
anonymous-auth=falsebreaks anonymous health probes;NodeRestriction
breaks node credentials modifying foreign node objects;read-only-port=0
breaks collectors on kubelet port 10255. Setk3s_security_hardening: false
to keep the previous behaviour. - k3s apiserver and kubelet args merge instead of overwrite:
k3s_kube_apiserver_argsandk3s_kubelet_argspreviously replaced the
argument list wholesale, leaving no room for role-managed hardening flags.
They are now appended after the hardening args, so a user-supplied duplicate
still wins (K3s keeps the last occurrence of a repeated flag). - docker role — daemon config is now merged, not replaced (BREAKING).
/etc/docker/daemon.jsonis rendered from the newdocker_daemon_base
(role defaults) recursively merged withdocker_daemon(user config,
now defaulting to{}). Previously the template rendereddocker_daemon
alone, so any playbook setting it silently dropped the role defaults
log-driver: journaldandlive-restore: true. Existing playbooks keep
working and additionally inherit the defaults; to drop a base key instead
of overriding it, replacedocker_daemon_base. The per-keydefault:
entries forlog-driverandlive-restorewere removed from the
docker_daemonargument spec, since argument validation would otherwise
inject them into the user dict and defeat the merge. - docker role —
no-new-privileges: trueis now a default (BREAKING).
Set indocker_daemon_base, so new containers can no longer gain
privileges via setuid/setgid binaries. Images relying on them (e.g.sudo,
somepingbuilds) will break. Opt out with
docker_daemon: {no-new-privileges: false}.userns-remapdeliberately
stays opt-in; see the role README for its costs. - Deprecated syntax: replace top-level
ansible_*fact references with
ansible_facts['...']across the docker, k3s and helm roles (and their
defaults/argument_specs), and migrate the docker Debian/Ubuntu
repository tasks from the deprecatedansible.builtin.apt_repositoryto
ansible.builtin.deb822_repository. Removes theINJECT_FACTS_AS_VARS
(core 2.24) andapt_repository(core 2.25) deprecation warnings. The
docker role now installspython3-debian(required by
deb822_repository) and removes any legacydocker.listleft by earlier
role versions to avoid a duplicate apt source. A follow-up pass covers the
references the first migration missed: thehostvars[...]['ansible_*']
lookups ininstall_docker_redhat.ymland the k3s server-URL fallbacks,
ansible_local.k3s→ansible_facts['ansible_local']['k3s']on the k3s
cluster-init path and in the agent config template, plus
ansible_swaptotal_mband the molecule/integration references. Custom facts
keep theiransible_localkey insideansible_facts, unlike the built-in
facts that drop theansible_prefix there. - Renovate: track the
python_versionCI input via Renovate. Each
python_version:inpull-request.yml/merge.ymlcarries a
# renovate: datasource=github-releases depName=python/cpythonmarker, and
the local regex manager now scans.github/workflows/*.yml, so the Python
version is bumped automatically instead of drifting hardcoded. - Dependency: raise the
arillso.systemlower bound from>=0.0.17to
>=1.0.0— thesystemdrole with thesystemd_unitsinterface the docker
role now uses is only available from 1.0.0 onwards. - Molecule (CI): run the docker and k3s scenarios on both Ubuntu 22.04 and
Debian 12 (was Ubuntu-only), restoring the two-distro coverage from the
pre-KVM docker-driver setup. - Align the release workflow with the org convention: set
name: Release - Ansible Collection, simplifyrun-nameto
Release <ref>, use arelease-<ref>concurrency group, rename the job to
release, and pin the reusable workflow to@2026-06-18. - The
python_versionCI input is pinned to3.13inpull-request.ymland
merge.yml, becauseansible-test sanityrejects3.14. The local
.python-versiontracks the newest CPython release (3.14.7) and is not
used by the test matrix. - Security scan runs as
nightly-security.yml(name: Nightly Security Scan)
on a daily cron (0 2 * * *). Per the repo-standard visibility rule, public
repos run the scan daily (free Actions minutes); private repos run it weekly. LICENSEcopyright2025→2023-2026(org rangeFIRST-CURRENT,
consistent with the README).- Repo standard alignment: dropped scanner configs no consumer reads
(.checkov.yml,.grype.yaml,.jscpd.json,.markdown-link-check.json,
.secretlintrc.json,.trivy.yaml) — no reusable workflow or lefthook hook
passed them to their tools, so they documented settings that never reached a
scanner. The repo-local community health files (issue templates, PR template,
CODEOWNERS) are gone as well, so thearillso/.githuborg defaults apply
directly; the copies had drifted, andCODEOWNERSreplaced the org file
rather than extending it. - helm chart install in the fleet and tailscale molecule prepare steps now
useshelm/helmv4 (Renovate). - Role metadata: drop EOL Ubuntu
focal(and Debianbusterwhere present)
fromgalaxy_info.platformsacross all roles; ensurejammy/nobleand
bullseye/bookwormare listed. - Role READMEs: add a
Troubleshootingsection to thedocker,k3s, and
fleetREADMEs and correct thehelmREADME, which documented a non-existent
helm_versionbinary install — the role drives the K3s-embedded Helm
controller viaHelmChartCRDs, so the Quick Start now useshelm_charts.