@abtnode/certificate-manager 1.17.6-beta-20251221-021146-1a145a92 → 1.17.7-beta-20251223-090654-55d57623
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/libs/acme-manager.js +26 -9
- package/package.json +7 -7
- package/sdk/manager.js +4 -3
- package/states/certificate.js +36 -7
package/libs/acme-manager.js
CHANGED
|
@@ -18,12 +18,21 @@ const http01 = require('./http-01').create({});
|
|
|
18
18
|
|
|
19
19
|
const DEFAULT_AGENT_NAME = 'blocklet-server';
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// Default renewal ratio: renew when remaining lifetime <= 1/3 of total lifetime
|
|
22
|
+
const DEFAULT_RENEWAL_RATIO = 1 / 3;
|
|
23
|
+
// Minimum days threshold: always renew if remaining days <= 10
|
|
24
|
+
const MINIMUM_RENEWAL_DAYS = 10;
|
|
22
25
|
|
|
23
26
|
class Manager extends EventEmitter {
|
|
24
|
-
constructor({ dataDir, maintainerEmail, staging = false,
|
|
27
|
+
constructor({ dataDir, maintainerEmail, staging = false, renewalRatio = DEFAULT_RENEWAL_RATIO }) {
|
|
25
28
|
super();
|
|
26
29
|
logger.info('initialize manager in data dir:', { dataDir });
|
|
30
|
+
|
|
31
|
+
// Validate renewalRatio
|
|
32
|
+
if (typeof renewalRatio !== 'number' || Number.isNaN(renewalRatio) || renewalRatio <= 0) {
|
|
33
|
+
throw new Error(`Invalid renewalRatio: ${renewalRatio}. Must be a positive number.`);
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
this.acme = new AcmeWrapper({
|
|
28
37
|
packageAgent: `${DEFAULT_AGENT_NAME}/${pkg.version}`,
|
|
29
38
|
staging,
|
|
@@ -31,7 +40,7 @@ class Manager extends EventEmitter {
|
|
|
31
40
|
});
|
|
32
41
|
|
|
33
42
|
this.maintainerEmail = maintainerEmail;
|
|
34
|
-
this.
|
|
43
|
+
this.renewalRatio = renewalRatio;
|
|
35
44
|
this.dataDir = dataDir;
|
|
36
45
|
this.getJobId = (job) => (job ? md5(`${job.domain}-${job.challenge}`) : '');
|
|
37
46
|
this.queue = createQueue({
|
|
@@ -162,10 +171,17 @@ class Manager extends EventEmitter {
|
|
|
162
171
|
|
|
163
172
|
if (cert.certificate) {
|
|
164
173
|
const info = Certificate.fromPEM(cert.certificate);
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
const now = dayjs();
|
|
175
|
+
const validFrom = dayjs(info.validFrom);
|
|
176
|
+
const validTo = dayjs(info.validTo);
|
|
177
|
+
const totalLifetime = validTo.diff(validFrom, 'days');
|
|
178
|
+
const remainingDays = validTo.diff(now, 'days');
|
|
179
|
+
const renewalThreshold = Math.max(totalLifetime * this.renewalRatio, MINIMUM_RENEWAL_DAYS);
|
|
180
|
+
|
|
181
|
+
if (force === false && remainingDays > renewalThreshold) {
|
|
182
|
+
logger.info(
|
|
183
|
+
`no need to renewal ${cert.domain}, remaining ${remainingDays} days > threshold ${renewalThreshold.toFixed(1)} days (max of ${(this.renewalRatio * 100).toFixed(0)}% of ${totalLifetime} days lifetime or ${MINIMUM_RENEWAL_DAYS} days)`
|
|
184
|
+
);
|
|
169
185
|
return null;
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -214,7 +230,7 @@ class Manager extends EventEmitter {
|
|
|
214
230
|
async checkRenewalCerts() {
|
|
215
231
|
logger.info('run renewal certificate job');
|
|
216
232
|
|
|
217
|
-
const certs = await states.certificate.findRenewCerts(this.
|
|
233
|
+
const certs = await states.certificate.findRenewCerts(this.renewalRatio, MINIMUM_RENEWAL_DAYS);
|
|
218
234
|
|
|
219
235
|
logger.info(`found ${certs.length} certs need to renewal`);
|
|
220
236
|
|
|
@@ -227,7 +243,7 @@ class Manager extends EventEmitter {
|
|
|
227
243
|
|
|
228
244
|
let instance = null;
|
|
229
245
|
|
|
230
|
-
Manager.getInstance = async ({ maintainerEmail, baseDataDir }) => {
|
|
246
|
+
Manager.getInstance = async ({ maintainerEmail, baseDataDir, renewalRatio }) => {
|
|
231
247
|
if (instance) {
|
|
232
248
|
return instance;
|
|
233
249
|
}
|
|
@@ -238,6 +254,7 @@ Manager.getInstance = async ({ maintainerEmail, baseDataDir }) => {
|
|
|
238
254
|
packageRoot: baseDataDir,
|
|
239
255
|
dataDir,
|
|
240
256
|
maintainerEmail,
|
|
257
|
+
renewalRatio,
|
|
241
258
|
staging: typeof process.env.STAGING === 'undefined' ? process.env.NODE_ENV !== 'production' : !!process.env.STAGING,
|
|
242
259
|
});
|
|
243
260
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abtnode/certificate-manager",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.7-beta-20251223-090654-55d57623",
|
|
4
4
|
"description": "Manage ABT Node SSL certificates",
|
|
5
5
|
"author": "polunzh <polunzh@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/ArcBlock/blocklet-server#readme",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"url": "https://github.com/ArcBlock/blocklet-server/issues"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@abtnode/cron": "1.17.
|
|
34
|
-
"@abtnode/logger": "1.17.
|
|
35
|
-
"@abtnode/models": "1.17.
|
|
36
|
-
"@abtnode/queue": "1.17.
|
|
37
|
-
"@abtnode/util": "1.17.
|
|
33
|
+
"@abtnode/cron": "1.17.7-beta-20251223-090654-55d57623",
|
|
34
|
+
"@abtnode/logger": "1.17.7-beta-20251223-090654-55d57623",
|
|
35
|
+
"@abtnode/models": "1.17.7-beta-20251223-090654-55d57623",
|
|
36
|
+
"@abtnode/queue": "1.17.7-beta-20251223-090654-55d57623",
|
|
37
|
+
"@abtnode/util": "1.17.7-beta-20251223-090654-55d57623",
|
|
38
38
|
"@blocklet/error": "^0.3.5",
|
|
39
39
|
"@fidm/x509": "^1.2.1",
|
|
40
40
|
"@greenlock/manager": "^3.1.0",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"joi": "17.12.2",
|
|
48
48
|
"punycode": "^2.3.1"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "00a82b6f4ef5818d6ec37d74be404031c693247c"
|
|
51
51
|
}
|
package/sdk/manager.js
CHANGED
|
@@ -11,15 +11,16 @@ const { validateCertificate, getCertInfo } = require('../libs/util');
|
|
|
11
11
|
|
|
12
12
|
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
|
13
13
|
const CERTIFICATE_EXPIRES_WARNING_OFFSET = 7 * DAY_IN_MS;
|
|
14
|
+
const DEFAULT_RENEWAL_RATIO = 1 / 3;
|
|
14
15
|
|
|
15
16
|
class Manager extends EventEmitter {
|
|
16
|
-
constructor({
|
|
17
|
+
constructor({ renewalRatio = DEFAULT_RENEWAL_RATIO, maintainerEmail, dataDir }) {
|
|
17
18
|
super();
|
|
18
19
|
if (!dataDir) {
|
|
19
20
|
throw new Error('dataDir is required');
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
this.
|
|
23
|
+
this.renewalRatio = renewalRatio;
|
|
23
24
|
this.maintainerEmail = maintainerEmail;
|
|
24
25
|
this.acmeManager = null;
|
|
25
26
|
this.dataDir = dataDir;
|
|
@@ -30,7 +31,7 @@ class Manager extends EventEmitter {
|
|
|
30
31
|
async start() {
|
|
31
32
|
const acmeManager = await AcmeManager.initInstance({
|
|
32
33
|
maintainerEmail: this.maintainerEmail,
|
|
33
|
-
|
|
34
|
+
renewalRatio: this.renewalRatio,
|
|
34
35
|
baseDataDir: this.dataDir,
|
|
35
36
|
});
|
|
36
37
|
|
package/states/certificate.js
CHANGED
|
@@ -42,14 +42,43 @@ class Certificate extends BaseState {
|
|
|
42
42
|
return super.update(condition, data);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Find Let's Encrypt certificates that should be renewed based on their remaining
|
|
47
|
+
* lifetime and a minimum renewal window.
|
|
48
|
+
*
|
|
49
|
+
* The renewal threshold is computed as:
|
|
50
|
+
* max(totalLifetime * renewalRatio, minimumRenewalDays in milliseconds)
|
|
51
|
+
* and a certificate is selected if its remaining validity is less than or equal
|
|
52
|
+
* to this threshold.
|
|
53
|
+
*
|
|
54
|
+
* @param {number} renewalRatio
|
|
55
|
+
* Fraction of the certificate's total lifetime at which renewal should be
|
|
56
|
+
* considered. Expected to be between 0 and 1 (e.g. 0.5 = renew after 50%
|
|
57
|
+
* of the lifetime has elapsed).
|
|
58
|
+
* @param {number} [minimumRenewalDays=10]
|
|
59
|
+
* Minimum number of days before expiry at which renewal should be considered,
|
|
60
|
+
* regardless of the value of {@link renewalRatio}. Must be a non-negative number.
|
|
61
|
+
* @returns {Promise<import('@abtnode/models').CertificateState[]>}
|
|
62
|
+
* A promise that resolves to the list of certificates that should be renewed.
|
|
63
|
+
*/
|
|
64
|
+
async findRenewCerts(renewalRatio, minimumRenewalDays = 10) {
|
|
46
65
|
const certs = await super.find({ source: CERT_SOURCE.letsEncrypt });
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
const now = dayjs();
|
|
67
|
+
const minimumThreshold = minimumRenewalDays * 24 * 60 * 60 * 1000;
|
|
68
|
+
|
|
69
|
+
return certs.filter((cert) => {
|
|
70
|
+
if (!cert.meta?.validTo || !cert.meta?.validFrom) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const validFrom = dayjs(cert.meta.validFrom);
|
|
75
|
+
const validTo = dayjs(cert.meta.validTo);
|
|
76
|
+
const totalLifetime = validTo.diff(validFrom, 'milliseconds');
|
|
77
|
+
const remainingTime = validTo.diff(now, 'milliseconds');
|
|
78
|
+
const renewalThreshold = Math.max(totalLifetime * renewalRatio, minimumThreshold);
|
|
79
|
+
|
|
80
|
+
return remainingTime <= renewalThreshold;
|
|
81
|
+
});
|
|
53
82
|
}
|
|
54
83
|
}
|
|
55
84
|
|