Skip to content

bootc-fetch-apply-updates.timer/service never run on the composefs backend (/run/ostree-booted condition) #2578

Open

Description

@SNThrailkill

Describe the bug

On a composefs-backend (sealed / UKI) install, the automatic update units never run:

$ systemctl status bootc-fetch-apply-updates.timer
○ bootc-fetch-apply-updates.timer - Apply bootc updates
     Loaded: loaded (/usr/lib/systemd/system/bootc-fetch-apply-updates.timer; enabled; preset: enabled)
     Active: inactive (dead)
    Trigger: n/a
   Triggers: ● bootc-fetch-apply-updates.service
  Condition: start condition unmet at ...; 8min ago
             └─ ConditionPathExists=/run/ostree-booted was not met

/run/ostree-booted is only created by the ostree backend (ostree-prepare-root). On the composefs backend that service is skipped (ConditionKernelCommandLine=ostree) and no equivalent marker is created, so both bootc-fetch-apply-updates.timer and bootc-fetch-apply-updates.service carry an unmet ConditionPathExists=/run/ostree-booted and stay inactive forever.

Impact

Hosts installed with the composefs backend (e.g. sealed images booting a signed UKI) never fetch or apply updates automatically; they need an explicit bootc upgrade. The units are shipped enabled and there is no composefs-specific equivalent, so this is silent.

Environment

  • bootc 1.16.13 (quay.io/fedora/fedora-bootc:44, systemd 259.9)
  • Installed with bootc install to-disk from a sealed (UKI + composefs + fs-verity) image with systemd-boot + Secure Boot
  • Running as a VM on Proxmox VE 9.2; reproduced on a plain composefs install too

Workaround

# /etc/systemd/system/bootc-fetch-apply-updates.timer.d/10-composefs.conf
[Unit]
ConditionPathExists=

and the same drop-in for bootc-fetch-apply-updates.service. Verified: the timer then becomes active and schedules normally.

Suggested fix

Either drop the condition and let bootc itself decide whether the host is bootc-managed, or use a marker both backends provide (the composefs backend already creates /ostree/bootc; a /run/bootc-booted-style marker or a bootc status check would work).

Activity

  1. SNThrailkill commented on Oct 18, 2026

    @SNThrailkill
    Author

    The issue is that for the units systemd/bootc-fetch-apply-updates.timer/service, and also systemd/bootc-status-updated.target all three carry:

    ConditionPathExists=/run/ostree-booted
    

    crates/lib/src/generator.rs already documents this exact gap and works around it for other units:

    // === Relabel unit: runs for transient composefs boots (native or ostree) ===
    // Must be before the ostree-booted guard because native composefs boots do
    // not write /run/ostree-booted, but still need the relabel unit when any
    // transient overlay is active.
    
    // === Shadow sync unit: runs for ALL bootc systems (native composefs or ostree) ===
    // Must be before the ostree-booted guard because native composefs boots do
    // not write /run/ostree-booted, but still need the shadow sync to clean up
    // stale shadow/gshadow entries (the rechunk scenario).  Gate on either a
    // composefs mount source (native composefs boot) or the ostree-booted marker.
    

    So the pattern is established: gate on either a composefs mount source (composefs: / transient:composefs=) or the /run/ostree-booted marker. The fetch-apply units (and bootc-status-updated.target) were simply missed, so on native composefs installs:

    • bootc-fetch-apply-updates.{timer,service} never start (no automatic updates), and
    • bootc-status-updated.target never activates, so cached bootc status data is not refreshed on changes.

    Suggested fix: apply the same composefs-or-marker gate to those three units (or write /run/ostree-booted on native composefs boots). Repro is a plain bootc install to-disk from a sealed/composefs image.

  2. cgwalters commented on Oct 23, 2026

    @cgwalters
    Collaborator

    See #2437

  3. cgwalters-bot commented on Oct 23, 2026

    @cgwalters-bot
  4. cgwalters-bot commented on Oct 23, 2026

    @cgwalters-bot
  5. cgwalters-bot commented on Oct 23, 2026

    @cgwalters-bot
    Contributor

    Triage: the bug is valid and the cause is clear. It is a duplicate of the open draft #2437, which is blocked on a design decision, not on code.

    • Cause: bootc-fetch-apply-updates.timer, .service and also bootc-status-updated.target all have ConditionPathExists=/run/ostree-booted. Only ostree creates that file. Native composefs boots never write it, which generator.rs already works around for its own units. I didn't boot a test system; the reporter's systemctl status output matches what the code would do.
    • Why it isn't a one-line fix: In the #2437 review, @cgwalters raised a compatibility concern. Existing composefs users may depend on the timer not running because they bring their own update tooling, so turning it on now silently changes behaviour for them. Turning the timer off for ostree would break things the other way. There was no agreed way out of this "without VERY LOUD warnings on either side".
    • The current Remove condition on existance of /run/ostree-booted for bootc systemd units/timers #2437 diff (ConditionKernelCommandLine=|composefs) is incomplete. As noted in that review, Add support for multiple composefs= digests #2248 (now merged) added the composefs.digest= karg, and kernel command lines that use only that karg won't match. The check suggested in review is better: test whether / is mounted from composefs, as the generator does, or add a /run/composefs-booted marker. Either way, bootc-status-updated.target should get the same change.

    What a fix needs:

    1. A maintainer decision on the default: enable on composefs (with a release note, and maybe a warning) or keep it off and document how to enable it.
    2. A reliable composefs-booted signal: a /run/composefs-booted written early, or a generator that turns the units on when it detects composefs.
    3. Update all three units and add a composefs integration test checking that the timer is active.

    Unclear: whether composefs users depend on the current no-auto-update behaviour, and so which default to pick. Until that's decided, the reporter's drop-in that clears ConditionPathExists= is a fine opt-in workaround.

    Generated by dispatch · ◷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions