Releases: Sev7eNup/NodePilot
Release list
NodePilot 1.4.0
The dashboard answers its history from precomputed hourly buckets and charts execution durations, the web UI gains minimal light and dark skins, HA recovery and execution dispatch were hardened on real PostgreSQL and SQL Server, and the desktop package can be installed over itself, uninstalled completely and installed again. CHANGELOG.md carries the full entries.
Upgrading from 1.3.0 — read this first
There is no automatic upgrade path from 1.3.0 or earlier. The development-era migration history was consolidated into a single baseline. A 1.3.0 database does not know that migration, so 1.4.0 tries to create the schema again and the API stops at start-up with relation "AuditLog" already exists. A server update rolls back and keeps 1.3.0 running; the desktop setup reports the failed provisioning.
To move across:
- On 1.3.0, export a system configuration backup (
/backupornp backup export) and keep its passphrase. - Install 1.4.0 against a new, empty database. On the desktop, uninstall first and choose Delete everything.
- Restore the backup.
Workflows, folders, machines, credentials, globals, users, custom activities, alerting rules and settings come across; execution history, audit log and statistics do not. Databases created by 1.4.0 upgrade normally from here on.
Dashboard
- Execution duration instead of a success-rate trend: median and P95 per time bucket, for all workflows or one selected workflow.
- History from hourly buckets. A 30-day window sums at most 720 bucket rows instead of scanning the raw table. The dashboard falls back to raw rows while the initial backfill runs and whenever the buckets stop being updated; buckets older than 32 days are removed.
- Minimal light and dark skins across the web UI.
- The AI chat page column and the floating widget can be dragged wider; the global launcher stays out of the workflow designer.
Desktop package
- Uninstall asks about your data: Keep data, Delete everything or Cancel (
/PURGEDATA=1unattended). Delete everything leaves nothing behind — database, keys, settings, logs, per-user app data and the certificate's private key. - Installing over a running installation works: setup backs up the database and stops the app, clients and services before copying files, and aborts without touching a file if it cannot.
- No default password. The first administrator is created on the setup page. Data left behind by an uninstall is offered for keeping or deleting on the next install (
/DISCARDDATA=1unattended). - The API service now starts reliably on slow machines, and a failed provisioning makes setup exit with code 20 instead of 0.
Engine, HA and operations
- HA recovery works in bounded batches and no longer holds the lease for a whole backlog; dispatch claims are atomic on PostgreSQL and SQL Server.
llmQueryretries withouttemperaturewhen a reasoning model rejects it.- The REST activity reaches hosts whose DNS records mix link-local and routable addresses; blocked addresses are still never connected to.
/metricsworks again with Prometheus scraping enabled — every scrape containing a histogram answered HTTP 500 since 1.2.15.- The service reads its early start-up configuration from the installation instead of
System32, soPerformance:ManualTuningapplies from the first second. - Both installer wizards carry the NodePilot logo.
Downloads
| File | What it is |
|---|---|
NodePilot-Server-Setup-1.4.0.exe |
Windows server installer (GUI and /VERYSILENT) |
NodePilot-Desktop-Setup-1.4.0.exe |
offline desktop installer, all services set to start at boot |
NodePilot-1.4.0.zip |
server payload for Install-NodePilot.ps1 |
NodePilot-Deploy-Scripts-1.4.0.zip |
the install scripts on their own, so the verifying script can be had before the archive it verifies |
NodePilot-Switcher-1.4.0-win-x64.zip |
the Switcher standalone, for a machine with no NodePilot installation |
NodePilot-1.4.0.zip.manifest.json + .p7s |
detached signed manifest |
nodepilot-release-signing.cer |
publisher certificate |
NodePilot-1.4.0.SHA256SUMS.txt |
checksums covering every file above |
Signature and first run
Publisher: CN=NodePilot Release Signing.
Certificate thumbprint: 277EAB317A581C88302CE92BE805938C86B4650D.
Both installers are Authenticode-signed with a self-signed release certificate. Windows SmartScreen will show an unrecognised-app prompt and the chain will read as untrusted — there is no public CA behind this certificate yet. Before choosing More info → Run anyway, compare the download's SHA-256 against the attached checksum file and the publisher certificate against the thumbprint above. The attached .cer carries no private key.
NodePilot 1.3.0
The first minor release since 1.2.0, cut from thirteen development builds and 65 commits. Highlights below; CHANGELOG.md carries the full entries with the reasoning behind each one.
Designer
- Editing no longer goes missing. Adding, pasting, duplicating, deleting and grouping nodes, and undo/redo, never marked the workflow as changed. Ctrl+S is gated on that flag, so after any of them the save did nothing, autosave never armed, and leaving the page warned about nothing.
- Undo stopped deleting the children of a collapsed group. History snapshotted the projected graph, which omits them, and autosave persisted the loss.
- Lint is off the frame path. It ran on every frame of a drag, sampling 23 bezier points per edge against every node. It now runs on a settled graph, with the publish path linting live so its verdict is never stale.
- Atelier is the only designer look; the classic design language is gone.
dup-published-paramno longer fires on names the author cannot change.
Assistants
- The knowledge assistant is reachable from every page. A launcher opens it as a widget, and conversation, draft and any running stream survive route changes. Minimising does not interrupt a stream.
- The MCP server is at 102 tools, including
get_failure_causes.
Live-Ops and dashboard
- Timeline zoom, selection and shared-workflow grouping reworked.
- The run-status donut is a readable summary: total, one proportion bar, every status with count and share, keyboard-selectable.
- The success-rate trend draws a point per observed hour, so an isolated active hour is visible at all.
- Recent failures are grouped by normalised cause, with a retry counter — also via
np failure-causes. - The long-running count uses the alerting threshold instead of its own.
Engine
startProgramno longer retains resources in reused runspaces. Output is drained without PowerShell event jobs, capture memory is bounded, and waited processes are cleaned up on timeout or cancellation.- A
runScriptstep that never ran no longer reports success, and queued PowerShell invocations cancel correctly instead of reporting a false result. - Workflow input data can no longer end up as PowerShell code — the escaping now covers every quote character the parser recognises.
- Edge conditions fail closed. An unknown type or operator, a group without children or a broken legacy string is refused at save and aborts the run instead of opening the path it was meant to guard. A missing global fails the step rather than travelling on as text, and
<,<=andisFalseno longer fire on missing data. - A step no longer stays
Runningunder a finished execution, and a step that fails to persist no longer leaves its siblings running. - A retry no longer repeats a permanent remote failure — a denied WinRM logon could lock the account out.
config.retryapplies to remote activities;forEachchildren record their parent execution.
Clients and security
- The
npCLI and the MCP server pin the server certificate. A SHA-256 pin replaces a root-store import for a server the client machine does not trust. The pin is additive so renewal keeps working, a mismatching pin is rejected even under--insecure-tls, and a stored pin is origin-bound. - TLS remedies match the diagnosed cause. A name mismatch is answered with a ready-made
np config set server <url>rather than a root-store import that cannot fix it; a second cause is no longer swallowed by the first. - The Switcher signs in with Kerberos before asking for a password.
Performance
GET /api/machinesstopped parsing every workflow definition per request,GET /api/workflowsno longer shipsDefinitionJson, andGET /api/executionsgained an opt-out for its two 32 KiB payload fields.- Sidebar badges have their own endpoint instead of pulling the whole dashboard aggregate every 60 s from every page.
- ECharts left the boot chunk: eager path 2029 → 1419 KB raw, 591 → 386 KB gzipped.
Deployment
- The bundled ASP.NET Core runtime moves to 10.0.12. The documented 10.0.11 floor is unchanged — it is a minimum that 10.0.12 satisfies.
- The installer's closing summary prints addresses that carry the HTTPS port, so they can be opened as printed on an installation that does not listen on 443.
- The Engine Switcher is now simply the Switcher, everywhere it is persisted.
Downloads
| File | What it is |
|---|---|
NodePilot-Server-Setup-1.3.0.exe |
Windows server installer (GUI and /VERYSILENT) |
NodePilot-Desktop-Setup-1.3.0.exe |
offline desktop installer, all services set to start at boot |
NodePilot-1.3.0.zip |
server payload for Install-NodePilot.ps1 |
NodePilot-Deploy-Scripts-1.3.0.zip |
the install scripts on their own, so the verifying script can be had before the archive it verifies |
NodePilot-Switcher-1.3.0-win-x64.zip |
the Switcher standalone, for a machine with no NodePilot installation |
NodePilot-1.3.0.zip.manifest.json + .p7s |
detached signed manifest |
nodepilot-release-signing.cer |
publisher certificate |
NodePilot-1.3.0.SHA256SUMS.txt |
checksums covering every file above |
New in this release: the server installer bundles the PostgreSQL command-line client. Every release up to 1.2.26 shipped without it, which meant the installer skipped CREATE ROLE / CREATE DATABASE and the readiness page asked for both to be created by hand. It can now create them for you. That is why the server installer is roughly 19 MB larger than in 1.2.26.
Signature and first run
Publisher: CN=NodePilot Release Signing.
Certificate thumbprint: 277EAB317A581C88302CE92BE805938C86B4650D.
Both installers are Authenticode-signed with a self-signed release certificate. Windows SmartScreen will show an unrecognised-app prompt and the chain will read as untrusted — there is no public CA behind this certificate yet. Before choosing More info → Run anyway, compare the download's SHA-256 against the attached checksum file and the publisher certificate against the thumbprint above. The attached .cer carries no private key.
NodePilot 1.2.26
One fix, found while installing 1.2.25 on the lab machine: that release covered a fresh install but not an upgrade.
Fixed
An update no longer reverts the Engine Switcher's server URL. 1.2.25 taught the installer to fill in nodePilot.serverUrl, so the switcher can drive np without a per-user CLI configuration. An update, however, wipes and repopulates the install directory:
Get-ChildItem -LiteralPath $InstallPath -Force | Remove-Item -Recurse -Force
Copy-DirectoryContents -Source $artifactStage -Destination $InstallPathtools\engine-switcher\engine-switcher.json went with it and came back as the shipped template with serverUrl: null. Every upgrade therefore undid the fix, and the switch to NodePilot failed again with No server URL configured.
The update now reads the value before the wipe and writes it back afterwards. An installation that predates the setting has nothing to carry, so it derives one from the first real entry of AllowedHosts and the Kestrel HTTPS port — both from the appsettings.Production.json the update already parses for its health probe. Install and update share one implementation instead of two copies of the same string surgery.
Verified against a real installation
The Engine Switcher was run end to end against System Center Orchestrator 2025 in the lab, in both directions and with the failure path forced:
- To Orchestrator — services started, the listed runbook started, verification settled, exit 0.
- To NodePilot — the running Orchestrator job stopped and verified, services handed over, workflow allowlist verified with the enabled set unchanged, exit 0.
- Reconciliation deadline forced to one second — a described error naming the runbook that did not settle, the fail-closed cleanup, exit 1 and an empty stderr. Before 1.2.25 this path ended in an unhandled
TaskCanceledExceptionthat terminated the process.
Verifying this download
Every file below is listed in NodePilot-1.2.26.SHA256SUMS.txt, the publisher certificate included.
Get-FileHash .\NodePilot-1.2.26.zip -Algorithm SHA256The installers and the standalone switcher are Authenticode-signed with the NodePilot release certificate:
Subject CN=NodePilot Release Signing
Thumbprint 277EAB317A581C88302CE92BE805938C86B4650D
Compare that thumbprint against nodepilot-release-signing.cer, attached here:
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint
$sig = Get-AuthenticodeSignature .\NodePilot-Server-Setup-1.2.26.exe
$sig.SignerCertificate.Subject
$sig.SignerCertificate.ThumbprintGet-AuthenticodeSignature reports Status: UnknownError, and that is the expected result — the certificate is self-signed, so its root is in nobody's trust store and no chain can be built. What carries the meaning is the pair above: the signer's subject, and a thumbprint equal to the certificate shipped alongside. The checksum file proves the download is intact; the thumbprint is what proves who built it.
SmartScreen
Windows SmartScreen warns on first run of a downloaded installer — "Windows protected your PC". That is expected and unrelated to the signature: the release certificate is self-signed and carries no reputation, so signing does not silence it. Choose More info → Run anyway. SmartScreen ignores antivirus exclusion lists entirely; see the deployment guide for the full explanation.
NodePilot 1.2.25
Four defects from running the Engine Switcher against a real System Center Orchestrator installation, and from installing NodePilot with the GUI setup rather than the deployment scripts. Two of them ended a switch that had already started services.
Fixed
A stalled SCOrch reconciliation no longer terminates the Engine Switcher. The switch ran the workload reconciliation under its own deadline. When that deadline expired, the resulting cancellation passed straight through the coordinator — whose error handling excluded every OperationCanceledException — and then out of an async void command handler, which had no handler at all:
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Threading.Tasks.TaskCanceledException: A task was canceled.
at ScorchRunbookReconciler.ReconcileAsync(...)
at SwitchCoordinator.SwitchAsync(...)
at AsyncCommand.Execute(Object parameter)
The window disappeared: no dialog, no fail-closed cleanup, and the target services left running — so the next start reported the other engine as active. The exclusion was guarding against something that could not happen, because the coordinator is invoked with CancellationToken.None and the only cancellation was its own deadline.
A deadline is now reported like any other failure and names the runbooks or jobs that did not settle, the cleanup runs, and nothing escaping a command can take the process down. A caller's own cancellation still propagates.
A listed SCOrch runbook that finishes quickly no longer blocks the switch. Verification required every listed runbook to be running at the same moment. Only a long-lived monitor runbook can satisfy that; an ordinary runbook completed within seconds, left the active job set, and was counted as missing until the deadline expired — which is what reached the crash above.
A runbook now settles once its job is running, or once the job this switch started has finished. Unlisted jobs are rejected exactly as before.
The server installer fills in the Engine Switcher's server URL. Switching to NodePilot failed with No server URL configured. Run 'np config set server <URL>' or pass --server. because serverUrl shipped empty and np fell back to its own configuration — which is per-user and DPAPI-protected, so the account that ran setup is not the account that runs the switcher.
The installer writes the hostname and HTTPS port it just configured into the configuration next to the executable, and the switcher passes --server on every call. A machine-wide configuration under %ProgramData%\NodePilot\EngineSwitcher still wins at load time and is left untouched.
The GUI server setup puts np on the machine PATH again. np.exe sat in <install>\tools\np, np in a console reported "not found", and the installation had reported success.
Setup runs Install-NodePilot.ps1 from its own payload, and that payload was missing MachinePath.ps1 — the helper the PATH block loads. Because the block is wrapped in try/catch, the failure degraded to a warning nobody reads. Installations driven by the deployment-scripts zip carry the helper in their own list and were never affected, which is why only double-clicked setups were.
The payload carries it now. Install and update also read the machine PATH back after writing it, and the installer states the outcome in its closing summary — including that an already-open console keeps the environment it started with, so np needs a new one.
Existing installations do not need a repair beyond installing this version, but an already-open console still will not see the entry.
Verifying this download
Every file below is listed in NodePilot-1.2.25.SHA256SUMS.txt, the publisher certificate included.
Get-FileHash .\NodePilot-1.2.25.zip -Algorithm SHA256The installers and the standalone switcher are Authenticode-signed with the NodePilot release certificate:
Subject CN=NodePilot Release Signing
Thumbprint 277EAB317A581C88302CE92BE805938C86B4650D
Compare that thumbprint against nodepilot-release-signing.cer, attached here:
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint
$sig = Get-AuthenticodeSignature .\NodePilot-Server-Setup-1.2.25.exe
$sig.SignerCertificate.Subject
$sig.SignerCertificate.ThumbprintGet-AuthenticodeSignature reports Status: UnknownError, and that is the expected result — the certificate is self-signed, so its root is in nobody's trust store and no chain can be built. What carries the meaning is the pair above: the signer's subject, and a thumbprint equal to the certificate shipped alongside. The checksum file proves the download is intact; the thumbprint is what proves who built it.
SmartScreen
Windows SmartScreen warns on first run of a downloaded installer — "Windows protected your PC". That is expected and unrelated to the signature: the release certificate is self-signed and carries no reputation, so signing does not silence it. Choose More info → Run anyway. SmartScreen ignores antivirus exclusion lists entirely; see the deployment guide for the full explanation.
NodePilot 1.2.24
A single fix, found by running the Engine Switcher against a System Center Orchestrator installation whose Web API build cannot answer the job query the switcher was sending.
Fixed
The Engine Switcher no longer uses the OData in operator to read active SCOrch jobs. Switching to System Center Orchestrator aborted after the SCOrch services had already been started, and the fail-closed cleanup then stopped everything again:
SCOrch returned a malformed ScorchJob response from
http://localhost:81/api/jobs?$filter=Status in ('Pending','Running'):
Expected depth to be zero at the end of the JSON payload. BytePositionInLine: 68
Body: {"@odata.context":"http://localhost:81/api/$metadata#Jobs","value":[
The body really was 68 bytes. Some Web API builds accept that filter, begin the response, and then abort it before the first job. The jobs endpoint returns a deferred collection, so the query runs only while the serializer is already writing value — by then the response is committed and the failure can no longer be reported, which leaves a semantically truncated payload under HTTP 200. api/runbooks and api/runbookServers answered normally on the same installation, so nothing was wrong with the port, the authentication or the database.
The shipped activeJobsPath now reads:
api/jobs?$select=Id,RunbookId,Status&$filter=Status eq 'Pending' or Status eq 'Running'
eq/or is equivalent and works across builds. $select additionally limits the response to the three fields the switcher actually reads, which cut the body from 8832 to 1482 characters on the affected installation. The set of job states is unchanged, and no $top was added — the service caps $top at 50 while the reconciler needs every active job.
An installation that already has a configuration file keeps its own value: activeJobsPath is read verbatim, so update engine-switcher.json by hand, or replace it with the template shipped in this release. docs/engine-switcher.md now records why the in operator is avoided, so it does not get reintroduced.
Verifying this download
Every file below is listed in NodePilot-1.2.24.SHA256SUMS.txt, the publisher certificate included.
Get-FileHash .\NodePilot-1.2.24.zip -Algorithm SHA256The installers and the standalone switcher are Authenticode-signed with the NodePilot release certificate:
Subject CN=NodePilot Release Signing
Thumbprint 277EAB317A581C88302CE92BE805938C86B4650D
Compare that thumbprint against nodepilot-release-signing.cer, attached here:
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint
$sig = Get-AuthenticodeSignature .\NodePilot-Server-Setup-1.2.24.exe
$sig.SignerCertificate.Subject
$sig.SignerCertificate.ThumbprintGet-AuthenticodeSignature reports Status: UnknownError, and that is the expected result — the certificate is self-signed, so its root is in nobody's trust store and no chain can be built. What carries the meaning is the pair above: the signer's subject, and a thumbprint equal to the certificate shipped alongside. The checksum file proves the download is intact; the thumbprint is what proves who built it.
SmartScreen
Windows SmartScreen warns on first run of a downloaded installer — "Windows protected your PC". That is expected and unrelated to the signature: the release certificate is self-signed and carries no reputation, so signing does not silence it. Choose More info → Run anyway. SmartScreen ignores antivirus exclusion lists entirely; see the deployment guide for the full explanation.
NodePilot 1.2.23
The Service Switcher became the Engine Switcher in more than its window title, and the utility got three fixes that came out of running it against a real System Center installation.
Changed
The Service Switcher is now the Engine Switcher everywhere. The utility had been called Engine Switcher in its own window for a while, but only there. Everything underneath still said Service Switcher: the project and its namespace, the assembly, the test project, the documentation, the build artifacts, and every name it writes to disk.
All of it was renamed, persisted names included:
| Before | Now | |
|---|---|---|
| Configuration file | service-switcher.json |
engine-switcher.json |
| Data directory | %ProgramData%\NodePilot\ServiceSwitcher |
%ProgramData%\NodePilot\EngineSwitcher |
| Theme registry key | HKCU\Software\NodePilot\ServiceSwitcher |
HKCU\Software\NodePilot\EngineSwitcher |
| Installation folder | {app}\tools\service-switcher |
{app}\tools\engine-switcher |
| Executable | NodePilot.ServiceSwitcher.exe |
NodePilot.EngineSwitcher.exe |
| Standalone drop | NodePilot-ServiceSwitcher-<version>-win-x64.zip |
NodePilot-EngineSwitcher-<version>-win-x64.zip |
| CLI profile in the template | service-switcher |
engine-switcher |
Upgrading a machine that already ran the switcher takes one manual step. The update places the new files but does not carry the old state over, so the switcher would look for engine-switcher.json while the machine still holds service-switcher.json. After updating:
$old = "$env:ProgramData\NodePilot\ServiceSwitcher"
$new = "$env:ProgramData\NodePilot\EngineSwitcher"
if ((Test-Path $old) -and -not (Test-Path $new)) { Rename-Item -LiteralPath $old -NewName 'EngineSwitcher' }
$config = Join-Path $new 'service-switcher.json'
if (Test-Path $config) { Rename-Item -LiteralPath $config -NewName 'engine-switcher.json' }A configuration that sits next to the executable needs the same rename in {app}\tools\engine-switcher. The old {app}\tools\service-switcher directory and the previous Start-menu entry are left behind by the update and can be deleted. The theme preference starts over once. docs/engine-switcher.md carries the full migration section.
Names that describe Windows services — ServiceDiscovery, IServiceControlGateway, ServiceRuntimeState — were deliberately left alone. They are about Windows services, not about the old product name.
Fixed
Windows paths in the configuration no longer have to be escaped, and a broken file is caught before the switch. JSON escapes every backslash, so a configuration edited by hand on the machine — "workflowAllowListPath": "D:\Scripts\runbooks.txt" — aborted the switch with 'S' is an invalid escapable character within a JSON string. Worse, the system check had reported success and both engine buttons stayed enabled, because the file was only read once a switch was already under way.
The three path properties now accept single backslashes and restore a UNC prefix written with one backslash per separator, so "\\fileserver\automation\runbooks.txt" loads as UNC. The escaped form is unchanged and remains the correct one; the tolerance is limited to those three properties, and a stray backslash anywhere else — in profile, in a URL — is still an error rather than something silently accepted.
The configuration is now loaded at every system check, not only when a switch starts. An unusable file is reported in the activity history with its path and disables both engine buttons; correcting the file releases them at the next refresh, without restarting the switcher.
A failed SCOrch query no longer stops every managed service. Deactivating the source workload begins by reading the active jobs, but the fail-closed cleanup was armed before that read. A web service that answered a job query with a truncated body under HTTP 200 therefore took down all four managed services, although nothing had been changed yet.
The cleanup is now armed by the first job that is actually stopped. A failure up to that point leaves every service exactly as it was; from the first stopped job onwards the switch is a mutation like any other and the existing fail-closed behaviour is unchanged.
An unreadable SCOrch response now says which call broke. The failure above surfaced as a bare Expected depth to be zero at the end of the JSON payload — no request URL, no status, no hint of what the server had actually sent. A response that is not a readable collection, whether truncated, an error page or an unsupported $filter, is now reported with the request URL and the beginning of the body.
The standalone Engine Switcher ships with its configuration template. The release drop was the bare executable, while the template stayed behind in the server artifact — so the one audience the standalone copy exists for, a machine with no NodePilot installation to take a template from, had nothing to configure and every switch aborted with "Switcher configuration not found". It is now a zip carrying the executable and the template together.
cliPath became optional and resolves through the installation marker HKLM\SOFTWARE\NodePilot\Server and then the machine PATH, because a relative path only points at np.exe while the configuration sits in the install directory. A missing configuration now names every location that was searched instead of only the machine-wide one, which is the single location nothing ever creates.
Verifying this download
Every file below is listed in NodePilot-1.2.23.SHA256SUMS.txt, the publisher certificate included.
Get-FileHash .\NodePilot-1.2.23.zip -Algorithm SHA256The installers and the standalone switcher are Authenticode-signed with the NodePilot release certificate:
Subject CN=NodePilot Release Signing
Thumbprint 277EAB317A581C88302CE92BE805938C86B4650D
Compare that thumbprint against nodepilot-release-signing.cer, attached here:
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint
$sig = Get-AuthenticodeSignature .\NodePilot-Server-Setup-1.2.23.exe
$sig.SignerCertificate.Subject
$sig.SignerCertificate.ThumbprintGet-AuthenticodeSignature reports Status: UnknownError, and that is the expected result — the certificate is self-signed, so its root is in nobody's trust store and no chain can be built. What carries the meaning is the pair above: the signer's subject, and a thumbprint equal to the certificate shipped alongside. The checksum file proves the download is intact; the thumbprint is what proves who built it.
SmartScreen
Windows SmartScreen warns on first run of a downloaded installer — "Windows protected your PC". That is expected and unrelated to the signature: the release certificate is self-signed and carries no reputation, so signing does not silence it. Choose More info → Run anyway. SmartScreen ignores antivirus exclusion lists entirely; see the deployment guide for the full explanation.
NodePilot 1.2.22
An update can now repair an install directory it would previously have refused, and the header's help button reaches the documentation on a development server.
Fixed
An update no longer dead-ends on an install directory that someone widened. C:\Program Files\NodePilot must not grant write access to anyone but SYSTEM, Administrators and TrustedInstaller — the service executes the binaries that live there, so write access is code execution as the service account. The installer both applies that DACL and verifies it; the updater only ever verified it, and refused when it did not hold.
That refusal came after the binaries had already been replaced, so it cost a rollback and left the host on the old version with no route forward, over a condition an update can simply fix. Nothing reads that ACL between updates, so an entry granted after the installation was laid down stays invisible until the next one — and then surfaces at the worst possible moment.
The updater now repairs the directory once, with the same protected DACL the installer applies: inheritance dropped, every explicit ACE wiped, owner forced back to Administrators. Then it checks again. That second check is what keeps this a hardening step rather than a bypass — if the directory is still writable by an untrusted principal, the update fails and rolls back exactly as before. The rule itself is unchanged.
Verified end to end against a real installation: a host was put into the failure state deliberately, and the update repaired it, completed with exit 0, and left the directory carrying SYSTEM and Administrators only.
The help button reaches the documentation on a development server. In production the API serves the documentation site from wwwroot/docs at /docs, and that path was always served correctly. On a development server nothing answered it, so /docs/ fell through to the application's own index.html and the router replied with its not-found screen — the question mark in the header led into an error page.
The app dev server now proxies /docs to the documentation dev server, which serves under /docs/ itself so its entry module stays inside the proxied prefix. Development-only; an installation is unaffected.
Verifying this download
Every file below is listed in NodePilot-1.2.22.SHA256SUMS.txt, the publisher certificate included.
Get-FileHash .\NodePilot-1.2.22.zip -Algorithm SHA256The installers and the standalone switcher are Authenticode-signed with the NodePilot release certificate:
Subject CN=NodePilot Release Signing
Thumbprint 277EAB317A581C88302CE92BE805938C86B4650D
Compare that thumbprint against nodepilot-release-signing.cer, attached here:
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint
$sig = Get-AuthenticodeSignature .\NodePilot-Server-Setup-1.2.22.exe
$sig.SignerCertificate.Subject
$sig.SignerCertificate.ThumbprintGet-AuthenticodeSignature reports Status: UnknownError, and that is the expected result — the certificate is self-signed, so its root is in nobody's trust store and no chain can be built. What carries the meaning is the pair above: the signer's subject, and a thumbprint equal to the certificate shipped alongside. The checksum file proves the download is intact; the thumbprint is what proves who built it.
SmartScreen
Windows SmartScreen warns on first run of a downloaded installer — "Windows protected your PC". That is expected and unrelated to the signature: the release certificate is self-signed and carries no reputation, so signing does not silence it. Choose More info → Run anyway. SmartScreen ignores antivirus exclusion lists entirely; see the deployment guide for the full explanation.
NodePilot 1.2.0
The first release that keeps running when something underneath it stops: a wedged database, a
network share that disappears, a trigger whose source dies. It also carries the 1.1.2 setup fixes,
which only ever shipped as a server installer attached to the 1.1.1 release.
Downloads
| Asset | For |
|---|---|
NodePilot-Server-Setup-1.2.0.exe |
Windows service — the production rollout. Wizard, or /VERYSILENT with an answer file. Carries the signed artifact inside; nothing else to download. |
NodePilot-Desktop-Setup-1.2.0.exe |
Desktop app — one machine. Bundles a local PostgreSQL and the .NET runtime, installs both as services, opens a native window. Offline, no prerequisites. |
NodePilot-1.2.0.zip + .manifest.json + .manifest.json.p7s |
The scripted path, for automation and troubleshooting: deploy/Install-NodePilot.ps1 -ArtifactPath …. This is exactly what the setup runs. |
NodePilot-1.2.0.SHA256SUMS.txt |
Checksums over everything above. |
nodepilot-release-signing.cer |
Public signing certificate (see below). |
| Source code (zip / tar.gz) | The full source of this tag, Apache-2.0. |
Surviving a database outage
When the database service wedges, NodePilot used to wedge with it: the process stayed up and
stopped answering. Every DbContext carried EnableRetryOnFailure(5) over a 120-second command
timeout, so a single query against a hung server burned roughly twelve minutes before an exception
surfaced — and token validation ran ahead of every authenticated request, so every request parked
in that budget, not just the slow one. Meanwhile the header's status pill polled /healthz/live,
which is 200 by design: the indicator stayed green while the product stood still.
A process-wide availability breaker now owns that state, with one rule everything else follows
from: after boot, only the recovery probe may publish Available; interceptors may only degrade.
The API answers 503 DATABASE_UNAVAILABLE immediately (with Retry-After), background services
park instead of throwing, the engine pauses before starting a new step rather than finalising a
failed one as succeeded, and recovery is automatic once the probe sees two clean reads.
Two measurements shaped it, both of which rule out the obvious implementation. Npgsql reports a
connect failure and a command timeout in an identical exception shape, so classification is by
context rather than form. And a command timeout never opens the breaker directly — a slow query is
not an outage; it only arms the probe. New health surface: /healthz/ready fails fast for a load
balancer, /healthz/database always answers 200 with a status the SPA renders as a banner and a
traffic light. See docs/adr/0011-database-availability.md.
Triggers that come back on their own
A fileWatcherTrigger on a UNC share stopped firing permanently when the share went away. Windows
tears down the watch handle, FileSystemWatcher raises Error, and the handler only logged it —
the dead source stayed registered with a matching config hash, so nothing evicted it and nothing
re-added it. Bringing the share back changed nothing; only a restart, a config edit, or disabling
and re-enabling the workflow helped.
ITriggerSource now answers Health, contractually a pure in-memory read because the orchestrator
evaluates it for every trigger inside its five-second pass. An unhealthy source is evicted and
routed back through the existing registration path, which already retries with exponential backoff
up to five minutes, indefinitely. A FileSystemWatcher cannot be re-armed in place — on the
re-issue failure path the runtime disposes the handle without clearing EnableRaisingEvents, so
the property still reads true on a corpse — so a fresh instance is created. A buffer overflow is
deliberately not a fault: the runtime re-issues the read, and evicting there would only flap.
Silence was the other half of the problem. A registration that keeps failing was caught per trigger
and retried, so every operator-facing surface kept showing green while a drop folder went unwatched.
The new trigger-unhealthy system-alert policy reports it, firing past 60 seconds — beyond what a
share restart explains.
Installing on a host that has never seen NodePilot
Three field reports, one theme: the setup checked everything except what actually stopped the
install.
- All nine prerequisite rows green, then a rollback.
Install-NodePilot.ps1verifies the
artifact's signature against the pinned publisher, which is self-signed — so every first
installation failed there, with a German certificate-chain message that names neither the
artifact nor the remedy. The remedy had shipped and been unreachable the whole time: the
readiness page had no row for the signer, and the wizard wrotetrustArtifactSigner: falseas a
constant, so it could not be asked for from the interface at all. - The trust decision itself is now unnecessary. Chain validation only ever confirmed what the
thumbprint pin already established — the publisher is its own trust anchor — at the price of a
permanent, machine-wide change on every target. The chain is no longer validated; what it
enforced on our behalf is now enforced explicitly (KeyUsage must permit signing, EKU must be
code-signing, validity window checked against the signing time). - 32-bit .NET hosts. NodePilot ships as win-x64. A machine whose only
dotnet.exewas 32-bit
passed the runtime row and then failed to start the service. The pre-flight now reads the machine
type out of the PE header —dotnet --infois localised and its architecture line is unfindable
on a German server — and reports the host red by path and architecture.
Also: the empty-certificate-field fix that was cut as 1.1.2, fix checkboxes that had no clickable
place on the page, and a setup log that narrated steps it had not taken.
Smaller, but visible
- AI buttons only when there is an LLM. The designer assistant, the script-editor generate
button and the AI workflow generation button rendered unconditionally and failed with a 503 when
no endpoint was configured. All three are now gated on a shared capability query, and the
script-editor button is additionally hidden for Viewers, matching its Admin/Operator endpoint. waitForConditioncan finally probe localhost. ThehttpOkprobe ran through the restApi
SSRF guard before consulting its own allow-list, so the shipped["localhost"]default — the one
documented as "check whether my own service is up" — was inert on every non-Development instance.
The probe path now has its own validation; link-local and cloud-metadata addresses stay blocked
for both probe types.- Locale-dependent formatting is routed through one module, so dates and numbers no longer differ
between components.
Under the floor
A coherence pass that leaves no user-visible trace but is worth knowing about: controller DTOs
moved out of controller files, folder RBAC routed through one authorization gate, LLM error mapping
and the agentic tool loop deduplicated, scheduler telemetry names and the DPAPI session entropy
single-sourced, and the ExternalIdentityResolutionController removed. Three new guard tests pin
the dependency direction, endpoint parity across API/CLI/MCP, and the deployment template grammar,
so the next drift fails a build instead of a review. Coverage now has exactly one authoritative
gate, in CI. And nanoid moves to 3.3.18 across all three npm trees (GHSA-2v37-7h3g-55p8, build-time
only) — a lockfile refresh, since the range that pulls it in already allowed the fixed version.
Verifying the download
Publisher : CN=NodePilot Release Signing
Thumbprint : 277EAB317A581C88302CE92BE805938C86B4650D
Valid until : 2031-08-02
Get-FileHash .\NodePilot-1.2.0.zip -Algorithm SHA256 # compare with NodePilot-1.2.0.SHA256SUMS.txt
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint # must equal the value aboveBoth installers are Authenticode-signed with that certificate, RFC-3161 timestamped. It is a
self-signed publisher, not a public CA — that is why the thumbprint is published here and why
you should compare it before pinning it. Importing it into LocalMachine\Root is no longer
required for an installation; if you do it anyway, compare the thumbprint first. Expect a
SmartScreen warning; the publisher has no reputation to draw on. If your organisation will not
trust a self-signed publisher, build and sign both artifacts with your own enterprise certificate —
the walkthrough is in docs/deployment-guide.md.
Licensed under Apache-2.0.
NodePilot 1.1.0
The server installation now has a setup you can double-click.
v1.0.1 shipped the artifacts; installing them still meant reading docs/deployment-guide.md,
creating a SQL login by hand, granting the service account access to a certificate's private key,
and getting a dozen Install-NodePilot.ps1 parameters right on the first try. This release adds
NodePilot-Server-Setup, a signed GUI installer that runs those same scripts, checks the host
before it changes anything, and fixes what it can fix itself.
Downloads
| Asset | For |
|---|---|
NodePilot-Server-Setup-1.1.0.exe |
Windows service — the production rollout. Wizard, or /VERYSILENT with an answer file. Carries the signed artifact inside; nothing else to download. |
NodePilot-Desktop-Setup-1.1.0.exe |
Desktop app — one machine. Bundles a local PostgreSQL and the .NET runtime, installs both as services, opens a native window. Offline, no prerequisites. |
NodePilot-1.1.0.zip + .manifest.json + .manifest.json.p7s |
The scripted path, for automation and troubleshooting: deploy/Install-NodePilot.ps1 -ArtifactPath …. This is exactly what the setup runs. |
SHA256SUMS.txt |
Checksums over everything above. |
nodepilot-release-signing.cer |
Public signing certificate (see below). |
Building from source stays fully supported and is documented in the README.
What the server setup does for you
- Checks the host first, and says why. Administrator rights, the ports (naming HTTP.SYS when
the kernel — not IIS — is holding 443), the .NET runtime, the TLS certificate, the service
account, and whether the database is reachable at all. Nothing is changed until every blocking
item is green. - Creates the database access it needs. SQL Server: the login for the service identity and the
database, withdb_owneron it. PostgreSQL: the role and the database, from superuser
credentials you supply on the page or in the answer file. Both check first and skip what already
exists, and both refuse politely — with the missing privilege named — when the running account
may not do it. This used to be a block of SQL the wizard printed for you to run somewhere else. - Picks the certificate from a list instead of asking for a thumbprint. An expired certificate
blocks the install; one whose name does not match the public hostname is a warning you can
override, because split-DNS deployments are legitimate. - Grants the private key to the service account, sets the ACLs and the firewall rule, installs
the service, starts it, and waits for/healthz/readybefore calling it done. - Shows the first-login details on the finish page — the setup token and, if you enabled it,
the external-trigger API key. That page is the only place they are ever shown. - Runs unattended for SCCM or GPO:
NodePilot-Server-Setup-1.1.0.exe /VERYSILENT /SUPPRESSMSGBOXES /ANSWERFILE=answers.json.
The answer file can create the first administrator and seed the installation from a
configuration backup, so a rollout does not end with someone typing a setup token into a browser. - Upgrades in place. An existing installation keeps its configuration; the service is down for
roughly half a minute.
You still bring two things: a TLS certificate in LocalMachine\My (imported with
MachineKeySet|PersistKeySet, otherwise the private key cannot be granted later) and a database
server whose TLS the NodePilot host trusts. The short version of all of this is the Quick start
at the top of deploy/server/README.md.
Fixes
- Changing the service identity no longer bricks the installation it replaces. Installing over
an existing instance under a different account (LocalSystem → gMSA) left the identity-bound
secrets readable by nobody. The secrets are handed over to the new identity, and a failed switch
now rolls the directory ACL back instead of leaving a half-migrated install. - An update leaves the service running. It used to stop the service and hand it back stopped.
- The uninstaller waits for the service process instead of orphaning a still-running one.
- The service waits for its database at boot rather than crash-looping past it, in both the
server and desktop postures. AllowedHostsderived from the public hostname no longer excludeslocalhost, which broke the
installer's own health probe.- Registry ACLs are addressed by path, not by a literal that drifted.
- The global query-error toast no longer fires every fifteen seconds while a background refresh
retries.
Verifying the download
Publisher : CN=NodePilot Release Signing
Thumbprint : 277EAB317A581C88302CE92BE805938C86B4650D
Valid until : 2031-08-02
Get-FileHash .\NodePilot-1.1.0.zip -Algorithm SHA256 # compare with SHA256SUMS.txt
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint # must equal the value above
Import-Certificate -FilePath .\nodepilot-release-signing.cer -CertStoreLocation Cert:\LocalMachine\RootBoth installers are Authenticode-signed with that same certificate, RFC-3161 timestamped. It is a
self-signed publisher, not a public CA — that is why the thumbprint is published here and why
you should compare it before importing anything into LocalMachine\Root. Expect a SmartScreen
warning; it has no reputation to draw on. If your organisation will not trust a self-signed
publisher, build and sign both artifacts with your own enterprise certificate — the walkthrough is
in docs/deployment-guide.md.
Also in this release
Roughly fifty commits since v1.0.1. Beyond the setup itself: the installer pre-flight was
extracted so it can run without side effects (the wizard and the scripts now check the same way),
the setup's PowerShell adapter and the deployment templates gained contract tests, and the
deployment documentation was corrected where it named a build parameter that no longer exists.
Licensed under Apache-2.0.
NodePilot 1.0.0
First public release — agentless Windows workflow orchestration, a modern, open replacement for Microsoft System Center Orchestrator.
Design, schedule, debug, and observe multi-step automation in your browser: run PowerShell, file/registry/service operations, REST calls, SQL and more across your Windows estate over WinRM — no agents on the targets.
Highlights
- Visual workflow designer (React Flow) with live execution status and a step debugger
- 27 activity types + 6 triggers, sub-workflows, per-step retries, conditional edges
- Agentless remote execution via WinRM / PowerShell SDK
- Scheduling (Quartz), webhooks (HMAC), file/database/event-log triggers
- REST API,
npCLI, and an opt-in MCP server for AI-driven workflow editing - JWT auth (Admin/Operator/Viewer), audit log, alerting, system backup/restore
- PostgreSQL or SQL Server; optional HA, LDAP/Windows-SSO, ECS/SIEM logging
Licensed under Apache-2.0.