diff --git a/docker/update.sh b/docker/update.sh index 74e036574..570192d51 100644 --- a/docker/update.sh +++ b/docker/update.sh @@ -163,6 +163,11 @@ function cleanup_previous_payload() { rm -rf "${UPDATE_PREVIOUS_APP}" "${UPDATE_PREVIOUS_PUBLIC}" } +function finalize_update_transaction() { + cleanup_previous_payload || return 1 + clear_update_pending +} + function restore_previous_payload() { local failed="false" @@ -197,8 +202,10 @@ function rollback_update_transaction() { UPDATE_RECOVERY_REQUIRED="true" return 1 fi - clear_update_pending - cleanup_previous_payload + if ! finalize_update_transaction; then + UPDATE_RECOVERY_REQUIRED="true" + return 1 + fi return 0 } @@ -209,8 +216,10 @@ function recover_pending_update() { if [ "${state}" = "committed" ]; then INFO "→ 清理已完成的容器更新事务" - cleanup_previous_payload - clear_update_pending + if ! finalize_update_transaction; then + WARN "→ 已完成更新的旧代际备份清理失败,保留事务标记以便下次启动重试" + return 1 + fi return 0 fi @@ -410,8 +419,9 @@ function install_backend_and_download_resources() { return 1 fi - clear_update_pending - cleanup_previous_payload || WARN "更新完成,但旧程序备份清理失败" + if ! finalize_update_transaction; then + WARN "更新完成,但旧程序备份清理失败,保留事务标记以便下次启动重试" + fi rm -rf "${TMP_PATH}" MOVIEPILOT_UPDATE_RESULT="updated" INFO "程序更新成功,前端版本:${frontend_version},后端版本:${1}" diff --git a/tests/test_docker_bootstrap.py b/tests/test_docker_bootstrap.py index 14b593161..19ceb7fcd 100644 --- a/tests/test_docker_bootstrap.py +++ b/tests/test_docker_bootstrap.py @@ -1068,6 +1068,47 @@ def test_pending_update_recovers_previous_payload_on_next_start(tmp_path: Path) ) +def test_update_transaction_keeps_marker_when_backup_cleanup_fails(tmp_path: Path) -> None: + config_dir = tmp_path / "config" + pending_file = config_dir / "temp" / "__update_pending__" + log_file = tmp_path / "cleanup.log" + script = textwrap.dedent( + f"""\ + CONFIG_DIR="$1" + UPDATE_PENDING_FILE="$2" + UPDATE_PREVIOUS_APP="$3" + UPDATE_PREVIOUS_PUBLIC="$4" + source {UPDATER!s} + cleanup_previous_payload() {{ + if [[ -f "${{UPDATE_PENDING_FILE}}" ]]; then + printf 'marker-present\n' > {shlex.quote(str(log_file))} + fi + return 1 + }} + set_update_pending committed + finalize_update_transaction || true + printf '%s\n' "$([[ -f "${{UPDATE_PENDING_FILE}}" ]] && printf present || printf missing)" + """ + ) + result = subprocess.run( + [ + "/bin/bash", + "-c", + script, + "transaction-finalize-test", + str(config_dir), + str(tmp_path / "previous-app"), + str(tmp_path / "previous-public"), + ], + text=True, + capture_output=True, + check=True, + ) + + assert result.stdout == "present\n" + assert log_file.read_text(encoding="utf-8") == "marker-present\n" + + def test_restore_does_not_nest_previous_app_when_current_removal_fails(tmp_path: Path) -> None: live_app = tmp_path / "app" live_public = tmp_path / "public"