pending-dns 1.2.5 → 1.3.0

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.
Files changed (46) hide show
  1. package/.github/codeql/codeql-config.yml +11 -0
  2. package/.github/workflows/codeql.yml +52 -0
  3. package/.github/workflows/deploy.yml +16 -3
  4. package/.github/workflows/release.yaml +43 -0
  5. package/.github/workflows/test.yml +75 -0
  6. package/.release-please-manifest.json +3 -0
  7. package/CHANGELOG.md +8 -0
  8. package/CLAUDE.md +97 -0
  9. package/README.md +28 -5
  10. package/SECURITY.md +88 -0
  11. package/SECURITY.txt +27 -0
  12. package/bin/pending-dns.js +1 -1
  13. package/config/default.toml +5 -0
  14. package/config/test.toml +25 -0
  15. package/eslint.config.js +38 -0
  16. package/lib/api-server.js +13 -6
  17. package/lib/cached-resolver.js +5 -3
  18. package/lib/certs.js +11 -4
  19. package/lib/dns-handler.js +13 -8
  20. package/lib/dns-server.js +30 -18
  21. package/lib/dns-tcp-server.js +1 -1
  22. package/lib/dns-udp-server.js +1 -1
  23. package/lib/logger.js +3 -0
  24. package/lib/public-server.js +20 -2
  25. package/lib/sentry.js +72 -0
  26. package/lib/tools.js +1 -1
  27. package/lib/zone-store.js +4 -4
  28. package/package.json +43 -33
  29. package/release-please-config.json +13 -0
  30. package/server.js +5 -24
  31. package/systemd/pending-dns.service +4 -4
  32. package/test/api.test.js +139 -0
  33. package/test/cached-resolver.test.js +57 -0
  34. package/test/certs.test.js +34 -0
  35. package/test/dns-handler.test.js +140 -0
  36. package/test/dns-server.test.js +69 -0
  37. package/test/helpers.js +25 -0
  38. package/test/sentry.test.js +21 -0
  39. package/test/tools.test.js +48 -0
  40. package/test/zone-store.test.js +209 -0
  41. package/workers/api.js +3 -1
  42. package/workers/dns.js +2 -24
  43. package/workers/health.js +3 -26
  44. package/workers/public.js +3 -25
  45. package/.eslintrc +0 -14
  46. package/Gruntfile.js +0 -16
package/lib/api-server.js CHANGED
@@ -8,7 +8,7 @@ const Inert = require('@hapi/inert');
8
8
  const Vision = require('@hapi/vision');
9
9
  const HapiSwagger = require('hapi-swagger');
10
10
  const packageData = require('../package.json');
11
- const Joi = require('@hapi/joi');
11
+ const Joi = require('joi');
12
12
  const logger = require('./logger').child({ component: 'api-server' });
13
13
  const { zoneStore, allowedTypes, allowedTags } = require('./zone-store');
14
14
  const { getCertificate } = require('./certs');
@@ -192,7 +192,7 @@ const recordScheme = Joi.object({
192
192
  .description('Certificate authority domain for CAA')
193
193
  .label('CADomain'),
194
194
 
195
- tags: Joi.any()
195
+ tag: Joi.any()
196
196
  .example('issue')
197
197
  .when('type', {
198
198
  is: 'CAA',
@@ -207,7 +207,7 @@ const recordScheme = Joi.object({
207
207
  .example(0)
208
208
  .when('type', {
209
209
  is: 'CAA',
210
- then: Joi.string().valid(0).default(0)
210
+ then: Joi.number().integer().min(0).max(255).default(0)
211
211
  })
212
212
  .description('Certificate authority flags for CAA')
213
213
  .label('Flags'),
@@ -273,7 +273,7 @@ const failAction = async (request, h, err) => {
273
273
  throw error;
274
274
  };
275
275
 
276
- const init = async () => {
276
+ const createServer = async () => {
277
277
  const server = Hapi.server({
278
278
  port: (process.env.API_PORT && Number(process.env.API_PORT)) || config.api.port,
279
279
  host: process.env.API_HOST || config.api.host
@@ -478,8 +478,8 @@ const init = async () => {
478
478
  throw new Error('Unknown type');
479
479
  }
480
480
 
481
- let updated = await zoneStore.update(request.params.zone, request.params.record, request.payload.subdomain, request.payload.type, value);
482
- return { zone: request.params.zone, updated };
481
+ let record = await zoneStore.update(request.params.zone, request.params.record, request.payload.subdomain, request.payload.type, value);
482
+ return { zone: request.params.zone, record };
483
483
  } catch (err) {
484
484
  if (Boom.isBoom(err)) {
485
485
  throw err;
@@ -639,7 +639,14 @@ const init = async () => {
639
639
  }
640
640
  });
641
641
 
642
+ return server;
643
+ };
644
+
645
+ const init = async () => {
646
+ const server = await createServer();
642
647
  await server.start();
648
+ return server;
643
649
  };
644
650
 
645
651
  module.exports = init;
652
+ module.exports.createServer = createServer;
@@ -32,12 +32,13 @@ function formatResult(record) {
32
32
  * @param {Object} [options]
33
33
  * @param {Number} [options.minTtl=10min] Cache timeout for successful resolving operations. If cached response is older than this value then resolving is retried. If resolving fails then cached value is used.
34
34
  * @param {Number} [options.maxTtl=8h] Time after cached data of a successful resolving is permanently deleted
35
- * @param {Number} [options.errorTtl=1min] Cache timeout for errored resolving operations. If cached response is older than this value then resolving is retried
35
+ * @param {Number} [options.errorMinTtl=1min] Cache timeout for errored resolving operations. If cached response is older than this value then resolving is retried
36
+ * @param {Number} [options.errorMaxTtl=1h] Time after cached error data is permanently deleted
36
37
  * @returns {Array|Boolean} Resolve response or `false` if query failed for whatever reason
37
38
  */
38
39
  const cachedResolver = async (target, type, options) => {
39
40
  try {
40
- target = punycode.toASCII(target.trim().toLowerCase().trim().toLowerCase());
41
+ target = punycode.toASCII(target.trim().toLowerCase());
41
42
  } catch (err) {
42
43
  return false;
43
44
  }
@@ -120,12 +121,13 @@ const cachedResolver = async (target, type, options) => {
120
121
  .set(
121
122
  cacheKey,
122
123
  JSON.stringify({
124
+ expires: Date.now() + options.errorMinTtl,
123
125
  data: false,
124
126
  error: err.message,
125
127
  code: err.code || err.errno
126
128
  })
127
129
  )
128
- .expire(cacheKey, Math.round(options.errorTtl / 1000))
130
+ .expire(cacheKey, Math.round(options.errorMaxTtl / 1000))
129
131
  .exec();
130
132
  throw err;
131
133
  }
package/lib/certs.js CHANGED
@@ -9,7 +9,6 @@ const crypto = require('crypto');
9
9
  const { checkNSStatus, normalizeDomain } = require('./tools');
10
10
  const ACME = require('@root/acme');
11
11
  const { pem2jwk } = require('pem-jwk');
12
- const NodeRSA = require('node-rsa');
13
12
  const logger = require('./logger').child({ component: 'certs' });
14
13
  const CSR = require('@root/csr');
15
14
  const { Certificate } = require('@fidm/x509');
@@ -17,6 +16,8 @@ const { Resolver } = require('dns').promises;
17
16
  const Lock = require('ioredfour');
18
17
  const util = require('util');
19
18
 
19
+ const generateKeyPairAsync = util.promisify(crypto.generateKeyPair);
20
+
20
21
  const certLock = new Lock({
21
22
  redis: db.redisWrite,
22
23
  namespace: 'd:lock:'
@@ -78,9 +79,12 @@ const ensureAcme = async () => {
78
79
  };
79
80
 
80
81
  const generateKey = async bits => {
81
- const key = new NodeRSA({ b: bits || 2048, e: 65537 });
82
- const pem = key.exportKey('pkcs1-private-pem');
83
- return pem;
82
+ const { privateKey } = await generateKeyPairAsync('rsa', {
83
+ modulusLength: bits || 2048,
84
+ publicExponent: 65537,
85
+ privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
86
+ });
87
+ return privateKey;
84
88
  };
85
89
 
86
90
  class AcmeDNSPlugin {
@@ -472,3 +476,6 @@ module.exports = {
472
476
  getCertificate,
473
477
  loadCertificate
474
478
  };
479
+
480
+ // Exposed for unit testing
481
+ module.exports.testables = { generateKey };
@@ -8,15 +8,16 @@ const cachedResolver = require('./cached-resolver');
8
8
  const ipaddr = require('ipaddr.js');
9
9
  const logger = require('./logger').child({ component: 'dns-handler' });
10
10
  const { normalizeDomain } = require('./tools');
11
- const { v4: uuidv4 } = require('uuid');
11
+ const { randomUUID } = require('crypto');
12
12
 
13
- // Split long string values into character chunks
13
+ // Split a TXT value into DNS character-strings (max 255 bytes each).
14
+ // Always returns an array, which is what the dns2 packet builder expects.
14
15
  const formatTXTData = data => {
15
16
  data = (data || '').toString();
16
- if (data.length < 128) {
17
- return data;
17
+ if (!data.length) {
18
+ return [''];
18
19
  }
19
- return Array.from(data.match(/.{1,84}/g));
20
+ return Array.from(data.match(/.{1,255}/g));
20
21
  };
21
22
 
22
23
  // Helps to convert DNS type integer into a string (0x01 -> 'A')
@@ -91,7 +92,7 @@ const processQuestion = async (response, question, domain, depth) => {
91
92
  if (records && records.length > 1) {
92
93
  switch (type) {
93
94
  case 'A':
94
- case 'AAAAA':
95
+ case 'AAAA':
95
96
  // randomize A/AAAA records
96
97
  records = shuffle(filterUnhealthy(records));
97
98
  break;
@@ -275,7 +276,8 @@ const processQuestion = async (response, question, domain, depth) => {
275
276
  try {
276
277
  entry.address = ipaddr.parse(value[0]).toNormalizedString();
277
278
  } catch (err) {
278
- return;
279
+ // skip just this malformed record, keep processing the rest
280
+ continue;
279
281
  }
280
282
  break;
281
283
 
@@ -360,7 +362,7 @@ const processQuestion = async (response, question, domain, depth) => {
360
362
  const dnsHandler = async request => {
361
363
  let startTime = Date.now();
362
364
  const response = new dns2.Packet(request);
363
- request._id = response._id = uuidv4();
365
+ request._id = response._id = randomUUID();
364
366
 
365
367
  response.header.qr = 1;
366
368
  response.header.aa = 1;
@@ -404,3 +406,6 @@ const dnsHandler = async request => {
404
406
  };
405
407
 
406
408
  module.exports = dnsHandler;
409
+
410
+ // Exposed for unit testing
411
+ module.exports.testables = { formatTXTData, shuffle, filterUnhealthy, reversedTypes, processQuestion };
package/lib/dns-server.js CHANGED
@@ -19,7 +19,7 @@ const SUPPORTED_TYPES = new Set(
19
19
 
20
20
  const init = async () => {
21
21
  // create UDP server
22
- createDNSUdpServer((request, send) => {
22
+ const udpServer = createDNSUdpServer((request, send) => {
23
23
  // filter out unsupported requests (eg. EDNS)
24
24
  if (request.additionals && request.additionals.length) {
25
25
  request.additionals = request.additionals.filter(additional => SUPPORTED_TYPES.has(additional.type));
@@ -38,16 +38,14 @@ const init = async () => {
38
38
  }
39
39
  logger.error({ msg: 'Failed to send DNS response', protocol: 'udp', err });
40
40
  });
41
- })
42
- .listen(config.dns.port, config.dns.host, () => {
43
- logger.info({ msg: 'DNS server listening', protocol: 'udp', host: config.dns.host, port: config.dns.port });
44
- })
45
- .on('error', err => {
46
- logger.error({ msg: 'DNS server error', protocol: 'udp', err });
47
- });
41
+ });
42
+
43
+ udpServer.on('error', err => {
44
+ logger.error({ msg: 'DNS server error', protocol: 'udp', err });
45
+ });
48
46
 
49
47
  // create TCP server
50
- createDNSTcpServer((request, send) => {
48
+ const tcpServer = createDNSTcpServer((request, send) => {
51
49
  // filter out unsupported requests (eg. EDNS)
52
50
  if (request.additionals && request.additionals.length) {
53
51
  request.additionals = request.additionals.filter(additional => SUPPORTED_TYPES.has(additional.type));
@@ -58,17 +56,31 @@ const init = async () => {
58
56
  .catch(err => {
59
57
  logger.error({ msg: 'Failed to send DNS response', protocol: 'tcp', err });
60
58
  });
61
- })
62
- .listen(config.dns.port, config.dns.host, () => {
59
+ });
60
+
61
+ tcpServer.on('error', err => {
62
+ let method = 'error';
63
+ if (err && ['ECONNRESET'].includes(err.code)) {
64
+ method = 'trace';
65
+ }
66
+ logger[method]({ msg: 'DNS server error', protocol: 'tcp', err });
67
+ });
68
+
69
+ await new Promise(resolve => {
70
+ udpServer.listen(config.dns.port, config.dns.host, () => {
71
+ logger.info({ msg: 'DNS server listening', protocol: 'udp', host: config.dns.host, port: config.dns.port });
72
+ resolve();
73
+ });
74
+ });
75
+
76
+ await new Promise(resolve => {
77
+ tcpServer.listen(config.dns.port, config.dns.host, () => {
63
78
  logger.info({ msg: 'DNS server listening', protocol: 'tcp', host: config.dns.host, port: config.dns.port });
64
- })
65
- .on('error', err => {
66
- let method = 'error';
67
- if (err && ['ECONNRESET'].includes(err.code)) {
68
- method = 'trace';
69
- }
70
- logger[method]({ msg: 'DNS server error', protocol: 'tcp', err });
79
+ resolve();
71
80
  });
81
+ });
82
+
83
+ return { udpServer, tcpServer };
72
84
  };
73
85
 
74
86
  module.exports = init;
@@ -2,7 +2,7 @@
2
2
 
3
3
  const net = require('net');
4
4
  const EventEmitter = require('events');
5
- const Packet = require('dns2/packet');
5
+ const { Packet } = require('dns2');
6
6
  const logger = require('./logger').child({ component: 'dns-tcp-server' });
7
7
 
8
8
  class DNSTcpServer extends EventEmitter {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const udp = require('dgram');
4
4
  const EventEmitter = require('events');
5
- const Packet = require('dns2/packet');
5
+ const { Packet } = require('dns2');
6
6
  const logger = require('./logger').child({ component: 'dns-udp-server' });
7
7
 
8
8
  /**
package/lib/logger.js CHANGED
@@ -11,6 +11,9 @@ if (threadId) {
11
11
  logger = logger.child({ tid: threadId });
12
12
  }
13
13
 
14
+ // No-op by default; lib/sentry.js overrides this with a real reporter when a DSN is configured
15
+ logger.notifyError = () => false;
16
+
14
17
  process.on('uncaughtException', err => {
15
18
  logger.fatal({
16
19
  msg: 'uncaughtException',
@@ -254,7 +254,16 @@ const setupHttps = () =>
254
254
  },
255
255
  (req, res) => {
256
256
  req.proto = 'https';
257
- middleware(req, res);
257
+
258
+ try {
259
+ middleware(req, res);
260
+ } catch (err) {
261
+ logger.error({ msg: 'Failed to process request', err });
262
+ res.statusCode = 500;
263
+ res.setHeader('Content-Type', 'text/html');
264
+ return res.end(errors.error500({}));
265
+ }
266
+
258
267
  handler(req, res).catch(err => {
259
268
  res.statusCode = 500;
260
269
  res.setHeader('Content-Type', 'text/html');
@@ -334,7 +343,16 @@ const setupHttp = () =>
334
343
  new Promise((resolve, reject) => {
335
344
  const server = http.createServer((req, res) => {
336
345
  req.proto = 'http';
337
- middleware(req, res);
346
+
347
+ try {
348
+ middleware(req, res);
349
+ } catch (err) {
350
+ logger.error({ msg: 'Failed to process request', err });
351
+ res.statusCode = 500;
352
+ res.setHeader('Content-Type', 'text/html');
353
+ return res.end(errors.error500({}));
354
+ }
355
+
338
356
  handler(req, res).catch(err => {
339
357
  res.statusCode = 500;
340
358
  res.setHeader('Content-Type', 'text/html');
package/lib/sentry.js ADDED
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ /* eslint global-require: 0 */
4
+
5
+ const config = require('wild-config');
6
+ const packageData = require('../package.json');
7
+ const logger = require('./logger');
8
+
9
+ // Initialize Sentry error tracking. With no DSN configured, error reporting stays
10
+ // disabled and logger.notifyError keeps its no-op default from lib/logger.js.
11
+ function initSentry(worker) {
12
+ // The SENTRY_DSN environment variable overrides the configured value, otherwise
13
+ // fall back to the wild-config value. An empty DSN disables error reporting.
14
+ const dsn = (process.env.SENTRY_DSN || (config.sentry && config.sentry.dsn) || '').trim();
15
+ if (!dsn) {
16
+ return;
17
+ }
18
+
19
+ // require lazily, the SDK loads several hundred modules in every worker,
20
+ // so only pay that cost when error tracking is actually enabled
21
+ const Sentry = require('@sentry/node');
22
+
23
+ Sentry.init({
24
+ dsn,
25
+ release: packageData.version,
26
+ environment: process.env.NODE_ENV || 'development',
27
+ // Error capture only: skip the OpenTelemetry setup and the default
28
+ // integrations that patch http/fetch/console on hot paths. The uncaught
29
+ // exception / unhandled rejection integrations are added back explicitly
30
+ // so crashes are still reported (Bugsnag's autoDetectErrors did this).
31
+ skipOpenTelemetrySetup: true,
32
+ defaultIntegrations: false,
33
+ integrations: [
34
+ Sentry.eventFiltersIntegration(),
35
+ Sentry.functionToStringIntegration(),
36
+ Sentry.linkedErrorsIntegration(),
37
+ Sentry.contextLinesIntegration(),
38
+ Sentry.nodeContextIntegration(),
39
+ Sentry.modulesIntegration(),
40
+ // captures, flushes and exits the worker so the supervisor restarts it
41
+ Sentry.onUncaughtExceptionIntegration(),
42
+ // captures and warns, but does not exit (matches the previous behaviour)
43
+ Sentry.onUnhandledRejectionIntegration({ mode: 'warn' })
44
+ ],
45
+ initialScope: {
46
+ tags: { worker, app: packageData.name }
47
+ }
48
+ });
49
+
50
+ // Signals to the worker bootstraps that an error reporter with its own
51
+ // crash handler is active, so closeProcess() should let Sentry flush and
52
+ // exit instead of exiting immediately (which would drop the in-flight event).
53
+ logger.errorReportingEnabled = true;
54
+
55
+ logger.notifyError = (err, opts) => {
56
+ let captureContext = {};
57
+ if (opts && opts.level) {
58
+ captureContext.level = opts.level;
59
+ }
60
+ if (opts && opts.context) {
61
+ captureContext.tags = { context: opts.context };
62
+ }
63
+ if (opts && opts.meta && Object.keys(opts.meta).length) {
64
+ captureContext.contexts = { error: opts.meta };
65
+ }
66
+ Sentry.captureException(err, captureContext);
67
+ };
68
+
69
+ logger.info({ msg: 'Enabled Sentry error reporting', worker });
70
+ }
71
+
72
+ module.exports = { initSentry };
package/lib/tools.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const cachedResolver = require('./cached-resolver');
4
4
  const punycode = require('punycode/');
5
- const Joi = require('@hapi/joi');
5
+ const Joi = require('joi');
6
6
 
7
7
  const emailSchema = Joi.string().email({}).required();
8
8
 
package/lib/zone-store.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const punycode = require('punycode/');
4
- const shortid = require('shortid');
4
+ const { nanoid } = require('nanoid');
5
5
  const db = require('./db');
6
6
  const logger = require('./logger').child({ component: 'zone-store' });
7
7
  const { normalizeDomain } = require('./tools');
@@ -70,8 +70,8 @@ class ZoneStore {
70
70
  let recordKey = `d:${name}:r:${type}`;
71
71
  let res = await this.db.redisWrite.multi().hdel(recordKey, hid).exists(recordKey).exec();
72
72
 
73
- if (res[res.length - 1] && !res[res.length - 1][0] && !res[res.length - 1][0]) {
74
- // key was deleted, update zone
73
+ if (res[res.length - 1] && !res[res.length - 1][0] && !res[res.length - 1][1]) {
74
+ // the record key no longer exists (no entries left), remove it from the zone
75
75
  await this.db.redisWrite.srem(`d:${zone}:z`, recordKey);
76
76
  }
77
77
 
@@ -356,7 +356,7 @@ class ZoneStore {
356
356
  }
357
357
 
358
358
  let recordKey = `d:${name}:r:${type}`;
359
- let hid = shortid.generate();
359
+ let hid = nanoid();
360
360
 
361
361
  let id = this.getFullId(name, type, hid);
362
362
 
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "pending-dns",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "description": "Lightweight API driven DNS server",
5
+ "productTitle": "PendingDNS",
5
6
  "main": "index.js",
7
+ "private": false,
8
+ "engines": {
9
+ "node": ">=18"
10
+ },
6
11
  "scripts": {
7
12
  "start": "node server.js",
8
- "test": "grunt",
9
- "build-source": "rm -rf node_modules package-lock.json && npm install && npm run licenses && rm -rf node_modules package-lock.json && npm install --production && rm -rf package-lock.json",
10
- "build-dist-fast": "npx pkg --debug package.json && rm -rf package-lock.json && npm install",
11
- "build-dist": "npx pkg --compress Brotli package.json && rm -rf package-lock.json && npm install",
12
- "licenses": "license-report --only=prod --output=table --config license-report-config.json > licenses.txt"
13
+ "lint": "eslint .",
14
+ "test": "NODE_ENV=test node --test --test-force-exit --test-concurrency=1 test/*.test.js",
15
+ "build-source": "rm -rf node_modules && npm install && npm run licenses && rm -rf node_modules && npm ci --omit=dev",
16
+ "build-dist-fast": "pkg --debug package.json && npm install",
17
+ "build-dist": "pkg --compress Brotli package.json && npm install",
18
+ "licenses": "license-report --only=prod --output=table --config license-report-config.json > licenses.txt",
19
+ "update": "rm -rf node_modules package-lock.json && ncu -u && npm install"
13
20
  },
14
21
  "repository": {
15
22
  "type": "git",
@@ -28,55 +35,58 @@
28
35
  },
29
36
  "homepage": "https://github.com/postalsys/pending-dns#readme",
30
37
  "devDependencies": {
31
- "eslint": "8.47.0",
38
+ "@eslint/eslintrc": "3.3.5",
39
+ "@eslint/js": "9.39.4",
40
+ "eslint": "9.39.4",
32
41
  "eslint-config-nodemailer": "1.2.0",
33
- "eslint-config-prettier": "9.0.0",
34
- "grunt": "1.6.1",
35
- "grunt-cli": "1.4.3",
36
- "grunt-eslint": "24.3.0",
37
- "license-report": "6.4.0"
42
+ "eslint-config-prettier": "10.1.8",
43
+ "license-report": "6.8.5"
38
44
  },
39
45
  "dependencies": {
40
- "@bugsnag/js": "7.21.0",
41
46
  "@fidm/x509": "1.2.1",
42
47
  "@hapi/boom": "10.0.1",
43
- "@hapi/hapi": "21.3.2",
44
- "@hapi/inert": "7.1.0",
45
- "@hapi/joi": "17.1.1",
48
+ "@hapi/hapi": "21.4.9",
49
+ "@hapi/inert": "7.1.2",
46
50
  "@hapi/vision": "7.0.3",
47
51
  "@root/acme": "3.1.0",
48
52
  "@root/csr": "0.8.1",
49
- "dns2": "2.1.0",
50
- "handlebars": "4.7.8",
51
- "hapi-pino": "12.1.0",
52
- "hapi-swagger": "17.1.0",
53
+ "@sentry/node": "10.57.0",
54
+ "dns2": "3.0.0",
55
+ "handlebars": "4.7.9",
56
+ "hapi-pino": "13.0.0",
57
+ "hapi-swagger": "17.3.2",
53
58
  "http-proxy": "1.18.1",
54
- "ioredfour": "1.2.0-ioredis-07",
55
- "ioredis": "5.3.2",
56
- "ipaddr.js": "2.1.0",
59
+ "ioredfour": "1.4.1",
60
+ "ioredis": "5.11.1",
61
+ "ipaddr.js": "2.4.0",
62
+ "joi": "17.13.4",
57
63
  "minimist": "1.2.8",
58
- "node-rsa": "1.1.1",
64
+ "nanoid": "3.3.12",
59
65
  "pem-jwk": "2.0.0",
60
- "pino": "8.15.0",
61
- "punycode": "2.3.0",
62
- "shortid": "2.2.16",
63
- "uuid": "9.0.0",
64
- "wild-config": "1.7.0"
66
+ "pino": "10.3.1",
67
+ "punycode": "2.3.1",
68
+ "wild-config": "1.7.1"
65
69
  },
66
70
  "bin": {
67
71
  "pending-dns": "bin/pending-dns.js"
68
72
  },
69
73
  "pkg": {
74
+ "scripts": [
75
+ "lib/**/*.js",
76
+ "workers/**/*.js"
77
+ ],
70
78
  "assets": [
79
+ "lib/lua/**/*",
80
+ "config/*.pem",
71
81
  "licenses.txt",
72
82
  "LICENSE.txt",
73
83
  "help.txt"
74
84
  ],
75
85
  "targets": [
76
- "node18-linux-x64",
77
- "node18-macos-x64",
78
- "node18-macos-arm64",
79
- "node18-win-x64"
86
+ "node24-linux-x64",
87
+ "node24-macos-x64",
88
+ "node24-macos-arm64",
89
+ "node24-win-x64"
80
90
  ],
81
91
  "outputPath": "ee-dist"
82
92
  }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "packages": {
4
+ ".": {
5
+ "release-type": "node",
6
+ "package-name": "pending-dns",
7
+ "changelog-path": "CHANGELOG.md",
8
+ "bump-minor-pre-major": false,
9
+ "draft": false,
10
+ "prerelease": false
11
+ }
12
+ }
13
+ }
package/server.js CHANGED
@@ -7,7 +7,6 @@ const argv = process.argv.slice(2);
7
7
  const config = require('wild-config');
8
8
  const logger = require('./lib/logger').child({ component: 'server' });
9
9
  const pathlib = require('path');
10
- const packageData = require('./package.json');
11
10
  const { Worker, SHARE_ENV } = require('worker_threads');
12
11
  const { isemail } = require('./lib/tools');
13
12
 
@@ -16,28 +15,7 @@ if (!config.acme || !isemail(config.acme.email)) {
16
15
  process.exit(51);
17
16
  }
18
17
 
19
- const Bugsnag = require('@bugsnag/js');
20
- if (process.env.BUGSNAG_API_KEY) {
21
- Bugsnag.start({
22
- apiKey: process.env.BUGSNAG_API_KEY,
23
- appVersion: packageData.version,
24
- logger: {
25
- debug(...args) {
26
- logger.debug({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
27
- },
28
- info(...args) {
29
- logger.debug({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
30
- },
31
- warn(...args) {
32
- logger.warn({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
33
- },
34
- error(...args) {
35
- logger.error({ msg: args.shift(), worker: 'main', source: 'bugsnag', args: args.length ? args : undefined });
36
- }
37
- }
38
- });
39
- logger.notifyError = Bugsnag.notify.bind(Bugsnag);
40
- }
18
+ require('./lib/sentry').initSentry('main');
41
19
 
42
20
  let closing = false;
43
21
 
@@ -109,7 +87,10 @@ const closeProcess = (code, errType, err) => {
109
87
  err
110
88
  });
111
89
 
112
- if (!logger.notifyError) {
90
+ if (!logger.errorReportingEnabled) {
91
+ // No external reporter is handling the crash, so exit here and let the
92
+ // supervisor restart us. When Sentry is enabled its crash integration
93
+ // captures, flushes and exits instead.
113
94
  setTimeout(() => process.exit(code), 10);
114
95
  }
115
96
  };
@@ -35,15 +35,15 @@ Environment="NODE_CONFIG_PATH=/etc/pending-dns.toml"
35
35
 
36
36
  # This is the folder where PendingDNS files reside.
37
37
 
38
- # Normally this folder would include a clean copy from the PendingDNS Github repository + `npm install --production`.
38
+ # Normally this folder would include a clean copy from the PendingDNS Github repository + `npm install --omit=dev`.
39
39
  # To set up:
40
- # git clone git://github.com/postalsys/pending-dns.git
40
+ # git clone https://github.com/postalsys/pending-dns.git
41
41
  # cd pending-dns
42
- # npm install --production
42
+ # npm install --omit=dev
43
43
 
44
44
  # If PendingDNS files are cloned from Github then an easy way to upgrade the application would look like this:
45
45
  # git pull origin master
46
- # npm install --production
46
+ # npm install --omit=dev
47
47
  # And then (as root or a user with sudo privileges):
48
48
  # sudo systemctl restart pending-dns
49
49