From ff434994fde29d3f5422fa1604753beae796e5cd Mon Sep 17 00:00:00 2001 From: merge-check <140916359+agentHits@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:46:14 -0400 Subject: [PATCH] fix: restore dokploy-server schedule runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Что: - В runCommand возвращена ветка `else if (scheduleType === "dokploy-server")`, как в upstream. Её код остался в файле, но после слияния upstream 5a1d8eac3 (2026-10-01) оказался внутри ветки application/compose. - Тесты: расписание dokploy-server запускает ./script.sh из своей папки и получает статус error, если скрипт падает; расписание приложения выполняет только команду в контейнере. Зачем: - С 2026-10-01 расписания dokploy-server вообще не запускались, но получали статус done. Так на dev молча перестал работать ночной дамп баз. - Расписания application и compose после своей команды дополнительно запускали `bash -c ./script.sh` в папке расписания. Такого файла у них нет, поэтому успешный запуск помечался как error. Риски: - Поведение возвращается к upstream, других изменений нет. Проверки: - Команды и результаты: CI=true bunx vitest run --config __test__/vitest.config.ts __test__/schedule/schedule-command-boundary.test.ts - на старом коде 3 новых теста падают (скрипт не запускается; статус done вместо error; два вызова spawnAsync вместо одного), после правки проходят все 9; все тесты __test__/schedule и __test__/backups/volume-backup-restart.test.ts - 33 прошли. Полный прогон: 2073 прошли, 1 пропущен, 8 упали только в *.real.test.ts (нет сокета Docker Desktop). bun run typecheck - 4/4 без ошибок. bun run format-and-lint - 0 ошибок. - Ограничения: на сервере исправление ещё не проверялось; ночной дамп на dev один раз запущен вручную, свежие дампы есть. What: - runCommand gets back the `else if (scheduleType === "dokploy-server")` branch, as in upstream. Its code was still in the file, but after the upstream merge 5a1d8eac3 (2026-10-01) it ended up inside the application/compose branch. - Tests: a dokploy-server schedule runs ./script.sh from its own directory and gets the error status when the script fails; an application schedule runs only its container command. Why: - Since 2026-10-01 dokploy-server schedules did not run at all but still got the done status. That is how the nightly database dump on dev silently stopped. - Application and compose schedules also ran `bash -c ./script.sh` in the schedule directory after their own command. They have no such file, so a successful run was marked as error. Risks: - The behaviour goes back to upstream; nothing else changes. Checks: - Commands and results: CI=true bunx vitest run --config __test__/vitest.config.ts __test__/schedule/schedule-command-boundary.test.ts - on the old code the 3 new tests fail (the script does not run; done instead of error; two spawnAsync calls instead of one), after the fix all 9 pass; all tests in __test__/schedule and __test__/backups/volume-backup-restart.test.ts - 33 passed. Full run: 2073 passed, 1 skipped, 8 failed only in *.real.test.ts (no Docker Desktop socket). bun run typecheck - 4/4 clean. bun run format-and-lint - 0 errors. - Limitations: the fix has not been checked on the server yet; the nightly dump on dev was run once by hand, so fresh dumps exist. Co-Authored-By: Claude Opus 5.5 --- .../schedule-command-boundary.test.ts | 65 +++++++++++++++++++ packages/server/src/utils/schedules/utils.ts | 1 + 2 files changed, 66 insertions(+) diff --git a/apps/dokploy/__test__/schedule/schedule-command-boundary.test.ts b/apps/dokploy/__test__/schedule/schedule-command-boundary.test.ts index b7febba18f9..aa440051a39 100644 --- a/apps/dokploy/__test__/schedule/schedule-command-boundary.test.ts +++ b/apps/dokploy/__test__/schedule/schedule-command-boundary.test.ts @@ -69,6 +69,7 @@ describe("schedule command boundary", () => { }); mocks.execAsyncRemote.mockResolvedValue({ stdout: "", stderr: "" }); mocks.getServiceContainer.mockResolvedValue({ Id: "container-one" }); + mocks.spawnAsync.mockResolvedValue(undefined); }); it("quotes remote application docker exec command and log paths", async () => { @@ -119,6 +120,70 @@ describe("schedule command boundary", () => { expect(command).toContain("tee -a '/tmp/deployment log;id.log'"); }); + it("runs dokploy-server schedule scripts from the local schedule directory", async () => { + mocks.findScheduleById.mockResolvedValue({ + appName: "schedule-one", + command: "", + shellType: "bash", + scheduleType: "dokploy-server", + }); + + await expect(runCommand("schedule-1")).resolves.toMatchObject({ + deploymentId: "deployment-1", + status: "done", + }); + + expect(mocks.spawnAsync).toHaveBeenCalledTimes(1); + expect(mocks.spawnAsync).toHaveBeenCalledWith( + "bash", + ["-c", "./script.sh"], + expect.any(Function), + { cwd: "/local schedules/schedule-one" }, + ); + expect(mocks.execAsyncRemote).not.toHaveBeenCalled(); + }); + + it("marks dokploy-server schedule runs as failed when the script fails", async () => { + mocks.findScheduleById.mockResolvedValue({ + appName: "schedule-one", + command: "", + shellType: "bash", + scheduleType: "dokploy-server", + }); + mocks.spawnAsync.mockRejectedValue(new Error("exit code 1")); + + await expect(runCommand("schedule-1")).resolves.toMatchObject({ + deploymentId: "deployment-1", + status: "error", + }); + expect(mocks.updateDeploymentStatus).toHaveBeenCalledWith( + "deployment-1", + "error", + ); + }); + + it("runs only the container command for application schedules", async () => { + mocks.findScheduleById.mockResolvedValue({ + appName: "schedule-one", + application: { appName: "app-one", serverId: null }, + command: "echo done", + shellType: "sh", + scheduleType: "application", + }); + + await expect(runCommand("schedule-1")).resolves.toMatchObject({ + deploymentId: "deployment-1", + status: "done", + }); + + expect(mocks.spawnAsync).toHaveBeenCalledTimes(1); + expect(mocks.spawnAsync).toHaveBeenCalledWith( + "docker", + ["exec", "container-one", "sh", "-c", "echo done"], + expect.any(Function), + ); + }); + it("rejects unsafe stored schedule app names before remote script execution", async () => { mocks.findScheduleById.mockResolvedValue({ appName: "schedule;id", diff --git a/packages/server/src/utils/schedules/utils.ts b/packages/server/src/utils/schedules/utils.ts index c9531f68ad1..87cb4c9862d 100644 --- a/packages/server/src/utils/schedules/utils.ts +++ b/packages/server/src/utils/schedules/utils.ts @@ -166,6 +166,7 @@ export const runCommand = async (scheduleId: string) => { throw error; } } + } else if (scheduleType === "dokploy-server") { const writeStream = createWriteStream(deployment.logPath, { flags: "a" }); const { SCHEDULES_PATH } = paths(); const fullPath = getScheduleDirectory(SCHEDULES_PATH, appName);