Skip to content

fix(bootstrap): explain why dnf packages cannot install on ostree hosts - #14353

Merged
jdx merged 3 commits into
mainfrom
fix/dnf-ostree-error
Oct 13, 2026
Merged

jdx merged 3 commits into
mainfrom
fix/dnf-ostree-error

Conversation

@jdx

@jdx jdx commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

https://entire.io/gh/jdx/mise/trails/676

Summary

On Fedora Atomic hosts (Silverblue, Kinoite, Bazzite, secureblue, ...) /usr is read-only, so mise bootstrap packages apply with a dnf: entry ended in dnf's own error after the prompt:

$ mise bootstrap packages apply --manager dnf
dnf: install gimp? Yes
mise $ dnf install -y gimp
Error: /usr is configured to be read-only. You may be running an image-based or immutable operating system. ...

mise now detects an ostree-booted host (/run/ostree-booted) before running dnf and stops with an explanation:

cannot install dnf packages: this host is booted from an ostree image, so /usr is read-only
Bake the packages into the image, install them in a toolbox, or layer them yourself with
  rpm-ostree install <packages>

Status checks are unchanged (rpm -q already works there), and the file is absent inside a bootc/Containerfile build, so dnf keeps working in image builds. Opt-in layering through rpm-ostree follows in a stacked PR.

Part of discussion #14278.

Test plan

  • cargo test --lib dnf

AI-assisted — Tool: Claude Code; model: anthropic/claude-sonnet-5-5; version: unavailable.

🤖 Generated with Claude Code


Note

Low Risk
Localized bootstrap UX change for dnf only; no change to install paths on normal Linux hosts or to package status checks.

Overview
dnf bootstrap on Fedora Atomic / ostree hosts now fails fast with a clear message instead of invoking dnf and surfacing a read-only /usr error.

Detection uses the presence of /run/ostree-booted; install and upgrade return guidance to bake packages into the image, use a toolbox, or run rpm-ostree install. Status via rpm -q is unchanged, and non-ostree environments (including bootc Containerfile builds without the marker) still run dnf as before. Docs add a Fedora Atomic hosts section; unit tests cover the marker, error text, and install/upgrade rejection.

Reviewed by Cursor Bugbot for commit 97d1e5b. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • On Fedora Atomic hosts, package installation and upgrades now stop with guidance on supported alternatives instead of attempting to run dnf. RPM status checks remain available.
    • dnf continues to work normally on hosts that are not booted from an ostree image, including bootc Containerfile builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4S46J0YFMTZ1JP3QHKB9H3V
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c6f7a2e0-2ec3-4cfb-8a24-a99af6f1134d
📥 Commits

Reviewing files that changed from the base of the PR and between 43c0bdf and 97d1e5b.

📒 Files selected for processing (2)
  • docs/bootstrap/packages/dnf.md
  • src/system/packages/dnf.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3b2b4cd9-e696-4ac9-be23-1ab5ce7651c9
📥 Commits

Reviewing files that changed from the base of the PR and between f92bddd and 43c0bdf.

📒 Files selected for processing (2)
  • docs/bootstrap/packages/dnf.md
  • src/system/packages/dnf.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

📝 Walkthrough

Walkthrough

mise checks whether a host is booted from an ostree image before DNF installation or upgrade. If the marker exists, these operations return an error with alternative installation methods. Documentation describes this behavior and notes that RPM status checks remain available.

Changes

DNF on ostree-booted hosts

Layer / File(s) Summary
Ostree detection and error
src/system/packages/dnf.rs
The DNF module checks for /run/ostree-booted and defines an error that describes the read-only /usr condition and suggests alternatives.
Install and upgrade guards
src/system/packages/dnf.rs, docs/bootstrap/packages/dnf.md
Install and upgrade return the ostree-specific error when the marker exists. Tests cover detection and operation behavior. The documentation describes the behavior, available alternatives, RPM status checks, and bootc Containerfile builds.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant mise
  participant ostreeMarker as /run/ostree-booted
  participant dnf as DNF
  mise->>ostreeMarker: Check whether the marker exists
  alt Marker exists
    mise-->>mise: Return ostree-specific error
  else Marker does not exist
    mise->>dnf: Continue existing install or upgrade command path
  end
Loading

Merge Risk: ⚪ Minimal · up to 43c0b

On Fedora Atomic hosts, dnf package installs now stop with a clear explanation instead of failing inside dnf. Behavior on other hosts is unchanged, and no actionable merge risk was found.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 1 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: explaining why DNF package installation fails on ostree hosts.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 1 files. (1 skipped: 1 unsupported.)

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

The PR checks /run/ostree-booted before installing or upgrading dnf packages and explains the supported alternatives.

  • dnf installs and upgrades stop with guidance on ostree-booted hosts.

The previous and current heads have different merge bases with the target commit, so this review used the full PR diff.

Reviews (3) · Last reviewed commit: "Merge origin/main into fix/dnf-ostree-er..." · Reviewed by Greptile

Comment thread src/system/packages/dnf.rs Resolved
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4S55VFG1MTJ5XDE0V18M975
@jdx
jdx added this pull request to stack #14365 October 12, 2026 23:39
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4S8B5ZSYQBMPC205NHX32KX
@jdx
jdx merged commit 4f09636 into main Oct 13, 2026
52 checks passed
@jdx
jdx deleted the fix/dnf-ostree-error branch October 13, 2026 01:31
jdx added a commit that referenced this pull request Oct 13, 2026
…hosts (#14357)

<!-- entire-trail-link-start -->
https://entire.io/gh/jdx/mise/trails/680
<!-- entire-trail-link-end -->

## Summary

On Fedora Atomic hosts (Silverblue, Bazzite, secureblue, ...) `dnf
install` cannot work because `/usr` is read-only; packages are layered
with `rpm-ostree` and take effect on the next boot. This lets the `dnf`
manager do that, **opt-in**, so one `[bootstrap.packages]` list keeps
working on Fedora Workstation, atomic hosts, and bootc image builds:

```toml
[settings]
system_packages.ostree = "layer" # default: "error"
```

```
$ mise bootstrap packages apply --manager dnf
mise $ sudo rpm-ostree install --idempotent --allow-inactive gimp
dnf: reboot to start using the layered packages
```

With the default `"error"`, `apply` explains why it stopped and points
at this setting (stacked on #14353, which adds the ostree detection).

With `layer`:

- one `rpm-ostree install --idempotent --allow-inactive` transaction for
the whole batch (each call builds a new deployment); `--update` runs
`rpm-ostree refresh-md` first; a reboot notice follows
- packages in the next deployment's `requested-packages` count as
installed and show `pending reboot`, so later runs do not request them
again
- version pins are skipped with a warning
- `state = "absent"` runs `rpm-ostree uninstall`
- `upgrade` fails with a hint to run `rpm-ostree upgrade`, since layered
packages only update with the base image

Not included, left as follow-ups: `--apply-live`, calling `rpm-ostree`
without sudo through polkit for local admin sessions, and `mise
bootstrap packages prune` for dnf (dnf has no prune path today;
declarative removal covers uninstalling).

Part of discussion
[#14278](#14278).

## Test plan

- `cargo test --lib dnf` (argument building, `requested-packages`
parsing, pending state)
- `cargo clippy --workspace --all-features --all-targets -- -D warnings`
- Not run on a real ostree host.

*AI-assisted — Tool: Claude Code; model: anthropic/claude-sonnet-5-5;
version: 2.1.295.*

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for managing DNF packages on Fedora hosts booted from
OSTree images. Opt in to package layering with `system_packages.ostree =
"layer"`; package changes take effect after reboot.
  * Packages queued for the next boot are reported as pending reboot.
* **Limitations**
* By default, package changes on OSTree hosts are refused. Layering
doesn’t support version pins or DNF groups. Use `rpm-ostree upgrade` to
upgrade packages; packages included in the base image can’t be removed.
* **Documentation**
  * Added guidance on enabling OSTree package layering and its behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
env ▆▆▃▁█ 27,884,702 → 27,944,528 +0.21% 20.14 → 19.10ms -5.15%
hook-env ▅█▅▁▄ 30,207,994 → 30,229,311 +0.07% 20.81 → 20.71ms -0.47%
ls ▃▂▄▁█ 32,098,384 → 32,192,270 +0.29% 21.99 → 21.98ms -0.06%
registry ▁▂▂▁█ 40,163,734 → 40,247,946 +0.21% 18.22 → 17.68ms -2.98%
startup ▇▅█▁▅ 11,072,896 → 11,079,815 +0.06% 12.24 → 12.18ms -0.49%

No instruction-count regression above 1%.

Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run.

Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.

97d1e5b8345c vs 5b983e14a87f · measured on the runner, not pushed to the history.

jdx pushed a commit that referenced this pull request Oct 13, 2026
<!-- entire-trail-link-start -->
https://entire.io/gh/jdx/mise/trails/640
<!-- entire-trail-link-end -->

### 🚀 Features

- **(bootstrap)** add apt repositories and PPAs with
[bootstrap.apt.sources] by @jdx in
[#14306](#14306)
- **(bootstrap)** embed apt source signing keys inline in .sources files
by @jdx in [#14348](#14348)
- **(bootstrap)** render templates in [bootstrap.repos] checkout paths
by @jdx in [#14359](#14359)
- **(bootstrap)** declare macOS login items by @jdx in
[#14354](#14354)
- **(bootstrap)** layer dnf packages with rpm-ostree on Fedora Atomic
hosts by @jdx in [#14357](#14357)
- **(bootstrap)** support run0 and doas for privilege elevation by @jdx
in [#14351](#14351)
- **(dotfiles)** show tracked entries that no variant selects in status
by @jdx in [#14266](#14266)
- **(dotfiles)** group mise dot status into sections by @jdx in
[#14361](#14361)
- **(github)** add oauth_copy_code to copy the device code to the
clipboard by @jdx in [#14269](#14269)
- **(github)** keep the native OAuth refresh token in a password manager
or keychain by @jdx in [#14360](#14360)
- **(http)** add libc() and avx2() template functions by @jdx in
[#14298](#14298)
- **(pypi)** read upload times from PEP 691 simple indexes by @jdx in
[#14352](#14352)
- **(rust)** download rustup-init from RUSTUP_UPDATE_ROOT by @jdx in
[#14356](#14356)

### 🐛 Bug Fixes

- **(aqua)** warn once for packages with no repo to list versions from
by @jdx in [#14336](#14336)
- **(backend)** keep decoded download file names inside the download
directory by @JamBalaya56562 in
[#14274](#14274)
- **(bootstrap)** support dnf package groups by @jdx in
[#14347](#14347)
- **(bootstrap)** don't print a stdin-only helper as a manual command by
@jdx in [#14350](#14350)
- **(bootstrap)** explain why dnf packages cannot install on ostree
hosts by @jdx in [#14353](#14353)
- **(brew)** install bottle etc files and run post_install_steps when
pouring by @jdx in [#14272](#14272)
- **(brew)** refuse to chown a prefix owned by another user by @jdx in
[#14345](#14345)
- **(brew)** preserve protected post-install copy destinations by @jdx
in [#14311](#14311)
- **(brew-cask)** support generate completions in evaluated casks by
@pmmmwh in [#14255](#14255)
- **(docker)** install libatomic1 in the Debian image so Node.js 26 runs
by @jdx in [#14303](#14303)
- **(dotfiles)** keep existing ciphertext when an encrypted file is
unchanged by @jdx in [#14300](#14300)
- **(dotfiles)** stop a newly selected profile from deleting its shared
file by @jdx in [#14275](#14275)
- **(dotfiles)** restore config entries when adopting under a symlinked
home by @jdx in [#14315](#14315)
- **(dotfiles)** show per-file diffs for directory copy entries in dot
diff by @jdx in [#14333](#14333)
- **(dotfiles)** apply mise's own config before planning the other
entries by @jdx in [#14339](#14339)
- **(go)** read an exact go line in go.mod as the toolchain version by
@jdx in [#14308](#14308)
- **(http)** retry artifact downloads started by mise exec and shims by
@jdx in [#14342](#14342)
- **(npm)** resolve unlocked latest to the npm version of a graph-locked
install by @jdx in [#14307](#14307)
- **(npm)** give unlocked requests their own install under the identity
layout by @jdx in [#14309](#14309)
- **(oci)** ignore host apt hooks when installing apt bootstrap packages
by @jdx in [#14296](#14296)
- **(packslip)** let a workflow list carry the signer pin across a
workflow move by @jdx in
[#14338](#14338)
- **(packslip)** keep a renamed project's lockfile signer under packslip
1.7 by @jdx in [#14362](#14362)
- **(prune)** keep a uv-locked pipx tool's installs for every Python by
@jdx in [#14335](#14335)
- **(prune)** keep versions of task-scoped tools by @jdx in
[#14334](#14334)
- **(schema)** accept [bootstrap.secrets] by @JamBalaya56562 in
[#14322](#14322)
- **(schema)** accept [bootstrap.groups] and [bootstrap.users] by
@JamBalaya56562 in [#14340](#14340)
- **(schema)** accept table forms for vars._.file and vars._.source by
@JamBalaya56562 in [#14383](#14383)
- **(shim)** keep other projects' command wrappers on reshim by @jdx in
[#14355](#14355)
- **(task)** bump gix so remote tasks work in git hooks by @any-victor
in [#14316](#14316)
- hide the Windows console for the tool-update service by @jdx in
[#14344](#14344)

### 🚜 Refactor

- replace serde_yaml with serde-saphyr by @jdx in
[#14328](#14328)

### 📚 Documentation

- **(bootstrap)** explain when to use --from versus --adopt by @jdx in
[#14301](#14301)
- **(bootstrap)** recommend fish = "auto" for fish shell activation by
@jdx in [#14304](#14304)
- **(dotfiles)** explain when to use sync, pull, and apply by @jdx in
[#14343](#14343)
- **(shims)** explain how to check whether a tool is installed by @jdx
in [#14337](#14337)
- switch body text to DM Sans and tighten the releases page by @jdx in
[#14302](#14302)
- self-host DM Sans and JetBrains Mono so they load on mise.jdx.dev by
@jdx in [#14310](#14310)

### 🧪 Testing

- **(bootstrap)** don't depend on bc being absent in the apt sources
test by @jdx in [#14371](#14371)

### 📦️ Dependency Updates

- install Homebrew uses_from_macos dependencies on Linux by @itskyf in
[#14288](#14288)
- bump packslip to 1.7.0 by @jdx in
[#14363](#14363)
- lock file maintenance by @renovate[bot] in
[#14320](#14320)
- update github actions by @renovate[bot] in
[#14380](#14380)
- update dependency python to v3.14.8 by @renovate[bot] in
[#14381](#14381)
- update rust crates by @renovate[bot] in
[#14382](#14382)
- update docker images by @renovate[bot] in
[#14379](#14379)

### 📦 Registry

- install opencode 2 from npm by @jdx in
[#14295](#14295)

### Chore

- **(ci)** switch to jactionlint v2 and drop zizmor by @jdx in
[#14256](#14256)
- fix CI for the aqua registry update and the unsafe-libyaml advisory by
@jdx in [#14326](#14326)

### Ci

- double the unit-macos job timeout by @jdx in
[#14346](#14346)

### Security

- **(config)** require trust for path: tool versions by @jdx in
[#14358](#14358)

### New Contributors

- @pmmmwh made their first contribution in
[#14255](#14255)
- @itskyf made their first contribution in
[#14288](#14288)

## 📦 Aqua Registry Updates

### New Packages (3)

- [`Alice39s/xmtr`](https://github.com/Alice39s/xmtr)
-
[`bufbuild/protoschema-plugins/protoc-gen-jsonschema`](https://github.com/bufbuild/protoschema-plugins)
- [`takumin/qemu-bin`](https://github.com/takumin/qemu-bin)

### Updated Packages (3)

- [`bootandy/dust`](https://github.com/bootandy/dust)
-
[`connectrpc/connect-go/protoc-gen-connect-go`](https://github.com/connectrpc/connect-go)
- [`sharkdp/hyperfine`](https://github.com/sharkdp/hyperfine)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant