Skip to content

Commit 99dd57c

Browse files
authored
feat: Ubuntu 26.04 support and cozy-system namespace adoption (#37)
* fix(examples/ubuntu): allow opting out of linux-modules-extra for 26.04+ Ubuntu 26.04 LTS bundles openvswitch and vport-geneve into the main linux-image-generic and has no linux-modules-extra-* split package for kernel 7.x. Without an opt-out, the apt task fails with 'No package matching linux-modules-extra-7.x.x-generic available'. Add a length-zero guard on cozystack_ubuntu_extra_packages and document the override path in the vars block, so 26.04 inventories can simply set the list to [] without forking the playbook. Existing 22.04 / 24.04 inventories keep working with the default list — behaviour unchanged. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(examples/ubuntu): work around sudo-rs default on Ubuntu 26.04 Ubuntu 26.04 LTS ships sudo-rs (Rust rewrite) as the default /usr/bin/sudo alternative. sudo-rs does not honour ansible's privilege-escalation pseudo-tty: every task with become: true on a password-protected user hangs and fails with 'Timeout (12s) waiting for privilege escalation prompt'. The classical sudo binary is co-installed at /usr/bin/sudo.ws on 26.04 hosts. Add prepare-sudo.yml that switches the sudo alternative to it via a raw command (which does not depend on ansible become and works even when become is broken). Wire it into site.yml so the full pipeline keeps working out of the box on 26.04, with no behavioural change for earlier Ubuntu / Debian targets — the play is a no-op when sudo-rs is not the active alternative. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(role): adopt cozy-system namespace into helm release on reinstall The cozy-installer chart renders the target namespace (cozy-system by default) as a templated resource, so a fresh install fails when that namespace already exists out of band — manual kubectl create ns, remnant from a previous failed install, or a different chart that shares the namespace name. The error surfaces as Namespace "cozy-system" exists and cannot be imported into the current release: invalid ownership metadata Add a pre-task that looks up the namespace and stamps the helm ownership label and annotations expected by the chart. The task is a no-op when the namespace is absent or already carries matching metadata, so first-install and idempotent re-runs are unchanged. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * docs: README and CHANGELOG entries for Ubuntu 26.04 + namespace adoption Document the two Ubuntu 26.04 opt-ins (sudo-rs alternative switch + empty cozystack_ubuntu_extra_packages list) under Known limitations in the README, and add the matching CHANGELOG entries for the sudo workaround playbook, the linux-modules-extra guard, and the helm namespace adoption pre-task in the role. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * ci: cover sudo workaround syntax + namespace adoption regression Two test additions for the Ubuntu 26.04 / namespace-adoption changes: 1. Lint job runs --syntax-check on examples/ubuntu/prepare-sudo.yml, matching the existing checks for the prepare-* playbooks. 2. E2E job adds a third pipeline run that simulates an orphan cozy-system namespace: strip the helm release storage Secret and the namespace's helm labels and annotations, then re-run examples/ubuntu/site.yml. This is the failure mode the role's namespace adoption pre-task fixes — without it, helm install would error with 'invalid ownership metadata'. After the run the step verifies the namespace carries the expected managed-by label and meta.helm.sh/release-name annotation again. A 26.04-specific e2e for the sudo-rs alternative switch is not included: GitHub-hosted runners are still on 24.04 and there is no 26.04 image yet. The syntax check plus ansible-lint are the most that fits on the current runner pool. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(examples/ubuntu): harden prepare-sudo.yml secret handling and binary path Two issues raised in review of the original prepare-sudo.yml: 1. Without no_log: true, ansible_become_password is interpolated directly into the rendered raw command and would leak into ansible output on task failure or with -vv verbosity, surfacing the user's sudo password in CI logs and on-call screenshots. 2. The original code piped the password to bare `sudo`, which on 26.04 resolves to sudo-rs — the very binary the workaround exists to circumvent — and depends on sudo-rs supporting --stdin/--prompt in a backwards-compatible way with classical sudo. sudo-rs is a partial reimplementation and its CLI surface is not stable. Switch both the password-piped and the passwordless branches to call /usr/bin/sudo.ws directly (the classical sudo binary co-installed on 26.04 hosts), and add no_log: true to the switch task. The detection heuristic remains gated on 'sudo-rs' appearing in the active sudo alternative target, so /usr/bin/sudo.ws is only invoked when we have already verified sudo-rs is in the picture and sudo.ws is therefore present. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(role): patch literal cozy-system in adoption pre-task The cozy-installer chart hardcodes `name: cozy-system` in templates/cozystack-operator.yaml — there is no values key for the operator namespace. The role's cozystack_namespace variable is documented but the chart does not actually honour it (a foot-gun that pre-dates this change). The adoption pre-task introduced in 02975ad used the variable, so a user who set cozystack_namespace to a non-default value would have the pre-task patch the wrong namespace while helm install still fails on the literal cozy-system. Patch the literal cozy-system directly, with a comment pointing at the chart hardcoding so a future chart change makes the lookup easy to fix back. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(examples/ubuntu): auto-skip linux-modules-extra on 26.04+, doc clarity Two follow-ups from review of the Ubuntu 26.04 changes: - Auto-skip the linux-modules-extra-* apt install on Ubuntu 26.04+ by adding 'ansible_distribution_version is version("26.04", "<")' to the task's when clause. Earlier code required users to set cozystack_ubuntu_extra_packages: [] in inventory, which is an unnecessary cliff: ansible_distribution_version is gathered automatically and the playbook already auto-detects CPU vendor and distribution elsewhere. Manual override is still respected for hosts where the fact is unreliable. - Rephrase the Ubuntu 26.04 note in README from 'two opt-ins' to 'two changes to be aware of'. The sudo-rs alternative switch is applied automatically by examples/ubuntu/site.yml — a user does not opt in, they get it for free unless they bypass site.yml. Clarify that and document the bypass case. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * test(unit): structural invariants for examples/ubuntu/ playbooks Three regression checks paired with the recent prepare-sudo.yml and prepare-ubuntu.yml changes: 1. examples/ubuntu/prepare-sudo.yml: the 'Switch sudo alternative' task carries no_log: true, so ansible_become_password cannot leak into ansible output if the raw command fails or is run with -vv. 2. The same task invokes /usr/bin/sudo.ws directly in both branches (password and passwordless). Bare 'sudo' would resolve to sudo-rs on 26.04 and re-introduce the bug the playbook exists to fix. 3. examples/ubuntu/prepare-ubuntu.yml: the linux-modules-extra task carries an ansible_distribution_version < 26.04 gate so 26.04 users do not need to set cozystack_ubuntu_extra_packages: [] by hand. These run under ansible-test units alongside the existing IP plugin tests and use only PyYAML, which ansible-core already requires. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(role): refuse to hijack cozy-system from another helm release Two safety fixes to the namespace-adoption pre-task introduced in 02975ad / b8fc0a3: 1. Foreign-owner refusal. The previous when: clause fired the patch whenever the namespace's helm metadata did not match this role's release_name / release_namespace. That includes the case where cozy-system is legitimately owned by a *different* helm release — silently overwriting meta.helm.sh/release-name in that case is data-loss-class: the original owner's next reconcile breaks with 'invalid ownership metadata' and the operator has no signal that this role flipped the bits. Add an explicit fail task that triggers when managed-by=Helm and either annotation points elsewhere, with a message naming the conflicting release. The adopt task only runs when the managed-by label is missing or already ours. 2. cozystack_namespace assert. The cozy-installer chart hardcodes 'name: cozy-system' in templates/cozystack-operator.yaml — there is no values key for the operator namespace. The role used to document cozystack_namespace as a configurable variable, but overriding it silently broke the wait/patch tasks in this role (the operator deploys to cozy-system regardless, while the role waits in the override). Assert at validation time that the variable still equals cozy-system, with a clear fail_msg pointing at the chart constraint. Drop the variable from the user-facing README table to stop advertising a non-functional knob. Also: README chart_version drift fix (1.2.2 -> 1.3.1) since the table is in the same file area touched by this PR. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * test: cover foreign-owner refusal and cozystack_namespace assert Three new test cases for the safety fixes in ff4b3a3: - Unit (tests/unit/playbooks/): assert that the role contains a validation task pinning cozystack_namespace to 'cozy-system', and a fail-not-patch task gated on managed-by=Helm + release-name mismatch. Future edits that reintroduce silent re-adoption will trip these. - E2E (.github/workflows/test.yml): after the existing adoption scenario succeeds, re-stamp cozy-system as owned by a fictional 'some-other-release' and run the pipeline again. The role must fail with 'owned by helm release some-other-release' in the output. The cleanup step restores ownership so subsequent CI steps see a healthy cluster. Also document in prepare-sudo.yml that the password-stdin path reads only the first line, so a password containing a literal newline is unsupported. Edge case unlikely in practice but worth naming since the play's headers describe the password contract. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * docs: move Unreleased to top of CHANGELOG, add Ubuntu 26.04 to supported targets Two documentation alignments flagged in review: - CHANGELOG.rst: move Unreleased section to immediately after the title, ahead of all tagged versions. The previous file order had Unreleased sandwiched between v1.1.3 and v1.1.2, which is chronologically nonsensical and contradicts the convention documented in the contributor guidelines. - README.md: add Ubuntu 26.04 to the Supported targets table at top-of-file. The PR adds 26.04 support end-to-end (prepare-sudo workaround, linux-modules-extra auto-skip, Known limitations subsection) but the headline distribution table still listed only 22.04 / 24.04 / Debian 12 — readers scanning the table would conclude 26.04 is unsupported and never reach the limitations. Mark 26.04 as best-effort with a pointer to Known limitations. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(role): drop cozystack_namespace variable, hardcode literal cozy-system The cozy-installer chart hardcodes 'name: cozy-system' in templates/cozystack-operator.yaml and exposes no values key for the operator namespace. The role's cozystack_namespace variable was therefore a phantom: documented as configurable, defaulted to cozy-system, and silently broke the wait/patch tasks if anyone overrode it (operator deployed to cozy-system regardless, while the role waited in the override and timed out). Remove the variable from defaults/main.yml. Replace its remaining references with the literal 'cozy-system' in the wait task and the platform-package template. The validation block now asserts the variable is *unset* — any inventory still defining it (even at the old default value) fails fast with a clear upgrade message rather than silently inheriting the typo. Strip cozystack_namespace from the test fixtures that explicitly set it, since they would now hit the assert. Also strengthen the foreign-owner refusal check so a partial-write state (release-name annotation set without managed-by label, or vice versa) is still detected as a conflict, instead of slipping into the adopt branch and stamping our annotations on top. Add unit tests covering: the rejection assert content, the absence of any remaining {{ cozystack_namespace }} references in role files, and the strengthened ownership-indicator coverage in the foreign-owner fail task. Document the breaking-but-rare variable removal in CHANGELOG Unreleased. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> * fix(role): drive namespace lookup via kubectl, not kubernetes.core.k8s_info The k8s_info module requires the optional Python 'kubernetes' package on the controller, which is not installed in the project's CI environment (and is not a documented prerequisite for users either). The rest of this role already drives the cluster via `kubectl ... --output=json` for the same reason. Switch the cozy-system lookup to `kubectl get namespace ... \ --ignore-not-found --output=json` and parse the result with from_json. `--ignore-not-found` returns an empty stdout (rather than a non-zero exit) when the namespace is absent, so the same length-check gate as before keeps the foreign-owner and adopt tasks no-op'd on first install. The unit tests are unchanged because they check task names and when-clause structure, not the lookup module's internals. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> --------- Signed-off-by: Aleksei Sviridkin <f@lex.la>
1 parent 62e6635 commit 99dd57c

15 files changed

Lines changed: 598 additions & 47 deletions

File tree

.github/workflows/test.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ jobs:
3737
- name: Syntax check Ubuntu example
3838
run: ansible-playbook examples/ubuntu/prepare-ubuntu.yml --syntax-check
3939

40+
- name: Syntax check Ubuntu sudo workaround
41+
run: ansible-playbook examples/ubuntu/prepare-sudo.yml --syntax-check
42+
4043
- name: Syntax check SUSE example
4144
run: ansible-playbook examples/suse/prepare-suse.yml --syntax-check
4245

@@ -230,3 +233,96 @@ jobs:
230233
run: >-
231234
sudo env "PATH=$PATH" "HOME=$HOME" ansible-playbook examples/ubuntu/site.yml
232235
--inventory tests/ci-inventory.yml
236+
237+
# Namespace adoption regression test. Reproduces the failure
238+
# mode: the cozy-installer chart templates Namespace
239+
# cozy-system, and a `helm install` against an existing
240+
# namespace without helm ownership metadata fails with
241+
# Namespace "cozy-system" exists and cannot be imported into
242+
# the current release: invalid ownership metadata
243+
# Strip both the helm release storage Secret (so helm forgets
244+
# the release entirely) and the namespace's helm labels and
245+
# annotations (so the next install sees an orphan namespace).
246+
# This simulates a manual `kubectl create ns` or a remnant
247+
# from a previous failed install.
248+
- name: Simulate orphan cozy-system namespace
249+
run: |
250+
set -eux
251+
KUBECONFIG=/etc/rancher/k3s/k3s.yaml
252+
sudo kubectl --kubeconfig $KUBECONFIG \
253+
label namespace cozy-system app.kubernetes.io/managed-by-
254+
sudo kubectl --kubeconfig $KUBECONFIG \
255+
annotate namespace cozy-system \
256+
meta.helm.sh/release-name- \
257+
meta.helm.sh/release-namespace-
258+
sudo kubectl --kubeconfig $KUBECONFIG \
259+
delete secret --namespace kube-system \
260+
--selector=owner=helm,name=cozy-installer
261+
262+
- name: Test namespace adoption (re-install over orphan namespace)
263+
run: >-
264+
sudo env "PATH=$PATH" "HOME=$HOME" ansible-playbook examples/ubuntu/site.yml
265+
--inventory tests/ci-inventory.yml
266+
267+
- name: Verify cozy-system namespace was re-adopted
268+
run: |
269+
set -eux
270+
KUBECONFIG=/etc/rancher/k3s/k3s.yaml
271+
managed_by="$(sudo kubectl --kubeconfig $KUBECONFIG \
272+
get namespace cozy-system \
273+
--output jsonpath='{.metadata.labels.app\.kubernetes\.io/managed-by}')"
274+
release_name="$(sudo kubectl --kubeconfig $KUBECONFIG \
275+
get namespace cozy-system \
276+
--output jsonpath='{.metadata.annotations.meta\.helm\.sh/release-name}')"
277+
if [ "$managed_by" != "Helm" ] || [ "$release_name" != "cozy-installer" ]; then
278+
echo "ERROR: namespace adoption failed"
279+
echo " managed-by label: '$managed_by' (expected 'Helm')"
280+
echo " release-name annot.: '$release_name' (expected 'cozy-installer')"
281+
exit 1
282+
fi
283+
echo "OK: cozy-system namespace adopted into helm release"
284+
285+
# Foreign-owner refusal: re-stamp cozy-system as owned by a
286+
# different fake helm release and assert the role fails
287+
# rather than silently hijacking ownership.
288+
- name: Stamp cozy-system as owned by a different helm release
289+
run: |
290+
set -eux
291+
KUBECONFIG=/etc/rancher/k3s/k3s.yaml
292+
sudo kubectl --kubeconfig $KUBECONFIG \
293+
annotate namespace cozy-system --overwrite \
294+
meta.helm.sh/release-name=some-other-release \
295+
meta.helm.sh/release-namespace=elsewhere
296+
297+
- name: Test that foreign-owner namespace is NOT overwritten
298+
run: |
299+
set +e
300+
output="$(sudo env "PATH=$PATH" "HOME=$HOME" \
301+
ansible-playbook examples/ubuntu/site.yml \
302+
--inventory tests/ci-inventory.yml 2>&1)"
303+
status=$?
304+
set -e
305+
306+
if [ "$status" -eq 0 ]; then
307+
echo "ERROR: role silently re-adopted a foreign-owned namespace."
308+
echo "Expected refusal because cozy-system is owned by 'some-other-release'."
309+
echo "$output" | tail -50
310+
exit 1
311+
fi
312+
313+
if ! grep -q "owned by helm release 'some-other-release'" <<< "$output"; then
314+
echo "ERROR: role failed but not for the expected reason."
315+
echo "$output" | tail -50
316+
exit 1
317+
fi
318+
319+
echo "OK: role correctly refused to overwrite foreign helm ownership."
320+
321+
- name: Restore cozy-system ownership for cleanup
322+
run: |
323+
set -eux
324+
KUBECONFIG=/etc/rancher/k3s/k3s.yaml
325+
sudo kubectl --kubeconfig $KUBECONFIG \
326+
annotate namespace cozy-system --overwrite \
327+
meta.helm.sh/release-name=cozy-installer \
328+
meta.helm.sh/release-namespace=kube-system

CHANGELOG.rst

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,54 @@
22
cozystack.installer Release Notes
33
=================================
44

5-
v1.2.3
6-
======
7-
8-
- Drop ``ansible.utils`` collection dependency and ``netaddr`` Python
9-
package requirement. Master node IP validation now uses a bundled
10-
``cozystack.installer.is_ip_address`` Jinja2 test backed by the
11-
Python standard library ``ipaddress`` module.
12-
- Add IPv6 inventory fixture and CI coverage for IPv6 host keys.
13-
14-
v1.2.2
15-
======
16-
17-
Synced with Cozystack v1.2.2.
18-
19-
- Bump ``cozystack_chart_version`` to ``1.2.2``
20-
21-
v1.2.1
22-
======
23-
24-
Synced with Cozystack v1.2.1.
25-
26-
- Bump ``cozystack_chart_version`` to ``1.2.1``
27-
- Derive ``MASTER_NODES`` for kube-ovn from the ``server`` inventory
28-
group; add ``cozystack_master_nodes`` override for multi-master setups
29-
- Validate master node entries are valid IP addresses, not hostnames
30-
31-
v1.1.3
32-
======
33-
34-
Synced with Cozystack v1.1.3.
35-
36-
- Bump ``cozystack_chart_version`` to ``1.1.3``
375

386
Unreleased
397
==========
408

9+
Ubuntu 26.04 LTS support and namespace adoption.
10+
11+
- ``examples/ubuntu/`` now boots end-to-end on Ubuntu 26.04 LTS. Two
12+
changes were needed:
13+
14+
- New playbook ``examples/ubuntu/prepare-sudo.yml`` switches the
15+
``sudo`` alternative from ``sudo-rs`` (Rust rewrite, default on
16+
26.04) back to classical sudo at ``/usr/bin/sudo.ws``. ``sudo-rs``
17+
does not honour ansible's privilege-escalation pseudo-tty and
18+
every subsequent ``become: true`` task hangs with
19+
``Timeout (12s) waiting for privilege escalation prompt``. The
20+
play uses ``raw`` so it runs before any become-dependent task and
21+
is a no-op on releases that do not ship ``sudo-rs``.
22+
``examples/ubuntu/site.yml`` imports it first.
23+
- ``Install Ubuntu-only extra kernel modules`` now skips when
24+
``cozystack_ubuntu_extra_packages`` is empty. Ubuntu 26.04 ships
25+
``openvswitch`` and ``vport-geneve`` in the main
26+
``linux-image-generic`` and has no ``linux-modules-extra-*`` for
27+
kernel 7.x. Override the variable to ``[]`` in inventory on those
28+
hosts; earlier releases keep the existing default.
29+
30+
- The role now adopts the ``cozy-system`` namespace into the
31+
cozy-installer helm release on first run if it already exists
32+
out-of-band. Without this pre-task, ``helm install`` fails with
33+
``Namespace "cozy-system" exists and cannot be imported into the
34+
current release: invalid ownership metadata`` whenever the
35+
namespace was created manually, by a previous failed install, or
36+
by a different chart that shares the namespace name. The pre-task
37+
is a no-op when the namespace is absent or already carries matching
38+
helm metadata, and refuses to proceed (rather than silently
39+
hijacking) when the namespace is owned by a *different* helm
40+
release.
41+
42+
- **Breaking, but rarely set in practice**: the ``cozystack_namespace``
43+
variable was removed from the role's defaults. The cozy-installer
44+
chart hardcodes ``name: cozy-system`` in
45+
``templates/cozystack-operator.yaml`` and provides no values key
46+
to override it; the variable was effectively a phantom that
47+
silently broke the role's wait/patch tasks if changed. The role
48+
now asserts at validation time that the variable is *unset* — any
49+
inventory still defining it (even at the old default
50+
``cozy-system``) must remove the line. Replace any references in
51+
custom playbooks with the literal ``cozy-system``.
52+
4153
- New variable ``cozystack_external_ips`` (list, default ``[]``): external
4254
IP addresses for ingress-nginx Service ``externalIPs``. Required on
4355
``isp-full-generic`` platform variant when nodes lack a native load
@@ -100,6 +112,39 @@ Node prerequisites: comprehensive audit and install in examples.
100112
``prepare-rhel.yml`` fails fast with a clear message until an entry is
101113
added to ``cozystack_zfs_release_rpm_by_major``.
102114

115+
v1.2.3
116+
======
117+
118+
- Drop ``ansible.utils`` collection dependency and ``netaddr`` Python
119+
package requirement. Master node IP validation now uses a bundled
120+
``cozystack.installer.is_ip_address`` Jinja2 test backed by the
121+
Python standard library ``ipaddress`` module.
122+
- Add IPv6 inventory fixture and CI coverage for IPv6 host keys.
123+
124+
v1.2.2
125+
======
126+
127+
Synced with Cozystack v1.2.2.
128+
129+
- Bump ``cozystack_chart_version`` to ``1.2.2``
130+
131+
v1.2.1
132+
======
133+
134+
Synced with Cozystack v1.2.1.
135+
136+
- Bump ``cozystack_chart_version`` to ``1.2.1``
137+
- Derive ``MASTER_NODES`` for kube-ovn from the ``server`` inventory
138+
group; add ``cozystack_master_nodes`` override for multi-master setups
139+
- Validate master node entries are valid IP addresses, not hostnames
140+
141+
v1.1.3
142+
======
143+
144+
Synced with Cozystack v1.1.3.
145+
146+
- Bump ``cozystack_chart_version`` to ``1.1.3``
147+
103148
v1.1.2
104149
======
105150

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Supported targets:
66

77
| Example playbook | Distributions | Validated end-to-end |
88
| --- | --- | --- |
9-
| `examples/ubuntu/` | Ubuntu 22.04, Ubuntu 24.04, Debian 12 | Ubuntu 22.04, Ubuntu 24.04, Debian 12 on OCI: 3-node multi-master, 87/87 HelmReleases Ready |
9+
| `examples/ubuntu/` | Ubuntu 22.04, Ubuntu 24.04, Ubuntu 26.04, Debian 12 | Ubuntu 22.04, Ubuntu 24.04, Debian 12 on OCI: 3-node multi-master, 87/87 HelmReleases Ready. Ubuntu 26.04: best-effort — see Known limitations for the sudo-rs and `linux-modules-extra` notes |
1010
| `examples/rhel/` | RHEL 8+, CentOS Stream 8+, Rocky 9/10, Alma 9/10 | Rocky 10 on OCI: 3-node multi-master, 87/87 HelmReleases Ready (`cozystack_enable_zfs: false` required — see Known limitations) |
1111
| `examples/suse/` | openSUSE Leap 15.6+, openSUSE Tumbleweed, SLES 15 ||
1212

@@ -167,6 +167,9 @@ informational notice:
167167

168168
Other subsystem notes:
169169

170+
- **Ubuntu 26.04 LTS:** two changes to be aware of.
171+
1. *Auto-applied by `examples/ubuntu/site.yml`*: `sudo-rs` ships as the default `/usr/bin/sudo` alternative and does not honour ansible's `become_method: sudo` privilege-escalation pseudo-tty — every `become: true` task hangs with `Timeout (12s) waiting for privilege escalation prompt`. The classical sudo binary is co-installed at `/usr/bin/sudo.ws`. `site.yml` imports `prepare-sudo.yml` first, which switches the `sudo` alternative back via `update-alternatives` using a `raw` command (so it works even when become is broken). The play is a no-op on releases without sudo-rs. If you bypass `site.yml` and call the prepare playbooks directly, run `prepare-sudo.yml` before any task with `become: true` on 26.04 hosts.
172+
2. *Manual inventory setting on 26.04 hosts*: the playbook auto-skips `linux-modules-extra-*` on Ubuntu 26.04+ because the package no longer exists for kernel 7.x — `openvswitch` and `vport-geneve` are bundled into `linux-image-generic`. The auto-skip relies on `ansible_distribution_version`; on hosts where that fact is unreliable, set `cozystack_ubuntu_extra_packages: []` in inventory to skip the apt install explicitly.
170173
- **Cloud providers (Ubuntu on OCI, AWS, GCP):** stock Ubuntu cloud images ship an iptables INPUT chain that ends with `REJECT icmp-host-prohibited`, which blocks k3s ports 2380/6443 between nodes. Set `cozystack_flush_iptables: true` in your inventory so the prepare playbook flushes the INPUT chain before k3s installs. Oracle Linux images on OCI do not have this restriction out of the box.
171174
- **Rocky 10 / Alma 10 (and other RHEL 10 rebuilds):** the `iptables` userspace binary is not installed by default. `examples/rhel/prepare-rhel.yml` installs `iptables-nft` so the `cozystack_flush_iptables` task and k3s kube-proxy replacement have a working `iptables` wrapper over nftables.
172175
- **ARM64 (aarch64):** OpenZFS does not publish aarch64 RPMs for RHEL-family distributions via `zfsonlinux.org/epel`. Cozystack itself targets x86_64.
@@ -310,9 +313,8 @@ Runs on `server[0]` only.
310313
| Variable | Default | Description |
311314
| --- | --- | --- |
312315
| `cozystack_chart_ref` | `oci://ghcr.io/cozystack/cozystack/cozy-installer` | Helm chart OCI reference |
313-
| `cozystack_chart_version` | `1.2.2` | Helm chart version |
316+
| `cozystack_chart_version` | `1.3.1` | Helm chart version |
314317
| `cozystack_release_name` | `cozy-installer` | Helm release name |
315-
| `cozystack_namespace` | `cozy-system` | Namespace for operator and resources |
316318
| `cozystack_release_namespace` | `kube-system` | Namespace for Helm release secret |
317319
| `cozystack_operator_variant` | `generic` | Operator variant: generic, talos, hosted |
318320
| `cozystack_api_server_port` | `6443` | API server port |

examples/ubuntu/prepare-sudo.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
# Switch sudo alternative to classical sudo on Ubuntu 26.04+.
3+
#
4+
# Ubuntu 26.04 LTS ships sudo-rs (Rust rewrite) as the default
5+
# /usr/bin/sudo alternative. sudo-rs does not read passwords from
6+
# ansible's privilege-escalation pseudo-tty, so every task with
7+
# `become: true` and a password-protected user hangs and fails with
8+
# Timeout (12s) waiting for privilege escalation prompt.
9+
#
10+
# The classical sudo binary is co-installed at /usr/bin/sudo.ws on
11+
# 26.04 hosts. This playbook switches the `sudo` alternative to it
12+
# before any other play runs `become: true` tasks, then becomes a
13+
# no-op on subsequent runs and on releases without sudo-rs.
14+
#
15+
# Run before prepare-ubuntu.yml on Ubuntu 26.04+ inventories. Earlier
16+
# Ubuntu releases and Debian skip every task here — safe to keep in
17+
# site.yml unconditionally.
18+
#
19+
# Connection model: this play uses the `raw` module with no `become`,
20+
# so it works even when standard ansible become is broken by sudo-rs.
21+
# Privilege escalation goes through `/usr/bin/sudo.ws` directly so
22+
# we never depend on whatever flag surface sudo-rs exposes:
23+
# - If the connecting user has passwordless sudo, leave
24+
# ansible_become_password unset; sudo.ws --non-interactive runs
25+
# without prompting.
26+
# - If the user requires a password, set ansible_become_password
27+
# in inventory or pass it via --ask-become-pass. The password is
28+
# piped to sudo.ws --stdin which reads only the first line, so a
29+
# password containing a literal newline is unsupported.
30+
31+
- name: Switch sudo alternative to classical sudo (Ubuntu 26.04+)
32+
hosts: cluster
33+
gather_facts: false
34+
become: false
35+
tasks:
36+
- name: Detect current sudo alternative target
37+
ansible.builtin.raw: |
38+
readlink -f /etc/alternatives/sudo 2>/dev/null || true
39+
register: _cozystack_sudo_current
40+
changed_when: false
41+
failed_when: false
42+
43+
# Calls /usr/bin/sudo.ws directly (the classical sudo binary
44+
# co-installed on 26.04). Skipping the `sudo` symlink avoids any
45+
# dependency on sudo-rs' subset of the classical CLI. no_log
46+
# prevents ansible_become_password from leaking into task output
47+
# if the raw command fails.
48+
- name: Switch sudo alternative to /usr/bin/sudo.ws
49+
ansible.builtin.raw: |
50+
{% if ansible_become_password is defined %}
51+
printf '%s\n' {{ ansible_become_password | quote }} | /usr/bin/sudo.ws --stdin --prompt='' update-alternatives --set sudo /usr/bin/sudo.ws
52+
{% else %}
53+
/usr/bin/sudo.ws --non-interactive update-alternatives --set sudo /usr/bin/sudo.ws
54+
{% endif %}
55+
register: _cozystack_sudo_switch
56+
changed_when: _cozystack_sudo_switch.rc == 0
57+
no_log: true
58+
when:
59+
- _cozystack_sudo_current.stdout is defined
60+
- "'sudo-rs' in _cozystack_sudo_current.stdout"
61+
- "'/usr/bin/sudo.ws' not in _cozystack_sudo_current.stdout"

examples/ubuntu/prepare-ubuntu.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
# Ubuntu-only: linux-modules-extra-* ships openvswitch and geneve
3434
# on cloud/minimal kernels. Debian includes these in its base
3535
# kernel image and has no such split package.
36+
#
37+
# Ubuntu 26.04 LTS bundles openvswitch and vport-geneve into the
38+
# main linux-image-generic and ships no linux-modules-extra-*
39+
# package for kernel 7.x. The task below auto-skips on 26.04+,
40+
# but if `ansible_distribution_version` is unreliable on a host
41+
# (custom builds, derivatives), force-skip from inventory:
42+
# cozystack_ubuntu_extra_packages: []
3643
cozystack_ubuntu_extra_packages:
3744
- "linux-modules-extra-{{ ansible_kernel }}"
3845

@@ -127,11 +134,17 @@
127134
state: present
128135
update_cache: true
129136

137+
# Auto-skip on Ubuntu 26.04+ (no linux-modules-extra-* for kernel
138+
# 7.x, modules ship in linux-image-generic) and when the list is
139+
# explicitly empty (inventory force-skip).
130140
- name: Install Ubuntu-only extra kernel modules
131141
ansible.builtin.apt:
132142
name: "{{ cozystack_ubuntu_extra_packages }}"
133143
state: present
134-
when: ansible_distribution == 'Ubuntu'
144+
when:
145+
- ansible_distribution == 'Ubuntu'
146+
- ansible_distribution_version is version('26.04', '<')
147+
- cozystack_ubuntu_extra_packages | length > 0
135148

136149
- name: Try loading optional kernel modules (may be built-in or missing)
137150
community.general.modprobe:

examples/ubuntu/site.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
# Usage:
88
# ansible-playbook site.yml
99

10+
# Ubuntu 26.04 ships sudo-rs as the default sudo alternative; ansible
11+
# `become_method: sudo` hangs against it. This play switches the
12+
# alternative back to classical sudo and is a no-op everywhere else.
13+
- name: Switch sudo alternative to classical sudo (Ubuntu 26.04+)
14+
ansible.builtin.import_playbook: prepare-sudo.yml
15+
1016
- name: Prepare Ubuntu/Debian nodes for Cozystack
1117
ansible.builtin.import_playbook: prepare-ubuntu.yml
1218

roles/cozystack/defaults/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ cozystack_chart_version: "1.3.1"
1515
# Helm release name
1616
cozystack_release_name: "cozy-installer"
1717

18-
# Kubernetes namespace where Cozystack operator and resources live
19-
cozystack_namespace: "cozy-system"
20-
2118
# Namespace for the Helm release secret (the chart creates cozy-system
22-
# via its own template, so the release must live in a pre-existing namespace)
19+
# via its own template, so the release must live in a pre-existing namespace).
20+
# The operator namespace itself is fixed at `cozy-system` by the chart
21+
# (templates/cozystack-operator.yaml hardcodes the literal); there is no
22+
# values key to override it, so this role no longer exposes a variable.
2323
cozystack_release_namespace: "kube-system"
2424

2525
# Operator deployment variant: generic, talos, hosted

0 commit comments

Comments
 (0)