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.
Context
rspamd_ynhshipped an upgrade that fails on real servers but passes CI at level 8. The app installs a vendor.debwith a baredpkg -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: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_checkupgrades 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:
In CI, every file is in the
options.incstate. 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
preupgradealready runs an arbitrary script inside the container immediately before the upgrade, withAPP/DOMAIN/FROM_COMMITin scope (lib/tests.sh,_PREUPGRADE). So an app can already cover this today:But
preupgradeappears nowhere in the README — the Features list and thetests.tomlsection don't mention it. I only found it by readinglib/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(andpreinstall) 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 inpackage_linterinstead as a static check fordpkg -iwithout--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 thandpkg.