@abtnode/core 1.6.3 → 1.6.7

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.
@@ -1,4 +1,5 @@
1
1
  const get = require('lodash/get');
2
+ const pick = require('lodash/pick');
2
3
  const cloneDeep = require('lodash/cloneDeep');
3
4
  const {
4
5
  DOMAIN_FOR_DEFAULT_SITE,
@@ -9,7 +10,6 @@ const {
9
10
  BLOCKLET_SITE_GROUP_SUFFIX,
10
11
  } = require('@abtnode/constant');
11
12
  const { BLOCKLET_UI_INTERFACES } = require('@blocklet/meta/lib/constants');
12
- const { pick } = require('lodash');
13
13
  const logger = require('@abtnode/logger')('@abtnode/core:router');
14
14
 
15
15
  const expandSites = (sites = []) => {
@@ -70,8 +70,9 @@ const normalizeRedirectUrl = (url) => {
70
70
  };
71
71
 
72
72
  class RouterManager extends EventEmitter {
73
- constructor() {
73
+ constructor({ certManager }) {
74
74
  super();
75
+ this.certManager = certManager;
75
76
 
76
77
  // HACK: do not emit any events from CLI
77
78
  if (isCLI()) {
@@ -300,7 +301,10 @@ class RouterManager extends EventEmitter {
300
301
  await this.validateRouterConfig('updateRoutingRule', { id, rule });
301
302
 
302
303
  // update rules
303
- const newRules = [...dbSite.rules.filter((x) => x.groupId !== rule.id), ...(await this.getRules(rule))];
304
+ const newRules = [
305
+ ...dbSite.rules.filter((x) => x.groupId !== rule.id || x.id !== rule.id), // 有些路由没有 rule.groupId
306
+ ...(await this.getRules(rule)),
307
+ ];
304
308
 
305
309
  const updateResult = await states.site.update({ _id: id }, { $set: { rules: newRules } });
306
310
  logger.info('update result', { updateResult });
@@ -363,7 +367,7 @@ class RouterManager extends EventEmitter {
363
367
  domain,
364
368
  });
365
369
  logger.info('add certificate result', { domain: newCert.domain });
366
- this.emit('router.certificate.add', { type: 'nginx', data: newCert });
370
+ this.emit('cert.added', { type: 'nginx', data: newCert });
367
371
  }
368
372
 
369
373
  // eslint-disable-next-line no-unused-vars
@@ -376,7 +380,7 @@ class RouterManager extends EventEmitter {
376
380
  const removeResult = await states.certificate.remove({ _id: id });
377
381
 
378
382
  logger.info('delete certificate', { removeResult, domain: tmpCert.domain });
379
- this.emit('router.certificate.remove', { type: 'nginx', data: { domain: tmpCert.domain } });
383
+ this.emit('cert.removed', { type: 'nginx', data: { domain: tmpCert.domain } });
380
384
  return {};
381
385
  }
382
386
 
@@ -386,7 +390,7 @@ class RouterManager extends EventEmitter {
386
390
  this.fixCertificate(entity);
387
391
  this.validateCertificate(entity, entity.domain);
388
392
  const dbEntity = await states.certificate.upsert(entity);
389
- this.emit('router.certificate.updated', { type: 'nginx', data: dbEntity });
393
+ this.emit('cert.issued', { type: 'nginx', data: dbEntity });
390
394
  }
391
395
 
392
396
  findCertificateByDomain(domain) {
@@ -448,7 +452,7 @@ class RouterManager extends EventEmitter {
448
452
  }
449
453
 
450
454
  async getMatchedCert(domain) {
451
- const certs = await states.certificate.find();
455
+ const certs = await this.certManager.getAll();
452
456
  const matchedCert = certs.find((cert) => this.isCertMatchedDomain(cert, domain));
453
457
 
454
458
  if (matchedCert) {
@@ -1,226 +1,17 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const util = require('util');
4
- const { EventEmitter } = require('events');
5
- const cloneDeep = require('lodash/cloneDeep');
6
- const DataStore =
7
- process.env.NODE_ENV === 'test' ? require('@nedb/core') : require('@nedb/multi')(Number(process.env.NEDB_MULTI_PORT));
1
+ const DB = require('@abtnode/db');
8
2
  const logger = require('@abtnode/logger')('@abtnode/core:states');
9
3
 
10
4
  const { isCLI } = require('../util');
11
5
 
12
- class BaseState extends EventEmitter {
6
+ class BaseState extends DB {
13
7
  constructor(baseDir, options) {
14
- super();
8
+ super(baseDir, options);
15
9
 
16
10
  // HACK: do not emit any events from CLI
17
11
  if (isCLI() && process.env.NODE_ENV !== 'test') {
18
12
  this.emit = (name) => logger.debug('stopped state db event in CLI', name);
19
13
  }
20
-
21
- const dbOptions = options.db || {};
22
- this.filename = path.join(baseDir, options.filename);
23
- this.options = Object.freeze(cloneDeep(options));
24
- this.db = new DataStore({
25
- filename: this.filename,
26
- timestampData: true,
27
- ...dbOptions,
28
- });
29
-
30
- logger.info('initialized', { filename: this.filename });
31
-
32
- this.ready = false;
33
- this.readyCallbacks = [];
34
- this.db.loadDatabase((err) => {
35
- if (err) {
36
- logger.error(`failed to load disk database ${this.filename}`, { error: err });
37
- console.error(err);
38
- } else {
39
- this.ready = true;
40
- if (this.readyCallbacks.length) {
41
- this.readyCallbacks.forEach((x) => x());
42
- }
43
- }
44
- });
45
-
46
- this.asyncDB = new Proxy(this.db, {
47
- get(target, property) {
48
- if (typeof target[property] === 'function') {
49
- return util
50
- .promisify((...args) => {
51
- const cb = args[args.length - 1];
52
- const rest = args.slice(0, args.length - 1);
53
-
54
- target[property](...rest, (err, ...result) => {
55
- if (err) {
56
- return cb(err);
57
- }
58
-
59
- if (result.length === 1) {
60
- return cb(null, result[0]);
61
- }
62
-
63
- return cb(null, result);
64
- });
65
- })
66
- .bind(target);
67
- }
68
-
69
- return target[property];
70
- },
71
- });
72
- }
73
-
74
- onReady(cb) {
75
- if (this.ready) {
76
- cb();
77
- } else {
78
- this.readyCallbacks.push(cb);
79
- }
80
- }
81
-
82
- createNotification(payload) {
83
- if (this.notification && typeof this.notification.create === 'function') {
84
- this.notification.create(payload);
85
- }
86
- }
87
-
88
- paginate(conditions, sort, paging) {
89
- const { pageSize: size = 20, page = 1 } = paging || {};
90
- const pageSize = Math.min(100, size);
91
-
92
- return new Promise((resolve, reject) => {
93
- this.db
94
- .find(conditions)
95
- .sort(sort)
96
- .exec((err, docs) => {
97
- if (err) {
98
- return reject(err);
99
- }
100
-
101
- const pageCount = Math.ceil(docs.length / pageSize);
102
- const total = docs.length;
103
- const skip = (page - 1) * pageSize;
104
- const list = docs.slice(skip, skip + pageSize);
105
-
106
- list.forEach((doc) => {
107
- // eslint-disable-next-line no-underscore-dangle
108
- doc.id = doc._id;
109
- });
110
-
111
- return resolve({
112
- list,
113
- paging: {
114
- total,
115
- pageSize,
116
- pageCount,
117
- page,
118
- },
119
- });
120
- });
121
- });
122
- }
123
-
124
- async updateById(id, updates, options = {}) {
125
- const [, doc] = await this.asyncDB.update({ _id: id }, updates, {
126
- multi: false,
127
- upsert: false,
128
- returnUpdatedDocs: true,
129
- ...options,
130
- });
131
-
132
- return doc;
133
- }
134
-
135
- update(...args) {
136
- if (args.length === 0) {
137
- throw new Error('param is required by update method');
138
- }
139
-
140
- if (typeof args[0] === 'string') {
141
- return this.updateById(...args);
142
- }
143
-
144
- return this.asyncDB.update(args[0], args[1], { returnUpdatedDocs: true, ...(args[2] || {}) });
145
- }
146
-
147
- count(conditions = {}) {
148
- return new Promise((resolve, reject) => {
149
- this.db.count(conditions, (err, num) => {
150
- if (err) {
151
- return reject(err);
152
- }
153
-
154
- return resolve(num);
155
- });
156
- });
157
- }
158
-
159
- remove(conditions = {}, options = { multi: false }) {
160
- return new Promise((resolve, reject) => {
161
- this.db.remove(conditions, options, (err, num) => {
162
- if (err) {
163
- return reject(err);
164
- }
165
-
166
- return resolve(num);
167
- });
168
- });
169
- }
170
-
171
- reset() {
172
- fs.unlinkSync(this.filename);
173
- }
174
-
175
- find(...args) {
176
- if (args.length === 0) {
177
- return this.asyncDB.find({});
178
- }
179
-
180
- return this.asyncDB.find(...args);
181
- }
182
-
183
- findOne(...args) {
184
- if (args.length === 0) {
185
- return this.asyncDB.findOne({});
186
- }
187
-
188
- return this.asyncDB.findOne(...args);
189
- }
190
-
191
- insert(...args) {
192
- return this.asyncDB.insert(...args);
193
14
  }
194
15
  }
195
16
 
196
- /**
197
- * Rename _id field name to id, this method has side effects
198
- * @param {object} entities
199
- */
200
- const renameIdFiledName = (entities, from = '_id', to = 'id') => {
201
- /* eslint-disable no-underscore-dangle, no-param-reassign */
202
-
203
- if (!entities) {
204
- return entities;
205
- }
206
-
207
- const mapEntity = (entity) => {
208
- if (entity[from]) {
209
- entity[to] = entity[from];
210
- delete entity[from];
211
- }
212
- };
213
-
214
- if (!Array.isArray(entities)) {
215
- mapEntity(entities);
216
- return entities;
217
- }
218
-
219
- entities.forEach(mapEntity);
220
-
221
- return entities;
222
- };
223
-
224
- BaseState.renameIdFiledName = renameIdFiledName;
225
-
226
17
  module.exports = BaseState;
@@ -6,7 +6,9 @@ const camelCase = require('lodash/camelCase');
6
6
 
7
7
  const BaseState = require('./base');
8
8
 
9
- const { mergeConfigs } = require('../blocklet/extras');
9
+ const { mergeConfigs, parseConfigs } = require('../blocklet/extras');
10
+
11
+ const noop = (k) => (v) => v[k];
10
12
 
11
13
  class BlockletExtrasState extends BaseState {
12
14
  constructor(baseDir, options = {}) {
@@ -17,12 +19,13 @@ class BlockletExtrasState extends BaseState {
17
19
  {
18
20
  name: 'configs',
19
21
  beforeSet: mergeConfigs,
22
+ afterGet: parseConfigs,
20
23
  },
21
24
 
22
25
  // setting
23
26
  {
24
27
  name: 'settings',
25
- beforeSet: (old, cur) => {
28
+ beforeSet: ({ old, cur }) => {
26
29
  const merged = { ...old, ...cur };
27
30
  Object.keys(merged).forEach((key) => {
28
31
  if (merged[key] === undefined || merged[key] === null) {
@@ -67,22 +70,23 @@ class BlockletExtrasState extends BaseState {
67
70
 
68
71
  generateGetFn(extra) {
69
72
  return async (did) => {
70
- const { name } = extra;
73
+ const { dek } = this.options;
74
+ const { name, afterGet = noop('data') } = extra;
71
75
  const item = await this.asyncDB.findOne({ did });
72
- return item ? item[name] : item;
76
+ return afterGet({ data: item ? item[name] : item, did, dek });
73
77
  };
74
78
  }
75
79
 
76
80
  generateSetFn(extra) {
77
81
  return async (did, data) => {
78
- const { name, beforeSet } = extra;
79
- const hasBeforeSet = beforeSet && typeof beforeSet === 'function';
82
+ const { dek } = this.options;
83
+ const { name, beforeSet = noop('cur') } = extra;
80
84
  const item = await this.asyncDB.findOne({ did });
81
85
 
82
86
  if (!item) {
83
87
  const insertData = {
84
88
  did,
85
- [name]: hasBeforeSet ? beforeSet(undefined, data) : data,
89
+ [name]: beforeSet({ old: undefined, cur: data, did, dek }),
86
90
  };
87
91
 
88
92
  const info = await this.asyncDB.insert(insertData);
@@ -93,7 +97,7 @@ class BlockletExtrasState extends BaseState {
93
97
  const itemNameValue = item[name];
94
98
  const updated = await this.update(item._id, {
95
99
  $set: {
96
- [name]: hasBeforeSet ? beforeSet(itemNameValue, data) : data,
100
+ [name]: beforeSet({ old: itemNameValue, cur: data, did, dek }),
97
101
  },
98
102
  });
99
103
  return updated[name];
@@ -132,22 +136,23 @@ class BlockletExtrasState extends BaseState {
132
136
 
133
137
  generateGetChildFn(extra) {
134
138
  return async (did, childDid) => {
135
- const { name } = extra;
139
+ const { dek } = this.options;
140
+ const { name, afterGet = noop('data') } = extra;
136
141
  const item = await this.asyncDB.findOne({ did });
137
142
  const children = (item || {}).children || [];
138
143
  const subItem = (children || []).find((x) => x.did === childDid);
139
- return subItem ? subItem[name] : null;
144
+ return afterGet({ data: subItem ? subItem[name] : null, did, dek });
140
145
  };
141
146
  }
142
147
 
143
148
  generateSetChildFn(extra) {
144
149
  return async (did, childDid, data) => {
145
- const { name, beforeSet } = extra;
146
- const hasBeforeSet = beforeSet && typeof beforeSet === 'function';
150
+ const { dek } = this.options;
151
+ const { name, beforeSet = noop('cur') } = extra;
147
152
  const item = await this.asyncDB.findOne({ did });
148
153
 
149
154
  if (!item) {
150
- const newData = hasBeforeSet ? beforeSet(undefined, data) : data;
155
+ const newData = beforeSet({ old: undefined, cur: data, did, dek });
151
156
  const insertData = {
152
157
  did,
153
158
  children: [
@@ -168,7 +173,7 @@ class BlockletExtrasState extends BaseState {
168
173
  const subItem = (children || []).find((x) => x.did === childDid);
169
174
 
170
175
  if (!subItem) {
171
- const newData = hasBeforeSet ? beforeSet(undefined, data) : data;
176
+ const newData = beforeSet({ old: undefined, cur: data, did, dek });
172
177
  await this.update(item._id, {
173
178
  $addToSet: {
174
179
  children: {
@@ -182,7 +187,7 @@ class BlockletExtrasState extends BaseState {
182
187
  return newData;
183
188
  }
184
189
 
185
- const newData = hasBeforeSet ? beforeSet(subItem[name], data) : data;
190
+ const newData = beforeSet({ old: subItem[name], cur: data, did, dek });
186
191
 
187
192
  children.forEach((x) => {
188
193
  if (x.did === childDid) {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-async-promise-executor */
1
2
  /* eslint-disable no-await-in-loop */
2
3
  /* eslint-disable function-paren-newline */
3
4
  /* eslint-disable no-underscore-dangle */
@@ -5,6 +6,7 @@ const omit = require('lodash/omit');
5
6
  const uniq = require('lodash/uniq');
6
7
  const detectPort = require('detect-port');
7
8
  const Lock = require('@abtnode/util/lib/lock');
9
+ const security = require('@abtnode/util/lib/security');
8
10
  const { fixPerson, fixInterfaces } = require('@blocklet/meta/lib/fix');
9
11
  const {
10
12
  BlockletStatus,
@@ -27,6 +29,40 @@ const getMaxPort = (ports = {}) => Math.max(Object.values(ports).map(Number));
27
29
  const getExternalPortsFromMeta = (meta) =>
28
30
  (meta.interfaces || []).map((x) => x.port && x.port.external).filter(Boolean);
29
31
 
32
+ const formatBlocklet = (blocklet, phase, dek) => {
33
+ forEachBlocklet(
34
+ blocklet,
35
+ (b) => {
36
+ if (b.meta) {
37
+ fixPerson(b.meta);
38
+ fixInterfaces(b.meta);
39
+ }
40
+
41
+ b.children = b.children || [];
42
+
43
+ if (!b.environments || !b.meta || !dek) {
44
+ return;
45
+ }
46
+
47
+ ['BLOCKLET_APP_SK'].forEach((key) => {
48
+ const env = b.environments.find((x) => x.key === key);
49
+ if (!env) {
50
+ return;
51
+ }
52
+ if (phase === 'onUpdate' && env.value.indexOf('0x') === 0) {
53
+ env.value = security.encrypt(env.value, b.meta.did, dek);
54
+ }
55
+ if (phase === 'onRead' && env.value.indexOf('0x') === -1) {
56
+ env.value = security.decrypt(env.value, b.meta.did, dek);
57
+ }
58
+ });
59
+ },
60
+ { sync: true }
61
+ );
62
+
63
+ return blocklet;
64
+ };
65
+
30
66
  class BlockletState extends BaseState {
31
67
  /**
32
68
  * Creates an instance of BlockletState
@@ -48,14 +84,7 @@ class BlockletState extends BaseState {
48
84
  return reject(err);
49
85
  }
50
86
 
51
- if (doc) {
52
- // TODO: this only exists for backward compatible
53
- fixPerson(doc.meta);
54
- fixInterfaces(doc.meta);
55
- doc.children = doc.children || [];
56
- }
57
-
58
- return resolve(doc);
87
+ return resolve(doc ? formatBlocklet(doc, 'onRead', this.options.dek) : null);
59
88
  });
60
89
  });
61
90
  }
@@ -70,14 +99,7 @@ class BlockletState extends BaseState {
70
99
  return reject(err);
71
100
  }
72
101
 
73
- return resolve(
74
- docs.filter(Boolean).map((doc) => {
75
- // TODO: this only exists for backward compatible
76
- fixPerson(doc.meta);
77
- fixInterfaces(doc.meta);
78
- return doc;
79
- })
80
- );
102
+ return resolve(docs.filter(Boolean).map((doc) => formatBlocklet(doc, 'onRead', this.options.dek)));
81
103
  });
82
104
  });
83
105
  }
@@ -114,7 +136,6 @@ class BlockletState extends BaseState {
114
136
  } = {}) {
115
137
  return this.getBlocklet(did).then(
116
138
  (doc) =>
117
- // eslint-disable-next-line no-async-promise-executor
118
139
  new Promise(async (resolve, reject) => {
119
140
  if (doc) {
120
141
  reject(new Error('Blocklet already added'));
@@ -165,6 +186,26 @@ class BlockletState extends BaseState {
165
186
  );
166
187
  }
167
188
 
189
+ updateBlocklet(did, updates) {
190
+ return this.getBlocklet(did).then(
191
+ (doc) =>
192
+ new Promise(async (resolve, reject) => {
193
+ if (!doc) {
194
+ reject(new Error('Blocklet does not exist'));
195
+ return;
196
+ }
197
+
198
+ try {
199
+ const formatted = formatBlocklet(updates, 'onUpdate', this.options.dek);
200
+ const newDoc = await this.updateById(doc._id, { $set: formatted });
201
+ resolve(newDoc);
202
+ } catch (err) {
203
+ reject(err);
204
+ }
205
+ })
206
+ );
207
+ }
208
+
168
209
  upgradeBlocklet({ meta, source, deployedFrom, children } = {}) {
169
210
  return this.getBlocklet(meta.did).then(
170
211
  (doc) =>
@@ -1,4 +1,4 @@
1
- const BaseState = require('./base');
1
+ const stateFactory = require('@abtnode/db/lib/factory');
2
2
  const NodeState = require('./node');
3
3
  const ChallengeState = require('./challenge');
4
4
  const BlockletState = require('./blocklet');
@@ -12,8 +12,6 @@ const SessionState = require('./session');
12
12
  const ExtrasState = require('./blocklet-extras');
13
13
  const CacheState = require('./cache');
14
14
 
15
- const states = {};
16
-
17
15
  const init = (dataDirs, options) => {
18
16
  const notificationState = new NotificationState(dataDirs.core, options);
19
17
  const nodeState = new NodeState(dataDirs.core, options, dataDirs, notificationState);
@@ -28,7 +26,7 @@ const init = (dataDirs, options) => {
28
26
  const extrasState = new ExtrasState(dataDirs.core, options);
29
27
  const cacheState = new CacheState(dataDirs.core, options);
30
28
 
31
- Object.assign(states, {
29
+ return {
32
30
  node: nodeState,
33
31
  blocklet: blockletState,
34
32
  notification: notificationState,
@@ -41,22 +39,7 @@ const init = (dataDirs, options) => {
41
39
  session: sessionState,
42
40
  blockletExtras: extrasState,
43
41
  cache: cacheState,
44
- });
42
+ };
45
43
  };
46
44
 
47
- module.exports = new Proxy(
48
- {},
49
- {
50
- get(target, prop) {
51
- if (prop === 'init') {
52
- return init;
53
- }
54
-
55
- if (states[prop] instanceof BaseState) {
56
- return states[prop];
57
- }
58
-
59
- throw new Error(`State ${String(prop)} may not be initialized`);
60
- },
61
- }
62
- );
45
+ module.exports = stateFactory(init);
@@ -1,7 +1,9 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
+ const semver = require('semver');
2
3
  const omit = require('lodash/omit');
3
4
  const isEqual = require('lodash/isEqual');
4
5
  const isEmpty = require('lodash/isEmpty');
6
+ const security = require('@abtnode/util/lib/security');
5
7
  const { isFromPublicKey } = require('@arcblock/did');
6
8
  const logger = require('@abtnode/logger')('@abtnode/core:node');
7
9
  const { ROUTER_PROVIDER_NONE, NODE_MODES, DISK_ALERT_THRESHOLD_PERCENT } = require('@abtnode/constant');
@@ -58,6 +60,7 @@ class NodeState extends BaseState {
58
60
  */
59
61
  read() {
60
62
  return new Promise((resolve, reject) => {
63
+ const { nodeDid, dek } = this.options;
61
64
  this.db.findOne({ did: this.options.nodeDid }, (err, record) => {
62
65
  if (err) {
63
66
  // eslint-disable-next-line no-console
@@ -66,6 +69,10 @@ class NodeState extends BaseState {
66
69
  }
67
70
 
68
71
  if (record) {
72
+ if (dek) {
73
+ record.sk = security.decrypt(record.sk, record.did, dek);
74
+ }
75
+
69
76
  return resolve(record);
70
77
  }
71
78
 
@@ -74,7 +81,6 @@ class NodeState extends BaseState {
74
81
  description,
75
82
  nodeSk,
76
83
  nodePk,
77
- nodeDid,
78
84
  nodeOwner,
79
85
  port,
80
86
  version,
@@ -100,7 +106,7 @@ class NodeState extends BaseState {
100
106
  name,
101
107
  description,
102
108
  pk: nodePk,
103
- sk: nodeSk,
109
+ sk: dek ? security.encrypt(nodeSk, nodeDid, dek) : nodeSk,
104
110
  did: nodeDid,
105
111
  initialized,
106
112
  version,
@@ -158,7 +164,7 @@ class NodeState extends BaseState {
158
164
 
159
165
  cleanupDirtyUpgradeState() {
160
166
  return this.read().then((doc) => {
161
- if (doc.nextVersion === doc.version) {
167
+ if (doc.nextVersion && semver.lte(doc.nextVersion, doc.version)) {
162
168
  const updates = { nextVersion: '', upgradeSessionId: '' };
163
169
 
164
170
  // FIXME: this may cause the node exit some mode unexpectedly if it is not being upgraded
@@ -192,7 +192,6 @@ const fillBlockletConfigs = (blocklet, configs) => {
192
192
  acc[x.key] = x.value;
193
193
  return acc;
194
194
  }, {});
195
- blocklet.userEnvironments = blocklet.configObj; // deprecated
196
195
  blocklet.environmentObj = (blocklet.environments || []).reduce((acc, x) => {
197
196
  acc[x.key] = x.value;
198
197
  return acc;
@@ -308,6 +307,7 @@ const getRuntimeEnvironments = (blocklet, nodeEnvironments) => {
308
307
 
309
308
  return {
310
309
  ...blocklet.environmentObj,
310
+ ...blocklet.configObj,
311
311
  ...nodeEnvironments,
312
312
  ...safeNodeEnvironments,
313
313
  };
package/lib/util/index.js CHANGED
@@ -332,6 +332,8 @@ const getDataDirs = (dataDir) => ({
332
332
  tmp: path.join(dataDir, 'tmp'),
333
333
  blocklets: path.join(dataDir, 'blocklets'),
334
334
  services: path.join(dataDir, 'services'),
335
+ modules: path.join(dataDir, 'modules'),
336
+ certManagerModule: path.join(dataDir, 'modules', 'certificate-manager'),
335
337
  });
336
338
 
337
339
  // Ensure data dir for Blocklet Server exists