@abtnode/core 1.6.15 → 1.6.19
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/api/node.js +1 -1
- package/lib/blocklet/hooks.js +16 -63
- package/lib/blocklet/manager/disk.js +429 -151
- package/lib/blocklet/migration.js +16 -52
- package/lib/event.js +1 -1
- package/lib/index.js +3 -1
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/router/helper.js +18 -12
- package/lib/router/manager.js +45 -41
- package/lib/states/blocklet-extras.js +39 -3
- package/lib/states/blocklet.js +89 -20
- package/lib/states/node.js +17 -2
- package/lib/util/blocklet.js +288 -67
- package/lib/util/default-node-config.js +1 -1
- package/lib/util/get-domain-for-blocklet.js +2 -2
- package/lib/util/upgrade.js +8 -4
- package/lib/validators/node.js +11 -12
- package/package.json +23 -23
package/lib/states/node.js
CHANGED
|
@@ -49,7 +49,10 @@ class NodeState extends BaseState {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
isInitialized(doc) {
|
|
52
|
-
|
|
52
|
+
const isOwnerConnected = !!doc.nodeOwner;
|
|
53
|
+
const isControlledBy3rdParty =
|
|
54
|
+
!doc.enablePassportIssuance && Array.isArray(doc.trustedPassports) && doc.trustedPassports.length > 0;
|
|
55
|
+
return isOwnerConnected || isControlledBy3rdParty;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -92,13 +95,15 @@ class NodeState extends BaseState {
|
|
|
92
95
|
launcherInfo,
|
|
93
96
|
didRegistry,
|
|
94
97
|
didDomain,
|
|
98
|
+
enablePassportIssuance = true,
|
|
99
|
+
trustedPassports = [],
|
|
95
100
|
} = this.options;
|
|
96
101
|
|
|
97
102
|
if (nodeOwner && !validateOwner(nodeOwner)) {
|
|
98
103
|
return reject(new Error('Node owner is invalid'));
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
const initialized = this.isInitialized({ nodeOwner });
|
|
106
|
+
const initialized = this.isInitialized({ nodeOwner, enablePassportIssuance, trustedPassports });
|
|
102
107
|
|
|
103
108
|
return getDefaultConfigs()
|
|
104
109
|
.then((defaultConfigs) =>
|
|
@@ -126,6 +131,9 @@ class NodeState extends BaseState {
|
|
|
126
131
|
launcherInfo: launcherInfo || undefined,
|
|
127
132
|
didRegistry,
|
|
128
133
|
didDomain,
|
|
134
|
+
enablePassportIssuance,
|
|
135
|
+
trustedPassports,
|
|
136
|
+
customBlockletNumber: 0,
|
|
129
137
|
},
|
|
130
138
|
async (e, data) => {
|
|
131
139
|
if (e) {
|
|
@@ -262,6 +270,13 @@ class NodeState extends BaseState {
|
|
|
262
270
|
getBlockletRegistry() {
|
|
263
271
|
return this.read().then((info) => info.blockletRegistryList.find((item) => item.selected).url);
|
|
264
272
|
}
|
|
273
|
+
|
|
274
|
+
async increaseCustomBlockletNumber() {
|
|
275
|
+
const { _id, customBlockletNumber = 0 } = await this.read();
|
|
276
|
+
const num = customBlockletNumber + 1;
|
|
277
|
+
await this.update(_id, { $set: { customBlockletNumber: num } });
|
|
278
|
+
return num;
|
|
279
|
+
}
|
|
265
280
|
}
|
|
266
281
|
|
|
267
282
|
module.exports = NodeState;
|
package/lib/util/blocklet.js
CHANGED
|
@@ -6,9 +6,11 @@ const os = require('os');
|
|
|
6
6
|
const tar = require('tar');
|
|
7
7
|
const get = require('lodash/get');
|
|
8
8
|
const intersection = require('lodash/intersection');
|
|
9
|
+
const intersectionBy = require('lodash/intersectionBy');
|
|
9
10
|
const streamToPromise = require('stream-to-promise');
|
|
10
11
|
const { Throttle } = require('stream-throttle');
|
|
11
12
|
const ssri = require('ssri');
|
|
13
|
+
const diff = require('deep-diff');
|
|
12
14
|
|
|
13
15
|
const { toHex } = require('@ocap/util');
|
|
14
16
|
const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
|
|
@@ -17,7 +19,10 @@ const sleep = require('@abtnode/util/lib/sleep');
|
|
|
17
19
|
const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
|
|
18
20
|
const CustomError = require('@abtnode/util/lib/custom-error');
|
|
19
21
|
const getFolderSize = require('@abtnode/util/lib/get-folder-size');
|
|
22
|
+
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
23
|
+
const hashFiles = require('@abtnode/util/lib/hash-files');
|
|
20
24
|
const { BLOCKLET_MAX_MEM_LIMIT_IN_MB } = require('@abtnode/constant');
|
|
25
|
+
|
|
21
26
|
const {
|
|
22
27
|
BlockletStatus,
|
|
23
28
|
BlockletSource,
|
|
@@ -29,6 +34,7 @@ const {
|
|
|
29
34
|
BLOCKLET_DEFAULT_PORT_NAME,
|
|
30
35
|
BLOCKLET_INTERFACE_TYPE_WEB,
|
|
31
36
|
BLOCKLET_CONFIGURABLE_KEY,
|
|
37
|
+
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
32
38
|
fromBlockletStatus,
|
|
33
39
|
} = require('@blocklet/meta/lib/constants');
|
|
34
40
|
const verifyMultiSig = require('@blocklet/meta/lib/verify-multi-sig');
|
|
@@ -41,6 +47,7 @@ const { forEachBlocklet } = require('@blocklet/meta/lib/util');
|
|
|
41
47
|
const { validate: validateEngine, get: getEngine } = require('../blocklet/manager/engine');
|
|
42
48
|
|
|
43
49
|
const isRequirementsSatisfied = require('./requirement');
|
|
50
|
+
const { getDidDomainForBlocklet } = require('./get-domain-for-blocklet');
|
|
44
51
|
const { getServices } = require('./service');
|
|
45
52
|
const {
|
|
46
53
|
isBeforeInstalled,
|
|
@@ -60,8 +67,6 @@ const getBlockletEngineNameByPlatform = (blockletMeta) => getBlockletEngine(bloc
|
|
|
60
67
|
|
|
61
68
|
const noop = () => {};
|
|
62
69
|
|
|
63
|
-
const asyncFs = fs.promises;
|
|
64
|
-
|
|
65
70
|
const statusMap = {
|
|
66
71
|
online: BlockletStatus.running,
|
|
67
72
|
launching: BlockletStatus.starting,
|
|
@@ -79,8 +84,6 @@ const PRIVATE_NODE_ENVS = [
|
|
|
79
84
|
'ABT_NODE_TOKEN_SECRET',
|
|
80
85
|
'ABT_NODE_SK',
|
|
81
86
|
'ABT_NODE_SESSION_SECRET',
|
|
82
|
-
'ABT_NODE_NAME',
|
|
83
|
-
'ABT_NODE_DESCRIPTION',
|
|
84
87
|
'ABT_NODE_BASE_URL',
|
|
85
88
|
'ABT_NODE_LOG_LEVEL',
|
|
86
89
|
'ABT_NODE_LOG_DIR',
|
|
@@ -119,19 +122,9 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
|
|
|
119
122
|
logsDir = path.join(dataDirs.logs, rootBlocklet.meta.name);
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
if (ensure) {
|
|
123
|
-
try {
|
|
124
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
125
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
126
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
127
|
-
} catch (err) {
|
|
128
|
-
logger.error('make blocklet dir failed', { error: err });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
125
|
// get app dirs
|
|
133
126
|
|
|
134
|
-
const {
|
|
127
|
+
const { main, group } = blocklet.meta;
|
|
135
128
|
|
|
136
129
|
const startFromDevEntry =
|
|
137
130
|
blocklet.mode === BLOCKLET_MODES.DEVELOPMENT && blocklet.meta.scripts && blocklet.meta.scripts.dev;
|
|
@@ -146,13 +139,24 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
|
|
|
146
139
|
if (blocklet.source === BlockletSource.local) {
|
|
147
140
|
appDir = blocklet.deployedFrom;
|
|
148
141
|
} else {
|
|
149
|
-
appDir =
|
|
142
|
+
appDir = getBundleDir(dataDirs.blocklets, blocklet.meta);
|
|
150
143
|
}
|
|
151
144
|
|
|
152
145
|
if (!appDir) {
|
|
153
146
|
throw new Error('Can not determine blocklet directory, maybe invalid deployment from local blocklets');
|
|
154
147
|
}
|
|
155
148
|
|
|
149
|
+
if (ensure) {
|
|
150
|
+
try {
|
|
151
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
152
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
153
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
154
|
+
fs.mkdirSync(appDir, { recursive: true }); // prevent getDiskInfo failed from custom blocklet
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger.error('make blocklet dir failed', { error: err });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
156
160
|
mainDir = appDir;
|
|
157
161
|
|
|
158
162
|
if (!startFromDevEntry && !isBeforeInstalled(rootBlocklet.status)) {
|
|
@@ -247,12 +251,16 @@ const getRootSystemEnvironments = (blocklet, nodeInfo) => {
|
|
|
247
251
|
const appName = title || name || result.name;
|
|
248
252
|
const appDescription = description || result.description;
|
|
249
253
|
|
|
254
|
+
// FIXME: we should use https here when possible, eg, when did-gateway is available
|
|
255
|
+
const appUrl = `http://${getDidDomainForBlocklet({ name, daemonDid: nodeInfo.did, didDomain: nodeInfo.didDomain })}`;
|
|
256
|
+
|
|
250
257
|
return {
|
|
251
258
|
BLOCKLET_DID: did,
|
|
252
259
|
BLOCKLET_APP_SK: appSk,
|
|
253
260
|
BLOCKLET_APP_ID: appId,
|
|
254
261
|
BLOCKLET_APP_NAME: appName,
|
|
255
262
|
BLOCKLET_APP_DESCRIPTION: appDescription,
|
|
263
|
+
BLOCKLET_APP_URL: appUrl,
|
|
256
264
|
};
|
|
257
265
|
};
|
|
258
266
|
|
|
@@ -432,11 +440,13 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
|
|
|
432
440
|
const options = {
|
|
433
441
|
namespace: 'blocklets',
|
|
434
442
|
name: appId,
|
|
435
|
-
|
|
443
|
+
cwd: appCwd,
|
|
436
444
|
time: true,
|
|
437
445
|
output: path.join(logsDir, 'output.log'),
|
|
438
446
|
error: path.join(logsDir, 'error.log'),
|
|
439
|
-
|
|
447
|
+
wait_ready: process.env.NODE_ENV !== 'test',
|
|
448
|
+
listen_timeout: 5000,
|
|
449
|
+
max_memory_restart: `${maxMemoryRestart}M`,
|
|
440
450
|
max_restarts: b.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 3,
|
|
441
451
|
env: {
|
|
442
452
|
...env,
|
|
@@ -464,17 +474,17 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
|
|
|
464
474
|
const engine = getEngine(blockletEngineInfo.interpreter);
|
|
465
475
|
options.interpreter = engine.interpreter === 'node' ? '' : engine.interpreter;
|
|
466
476
|
options.interpreterArgs = engine.args || '';
|
|
467
|
-
|
|
468
477
|
options.script = blockletEngineInfo.script || appMain;
|
|
469
|
-
|
|
470
|
-
logger.debug('start.blocklet.engine.info', { blockletEngineInfo });
|
|
471
|
-
logger.debug('start.blocklet.max_memory_restart', { maxMemoryRestart });
|
|
472
478
|
}
|
|
473
479
|
|
|
474
480
|
await pm2.startAsync(options);
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
481
|
+
|
|
482
|
+
const status = await getProcessState(appId);
|
|
483
|
+
if (status === BlockletStatus.error) {
|
|
484
|
+
throw new Error(`${appId} is not running within 5 seconds`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
logger.info('blocklet started', { appId, status });
|
|
478
488
|
},
|
|
479
489
|
{ parallel: true }
|
|
480
490
|
);
|
|
@@ -537,6 +547,10 @@ const getBlockletStatusFromProcess = async (blocklet) => {
|
|
|
537
547
|
|
|
538
548
|
const list = await Promise.all(tasks);
|
|
539
549
|
|
|
550
|
+
if (!list.length) {
|
|
551
|
+
return blocklet.status;
|
|
552
|
+
}
|
|
553
|
+
|
|
540
554
|
return getRootBlockletStatus(list);
|
|
541
555
|
};
|
|
542
556
|
|
|
@@ -615,26 +629,76 @@ const reloadProcess = (appId) =>
|
|
|
615
629
|
});
|
|
616
630
|
});
|
|
617
631
|
|
|
618
|
-
const
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
632
|
+
const findWebInterface = (blocklet) => {
|
|
633
|
+
const meta = blocklet.meta || blocklet || {};
|
|
634
|
+
const { interfaces = [] } = meta;
|
|
635
|
+
|
|
636
|
+
if (!Array.isArray(interfaces)) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return interfaces.find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* this function has side effect on children
|
|
645
|
+
*/
|
|
646
|
+
const parseChildren = async (src, { children, dynamic } = {}) => {
|
|
647
|
+
const configs = Array.isArray(src) ? src : src.children || [];
|
|
648
|
+
|
|
649
|
+
if (children) {
|
|
650
|
+
children.forEach((x) => {
|
|
651
|
+
x.dynamic = !!dynamic;
|
|
652
|
+
});
|
|
653
|
+
mergeMeta(configs, children);
|
|
654
|
+
return children;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (!configs || !configs.length) {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const results = [];
|
|
662
|
+
|
|
663
|
+
for (const config of configs) {
|
|
664
|
+
const mountPoint = config.mountPoint || config.mountPoints[0].root.prefix;
|
|
665
|
+
if (!mountPoint) {
|
|
666
|
+
throw new Error(`MountPoint does not found in child ${config.name}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const m = await getBlockletMetaFromUrl(config.resolved);
|
|
670
|
+
if (m.name !== config.name) {
|
|
671
|
+
logger.error('Resolved child blocklet name does not match in the configuration', {
|
|
672
|
+
expected: config.name,
|
|
673
|
+
resolved: m.name,
|
|
674
|
+
});
|
|
675
|
+
throw new Error(
|
|
676
|
+
`Child blocklet name does not match in the configuration. expected: ${config.name}, resolved: ${m.name}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
validateBlockletMeta(m, { ensureDist: true });
|
|
680
|
+
|
|
681
|
+
const webInterface = findWebInterface(m);
|
|
682
|
+
if (!webInterface) {
|
|
683
|
+
throw new Error(`Web interface does not found in child ${config.name}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const rule = webInterface.prefix;
|
|
687
|
+
if (rule !== BLOCKLET_DYNAMIC_PATH_PREFIX && normalizePathPrefix(rule) !== normalizePathPrefix(mountPoint)) {
|
|
688
|
+
throw new Error(`Prefix does not match in child ${config.name}. expected: ${rule}, resolved: ${mountPoint}`);
|
|
634
689
|
}
|
|
690
|
+
|
|
691
|
+
results.push({
|
|
692
|
+
mountPoint,
|
|
693
|
+
meta: m,
|
|
694
|
+
dynamic: !!dynamic,
|
|
695
|
+
sourceUrl: config.resolved,
|
|
696
|
+
});
|
|
635
697
|
}
|
|
636
698
|
|
|
637
|
-
|
|
699
|
+
mergeMeta(configs, results);
|
|
700
|
+
|
|
701
|
+
return results;
|
|
638
702
|
};
|
|
639
703
|
|
|
640
704
|
const validateBlocklet = (blocklet) =>
|
|
@@ -732,7 +796,7 @@ const verifyIntegrity = async ({ file, integrity: expected }) => {
|
|
|
732
796
|
return true;
|
|
733
797
|
};
|
|
734
798
|
|
|
735
|
-
const pruneBlockletBundle = async (blocklets, installDir) => {
|
|
799
|
+
const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings }) => {
|
|
736
800
|
for (const blocklet of blocklets) {
|
|
737
801
|
if (
|
|
738
802
|
[
|
|
@@ -750,53 +814,104 @@ const pruneBlockletBundle = async (blocklets, installDir) => {
|
|
|
750
814
|
}
|
|
751
815
|
}
|
|
752
816
|
|
|
753
|
-
// blockletMap: { <name/version>: true }
|
|
754
|
-
const blockletMap =
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
817
|
+
// blockletMap: { <[scope/]name/version>: true }
|
|
818
|
+
const blockletMap = {};
|
|
819
|
+
for (const blocklet of blocklets) {
|
|
820
|
+
blockletMap[`${blocklet.meta.name}/${blocklet.meta.version}`] = true;
|
|
821
|
+
for (const child of blocklet.children || []) {
|
|
822
|
+
blockletMap[`${child.meta.name}/${child.meta.version}`] = true;
|
|
758
823
|
}
|
|
759
|
-
|
|
760
|
-
|
|
824
|
+
}
|
|
825
|
+
for (const setting of blockletSettings) {
|
|
826
|
+
for (const child of setting.children || []) {
|
|
827
|
+
blockletMap[`${child.meta.name}/${child.meta.version}`] = true;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
761
830
|
|
|
762
|
-
// appDirs: [{ key: <name/version>, dir: appDir }]
|
|
831
|
+
// appDirs: [{ key: <[scope/]name/version>, dir: appDir }]
|
|
763
832
|
const appDirs = [];
|
|
764
833
|
|
|
765
834
|
// fill appDirs
|
|
766
835
|
try {
|
|
767
|
-
|
|
768
|
-
|
|
836
|
+
// @return root/scope/bundle/version
|
|
837
|
+
const getNextLevel = (level, name) => {
|
|
838
|
+
if (level === 'root') {
|
|
839
|
+
if (name.startsWith('@')) {
|
|
840
|
+
return 'scope';
|
|
841
|
+
}
|
|
842
|
+
return 'bundle';
|
|
843
|
+
}
|
|
844
|
+
if (level === 'scope') {
|
|
845
|
+
return 'bundle';
|
|
846
|
+
}
|
|
847
|
+
if (level === 'bundle') {
|
|
848
|
+
return 'version';
|
|
849
|
+
}
|
|
850
|
+
throw new Error(`Invalid level ${level}`);
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
const fillAppDirs = async (dir, level = 'root') => {
|
|
854
|
+
if (level === 'version') {
|
|
855
|
+
if (!fs.existsSync(path.join(dir, 'blocklet.yml'))) {
|
|
856
|
+
logger.error('blocklet.yml does not exist in blocklet bundle dir', { dir });
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
769
860
|
appDirs.push({
|
|
770
861
|
key: path.relative(installDir, dir),
|
|
771
862
|
dir,
|
|
772
863
|
});
|
|
864
|
+
|
|
773
865
|
return;
|
|
774
866
|
}
|
|
867
|
+
|
|
775
868
|
const nextDirs = [];
|
|
776
|
-
for (const x of await
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
return;
|
|
869
|
+
for (const x of await fs.promises.readdir(dir)) {
|
|
870
|
+
if (!fs.lstatSync(path.join(dir, x)).isDirectory()) {
|
|
871
|
+
logger.error('pruneBlockletBundle: invalid file in bundle storage', { dir, file: x });
|
|
872
|
+
// eslint-disable-next-line no-continue
|
|
873
|
+
continue;
|
|
782
874
|
}
|
|
783
|
-
nextDirs.push(
|
|
875
|
+
nextDirs.push(x);
|
|
784
876
|
}
|
|
785
877
|
|
|
786
878
|
for (const x of nextDirs) {
|
|
787
|
-
await fillAppDirs(x);
|
|
879
|
+
await fillAppDirs(path.join(dir, x), getNextLevel(level, x));
|
|
788
880
|
}
|
|
789
881
|
};
|
|
790
|
-
await fillAppDirs(installDir);
|
|
882
|
+
await fillAppDirs(installDir, 'root');
|
|
791
883
|
} catch (error) {
|
|
792
884
|
logger.error('fill app dirs failed', { error });
|
|
793
885
|
}
|
|
794
886
|
|
|
887
|
+
const ensureBundleDirRemoved = async (dir) => {
|
|
888
|
+
const relativeDir = path.relative(installDir, dir);
|
|
889
|
+
const arr = relativeDir.split('/').filter(Boolean);
|
|
890
|
+
const { length } = arr;
|
|
891
|
+
const bundleName = arr[length - 2];
|
|
892
|
+
const scopeName = length > 2 ? arr[length - 3] : '';
|
|
893
|
+
const bundleDir = path.join(installDir, scopeName, bundleName);
|
|
894
|
+
const isEmpty = (await fs.promises.readdir(bundleDir)).length === 0;
|
|
895
|
+
if (isEmpty) {
|
|
896
|
+
logger.info('Remove bundle folder', { bundleDir });
|
|
897
|
+
await fs.remove(bundleDir);
|
|
898
|
+
}
|
|
899
|
+
if (scopeName) {
|
|
900
|
+
const scopeDir = path.join(installDir, scopeName);
|
|
901
|
+
const isScopeEmpty = (await fs.promises.readdir(scopeDir)).length === 0;
|
|
902
|
+
if (isScopeEmpty) {
|
|
903
|
+
logger.info('Remove scope folder', { scopeDir });
|
|
904
|
+
await fs.remove(scopeDir);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
795
909
|
// remove trash
|
|
796
910
|
for (const app of appDirs) {
|
|
797
911
|
if (!blockletMap[app.key]) {
|
|
798
|
-
logger.info('Remove
|
|
912
|
+
logger.info('Remove app folder', { dir: app.dir });
|
|
799
913
|
await fs.remove(app.dir);
|
|
914
|
+
await ensureBundleDirRemoved(app.dir);
|
|
800
915
|
}
|
|
801
916
|
}
|
|
802
917
|
|
|
@@ -834,15 +949,25 @@ const getRuntimeInfo = async (appId) => {
|
|
|
834
949
|
};
|
|
835
950
|
};
|
|
836
951
|
|
|
837
|
-
|
|
952
|
+
/**
|
|
953
|
+
* merge services
|
|
954
|
+
* from meta.children[].mountPoints[].services
|
|
955
|
+
* to childrenMeta[].interfaces[].services
|
|
956
|
+
*
|
|
957
|
+
* @param {array<child>|object{children:array}} source e.g. [<config>] or { children: [<config>] }
|
|
958
|
+
* @param {array<meta|{meta}>} childrenMeta e.g. [<meta>] or [{ meta: <meta> }]
|
|
959
|
+
*/
|
|
960
|
+
|
|
961
|
+
const mergeMeta = (source, childrenMeta = []) => {
|
|
838
962
|
// configMap
|
|
839
963
|
const configMap = {};
|
|
840
|
-
(
|
|
964
|
+
(Array.isArray(source) ? source : source.children || []).forEach((x) => {
|
|
841
965
|
configMap[x.name] = x;
|
|
842
966
|
});
|
|
843
967
|
|
|
844
968
|
// merge service from config to child meta
|
|
845
|
-
childrenMeta.forEach((
|
|
969
|
+
childrenMeta.forEach((child) => {
|
|
970
|
+
const childMeta = child.meta || child;
|
|
846
971
|
const config = configMap[childMeta.name];
|
|
847
972
|
if (!config) {
|
|
848
973
|
return;
|
|
@@ -902,10 +1027,100 @@ const getUpdateMetaList = (oldMetas = [], newMetas = []) => {
|
|
|
902
1027
|
return newMetas.filter(({ version, did }) => did && version !== oldMap[did]);
|
|
903
1028
|
};
|
|
904
1029
|
|
|
1030
|
+
const getSourceFromInstallParams = (params) => {
|
|
1031
|
+
if (params.url) {
|
|
1032
|
+
return BlockletSource.url;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (params.file) {
|
|
1036
|
+
return BlockletSource.upload;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (params.did) {
|
|
1040
|
+
return BlockletSource.registry;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (params.title && params.description) {
|
|
1044
|
+
return BlockletSource.custom;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
throw new Error('Can only install blocklet from store/url/upload/custom');
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
const checkDuplicateComponents = (dynamicComponents, staticComponents) => {
|
|
1051
|
+
const duplicates = intersectionBy(dynamicComponents, staticComponents, 'meta.did');
|
|
1052
|
+
if (duplicates.length) {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Cannot add duplicate component${duplicates.length > 1 ? 's' : ''}: ${duplicates
|
|
1055
|
+
.map((x) => x.meta.title || x.meta.name)
|
|
1056
|
+
.join(', ')}`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
const getDiffFiles = async (inputFiles, sourceDir) => {
|
|
1062
|
+
if (!fs.existsSync(sourceDir)) {
|
|
1063
|
+
throw new Error(`${sourceDir} does not exist`);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const files = inputFiles.reduce((obj, item) => {
|
|
1067
|
+
obj[item.file] = item.hash;
|
|
1068
|
+
return obj;
|
|
1069
|
+
}, {});
|
|
1070
|
+
|
|
1071
|
+
const { files: sourceFiles } = await hashFiles(sourceDir, {
|
|
1072
|
+
filter: (x) => x.indexOf('node_modules') === -1,
|
|
1073
|
+
concurrentHash: 1,
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const addSet = [];
|
|
1077
|
+
const changeSet = [];
|
|
1078
|
+
const deleteSet = [];
|
|
1079
|
+
|
|
1080
|
+
const diffFiles = diff(sourceFiles, files);
|
|
1081
|
+
if (diffFiles) {
|
|
1082
|
+
diffFiles.forEach((item) => {
|
|
1083
|
+
if (item.kind === 'D') {
|
|
1084
|
+
deleteSet.push(item.path[0]);
|
|
1085
|
+
}
|
|
1086
|
+
if (item.kind === 'E') {
|
|
1087
|
+
changeSet.push(item.path[0]);
|
|
1088
|
+
}
|
|
1089
|
+
if (item.kind === 'N') {
|
|
1090
|
+
addSet.push(item.path[0]);
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return {
|
|
1096
|
+
addSet,
|
|
1097
|
+
changeSet,
|
|
1098
|
+
deleteSet,
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
const getBundleDir = (installDir, bundle) => path.join(installDir, bundle.name, bundle.version);
|
|
1103
|
+
|
|
1104
|
+
const needBlockletDownload = (blocklet, oldBlocklet) => {
|
|
1105
|
+
if ([BlockletSource.upload, BlockletSource.local, BlockletSource.custom].includes(blocklet.source)) {
|
|
1106
|
+
return false;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (!get(oldBlocklet, 'meta.dist.integrity')) {
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (get(oldBlocklet, 'meta.dist.integrity') === get(blocklet, 'meta.dist.integrity')) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return true;
|
|
1118
|
+
};
|
|
1119
|
+
|
|
905
1120
|
module.exports = {
|
|
906
1121
|
forEachBlocklet,
|
|
907
1122
|
getBlockletMetaFromUrl,
|
|
908
|
-
|
|
1123
|
+
parseChildren,
|
|
909
1124
|
getBlockletDirs,
|
|
910
1125
|
getRootSystemEnvironments,
|
|
911
1126
|
getSystemEnvironments,
|
|
@@ -931,4 +1146,10 @@ module.exports = {
|
|
|
931
1146
|
mergeMeta,
|
|
932
1147
|
fixAndVerifyBlockletMeta,
|
|
933
1148
|
getUpdateMetaList,
|
|
1149
|
+
getSourceFromInstallParams,
|
|
1150
|
+
findWebInterface,
|
|
1151
|
+
checkDuplicateComponents,
|
|
1152
|
+
getDiffFiles,
|
|
1153
|
+
getBundleDir,
|
|
1154
|
+
needBlockletDownload,
|
|
934
1155
|
};
|
|
@@ -16,8 +16,8 @@ const getIpDnsDomainForBlocklet = (blocklet, blockletInterface) => {
|
|
|
16
16
|
}${iName}-${SLOT_FOR_IP_DNS_SITE}.${DEFAULT_IP_DNS_DOMAIN_SUFFIX}`;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const getDidDomainForBlocklet = ({
|
|
20
|
-
return `${
|
|
19
|
+
const getDidDomainForBlocklet = ({ name, daemonDid, didDomain }) => {
|
|
20
|
+
return `${formatName(name)}-${daemonDid.toLowerCase()}.${didDomain}`;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
module.exports = { getIpDnsDomainForBlocklet, getDidDomainForBlocklet };
|
package/lib/util/upgrade.js
CHANGED
|
@@ -27,9 +27,6 @@ const waitUpdaterRpc = async (message) =>
|
|
|
27
27
|
const checkNewVersion = async (params, context) => {
|
|
28
28
|
try {
|
|
29
29
|
const info = await states.node.read();
|
|
30
|
-
if (!info.autoUpgrade) {
|
|
31
|
-
return '';
|
|
32
|
-
}
|
|
33
30
|
|
|
34
31
|
if (!process.env.ABT_NODE_PACKAGE_NAME) {
|
|
35
32
|
logger.error('ABT_NODE_PACKAGE_NAME name was not found in environment');
|
|
@@ -176,7 +173,14 @@ const getCron = () => ({
|
|
|
176
173
|
name: 'check-update',
|
|
177
174
|
time: '0 0 8 * * *', // check every day
|
|
178
175
|
// time: '0 */5 * * * *', // check every 5 minutes
|
|
179
|
-
fn:
|
|
176
|
+
fn: async () => {
|
|
177
|
+
const info = await states.node.read();
|
|
178
|
+
if (!info.autoUpgrade) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
checkNewVersion();
|
|
183
|
+
},
|
|
180
184
|
options: { runOnInit: false },
|
|
181
185
|
});
|
|
182
186
|
|
package/lib/validators/node.js
CHANGED
|
@@ -9,14 +9,23 @@ const nodeInfoSchema = Joi.object({
|
|
|
9
9
|
description: Joi.string()
|
|
10
10
|
.required()
|
|
11
11
|
.messages({ zh: { 'string.empty': '描述不能为空' }, en: { 'string.empty': 'Description cannot be empty' } }),
|
|
12
|
+
registerUrl: Joi.string()
|
|
13
|
+
.uri({ scheme: [/https?/] })
|
|
14
|
+
.label('register url')
|
|
15
|
+
.allow('')
|
|
16
|
+
.optional()
|
|
17
|
+
.messages({
|
|
18
|
+
zh: { 'string.uriCustomScheme': '应用启动器必须是合法的 URL' },
|
|
19
|
+
en: { 'string.uriCustomScheme': 'Blocklet Launcher must be a valid URL' },
|
|
20
|
+
}),
|
|
12
21
|
webWalletUrl: Joi.string()
|
|
13
22
|
.uri({ scheme: [/https?/] })
|
|
14
23
|
.label('web wallet url')
|
|
15
24
|
.allow('')
|
|
16
25
|
.optional()
|
|
17
26
|
.messages({
|
|
18
|
-
zh: { 'string.uriCustomScheme': 'Web
|
|
19
|
-
en: { 'string.uriCustomScheme': 'Web
|
|
27
|
+
zh: { 'string.uriCustomScheme': 'Web Wallet 必须是合法的 URL' },
|
|
28
|
+
en: { 'string.uriCustomScheme': 'Web Wallet must be a valid URL' },
|
|
20
29
|
}),
|
|
21
30
|
autoUpgrade: Joi.boolean(),
|
|
22
31
|
enableWelcomePage: Joi.boolean(),
|
|
@@ -31,16 +40,6 @@ const nodeInfoSchema = Joi.object({
|
|
|
31
40
|
'number.max': 'Disk usage alert threshold cannot be higher than 99%',
|
|
32
41
|
},
|
|
33
42
|
}),
|
|
34
|
-
// removed in 1.5.1
|
|
35
|
-
registerUrl: Joi.string()
|
|
36
|
-
.uri({ scheme: [/https?/] })
|
|
37
|
-
.label('register url')
|
|
38
|
-
.allow('')
|
|
39
|
-
.optional()
|
|
40
|
-
.messages({
|
|
41
|
-
zh: { 'string.uriCustomScheme': '注册地址必须是合法的 URL' },
|
|
42
|
-
en: { 'string.uriCustomScheme': 'Registry URL must be a valid URL' },
|
|
43
|
-
}),
|
|
44
43
|
}).options({ stripUnknown: true });
|
|
45
44
|
|
|
46
45
|
module.exports = {
|