feat: add n8n as a second workflow runtime alongside Node-RED#38
feat: add n8n as a second workflow runtime alongside Node-RED#38
Conversation
Solutions whose chain-declared executionEnvironment is N8nV1 are now installed into an embedded n8n child process. Solutions declared NodeRedV1 or with the field empty continue to run on Node-RED exactly as before (no behavior change). The n8n runtime: - Spawns n8n 2.17.4 as a managed child process bound to 127.0.0.1 - Waits for n8n's end-of-boot stdout marker before authenticating - Authenticates via generated worker-internal owner credentials stored at node-red-data/.n8n/owner-credentials.json - Creates workflows with embedded VCC tracking metadata - Activates via POST /rest/workflows/:id/activate with versionId - Deletes via archive-then-delete (n8n 2.17 requirement) A new src/runtime/ directory introduces a Runtime interface with two implementations (node-red, n8n). src/solution.ts, src/vote.ts, src/worker-config.ts, and src/health.ts are now runtime-agnostic. Includes: - Zod override to 3.25.67 to fix n8n api-types discriminated union - test-flows/vcc-1.json sanity flow - DEV_RUNTIME_OVERRIDES and DEV_LOCAL_FLOW_OVERRIDES for local iteration - RUNTIME-N8N.md with architecture, lifecycle, and operational notes Verified end-to-end on mainnet: vcc-1 installed to n8n, scheduled trigger fires every 30s, votes signed and settled on chain, confirmed via VoteSubmitted event on workerNodePallet. package-lock.json has ~43K lines of additions. This is expected from introducing n8n as a dependency; no existing dependencies were removed or downgraded. No unit tests added - branch is verified by end-to-end testing. Test scaffolding is a follow-up.
hejkerooo
left a comment
There was a problem hiding this comment.
I like the idea of running n8n as separate binary - wish we would've done the same for NodeRed if it's possible in current situation, it's something to be considered in future.
However this PR should've been created from this branch #34
It should be stable as of now, not sure why it's not merged yet.
| const raw: string = readFileSync(credentialsPath, 'utf8'); | ||
|
|
||
| try { | ||
| return JSON.parse(raw) as OwnerCredentials; |
There was a problem hiding this comment.
use zod validation for this to make sure that file is compatible with OwnerCredentials
| }; | ||
|
|
||
| writeFileSync(credentialsPath, JSON.stringify(credentials), { mode: 0o600 }); | ||
| logger.info({ credentialsPath }, 'generated new n8n owner credentials'); |
There was a problem hiding this comment.
I would suggest to remove n8n keyword as logger has N8nRuntime context
| let baseUrl: string | null = null; | ||
|
|
||
| /** Generate or load the encryption key n8n uses for its own credentials. */ | ||
| const ensureEncryptionKey = (n8nUserDir: string): string => { |
There was a problem hiding this comment.
| const ensureEncryptionKey = (n8nUserDir: string): string => { | |
| const createEncryptionKey = (n8nUserDir: string): string => { |
| }; | ||
|
|
||
| /** Resolve path to the n8n CLI binary inside node_modules. */ | ||
| const resolveN8nBinary = (): string => { |
There was a problem hiding this comment.
| const resolveN8nBinary = (): string => { | |
| const resolveN8nBinaryPath = (): string => { |
| await deleteAll(); | ||
| // Wipe any leftover installed flows from prior runs across every runtime | ||
| // that started successfully, so reconcile starts from a clean slate. | ||
| for (const rt of ALL_RUNTIMES) { |
There was a problem hiding this comment.
can we extract it as a function in runtime.ts?
| @@ -0,0 +1,152 @@ | |||
| # n8n runtime | |||
There was a problem hiding this comment.
We should move this to docs/
| @@ -1,17 +1,9 @@ | |||
| import { type ApiPromise } from '@polkadot/api'; | |||
There was a problem hiding this comment.
This file will be reviewed once merge with feat/auth occurs
| @@ -0,0 +1,24 @@ | |||
| { | |||
| * directly. Returns null if the identifier matches nothing known. | ||
| */ | ||
| const resolveSolutionNamespace = async ( | ||
| noderedId: string | undefined, |
There was a problem hiding this comment.
can we extract it to interface? in case someone wanted to fetch with solutionId right now it would be
resolveSolutionNamespace(undefined, 'some-value');which is not clear imo
| // Idempotency: same solutionId at the same workLogic is a no-op. | ||
| const existingWorkflowId: string | undefined = installedBySolutionId.get(input.solutionId); | ||
|
|
||
| if (existingWorkflowId != null) { |
There was a problem hiding this comment.
since it's shared behavior, can we create a function that is shared with NR and N8N? some sort of default implementation
Addresses all review comments from #38: - Move RUNTIME-N8N.md to docs/runtime-n8n.md - Extract runtime bootstrap loops into startAllRuntimes and deleteAllFromAllRuntimes helpers in registry.ts - Add getHealth() to the Runtime interface; remove NR special-casing in health.ts (which is now pure iteration over ALL_RUNTIMES) - Replace resolveSolutionNamespace and resolveSolutionDetails awkward (noderedId?, solutionId?) signatures with a discriminated SolutionIdentifier type that makes intent explicit at call sites Also cleans up lint issues surfaced during the refactor: nullish coalescing in red.ts, unused sleep import, Promise constructor param naming, type assertions in the flow-parse path. No behavior change. TypeScript and eslint pass clean.
| }, timeoutMs); | ||
| }); | ||
|
|
||
| await Promise.race([n8nReadyPromise, timeout]); |
There was a problem hiding this comment.
need to clear timeout, when n8nReadyPromise are return before timeout
Solutions whose chain-declared executionEnvironment is N8nV1 are now
installed into an embedded n8n child process. Solutions declared
NodeRedV1 or with the field empty continue to run on Node-RED exactly
as before (no behavior change).
The n8n runtime:
at node-red-data/.n8n/owner-credentials.json
A new src/runtime/ directory introduces a Runtime interface with two
implementations (node-red, n8n). src/solution.ts, src/vote.ts,
src/worker-config.ts, and src/health.ts are now runtime-agnostic.
Includes:
Verified end-to-end on mainnet: vcc-1 installed to n8n, scheduled
trigger fires every 30s, votes signed and settled on chain, confirmed
via VoteSubmitted event on workerNodePallet.
package-lock.json has ~43K lines of additions. This is expected from
introducing n8n as a dependency; no existing dependencies were removed
or downgraded.
No unit tests added - branch is verified by end-to-end testing.
Test scaffolding is a follow-up.