#!/usr/bin/env node /** * Emulated N8N License Patcher * Replaces minified LicenseManager with clean implementation */ const fs = require('fs'); const { execSync } = require('child_process'); class EmulatedLicensePatcher { constructor() { this.installations = []; } findN8NInstallations() { console.log('šŸ” Finding n8n installations...'); const searchPaths = [ // Current directory and parent (for local installs) './node_modules/@n8n_io/license-sdk/dist/LicenseManager.js', '../node_modules/@n8n_io/license-sdk/dist/LicenseManager.js', '../../node_modules/@n8n_io/license-sdk/dist/LicenseManager.js', ]; // Try to find pnpm installations try { const pnpmPaths = execSync('find . -path "*/node_modules/.pnpm/@n8n_io+license-sdk*/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js" 2>/dev/null || true', { encoding: 'utf8' }).trim(); if (pnpmPaths) { pnpmPaths.split('\n').forEach(path => path.trim() && searchPaths.push(path)); } } catch (e) {} // Try to find npm global installation try { const globalPath = execSync('npm root -g', { encoding: 'utf8' }).trim(); searchPaths.push(`${globalPath}/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js`); } catch (e) {} // Try to find where n8n is installed globally try { const n8nPath = execSync('which n8n', { encoding: 'utf8' }).trim(); if (n8nPath) { const realPath = fs.realpathSync(n8nPath); const licenseSDKPath = realPath.replace(/bin\/n8n$/, 'node_modules/@n8n_io/license-sdk/dist/LicenseManager.js'); searchPaths.push(licenseSDKPath); } } catch (e) {} // Try to find via yarn global try { const yarnGlobalDir = execSync('yarn global dir', { encoding: 'utf8' }).trim(); searchPaths.push(`${yarnGlobalDir}/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js`); } catch (e) {} // Try common nvm paths (cross-platform) const os = require('os'); const homeDir = os.homedir(); try { // Find nvm node versions const nvmDir = process.env.NVM_DIR || `${homeDir}/.nvm`; if (fs.existsSync(nvmDir)) { const versionsDir = `${nvmDir}/versions/node`; if (fs.existsSync(versionsDir)) { const versions = fs.readdirSync(versionsDir); versions.forEach(version => { searchPaths.push(`${versionsDir}/${version}/lib/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js`); }); } } } catch (e) {} // Try Windows AppData paths if (process.platform === 'win32') { const appData = process.env.APPDATA || `${homeDir}/AppData/Roaming`; searchPaths.push(`${appData}/npm/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js`); } // Try macOS/Linux common paths if (process.platform !== 'win32') { searchPaths.push('/usr/local/lib/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js'); searchPaths.push('/usr/lib/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js'); } // Try Docker container paths (when running inside containers) searchPaths.push('/usr/local/lib/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js'); searchPaths.push('/usr/local/bin/node_modules/n8n/node_modules/@n8n_io/license-sdk/dist/LicenseManager.js'); // Remove duplicates and filter existing paths const uniquePaths = [...new Set(searchPaths)]; for (const path of uniquePaths) { if (fs.existsSync(path)) { this.installations.push({ path: path, backupPath: path + '.original' }); } } console.log(`Found ${this.installations.length} installation(s):`); this.installations.forEach((inst, i) => { const status = fs.existsSync(inst.backupPath) ? '(has backup)' : '(no backup)'; console.log(` ${i + 1}. ${inst.path} ${status}`); }); return this.installations; } generateCleanLicenseManager() { return `"use strict"; // Emulated LicenseManager that bypasses all validation const AUTORENEWAL_INTERVAL = 15 * 60 * 1000; class LicenseManager { constructor(config) { this.config = config; this.expirySoonCallbackFired = false; this.isShuttingDown = false; this.initializationPromise = null; this.deviceFingerprint = null; this.currentFeatures = {}; // Mock license certificate this.licenseCert = { consumerId: "emulated-consumer", version: 1, tenantId: config.tenantId || 1, renewalToken: "emulated-token", deviceLock: false, deviceFingerprint: "", createdAt: new Date("2025-01-01"), issuedAt: new Date("2025-01-01"), expiresAt: new Date("2030-01-01"), terminatesAt: new Date("2030-01-01"), planName: "Enterprise", entitlements: [{ id: "emulated-entitlement", productId: "emulated-product", productMetadata: { terms: { isMainPlan: true }, planName: "Enterprise" }, features: { "feat:sharing": true, "feat:ldap": true, "feat:saml": true, "feat:variables": true, "feat:external-secrets": true, "feat:workflow-history": true, "feat:binary-data-s3": true, "feat:multiple-main-instances": true, "feat:source-control": true, "feat:advanced-permissions": true, "feat:debug-in-editor": true, "feat:showNonProdBanner": false, "quota:users": -1, "quota:activeWorkflowTriggers": -1, "quota:variables": -1 }, featureOverrides: {}, validFrom: new Date("2025-01-01"), validTo: new Date("2030-01-01"), isFloatable: false }], detachedEntitlementsCount: 0, managementJwt: "", isEphemeral: true }; this.updateCurrentFeatures(); // Setup logger if (config.logger) { this.logger = config.logger; } else { this.logger = { error: () => console.log("ERROR:", ...arguments), warn: () => console.log("WARN:", ...arguments), info: () => console.log("INFO:", ...arguments), debug: () => console.log("DEBUG:", ...arguments) }; } } get isInitialized() { return !!this.initializationPromise; } log(message, level) { this.logger[level](\`[license SDK] \${message}\`); } async initialize() { if (this.initializationPromise) { return this.initializationPromise; } this.initializationPromise = this._doInitialization(); return await this.initializationPromise; } async _doInitialization() { this.deviceFingerprint = await this.computeDeviceFingerprint(); this.log(\`initializing for deviceFingerprint \${this.deviceFingerprint}\`, "debug"); this.log("Emulated license manager initialized with Enterprise features", "info"); } async computeDeviceFingerprint() { if (this.config.deviceFingerprint && typeof this.config.deviceFingerprint === "function") { return await this.config.deviceFingerprint(); } return "emulated-device-fingerprint"; } // All validation methods return true isValid(logErrors = true) { return true; } hasFeatureEnabled(feature, validateLicense = true) { // Special case: this feature should be FALSE for licensed instances if (feature === 'feat:showNonProdBanner') { return false; } // All other enterprise features should be true return true; } hasFeatureDefined(feature, validateLicense = true) { return true; } hasQuotaLeft(feature, used) { return true; } getFeatureValue(feature, validateLicense = true) { // Special case: this feature should be FALSE for licensed instances if (feature === 'feat:showNonProdBanner') { return false; } // Return unlimited quota for quota features if (feature.startsWith("quota:")) { return -1; } // Return true for boolean features return true; } hasCert() { return true; } isTerminated() { return false; } getExpiryDate() { return this.licenseCert.expiresAt; } getTerminationDate() { return this.licenseCert.terminatesAt; } getFeatures() { return this.currentFeatures; } updateCurrentFeatures() { this.currentFeatures = { "feat:sharing": true, "feat:ldap": true, "feat:saml": true, "feat:variables": true, "feat:external-secrets": true, "feat:workflow-history": true, "feat:binary-data-s3": true, "feat:multiple-main-instances": true, "feat:source-control": true, "feat:advanced-permissions": true, "feat:debug-in-editor": true, "feat:showNonProdBanner": false, "quota:users": -1, "quota:activeWorkflowTriggers": -1, "quota:variables": -1 }; } getCurrentEntitlements() { return this.licenseCert.entitlements; } getManagementJwt() { return this.licenseCert.managementJwt; } async getCertStr() { return "emulated-cert-string"; } getConsumerId() { return this.licenseCert.consumerId; } isRenewalDue() { return false; } // License management methods (no-ops) async activate(activationKey) { this.log("license activation bypassed (emulated)", "info"); return Promise.resolve(); } async renew() { this.log("license renewal bypassed (emulated)", "info"); return Promise.resolve(); } async clear() { this.log("license clear bypassed (emulated)", "info"); return Promise.resolve(); } async shutdown() { this.isShuttingDown = true; this.log("license manager shutdown (emulated)", "info"); return Promise.resolve(); } // Timer management (no-ops) setupSingleTimer(callback, delay) { return setTimeout(callback, delay); } clearSingleTimer(timer) { if (timer) clearTimeout(timer); } setupRepeatingTimer(callback, interval) { return setInterval(callback, interval); } clearRepeatingTimer(timer) { if (timer) clearInterval(timer); } // Utility methods formatDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; return \`\${hours}h \${minutes}m \${secs}s\`; } toString() { return \`## EMULATED LICENSE MANAGER ## License: Enterprise (Emulated) Valid: true Features: All Enterprise features enabled Expiry: \${this.licenseCert.expiresAt} Status: Active (Bypassed)\`; } // Additional methods that might be called async reload() { await this.initialize(); } async reset() { // No-op for emulated manager } triggerOnFeatureChangeCallback() { if (this.config.onFeatureChange) { this.config.onFeatureChange(this.currentFeatures); } } enableAutoRenewals() { this.log("Auto-renewals enabled (emulated)", "info"); } disableAutoRenewals() { this.log("Auto-renewals disabled (emulated)", "info"); } } // Export in the expected format module.exports = { AUTORENEWAL_INTERVAL, LicenseManager }; `; } debugFilePermissions(filePath) { try { const stats = fs.statSync(filePath); const perms = (stats.mode & parseInt('777', 8)).toString(8); console.log(`šŸ“‹ File: ${filePath}`); console.log(` Permissions: ${perms}`); console.log(` Owner: ${stats.uid}:${stats.gid}`); console.log(` Size: ${stats.size} bytes`); // Check current user try { const whoami = execSync('whoami', { encoding: 'utf8' }).trim(); const id = execSync('id', { encoding: 'utf8' }).trim(); console.log(` Current user: ${whoami}`); console.log(` User info: ${id}`); } catch (e) {} // Check if directory is writable const dirPath = require('path').dirname(filePath); try { fs.accessSync(dirPath, fs.constants.W_OK); console.log(` Directory writable: āœ…`); } catch (e) { console.log(` Directory writable: āŒ ${e.message}`); } } catch (e) { console.log(`āŒ Could not debug file: ${e.message}`); } } patchInstallation(installation) { console.log(`\nšŸ”§ Patching: ${installation.path}`); // Debug file permissions this.debugFilePermissions(installation.path); try { // Create backup first if possible if (!fs.existsSync(installation.backupPath)) { try { fs.copyFileSync(installation.path, installation.backupPath); console.log('āœ… Created backup'); } catch (backupError) { console.log('āš ļø Could not create backup, proceeding anyway...'); } } // Try direct write first const cleanImplementation = this.generateCleanLicenseManager(); fs.writeFileSync(installation.path, cleanImplementation); console.log('āœ… Replaced with clean emulated license manager'); return true; } catch (error) { console.error(`āŒ Direct patching failed: ${error.message}`); // Try alternative approaches for Docker/readonly filesystems const alternatives = [ () => { console.log('šŸ”§ Trying to change file permissions...'); execSync(`chmod 666 "${installation.path}"`); fs.writeFileSync(installation.path, this.generateCleanLicenseManager()); }, () => { console.log('šŸ”§ Trying to remove and recreate file...'); execSync(`rm -f "${installation.path}"`); fs.writeFileSync(installation.path, this.generateCleanLicenseManager()); }, () => { console.log('šŸ”§ Trying with sudo-like approach...'); const tempFile = '/tmp/LicenseManager.js.patched'; fs.writeFileSync(tempFile, this.generateCleanLicenseManager()); execSync(`cat "${tempFile}" > "${installation.path}"`); }, () => { console.log('šŸ”§ Trying to mount tmpfs overlay...'); const tempFile = '/tmp/LicenseManager.js.patched'; fs.writeFileSync(tempFile, this.generateCleanLicenseManager()); execSync(`mount --bind "${tempFile}" "${installation.path}"`); } ]; for (let i = 0; i < alternatives.length; i++) { try { alternatives[i](); console.log(`āœ… Alternative method ${i + 1} succeeded`); return true; } catch (altError) { console.log(`āŒ Alternative method ${i + 1} failed: ${altError.message}`); } } console.log('šŸ’” All patching methods failed. This might be a read-only container.'); console.log(' Consider running the container with --privileged or mounting the file as writable.'); return false; } } restoreInstallation(installation) { console.log(`\nšŸ”„ Restoring: ${installation.path}`); if (!fs.existsSync(installation.backupPath)) { console.log('āŒ No backup found'); return false; } try { fs.copyFileSync(installation.backupPath, installation.path); console.log('āœ… Restored successfully'); return true; } catch (error) { console.error(`āŒ Error restoring: ${error.message}`); return false; } } patchAll() { const installations = this.findN8NInstallations(); if (installations.length === 0) { console.log('\nāŒ No n8n installations found!'); console.log('Make sure n8n is installed or check the search paths.'); return; } console.log('\nšŸš€ Applying patches...'); let successCount = 0; for (const installation of installations) { if (this.patchInstallation(installation)) { successCount++; } } console.log(`\nāœ… Successfully patched ${successCount}/${installations.length} installations!`); if (successCount > 0) { console.log('\nšŸŽ‰ N8N License Manager Replaced with Emulated Version!'); console.log('\nWhat this does:'); console.log(' • Replaces minified code with clean implementation'); console.log(' • All license validation methods return true'); console.log(' • All Enterprise features are enabled'); console.log(' • No certificate or signature validation'); console.log(' • Clean, readable code that\'s easy to maintain'); console.log('\nTry running n8n now:'); console.log(' n8n --help'); console.log(' n8n start'); } } restoreAll() { const installations = this.findN8NInstallations(); console.log('\nšŸ”„ Restoring all installations...'); let successCount = 0; for (const installation of installations) { if (this.restoreInstallation(installation)) { successCount++; } } console.log(`\nāœ… Successfully restored ${successCount} installations!`); } patchDockerCompose(filePath) { console.log(`\n🐳 Patching Docker Compose file: ${filePath}`); if (!fs.existsSync(filePath)) { console.log('āŒ Docker Compose file not found'); return false; } try { const content = fs.readFileSync(filePath, 'utf8'); const backupPath = filePath + '.backup'; // Create backup if it doesn't exist if (!fs.existsSync(backupPath)) { fs.writeFileSync(backupPath, content); console.log('āœ… Created backup'); } // Check if already patched if (content.includes('./emulated-license-patcher.js:/tmp/patcher.js:ro')) { console.log('āš ļø File appears to already be patched'); return true; } // Find n8n service const lines = content.split('\n'); let n8nServiceIndex = -1; let volumesIndex = -1; let inN8nService = false; let serviceIndent = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Look for n8n service if (line.match(/^\s+n8n:\s*$/) || line.match(/^\s+n8n:\s+#/)) { n8nServiceIndex = i; inN8nService = true; serviceIndent = line.match(/^(\s+)/)?.[1] || ' '; continue; } // If we're in n8n service, look for volumes if (inN8nService && line.match(/^\s+volumes:\s*$/)) { volumesIndex = i; continue; } // Stop when we hit another service or end of current service if (inN8nService && line.match(/^\s+\w+:\s*$/) && !line.match(/^\s+(volumes|environment|ports|links|depends_on|entrypoint|command):/)) { inN8nService = false; } } if (n8nServiceIndex === -1) { console.log('āŒ Could not find n8n service in docker-compose file'); return false; } console.log(`āœ… Found n8n service at line ${n8nServiceIndex + 1}`); // Insert patcher volume mount and entrypoint override let insertionPoint = -1; let hasEntrypoint = false; // Find where to insert the volume mount if (volumesIndex !== -1) { // Add to existing volumes section let lastVolumeIndex = volumesIndex; for (let i = volumesIndex + 1; i < lines.length; i++) { if (lines[i].match(/^\s+- /)) { lastVolumeIndex = i; } else if (lines[i].match(/^\s+\w+:/)) { break; } } insertionPoint = lastVolumeIndex + 1; } else { // Create volumes section let servicesEndIndex = n8nServiceIndex + 1; for (let i = n8nServiceIndex + 1; i < lines.length; i++) { if (lines[i].match(/^\s+\w+:\s*$/) && !lines[i].match(/^\s+(image|restart|environment|ports|links|depends_on|entrypoint|command):/)) { break; } if (lines[i].match(/^\s+(ports|links|depends_on):/)) { servicesEndIndex = i; } } // Insert volumes section lines.splice(servicesEndIndex + 1, 0, `${serviceIndent} volumes:`); insertionPoint = servicesEndIndex + 2; volumesIndex = servicesEndIndex + 1; } // Add patcher volume mount lines.splice(insertionPoint, 0, `${serviceIndent} - ./emulated-license-patcher.js:/tmp/patcher.js:ro`); // Find or add entrypoint for (let i = n8nServiceIndex; i < lines.length; i++) { if (lines[i].match(/^\s+entrypoint:/)) { hasEntrypoint = true; break; } if (lines[i].match(/^\s+\w+:\s*$/) && !lines[i].match(/^\s+(image|restart|environment|ports|links|depends_on|volumes|entrypoint|command):/)) { break; } } if (hasEntrypoint) { console.log('āš ļø Found existing entrypoint - you may need to manually merge the patching logic'); } else { // Add entrypoint after volumes section const entrypointLines = [ `${serviceIndent} # Override entrypoint to patch before starting`, `${serviceIndent} entrypoint: >`, `${serviceIndent} sh -c "`, `${serviceIndent} echo 'šŸ”‘ Applying license patch...';`, `${serviceIndent} node /tmp/patcher.js patch;`, `${serviceIndent} echo 'āœ… Patch applied, starting n8n...';`, `${serviceIndent} exec tini -- n8n start;`, `${serviceIndent} "` ]; // Find where to insert entrypoint (after volumes) let entrypointInsertIndex = insertionPoint + 1; for (let i = insertionPoint + 1; i < lines.length; i++) { if (lines[i].match(/^\s+\w+:/)) { entrypointInsertIndex = i; break; } } lines.splice(entrypointInsertIndex, 0, ...entrypointLines); } // Write patched file fs.writeFileSync(filePath, lines.join('\n')); console.log('āœ… Docker Compose file patched successfully'); console.log('\nšŸ“‹ What was added:'); console.log(' • Volume mount: ./emulated-license-patcher.js:/tmp/patcher.js:ro'); if (!hasEntrypoint) { console.log(' • Entrypoint override to run patcher before n8n starts'); } console.log('\nāš ļø Make sure emulated-license-patcher.js is in the same directory as your docker-compose.yml'); return true; } catch (error) { console.error(`āŒ Error patching Docker Compose: ${error.message}`); return false; } } unpatchDockerCompose(filePath) { console.log(`\nšŸ”„ Unpatching Docker Compose file: ${filePath}`); const backupPath = filePath + '.backup'; if (!fs.existsSync(backupPath)) { console.log('āŒ No backup found'); return false; } try { const backupContent = fs.readFileSync(backupPath, 'utf8'); fs.writeFileSync(filePath, backupContent); console.log('āœ… Docker Compose file restored from backup'); return true; } catch (error) { console.error(`āŒ Error restoring Docker Compose: ${error.message}`); return false; } } showUsage() { console.log('šŸ”‘ Emulated N8N License Patcher'); console.log('==============================='); console.log(''); console.log('Replaces minified LicenseManager with clean emulated implementation.'); console.log(''); console.log('Usage:'); console.log(' node emulated-license-patcher.js patch # Apply patches'); console.log(' node emulated-license-patcher.js restore # Restore original files'); console.log(' node emulated-license-patcher.js find # Just find installations'); console.log(' node emulated-license-patcher.js docker-patch # Patch docker-compose.yml'); console.log(' node emulated-license-patcher.js docker-unpatch # Unpatch docker-compose.yml'); console.log(' node emulated-license-patcher.js help # Show this help'); console.log(''); console.log('Docker Examples:'); console.log(' node emulated-license-patcher.js docker-patch docker-compose.yml'); console.log(' node emulated-license-patcher.js docker-unpatch docker-compose.yml'); console.log(''); console.log('āš ļø For development/testing purposes only!'); } } // Main execution const patcher = new EmulatedLicensePatcher(); const command = process.argv[2]; const filePath = process.argv[3]; switch (command) { case 'patch': patcher.patchAll(); break; case 'restore': patcher.restoreAll(); break; case 'find': patcher.findN8NInstallations(); break; case 'docker-patch': if (!filePath) { console.log('āŒ Please specify docker-compose file path'); console.log('Usage: node emulated-license-patcher.js docker-patch '); process.exit(1); } patcher.patchDockerCompose(filePath); break; case 'docker-unpatch': if (!filePath) { console.log('āŒ Please specify docker-compose file path'); console.log('Usage: node emulated-license-patcher.js docker-unpatch '); process.exit(1); } patcher.unpatchDockerCompose(filePath); break; case 'help': case '--help': case '-h': case undefined: patcher.showUsage(); break; default: console.log('āŒ Invalid command. Use "help" for usage information.'); process.exit(1); }