docs/distro-support.md:120 already promises this (this issue said line 91 when it was filed; the row has been at 120 the whole time):
| Debian stable/testing | Planned after Ubuntu hardening |
Ubuntu hardening is where it was meant to be: three LTS releases validated at 79/79 on live VMs with committed replay twins. This issue is the follow-through.
What already exists
Debian is not an unrecognised distro. It is a parsed, first-class variant that is deliberately refused:
DistroId::Debian { version: Option<u32> } (crates/sysknife-core/src/distro.rs:142)
detect_debian parses VERSION_ID into that field (distro.rs:453)
family() already returns DistroFamily::Debian for it (distro.rs:205)
- a Debian 12 bookworm fixture and its parse and detect tests exist (
distro.rs:643, :764, :958)
- Linux Mint and Pop!_OS already fall back to the Debian family via
ID_LIKE
The single line that refuses it (distro.rs:252):
Self::UbuntuCore { .. } | Self::Debian { .. } | Self::Other { .. } => false,
Note what the doc comment above it justifies and what it does not. Pre-20.04 Ubuntu is excluded because it stops receiving security updates. UbuntuCore is excluded structurally, having no apt and a read-only root. Plain Fedora stays detectable so the planner can explain the gap. Debian is given no reason at all. It sits in that arm because nothing has needed it to be elsewhere.
Why it is cheap
Of the 67 Debian-family actions, 57 run on Debian unchanged, and all 97 All
actions already do. That is 154 of 189, ≈ 81.5 %, with no new action code. #237
did the inventory and the list surgery, and it merged as #384 today, so nothing
blocks this now.
Recounted 2026-09-09 at 5673d20. This paragraph said 43 actions and 140
of 189 when it was filed. #237 split the fences, so the 57 now falls out of
the constants directly: DEBIAN_ONLY_ACTIONS (19) plus
NON_CANONICAL_ON_FEDORA (38), leaving behind the 10 in
UBUNTU_ONLY_ACTIONS. The three sets are disjoint and their union is exactly
the 67.
$ python3 - <<'PY'
import re, pathlib, collections
rows=[l for l in pathlib.Path("docs/action-reference.md").read_text().splitlines()
if re.match(r'^\|\s*`[A-Za-z0-9_]+`\s*\|', l)]
tally=collections.Counter(); noclass=[]
for l in rows:
fields=[f.strip() for f in l.split('|')]
hits=[f for f in fields if f in ("All","Ubuntu","Fedora")]
if len(hits)==1: tally[hits[0]]+=1
else: noclass.append((fields[1], hits))
print("action rows:", len(rows))
print(dict(tally), "sum:", sum(tally.values()))
print("unclassified:", len(noclass))
PY
action rows: 189
{'Fedora': 25, 'All': 97, 'Ubuntu': 67} sum: 189
unclassified: 0
$ python3 - <<'PY'
import re,pathlib
src=pathlib.Path("crates/sysknife-core/src/action_family.rs").read_text()
def const(name):
m=re.search(r'pub const '+name+r': &\[&str\] = &\[(.*?)\];', src, re.S)
return set(re.findall(r'"([A-Za-z0-9_]+)"', m.group(1)))
d,u,n,f=const("DEBIAN_ONLY_ACTIONS"),const("UBUNTU_ONLY_ACTIONS"),const("NON_CANONICAL_ON_FEDORA"),const("FEDORA_ONLY_ACTIONS")
print("sizes", len(d),len(u),len(n),len(f))
print("pairwise overlaps", len(d&u), len(d&n), len(u&n))
print("union of the three", len(d|u|n))
print("runs on Debian unchanged (debian_only + non_canonical)", len(d|n))
PY
sizes 19 10 38 25
pairwise overlaps 0 0 0
union of the three 67
runs on Debian unchanged (debian_only + non_canonical) 57
Recount against the constants rather than trusting either figure, and say in
the PR which number you got. Whether all 57 belong in a Debian support claim
is a judgement about apt behaviour on bookworm, not arithmetic.
The version floor, and the decision it needs
Ubuntu's floor is major >= 20 (the constant at distro.rs:36, the comparison at distro.rs:251) on the stated rationale that older releases stop receiving security updates. Applying the same rule to Debian, with dates from Debian's own release pages:
| release |
full support ended |
LTS ends |
eligible under the Ubuntu rule? |
| 11 bullseye |
2024-08-14 |
2026-08-31 |
no, LTS ended on 2026-08-31 |
| 12 bookworm |
2026-07-11 |
2028-06-30 |
yes, in LTS |
| 13 trixie |
2028-08-09 |
2030-06-30 |
yes, current stable |
So the floor is 12, and OLDEST_SUPPORTED_DEBIAN alongside its Ubuntu sibling is the natural shape.
The part that needs your call: version is Option<u32> because Debian testing and sid publish no VERSION_ID at all, so both parse to None. Debian's security team does not support sid. Refusing None is consistent with the security-updates rationale and is what I would do, but it means Debian stable/testing in the support matrix becomes stable-only, and that row should then be reworded.
Scope
- Add
OLDEST_SUPPORTED_DEBIAN: u32 = 12 and give Debian { version } its own arm comparing against it, which also closes a standing gap: the parsed version is currently never compared by any gate, so Debian 11, 12 and 13 are indistinguishable to the whole tree.
- Decide and encode the
None case.
- Extend the doc comment on
is_supported to state Debian's reason, the way it does for the other three.
- Invert
unsupported_debian (distro.rs:1094), which asserts Debian { version: Some(12) } is unsupported. It is a real test, not a vacuous one, so it will fail and should.
- Add
os-release fixtures for trixie and for a no-VERSION_ID testing image, following the bookworm fixture at :643.
- Update
docs/distro-support.md: the eligibility prose, the tier row, and the wording of the Debian stable/testing line.
Validation before claiming support
Eligibility is not validation, and this repo keeps those apart deliberately. A Debian row should not read Validated until the story suite has run on it, which means ubuntu-vm.sh needs a Debian target or a sibling script. Expect real failures on the first run rather than a clean pass: NetplanGetConfig and GetAuthorizedKeys already fail on stock Ubuntu 24.04 for permission reasons, and Debian's firewall default is nftables rather than ufw (#239).
Recording a live run needs provider credentials, so comment here before starting that part.
Difficulty
medium. The code change is small and the judgement calls plus the validation run are the work.
Getting started
CONTRIBUTING.md has the build and test commands, and docs/distro-support.md explains the eligibility-versus-validation split. No CLA and no copyright waiver. The project is MIT.
docs/distro-support.md:120already promises this (this issue said line 91 when it was filed; the row has been at 120 the whole time):| Debian stable/testing | Planned after Ubuntu hardening |
Ubuntu hardening is where it was meant to be: three LTS releases validated at 79/79 on live VMs with committed replay twins. This issue is the follow-through.
What already exists
Debian is not an unrecognised distro. It is a parsed, first-class variant that is deliberately refused:
DistroId::Debian { version: Option<u32> }(crates/sysknife-core/src/distro.rs:142)detect_debianparsesVERSION_IDinto that field (distro.rs:453)family()already returnsDistroFamily::Debianfor it (distro.rs:205)distro.rs:643,:764,:958)ID_LIKEThe single line that refuses it (
distro.rs:252):Note what the doc comment above it justifies and what it does not. Pre-20.04 Ubuntu is excluded because it stops receiving security updates.
UbuntuCoreis excluded structurally, having no apt and a read-only root. Plain Fedora stays detectable so the planner can explain the gap. Debian is given no reason at all. It sits in that arm because nothing has needed it to be elsewhere.Why it is cheap
Of the 67 Debian-family actions, 57 run on Debian unchanged, and all 97
Allactions already do. That is 154 of 189, ≈ 81.5 %, with no new action code. #237
did the inventory and the list surgery, and it merged as #384 today, so nothing
blocks this now.
The version floor, and the decision it needs
Ubuntu's floor is
major >= 20(the constant atdistro.rs:36, the comparison atdistro.rs:251) on the stated rationale that older releases stop receiving security updates. Applying the same rule to Debian, with dates from Debian's own release pages:So the floor is 12, and
OLDEST_SUPPORTED_DEBIANalongside its Ubuntu sibling is the natural shape.The part that needs your call:
versionisOption<u32>because Debian testing and sid publish noVERSION_IDat all, so both parse toNone. Debian's security team does not support sid. RefusingNoneis consistent with the security-updates rationale and is what I would do, but it meansDebian stable/testingin the support matrix becomes stable-only, and that row should then be reworded.Scope
OLDEST_SUPPORTED_DEBIAN: u32 = 12and giveDebian { version }its own arm comparing against it, which also closes a standing gap: the parsed version is currently never compared by any gate, so Debian 11, 12 and 13 are indistinguishable to the whole tree.Nonecase.is_supportedto state Debian's reason, the way it does for the other three.unsupported_debian(distro.rs:1094), which assertsDebian { version: Some(12) }is unsupported. It is a real test, not a vacuous one, so it will fail and should.os-releasefixtures for trixie and for a no-VERSION_IDtesting image, following the bookworm fixture at:643.docs/distro-support.md: the eligibility prose, the tier row, and the wording of theDebian stable/testingline.Validation before claiming support
Eligibility is not validation, and this repo keeps those apart deliberately. A Debian row should not read
Validateduntil the story suite has run on it, which meansubuntu-vm.shneeds a Debian target or a sibling script. Expect real failures on the first run rather than a clean pass:NetplanGetConfigandGetAuthorizedKeysalready fail on stock Ubuntu 24.04 for permission reasons, and Debian's firewall default is nftables rather than ufw (#239).Recording a live run needs provider credentials, so comment here before starting that part.
Difficulty
medium. The code change is small and the judgement calls plus the validation run are the work.Getting started
CONTRIBUTING.md has the build and test commands, and docs/distro-support.md explains the eligibility-versus-validation split. No CLA and no copyright waiver. The project is MIT.