diff --git a/app/runtime/state.py b/app/runtime/state.py index 1112f5588..3a9f907b8 100644 --- a/app/runtime/state.py +++ b/app/runtime/state.py @@ -178,8 +178,10 @@ class SystemHelper(ConfigReloadMixin): @staticmethod def _schedule_supervisor_restart() -> None: - """延迟调用本地 supervisor,确保重启接口有机会完成响应。""" - SystemHelper._schedule_supervisor_command("restart", "all") + """延迟只重启 Nginx 和后端,避免按需更新 worker 被普通重启拉起。""" + SystemHelper._schedule_supervisor_command( + "restart", ("moviepilot-nginx", "moviepilot-backend") + ) @staticmethod def _schedule_supervisor_shutdown() -> None: @@ -187,7 +189,9 @@ class SystemHelper(ConfigReloadMixin): SystemHelper._schedule_supervisor_command("shutdown") @staticmethod - def _schedule_supervisor_command(action: str, target: Optional[str] = None) -> None: + def _schedule_supervisor_command( + action: str, target: Optional[str | tuple[str, ...]] = None + ) -> None: """延迟调用本地 supervisor 控制命令,确保重启接口有机会完成响应。""" def run_command() -> None: command = [ @@ -197,7 +201,10 @@ class SystemHelper(ConfigReloadMixin): action, ] if target is not None: - command.append(target) + if isinstance(target, str): + command.append(target) + else: + command.extend(target) try: subprocess.Popen( command, diff --git a/docker/update-worker.sh b/docker/update-worker.sh index b9c2e9143..3ce3621a8 100644 --- a/docker/update-worker.sh +++ b/docker/update-worker.sh @@ -8,6 +8,7 @@ CONFIG_DIR="${CONFIG_DIR:-/config}" export VENV_PATH CONFIG_DIR SUPERVISOR_CONFIG="/etc/supervisor/supervisord.conf" RESTART_REQUEST_FILE="${CONFIG_DIR}/temp/moviepilot.pending_supervisor_restart" +INSTALL_MANIFEST="${CONFIG_DIR}/temp/moviepilot-update/install.json" function INFO() { echo "[INFO] ${1}" @@ -17,6 +18,11 @@ function ERROR() { echo "[ERROR] ${1}" >&2 } +if [ ! -f "${INSTALL_MANIFEST}" ]; then + INFO "→ 未检测到待安装的更新清单,跳过 Docker 更新 worker。" + exit 0 +fi + cd /app || exit 1 INFO "→ 开始将已下载的更新包替换到 Docker 程序目录..." diff --git a/docs/docker-startup.md b/docs/docker-startup.md index 83867cbe5..5611066d9 100644 --- a/docs/docker-startup.md +++ b/docs/docker-startup.md @@ -422,7 +422,8 @@ Python lifespan 会先撤销 readiness,再按组件声明的 `stop_order` 停 ### 9.2 应用内重启 -普通应用重启通过本地 `supervisorctl restart all` 同时重启 Nginx 和后端。确认安装 Release 时, +普通应用重启通过本地 `supervisorctl restart moviepilot-nginx moviepilot-backend` 同时重启 Nginx 和后端, +避免把按需运行的更新 worker 拉起。确认安装 Release 时, `SystemHelper` 只启动 root `moviepilot-update-worker`;worker 先替换 `/app`、`/public` 或站点资源, 再写入 `moviepilot.pending_supervisor_restart` 并执行 `supervisorctl shutdown`。外层 entrypoint 看到标记后 重新执行 launcher,加载新代码。载荷替换期间外层 entrypoint、worker 的控制面调用和所有入口重入都使用 `/` diff --git a/tests/test_docker_bootstrap.py b/tests/test_docker_bootstrap.py index b8510c63f..bb41c65a5 100644 --- a/tests/test_docker_bootstrap.py +++ b/tests/test_docker_bootstrap.py @@ -10,6 +10,7 @@ import pytest ROOT = Path(__file__).resolve().parents[1] LAUNCHER = ROOT / "docker" / "launcher.sh" UPDATER = ROOT / "docker" / "update.sh" +UPDATE_WORKER = ROOT / "docker" / "update-worker.sh" BASE_CONTROL_FILES = ("entrypoint.sh", "update.sh", "browser.sh", "cert.sh") @@ -942,6 +943,32 @@ def test_release_update_worker_applies_before_supervisor_shutdown() -> None: assert worker.index("apply-prepared-update") < worker.index("shutdown") +def test_release_update_worker_skips_without_install_manifest(tmp_path: Path) -> None: + """普通 supervisor 重启误启动 worker 时,无安装清单应安全跳过。""" + python_bin = tmp_path / "venv" / "bin" / "python3" + python_bin.parent.mkdir(parents=True) + python_bin.write_text("#!/bin/bash\nexit 99\n", encoding="utf-8") + python_bin.chmod(0o755) + + script = textwrap.dedent( + f"""\ + CONFIG_DIR={shlex.quote(str(tmp_path / "config"))} + VENV_PATH={shlex.quote(str(tmp_path / "venv"))} + source {shlex.quote(str(UPDATE_WORKER))} + """ + ) + + result = subprocess.run( + ["bash", "-c", script, "update-worker-noop"], + text=True, + capture_output=True, + check=False, + ) + + assert result.returncode == 0 + assert "未检测到待安装的更新清单" in result.stdout + + def test_update_reexec_uses_stable_working_directory() -> None: """更新替换 /app 后,入口重入和 worker 控制面调用必须离开旧工作目录。""" entrypoint = (ROOT / "docker" / "entrypoint.sh").read_text(encoding="utf-8") diff --git a/tests/test_system_utils.py b/tests/test_system_utils.py index d60613005..80ede6254 100644 --- a/tests/test_system_utils.py +++ b/tests/test_system_utils.py @@ -154,7 +154,11 @@ def test_supervisor_restart_command_restarts_frontend_and_backend(monkeypatch): SystemHelper._schedule_supervisor_restart() - assert popen_mock.call_args.args[0][-2:] == ["restart", "all"] + assert popen_mock.call_args.args[0][-3:] == [ + "restart", + "moviepilot-nginx", + "moviepilot-backend", + ] def test_supervisor_shutdown_command(monkeypatch):