#!/usr/bin/env ruby # frozen_string_literal: true # # Generates importable n8n workflow JSON from component pieces. # Run: ruby scripts/build-n8n-workflows.rb # Output: scripts/n8n-workflow-receiver.json # scripts/n8n-workflow-reporter.json # # Before running: # 1. Complete Task 7 and verify DATATABLE_TYPE / DATATABLE_TYPE_VERSION below. # 2. After Task 10 (credentials set up), replace REPLACE_WITH_*_CREDENTIAL_ID # in the generated JSON and re-run to regenerate clean files before import. require 'json' require 'securerandom' SCRIPTS_DIR = File.dirname(__FILE__) # Verify against the exported scratch-workflow JSON from Task 7. DATATABLE_TYPE = 'n8n-nodes-base.dataTable' DATATABLE_TYPE_VERSION = 1 evaluate_sources_js = File.read(File.join(SCRIPTS_DIR, 'n8n-evaluate-sources.js')) build_email_js = File.read(File.join(SCRIPTS_DIR, 'n8n-build-email.js')) # ── Workflow 1: Receiver ────────────────────────────────────────────────────── receiver = { name: 'Backup Monitor — Receiver', nodes: [ { parameters: { httpMethod: 'POST', path: 'backup-notify', authentication: 'headerAuth', responseMode: 'responseNode', options: {} }, id: SecureRandom.uuid, name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [240, 300], webhookId: SecureRandom.uuid, credentials: { httpHeaderAuth: { id: 'REPLACE_WITH_BACKUP_CREDENTIAL_ID', name: 'backup-monitor-token' } } }, { parameters: { resource: 'row', operation: 'insert', dataTableId: { '__rl' => true, mode: 'list', value: 'REPLACE_WITH_DATATABLE_ID', cachedResultName: 'backup_runs' }, columns: { mappingMode: 'defineBelow', value: { timestamp: '={{ $json.body.timestamp }}', machine: '={{ $json.body.machine }}', destination: '={{ $json.body.destination }}', status: '={{ $json.body.status }}', bytes_transferred: '={{ $json.body.bytes_transferred }}', snapshot_id: '={{ $json.body.snapshot_id }}', log_path: '={{ $json.body.log_path }}', notes: '={{ $json.body.notes }}' } } }, id: SecureRandom.uuid, name: 'Insert to backup_runs', type: DATATABLE_TYPE, typeVersion: DATATABLE_TYPE_VERSION, position: [520, 300] }, { parameters: { respondWith: 'json', responseBody: '={{ JSON.stringify({ ok: true }) }}', options: { responseCode: 200 } }, id: SecureRandom.uuid, name: 'Respond 200', type: 'n8n-nodes-base.respondToWebhook', typeVersion: 1.1, position: [800, 300] } ], connections: { 'Webhook' => { main: [[{ node: 'Insert to backup_runs', type: 'main', index: 0 }]] }, 'Insert to backup_runs' => { main: [[{ node: 'Respond 200', type: 'main', index: 0 }]] } }, active: false, settings: { executionOrder: 'v1' }, pinData: {}, meta: { templateCredsSetupCompleted: true }, tags: [] } # ── Workflow 2: Reporter ────────────────────────────────────────────────────── reporter = { name: 'Backup Monitor — Reporter', nodes: [ { parameters: { rule: { interval: [ { field: 'weeks', weeksInterval: 1, triggerAtDay: [5], triggerAtHour: 8, triggerAtMinute: 0 } ] } }, id: SecureRandom.uuid, name: 'Every Friday 8 AM', type: 'n8n-nodes-base.scheduleTrigger', typeVersion: 1.2, position: [240, 300] }, { parameters: { mode: 'manual', includeOtherFields: false, assignments: { assignments: [ { id: SecureRandom.uuid, name: 'sources', value: '={{ [{"machine":"desktop","destination":"synology"},{"machine":"desktop","destination":"backblaze-b2"},{"machine":"vps-ovh-prod-01","destination":"local"}] }}', type: 'arrayValue' } ] }, options: {} }, id: SecureRandom.uuid, name: 'Set sources', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [460, 300] }, { parameters: { resource: 'row', operation: 'getMany', dataTableId: { '__rl' => true, mode: 'list', value: 'REPLACE_WITH_DATATABLE_ID', cachedResultName: 'backup_runs' }, filters: { conditions: [ { keyName: 'timestamp', condition: 'gt', keyValue: '={{ $now.minus({days: 7}).toISO() }}' } ] }, options: {} }, id: SecureRandom.uuid, name: 'Get backup_runs (last 7d)', type: DATATABLE_TYPE, typeVersion: DATATABLE_TYPE_VERSION, position: [680, 300] }, { parameters: { mode: 'runOnceForAllItems', language: 'javaScript', jsCode: evaluate_sources_js }, id: SecureRandom.uuid, name: 'Evaluate Sources', type: 'n8n-nodes-base.code', typeVersion: 2, position: [900, 300] }, { parameters: { mode: 'runOnceForAllItems', language: 'javaScript', jsCode: build_email_js }, id: SecureRandom.uuid, name: 'Build Email', type: 'n8n-nodes-base.code', typeVersion: 2, position: [1120, 300] }, { parameters: { method: 'POST', url: 'https://app.mailpace.com/api/v1/send', authentication: 'genericCredentialType', genericAuthType: 'httpHeaderAuth', sendBody: true, specifyBody: 'json', jsonBody: '={{ { from: "REPLACE_WITH_MAILPACE_FROM_ADDRESS", to: "jaredmswanson@gmail.com", subject: $json.subject, text_body: $json.body } }}', options: {} }, id: SecureRandom.uuid, name: 'Send via MailPace', type: 'n8n-nodes-base.httpRequest', typeVersion: 4.2, position: [1340, 300], credentials: { httpHeaderAuth: { id: 'REPLACE_WITH_MAILPACE_CREDENTIAL_ID', name: 'mailpace-token' } } } ], connections: { 'Every Friday 8 AM' => { main: [[{ node: 'Set sources', type: 'main', index: 0 }]] }, 'Set sources' => { main: [[{ node: 'Get backup_runs (last 7d)', type: 'main', index: 0 }]] }, 'Get backup_runs (last 7d)' => { main: [[{ node: 'Evaluate Sources', type: 'main', index: 0 }]] }, 'Evaluate Sources' => { main: [[{ node: 'Build Email', type: 'main', index: 0 }]] }, 'Build Email' => { main: [[{ node: 'Send via MailPace', type: 'main', index: 0 }]] } }, active: false, settings: { executionOrder: 'v1' }, pinData: {}, meta: { templateCredsSetupCompleted: true }, tags: [] } File.write(File.join(SCRIPTS_DIR, 'n8n-workflow-receiver.json'), JSON.pretty_generate(receiver)) File.write(File.join(SCRIPTS_DIR, 'n8n-workflow-reporter.json'), JSON.pretty_generate(reporter)) puts 'Generated:' puts " #{SCRIPTS_DIR}/n8n-workflow-receiver.json" puts " #{SCRIPTS_DIR}/n8n-workflow-reporter.json"