commit a6676a630821debefd92fac278b0c9ce57cd47da Author: Pedro Amorim Date: Tue Oct 6 12:25:10 2026 +0000 Bug 21860: DBRev 26.06.00.031 Signed-off-by: Pedro Amorim commit 92cd44105667bc362ee924a563a5a393ec8c6a04 Author: Laura Escamilla Date: Mon Sep 28 20:44:31 2026 +0000 Bug 21860: Update fields with missing subfields Signed-off-by: Pedro Amorim commit 8697e2697802b2fcca9c3033ce45a43a90809663 Author: Laura Escamilla Date: Fri Sep 25 19:52:36 2026 +0000 Bug 21860: Address additional QA findings Signed-off-by: Pedro Amorim commit 96641f560dd88bf0682c22edff3d4bf4437f21fa Author: Laura Escamilla Date: Thu Sep 3 20:23:55 2026 +0000 Bug 21860: Address additional QA findings Addressed the additional QA findings. * Updated update_field indicator handling so indicators are treated as values to update rather than match criteria. Existing fields are now updated in place rather than creating a duplicate when the requested indicator differs. * Preserved the "add new" behavior when the target field/subfield does not exist. * Moved the _ to MARC blank indicator conversion until after form validation succeeds, so failed validation does not alter the displayed indicator values. * Added/updated regression coverage for updating indicators on an existing field. Manually tested both update and add-new behavior in the staff interface, including the reported 650 _0 → 650 70 case and the failed-validation _ behavior. QA tools and tests pass: * t/SimpleMARC.t * t/db_dependent/MarcModificationTemplates.t * node --check * git diff --check * qa Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit 3f9c2b22a515849a394a4a0adbd87ade09a85261 Author: Laura Escamilla Date: Thu Aug 27 20:02:31 2026 +0000 Bug 21860: Address QA findings Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit 9d5dc0559a58fea76eab58aad9221d7b1cd6ec0c Author: Laura Escamilla Date: Thu Aug 13 19:48:50 2026 +0000 Bug 21860: Fix indicator handling and display Signed-off-by: Angela Berrett Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit 502f8aba8810fc06cd3614bb3f6013648f1d25f0 Author: Laura Escamilla Date: Tue Aug 11 22:26:16 2026 +0000 Bug 21860: Apply MARC indicators in modification templates Test plan 1. Apply the database update for this bug if it has not already been applied. 2. Run the automated tests: prove t/SimpleMARC.t prove t/db_dependent/MarcModificationTemplates.t Both tests should pass. Also run: perl -c C4/MarcModificationTemplates.pm perl -c Koha/SimpleMARC.pm node --check koha-tmpl/intranet-tmpl/prog/js/marc_modification_templates.js git diff --check The Perl files should report "syntax OK". The JavaScript syntax check and git diff --check should return no errors. MARC modification template UI ----------------------------- 3. Go to: Tools > MARC modification templates 4. Create a new template named: Bug 21860 indicator test 5. Test whole-field copy with source and destination indicators. Add a new action with: Action: Copy Field number: All Source field: 650 Source subfield: blank Check "Use indicators" Source indicator 1: 1 Source indicator 2: 7 Destination field: 651 Destination subfield: blank Destination indicator 1: one blank space Destination indicator 2: 0 Condition: blank Description: Copy 650 ind 1/7 to 651 ind blank/0 6. Save the action. 7. Confirm that the action summary displays the source and destination indicators, for example: Copy field 650 (ind1: 1, ind2: 7) to 651 (ind1: , ind2: 0) 8. Click Edit on the action. 9. Confirm that all saved values are restored: - Use indicators is checked - Source field is 650 - Source indicator 1 is 1 - Source indicator 2 is 7 - Destination field is 651 - Destination indicator 1 is blank - Destination indicator 2 is 0 10. Change destination indicator 2 to another value, save the action, then edit it again. Confirm that the updated indicator value is retained. Conditional indicators ---------------------- 11. Test conditional indicators. Add or edit an action with: Action: Copy Source field: 650 Source indicators: 1 / 7 Destination field: 651 Destination indicators: blank / 0 Condition: if Conditional field: 650 Conditional subfield: blank Conditional indicator 1: 1 Conditional indicator 2: 7 Comparison: exists 12. Save the action. 13. Confirm that the action summary displays the conditional indicators, for example: Copy field 650 (ind1: 1, ind2: 7) to 651 (ind1: , ind2: 0) if 650 (ind1: 1, ind2: 7) exists 14. Edit the action again and confirm that the conditional indicator values are restored. Form reset behavior ------------------- 15. Test form reset behavior. - Edit an existing action containing indicator values. - Click Cancel. - Click New action. - Confirm that "Use indicators" is unchecked. - Check "Use indicators". - Confirm that the source, destination, and conditional indicator fields do not contain values from the previously edited action. Control fields -------------- 16. Test control fields as the source. Create a Copy action and enter 008 as the source field. Check "Use indicators". Confirm that: - the source indicator inputs are not displayed - indicator controls are not offered for the 008 field 17. Enter 650 as the destination field. Confirm that: - destination indicator 1 and indicator 2 inputs are displayed - source indicator inputs remain hidden for 008 18. Test the reverse direction. Source field: 650 Destination field: 008 Use indicators: checked Confirm that: - source indicator inputs are displayed for 650 - destination indicator inputs are not displayed for 008 19. Test a control field as the conditional field. Select a condition such as "if" and enter 008 as the conditional field. Confirm that conditional indicator inputs are not displayed. 20. Change the conditional field to 650. Confirm that conditional indicator 1 and indicator 2 inputs become available. Functional testing against a bibliographic record ------------------------------------------------- 21. Create or edit a test bibliographic record so that it contains at least these two fields: 650 17 $a Dogs 650 _0 $a Cats In the examples above, "_" represents a literal blank MARC indicator. The two fields deliberately use different indicators so that indicator matching can be verified. Source indicator filtering -------------------------- 22. Create a new MARC modification template/action: Action: Copy Field number: All Source field: 650 Source subfield: blank Use indicators: checked Source indicator 1: 1 Source indicator 2: 7 Destination field: 651 Destination subfield: blank Destination indicator 1: leave unset Destination indicator 2: leave unset 23. Apply the template to the test bibliographic record using: Tools > Batch record modification 24. Open the modified bibliographic record. Expected result: 650 17 $a Dogs 650 _0 $a Cats 651 17 $a Dogs There should NOT be a new: 651 _0 $a Cats This confirms that source indicator criteria select only fields whose indicators match 1/7. Explicit blank source indicator ------------------------------- 25. Create another action using: Action: Copy Field number: All Source field: 650 Source subfield: blank Use indicators: checked Source indicator 1: one literal blank space Source indicator 2: 0 Destination field: 651 Destination subfield: blank Destination indicator 1: leave unset Destination indicator 2: leave unset 26. Apply the template to the test record. Expected result: 651 _0 $a Cats The following should NOT be copied by this action: 650 17 $a Dogs This confirms that a literal blank MARC indicator can be explicitly matched and is distinct from leaving an indicator criterion unset. Partial source indicator matching --------------------------------- 27. Create another action: Action: Copy Field number: All Source field: 650 Source subfield: blank Use indicators: checked Source indicator 1: 1 Source indicator 2: leave unset Destination field: 651 Destination subfield: blank Destination indicator 1: leave unset Destination indicator 2: leave unset 28. Apply the template. Expected result: 651 17 $a Dogs The following should NOT be copied: 650 _0 $a Cats This confirms that an unset source indicator does not require a blank indicator. Instead, no restriction is applied for that indicator position. Explicit destination indicators ------------------------------- 29. Create another action: Action: Copy Field number: All Source field: 650 Source subfield: blank Use indicators: checked Source indicator 1: 1 Source indicator 2: 7 Destination field: 651 Destination subfield: blank Destination indicator 1: one literal blank space Destination indicator 2: 0 30. Apply the template. Expected destination field: 651 _0 $a Dogs Confirm in the MARC editor that: - indicator 1 is blank - indicator 2 is 0 - $a contains Dogs This confirms that explicitly supplied destination indicators replace the source indicators on the newly created destination field. Unset destination indicators ---------------------------- 31. Repeat the copy from: 650 17 $a Dogs to field 651, but leave both destination indicators completely unset. Expected destination: 651 17 $a Dogs This confirms that when destination indicators are not supplied, the source field's indicators are preserved. Regression testing ------------------ 32. Create a MARC modification template action without selecting "Use indicators". Configure a normal existing MARC modification operation. 33. Save, edit, and apply the action. Confirm that the action behaves as it did before this enhancement and that indicator criteria are not required. 34. Confirm that existing MARC modification template actions created before this enhancement can still be viewed, edited, and applied without adding indicator criteria. Additional functional coverage ------------------------------ 35. Test Move with indicators against a bibliographic record. Verify that: - only the source field matching the specified indicators is moved - the source field is removed - the destination field is created - explicitly supplied destination indicators are applied - unrelated fields are unchanged 36. Test Copy and replace with indicators against a bibliographic record. Verify that: - only fields matching the source indicator criteria participate in the operation - the destination receives the expected value - explicitly supplied destination indicators are applied - unrelated fields are unchanged 37. Test a conditional indicator criterion against an actual bibliographic record. Use a record containing fields with different indicators. Confirm that the action runs when the conditional field has the requested indicators. 38. Repeat the conditional test using indicators that do not match the record. Confirm that the action does not run. 39. Confirm the resulting MARC record after each functional test and verify that no unrelated fields or indicators have been modified. 40. Sign off and have a wonderful day! :D Sponsored-by: koha-US Signed-off-by: Angela Berrett Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit 6ab639a62ebf683b1ec26ca1edbc105d90326cda Author: Laura Escamilla Date: Mon Aug 3 20:43:58 2026 +0000 Bug 21860: Store and apply MARC indicator criteria Signed-off-by: Angela Berrett Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit 54a6bc3f712000e50328302853ffce9fdf687abb Author: Laura Escamilla Date: Mon Aug 3 16:17:27 2026 +0000 Bug 21860: Add indicator matching to field_equals Signed-off-by: Angela Berrett Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit fec53ec258d4dd63f98f5408a5f77c8d7566b365 Author: Laura Escamilla Date: Mon Aug 3 16:04:49 2026 +0000 Bug 21860: Add indicator matching to field_exists Signed-off-by: Angela Berrett Signed-off-by: Matt Blenkinsop Signed-off-by: Pedro Amorim commit 751129ae6018b1586d37a39bd0cb3b506f5db35c Author: Martin Renvoize Date: Thu Oct 1 11:03:37 2026 +0100 Bug 43570: (QA follow-up) Make SafeURL a static filter SafeURL's filter() never reads $args or $config, so there is no need for it to be a dynamic filter re-created on every FILTER invocation - unlike HtmlScrubber, which genuinely needs _DYNAMIC for its 'type' config argument. David Cook noted in comment #8 that the _DYNAMIC flag here was likely copied out of habit rather than need. Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 86f905d65002dadbff23f4b82375e8d5d4980e9a Author: Martin Renvoize Date: Thu Oct 1 11:02:52 2026 +0100 Bug 43570: (QA follow-up) Guard weaken() against non-ref/already-weak _CONTEXT Mirror the guard Template-Toolkit itself uses from 2.29 onwards (weaken(...) if ref ... && !isweak ...) instead of the bare weaken() copied from the 2.28 fix. Calling weaken() twice on the same slot is harmless in the Scalar::Util shipped with Koha's supported perls, so this isn't fixing a live bug, just bringing us in line with upstream's own defensive style, as noted by David Cook in comment #7. Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit a142bae70324801ab9deadcf3ac98b6f21aaa16f Author: Kyle M Hall Date: Fri Sep 11 06:25:47 2026 -0400 Bug 43570: SafeURL and HtmlScrubber template plugins leak the template context on every request Every request that renders a template using the SafeURL or HtmlScrubber filter leaks the whole Template::Context: the compiled templates, the stash and everything in it. That's 7 to 13 MB per request that doesn't come back until the worker is recycled. Both plugins call install_filter, which stores a closure over the plugin in the context's filter provider, while the plugin holds the context in _CONTEXT. Template::Plugin::Filter has the weaken() that would break the cycle commented out. Weakening the plugin's reference to the context fixes it; the context is always alive while a template is being processed, which is the only time the plugin uses it. Test Plan: 1) Apply the first patch 2) prove t/db_dependent/Template/Plugin/SafeURL.t t/db_dependent/Template/Plugin/HtmlScrubber.t 3) Note the "template context is released" subtests fail 4) Set plack_workers to 1 and plack_max_requests to 5000 in koha-conf.xml 5) Restart all the things! 6) Note the RSS of the starman worker 7) Request /cgi-bin/koha/opac-detail.pl?biblionumber=N fifty times, using a different biblionumber each time 8) Note the worker has put on about 400 MB! 9) Apply the second patch 10) prove the tests again, note they pass! 11) Restart all the things and repeat steps 6 through 8 12) Note the worker stays put! Signed-off-by: Juliet Heltibridle Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit d7420462ab3afdc9325e96a8afd443094058b8ff Author: Kyle M Hall Date: Fri Sep 11 06:25:44 2026 -0400 Bug 43570: Add unit tests Signed-off-by: Juliet Heltibridle Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 103f60a4ff6dd5ef07316c154b0ec5430c1c08a4 Author: Alex Carver [Acerock7] Date: Thu Oct 1 13:06:46 2026 +0000 Bug 41253: Add new permission to superlibrarian test Adds items_modification_by_age to the superlibrarian subtest in t/Koha/Auth/Permissions.t Signed-off-by: Pedro Amorim commit d1f8d5bbd2d221457787262b6724049294d1ec56 Author: Tomás Cohen Arazi Date: Wed Sep 30 11:41:21 2026 -0300 Bug 42719: (QA follow-up) Adjust tests This patch makes the tests reflect the design decision made on the bug report. The author for the original 'No CGISESSID then error' patch signed off on this so I'm confident this is the right thing to do. Signed-off-by: Tomás Cohen Arazi Signed-off-by: Pedro Amorim commit 9f50aa986275d559be3d69bf94d3fe075de36558 Author: Lucas Gass Date: Wed Sep 30 14:06:29 2026 +0000 Bug 42667: (follow-up) Fix POD coverage Signed-off-by: Pedro Amorim commit 541cbd84a5323be81f3047aa648ef8086e84b31c Author: Jonathan Druart Date: Tue Sep 22 13:50:51 2026 +0200 Bug 42685: Fix Cypress test AssertionError: Timed out retrying after 10000ms: Expected to find content: 'Requesting Agencies' within the element: but never did. Signed-off-by: Pedro Amorim commit 36606ea9d600ed16cda05c2ee03e76e824706dab Author: Jonathan Druart Date: Tue Aug 25 11:26:00 2026 +0200 Bug 41674: Fix review issues - remove unused import, fix POD synopsis, remove unused variable Signed-off-by: Pedro Amorim commit 8526f2ac3ba1860ad8f0c79c0925c2612be6e183 Author: Jonathan Druart Date: Fri Jun 19 13:59:18 2026 +0200 Bug 41674: Use LinkPref for CurbsidePickup Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit a746db644c5b94d0fd064303928040f65ba8003a Author: Jonathan Druart Date: Fri Jun 19 13:56:44 2026 +0200 Bug 41674: Add a test Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit e7aee90141a908ff6eff0b7063adbab0f062a226 Author: Owen Leonard Date: Wed Jan 21 10:15:13 2026 -0500 Bug 41674: (follow-up) Proof of concept: branches.pl This patch implements the new template plugin on Administration -> Libraries. - When viewed as a user with permission to manage system preferences, the system preference names under "Reply-To", "Return-Path", and "MARC organization code" should be linked to system preferences, and the link should take you to the correct preference search. - When viewed as a user without permission the system preference names should not be linked. Sponsored-by: Athens County Public Libraries Signed-off-by: David Nind Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit 797c1b352ebef08cd4f99ac42842996bde56b731 Author: Owen Leonard Date: Wed Jan 21 09:19:08 2026 -0500 Bug 41674: Add template plugin for linking to system preferences based on user permission This patch adds a new template plugin, LinkPref, for displaying system preference names as links depending on the logged-in user's permission. Syntax: [% "SYSTEM_PREFERENCE_NAME" | html | $LinkPref %] For a user with 'CAN_user_parameters_manage_sysprefs' permission it outputs: SYSTEM_PREFERENCE_NAME For a user without permission it outputs: SYSTEM_PREFERENCE_NAME Sponsored-by: Athens County Public Libraries Signed-off-by: David Nind Signed-off-by: Jonathan Druart Signed-off-by: Pedro Amorim commit 106fabbf9a61cea7199eb62a984ab9cf4030c845 Author: Martin Renvoize Date: Tue Sep 29 15:21:00 2026 +0100 Bug 43669: Make t::lib::Selenium wait for the server to be ready This patch adds a wait_for_server_ready check to t::lib::Selenium, so Selenium-based tests poll the server until it responds successfully before driving it, instead of assuming it is already up. This guards against a race we saw on Jenkins CI: t/db_dependent/selenium/00-onboarding.t and t/db_dependent/selenium/01-installation.t both failed with 'no such element: //div[@class="alert alert-success"]' right after the DB connection check step, immediately after run_tests.pl's get_commands_to_reset_db() restarted Apache/Plack. That helper restarts services and moves straight to running the tests with no readiness wait, so the very first requests can hit workers that are still warming up. The test then fails on a missing page element even though there is no bug in the installer flow itself. A companion issue has been filed against koha-misc4dev to add a readiness wait to run_tests.pl itself: https://gitlab.com/koha-community/koha-misc4dev/-/work_items/108 Test plan: 1. Apply the patch 2. Run t/db_dependent/selenium/01-installation.t (or 00-onboarding.t) against a fresh empty database with KOHA_TESTING=1, as usual 3. Confirm it still passes normally 4. To exercise the new wait: drop/recreate the database, then run "sudo service apache2 restart" and "sudo service koha-common restart", then immediately run the selenium test with no delay. Confirm it either passes (server came up in time) or fails fast with a clear "Cannot wait more for the server to be ready" message rather than a confusing missing-element error deep in the install wizard Co-Authored-By: Claude Sonnet 5 Signed-off-by: Lucas Gass Signed-off-by: Pedro Amorim commit a86b7a063d21bf5a3b4537d4fd7219358befe1af Author: nela Date: Sat Sep 26 18:25:30 2026 +0100 Bug 43650: incremental EAN-13 barcodes produce unexpected sequence When incremental EAN-13 barcodes are selected for autoBarcode and new item records are created an unexpected sequence of barcode numbers is produced instead of a sequence where the barcode is incremented by 1 between each item. This patch fixes that by adding quote marks in the inline JS, thus treating the barcode as a string rather than an integer :+) Test plan: 1. have records imported from somewhere but with no items 2. autoBarcode set to incremental EAN-13 barcodes 3. create a number of new items 4. observe that the item barcodes don't follow the expected sequence 5. apply patch 6. delete item records 7. repeat step 3 8. observe that the item barcodes follow the expected sequence Signed-off-by: David Cook Signed-off-by: Pedro Amorim commit e1e08a7f5d0f199c16a732f54bd70620c1034bc7 Author: Martin Renvoize Date: Tue Sep 15 18:17:57 2026 +0100 Bug 40901: (QA follow-up) Keep daemon output in the traditional log files under systemd Under koha-sysv the daemon(1) wrapper captures each daemon's stdout and stderr to /var/log/koha/INSTANCE/NAME-output.log and NAME-error.log. The systemd units sent that output to the journal instead, so zebra-*.log, indexer-*.log, es-indexer-*.log, worker-*.log and sip-error.log silently stopped being written on migrated hosts, and log locations differed between the two init packages. Use StandardOutput=append: and StandardError=append: on those six units so the files are identical whichever package supervises the daemons. The shipped logrotate stanza uses copytruncate, which is safe with append-mode descriptors. The journal still records systemd's own messages about each unit, which is what matters when one fails to start, and README.Debian shows the drop-in for sites that prefer the journal. Plack and the Z39.50 responder already wrote their own files. The README, NEWS.Debian entry and koha-common(8) text that described the journal-only behaviour are updated accordingly. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 0aa04d9ddc0d8bd9936be8f58cef90f9062463df Author: Martin Renvoize Date: Tue Sep 15 17:40:49 2026 +0100 Bug 40901: (QA follow-up) Document the two init packages, the new options and the logging change - koha-systemd.README.Debian was written for the first prototype: it named koha-worker-long@.service (now koha-worker-long_tasks@), said SIP needs the sip.enabled flag file (no longer read on systemd hosts), described coexisting with koha-common and a manual migration that the postinst now performs. Rewrite it to describe the actual units, how the koha-* helpers map onto them, where each configuration value is read from, how to relax the sandboxing with a drop-in, and the migration. - Logging is the one visible behavioural change and was undocumented: with koha-sysv the daemon(1) wrapper captured stdout/stderr to /var/log/koha/INSTANCE/NAME-output.log and NAME-error.log; with koha-systemd that output is in the journal, so zebra-*, indexer-*, es-indexer-*, worker-* and sip-error.log are no longer written, while everything Koha writes itself (Starman's plack.log/plack-error.log, the log4perl files, z3950.log, the Apache vhost logs) is unchanged. Spell that out in README.Debian, in a new koha-systemd NEWS.Debian entry shown by apt-listchanges on upgrade, and in a "Service management" section of the koha-common(8) man page. - Add --enable/--disable to the koha-zebra, koha-worker, koha-indexer and koha-es-indexer man pages. - z3950_responder.pl's synopsis listed --config-dir twice and had lost -c, which is still a YAZ pass-through option; put -c back. - t/00-valid-systemd-units.t: give it the usual shebang, and ignore every "Command ... is not executable" line from systemd-analyze rather than only the ones for scripts in this repository. Whether starman or /usr/share/koha/bin/... is installed on the host running the test says nothing about the unit files, and the old filter made the test fail on any development machine without the Koha packages. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit ffe8b55b57ce4a5a0bfbb6db5c9a3151438acd67 Author: Martin Renvoize Date: Tue Sep 15 17:38:29 2026 +0100 Bug 40901: (QA follow-up) Make koha-disable/koha-enable symmetric and fix koha-remove/koha-create koha-disable had gained "koha-sip --disable" and an unconditional "koha-plack --disable". On both backends those are not just systemd bookkeeping: on SysV the former deletes the sip.enabled flag and the latter comments the Plack includes out of the Apache vhost (which koha-disable then restarts Apache to apply). koha-enable was unchanged, so "koha-disable x && koha-enable x" left the instance on CGI with SIP off. Go back to stopping the daemons only (the new Plack and ES indexer stops are kept), and under systemd stop and disable the per-instance koha@.target so the instance stays down across reboots while the individual units keep their enablement. koha-enable re-enables and starts that target, so an enabled instance comes back exactly as it was. koha-remove nested every --disable inside "if is_*_running", so a unit that was enabled but stopped kept its symlink in koha@.target.wants/ after the instance was gone, which is the very thing the block below it says it prevents. Disable unconditionally. koha-create aborted after the database and vhost existed if a systemd-only --enable failed, because the helpers run under set -e; guard them like the systemctl calls already were. It also only started koha.target, which is a no-op when the target is already active, so the new instance's units were not started; start koha@.target itself. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 0bdb94bfbb204b95d75d759a5104fdd507d889c4 Author: Martin Renvoize Date: Tue Sep 15 17:38:29 2026 +0100 Bug 40901: (QA follow-up) Harden the service abstraction in koha-functions.sh - koha_init_backend relied on "systemctl list-unit-files" exiting non-zero when nothing matches. That behaviour is version dependent, and on a systemd host running koha-sysv a false positive would send every koha-* helper to units that do not exist. Test for the koha-plack@.service unit file instead: unambiguous, and no systemctl fork per call. The _KOHA_INIT_BACKEND cache is dropped; every caller ran the function in a $(...) subshell so it never took effect anyway. - The _sysv_worker and _sysv_z3950 code moved here from bash-only scripts brought "((error_count++))" and "[[ ! $VAR ]]" into a #!/bin/sh library that koha-disable, koha-enable, koha-remove, koha-list and the init script source under dash. Use POSIX equivalents. - _sysv_restart_zebra and _sysv_restart_sip lost the explicit "return 0" of the original quiet not-running branch, so a start failure now aborted the caller's whole instance loop under set -e (including the init script's restart across all instances). Restore it. - systemctl status is run with --no-pager from koha_service_ctl and koha-systemd-ctl so the helpers never block on a pager. - koha-plack --enable/--disable ran systemctl before editing the Apache configuration, so a systemctl failure under set -e left the vhost untouched. Do the systemd half after the Apache edits and warn instead of aborting. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 606fc6bcd6a589738a3c584e98fb410f56cdbde9 Author: Martin Renvoize Date: Tue Sep 15 17:35:25 2026 +0100 Bug 40901: (QA follow-up) Fix the koha-systemd migration, upgrade and removal paths First install (migration from the SysV layout): - The postinst ran daemon-reload before stopping koha-common.service, so the name already resolved to the new native unit and the stop was a no-op; the daemon(1)-supervised starman/zebrasrv/workers survived and kept plack.sock and the Zebra sockets. The init script itself is gone by then (koha-common's rm_conffile) and the koha-* helpers already dispatch to systemd, so neither can be used either. Kill the legacy koha-common.service cgroup (the generated unit has KillMode=process, a plain stop leaves the supervisors alive), sweep remaining pid files, and wait for them to exit, all before daemon-reload. - Nothing ever started the new units, and koha-common.service was never enabled, so a migrated host came back with every instance stopped and "systemctl restart koha-common" doing nothing. Enable both koha.target and koha-common.service and start them through deb-systemd-invoke. - koha-plack@ was enabled for every instance including CGI-only ones, and koha-indexer@ regardless of USE_INDEXER_DAEMON. Use "koha-list --enabled --plack" and /etc/default/koha-common like the init script did. - Drop the "systemctl disable koha-common.service" that targeted our own compat unit. Upgrade: deb-systemd-invoke takes unit names, not glob patterns, so enumerate the active koha-*@*.service units before try-restarting them. Removal: the prerm only did a daemon-reload, leaving every daemon running with its unit file deleted, so swapping to koha-sysv started a second copy of each. Stop koha-common.service and koha.target on remove; a new postrm removes the enablement symlinks on purge and reloads the manager. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 1a81cf9bc7d555a0c8061997b3898b5ab81bdd35 Author: Martin Renvoize Date: Tue Sep 15 17:34:02 2026 +0100 Bug 40901: (QA follow-up) Polish the per-component units - Order services Before= their instance target instead of After=. With After=, koha@.target (and therefore koha.target) reported active before a single daemon had started, so its state meant nothing to anything ordered after Koha. koha@.target is likewise Before=koha.target. - Add SyslogIdentifier=%i-koha- to every unit so journal lines can be filtered per instance and component (the template this series removed already did this) and Documentation= pointing at each helper's man page. - koha-plack@: stop with SIGQUIT and a 30s timeout. Starman treats QUIT as graceful shutdown and TERM as immediate; the SysV path used QUIT/30/KILL/5, so the default SIGTERM cut in-flight requests on every restart. Drop the redundant --user/--group starman flags, User=/Group= already apply. - koha-worker@, koha-worker-long_tasks@: KillMode=mixed with a 120s timeout so the parent is signalled but a running forked job gets a chance to finish before it is killed. - koha-zebra@: remove every *socket file in the run directory rather than the two default names, so sites with customised entries get the same stale-socket protection. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 5562b26bb023b4f37dbef806cc34038ebcb65bef Author: Martin Renvoize Date: Tue Sep 15 17:32:52 2026 +0100 Bug 40901: (QA follow-up) Fix Z39.50 option expansion and honour the indexer defaults Two problems in how the units consume koha-generate-env's output: 1. koha-z3950@.service passed ${Z3950_ADDITIONAL_OPTS}. In systemd unit files "${VAR}" always expands to exactly one argument, and to a single empty argument when the variable is empty; only unbraced "$VAR" is word-split. So a configured value like "--add-item-status k -t 5" reached z3950_responder.pl as one unknown option, and the default empty value reached it as a "" listener address. Verified with systemd-run: ${X}="--a --b" gives ["--a --b"], ${Y}="" gives [""], $X gives ["--a"] ["--b"]. Use the unbraced form. 2. koha-indexer@.service hardcoded rebuild_zebra.pl -daemon -sleep, ignoring ALTERNATE_INDEXER_DAEMON and INDEXER_PARAMS from /etc/default/koha-common that the SysV path honours. koha-generate-env now resolves INDEXER_DAEMON and INDEXER_PARAMS exactly like koha-indexer and the unit runs them through /usr/bin/env, so any configured indexer works. koha-generate-env is also tightened up: numeric values are validated and fall back to their defaults, every value is forced onto one line without quote characters so the EnvironmentFile always parses, the debug_mode test matches is_debug_mode, Z3950_ADDITIONAL_OPTS is read from /etc/default/koha-common first (as before this patchset), the Z39.50 config dir uses the same config.xml test as is_z3950_enabled, the env dir is created with the instance's ownership if it does not exist yet, and unknown services get an empty env file instead of a chmod failure. The responder is invoked through /usr/bin/perl like the other Perl daemons. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 3fb9421561bcc03cf99a13e2a266de2b8ac383f5 Author: Martin Renvoize Date: Tue Sep 15 17:32:34 2026 +0100 Bug 40901: (QA follow-up) Make koha-common.service a dependency-free shim The compat unit declared BindsTo=koha.target and After=koha.target while its ExecStop ran "systemctl stop koha.target". After= orders this unit's stop before the target's, so the ExecStop waited on a job that could not run until the ExecStop itself finished. Every "systemctl stop koha-common" and every "systemctl restart koha-common" hung for TimeoutStopSec (90s by default) and left the unit in failed state. An async --no-block stop does not help either: the queued target stop replaces the start job the restart transaction needs, and everything ends up stopped. Drop the dependencies. The unit is now a plain oneshot with RemainAfterExit that starts koha.target in ExecStart and stops it in ExecStop, which is all the old "service koha-common" UX requires. Verified with a replica set of user units: start, stop and restart of the shim each complete in well under a second, restart gives every service a new PID, and managing koha.target directly is unaffected. The one cosmetic consequence is that the shim stays "active (exited)" if koha.target is stopped behind its back; README.Debian says to look at koha.target for real status. Also sets SyslogIdentifier so its journal lines are identifiable. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 529df17b4291ea99a91aae34b4e8ff0cc98311ae Author: Martin Renvoize Date: Tue Sep 15 17:30:32 2026 +0100 Bug 40901: (QA follow-up) Install units to /lib/systemd/system and tidy control The old koha-common package shipped /lib/systemd/system/koha-common.service while koha-systemd installs to /usr/lib/systemd/system. On a merged-/usr Debian 12 host these are the same file through the /lib symlink, but dpkg compares path strings, so Replaces does not apply and removing the old package's copy can delete the file koha-systemd just unpacked (the DEP-17 aliasing problem). Keep the units under lib/systemd/system, the path the previous package used, so this is an ordinary takeover. While here: - koha-core gains Recommends: koha-systemd | koha-sysv. Installing it alone previously gave helpers that fell back to the daemon(1) wrapper that no longer gets installed. - drop a stray comment from the Source stanza of control.in - stop appending Perl dependencies to koha-common.substvars: the metapackage no longer uses ${koha:Depends} Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 2992ea058b32aec096d4159a186dbd44dc1bad0c Author: Martin Renvoize Date: Tue Sep 15 17:30:20 2026 +0100 Bug 40901: (QA follow-up) Migrate debconf answers from koha-common to koha-core The debconf questions moved from the koha-common/* to the koha-core/* namespace, but nothing carried the existing answers across, so a site that had opted out of automatic translation updates was asked again and defaulted back to "yes". koha-core.config now seeds the new question, including its "seen" flag, from the old one when it exists. debian/koha-common.templates was left behind after koha-common.config was deleted; the metapackage registered three questions that no config script ever asks, so remove it. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 67d9b903d66c929e2f04be8a22610ecab20504bf Author: Martin Renvoize Date: Tue Sep 15 17:30:13 2026 +0100 Bug 40901: (QA follow-up) Restore the RabbitMQ and memcached steps in koha-common.postinst The monolithic koha-common.postinst ended by enabling the rabbitmq_stomp plugin, restarting RabbitMQ and restarting memcached (Bug 35242). None of that survived the split: koha-core.postinst never had it and the new koha-common.postinst was an empty stub. A fresh "apt install koha-common" therefore left the STOMP plugin disabled, and upgrades stopped flushing memcached, which is exactly the stale-cache class of bug 35242 fixed. koha-common is the package that depends on rabbitmq-server and memcached, so the steps belong in its postinst. The copy in koha-full.postinst is dropped now that koha-full depends on koha-common. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 9485938333efb0e19ae8654d4654b4f92b30be0a Author: Martin Renvoize Date: Tue Sep 15 17:29:28 2026 +0100 Bug 40901: (QA follow-up) Keep /etc conffiles under their koha-common names The cron, logrotate and defaults files were renamed from koha-common.* to koha-core.*, so debhelper installed them as /etc/cron.d/koha-core, /etc/logrotate.d/koha-core and /etc/default/koha-core. dpkg never deletes a conffile that a package stops shipping, so after an upgrade both the old and the new files exist side by side: - every cron job (process_message_queue.pl every 15 minutes, overdue_notices.pl, fines.pl, backups...) runs twice - logrotate complains about duplicate log entries every night - the admin's /etc/default/koha-common (USE_INDEXER_DAEMON, INDEXER_PARAMS, ES_* tuning) is replaced by a symlink to a fresh copy of the packaged defaults, silently losing every customisation Keep the on-disk names instead: koha-core now ships the files as debian/koha-core.koha-common.* and debian/rules passes --name=koha-common to dh_installinit, dh_installcron and dh_installlogrotate. Because koha-core Replaces the old koha-common, dpkg hands each conffile over with the admin's content intact and no duplicates are left behind. The defaults file content is byte-identical to the old koha-common.default so nobody gets a spurious conffile prompt, and koha-generate-env reads the single canonical name again. The experimental pre-split koha-core (used by koha-full) had its own /etc/default/koha-core and /etc/init.d/koha-core; a new koha-core.maintscript migrates the former with mv_conffile and removes the latter, with the matching rc.d links dropped in the postinst. /etc/koha/koha-worker@.service, whose template was deleted, is removed from upgrading systems too. koha-core.preinst gains the #DEBHELPER# token the maintscript machinery needs. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 760641c550836f22fa7d2d334eb4a8013579540b Author: Martin Renvoize Date: Tue Sep 15 17:28:42 2026 +0100 Bug 40901: (QA follow-up) Install the SysV init script through dh_installinit debhelper treats debian/.init as that package's init script, so koha-sysv would have shipped /etc/init.d/koha-sysv (with generated update-rc.d and invoke-rc.d handling) as well as the .install-copied /etc/init.d/koha-common (with none). That meant two identical init scripts, two generated units on systemd hosts, and on a fresh non-systemd install the "update-rc.d koha-common disable" call in the postinst aborting with "no runlevel symlinks to modify" because nothing had ever created them. Rename the script to debian/koha-sysv.koha-common.init and run dh_installinit --name=koha-common for koha-sysv. debhelper now installs it as /etc/init.d/koha-common, registers the rc.d links, starts it on install, restarts it on upgrade, stops it on removal and removes the links on purge, which is exactly what the old koha-common package did. The hand-written prerm/postrm and the Bug 18250 disable/enable dance (a one-off link reshuffle from 2017 that is a no-op under dependency-based boot) are dropped in favour of the generated code. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit b8b064bf404fbd10c4a84d1d861232005590bd63 Author: Martin Renvoize Date: Tue Sep 15 17:27:57 2026 +0100 Bug 40901: (QA follow-up) Fix Replaces/Breaks version bound for the package split The split lands in the 26.06 development cycle (Koha.pm is at 26.06.00.xxx), but the Replaces/Breaks on the old monolithic koha-common were written as (<< 25.11). Released 25.11.x and 26.05.x koha-common packages therefore don't match, and upgrading from them aborts with dpkg's "trying to overwrite '/usr/sbin/koha-list', which is also in package koha-common". Use (<< 26.06~) everywhere, including the rm_conffile version in koha-common.maintscript, so every pre-split release is covered. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 699a7a3c0cb8c1979af1bb5857706ad41e4ec619 Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Use /run instead of legacy /var/run in unit files systemd 257 warns that these units point PIDFile at the legacy /var/run directory. This patch updates the remaining /var/run/koha paths to /run/koha to match the EnvironmentFile lines; /var/run is a symlink to /run on Debian, so nothing changes at runtime. Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 29e31a22573f867966bd7c2d8f441e9007f88a0a Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Make the systemd unit validation test run in KTD The test failed in KTD instead of skipping, and the systemd-analyze option it relied on doesn't exist in any systemd QA will meet, so the unit files were never actually checked anywhere. Load Test::NoWarnings after the plan, use the SYSTEMD_UNIT_PATH environment variable instead, and ignore noise from helper scripts the packages haven't installed yet. Test Plan: 1) Apply this patch 2) prove t/00-valid-systemd-units.t 3) Note all units are now verified instead of the file failing! Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 7169ea9e2d749ecbc7e63f78d4180df9b0951e9a Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Move ES indexer re-discovery into koha-systemd-ctl koha-common.service had an unexplained one-line shell loop in ExecStartPre. It's now a documented sync-es-indexers command in koha-systemd-ctl that does the same thing: notice instances that switched to Elasticsearch and start their indexers, like the SysV init script did on every start. https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c132 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit a46f1aa4ca27f0765338f610d5ced782aa2b1282 Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Don't stop all Koha services on every koha-systemd upgrade The postinst stopped and disabled koha-common.service on every configure, which after migration means every upgrade of this package shuts down every Koha instance. Now it only happens on first install, where it exists to stop the old SysV-started services. The hand-rolled deletion of the old init script is replaced with dpkg's standard rm_conffile mechanism. https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c130 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 67ec61a60fe489c3d66af6b4d0db31e08ca9fb3f Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Add Replaces/Breaks on old koha-common to koha-systemd The old koha-common package shipped the same koha-common.service file that koha-systemd now ships. Declaring Replaces/Breaks lets upgrades hand the file over cleanly, matching what koha-sysv and koha-core already declare. https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c129 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit de946eac7fa4279bb6a3614997c9164e9af28863 Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Remove redundant unit files ( koha-sysv.service, koha-worker@.service ) Nothing ever used either of these files. On systemd hosts koha-sysv already gets a generated koha-common.service, so shipping its own unit meant two units driving the same init script. The koha-worker@.service sample in /etc/koha was a copy-by-hand template that koha-systemd's real worker unit replaces. https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c129 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 653d6092b5bf2920a6056546fbe07c3e7be9d0ab Author: Kyle M Hall Date: Fri Jul 24 12:12:04 2026 -0400 Bug 40901: (QA follow-up) Make koha-full depend on koha-common so it gets an init system After the package split, installing koha-full got you Koha with no way to start its services. Depending on koha-common brings in an init system and the other services, leaving only the database server to add. https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=40901#c128 Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 71f9a686795303efb8cf08b3a12d544dd4e04a50 Author: Martin Renvoize Date: Fri May 22 10:46:10 2026 +0100 Bug 40901: (QA follow-up) Fix systemd unit test for Test::NoWarnings and older systemd Add Test::NoWarnings and probe for --unit-path support, skipping on systemd versions that don't recognise the flag (e.g. KTD's Debian image). Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 445b955b787807ff7aaa26533bd929b02bfcd82d Author: Martin Renvoize Date: Fri May 22 09:23:02 2026 +0100 Bug 40901: (QA follow-up) Fix koha-disable --disable idempotency under set -e koha-plack, koha-zebra, koha-indexer and koha-es-indexer all return exit code 1 when asked to disable a service that is already disabled. koha-disable has set -e, so this propagated as a fatal error when koha-create called koha-disable on a freshly-created instance (where none of the per-service configs are enabled yet). Apply the same || true guard that koha-worker already had to the four unconditional --disable calls. The desired postcondition (service disabled) is achieved either way; the exit code from the sub-script is not meaningful here. Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 6b54ee976b2703ee1e15b24414a6b62c7c21b90c Author: Martin Renvoize Date: Fri May 22 07:53:54 2026 +0100 Bug 40901: Add systemd unit file validation test Adds t/00-valid-systemd-units.t which runs systemd-analyze verify against every unit file in debian/systemd/. Skips gracefully if systemd-analyze is not installed (requires the systemd package). To enable this test in KTD-based CI, add 'systemd' to the KTD container's package list. systemd-analyze verify does not require systemd to be running as PID 1 — it is pure static analysis and works correctly in a standard Docker container. Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim commit 3eb65c1041a3dcef3b7aaf53d3670c55d1a1e1f1 Author: Martin Renvoize Date: Fri May 22 07:29:55 2026 +0100 Bug 40901: (QA follow-up) Drop redundant ExecStart systemctl call in koha-common.service koha-common.service already declares BindsTo=koha.target and After=koha.target. BindsTo= implies Requires=, so systemd will start koha.target automatically when this compat unit starts and will wait for it to be active before proceeding. The explicit ExecStart=/bin/systemctl start koha.target was therefore redundant, and calling systemctl from within a service body is an anti-pattern (it makes an extra D-Bus round-trip back to PID 1 and can deadlock in edge cases during early boot). Replace with /bin/true so the oneshot succeeds immediately after the ExecStartPre ES-indexer discovery step. ExecStop is kept as-is because there is no declarative equivalent for "stopping this unit should stop koha.target" — BindsTo= only propagates stops in the other direction. Signed-off-by: Martin Renvoize Signed-off-by: Pedro Amorim