From af78ce168124439bf6f4fb2ced9f8683c5d0c7c0 Mon Sep 17 00:00:00 2001 From: librelad Date: Tue, 18 Aug 2026 23:03:23 +0100 Subject: [PATCH] stalwart: make the mode switch finish the job itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switching between private and public wrote the setting, reconfigured the server and then asked the user to run `libreportal app install stalwart` to make the ports actually change. That left a window where the WebUI reported public while port 25 was still closed — or worse, reported private while 25 was still open and listening. A mode switch that does not move the ports is not a mode switch. The tool now runs the install itself. Safe from here: tools are dispatched inline rather than as their own task, so this is not a nested task and cannot deadlock on the task lock, and nothing in Stalwart's install hooks calls back into the tool. Provisioning inside that install is a no-op because it skips once config.json exists. Dropped the separate firewall rebuild — the install reallocates the ports and rebuilds the rules from the result, so doing it beforehand only worked from the old allocation and was then immediately redone. Verified both directions on a real install: private -> public publishes 25, public -> private removes it, the admin port keeps its existing random allocation across both (no --reset-network, so bookmarked WebUI links do not move), mailboxes survive with their original creation timestamps, and re-selecting the current mode is a no-op. Co-Authored-By: Claude Opus 5 --- .../stalwart/tools/stalwart_set_mode.sh | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/containers/stalwart/tools/stalwart_set_mode.sh b/containers/stalwart/tools/stalwart_set_mode.sh index 2b59242..72421f4 100644 --- a/containers/stalwart/tools/stalwart_set_mode.sh +++ b/containers/stalwart/tools/stalwart_set_mode.sh @@ -102,20 +102,41 @@ appStalwartSetMode() { fi fi - # ---- 3. The firewall -------------------------------------------------- - if declare -F firewallRebuildFromDatabase >/dev/null 2>&1; then - firewallRebuildFromDatabase >/dev/null 2>&1 \ - && isSuccessful "Firewall rules rebuilt." + # ---- 3. Make the port change real -------------------------------------- + # No separate firewall rebuild here: the install below reallocates ports and + # rebuilds the rules from the result. Rebuilding first would only work from + # the old allocation and then be immediately redone. + # + # Port publishing lives in the compose file, which is only written from the + # app config during an install — so without this the setting would be saved, + # the WebUI would show the new mode, and the container would carry on + # publishing exactly the ports it did before. A mode switch that leaves port + # 25 open is worse than no mode switch at all. + # + # Safe to call from here: tools are dispatched inline rather than as their + # own task, so this is not a nested task and cannot deadlock on the task + # lock. It also cannot recurse — the install path calls Stalwart's install + # hooks, and none of them call back into this tool. Provisioning inside that + # install is a no-op too, since it skips once config.json exists. + # + # No --reset-network: the admin port keeps its existing random allocation, + # so the WebUI link people have bookmarked does not move underneath them. + echo "" + isNotice "Applying the port changes (reinstalling the app — mail and mailboxes are kept)…" + + if ! declare -F dockerInstallApp >/dev/null 2>&1; then + isError "Cannot reinstall automatically from here." + isNotice " Run it yourself to finish the switch: libreportal app install stalwart" + return 1 fi - # ---- 4. What is left --------------------------------------------------- - # Port publishing lives in the compose file, which is written from the app - # config at install time. Saying this plainly beats letting someone believe - # port 25 closed when it is still bound. - echo "" - isNotice "One step left: reinstall the app so the port changes reach the container." - isNotice " libreportal app install stalwart" - isNotice " Your mail, mailboxes and DKIM keys are untouched by this." + if dockerInstallApp "stalwart" "" "false"; then + isSuccessful "Stalwart is now running in '$mode' mode." + else + isError "The reinstall did not complete — the setting is saved, but the ports" + isNotice " have not changed yet. Retry with: libreportal app install stalwart" + return 1 + fi if [[ "$mode" == "public" ]]; then echo ""