mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
feat(core): Add license:info command (#6047)
* feat(core): Add license:info command * revert changes to start.ts * revert changes to start.ts * fix typo
This commit is contained in:
parent
54f99a7d0d
commit
ab12d3e327
|
@ -116,7 +116,7 @@
|
||||||
"tsconfig-paths": "^4.1.2"
|
"tsconfig-paths": "^4.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n_io/license-sdk": "~2.1.0",
|
"@n8n_io/license-sdk": "~2.3.0",
|
||||||
"@oclif/command": "^1.8.16",
|
"@oclif/command": "^1.8.16",
|
||||||
"@oclif/core": "^1.16.4",
|
"@oclif/core": "^1.16.4",
|
||||||
"@oclif/errors": "^1.3.6",
|
"@oclif/errors": "^1.3.6",
|
||||||
|
|
|
@ -12,34 +12,6 @@ import {
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
async function loadCertStr(): Promise<TLicenseBlock> {
|
|
||||||
// if we have an ephemeral license, we don't want to load it from the database
|
|
||||||
const ephemeralLicense = config.get('license.cert');
|
|
||||||
if (ephemeralLicense) {
|
|
||||||
return ephemeralLicense;
|
|
||||||
}
|
|
||||||
const databaseSettings = await Db.collections.Settings.findOne({
|
|
||||||
where: {
|
|
||||||
key: SETTINGS_LICENSE_CERT_KEY,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return databaseSettings?.value ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveCertStr(value: TLicenseBlock): Promise<void> {
|
|
||||||
// if we have an ephemeral license, we don't want to save it to the database
|
|
||||||
if (config.get('license.cert')) return;
|
|
||||||
await Db.collections.Settings.upsert(
|
|
||||||
{
|
|
||||||
key: SETTINGS_LICENSE_CERT_KEY,
|
|
||||||
value,
|
|
||||||
loadOnStartup: false,
|
|
||||||
},
|
|
||||||
['key'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class License {
|
export class License {
|
||||||
private logger: ILogger;
|
private logger: ILogger;
|
||||||
|
@ -67,8 +39,8 @@ export class License {
|
||||||
autoRenewEnabled,
|
autoRenewEnabled,
|
||||||
autoRenewOffset,
|
autoRenewOffset,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
loadCertStr,
|
loadCertStr: async () => this.loadCertStr(),
|
||||||
saveCertStr,
|
saveCertStr: async (value: TLicenseBlock) => this.saveCertStr(value),
|
||||||
deviceFingerprint: () => instanceId,
|
deviceFingerprint: () => instanceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -80,6 +52,34 @@ export class License {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadCertStr(): Promise<TLicenseBlock> {
|
||||||
|
// if we have an ephemeral license, we don't want to load it from the database
|
||||||
|
const ephemeralLicense = config.get('license.cert');
|
||||||
|
if (ephemeralLicense) {
|
||||||
|
return ephemeralLicense;
|
||||||
|
}
|
||||||
|
const databaseSettings = await Db.collections.Settings.findOne({
|
||||||
|
where: {
|
||||||
|
key: SETTINGS_LICENSE_CERT_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return databaseSettings?.value ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveCertStr(value: TLicenseBlock): Promise<void> {
|
||||||
|
// if we have an ephemeral license, we don't want to save it to the database
|
||||||
|
if (config.get('license.cert')) return;
|
||||||
|
await Db.collections.Settings.upsert(
|
||||||
|
{
|
||||||
|
key: SETTINGS_LICENSE_CERT_KEY,
|
||||||
|
value,
|
||||||
|
loadOnStartup: false,
|
||||||
|
},
|
||||||
|
['key'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async activate(activationKey: string): Promise<void> {
|
async activate(activationKey: string): Promise<void> {
|
||||||
if (!this.manager) {
|
if (!this.manager) {
|
||||||
return;
|
return;
|
||||||
|
@ -185,4 +185,12 @@ export class License {
|
||||||
getPlanName(): string {
|
getPlanName(): string {
|
||||||
return (this.getFeatureValue('planName') ?? 'Community') as string;
|
return (this.getFeatureValue('planName') ?? 'Community') as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getInfo(): string {
|
||||||
|
if (!this.manager) {
|
||||||
|
return 'n/a';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.manager.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
22
packages/cli/src/commands/license/info.ts
Normal file
22
packages/cli/src/commands/license/info.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { License } from '@/License';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import { BaseCommand } from '../BaseCommand';
|
||||||
|
|
||||||
|
export class LicenseInfoCommand extends BaseCommand {
|
||||||
|
static description = 'Print license information';
|
||||||
|
|
||||||
|
static examples = ['$ n8n license:info'];
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const license = Container.get(License);
|
||||||
|
await license.init(this.instanceId);
|
||||||
|
|
||||||
|
this.logger.info('Printing license information:\n' + license.getInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
async catch(error: Error) {
|
||||||
|
this.logger.error('\nGOT ERROR');
|
||||||
|
this.logger.info('====================================');
|
||||||
|
this.logger.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1130,7 +1130,7 @@ export const schema = {
|
||||||
format: Boolean,
|
format: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
env: 'N8N_LICENSE_AUTO_RENEW_ENABLED',
|
env: 'N8N_LICENSE_AUTO_RENEW_ENABLED',
|
||||||
doc: 'Whether autorenew for licenses is enabled.',
|
doc: 'Whether auto renewal for licenses is enabled.',
|
||||||
},
|
},
|
||||||
autoRenewOffset: {
|
autoRenewOffset: {
|
||||||
format: Number,
|
format: Number,
|
||||||
|
|
|
@ -75,6 +75,7 @@ licenseController.post(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const error = e as Error & { errorId?: string };
|
const error = e as Error & { errorId?: string };
|
||||||
|
|
||||||
|
//override specific error messages (to map License Server vocabulary to n8n terms)
|
||||||
switch (error.errorId ?? 'UNSPECIFIED') {
|
switch (error.errorId ?? 'UNSPECIFIED') {
|
||||||
case 'SCHEMA_VALIDATION':
|
case 'SCHEMA_VALIDATION':
|
||||||
error.message = 'Activation key is in the wrong format';
|
error.message = 'Activation key is in the wrong format';
|
||||||
|
@ -92,7 +93,7 @@ licenseController.post(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ResponseHelper.BadRequestError((e as Error).message);
|
throw new ResponseHelper.BadRequestError(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the read data, plus the management JWT
|
// Return the read data, plus the management JWT
|
||||||
|
@ -115,10 +116,12 @@ licenseController.post(
|
||||||
try {
|
try {
|
||||||
await license.renew();
|
await license.renew();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
const error = e as Error & { errorId?: string };
|
||||||
|
|
||||||
// not awaiting so as not to make the endpoint hang
|
// not awaiting so as not to make the endpoint hang
|
||||||
void Container.get(InternalHooks).onLicenseRenewAttempt({ success: false });
|
void Container.get(InternalHooks).onLicenseRenewAttempt({ success: false });
|
||||||
if (e instanceof Error) {
|
if (error instanceof Error) {
|
||||||
throw new ResponseHelper.BadRequestError(e.message);
|
throw new ResponseHelper.BadRequestError(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,8 +158,8 @@ importers:
|
||||||
packages/cli:
|
packages/cli:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@n8n_io/license-sdk':
|
'@n8n_io/license-sdk':
|
||||||
specifier: ~2.1.0
|
specifier: ~2.3.0
|
||||||
version: 2.1.0
|
version: 2.3.0
|
||||||
'@oclif/command':
|
'@oclif/command':
|
||||||
specifier: ^1.8.16
|
specifier: ^1.8.16
|
||||||
version: 1.8.18(@oclif/config@1.18.5)(supports-color@8.1.1)
|
version: 1.8.18(@oclif/config@1.18.5)(supports-color@8.1.1)
|
||||||
|
@ -4300,8 +4300,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@n8n_io/license-sdk@2.1.0:
|
/@n8n_io/license-sdk@2.3.0:
|
||||||
resolution: {integrity: sha512-SwIm9b6a30/fAvl1aY0a6cgoSyQBgKHX44M4Ykesn45VSGBKlzO5uuIiIcEPdVjjLEelm7u6wLoDFdIVG37b7Q==}
|
resolution: {integrity: sha512-1qOg4VEi2mZzhAJ5Uh9IT9Jn/b3xCaxyFbovYLtymzy3ObafUyWieUrSQri3BrCbW1dwQHz99DEVFxYCq1Je0Q==}
|
||||||
engines: {node: '>=14.0.0', npm: '>=7.10.0'}
|
engines: {node: '>=14.0.0', npm: '>=7.10.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
crypto-js: 4.1.1
|
crypto-js: 4.1.1
|
||||||
|
|
Loading…
Reference in a new issue