roster-server 2.1.34 → 2.2.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.
package/index.js CHANGED
@@ -8,6 +8,8 @@ const Greenlock = require('./vendor/greenlock-express/greenlock-express.js');
8
8
  const GreenlockShim = require('./vendor/greenlock-express/greenlock-shim.js');
9
9
  const log = require('lemonlog')('roster');
10
10
 
11
+ const isBunRuntime = typeof Bun !== 'undefined' || (typeof process !== 'undefined' && process.release?.name === 'bun');
12
+
11
13
  // CRC32 implementation for deterministic port assignment
12
14
  function crc32(str) {
13
15
  const crcTable = [];
@@ -234,9 +236,13 @@ class Roster {
234
236
  this.disableWildcard = options.disableWildcard !== undefined
235
237
  ? parseBooleanFlag(options.disableWildcard, false)
236
238
  : parseBooleanFlag(process.env.ROSTER_DISABLE_WILDCARD, false);
239
+ const combineDefault = isBunRuntime;
237
240
  this.combineWildcardCerts = options.combineWildcardCerts !== undefined
238
- ? parseBooleanFlag(options.combineWildcardCerts, false)
239
- : parseBooleanFlag(process.env.ROSTER_COMBINE_WILDCARD_CERTS, false);
241
+ ? parseBooleanFlag(options.combineWildcardCerts, combineDefault)
242
+ : parseBooleanFlag(process.env.ROSTER_COMBINE_WILDCARD_CERTS, combineDefault);
243
+ if (isBunRuntime && this.combineWildcardCerts) {
244
+ log.info('Bun runtime detected: combined wildcard certificates enabled by default (SNI bypass)');
245
+ }
240
246
 
241
247
  const port = options.port === undefined ? 443 : options.port;
242
248
  if (port === 80 && !this.local) {
@@ -775,38 +781,7 @@ class Roster {
775
781
  }
776
782
  }
777
783
 
778
- const isBunRuntime = typeof Bun !== 'undefined' || process.release?.name === 'bun';
779
- if (isBunRuntime && this.wildcardZones.size > 0) {
780
- const retryDelayMs = Number.isFinite(Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_RETRY_MS))
781
- ? Math.max(1000, Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_RETRY_MS))
782
- : 30000;
783
- const maxAttempts = Number.isFinite(Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_MAX_ATTEMPTS))
784
- ? Math.max(0, Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_MAX_ATTEMPTS))
785
- : 0; // 0 = retry forever
786
-
787
- for (const zone of this.wildcardZones) {
788
- const bootstrapHost = `bun-bootstrap.${zone}`;
789
- const attemptPrewarm = async (attempt = 1) => {
790
- try {
791
- log.warn(`⚠️ Bun runtime detected: prewarming wildcard certificate via ${bootstrapHost} (attempt ${attempt})`);
792
- await greenlockRuntime.get({ servername: bootstrapHost });
793
- log.info(`✅ Bun wildcard prewarm succeeded for ${zone} on attempt ${attempt}`);
794
- } catch (error) {
795
- log.warn(`⚠️ Bun wildcard prewarm failed for ${zone} (attempt ${attempt}): ${error?.message || error}`);
796
- if (maxAttempts > 0 && attempt >= maxAttempts) {
797
- log.warn(`⚠️ Bun wildcard prewarm stopped for ${zone} after ${attempt} attempts`);
798
- return;
799
- }
800
- setTimeout(() => {
801
- attemptPrewarm(attempt + 1).catch(() => {});
802
- }, retryDelayMs);
803
- }
804
- };
805
-
806
- // Background prewarm + retries so HTTPS startup is not blocked by DNS propagation timing.
807
- attemptPrewarm().catch(() => {});
808
- }
809
- }
784
+ const bunTlsHotReloadHandlers = [];
810
785
 
811
786
  // Create dispatcher for each port
812
787
  const createDispatcher = (portData) => {
@@ -963,7 +938,7 @@ class Roster {
963
938
  const primaryDomain = Object.keys(portData.virtualServers)[0];
964
939
  // Under Bun, avoid glx.httpsServer fallback (may serve invalid TLS on :443).
965
940
  // Require concrete PEM files and create native https server directly.
966
- const defaultPems = await ensureBunDefaultPems(primaryDomain);
941
+ let defaultPems = await ensureBunDefaultPems(primaryDomain);
967
942
  httpsServer = https.createServer({
968
943
  ...tlsOpts,
969
944
  key: defaultPems.key,
@@ -977,6 +952,21 @@ class Roster {
977
952
  .catch(callback);
978
953
  }
979
954
  }, dispatcher);
955
+ const reloadBunDefaultTls = async (servername, reason) => {
956
+ const nextPems = await issueAndReloadPemsForServername(servername);
957
+ if (!nextPems) return false;
958
+ defaultPems = nextPems;
959
+ if (typeof httpsServer.setSecureContext === 'function') {
960
+ try {
961
+ httpsServer.setSecureContext({ key: defaultPems.key, cert: defaultPems.cert });
962
+ log.info(`🔄 Bun TLS default certificate reloaded on port ${portNum} (${reason})`);
963
+ } catch (error) {
964
+ log.warn(`⚠️ Failed to hot-reload Bun TLS context on port ${portNum}: ${error?.message || error}`);
965
+ }
966
+ }
967
+ return true;
968
+ };
969
+ bunTlsHotReloadHandlers.push(reloadBunDefaultTls);
980
970
  log.warn(`⚠️ Bun runtime detected: using file-based TLS with SNI for ${primaryDomain} on port ${portNum}`);
981
971
  } else {
982
972
  httpsServer = glx.httpsServer(tlsOpts, dispatcher);
@@ -1036,11 +1026,51 @@ class Roster {
1036
1026
  });
1037
1027
  }
1038
1028
  }
1029
+
1030
+ if (isBunRuntime && !this.combineWildcardCerts && this.wildcardZones.size > 0 && bunTlsHotReloadHandlers.length > 0) {
1031
+ const retryDelayMs = Number.isFinite(Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_RETRY_MS))
1032
+ ? Math.max(1000, Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_RETRY_MS))
1033
+ : 30000;
1034
+ const maxAttempts = Number.isFinite(Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_MAX_ATTEMPTS))
1035
+ ? Math.max(0, Number(process.env.ROSTER_BUN_WILDCARD_PREWARM_MAX_ATTEMPTS))
1036
+ : 0; // 0 = retry forever
1037
+
1038
+ for (const zone of this.wildcardZones) {
1039
+ const bootstrapHost = `bun-bootstrap.${zone}`;
1040
+ const attemptPrewarm = async (attempt = 1) => {
1041
+ try {
1042
+ log.warn(`⚠️ Bun runtime detected: prewarming wildcard certificate via ${bootstrapHost} (attempt ${attempt})`);
1043
+ let reloaded = false;
1044
+ for (const reloadTls of bunTlsHotReloadHandlers) {
1045
+ // Trigger issuance + immediately hot-reload default TLS context when ready.
1046
+ reloaded = (await reloadTls(bootstrapHost, `prewarm ${bootstrapHost} attempt ${attempt}`)) || reloaded;
1047
+ }
1048
+ if (!reloaded) {
1049
+ throw new Error(`No certificate could be loaded for ${bootstrapHost}`);
1050
+ }
1051
+ log.info(`✅ Bun wildcard prewarm succeeded for ${zone} on attempt ${attempt}`);
1052
+ } catch (error) {
1053
+ log.warn(`⚠️ Bun wildcard prewarm failed for ${zone} (attempt ${attempt}): ${error?.message || error}`);
1054
+ if (maxAttempts > 0 && attempt >= maxAttempts) {
1055
+ log.warn(`⚠️ Bun wildcard prewarm stopped for ${zone} after ${attempt} attempts`);
1056
+ return;
1057
+ }
1058
+ setTimeout(() => {
1059
+ attemptPrewarm(attempt + 1).catch(() => {});
1060
+ }, retryDelayMs);
1061
+ }
1062
+ };
1063
+
1064
+ // Background prewarm + retries so HTTPS startup is not blocked by DNS propagation timing.
1065
+ attemptPrewarm().catch(() => {});
1066
+ }
1067
+ }
1039
1068
  });
1040
1069
  }
1041
1070
  }
1042
1071
 
1043
1072
  module.exports = Roster;
1073
+ module.exports.isBunRuntime = isBunRuntime;
1044
1074
  module.exports.wildcardRoot = wildcardRoot;
1045
1075
  module.exports.hostMatchesWildcard = hostMatchesWildcard;
1046
1076
  module.exports.wildcardSubjectForHost = wildcardSubjectForHost;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "2.1.34",
3
+ "version": "2.2.0",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,6 +8,7 @@ const http = require('http');
8
8
  const os = require('os');
9
9
  const Roster = require('../index.js');
10
10
  const {
11
+ isBunRuntime,
11
12
  wildcardRoot,
12
13
  hostMatchesWildcard,
13
14
  wildcardSubjectForHost,
@@ -324,6 +325,14 @@ describe('Roster', () => {
324
325
  else process.env.ROSTER_COMBINE_WILDCARD_CERTS = previous;
325
326
  }
326
327
  });
328
+ it('defaults combineWildcardCerts based on isBunRuntime', () => {
329
+ const roster = new Roster({ local: false });
330
+ assert.strictEqual(roster.combineWildcardCerts, isBunRuntime);
331
+ });
332
+ it('explicit combineWildcardCerts=false overrides Bun default', () => {
333
+ const roster = new Roster({ local: false, combineWildcardCerts: false });
334
+ assert.strictEqual(roster.combineWildcardCerts, false);
335
+ });
327
336
  });
328
337
 
329
338
  describe('register (normal domain)', () => {