Free Coming soon

Svalefelt Scheduler

A job scheduler that keeps its jobs in files — and knows how to run, watch and rescue the Nova Batch server.

Free to download when it ships. No registration, no email address.

Svalefelt Scheduler — Dashboard

One YAML file per job, kept in source control, reloaded on save

Cron, file-arrival, after-another-job and SQL triggers

Script, Python, SQL and file-transfer steps, with timeouts and retries

Steps that fail when the file they promised never appeared

Nova Batch server built in — steps that succeed only when Nova verifiably did the work

A heartbeat through the batch server, and an automatic restart when it stops

Dashboard, run log with the reason for every failure, and a page per batch server

A general job scheduler with a web UI: one YAML file per job in source control, triggers on a timetable, a file arriving, another job finishing or a SQL check, and steps that run scripts, SQL and file transfers. Built with the Nova Batch server in mind: it takes over the scheduling, hands the batch server the tasks only it can run, confirms in Nova's own tables that the work was done, and restarts the server when its heartbeat stops.

Most of what runs overnight in a collection operation is a handful of scripts, imports and exports that somebody once put in the Windows Task Scheduler, a cron table or the batch server's own schedule — and that nobody can see in one place. Svalefelt Scheduler is one place: every job is a small text file, every run is logged with what it did and why it failed, and the web UI shows what ran, what is running and what is next. It schedules anything that can be started from a command line, a Python script or a SQL statement.

It was built with the Nova Batch server in mind. The batch server is the only way to reach a good part of what Nova does — imports, exports, Object Basic, interest updates, payment commits — and it is also the part of a Nova installation that most often needs a person in the morning: a job that hangs, a schedule that was silently skipped, a job stuck at Running or parked On Hold and forgotten. The scheduler does not replace it. It takes over everything around it: deciding when things run, preparing and checking the inputs, watching the result, and recovering when the batch server does not.

A job is a file

name: nova-init-import
description: Import INIT files in Nova as they arrive

triggers:
  - type: file
    path: /mnt/nova/IMP/Init
    pattern: "*.xml"

steps:
  - name: import
    type: nova.impinit
    connection: nova
    timeoutSeconds: 1800
    retries: 5                        # MUTEX: another import is running
    retryDelaySeconds: 60
    settings:
      file: \\novasrv\Nova\IMP\Init\${TRIGGER_FILE_NAME}
      allowEmpty: "false"             # zero records read is a failure

  - name: after-import
    type: nova.execob
    connection: nova
    settings:
      file: \\novasrv\Nova\IED\AfterInitImport.ob

onError:
  - name: notify
    type: script
    command: ./notify.sh "Import failed: $SCHEDULER_ERROR"

One YAML file per job, in a folder that belongs in source control. Files are re-read when they change, without a restart; a file with a typo or a missing setting is listed as a problem and never half-run. A job is triggered by a cron schedule, by a file arriving, by another job finishing — successfully or not — or by a SQL query returning true. Its steps run in order: scripts, Python, SQL, file imports and exports to SQL Server, and — where Nova is involved — one step type per Nova batch task. Every step has a timeout, retries, and an output-file check: a step that reports success but leaves no new file behind has failed, which is the classic silent export failure. A job never overlaps itself, and an onError handler decides what happens next: restart the step, restart the job, or run a script that tells somebody.

The Nova Batch server, built in

A Nova step books the task in the batch server's own tables — the same way Nova itself does, with nothing installed in the Nova database — waits for the result, and then checks that Nova actually did the work: an import is verified against Nova's import log, not just against the batch server's return code. A file export fails if it reported success and left no new file behind, so an old export lying around cannot vouch for this run. Nova's another job of this type is running is a result the scheduler understands and retries; a booking that never started is withdrawn on timeout so it cannot fire later, unattended.

When the batch server stops, the scheduler notices

A heartbeat is a tiny job sent through the batch server on an interval: OK means the server picked it up, ran it and reported back — the whole chain, which a running service process alone does not prove. After a number of missed heartbeats the scheduler restarts the batch service and says so. Job-level recovery is yours to declare: on error, restart the step, restart the job, or run a script that tells somebody.

Taking over an existing schedule

An extract of Nova's schedule table is converted into job files in one command: chains become one job with ordered steps, repeats become cron expressions, and every converted job arrives disabled, so the switch-over is one file at a time, on your terms.

Watching it

The dashboard shows the last hour, today and yesterday, what is running now, and a heartbeat tile per batch server. The log lists every run with the reason it failed, filterable by job. The state page shows each batch server's last sixty heartbeats, its service state, and buttons to start, stop or restart it — and lists every path the jobs use, marked OK, missing or no access, which is the first thing to look at when a job stops.

The scheduler installs as a Windows service in one command and ships self-contained, so the host needs no .NET runtime; without the Nova parts it runs equally well on Linux or in Docker. Everything the UI does, the command line does too: scheduler validate for CI, scheduler run <job> by hand, scheduler nova status.

Svalefelt Scheduler in pictures

12 screens, and what each one does.

Dashboard

Dashboard

What happened last hour, today and yesterday, what is running, and whether the batch server answers.

The landing page after sign-in. The tiles count runs in the last hour, today and yesterday, with the failures called out in red; what is running right now; how many jobs exist and how many are enabled; and one tile per Nova batch server the scheduler watches, showing whether its heartbeat is answered and when it last was. Below, the latest runs and the latest failures. The failures table says which step failed and how - here the reconciliation that follows the bank export stopped at its reconcile step with exit code 3; the totals it printed are one click away in the run's log - so the failure is on the front page, not three clicks down. Every row opens the run's log.

The jobs

The jobs

Every job file the scheduler has loaded, grouped, with its last and next run.

One table per group - a group is just a word in the job file, used to keep a customer's or a system's jobs together - and one row per job: its description, when it last ran and how long that took, how it ended, and when it runs next. "Event-driven" means a file or a job trigger will start it; "after bank-file-export" is a job that follows another. Run appears only on jobs that have no trigger at all, the ones meant to be started by hand; every other job is started by its triggers, and the editor has a Run now for the rest. A disabled job is greyed. Clicking anywhere on a row opens the job in the editor. Jobs are YAML files in a folder: what you see here is the folder, read again the moment a file changes.

A job in the editor

A job in the editor

Triggers, steps, event handlers and variables, each a line you click to open.

A job is read top to bottom: what it is, what starts it, what it does in order, what to do when something goes wrong, and the variables every step gets. Each trigger, step and handler is one line - the kind in bold, the step's name, and a description that is either written in the file or made from the settings (a file trigger shows the folder and pattern it watches; a script step shows its command) - with badges for a connection, a timeout or retries. Click a line to open it. The purple handlers run at the phase on their pill: this one answers stop when the load step fails, after paging the on-call engineer. The Run now button runs the saved file, the YAML tab shows the same job as text, and Validate runs the same checks the scheduler applies when it loads the file.

A Nova import step, opened

A Nova import step, opened

The form of a step kind: the settings it takes, and every result it can end with.

Every step kind has its own form, so a Nova INIT import asks for exactly what it needs: the file as the batch server sees it (${TRIGGER_FILE_NAME} inserts the file the trigger found), whether to verify the import against Nova's import log, whether zero imported records is acceptable, and which results are worth a retry (MUTEX and TIMEOUT by default - a missing file is not). Under the form, What this step can end with lists every outcome with its meaning: the batch server's own results (OK, FAILED, MUTEX, FILE_MISSING, TIMEOUT) and the verification's (OK, EMPTY, FAILED, NOT_FOUND), which is how a job can fail although Nova reported success - the import log has the last word. The retry and timeout settings and the output file check sit in their own folds.

Adding a step

Adding a step

The step kinds, grouped: general, files and databases, and the Nova batch tasks.

Add step opens the catalogue of step kinds under their headings, each with a line saying what it does. The general kinds run a script, Python or SQL; the file kinds move data between folders and SQL Server tables; the Nova kinds each book one task on the Nova batch server and follow it to a verified result - the four built-in imports, an OB-code script, a program, and further down the batch tasks whose layouts were taken from live schedules (SQL definitions, printing, payment commits, ...). Any Nova task takes hand-written TaskData for anything else, and Start / stop the batch server controls the server itself. The same catalogue drives the forms and the validation: a setting the kind does not know is reported when the file is loaded, not when the job runs.

An event handler, opened

An event handler, opened

What runs when a step fails, scoped to a step, answering continue, restart or stop.

Handlers are how a job reacts to its own events without a wrapper script around it. They are one list; the Add handler menu asks in one click both when it runs - on start, on error, or finally - and what it is (script, Python or SQL). An on-error handler can be scoped to one step, to that step and everything after or before it, or to all but it, and can fire only on failures that would stop the job or only on those the job would continue past. It answers with a word - continue, restart-step, restart-job or stop - printed as SCHEDULER_RESULT= from a script or returned from a SQL query; the number of restarts is capped. The job file keeps the three lists onStart, onError and onFinally as before; only the editor shows them as one.

The same job as YAML

The same job as YAML

The file is the source of truth; the form and the text are two views of it.

The YAML tab is the job file itself, in an editor with syntax colouring. Switching tabs converts through the scheduler's own parser and serializer, so the form and the running scheduler can never disagree about what a file means, and a file written by hand - or generated by scheduler nova convert from a Nova schedule - opens in the form like any other. Saving from the form rewrites the file and drops hand-written comments; the UI says so, and the YAML tab keeps them. Because the files are the truth, they live in version control like code: a change is a diff, a rollback is a checkout, and a second scheduler is a copy of the folder.

The log

The log

Every run, newest first, in the buckets an operator thinks in.

The log lists runs under Last hour, Earlier today, Earlier this week and Older, so the question "what happened since I left" is answered by the top of the page. Each row is the job, when it started, how long it took, how it ended and - for a failed run - why, as the failing step reported it. The list refreshes on its own while auto-refresh is on, fetching only the newest runs and merging them in; Flat turns it into one table, and the filters narrow it to one job or to failures only. Underneath, every run is one JSONL file on disk, which is what makes the log greppable from a shell and readable long after the UI is gone.

A run's log

A run's log

What each step printed, when, and how the run ended.

Clicking a run opens its log: every event the runner recorded, in order - the run starting, each step starting and ending with its duration and exit code, every line the step printed on stdout or stderr, the handlers that fired and what they answered, and the final status. Here the reconciliation's second step printed the two totals and their difference and then failed, the report step was never reached, and the finally handler still ran and said so. The same text is in the JSONL file under runs/<job>/<run id>.jsonl, and scheduler run <job> streams it to the console when a job is started from a shell.

State: the batch server and the paths

State: the batch server and the paths

The Nova batch server's heartbeat and service state, and every path the jobs depend on, checked.

The state page is where the reason for this scheduler shows. For every configured Nova system it sends a small job through the batch server at an interval - a heartbeat that is only ok when the server picked the job up, ran it and wrote the result, which a running service process alone does not prove - and keeps a strip of the recent beats with their latency; Send heartbeat now asks for one straight away. With a service or container configured it also shows the server's state and offers Start, Stop and Restart (the two that interrupt work ask first and are logged with the user's name), and it can be told to restart the server on its own after a number of failed heartbeats. In this picture the system is the real Nova batch server of the Wine test environment, answering from a container. Below, every folder and file the jobs refer to - watched folders, script files, output files, files on the batch server - is listed and checked read-only from this machine, so a share that moved is found before the job that needs it runs at night.

What's new

What's new

The release notes of the running build, one click from the header.

The version in the header is a link. It opens the release notes this build carries - the same text as docs/RELEASE-NOTES.md, embedded into the binary when the release was built, so a deployment can always say what it is and what changed. scheduler notes prints the same from a shell, and a release is refused when the notes say nothing about its version. The build id under the title carries the date and the commit.

Signing in

Signing in

The sign-in box; every page behind it needs a token.

Every page of the tour sits behind this box: the web UI opens on it. Today any name is accepted - the credential check is the one piece meant to be replaced by a real one - but everything behind the box is the real thing: the browser gets a short-lived access token and a rotating refresh token, every /api/* call carries the token, and a restart of the service means signing in again. The command line (scheduler run, validate, nova status, ...) never goes through this; it reads the job files and the configuration directly.

Get Svalefelt Scheduler

Not released yet. It will be free to download and use, with no registration and no email address. Ask if you would like to try it before then.

Requirements

  • Windows Server (self-contained, no .NET runtime needed), or Linux and Docker
  • For the Nova integration — access to the Nova SQL Server database (nothing is installed in it), the Nova folders reachable by path, and a Windows host to control the Batch service