diff --git a/docs/superpowers/plans/2026-07-05-native-port-takeover.md b/docs/superpowers/plans/2026-07-05-native-port-takeover.md index dfafad1..d1c5c3d 100644 --- a/docs/superpowers/plans/2026-07-05-native-port-takeover.md +++ b/docs/superpowers/plans/2026-07-05-native-port-takeover.md @@ -23,9 +23,11 @@ existing plugin helper library `mysql_common.sh`. - Match existing test conventions in `tests/`: plain bash scripts with `fail()`/custom asserts, fake command stubs installed via `PATH`/env-var injection, no external test framework. -- Every new/changed script keeps `set -Eeuo pipefail` (the `-E` flag matters here: it's - what makes the `ERR` trap fire inside functions, which the rollback design depends on) - and the existing `log()`/`die()` pattern. +- Every new/changed script keeps `set -Eeuo pipefail` and the existing `log()`/`die()` + pattern. The rollback design uses a `trap ... EXIT` (guarded by a `ROLLBACK_ARMED` flag), + not `trap ... ERR` — `die()` calls `exit` directly, and bash's `ERR` trap does not fire + on an explicit `exit`, only on a command's non-zero status propagating through normal + control flow. This was verified empirically during Task 4's implementation. - Runs only on AlmaLinux 8 or 9 — a stricter, separate check from `install_db.sh`'s own permissive `detect_os()`, refusing immediately (before touching anything) on CloudLinux, Rocky, CentOS, plain RHEL, or AlmaLinux outside 8/9. @@ -1057,7 +1059,10 @@ verify_da_connectivity_new_port() { log "Potwierdzono łączność DirectAdmina z natywną instancją na porcie $NEW_NATIVE_PORT." } +ROLLBACK_ARMED=0 + rollback_service_state() { + [ "$ROLLBACK_ARMED" -eq 1 ] || return 0 rollback_config_changes restore_custombuild_mysql_management if [ -n "$NATIVE_SERVICE_NAME" ]; then @@ -1086,7 +1091,16 @@ main() { preflight_new_port_free detect_native_service_name - trap 'rollback_service_state' ERR + # Deliberately an EXIT trap, not an ERR trap. die() (used by every failure + # path below) calls `exit` directly, and bash's ERR trap does not fire for + # a function that terminates via an explicit `exit` call - it only fires + # when a command's non-zero status propagates through normal control flow. + # An EXIT trap fires on *any* shell exit (explicit exit, errexit-triggered + # exit, or falling off the end of the script), which is what "any failure + # from this point on triggers rollback" actually requires. ROLLBACK_ARMED + # guards it so it is a no-op before this point and after a successful run. + ROLLBACK_ARMED=1 + trap 'rollback_service_state' EXIT disable_custombuild_mysql_management apply_config_mutations @@ -1095,7 +1109,8 @@ main() { restart_directadmin_service verify_da_connectivity_new_port - trap - ERR + ROLLBACK_ARMED=0 + trap - EXIT log "Przejęcie portu natywnej instancji MariaDB zakończone powodzeniem: $NATIVE_CURRENT_PORT -> $NEW_NATIVE_PORT." } ```