Skip to content

Upgrade tests run on pristine containers, so dpkg conffile-prompt deadlocks are invisible (and preupgrade is undocumented) #211

Description

@TabTwo

Context

rspamd_ynh shipped an upgrade that fails on real servers but passes CI at level 8. The app installs a vendor .deb with a bare dpkg -i. YunoHost runs app scripts with stdin closed, so when dpkg finds a locally modified conffile it prints a prompt, reads EOF and aborts the whole transaction, leaving the package half-configured:

*** worker-controller.inc (Y/I/N/O/D/Z) [default=N] ? dpkg: error processing package rspamd (--install):
 end of file on stdin at conffile prompt

Two independent reports on consecutive days: YunoHost-Apps/rspamd_ynh#53, fix in YunoHost-Apps/rspamd_ynh#54.

Why the upgrade tests can't see it

The app does configure an upgrade test, and it passes. The trigger for this bug is a conffile that differs from the one the package shipped — and package_check upgrades inside a fresh LXC container, where by construction every conffile is pristine. dpkg therefore classifies them all as unmodified, replaces them silently, and never asks a question.

The failing log shows both branches side by side on the same run:

Installing new version of config file /etc/rspamd/options.inc ...     <- unmodified -> silent
Configuration file '/etc/rspamd/worker-controller.inc'
 ==> Modified (by you or by a script) since installation.             <- modified -> prompt -> EOF -> abort

In CI, every file is in the options.inc state. So "upgrade from previous version" validates the one scenario in which the bug is guaranteed invisible, and any app installing Debian packages that ship conffiles has the same blind spot.

The mechanism already exists — it's just undiscoverable

preupgrade already runs an arbitrary script inside the container immediately before the upgrade, with APP/DOMAIN/FROM_COMMIT in scope (lib/tests.sh, _PREUPGRADE). So an app can already cover this today:

preupgrade = "echo '# locally modified' >> /etc/rspamd/worker-controller.inc"

But preupgrade appears nowhere in the README — the Features list and the tests.toml section don't mention it. I only found it by reading lib/parse_tests_toml.py. A packager who doesn't read the source has no way to know the hook exists, let alone that this failure mode is worth covering.

Suggestion

Smallest useful change: document preupgrade (and preinstall) in the README, ideally with the modified-conffile case as the worked example, since it's a failure mode packagers can't otherwise discover.

Optionally, and more speculatively: for apps that install .debs directly, a generic "dirty a conffile, then upgrade" scenario would catch this class without every packager having to think of it. I don't know whether that's worth the runtime, and detecting "installs a .deb" reliably may not be clean — happy to be told this belongs in package_linter instead as a static check for dpkg -i without --force-confdef --force-confold, which would be much cheaper and would have caught this particular case.

I couldn't survey how many apps in the catalog use a bare dpkg -i (no code-search access), so I can't say whether this is one app's mistake or a broader pattern. The two apps I spot-checked install via apt extras rather than dpkg.

Activity

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

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions