Add emulated-license-patcher.js
This commit is contained in:
commit
1f3654a04c
802
emulated-license-patcher.js
Normal file
802
emulated-license-patcher.js
Normal file
@ -0,0 +1,802 @@
|
|||||||
|
#!/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 <file> # Patch docker-compose.yml');
|
||||||
|
console.log(' node emulated-license-patcher.js docker-unpatch <file> # 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 <file>');
|
||||||
|
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 <file>');
|
||||||
|
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);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user