@abtnode/core 1.8.63 → 1.8.64-beta-0b5ede51
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/team.js +48 -7
- package/lib/blocklet/manager/disk.js +40 -5
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +63 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +105 -0
- package/lib/blocklet/storage/backup/blocklet.js +53 -0
- package/lib/blocklet/storage/backup/blocklets.js +80 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +23 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +189 -0
- package/lib/blocklet/storage/restore/base.js +55 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +84 -0
- package/lib/blocklet/storage/restore/blocklets.js +24 -0
- package/lib/blocklet/storage/restore/spaces.js +137 -0
- package/lib/event.js +5 -1
- package/lib/index.js +4 -1
- package/lib/router/helper.js +65 -34
- package/lib/states/blocklet-extras.js +4 -0
- package/lib/states/blocklet.js +0 -22
- package/lib/util/blocklet.js +23 -2
- package/lib/validators/util.js +1 -0
- package/package.json +30 -27
- package/lib/validators/blocklet.js +0 -5
package/lib/api/team.js
CHANGED
|
@@ -408,6 +408,26 @@ class TeamAPI extends EventEmitter {
|
|
|
408
408
|
|
|
409
409
|
// Invite member
|
|
410
410
|
|
|
411
|
+
/**
|
|
412
|
+
* @type InvitationSession {
|
|
413
|
+
* type: 'invite';
|
|
414
|
+
* role: string;
|
|
415
|
+
* remark: string;
|
|
416
|
+
* expireDate: number;
|
|
417
|
+
* inviter: {
|
|
418
|
+
* did: string;
|
|
419
|
+
* email?: string;
|
|
420
|
+
* fullName?: string;
|
|
421
|
+
* role?: string;
|
|
422
|
+
* };
|
|
423
|
+
* teamDid: string;
|
|
424
|
+
* status: '' | 'success';
|
|
425
|
+
* receiver: {
|
|
426
|
+
* did: string;
|
|
427
|
+
* };
|
|
428
|
+
* }
|
|
429
|
+
* @returns
|
|
430
|
+
*/
|
|
411
431
|
async createMemberInvitation({ teamDid, role, expireTime, remark }, context) {
|
|
412
432
|
await this.teamManager.checkEnablePassportIssuance(teamDid);
|
|
413
433
|
|
|
@@ -484,15 +504,17 @@ class TeamAPI extends EventEmitter {
|
|
|
484
504
|
expireDate: new Date(invitation.expireDate).toString(),
|
|
485
505
|
inviter: invitation.inviter,
|
|
486
506
|
teamDid: invitation.teamDid,
|
|
507
|
+
status: invitation.status,
|
|
508
|
+
receiver: invitation.receiver,
|
|
487
509
|
};
|
|
488
510
|
}
|
|
489
511
|
|
|
490
|
-
async getInvitations({ teamDid }) {
|
|
512
|
+
async getInvitations({ teamDid, filter }) {
|
|
491
513
|
const state = await this.getSessionState(teamDid);
|
|
492
514
|
|
|
493
515
|
const invitations = await state.find({ type: 'invite' });
|
|
494
516
|
|
|
495
|
-
return invitations.map((d) => ({
|
|
517
|
+
return invitations.filter(filter || ((x) => x.status !== 'success')).map((d) => ({
|
|
496
518
|
// eslint-disable-next-line no-underscore-dangle
|
|
497
519
|
inviteId: d._id,
|
|
498
520
|
role: d.role,
|
|
@@ -500,6 +522,8 @@ class TeamAPI extends EventEmitter {
|
|
|
500
522
|
expireDate: new Date(d.expireDate).toString(),
|
|
501
523
|
inviter: d.inviter,
|
|
502
524
|
teamDid: d.teamDid,
|
|
525
|
+
status: d.status,
|
|
526
|
+
receiver: d.receiver,
|
|
503
527
|
}));
|
|
504
528
|
}
|
|
505
529
|
|
|
@@ -516,7 +540,7 @@ class TeamAPI extends EventEmitter {
|
|
|
516
540
|
return true;
|
|
517
541
|
}
|
|
518
542
|
|
|
519
|
-
async
|
|
543
|
+
async checkInvitation({ teamDid, inviteId }) {
|
|
520
544
|
const state = await this.getSessionState(teamDid);
|
|
521
545
|
|
|
522
546
|
const invitation = await state.read(inviteId);
|
|
@@ -538,16 +562,33 @@ class TeamAPI extends EventEmitter {
|
|
|
538
562
|
throw new Error(`Role does not exist: ${role}`);
|
|
539
563
|
}
|
|
540
564
|
|
|
541
|
-
await state.end(inviteId);
|
|
542
|
-
|
|
543
|
-
logger.info('Invitation session completed successfully', { inviteId, role });
|
|
544
|
-
|
|
545
565
|
return {
|
|
546
566
|
role,
|
|
547
567
|
remark,
|
|
548
568
|
};
|
|
549
569
|
}
|
|
550
570
|
|
|
571
|
+
async closeInvitation({ teamDid, inviteId, status, receiver, timeout = 30 * 1000 }) {
|
|
572
|
+
const state = await this.getSessionState(teamDid);
|
|
573
|
+
|
|
574
|
+
const invitation = await state.read(inviteId);
|
|
575
|
+
|
|
576
|
+
if (!invitation) {
|
|
577
|
+
throw new Error(`The invitation does not exist: ${inviteId}`);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
await state.update(inviteId, { status, receiver });
|
|
581
|
+
|
|
582
|
+
setTimeout(async () => {
|
|
583
|
+
try {
|
|
584
|
+
logger.info('Invitation session closed', { inviteId });
|
|
585
|
+
await state.end(inviteId);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
logger.error('close invitation failed', { error });
|
|
588
|
+
}
|
|
589
|
+
}, timeout);
|
|
590
|
+
}
|
|
591
|
+
|
|
551
592
|
// Issue Passport
|
|
552
593
|
|
|
553
594
|
async createPassportIssuance({ teamDid, ownerDid, name }) {
|
|
@@ -134,6 +134,8 @@ const handleInstanceInStore = require('../../util/public-to-store');
|
|
|
134
134
|
const { getNFTState, getServerDidDomain } = require('../../util');
|
|
135
135
|
const { BlockletRuntimeMonitor } = require('../../monitor/blocklet-runtime-monitor');
|
|
136
136
|
const getHistoryList = require('../../monitor/get-history-list');
|
|
137
|
+
const { SpacesBackup } = require('../storage/backup/spaces');
|
|
138
|
+
const { SpacesRestore } = require('../storage/restore/spaces');
|
|
137
139
|
const installFromBackup = require('./helper/install-from-backup');
|
|
138
140
|
|
|
139
141
|
const {
|
|
@@ -609,6 +611,35 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
609
611
|
return blocklet;
|
|
610
612
|
}
|
|
611
613
|
|
|
614
|
+
/**
|
|
615
|
+
*
|
|
616
|
+
*
|
|
617
|
+
* @param {import('@abtnode/client').BackupToSpacesParams} input
|
|
618
|
+
* @memberof BlockletManager
|
|
619
|
+
*/
|
|
620
|
+
async backupToSpaces(input) {
|
|
621
|
+
const spacesBackup = new SpacesBackup(input);
|
|
622
|
+
await spacesBackup.backup();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
*
|
|
627
|
+
*
|
|
628
|
+
* @param {import('@abtnode/client').RequestRestoreFromSpacesInput} input
|
|
629
|
+
* @memberof BlockletManager
|
|
630
|
+
*/
|
|
631
|
+
async restoreFromSpaces(input) {
|
|
632
|
+
const spacesRestore = new SpacesRestore(input);
|
|
633
|
+
await spacesRestore.restore();
|
|
634
|
+
|
|
635
|
+
// FIXME: 需要改成队列执行,本次失败,下次还需要重试,页面刷新后也能知道最新的状态
|
|
636
|
+
await this._installFromBackup({
|
|
637
|
+
url: `file://${spacesRestore.blockletRestoreDir}`,
|
|
638
|
+
blockletSecretKey: spacesRestore.blockletWallet.secretKey,
|
|
639
|
+
moveDir: true,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
612
643
|
async restart({ did }, context) {
|
|
613
644
|
logger.info('restart blocklet', { did });
|
|
614
645
|
|
|
@@ -1617,6 +1648,11 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1617
1648
|
* /data
|
|
1618
1649
|
* /blocklet.json
|
|
1619
1650
|
* /blocklet_extras.json
|
|
1651
|
+
* @see Blocklet 端到端备份方案的设计与实现(一期) https://team.arcblock.io/comment/discussions/e62084d5-fafa-489e-91d5-defcd6e93223
|
|
1652
|
+
* @param {{ url: string, blockletSecretKey: string, moveDir: boolean}} [{ url }={}]
|
|
1653
|
+
* @param {Record<string, string>} [context={}]
|
|
1654
|
+
* @return {Promise<any>}
|
|
1655
|
+
* @memberof BlockletManager
|
|
1620
1656
|
*/
|
|
1621
1657
|
async _installFromBackup({ url, blockletSecretKey, moveDir } = {}, context = {}) {
|
|
1622
1658
|
return installFromBackup({ url, blockletSecretKey, moveDir, context, manager: this, states });
|
|
@@ -2090,10 +2126,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2090
2126
|
|
|
2091
2127
|
if (controller?.nftId) {
|
|
2092
2128
|
// sometimes nedb will throw error if use states.blocklet.count({ ['controller.nftId']: controller.nftId })
|
|
2093
|
-
const
|
|
2094
|
-
const count = blocklets.filter((x) => x.controller?.nftId === controller.nftId).length;
|
|
2129
|
+
const installedCount = await states.blockletExtras.count({ 'controller.nftId': controller.nftId });
|
|
2095
2130
|
|
|
2096
|
-
if (
|
|
2131
|
+
if (installedCount >= (controller.appMaxCount || 1)) {
|
|
2097
2132
|
throw new Error(
|
|
2098
2133
|
`You can only install ${controller.appMaxCount} blocklet with this credential: ${controller.nftId}`
|
|
2099
2134
|
);
|
|
@@ -2691,7 +2726,6 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2691
2726
|
source,
|
|
2692
2727
|
deployedFrom,
|
|
2693
2728
|
children,
|
|
2694
|
-
controller,
|
|
2695
2729
|
});
|
|
2696
2730
|
|
|
2697
2731
|
await validateBlocklet(blocklet);
|
|
@@ -3798,7 +3832,8 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3798
3832
|
|
|
3799
3833
|
async _createNotification(did, notification) {
|
|
3800
3834
|
try {
|
|
3801
|
-
const
|
|
3835
|
+
const extra = await states.blockletExtras.getMeta(did);
|
|
3836
|
+
const isExternal = !!extra?.controller;
|
|
3802
3837
|
|
|
3803
3838
|
if (isExternal) {
|
|
3804
3839
|
return;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { removeSync, outputJsonSync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const states = require('../../../states');
|
|
4
|
+
const { BaseBackup } = require('./base');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* @class AuditLogBackup
|
|
10
|
+
*/
|
|
11
|
+
class AuditLogBackup extends BaseBackup {
|
|
12
|
+
filename = 'audit-log.json';
|
|
13
|
+
|
|
14
|
+
async export() {
|
|
15
|
+
/**
|
|
16
|
+
* @type {import('@abtnode/client').AuditLog}
|
|
17
|
+
*/
|
|
18
|
+
const auditLogs = await states.auditLog.find({
|
|
19
|
+
scope: this.blocklet.meta.did,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
removeSync(join(this.blockletBackupDir, this.filename));
|
|
23
|
+
outputJsonSync(join(this.blockletBackupDir, this.filename), auditLogs);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { AuditLogBackup };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class BaseBackup {
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @type {import('./spaces').SpaceBackupInput}
|
|
5
|
+
* @memberof BaseBackup
|
|
6
|
+
*/
|
|
7
|
+
input;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @description blocklet state 对象
|
|
11
|
+
* @type {import('@abtnode/client').BlockletState}
|
|
12
|
+
* @memberof BaseBackup
|
|
13
|
+
*/
|
|
14
|
+
blocklet;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @description 当前 blocklet 的数据目录
|
|
18
|
+
* @type {string}
|
|
19
|
+
* @memberof BaseBackup
|
|
20
|
+
*/
|
|
21
|
+
blockletBackupDir;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @description spaces 的 endpoint
|
|
26
|
+
* @type {import('@ocap/wallet').WalletObject}
|
|
27
|
+
* @memberof BaseBackup
|
|
28
|
+
*/
|
|
29
|
+
blockletWallet;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @description server 的数据目录
|
|
34
|
+
* @type {string}
|
|
35
|
+
* @memberof BaseBackup
|
|
36
|
+
*/
|
|
37
|
+
serverDataDir;
|
|
38
|
+
|
|
39
|
+
constructor(input) {
|
|
40
|
+
this.input = input;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
*
|
|
46
|
+
* @param {import('./spaces').SpacesBackup} spacesBackup
|
|
47
|
+
* @memberof BaseBackup
|
|
48
|
+
*/
|
|
49
|
+
ensureParams(spacesBackup) {
|
|
50
|
+
this.blocklet = spacesBackup.blocklet;
|
|
51
|
+
this.serverDataDir = spacesBackup.serverDataDir;
|
|
52
|
+
this.blockletBackupDir = spacesBackup.blockletBackupDir;
|
|
53
|
+
this.blockletWallet = spacesBackup.blockletWallet;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async export() {
|
|
57
|
+
throw new Error('not implemented');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
BaseBackup,
|
|
63
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const { removeSync, outputJsonSync, readFileSync } = require('fs-extra');
|
|
2
|
+
const { isEmpty, cloneDeep } = require('lodash');
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const security = require('@abtnode/util/lib/security');
|
|
5
|
+
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
6
|
+
const states = require('../../../states');
|
|
7
|
+
const { BaseBackup } = require('./base');
|
|
8
|
+
|
|
9
|
+
class BlockletExtrasBackup extends BaseBackup {
|
|
10
|
+
filename = 'blocklet-extras.json';
|
|
11
|
+
|
|
12
|
+
async export() {
|
|
13
|
+
const blockletExtra = await this.getBlockletExtra();
|
|
14
|
+
removeSync(join(this.blockletBackupDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.blockletBackupDir, this.filename), blockletExtra);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @description
|
|
21
|
+
* @return {Promise<import('@abtnode/client').BlockletState>}
|
|
22
|
+
* @memberof BlockletExtrasBackup
|
|
23
|
+
*/
|
|
24
|
+
async getBlockletExtra() {
|
|
25
|
+
/**
|
|
26
|
+
* @type {import('@abtnode/client').BlockletState}
|
|
27
|
+
*/
|
|
28
|
+
const blockletExtra = await states.blockletExtras.findOne({
|
|
29
|
+
did: this.blocklet.meta.did,
|
|
30
|
+
});
|
|
31
|
+
if (isEmpty(blockletExtra)) {
|
|
32
|
+
throw new Error('blockletExtra cannot be empty');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this.cleanDate(blockletExtra);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @description 清理数据并加密
|
|
41
|
+
* @param {import('@abtnode/client').BlockletState} blockletExtraInput
|
|
42
|
+
* @return {Promise<void>}
|
|
43
|
+
* @memberof BlockletExtrasBackup
|
|
44
|
+
*/
|
|
45
|
+
async cleanDate(blockletExtraInput) {
|
|
46
|
+
const blockletExtra = cloneDeep(blockletExtraInput);
|
|
47
|
+
|
|
48
|
+
const queue = [blockletExtra];
|
|
49
|
+
while (queue.length) {
|
|
50
|
+
const currentBlockletExtra = queue.pop();
|
|
51
|
+
|
|
52
|
+
// 删除父 blocklet 的某些数据
|
|
53
|
+
if (currentBlockletExtra._id) {
|
|
54
|
+
delete currentBlockletExtra._id;
|
|
55
|
+
delete currentBlockletExtra.createdAt;
|
|
56
|
+
delete currentBlockletExtra.updatedAt;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 加解密
|
|
60
|
+
this.useBlockletEncryptConfigs(currentBlockletExtra.configs);
|
|
61
|
+
|
|
62
|
+
if (currentBlockletExtra?.children) {
|
|
63
|
+
queue.push(...currentBlockletExtra.children);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return blockletExtra;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @description 清理数据并加密
|
|
73
|
+
* @param {import('@abtnode/client').ConfigEntry[]} configs
|
|
74
|
+
* @return {void}
|
|
75
|
+
* @memberof BlockletExtrasBackup
|
|
76
|
+
*/
|
|
77
|
+
useBlockletEncryptConfigs(configs) {
|
|
78
|
+
// 准备加解密所需的参数
|
|
79
|
+
// @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
|
|
80
|
+
const dek = readFileSync(join(this.serverDataDir, '.sock'));
|
|
81
|
+
|
|
82
|
+
for (const config of configs) {
|
|
83
|
+
// 置空 blocklet 的密钥,但是保留其他属性,这很重要
|
|
84
|
+
if (config.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
|
|
85
|
+
config.value = '';
|
|
86
|
+
} else if (config.secure) {
|
|
87
|
+
// secure 为 true 的配置才需要被加密保存上次到 did spaces
|
|
88
|
+
|
|
89
|
+
const encryptByServer = config.value;
|
|
90
|
+
// 先从 blocklet server 解密
|
|
91
|
+
// @see https://github.com/ArcBlock/blocklet-server/blob/f40338168a66893f325464cea79ae54c43f623b1/core/state/lib/blocklet/extras.js#L139
|
|
92
|
+
const decryptByServer = security.decrypt(encryptByServer, this.blocklet.meta.did, dek);
|
|
93
|
+
// 再用 blocklet secret 加密,然后才可以上传到 spaces
|
|
94
|
+
const encryptByBlocklet = security.encrypt(
|
|
95
|
+
decryptByServer,
|
|
96
|
+
this.blockletWallet.address,
|
|
97
|
+
Buffer.from(this.blockletWallet.secretKey)
|
|
98
|
+
);
|
|
99
|
+
config.value = encryptByBlocklet;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { BlockletExtrasBackup };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { removeSync, outputJsonSync } = require('fs-extra');
|
|
2
|
+
const { cloneDeep } = require('lodash');
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const { BaseBackup } = require('./base');
|
|
5
|
+
|
|
6
|
+
class BlockletBackup extends BaseBackup {
|
|
7
|
+
filename = 'blocklet.json';
|
|
8
|
+
|
|
9
|
+
async export() {
|
|
10
|
+
const blocklet = await this.cleanData();
|
|
11
|
+
removeSync(join(this.blockletBackupDir, this.filename));
|
|
12
|
+
outputJsonSync(join(this.blockletBackupDir, this.filename), blocklet);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description 清理数据
|
|
17
|
+
* @see blocklet.db 中需要删除哪些字段呢? https://github.com/ArcBlock/blocklet-server/issues/6120#issuecomment-1383798348
|
|
18
|
+
* @return {Promise<void>}
|
|
19
|
+
* @memberof BlockletBackup
|
|
20
|
+
*/
|
|
21
|
+
async cleanData() {
|
|
22
|
+
const originalBlocklet = cloneDeep(this.blocklet);
|
|
23
|
+
|
|
24
|
+
/** @type {import('@abtnode/client').ComponentState[]} */
|
|
25
|
+
const queue = [originalBlocklet];
|
|
26
|
+
|
|
27
|
+
// 广度优先遍历
|
|
28
|
+
while (queue.length) {
|
|
29
|
+
const currentBlocklet = queue.pop();
|
|
30
|
+
|
|
31
|
+
// 父组件才需要删除的属性
|
|
32
|
+
if (currentBlocklet._id) {
|
|
33
|
+
delete currentBlocklet._id;
|
|
34
|
+
delete currentBlocklet.createdAt;
|
|
35
|
+
delete currentBlocklet.startedAt;
|
|
36
|
+
delete currentBlocklet.installedAt;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 子组件和父组件都需要删除的属性
|
|
40
|
+
delete currentBlocklet.status;
|
|
41
|
+
delete currentBlocklet.ports;
|
|
42
|
+
delete currentBlocklet.environments;
|
|
43
|
+
|
|
44
|
+
if (currentBlocklet.children) {
|
|
45
|
+
queue.push(...currentBlocklet.children);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return originalBlocklet;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { BlockletBackup };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const { removeSync, existsSync, ensureDirSync, createWriteStream } = require('fs-extra');
|
|
2
|
+
const { join, dirname } = require('path');
|
|
3
|
+
const archiver = require('archiver');
|
|
4
|
+
const { BaseBackup } = require('./base');
|
|
5
|
+
|
|
6
|
+
class BlockletsBackup extends BaseBackup {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @returns {Promise<void>}
|
|
10
|
+
* @memberof BlockletsBackup
|
|
11
|
+
*/
|
|
12
|
+
async export() {
|
|
13
|
+
const blockletMetas = this.getBlockletMetas(this.blocklet);
|
|
14
|
+
const serverBlockletsDir = join(this.serverDataDir, 'blocklets');
|
|
15
|
+
|
|
16
|
+
const dirs = [];
|
|
17
|
+
for (const blockletMeta of blockletMetas) {
|
|
18
|
+
const sourceDir = join(serverBlockletsDir, blockletMeta.name, blockletMeta.version);
|
|
19
|
+
const destDir = join(blockletMeta.name, blockletMeta.version);
|
|
20
|
+
dirs.push({ sourceDir, destDir });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await this.dirsToZip(dirs, join(this.blockletBackupDir, 'blocklets.zip'));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
*
|
|
29
|
+
* @param {import('@abtnode/client').BlockletState} blocklet
|
|
30
|
+
* @returns {Array<{name: string, version: string}>}
|
|
31
|
+
* @memberof BlockletsBackup
|
|
32
|
+
*/
|
|
33
|
+
getBlockletMetas(blocklet) {
|
|
34
|
+
if (!blocklet?.meta?.bundleName || !blocklet?.meta?.version) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const metas = [];
|
|
39
|
+
metas.push({
|
|
40
|
+
name: blocklet.meta.bundleName,
|
|
41
|
+
version: blocklet.meta.version,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (const child of blocklet.children) {
|
|
45
|
+
metas.push(...this.getBlockletMetas(child));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return metas;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Array<{sourceDir: string, destDir: string}>} dirs: /some/folder/to/compress
|
|
53
|
+
* @param {String} zipPath: /path/to/created.zip
|
|
54
|
+
* @returns {Promise}
|
|
55
|
+
* @memberof BlockletsBackup
|
|
56
|
+
*/
|
|
57
|
+
async dirsToZip(dirs, zipPath) {
|
|
58
|
+
ensureDirSync(dirname(zipPath));
|
|
59
|
+
if (existsSync(zipPath)) {
|
|
60
|
+
removeSync(zipPath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
64
|
+
const stream = createWriteStream(zipPath);
|
|
65
|
+
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
archive.on('error', (err) => reject(err));
|
|
68
|
+
stream.on('close', () => resolve());
|
|
69
|
+
|
|
70
|
+
for (const dir of dirs) {
|
|
71
|
+
archive.directory(dir.sourceDir, dir.destDir);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
archive.pipe(stream);
|
|
75
|
+
archive.finalize();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { BlockletsBackup };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { copySync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const { BaseBackup } = require('./base');
|
|
4
|
+
|
|
5
|
+
class DataBackup extends BaseBackup {
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @returns {Promise<void>}
|
|
9
|
+
* @memberof BlockletsBackup
|
|
10
|
+
*/
|
|
11
|
+
async export() {
|
|
12
|
+
const blockletDataDir = join(this.serverDataDir, 'data', this.blocklet.meta.name);
|
|
13
|
+
const blockletBackupDataDir = join(this.blockletBackupDir, 'data');
|
|
14
|
+
|
|
15
|
+
copySync(blockletDataDir, blockletBackupDataDir, { overwrite: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { DataBackup };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { ensureDirSync, copySync, statSync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const { BaseBackup } = require('./base');
|
|
4
|
+
|
|
5
|
+
class LogsBackup extends BaseBackup {
|
|
6
|
+
async export() {
|
|
7
|
+
const sourceLogsDir = join(this.serverDataDir, 'logs', this.blocklet.meta.name);
|
|
8
|
+
ensureDirSync(sourceLogsDir);
|
|
9
|
+
|
|
10
|
+
const targetLogsDir = join(this.blockletBackupDir, 'logs');
|
|
11
|
+
|
|
12
|
+
copySync(sourceLogsDir, targetLogsDir, {
|
|
13
|
+
overwrite: true,
|
|
14
|
+
filter: (src) => {
|
|
15
|
+
return !statSync(src).isSymbolicLink();
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
LogsBackup,
|
|
23
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { removeSync, outputJsonSync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const states = require('../../../states');
|
|
4
|
+
const { BaseBackup } = require('./base');
|
|
5
|
+
|
|
6
|
+
class RoutingRuleBackup extends BaseBackup {
|
|
7
|
+
filename = 'routing_rule.json';
|
|
8
|
+
|
|
9
|
+
async export() {
|
|
10
|
+
const routingRule = await states.site.findOne({
|
|
11
|
+
domain: `${this.blocklet.meta.did}.blocklet-domain-group`,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
removeSync(join(this.blockletBackupDir, this.filename));
|
|
15
|
+
outputJsonSync(join(this.blockletBackupDir, this.filename), routingRule);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { RoutingRuleBackup };
|