@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.
@@ -18,12 +18,21 @@ const http01 = require('./http-01').create({});
18
18
 
19
19
  const DEFAULT_AGENT_NAME = 'blocklet-server';
20
20
 
21
- const DEFAULT_RENEWAL_OFFSET_IN_DAY = 30;
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, renewalOffsetInDay = DEFAULT_RENEWAL_OFFSET_IN_DAY }) {
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.renewalOffsetInDay = renewalOffsetInDay;
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 days = dayjs(info.validTo).diff(dayjs(), 'days');
166
-
167
- if (force === false && days > this.renewalOffsetInDay) {
168
- logger.info(`no need to renewal ${cert.domain}, the certificate will expires more than ${days} days`);
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.renewalOffsetInDay);
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.6-beta-20251221-021146-1a145a92",
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.6-beta-20251221-021146-1a145a92",
34
- "@abtnode/logger": "1.17.6-beta-20251221-021146-1a145a92",
35
- "@abtnode/models": "1.17.6-beta-20251221-021146-1a145a92",
36
- "@abtnode/queue": "1.17.6-beta-20251221-021146-1a145a92",
37
- "@abtnode/util": "1.17.6-beta-20251221-021146-1a145a92",
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": "6922b56b5f282ba14c864f6a7969d218e4065104"
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({ daysBeforeExpireToRenewal, maintainerEmail, dataDir }) {
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.daysBeforeExpireToRenewal = daysBeforeExpireToRenewal;
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
- daysBeforeExpireToRenewal: this.daysBeforeExpireToRenewal,
34
+ renewalRatio: this.renewalRatio,
34
35
  baseDataDir: this.dataDir,
35
36
  });
36
37
 
@@ -42,14 +42,43 @@ class Certificate extends BaseState {
42
42
  return super.update(condition, data);
43
43
  }
44
44
 
45
- async findRenewCerts(renewalOffsetInDay) {
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
- return certs
48
- .filter((cert) => cert.meta?.validTo)
49
- .filter((cert) => {
50
- const tmp = dayjs().add(renewalOffsetInDay, 'days').unix() * 1000;
51
- return cert.meta.validTo <= tmp;
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