fix(docker): avoid starting update worker on regular restart

This commit is contained in:
jxxghp
2026-09-17 17:37:38 +08:00
parent 1cea3bebf4
commit c2a232dec7
5 changed files with 51 additions and 6 deletions

View File

@@ -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,

View File

@@ -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 程序目录..."

View File

@@ -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 的控制面调用和所有入口重入都使用 `/`

View File

@@ -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")

View File

@@ -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):