#!/usr/bin/env node /** * Fallback host for pi-web-scheduler, usable without PI WEB. * pi-scheduler tick one scheduling pass, waits for started runs (for cron / Task Scheduler / systemd timers) * pi-scheduler daemon keep running, tick every 30s * pi-scheduler list list jobs * pi-scheduler run run a job now and wait * pi-scheduler show job + recent runs as JSON * Env: PI_SCHEDULER_HOME (store root), PI_SCHEDULER_PI (pi command), PI_SCHEDULER_ALLOW (comma list of executables) */ import { Engine } from "./core/engine.js"; import { NodeRuntime } from "./core/nodeRuntime.js"; import { describeSchedule } from "./core/schedule.js"; import { SchedulerService } from "./core/service.js"; import { defaultStoreRoot, JobStore } from "./core/store.js"; async function main(argv: string[]): Promise { const [command = "help", arg] = argv; const store = new JobStore(defaultStoreRoot()); await store.init(); const runtime = new NodeRuntime({ storeRoot: store.root, ...(process.env.PI_SCHEDULER_PI ? { piCommand: process.env.PI_SCHEDULER_PI } : {}) }); const allow = process.env.PI_SCHEDULER_ALLOW?.split(",").map((s) => s.trim()).filter(Boolean); const engine = new Engine(store, runtime, { tickMs: 30_000, ...(allow?.length ? { allowedExecutables: allow } : {}), log: (level, message) => console[level === "info" ? "log" : level](`[${level}] ${message}`), }); const service = new SchedulerService(store, engine); switch (command) { case "tick": { const started = await engine.tick(); await engine.idle(); console.log(started.length ? `run eseguiti: ${started.join(", ")}` : "nessun job in scadenza"); return 0; } case "daemon": { const controller = new AbortController(); for (const sig of ["SIGINT", "SIGTERM"] as const) process.on(sig, () => controller.abort()); await engine.start(controller.signal); console.log(`pi-scheduler daemon avviato (store ${store.root})`); await new Promise((resolve) => controller.signal.addEventListener("abort", () => resolve(), { once: true })); await engine.stop(); return 0; } case "list": { for (const { job } of await service.listJobs()) { console.log(`${job.id}\t${job.enabled ? "on " : "off"}\t${describeSchedule(job.schedule)}\tnext=${job.state.nextRunAt ?? "-"}\tlast=${job.state.lastStatus ?? "-"}`); } return 0; } case "run": { if (!arg) throw new Error("usage: pi-scheduler run "); const runId = await engine.runNow(arg); await engine.idle(); const run = await store.getRun(arg, runId); console.log(JSON.stringify(run, null, 2)); return run?.status === "succeeded" ? 0 : 1; } case "show": { if (!arg) throw new Error("usage: pi-scheduler show "); console.log(JSON.stringify(await service.detail(arg, 10), null, 2)); return 0; } default: console.log("usage: pi-scheduler |show >"); return command === "help" ? 0 : 2; } } main(process.argv.slice(2)).then((code) => { process.exitCode = code; }, (e: unknown) => { console.error(e instanceof Error ? e.message : String(e)); process.exitCode = 1; });