@abtnode/router-provider 1.17.7-beta-20251227-001958-ea2ba3f5 → 1.17.7-beta-20251229-085620-84f09930
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/lib/nginx/includes/params +1 -1
- package/lib/nginx/index.js +587 -146
- package/lib/util.js +8 -11
- package/package.json +7 -7
package/lib/nginx/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const pick = require('lodash/pick');
|
|
|
14
14
|
const camelCase = require('lodash/camelCase');
|
|
15
15
|
const toLower = require('lodash/toLower');
|
|
16
16
|
const isEmpty = require('lodash/isEmpty');
|
|
17
|
+
const objectHash = require('object-hash');
|
|
17
18
|
const formatBackSlash = require('@abtnode/util/lib/format-back-slash');
|
|
18
19
|
const {
|
|
19
20
|
ROUTING_RULE_TYPES,
|
|
@@ -30,6 +31,7 @@ const {
|
|
|
30
31
|
CSP_SYSTEM_SOURCES,
|
|
31
32
|
CSP_THIRD_PARTY_SOURCES,
|
|
32
33
|
CSP_ICONIFY_SOURCES,
|
|
34
|
+
DEFAULT_WELLKNOWN_PORT,
|
|
33
35
|
} = require('@abtnode/constant');
|
|
34
36
|
const { toHex } = require('@ocap/util');
|
|
35
37
|
const promiseRetry = require('promise-retry');
|
|
@@ -137,6 +139,7 @@ class NginxProvider extends BaseProvider {
|
|
|
137
139
|
this.securityLog = path.join(this.logDir, 'modsecurity.log');
|
|
138
140
|
this.tmpDir = path.join(this.configDir, 'tmp');
|
|
139
141
|
this.certDir = path.join(this.configDir, 'certs');
|
|
142
|
+
this.sitesDir = path.join(this.configDir, 'sites');
|
|
140
143
|
this.cacheDir = path.join(this.configDir, 'cache');
|
|
141
144
|
this.includesDir = path.join(this.configDir, 'includes');
|
|
142
145
|
this.wwwDir = path.join(this.configDir, 'www');
|
|
@@ -152,6 +155,14 @@ class NginxProvider extends BaseProvider {
|
|
|
152
155
|
this.conf = null; // nginx `conf` object
|
|
153
156
|
this.requestLimit = null;
|
|
154
157
|
|
|
158
|
+
// Hash storage for incremental updates
|
|
159
|
+
this.hashFilePath = path.join(this.configDir, 'config-hashes.json');
|
|
160
|
+
this.configHashes = {
|
|
161
|
+
global: null,
|
|
162
|
+
blocklets: new Map(), // blockletDid -> hash
|
|
163
|
+
};
|
|
164
|
+
this._loadHashes();
|
|
165
|
+
|
|
155
166
|
logger.info('nginx provider config', {
|
|
156
167
|
configDir,
|
|
157
168
|
httpPort: this.httpPort,
|
|
@@ -160,7 +171,7 @@ class NginxProvider extends BaseProvider {
|
|
|
160
171
|
});
|
|
161
172
|
|
|
162
173
|
// ensure directories
|
|
163
|
-
[this.configDir, this.logDir, this.cacheDir, this.tmpDir, this.certDir].forEach((dir) => {
|
|
174
|
+
[this.configDir, this.logDir, this.cacheDir, this.tmpDir, this.certDir, this.sitesDir].forEach((dir) => {
|
|
164
175
|
if (!fs.existsSync(dir)) {
|
|
165
176
|
try {
|
|
166
177
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -208,12 +219,13 @@ class NginxProvider extends BaseProvider {
|
|
|
208
219
|
wafDisabledBlocklets = [],
|
|
209
220
|
enableDefaultServer = false,
|
|
210
221
|
enableIpServer = false,
|
|
222
|
+
skipBlockletSites = false,
|
|
211
223
|
} = {}) {
|
|
212
224
|
logger.info('update nginx config', {
|
|
213
|
-
snapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
214
225
|
enableDefaultServer,
|
|
215
226
|
enableIpServer,
|
|
216
227
|
cacheEnabled,
|
|
228
|
+
skipBlockletSites,
|
|
217
229
|
});
|
|
218
230
|
|
|
219
231
|
if (!Array.isArray(routingTable)) {
|
|
@@ -230,125 +242,348 @@ class NginxProvider extends BaseProvider {
|
|
|
230
242
|
return new Promise((resolve, reject) => {
|
|
231
243
|
const confTemplate = this.getConfTemplate(proxyPolicy);
|
|
232
244
|
|
|
233
|
-
NginxConfFile.createFromSource(confTemplate, (err, conf) => {
|
|
245
|
+
NginxConfFile.createFromSource(confTemplate, async (err, conf) => {
|
|
234
246
|
if (err) {
|
|
235
247
|
logger.error('createFromSource error', { err });
|
|
236
248
|
reject(new Error(err.message));
|
|
237
249
|
return;
|
|
238
250
|
}
|
|
239
251
|
|
|
240
|
-
|
|
252
|
+
try {
|
|
253
|
+
this.conf = conf;
|
|
241
254
|
|
|
242
|
-
|
|
243
|
-
|
|
255
|
+
conf.on('flushed', () => resolve());
|
|
256
|
+
conf.live(this.configPath);
|
|
244
257
|
|
|
245
|
-
|
|
258
|
+
const { sites } = formatRoutingTable(routingTable);
|
|
246
259
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
this._addExposeServices(conf, services);
|
|
260
|
+
// Cache zones are now defined statically in the main template (blockletProxy)
|
|
261
|
+
// No need to add per-blocklet cache zones dynamically
|
|
262
|
+
conf.nginx.http._add('server_tokens', 'off');
|
|
263
|
+
this._addCommonResHeaders(conf.nginx.http, commonHeaders);
|
|
264
|
+
this._addExposeServices(conf, services);
|
|
253
265
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
266
|
+
if (requestLimit) {
|
|
267
|
+
this.requestLimit = requestLimit;
|
|
268
|
+
this.addRequestLimiting(conf.nginx.http, requestLimit);
|
|
269
|
+
}
|
|
270
|
+
if (blockPolicy) {
|
|
271
|
+
this.updateBlacklist(blockPolicy.enabled ? blockPolicy.blacklist : []);
|
|
272
|
+
} else {
|
|
273
|
+
this.updateBlacklist([]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.updateWhitelist();
|
|
263
277
|
|
|
264
|
-
|
|
278
|
+
this.updateProxyPolicy(proxyPolicy, commonHeaders);
|
|
265
279
|
|
|
266
|
-
|
|
280
|
+
const allRules = sites.reduce((acc, site) => {
|
|
281
|
+
acc.push(...(site.rules || []));
|
|
282
|
+
return acc;
|
|
283
|
+
}, []);
|
|
267
284
|
|
|
268
|
-
|
|
269
|
-
acc.push(...(site.rules || []));
|
|
270
|
-
return acc;
|
|
271
|
-
}, []);
|
|
285
|
+
this.ensureUpstreamServers(allRules);
|
|
272
286
|
|
|
273
|
-
|
|
287
|
+
this._addModSecurity(conf, wafPolicy, wafDisabledBlocklets);
|
|
274
288
|
|
|
275
|
-
|
|
289
|
+
// Group sites by blockletDid for separate conf files
|
|
290
|
+
const blockletSitesMap = new Map(); // blockletDid -> sites[]
|
|
291
|
+
const systemSites = []; // sites without blockletDid
|
|
276
292
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
293
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
294
|
+
for (const site of sites) {
|
|
295
|
+
if (site.blockletDid && site.blockletDid !== nodeInfo.did) {
|
|
296
|
+
if (!blockletSitesMap.has(site.blockletDid)) {
|
|
297
|
+
blockletSitesMap.set(site.blockletDid, []);
|
|
298
|
+
}
|
|
299
|
+
blockletSitesMap.get(site.blockletDid).push(site);
|
|
300
|
+
} else {
|
|
301
|
+
systemSites.push(site);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
281
304
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
305
|
+
// Process system sites in main conf
|
|
306
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
307
|
+
for (const site of systemSites) {
|
|
308
|
+
const { domain, port, rules, blockletDid } = site;
|
|
309
|
+
const certificate = findCertificate(certificates, domain);
|
|
310
|
+
|
|
311
|
+
const parsedServerName = parseServerName(domain);
|
|
312
|
+
if (!parsedServerName) {
|
|
313
|
+
logger.warn('invalid site, empty server name:', { site: JSON.stringify(site), domain, parsedServerName });
|
|
314
|
+
// eslint-disable-next-line no-continue
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (certificate) {
|
|
319
|
+
// HTTPS configurations
|
|
320
|
+
// update all certs to disk
|
|
321
|
+
certificates.forEach((item) => {
|
|
322
|
+
const crtPath = `${path.join(this.certDir, item.domain.replace('*', '-'))}.crt`;
|
|
323
|
+
const keyPath = `${path.join(this.certDir, item.domain.replace('*', '-'))}.key`;
|
|
324
|
+
fs.writeFileSync(crtPath, item.certificate);
|
|
325
|
+
fs.writeFileSync(keyPath, item.privateKey);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// if match certificate, then add https server
|
|
329
|
+
this._addHttpsServer({
|
|
330
|
+
conf,
|
|
331
|
+
serviceType: site.serviceType,
|
|
332
|
+
locations: rules,
|
|
333
|
+
certificateFileName: certificate.domain,
|
|
334
|
+
serverName: parsedServerName,
|
|
335
|
+
daemonPort: nodeInfo.port,
|
|
336
|
+
commonHeaders,
|
|
337
|
+
blockletDid,
|
|
338
|
+
});
|
|
339
|
+
} else {
|
|
340
|
+
this._addHttpServer({
|
|
341
|
+
conf,
|
|
342
|
+
serviceType: site.serviceType,
|
|
343
|
+
locations: rules,
|
|
344
|
+
serverName: parsedServerName,
|
|
345
|
+
port,
|
|
346
|
+
daemonPort: nodeInfo.port,
|
|
347
|
+
commonHeaders,
|
|
348
|
+
blockletDid,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
287
351
|
}
|
|
288
352
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
353
|
+
// Clean up old site conf files and generate new ones (skip when skipBlockletSites is true)
|
|
354
|
+
if (!skipBlockletSites) {
|
|
355
|
+
this._cleanupSiteConfFiles([...blockletSitesMap.keys()]);
|
|
356
|
+
|
|
357
|
+
// Generate separate conf files for each blocklet
|
|
358
|
+
const blockletConfPromises = [...blockletSitesMap.entries()].map(([blockletDid, blockletSites]) =>
|
|
359
|
+
this._generateBlockletSiteConfFile({
|
|
360
|
+
blockletDid,
|
|
361
|
+
sites: blockletSites,
|
|
362
|
+
certificates,
|
|
363
|
+
nodeInfo,
|
|
364
|
+
commonHeaders,
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
await Promise.all(blockletConfPromises);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
conf.nginx.http._add('include', `${this.getRelativeConfigDir(this.sitesDir)}/*.conf`);
|
|
371
|
+
|
|
372
|
+
if (!enableIpServer) {
|
|
373
|
+
this._addIpBlackHoleServer(conf);
|
|
374
|
+
logger.info('add ip blackhole server success');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (process.env.ABT_NODE_DOMAIN_BLACKLIST) {
|
|
378
|
+
this._addUnknownHostBlackHoleServer(conf, process.env.ABT_NODE_DOMAIN_BLACKLIST);
|
|
379
|
+
logger.info('add unknown host blacklist server success');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (enableDefaultServer) {
|
|
383
|
+
const existDefaultServer = !!sites.find((x) => x.domain === '_');
|
|
384
|
+
if (existDefaultServer) {
|
|
385
|
+
logger.info('default server is declared by blocklet server');
|
|
386
|
+
} else {
|
|
387
|
+
this._addDefaultServer(conf, nodeInfo.port);
|
|
388
|
+
logger.info('add default server success');
|
|
389
|
+
}
|
|
311
390
|
} else {
|
|
312
|
-
this.
|
|
313
|
-
|
|
314
|
-
serviceType: site.serviceType,
|
|
315
|
-
locations: rules,
|
|
316
|
-
serverName: parsedServerName,
|
|
317
|
-
corsAllowedOrigins,
|
|
318
|
-
port,
|
|
319
|
-
daemonPort: nodeInfo.port,
|
|
320
|
-
commonHeaders,
|
|
321
|
-
blockletDid,
|
|
322
|
-
});
|
|
391
|
+
this._addDefaultBlackHoleServer(conf);
|
|
392
|
+
logger.info('add default blackhole server success');
|
|
323
393
|
}
|
|
394
|
+
|
|
395
|
+
this._addStubStatusLocation(conf);
|
|
396
|
+
|
|
397
|
+
// Compute and save hashes for incremental updates
|
|
398
|
+
this._updateHashesAfterFullRegeneration({
|
|
399
|
+
nodeInfo,
|
|
400
|
+
requestLimit,
|
|
401
|
+
blockPolicy,
|
|
402
|
+
proxyPolicy,
|
|
403
|
+
wafPolicy,
|
|
404
|
+
cacheEnabled,
|
|
405
|
+
enableDefaultServer,
|
|
406
|
+
enableIpServer,
|
|
407
|
+
certificates,
|
|
408
|
+
services,
|
|
409
|
+
systemSites,
|
|
410
|
+
blockletSitesMap,
|
|
411
|
+
wafDisabledBlocklets,
|
|
412
|
+
skipBlockletSites,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
conf.flush();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
logger.error('update nginx config error', { error });
|
|
418
|
+
reject(error);
|
|
324
419
|
}
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
}
|
|
325
423
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Update hashes after a full regeneration
|
|
426
|
+
* @private
|
|
427
|
+
*/
|
|
428
|
+
_updateHashesAfterFullRegeneration(params) {
|
|
429
|
+
const {
|
|
430
|
+
nodeInfo,
|
|
431
|
+
requestLimit,
|
|
432
|
+
blockPolicy,
|
|
433
|
+
proxyPolicy,
|
|
434
|
+
wafPolicy,
|
|
435
|
+
cacheEnabled,
|
|
436
|
+
enableDefaultServer,
|
|
437
|
+
enableIpServer,
|
|
438
|
+
certificates,
|
|
439
|
+
services,
|
|
440
|
+
systemSites,
|
|
441
|
+
blockletSitesMap,
|
|
442
|
+
wafDisabledBlocklets,
|
|
443
|
+
skipBlockletSites = false,
|
|
444
|
+
} = params;
|
|
445
|
+
|
|
446
|
+
// Compute and store global hash
|
|
447
|
+
this.configHashes.global = this._computeGlobalConfigHash({
|
|
448
|
+
nodeInfo,
|
|
449
|
+
requestLimit,
|
|
450
|
+
blockPolicy,
|
|
451
|
+
proxyPolicy,
|
|
452
|
+
wafPolicy,
|
|
453
|
+
cacheEnabled,
|
|
454
|
+
enableDefaultServer,
|
|
455
|
+
enableIpServer,
|
|
456
|
+
certificates,
|
|
457
|
+
services,
|
|
458
|
+
systemSites,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Compute and store hashes for each blocklet (skip when skipBlockletSites is true)
|
|
462
|
+
if (!skipBlockletSites) {
|
|
463
|
+
this.configHashes.blocklets.clear();
|
|
464
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
465
|
+
for (const [did, blockletSites] of blockletSitesMap) {
|
|
466
|
+
const wafDisabled = wafDisabledBlocklets.some((b) => b.did === did);
|
|
467
|
+
const hash = this._computeBlockletConfigHash(did, blockletSites, certificates, wafDisabled);
|
|
468
|
+
this.configHashes.blocklets.set(did, hash);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Persist to disk
|
|
473
|
+
this._saveHashes();
|
|
474
|
+
|
|
475
|
+
logger.info('updated hashes after full regeneration', {
|
|
476
|
+
global: this.configHashes.global?.substring(0, 8),
|
|
477
|
+
blockletCount: this.configHashes.blocklets.size,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Clean up old site conf files that are no longer needed
|
|
483
|
+
* @param {string[]} keepDids - list of blockletDids to keep
|
|
484
|
+
*/
|
|
485
|
+
_cleanupSiteConfFiles(keepDids = []) {
|
|
486
|
+
try {
|
|
487
|
+
if (!fs.existsSync(this.sitesDir)) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const existingFiles = fs.readdirSync(this.sitesDir).filter((f) => f.endsWith('.conf'));
|
|
491
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
492
|
+
for (const file of existingFiles) {
|
|
493
|
+
const did = file.replace('.conf', '');
|
|
494
|
+
if (!keepDids.includes(did)) {
|
|
495
|
+
fs.unlinkSync(path.join(this.sitesDir, file));
|
|
496
|
+
logger.info('removed old site conf file', { file });
|
|
329
497
|
}
|
|
498
|
+
}
|
|
499
|
+
} catch (error) {
|
|
500
|
+
logger.error('Failed to cleanup site conf files', { error });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
330
503
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
504
|
+
/**
|
|
505
|
+
* Generate a separate nginx conf file for a blocklet's server blocks
|
|
506
|
+
* @param {object} options
|
|
507
|
+
* @param {string} options.blockletDid - the blocklet DID
|
|
508
|
+
* @param {Array} options.sites - sites belonging to this blocklet
|
|
509
|
+
* @param {Array} options.certificates - available certificates
|
|
510
|
+
* @param {object} options.nodeInfo - node info containing daemonPort
|
|
511
|
+
* @param {object} options.commonHeaders - common response headers
|
|
512
|
+
*/
|
|
513
|
+
// eslint-disable-next-line require-await
|
|
514
|
+
async _generateBlockletSiteConfFile({ blockletDid, sites, certificates, nodeInfo, commonHeaders }) {
|
|
515
|
+
const confPath = path.join(this.sitesDir, `${blockletDid}.conf`);
|
|
516
|
+
// Minimal template with http block - we'll extract just the server blocks
|
|
517
|
+
const template = 'events {}\nhttp {}\n';
|
|
518
|
+
|
|
519
|
+
return new Promise((resolve, reject) => {
|
|
520
|
+
NginxConfFile.createFromSource(template, (err, conf) => {
|
|
521
|
+
if (err) {
|
|
522
|
+
reject(err);
|
|
523
|
+
return;
|
|
334
524
|
}
|
|
335
525
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
526
|
+
try {
|
|
527
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
528
|
+
for (const site of sites) {
|
|
529
|
+
const { domain, rules, port, serviceType } = site;
|
|
530
|
+
const certificate = findCertificate(certificates, domain);
|
|
531
|
+
const parsedServerName = parseServerName(domain);
|
|
532
|
+
|
|
533
|
+
if (!parsedServerName) {
|
|
534
|
+
logger.warn('invalid site, empty server name:', { site: JSON.stringify(site), domain, parsedServerName });
|
|
535
|
+
// eslint-disable-next-line no-continue
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (certificate) {
|
|
540
|
+
// Write certificates to disk
|
|
541
|
+
certificates.forEach((item) => {
|
|
542
|
+
const crtPath = `${path.join(this.certDir, item.domain.replace('*', '-'))}.crt`;
|
|
543
|
+
const keyPath = `${path.join(this.certDir, item.domain.replace('*', '-'))}.key`;
|
|
544
|
+
fs.writeFileSync(crtPath, item.certificate);
|
|
545
|
+
fs.writeFileSync(keyPath, item.privateKey);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
this._addHttpsServer({
|
|
549
|
+
conf,
|
|
550
|
+
serviceType,
|
|
551
|
+
locations: rules,
|
|
552
|
+
certificateFileName: certificate.domain,
|
|
553
|
+
serverName: parsedServerName,
|
|
554
|
+
daemonPort: nodeInfo.port,
|
|
555
|
+
commonHeaders,
|
|
556
|
+
blockletDid,
|
|
557
|
+
});
|
|
558
|
+
} else {
|
|
559
|
+
this._addHttpServer({
|
|
560
|
+
conf,
|
|
561
|
+
serviceType,
|
|
562
|
+
locations: rules,
|
|
563
|
+
serverName: parsedServerName,
|
|
564
|
+
port,
|
|
565
|
+
daemonPort: nodeInfo.port,
|
|
566
|
+
commonHeaders,
|
|
567
|
+
blockletDid,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
343
570
|
}
|
|
344
|
-
} else {
|
|
345
|
-
this._addDefaultBlackHoleServer(conf);
|
|
346
|
-
logger.info('add default blackhole server success');
|
|
347
|
-
}
|
|
348
571
|
|
|
349
|
-
|
|
572
|
+
// Extract server blocks from http { } wrapper
|
|
573
|
+
const fullConfText = conf.toString();
|
|
574
|
+
const serverBlocksMatch = fullConfText.match(/http\s*\{([\s\S]*)\}/);
|
|
575
|
+
const serverBlocks = serverBlocksMatch?.[1]?.trim() || '';
|
|
350
576
|
|
|
351
|
-
|
|
577
|
+
if (serverBlocks) {
|
|
578
|
+
fs.writeFileSync(confPath, serverBlocks);
|
|
579
|
+
logger.info('generated blocklet site conf', { blockletDid, confPath });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
resolve();
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logger.error('Failed to generate blocklet site conf', { blockletDid, error });
|
|
585
|
+
reject(error);
|
|
586
|
+
}
|
|
352
587
|
});
|
|
353
588
|
});
|
|
354
589
|
}
|
|
@@ -537,17 +772,23 @@ class NginxProvider extends BaseProvider {
|
|
|
537
772
|
return;
|
|
538
773
|
}
|
|
539
774
|
|
|
775
|
+
// Check for static serving (public static blocklets served directly by Nginx)
|
|
776
|
+
if (type === ROUTING_RULE_TYPES.BLOCKLET && args.staticRoot) {
|
|
777
|
+
this._addStaticLocation(args);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
540
781
|
this._addBlockletTypeLocation(args);
|
|
541
782
|
}
|
|
542
783
|
|
|
543
|
-
addUpstreamServer(port) {
|
|
784
|
+
addUpstreamServer(port, keepalive = '2') {
|
|
544
785
|
this.conf.nginx.http._add('upstream', getUpstreamName(port));
|
|
545
786
|
const upstream = this.conf.nginx.http.upstream.length
|
|
546
787
|
? this.conf.nginx.http.upstream[this.conf.nginx.http.upstream.length - 1]
|
|
547
788
|
: this.conf.nginx.http.upstream;
|
|
548
789
|
|
|
549
790
|
upstream._add('server', `127.0.0.1:${port} max_fails=1 fail_timeout=2s`);
|
|
550
|
-
upstream._add('keepalive',
|
|
791
|
+
upstream._add('keepalive', keepalive);
|
|
551
792
|
}
|
|
552
793
|
|
|
553
794
|
ensureUpstreamServers(rules) {
|
|
@@ -556,7 +797,7 @@ class NginxProvider extends BaseProvider {
|
|
|
556
797
|
const servicePort = process.env.ABT_NODE_SERVICE_PORT;
|
|
557
798
|
|
|
558
799
|
if (!upstreamMap.has(servicePort)) {
|
|
559
|
-
this.addUpstreamServer(servicePort);
|
|
800
|
+
this.addUpstreamServer(servicePort, '16');
|
|
560
801
|
upstreamMap.set(servicePort, 1);
|
|
561
802
|
}
|
|
562
803
|
|
|
@@ -564,16 +805,14 @@ class NginxProvider extends BaseProvider {
|
|
|
564
805
|
const rule = rules[i];
|
|
565
806
|
|
|
566
807
|
if (
|
|
567
|
-
[
|
|
568
|
-
ROUTING_RULE_TYPES.DAEMON,
|
|
569
|
-
ROUTING_RULE_TYPES.SERVICE,
|
|
570
|
-
ROUTING_RULE_TYPES.BLOCKLET,
|
|
571
|
-
ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
572
|
-
].includes(rule.type) &&
|
|
808
|
+
[ROUTING_RULE_TYPES.DAEMON, ROUTING_RULE_TYPES.SERVICE].includes(rule.type) &&
|
|
573
809
|
rule.port &&
|
|
574
810
|
!upstreamMap.has(String(rule.port))
|
|
575
811
|
) {
|
|
576
|
-
this.addUpstreamServer(rule.port);
|
|
812
|
+
this.addUpstreamServer(rule.port, '16');
|
|
813
|
+
upstreamMap.set(String(rule.port), 1);
|
|
814
|
+
} else if (rule.port === DEFAULT_WELLKNOWN_PORT && !upstreamMap.has(String(rule.port))) {
|
|
815
|
+
this.addUpstreamServer(rule.port, '16');
|
|
577
816
|
upstreamMap.set(String(rule.port), 1);
|
|
578
817
|
}
|
|
579
818
|
}
|
|
@@ -610,7 +849,6 @@ class NginxProvider extends BaseProvider {
|
|
|
610
849
|
ruleId,
|
|
611
850
|
type,
|
|
612
851
|
proxyBehavior,
|
|
613
|
-
commonHeaders,
|
|
614
852
|
cacheGroup,
|
|
615
853
|
pageGroup,
|
|
616
854
|
serviceType,
|
|
@@ -619,13 +857,11 @@ class NginxProvider extends BaseProvider {
|
|
|
619
857
|
|
|
620
858
|
const location = this._getLastLocation(server);
|
|
621
859
|
|
|
622
|
-
this._addCommonResHeaders(location, commonHeaders);
|
|
623
860
|
if (!cacheGroup && !suffix) {
|
|
624
861
|
this._addTailSlashRedirection(location, prefix); // Note: 末尾 "/" 的重定向要放在 CORS(OPTIONS) 响应之后, 这样不会影响 OPTIONS 的响应
|
|
625
862
|
}
|
|
626
863
|
|
|
627
864
|
if (did) {
|
|
628
|
-
location._add('set', `$did "${did}"`);
|
|
629
865
|
location._add('proxy_set_header', `X-Blocklet-Did "${did}"`);
|
|
630
866
|
if (componentId) {
|
|
631
867
|
location._add('proxy_set_header', `X-Blocklet-Component-Id "${componentId}"`);
|
|
@@ -688,7 +924,47 @@ class NginxProvider extends BaseProvider {
|
|
|
688
924
|
location._add('rewrite', `^${prefix}/?(.*) ${`${target}/`.replace(/\/\//g, '/')}$1 break`);
|
|
689
925
|
}
|
|
690
926
|
|
|
691
|
-
|
|
927
|
+
if (port === DEFAULT_WELLKNOWN_PORT) {
|
|
928
|
+
location._add('proxy_pass', `http://${getUpstreamName(port)}`);
|
|
929
|
+
} else {
|
|
930
|
+
location._add('proxy_pass', `http://127.0.0.1:${port}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Add a static location block for serving files directly from Nginx
|
|
936
|
+
* Used for public static blocklets to bypass blocklet-service
|
|
937
|
+
*
|
|
938
|
+
* Generated config:
|
|
939
|
+
* location /app-path {
|
|
940
|
+
* alias /path/to/static/files/;
|
|
941
|
+
* try_files $uri $uri/ /app-path/index.html;
|
|
942
|
+
* expires 30d;
|
|
943
|
+
* add_header Cache-Control "public, max-age=2592000";
|
|
944
|
+
* }
|
|
945
|
+
*/
|
|
946
|
+
_addStaticLocation({ server, prefix, staticRoot, commonHeaders, serviceType }) {
|
|
947
|
+
server._add('location', prefix);
|
|
948
|
+
const location = this._getLastLocation(server);
|
|
949
|
+
|
|
950
|
+
// Serve static files from blocklet directory
|
|
951
|
+
// Use alias to map the location to the static root directory
|
|
952
|
+
location._add('alias', `${staticRoot}/`);
|
|
953
|
+
|
|
954
|
+
// SPA fallback - try file, then directory, then fallback to index.html
|
|
955
|
+
// For paths like /app-path/some/route, if no file exists, serve index.html
|
|
956
|
+
location._add('try_files', `$uri $uri/ ${prefix === '/' ? '' : prefix}/index.html`);
|
|
957
|
+
|
|
958
|
+
// Cache control for static assets
|
|
959
|
+
location._add('expires', '30d');
|
|
960
|
+
location._add('add_header', 'Cache-Control "public, max-age=2592000"');
|
|
961
|
+
|
|
962
|
+
// Security headers
|
|
963
|
+
location._add('add_header', 'X-Content-Type-Options "nosniff"');
|
|
964
|
+
|
|
965
|
+
// Add common response headers
|
|
966
|
+
this._addCommonResHeaders(location, commonHeaders);
|
|
967
|
+
this._addSecurityHeaders(location, serviceType);
|
|
692
968
|
}
|
|
693
969
|
|
|
694
970
|
_addRedirectTypeLocation({ server, url, redirectCode, prefix, suffix, serviceType }) {
|
|
@@ -748,7 +1024,11 @@ class NginxProvider extends BaseProvider {
|
|
|
748
1024
|
location._add('rewrite', `^${targetPrefix}/?(.*) /$1 break`);
|
|
749
1025
|
}
|
|
750
1026
|
|
|
751
|
-
|
|
1027
|
+
if (port === DEFAULT_WELLKNOWN_PORT) {
|
|
1028
|
+
location._add('proxy_pass', `http://${getUpstreamName(port)}`);
|
|
1029
|
+
} else {
|
|
1030
|
+
location._add('proxy_pass', `http://127.0.0.1:${port}`);
|
|
1031
|
+
}
|
|
752
1032
|
}
|
|
753
1033
|
|
|
754
1034
|
_addDirectResponseLocation({ server, response, prefix, suffix }) {
|
|
@@ -786,13 +1066,7 @@ class NginxProvider extends BaseProvider {
|
|
|
786
1066
|
}
|
|
787
1067
|
|
|
788
1068
|
_addCommonHeader(location) {
|
|
789
|
-
location._add('set', '$abt_proto $scheme');
|
|
790
|
-
// use $http_host to keep port when redirecting
|
|
791
|
-
// https://stackoverflow.com/questions/43397365/nginx-keep-port-number-when-301-redirecting
|
|
792
|
-
location._add('set', '$abt_host $http_host');
|
|
793
1069
|
location._add('set', '$abt_query_string ""');
|
|
794
|
-
location._addVerbatimBlock('if ($http_x_forwarded_proto)', 'set $abt_proto $http_x_forwarded_proto;');
|
|
795
|
-
location._addVerbatimBlock('if ($http_x_forwarded_host)', 'set $abt_host $http_x_forwarded_host;');
|
|
796
1070
|
location._addVerbatimBlock('if ($query_string)', 'set $abt_query_string "?$query_string";');
|
|
797
1071
|
}
|
|
798
1072
|
|
|
@@ -822,8 +1096,6 @@ class NginxProvider extends BaseProvider {
|
|
|
822
1096
|
server._add('location', '/_abtnode_502');
|
|
823
1097
|
const location502 = server.location[server.location.length - 1];
|
|
824
1098
|
location502._add('internal');
|
|
825
|
-
location502._addVerbatimBlock('if ($did ~ "^$")', 'set $did "";');
|
|
826
|
-
location502._add('proxy_set_header', 'x-did "$did"');
|
|
827
1099
|
location502._add('proxy_pass', `http://127.0.0.1:${daemonPort}/error/502`);
|
|
828
1100
|
|
|
829
1101
|
server._add('location', '/_abtnode_5xx');
|
|
@@ -847,10 +1119,6 @@ class NginxProvider extends BaseProvider {
|
|
|
847
1119
|
}
|
|
848
1120
|
|
|
849
1121
|
_copyConfigFiles() {
|
|
850
|
-
if (fs.existsSync(this.includesDir)) {
|
|
851
|
-
fs.rmSync(this.includesDir, { recursive: true });
|
|
852
|
-
}
|
|
853
|
-
|
|
854
1122
|
fs.copySync(path.join(__dirname, 'includes'), this.includesDir, { overwrite: true });
|
|
855
1123
|
fs.copySync(path.join(__dirname, '..', 'www'), this.wwwDir, { overwrite: true });
|
|
856
1124
|
}
|
|
@@ -1092,21 +1360,11 @@ class NginxProvider extends BaseProvider {
|
|
|
1092
1360
|
: conf.nginx.stream.server;
|
|
1093
1361
|
}
|
|
1094
1362
|
|
|
1095
|
-
_addHttpServer({
|
|
1096
|
-
locations = [],
|
|
1097
|
-
serverName,
|
|
1098
|
-
conf,
|
|
1099
|
-
corsAllowedOrigins,
|
|
1100
|
-
port,
|
|
1101
|
-
daemonPort,
|
|
1102
|
-
commonHeaders,
|
|
1103
|
-
blockletDid,
|
|
1104
|
-
serviceType,
|
|
1105
|
-
}) {
|
|
1363
|
+
_addHttpServer({ locations = [], serverName, conf, port, daemonPort, commonHeaders, blockletDid, serviceType }) {
|
|
1106
1364
|
const httpServerUnit = this._addHttpServerUnit({ conf, serverName, port });
|
|
1107
1365
|
this._addDefaultLocations({ server: httpServerUnit, daemonPort, serverName });
|
|
1108
1366
|
// eslint-disable-next-line max-len
|
|
1109
|
-
locations.forEach((x) => this._addReverseProxy({ server: httpServerUnit, ...x, serverName,
|
|
1367
|
+
locations.forEach((x) => this._addReverseProxy({ server: httpServerUnit, ...x, serverName, commonHeaders, blockletDid, serviceType })); // prettier-ignore
|
|
1110
1368
|
}
|
|
1111
1369
|
|
|
1112
1370
|
_addHttpsServer({
|
|
@@ -1115,7 +1373,6 @@ class NginxProvider extends BaseProvider {
|
|
|
1115
1373
|
certificateFileName,
|
|
1116
1374
|
serverName,
|
|
1117
1375
|
serviceType,
|
|
1118
|
-
corsAllowedOrigins,
|
|
1119
1376
|
daemonPort,
|
|
1120
1377
|
commonHeaders,
|
|
1121
1378
|
blockletDid,
|
|
@@ -1130,7 +1387,7 @@ class NginxProvider extends BaseProvider {
|
|
|
1130
1387
|
|
|
1131
1388
|
this._addDefaultLocations({ server: httpsServerUnit, daemonPort, serverName });
|
|
1132
1389
|
// eslint-disable-next-line max-len
|
|
1133
|
-
locations.forEach((x) => this._addReverseProxy({ server: httpsServerUnit, ...x, serverName,
|
|
1390
|
+
locations.forEach((x) => this._addReverseProxy({ server: httpsServerUnit, ...x, serverName, commonHeaders, blockletDid, serviceType })); // prettier-ignore
|
|
1134
1391
|
}
|
|
1135
1392
|
|
|
1136
1393
|
_addHttpServerUnit({ conf, serverName, port = '' }) {
|
|
@@ -1294,17 +1551,6 @@ class NginxProvider extends BaseProvider {
|
|
|
1294
1551
|
}
|
|
1295
1552
|
}
|
|
1296
1553
|
|
|
1297
|
-
_addCacheGroups(conf, cacheGroups) {
|
|
1298
|
-
const cacheDir = this.getRelativeConfigDir(formatBackSlash(this.cacheDir));
|
|
1299
|
-
cacheGroups.forEach((group) => {
|
|
1300
|
-
const config = ROUTER_CACHE_GROUPS.blockletProxy;
|
|
1301
|
-
conf.nginx.http._add(
|
|
1302
|
-
'proxy_cache_path',
|
|
1303
|
-
`${cacheDir}/${group} levels=1:2 keys_zone=${group}:${config.minSize} inactive=${config.period} max_size=${config.maxSize}`
|
|
1304
|
-
);
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
1554
|
addRequestLimiting(block, limit) {
|
|
1309
1555
|
if (!limit?.enabled) {
|
|
1310
1556
|
return;
|
|
@@ -1354,18 +1600,26 @@ class NginxProvider extends BaseProvider {
|
|
|
1354
1600
|
}
|
|
1355
1601
|
}
|
|
1356
1602
|
|
|
1357
|
-
updateProxyPolicy(proxyPolicy) {
|
|
1603
|
+
updateProxyPolicy(proxyPolicy, commonHeaders = {}) {
|
|
1358
1604
|
const proxyRaw = fs.readFileSync(path.join(this.includesDir, 'proxy.raw'), 'utf8');
|
|
1359
1605
|
const proxyPolicyFile = path.join(this.includesDir, 'proxy');
|
|
1606
|
+
|
|
1607
|
+
const lines = ['add_header X-Request-ID $request_id;'];
|
|
1608
|
+
Object.keys(commonHeaders).forEach((key) => {
|
|
1609
|
+
lines.push(`add_header ${key} ${commonHeaders[key]};`);
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1360
1612
|
if (proxyPolicy?.enabled) {
|
|
1361
1613
|
fs.writeFileSync(
|
|
1362
1614
|
proxyPolicyFile,
|
|
1363
|
-
[proxyRaw, 'proxy_set_header X-Forwarded-For "$http_x_forwarded_for,$realip_remote_addr";'].join(
|
|
1615
|
+
[...lines, proxyRaw, 'proxy_set_header X-Forwarded-For "$http_x_forwarded_for,$realip_remote_addr";'].join(
|
|
1616
|
+
os.EOL
|
|
1617
|
+
)
|
|
1364
1618
|
);
|
|
1365
1619
|
} else {
|
|
1366
1620
|
fs.writeFileSync(
|
|
1367
1621
|
proxyPolicyFile,
|
|
1368
|
-
[proxyRaw, 'proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;'].join(os.EOL)
|
|
1622
|
+
[...lines, proxyRaw, 'proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;'].join(os.EOL)
|
|
1369
1623
|
);
|
|
1370
1624
|
}
|
|
1371
1625
|
}
|
|
@@ -1518,6 +1772,193 @@ ctl:ruleEngine=${wafPolicy?.enabled ? defaultWAF : 'Off'}"`;
|
|
|
1518
1772
|
return [];
|
|
1519
1773
|
}
|
|
1520
1774
|
}
|
|
1775
|
+
|
|
1776
|
+
// ============================================================================
|
|
1777
|
+
// Hash-based incremental update methods
|
|
1778
|
+
// ============================================================================
|
|
1779
|
+
|
|
1780
|
+
/**
|
|
1781
|
+
* Load config hashes from file on startup
|
|
1782
|
+
*/
|
|
1783
|
+
_loadHashes() {
|
|
1784
|
+
try {
|
|
1785
|
+
if (fs.existsSync(this.hashFilePath)) {
|
|
1786
|
+
const data = JSON.parse(fs.readFileSync(this.hashFilePath, 'utf8'));
|
|
1787
|
+
this.configHashes.global = data.global || null;
|
|
1788
|
+
this.configHashes.blocklets = new Map(Object.entries(data.blocklets || {}));
|
|
1789
|
+
logger.info('loaded config hashes', {
|
|
1790
|
+
global: !!this.configHashes.global,
|
|
1791
|
+
blockletCount: this.configHashes.blocklets.size,
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
logger.warn('failed to load config hashes, will regenerate all', { error: error.message });
|
|
1796
|
+
this.configHashes = { global: null, blocklets: new Map() };
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* Check if hash file exists (indicates non-first startup)
|
|
1802
|
+
* @returns {boolean}
|
|
1803
|
+
*/
|
|
1804
|
+
hasHashFile() {
|
|
1805
|
+
return fs.existsSync(this.hashFilePath) && this.configHashes.global !== null;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
* Persist config hashes to file
|
|
1810
|
+
*/
|
|
1811
|
+
_saveHashes() {
|
|
1812
|
+
try {
|
|
1813
|
+
const data = {
|
|
1814
|
+
global: this.configHashes.global,
|
|
1815
|
+
blocklets: Object.fromEntries(this.configHashes.blocklets),
|
|
1816
|
+
savedAt: new Date().toISOString(),
|
|
1817
|
+
};
|
|
1818
|
+
fs.writeFileSync(this.hashFilePath, JSON.stringify(data, null, 2));
|
|
1819
|
+
logger.debug('saved config hashes');
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
logger.warn('failed to save config hashes', { error: error.message });
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Compute hash for global config (main nginx.conf settings)
|
|
1827
|
+
* @param {object} params - Update parameters
|
|
1828
|
+
* @returns {string} Hash of global config
|
|
1829
|
+
*/
|
|
1830
|
+
_computeGlobalConfigHash(params) {
|
|
1831
|
+
const {
|
|
1832
|
+
nodeInfo = {},
|
|
1833
|
+
requestLimit,
|
|
1834
|
+
blockPolicy,
|
|
1835
|
+
proxyPolicy,
|
|
1836
|
+
wafPolicy,
|
|
1837
|
+
cacheEnabled,
|
|
1838
|
+
enableDefaultServer,
|
|
1839
|
+
enableIpServer,
|
|
1840
|
+
certificates = [],
|
|
1841
|
+
services = [],
|
|
1842
|
+
systemSites = [],
|
|
1843
|
+
} = params;
|
|
1844
|
+
|
|
1845
|
+
const hashInput = {
|
|
1846
|
+
requestLimit: requestLimit?.enabled ? requestLimit : { enabled: false },
|
|
1847
|
+
blockPolicy: blockPolicy?.enabled
|
|
1848
|
+
? { enabled: true, blacklistCount: blockPolicy.blacklist?.length || 0 }
|
|
1849
|
+
: { enabled: false },
|
|
1850
|
+
proxyPolicy,
|
|
1851
|
+
wafPolicy: wafPolicy?.enabled ? wafPolicy : { enabled: false },
|
|
1852
|
+
cacheEnabled,
|
|
1853
|
+
enableDefaultServer,
|
|
1854
|
+
enableIpServer,
|
|
1855
|
+
headers: nodeInfo?.routing?.headers || {},
|
|
1856
|
+
certDomains: certificates.map((c) => c.domain).sort(),
|
|
1857
|
+
services: services.map((s) => ({ port: s.port, protocol: s.protocol })),
|
|
1858
|
+
systemSites: systemSites.map((s) => ({
|
|
1859
|
+
domain: s.domain,
|
|
1860
|
+
rulesHash: objectHash(s.rules || []),
|
|
1861
|
+
})),
|
|
1862
|
+
httpPort: this.httpPort,
|
|
1863
|
+
httpsPort: this.httpsPort,
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
return objectHash(hashInput);
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/**
|
|
1870
|
+
* Compute hash for a specific blocklet's config
|
|
1871
|
+
* @param {string} blockletDid - Blocklet DID
|
|
1872
|
+
* @param {Array} blockletSites - Sites for this blocklet
|
|
1873
|
+
* @param {Array} certificates - All certificates
|
|
1874
|
+
* @param {boolean} wafDisabled - Whether WAF is disabled for this blocklet
|
|
1875
|
+
* @returns {string} Hash of blocklet config
|
|
1876
|
+
*/
|
|
1877
|
+
_computeBlockletConfigHash(blockletDid, blockletSites, certificates, wafDisabled) {
|
|
1878
|
+
const relevantCerts = certificates.filter((c) =>
|
|
1879
|
+
blockletSites.some((s) => findCertificate(certificates, s.domain)?.domain === c.domain)
|
|
1880
|
+
);
|
|
1881
|
+
|
|
1882
|
+
const hashInput = {
|
|
1883
|
+
blockletDid,
|
|
1884
|
+
sites: blockletSites.map((s) => ({
|
|
1885
|
+
domain: s.domain,
|
|
1886
|
+
domainAliases: (s.domainAliases || []).map((a) => (typeof a === 'string' ? a : a.value)).sort(),
|
|
1887
|
+
rulesHash: objectHash(s.rules || []),
|
|
1888
|
+
port: s.port,
|
|
1889
|
+
})),
|
|
1890
|
+
certDomains: relevantCerts.map((c) => c.domain).sort(),
|
|
1891
|
+
wafDisabled,
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
return objectHash(hashInput);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* Update a single blocklet's config file
|
|
1899
|
+
* @param {string} blockletDid - The blocklet DID
|
|
1900
|
+
* @param {object} params - Update parameters
|
|
1901
|
+
*/
|
|
1902
|
+
async updateSingleBlocklet(blockletDid, params) {
|
|
1903
|
+
const { routingTable = [], certificates = [], commonHeaders, nodeInfo = {}, wafDisabledBlocklets = [] } = params;
|
|
1904
|
+
|
|
1905
|
+
const { sites } = formatRoutingTable(routingTable);
|
|
1906
|
+
const blockletSites = sites.filter((s) => s.blockletDid === blockletDid);
|
|
1907
|
+
|
|
1908
|
+
if (blockletSites.length === 0) {
|
|
1909
|
+
logger.warn('updateSingleBlocklet: no sites found for blocklet', { blockletDid });
|
|
1910
|
+
return false;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
await this._generateBlockletSiteConfFile({
|
|
1914
|
+
blockletDid,
|
|
1915
|
+
sites: blockletSites,
|
|
1916
|
+
certificates,
|
|
1917
|
+
nodeInfo,
|
|
1918
|
+
commonHeaders,
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
// Update hash
|
|
1922
|
+
const wafDisabled = wafDisabledBlocklets.some((b) => b.did === blockletDid);
|
|
1923
|
+
const hash = this._computeBlockletConfigHash(blockletDid, blockletSites, certificates, wafDisabled);
|
|
1924
|
+
this.configHashes.blocklets.set(blockletDid, hash);
|
|
1925
|
+
this._saveHashes();
|
|
1926
|
+
|
|
1927
|
+
logger.info('updated single blocklet config', { blockletDid });
|
|
1928
|
+
return true;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* Remove a blocklet's config file
|
|
1933
|
+
* @param {string} blockletDid - The blocklet DID
|
|
1934
|
+
*/
|
|
1935
|
+
_removeBlockletConfig(blockletDid) {
|
|
1936
|
+
const confPath = path.join(this.sitesDir, `${blockletDid}.conf`);
|
|
1937
|
+
try {
|
|
1938
|
+
if (fs.existsSync(confPath)) {
|
|
1939
|
+
fs.unlinkSync(confPath);
|
|
1940
|
+
logger.info('removed blocklet config', { blockletDid, confPath });
|
|
1941
|
+
}
|
|
1942
|
+
this.configHashes.blocklets.delete(blockletDid);
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
logger.error('failed to remove blocklet config', { blockletDid, error: error.message });
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/**
|
|
1949
|
+
* Remove a blocklet and trigger reload
|
|
1950
|
+
* @param {string} blockletDid - The blocklet DID
|
|
1951
|
+
*/
|
|
1952
|
+
async removeBlockletAndReload(blockletDid) {
|
|
1953
|
+
this._removeBlockletConfig(blockletDid);
|
|
1954
|
+
this._saveHashes();
|
|
1955
|
+
await this.reload();
|
|
1956
|
+
logger.info('removed blocklet and reloaded nginx', { blockletDid });
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
getStatus() {
|
|
1960
|
+
return util.getNginxStatus(this.configDir);
|
|
1961
|
+
}
|
|
1521
1962
|
}
|
|
1522
1963
|
|
|
1523
1964
|
NginxProvider.describe = async ({ configDir = '' } = {}) => {
|
package/lib/util.js
CHANGED
|
@@ -3,7 +3,6 @@ const fs = require('fs-extra');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const getPort = require('get-port');
|
|
5
5
|
const portUsed = require('port-used');
|
|
6
|
-
const uniq = require('lodash/uniq');
|
|
7
6
|
const sortBy = require('lodash/sortBy');
|
|
8
7
|
const isValidDomain = require('@arcblock/is-valid-domain');
|
|
9
8
|
const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
|
|
@@ -52,7 +51,6 @@ const concatPath = (prefix = '', suffix = '', root = false) => {
|
|
|
52
51
|
|
|
53
52
|
const formatRoutingTable = (routingTable) => {
|
|
54
53
|
const sites = {};
|
|
55
|
-
const cacheGroups = [];
|
|
56
54
|
const configs = [];
|
|
57
55
|
|
|
58
56
|
routingTable.forEach((site) => {
|
|
@@ -61,20 +59,14 @@ const formatRoutingTable = (routingTable) => {
|
|
|
61
59
|
domain = '_';
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
if (corsAllowedOrigins.includes('*')) {
|
|
66
|
-
corsAllowedOrigins = ['*'];
|
|
67
|
-
}
|
|
68
|
-
configs.push({ domain, corsAllowedOrigins });
|
|
62
|
+
configs.push({ domain });
|
|
69
63
|
|
|
70
64
|
if (!sites[domain]) {
|
|
71
|
-
cacheGroups.push(site.blockletDid);
|
|
72
65
|
sites[domain] = {
|
|
73
66
|
domain,
|
|
74
67
|
blockletDid: site.blockletDid,
|
|
75
68
|
rules: [],
|
|
76
69
|
port: site.port,
|
|
77
|
-
corsAllowedOrigins: site.corsAllowedOrigins,
|
|
78
70
|
serviceType: site.serviceType,
|
|
79
71
|
};
|
|
80
72
|
}
|
|
@@ -107,12 +99,17 @@ const formatRoutingTable = (routingTable) => {
|
|
|
107
99
|
} else {
|
|
108
100
|
rule.port = +x.to.port;
|
|
109
101
|
rule.did = x.to.did;
|
|
110
|
-
|
|
102
|
+
// Use shared cache zone for all blocklets (defined in nginx.conf http block)
|
|
103
|
+
rule.cacheGroup = x.to.cacheGroup ? 'blockletProxy' : '';
|
|
111
104
|
rule.componentId = x.to.componentId;
|
|
112
105
|
rule.target = trimEndSlash(normalizePathPrefix(x.to.target || '/'));
|
|
113
106
|
rule.targetPrefix = x.to.targetPrefix || '';
|
|
114
107
|
rule.services = x.services || [];
|
|
115
108
|
rule.pageGroup = x.to.pageGroup;
|
|
109
|
+
// Static serving fields for engine-based blocklets
|
|
110
|
+
if (x.to.staticRoot) {
|
|
111
|
+
rule.staticRoot = trimEndSlash(x.to.staticRoot);
|
|
112
|
+
}
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
const addRule = (r) => {
|
|
@@ -133,7 +130,7 @@ const formatRoutingTable = (routingTable) => {
|
|
|
133
130
|
sites[domain].rules = rulesWithoutSuffix.concat(rulesWithSuffix);
|
|
134
131
|
});
|
|
135
132
|
|
|
136
|
-
return { sites: Object.values(sites),
|
|
133
|
+
return { sites: Object.values(sites), configs };
|
|
137
134
|
};
|
|
138
135
|
|
|
139
136
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abtnode/router-provider",
|
|
3
|
-
"version": "1.17.7-beta-
|
|
3
|
+
"version": "1.17.7-beta-20251229-085620-84f09930",
|
|
4
4
|
"description": "Routing engine implementations for abt node",
|
|
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/constant": "1.17.7-beta-
|
|
34
|
-
"@abtnode/db-cache": "1.17.7-beta-
|
|
35
|
-
"@abtnode/logger": "1.17.7-beta-
|
|
36
|
-
"@abtnode/router-templates": "1.17.7-beta-
|
|
37
|
-
"@abtnode/util": "1.17.7-beta-
|
|
33
|
+
"@abtnode/constant": "1.17.7-beta-20251229-085620-84f09930",
|
|
34
|
+
"@abtnode/db-cache": "1.17.7-beta-20251229-085620-84f09930",
|
|
35
|
+
"@abtnode/logger": "1.17.7-beta-20251229-085620-84f09930",
|
|
36
|
+
"@abtnode/router-templates": "1.17.7-beta-20251229-085620-84f09930",
|
|
37
|
+
"@abtnode/util": "1.17.7-beta-20251229-085620-84f09930",
|
|
38
38
|
"@arcblock/http-proxy": "^1.19.1",
|
|
39
39
|
"@arcblock/is-valid-domain": "^1.0.5",
|
|
40
40
|
"@ocap/util": "^1.27.16",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"bluebird": "^3.7.2",
|
|
61
61
|
"fs-extra": "^11.2.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "fe2ffc3cf431bbaa89ac802bed793aa1188da4c3"
|
|
64
64
|
}
|