crnd 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/crnd +88 -0
- package/package.json +19 -43
- package/drizzle/0000_init.sql +0 -35
- package/drizzle/0001_add_runs.sql +0 -13
- package/drizzle/meta/_journal.json +0 -20
- package/src/cli/commands/createDeleteCommand.ts +0 -93
- package/src/cli/commands/createDoctorCommand.ts +0 -93
- package/src/cli/commands/createExportCommand.ts +0 -75
- package/src/cli/commands/createImportCommand.ts +0 -80
- package/src/cli/commands/createKillCommand.ts +0 -89
- package/src/cli/commands/createListCommand.ts +0 -67
- package/src/cli/commands/createLogsCommand.ts +0 -125
- package/src/cli/commands/createPauseCommand.ts +0 -78
- package/src/cli/commands/createResetCommand.ts +0 -78
- package/src/cli/commands/createResumeCommand.ts +0 -78
- package/src/cli/commands/createRootCommand.ts +0 -50
- package/src/cli/commands/createRunOnceCommand.ts +0 -77
- package/src/cli/commands/createRunsCommand.ts +0 -106
- package/src/cli/commands/createScheduleCommand.ts +0 -184
- package/src/cli/commands/createShowCommand.ts +0 -92
- package/src/cli/commands/createStatusCommand.ts +0 -145
- package/src/cli/commands/createStopCommand.ts +0 -89
- package/src/cli/commands/createUpdateCommand.ts +0 -13
- package/src/cli/commands/daemon/createDaemonCommand.ts +0 -24
- package/src/cli/commands/daemon/createDaemonInstallCommand.ts +0 -144
- package/src/cli/commands/daemon/createDaemonServeCommand.ts +0 -14
- package/src/cli/commands/daemon/createDaemonStartCommand.ts +0 -73
- package/src/cli/commands/daemon/createDaemonStatusCommand.ts +0 -5
- package/src/cli/commands/daemon/createDaemonStopCommand.ts +0 -59
- package/src/cli/commands/daemon/createDaemonUninstallCommand.ts +0 -99
- package/src/cli/commands/daemon/createLaunchdPlist.ts +0 -34
- package/src/cli/commands/daemon/createSystemdService.ts +0 -24
- package/src/cli/commands/daemon/escapeXml.ts +0 -8
- package/src/cli/commands/daemon/getDaemonServiceArgs.ts +0 -10
- package/src/cli/commands/daemon/quoteWindowsArg.ts +0 -4
- package/src/cli/commands/getCommandArgs.ts +0 -8
- package/src/cli/commands/parseEnvArgs.ts +0 -28
- package/src/cli/getDaemonSpawnArgs.ts +0 -9
- package/src/cli/main.ts +0 -8
- package/src/daemon/autostart/ensureAutostart.ts +0 -30
- package/src/daemon/autostart/getAutostartPath.ts +0 -29
- package/src/daemon/autostart/getDaemonInstallArgs.ts +0 -10
- package/src/daemon/createLogger.ts +0 -13
- package/src/daemon/createShutdownHandler.ts +0 -29
- package/src/daemon/jobs/createJobsFileSync.ts +0 -113
- package/src/daemon/jobs/deleteJobByName.ts +0 -18
- package/src/daemon/jobs/upsertJob.ts +0 -85
- package/src/daemon/main.ts +0 -64
- package/src/daemon/runner/createRunOutputFds.ts +0 -18
- package/src/daemon/runner/getRunStatus.ts +0 -14
- package/src/daemon/runner/recordSkippedRun.ts +0 -27
- package/src/daemon/runner/recoverRunningRuns.ts +0 -36
- package/src/daemon/runner/runJob.ts +0 -94
- package/src/daemon/scheduler/createScheduler.ts +0 -45
- package/src/daemon/scheduler/createSchedulerState.ts +0 -8
- package/src/daemon/scheduler/loadJobs.ts +0 -10
- package/src/daemon/scheduler/runJobWithTracking.ts +0 -48
- package/src/daemon/scheduler/scheduleJob.ts +0 -32
- package/src/daemon/scheduler/unscheduleJob.ts +0 -11
- package/src/daemon/scheduler/updateNextRunAt.ts +0 -14
- package/src/daemon/server/createApp.ts +0 -76
- package/src/daemon/server/createAuthMiddleware.ts +0 -16
- package/src/daemon/server/routes/registerExportRoute.ts +0 -21
- package/src/daemon/server/routes/registerHealthRoute.ts +0 -16
- package/src/daemon/server/routes/registerImportRoute.ts +0 -22
- package/src/daemon/server/routes/registerJobRunsRoute.ts +0 -46
- package/src/daemon/server/routes/registerJobsDeleteRoute.ts +0 -37
- package/src/daemon/server/routes/registerJobsGetRoute.ts +0 -29
- package/src/daemon/server/routes/registerJobsKillRoute.ts +0 -45
- package/src/daemon/server/routes/registerJobsListRoute.ts +0 -13
- package/src/daemon/server/routes/registerJobsPauseRoute.ts +0 -53
- package/src/daemon/server/routes/registerJobsResetRoute.ts +0 -54
- package/src/daemon/server/routes/registerJobsResumeRoute.ts +0 -53
- package/src/daemon/server/routes/registerJobsRunRoute.ts +0 -37
- package/src/daemon/server/routes/registerJobsStopRoute.ts +0 -45
- package/src/daemon/server/routes/registerJobsUpsertRoute.ts +0 -30
- package/src/daemon/server/routes/registerRunGetRoute.ts +0 -25
- package/src/daemon/server/routes/registerRunLogsRoute.ts +0 -32
- package/src/daemon/server/routes/registerShutdownRoute.ts +0 -9
- package/src/daemon/server/startServer.ts +0 -23
- package/src/db/getMigrationsDir.ts +0 -5
- package/src/db/migrateDatabase.ts +0 -21
- package/src/db/openDatabase.ts +0 -12
- package/src/db/schema/jobs.ts +0 -26
- package/src/db/schema/jobsSchemas.ts +0 -10
- package/src/db/schema/runs.ts +0 -23
- package/src/db/schema/runsSchemas.ts +0 -10
- package/src/db/schema.ts +0 -5
- package/src/shared/auth/createToken.ts +0 -5
- package/src/shared/events/appendEvent.ts +0 -16
- package/src/shared/jobs/createCommandSchema.ts +0 -5
- package/src/shared/jobs/createEnvSchema.ts +0 -5
- package/src/shared/jobs/createJobInputSchema.ts +0 -31
- package/src/shared/jobs/createTomlJobSchema.ts +0 -31
- package/src/shared/jobs/formatJobRow.ts +0 -14
- package/src/shared/jobs/isOverlapPolicy.ts +0 -5
- package/src/shared/jobs/parseCommand.ts +0 -6
- package/src/shared/jobs/parseEnv.ts +0 -10
- package/src/shared/jobs/parseJobsToml.ts +0 -36
- package/src/shared/jobs/readJobsToml.ts +0 -17
- package/src/shared/jobs/serializeCommand.ts +0 -6
- package/src/shared/jobs/serializeEnv.ts +0 -6
- package/src/shared/jobs/serializeJobsToml.ts +0 -45
- package/src/shared/jobs/writeJobsToml.ts +0 -12
- package/src/shared/paths/ensureDir.ts +0 -6
- package/src/shared/paths/getConfigDir.ts +0 -6
- package/src/shared/paths/getEventsPath.ts +0 -6
- package/src/shared/paths/getJobRunsDir.ts +0 -7
- package/src/shared/paths/getJobsTomlPath.ts +0 -6
- package/src/shared/paths/getPaths.ts +0 -16
- package/src/shared/paths/getRunOutputPaths.ts +0 -10
- package/src/shared/paths/getRunsDir.ts +0 -7
- package/src/shared/paths/getStateDir.ts +0 -8
- package/src/shared/rpc/createRpcClient.ts +0 -20
- package/src/shared/runs/formatRunRow.ts +0 -6
- package/src/shared/state/daemonStateSchema.ts +0 -13
- package/src/shared/state/getDaemonStatePath.ts +0 -6
- package/src/shared/state/readDaemonState.ts +0 -14
- package/src/shared/state/removeDaemonState.ts +0 -9
- package/src/shared/state/writeDaemonState.ts +0 -8
- package/src/shared/utils/isRecord.ts +0 -5
- package/src/shared/version.ts +0 -5
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
import createRpcClient from "../../../shared/rpc/createRpcClient";
|
|
3
|
-
import removeDaemonState from "../../../shared/state/removeDaemonState";
|
|
4
|
-
import getDaemonSpawnArgs from "../../getDaemonSpawnArgs";
|
|
5
|
-
|
|
6
|
-
export default function createDaemonStartCommand() {
|
|
7
|
-
return defineCommand({
|
|
8
|
-
meta: {
|
|
9
|
-
name: "start",
|
|
10
|
-
description: "Start the crnd daemon",
|
|
11
|
-
},
|
|
12
|
-
args: {
|
|
13
|
-
json: {
|
|
14
|
-
type: "boolean",
|
|
15
|
-
alias: "j",
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
async run({ args }) {
|
|
19
|
-
const existing = createRpcClient();
|
|
20
|
-
if (existing) {
|
|
21
|
-
try {
|
|
22
|
-
const res = await existing.health.$get();
|
|
23
|
-
if (res.ok) {
|
|
24
|
-
const payload = { status: "already_running" };
|
|
25
|
-
if (!process.stdout.isTTY || args.json) {
|
|
26
|
-
console.log(JSON.stringify(payload));
|
|
27
|
-
} else {
|
|
28
|
-
console.log("daemon: already running");
|
|
29
|
-
}
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
} catch {
|
|
33
|
-
removeDaemonState();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const proc = Bun.spawn(getDaemonSpawnArgs(), {
|
|
38
|
-
stdin: "ignore",
|
|
39
|
-
stdout: "ignore",
|
|
40
|
-
stderr: "ignore",
|
|
41
|
-
});
|
|
42
|
-
proc.unref();
|
|
43
|
-
|
|
44
|
-
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
45
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
46
|
-
const client = createRpcClient();
|
|
47
|
-
if (!client) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const res = await client.health.$get();
|
|
52
|
-
if (res.ok) {
|
|
53
|
-
const data = await res.json();
|
|
54
|
-
if (!process.stdout.isTTY || args.json) {
|
|
55
|
-
console.log(JSON.stringify({ status: "started", daemon: data }));
|
|
56
|
-
} else {
|
|
57
|
-
console.log("daemon: started");
|
|
58
|
-
}
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
} catch {}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const payload = { status: "start_timeout" };
|
|
65
|
-
if (!process.stdout.isTTY || args.json) {
|
|
66
|
-
console.log(JSON.stringify(payload));
|
|
67
|
-
} else {
|
|
68
|
-
console.log("daemon: start timeout");
|
|
69
|
-
}
|
|
70
|
-
process.exitCode = 3;
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "citty";
|
|
2
|
-
import createRpcClient from "../../../shared/rpc/createRpcClient";
|
|
3
|
-
|
|
4
|
-
export default function createDaemonStopCommand() {
|
|
5
|
-
return defineCommand({
|
|
6
|
-
meta: {
|
|
7
|
-
name: "stop",
|
|
8
|
-
description: "Stop the crnd daemon",
|
|
9
|
-
},
|
|
10
|
-
args: {
|
|
11
|
-
json: {
|
|
12
|
-
type: "boolean",
|
|
13
|
-
alias: "j",
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
async run({ args }) {
|
|
17
|
-
const client = createRpcClient();
|
|
18
|
-
if (!client) {
|
|
19
|
-
const payload = { status: "not_running" };
|
|
20
|
-
if (!process.stdout.isTTY || args.json) {
|
|
21
|
-
console.log(JSON.stringify(payload));
|
|
22
|
-
} else {
|
|
23
|
-
console.log("daemon: not running");
|
|
24
|
-
}
|
|
25
|
-
process.exitCode = 3;
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const res = await client.daemon.shutdown.$post();
|
|
31
|
-
if (!res.ok) {
|
|
32
|
-
const payload = { status: "stop_failed", code: res.status };
|
|
33
|
-
if (!process.stdout.isTTY || args.json) {
|
|
34
|
-
console.log(JSON.stringify(payload));
|
|
35
|
-
} else {
|
|
36
|
-
console.log(`daemon: stop failed (${res.status})`);
|
|
37
|
-
}
|
|
38
|
-
process.exitCode = 3;
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const payload = { status: "stopped" };
|
|
43
|
-
if (!process.stdout.isTTY || args.json) {
|
|
44
|
-
console.log(JSON.stringify(payload));
|
|
45
|
-
} else {
|
|
46
|
-
console.log("daemon: stopped");
|
|
47
|
-
}
|
|
48
|
-
} catch {
|
|
49
|
-
const payload = { status: "stop_failed" };
|
|
50
|
-
if (!process.stdout.isTTY || args.json) {
|
|
51
|
-
console.log(JSON.stringify(payload));
|
|
52
|
-
} else {
|
|
53
|
-
console.log("daemon: stop failed");
|
|
54
|
-
}
|
|
55
|
-
process.exitCode = 3;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { existsSync, unlinkSync } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { defineCommand } from "citty";
|
|
5
|
-
|
|
6
|
-
export default function createDaemonUninstallCommand() {
|
|
7
|
-
return defineCommand({
|
|
8
|
-
meta: {
|
|
9
|
-
name: "uninstall",
|
|
10
|
-
description: "Remove auto-start service",
|
|
11
|
-
},
|
|
12
|
-
args: {
|
|
13
|
-
json: {
|
|
14
|
-
type: "boolean",
|
|
15
|
-
alias: "j",
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
run({ args }) {
|
|
19
|
-
if (process.env.CRND_AUTOSTART_DRY_RUN === "1") {
|
|
20
|
-
const payload = { ok: true, dryRun: true };
|
|
21
|
-
if (!process.stdout.isTTY || args.json) {
|
|
22
|
-
console.log(JSON.stringify(payload));
|
|
23
|
-
} else {
|
|
24
|
-
console.log("daemon: uninstall dry run");
|
|
25
|
-
}
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const platform = process.platform;
|
|
30
|
-
|
|
31
|
-
if (platform === "darwin") {
|
|
32
|
-
const plistPath = path.join(
|
|
33
|
-
os.homedir(),
|
|
34
|
-
"Library",
|
|
35
|
-
"LaunchAgents",
|
|
36
|
-
"com.crnd.daemon.plist",
|
|
37
|
-
);
|
|
38
|
-
Bun.spawnSync(["launchctl", "unload", plistPath]);
|
|
39
|
-
if (existsSync(plistPath)) {
|
|
40
|
-
unlinkSync(plistPath);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!process.stdout.isTTY || args.json) {
|
|
44
|
-
console.log(JSON.stringify({ ok: true, path: plistPath }));
|
|
45
|
-
} else {
|
|
46
|
-
console.log(`daemon: uninstalled (${plistPath})`);
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (platform === "linux") {
|
|
52
|
-
const servicePath = path.join(
|
|
53
|
-
os.homedir(),
|
|
54
|
-
".config",
|
|
55
|
-
"systemd",
|
|
56
|
-
"user",
|
|
57
|
-
"crnd.service",
|
|
58
|
-
);
|
|
59
|
-
Bun.spawnSync([
|
|
60
|
-
"systemctl",
|
|
61
|
-
"--user",
|
|
62
|
-
"disable",
|
|
63
|
-
"--now",
|
|
64
|
-
"crnd.service",
|
|
65
|
-
]);
|
|
66
|
-
Bun.spawnSync(["systemctl", "--user", "daemon-reload"]);
|
|
67
|
-
if (existsSync(servicePath)) {
|
|
68
|
-
unlinkSync(servicePath);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!process.stdout.isTTY || args.json) {
|
|
72
|
-
console.log(JSON.stringify({ ok: true, path: servicePath }));
|
|
73
|
-
} else {
|
|
74
|
-
console.log(`daemon: uninstalled (${servicePath})`);
|
|
75
|
-
}
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (platform === "win32") {
|
|
80
|
-
const taskName = "crnd";
|
|
81
|
-
Bun.spawnSync(["schtasks", "/Delete", "/TN", taskName, "/F"]);
|
|
82
|
-
if (!process.stdout.isTTY || args.json) {
|
|
83
|
-
console.log(JSON.stringify({ ok: true, task: taskName }));
|
|
84
|
-
} else {
|
|
85
|
-
console.log(`daemon: uninstalled (${taskName})`);
|
|
86
|
-
}
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const payload = { ok: false, error: "unsupported_platform" };
|
|
91
|
-
if (!process.stdout.isTTY || args.json) {
|
|
92
|
-
console.log(JSON.stringify(payload));
|
|
93
|
-
} else {
|
|
94
|
-
console.log("daemon: unsupported platform");
|
|
95
|
-
}
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import escapeXml from "./escapeXml";
|
|
2
|
-
|
|
3
|
-
export default function createLaunchdPlist(
|
|
4
|
-
args: string[],
|
|
5
|
-
stdoutPath: string,
|
|
6
|
-
stderrPath: string,
|
|
7
|
-
) {
|
|
8
|
-
const items = args
|
|
9
|
-
.map((arg) => ` <string>${escapeXml(arg)}</string>`)
|
|
10
|
-
.join("\n");
|
|
11
|
-
return [
|
|
12
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
13
|
-
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
14
|
-
'<plist version="1.0">',
|
|
15
|
-
" <dict>",
|
|
16
|
-
" <key>Label</key>",
|
|
17
|
-
" <string>com.crnd.daemon</string>",
|
|
18
|
-
" <key>ProgramArguments</key>",
|
|
19
|
-
" <array>",
|
|
20
|
-
items,
|
|
21
|
-
" </array>",
|
|
22
|
-
" <key>RunAtLoad</key>",
|
|
23
|
-
" <true/>",
|
|
24
|
-
" <key>KeepAlive</key>",
|
|
25
|
-
" <true/>",
|
|
26
|
-
" <key>StandardOutPath</key>",
|
|
27
|
-
` <string>${escapeXml(stdoutPath)}</string>`,
|
|
28
|
-
" <key>StandardErrorPath</key>",
|
|
29
|
-
` <string>${escapeXml(stderrPath)}</string>`,
|
|
30
|
-
" </dict>",
|
|
31
|
-
"</plist>",
|
|
32
|
-
"",
|
|
33
|
-
].join("\n");
|
|
34
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export default function createSystemdService(
|
|
2
|
-
args: string[],
|
|
3
|
-
stdoutPath: string,
|
|
4
|
-
stderrPath: string,
|
|
5
|
-
) {
|
|
6
|
-
const execStart = args
|
|
7
|
-
.map((arg) => (arg.includes(" ") ? `"${arg}"` : arg))
|
|
8
|
-
.join(" ");
|
|
9
|
-
return [
|
|
10
|
-
"[Unit]",
|
|
11
|
-
"Description=crnd daemon",
|
|
12
|
-
"",
|
|
13
|
-
"[Service]",
|
|
14
|
-
`ExecStart=${execStart}`,
|
|
15
|
-
"Restart=always",
|
|
16
|
-
"RestartSec=1",
|
|
17
|
-
`StandardOutput=append:${stdoutPath}`,
|
|
18
|
-
`StandardError=append:${stderrPath}`,
|
|
19
|
-
"",
|
|
20
|
-
"[Install]",
|
|
21
|
-
"WantedBy=default.target",
|
|
22
|
-
"",
|
|
23
|
-
].join("\n");
|
|
24
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
export default function getDaemonServiceArgs() {
|
|
4
|
-
const [execPath, scriptPath] = process.argv;
|
|
5
|
-
if (scriptPath?.endsWith(".ts")) {
|
|
6
|
-
return [execPath, path.resolve(scriptPath), "daemon", "serve"];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return [execPath, "daemon", "serve"];
|
|
10
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import createEnvSchema from "../../shared/jobs/createEnvSchema";
|
|
2
|
-
|
|
3
|
-
export default function parseEnvArgs(input: unknown) {
|
|
4
|
-
if (!input) {
|
|
5
|
-
return undefined;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const values = Array.isArray(input) ? input : [input];
|
|
9
|
-
const env: Record<string, string> = {};
|
|
10
|
-
|
|
11
|
-
for (const value of values) {
|
|
12
|
-
if (typeof value !== "string") {
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
const index = value.indexOf("=");
|
|
16
|
-
if (index <= 0) {
|
|
17
|
-
throw new Error(`Invalid env entry: ${value}`);
|
|
18
|
-
}
|
|
19
|
-
const key = value.slice(0, index);
|
|
20
|
-
const val = value.slice(index + 1);
|
|
21
|
-
if (!key || !val) {
|
|
22
|
-
throw new Error(`Invalid env entry: ${value}`);
|
|
23
|
-
}
|
|
24
|
-
env[key] = val;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return createEnvSchema().parse(env);
|
|
28
|
-
}
|
package/src/cli/main.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import getAutostartPath from "./getAutostartPath";
|
|
3
|
-
import getDaemonInstallArgs from "./getDaemonInstallArgs";
|
|
4
|
-
|
|
5
|
-
export default function ensureAutostart() {
|
|
6
|
-
if (process.env.CRND_DISABLE_AUTOSTART === "1") {
|
|
7
|
-
return { ok: true, reason: "disabled" };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const path = getAutostartPath();
|
|
11
|
-
if (!path) {
|
|
12
|
-
return { ok: false, reason: "unsupported_platform" };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (process.platform === "win32") {
|
|
16
|
-
const result = Bun.spawnSync(["schtasks", "/Query", "/TN", path]);
|
|
17
|
-
if (result.success) {
|
|
18
|
-
return { ok: true, reason: "already_installed" };
|
|
19
|
-
}
|
|
20
|
-
} else if (existsSync(path)) {
|
|
21
|
-
return { ok: true, reason: "already_installed" };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const result = Bun.spawnSync(getDaemonInstallArgs());
|
|
25
|
-
if (result.success) {
|
|
26
|
-
return { ok: true, reason: "installed" };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return { ok: false, reason: "install_failed" };
|
|
30
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export default function getAutostartPath() {
|
|
5
|
-
if (process.platform === "darwin") {
|
|
6
|
-
return path.join(
|
|
7
|
-
os.homedir(),
|
|
8
|
-
"Library",
|
|
9
|
-
"LaunchAgents",
|
|
10
|
-
"com.crnd.daemon.plist",
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (process.platform === "linux") {
|
|
15
|
-
return path.join(
|
|
16
|
-
os.homedir(),
|
|
17
|
-
".config",
|
|
18
|
-
"systemd",
|
|
19
|
-
"user",
|
|
20
|
-
"crnd.service",
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (process.platform === "win32") {
|
|
25
|
-
return "crnd";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
export default function getDaemonInstallArgs() {
|
|
4
|
-
const [execPath, scriptPath] = process.argv;
|
|
5
|
-
if (scriptPath?.endsWith(".ts")) {
|
|
6
|
-
return [execPath, path.resolve(scriptPath), "daemon", "install"];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return [execPath, "daemon", "install"];
|
|
10
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import appendEvent from "../shared/events/appendEvent";
|
|
2
|
-
import removeDaemonState from "../shared/state/removeDaemonState";
|
|
3
|
-
import type createLogger from "./createLogger";
|
|
4
|
-
import type createJobsFileSync from "./jobs/createJobsFileSync";
|
|
5
|
-
import type createScheduler from "./scheduler/createScheduler";
|
|
6
|
-
|
|
7
|
-
type Logger = ReturnType<typeof createLogger>;
|
|
8
|
-
type Server = ReturnType<typeof Bun.serve>;
|
|
9
|
-
type Scheduler = ReturnType<typeof createScheduler>;
|
|
10
|
-
type JobsFileSync = ReturnType<typeof createJobsFileSync>;
|
|
11
|
-
|
|
12
|
-
export default function createShutdownHandler(
|
|
13
|
-
server: Server,
|
|
14
|
-
logger: Logger,
|
|
15
|
-
scheduler: Scheduler,
|
|
16
|
-
jobsFileSync: JobsFileSync,
|
|
17
|
-
) {
|
|
18
|
-
return () => {
|
|
19
|
-
logger.info("daemon_shutdown");
|
|
20
|
-
scheduler.stop();
|
|
21
|
-
jobsFileSync.stop();
|
|
22
|
-
appendEvent("daemon_stopped", { pid: process.pid });
|
|
23
|
-
removeDaemonState();
|
|
24
|
-
server.stop();
|
|
25
|
-
if (process.env.CRND_TEST_MODE !== "1") {
|
|
26
|
-
process.exit(0);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import type { FSWatcher } from "node:fs";
|
|
2
|
-
import { existsSync, watch } from "node:fs";
|
|
3
|
-
import type openDatabase from "../../db/openDatabase";
|
|
4
|
-
import { jobs } from "../../db/schema";
|
|
5
|
-
import formatJobRow from "../../shared/jobs/formatJobRow";
|
|
6
|
-
import parseJobsToml from "../../shared/jobs/parseJobsToml";
|
|
7
|
-
import readJobsToml from "../../shared/jobs/readJobsToml";
|
|
8
|
-
import writeJobsToml from "../../shared/jobs/writeJobsToml";
|
|
9
|
-
import getJobsTomlPath from "../../shared/paths/getJobsTomlPath";
|
|
10
|
-
import type createLogger from "../createLogger";
|
|
11
|
-
import type createScheduler from "../scheduler/createScheduler";
|
|
12
|
-
import deleteJobByName from "./deleteJobByName";
|
|
13
|
-
import upsertJob from "./upsertJob";
|
|
14
|
-
|
|
15
|
-
type Db = ReturnType<typeof openDatabase>["orm"];
|
|
16
|
-
type Logger = ReturnType<typeof createLogger>;
|
|
17
|
-
type Scheduler = ReturnType<typeof createScheduler>;
|
|
18
|
-
|
|
19
|
-
export default function createJobsFileSync(
|
|
20
|
-
db: Db,
|
|
21
|
-
scheduler: Scheduler,
|
|
22
|
-
logger: Logger,
|
|
23
|
-
) {
|
|
24
|
-
let ignore = false;
|
|
25
|
-
let watcher: FSWatcher | null = null;
|
|
26
|
-
|
|
27
|
-
const applyJobs = (jobsFromToml: ReturnType<typeof readJobsToml>) => {
|
|
28
|
-
const existing = db.select().from(jobs).all().map(formatJobRow);
|
|
29
|
-
const existingByName = new Map(existing.map((job) => [job.name, job]));
|
|
30
|
-
const seen = new Set<string>();
|
|
31
|
-
|
|
32
|
-
for (const entry of jobsFromToml) {
|
|
33
|
-
const payload = {
|
|
34
|
-
name: entry.name,
|
|
35
|
-
description: entry.description,
|
|
36
|
-
command: entry.command,
|
|
37
|
-
cwd: entry.cwd,
|
|
38
|
-
env: entry.env,
|
|
39
|
-
schedule: entry.schedule,
|
|
40
|
-
runAt: entry.run_at,
|
|
41
|
-
timezone: entry.timezone,
|
|
42
|
-
timeoutMs: entry.timeout_ms,
|
|
43
|
-
paused: entry.paused,
|
|
44
|
-
overlapPolicy: entry.overlap_policy,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const existingJob = existingByName.get(entry.name);
|
|
48
|
-
const { job } = upsertJob(db, payload, entry.id ?? existingJob?.id);
|
|
49
|
-
scheduler.upsert(job);
|
|
50
|
-
seen.add(entry.name);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for (const job of existing) {
|
|
54
|
-
if (!seen.has(job.name)) {
|
|
55
|
-
scheduler.remove(job.id);
|
|
56
|
-
deleteJobByName(db, job.name);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const applyFromFile = () => {
|
|
62
|
-
try {
|
|
63
|
-
const jobsFromToml = readJobsToml();
|
|
64
|
-
applyJobs(jobsFromToml);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
logger.error({ event: "jobs_toml_error", message: String(error) });
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const writeFromDb = () => {
|
|
71
|
-
const rows = db.select().from(jobs).all().map(formatJobRow);
|
|
72
|
-
ignore = true;
|
|
73
|
-
try {
|
|
74
|
-
writeJobsToml(rows);
|
|
75
|
-
} finally {
|
|
76
|
-
setTimeout(() => {
|
|
77
|
-
ignore = false;
|
|
78
|
-
}, 50);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
init() {
|
|
84
|
-
const path = getJobsTomlPath();
|
|
85
|
-
if (existsSync(path)) {
|
|
86
|
-
applyFromFile();
|
|
87
|
-
} else {
|
|
88
|
-
writeFromDb();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
watcher = watch(path, { persistent: false }, () => {
|
|
92
|
-
if (ignore) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
applyFromFile();
|
|
96
|
-
});
|
|
97
|
-
},
|
|
98
|
-
stop() {
|
|
99
|
-
watcher?.close();
|
|
100
|
-
},
|
|
101
|
-
writeFromDb,
|
|
102
|
-
applyFromText(content: string) {
|
|
103
|
-
try {
|
|
104
|
-
const jobsFromToml = parseJobsToml(content);
|
|
105
|
-
applyJobs(jobsFromToml);
|
|
106
|
-
writeFromDb();
|
|
107
|
-
return { ok: true };
|
|
108
|
-
} catch (error) {
|
|
109
|
-
return { ok: false, error: String(error) };
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
|
-
import type openDatabase from "../../db/openDatabase";
|
|
3
|
-
import { jobs, runs } from "../../db/schema";
|
|
4
|
-
import appendEvent from "../../shared/events/appendEvent";
|
|
5
|
-
|
|
6
|
-
type Db = ReturnType<typeof openDatabase>["orm"];
|
|
7
|
-
|
|
8
|
-
export default function deleteJobByName(db: Db, name: string) {
|
|
9
|
-
const row = db.select().from(jobs).where(eq(jobs.name, name)).get();
|
|
10
|
-
if (!row) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
db.delete(runs).where(eq(runs.jobId, row.id)).run();
|
|
15
|
-
db.delete(jobs).where(eq(jobs.id, row.id)).run();
|
|
16
|
-
appendEvent("job_deleted", { jobId: row.id, name: row.name });
|
|
17
|
-
return row.id;
|
|
18
|
-
}
|