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