@abtnode/core 1.16.32 → 1.16.33-beta-20241001-015316-119b726d

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 CHANGED
@@ -1740,7 +1740,7 @@ class TeamAPI extends EventEmitter {
1740
1740
  userSessions: [userSession],
1741
1741
  },
1742
1742
  });
1743
- logger.info('sync userSession to federated site successfully', {
1743
+ logger.debug('Sync userSession to federated site successfully', {
1744
1744
  userDid,
1745
1745
  visitorId,
1746
1746
  ua,
@@ -1751,8 +1751,8 @@ class TeamAPI extends EventEmitter {
1751
1751
  extra,
1752
1752
  });
1753
1753
  } catch (err) {
1754
- logger.info(
1755
- 'sync userSession to federated site failed',
1754
+ logger.error(
1755
+ 'Sync userSession to federated site failed',
1756
1756
  {
1757
1757
  userDid,
1758
1758
  visitorId,
@@ -102,7 +102,7 @@ const isFunction = require('lodash/isFunction');
102
102
  const { encode } = require('@abtnode/util/lib/base32');
103
103
  const formatContext = require('@abtnode/util/lib/format-context');
104
104
  const md5 = require('@abtnode/util/lib/md5');
105
- const { callFederated } = require('@abtnode/auth/lib/util/federated');
105
+ const { callFederated, isMaster } = require('@abtnode/auth/lib/util/federated');
106
106
  const pAll = require('p-all');
107
107
  const toBlockletDid = require('@blocklet/meta/lib/did');
108
108
  const launcher = require('../../util/launcher');
@@ -4622,7 +4622,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
4622
4622
  sites: [],
4623
4623
  });
4624
4624
  const masterSite = federated.sites[0];
4625
- if (masterSite && masterSite.isMaster !== false) {
4625
+ if (masterSite && isMaster(masterSite)) {
4626
4626
  const nodeInfo = await states.node.read();
4627
4627
  const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
4628
4628
  const { permanentWallet } = blockletInfo;
@@ -4673,7 +4673,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
4673
4673
  });
4674
4674
  const masterSite = federated.sites[0];
4675
4675
  // 只有 Master 可以调用这个逻辑
4676
- if (masterSite && masterSite.isMaster !== false && masterSite.appPid === did) {
4676
+ if (masterSite && isMaster(masterSite) && masterSite.appPid === did) {
4677
4677
  const nodeInfo = await states.node.read();
4678
4678
  const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
4679
4679
  const { permanentWallet } = blockletInfo;
@@ -4938,9 +4938,14 @@ class FederatedBlockletManager extends DiskBlockletManager {
4938
4938
  site: item,
4939
4939
  data: safeData,
4940
4940
  });
4941
+ logger.debug('Sync federated sites successfully', {
4942
+ action: 'sync',
4943
+ site: item,
4944
+ data: safeData,
4945
+ });
4941
4946
  return result;
4942
4947
  } catch (error) {
4943
- logger.warn('Failed to sync federated sites', {
4948
+ logger.error('Failed to sync federated sites', {
4944
4949
  error,
4945
4950
  action: 'sync',
4946
4951
  site: item,
@@ -194,7 +194,7 @@ const getLogContent = async (action, args, context, result, info, node) => {
194
194
  // blocklet federated config
195
195
  case 'requestJoinFederated':
196
196
  return `request join in federated: member site ${getSiteInfo(
197
- args.site
197
+ args.memberSite
198
198
  )} applied to join master site ${getBlockletInfo(result, info)}`;
199
199
  case 'quitFederated':
200
200
  return `quit federated: member site ${getSiteInfo(args.memberSite)} quit ${getBlockletInfo(result, info)}`;
@@ -204,16 +204,6 @@ const getLogContent = async (action, args, context, result, info, node) => {
204
204
  return `audit federated: master site ${getSiteInfo(
205
205
  args.masterSite
206
206
  )} audit federated login member site ${getBlockletInfo(result, info)} with status: ${args.status}`;
207
- case 'syncFederated':
208
- const syncData = {
209
- users: args.users,
210
- sites: args.sites,
211
- userSessions: args.userSessions,
212
- };
213
- return `sync federated: site ${getSiteInfo(args.callerSite)} sync data:
214
- \`\`\`json
215
- ${JSON.stringify(syncData, null, 2)}
216
- \`\`\``;
217
207
 
218
208
  // federated user relate
219
209
  case 'loginFederated':
@@ -415,7 +405,6 @@ const getLogCategory = (action) => {
415
405
  case 'requestQuitFederated':
416
406
  case 'disbandFederated':
417
407
  case 'auditFederated':
418
- case 'syncFederated':
419
408
  return 'blocklet';
420
409
 
421
410
  // store,此处应该返回 server
@@ -522,7 +522,8 @@ SELECT did,inviter,generation FROM UserTree`.trim();
522
522
  if (exist) {
523
523
  // immutable fields
524
524
  delete updates.locale;
525
- delete updates.sourceAppPid;
525
+ // NOTICE: 开放允许修改 sourceAppPid 字段,来进行旧数据的修正
526
+ // delete updates.sourceAppPid;
526
527
  delete updates.inviter;
527
528
  delete updates.generation;
528
529
 
@@ -69,6 +69,7 @@ const {
69
69
  BLOCKLET_RESOURCE_DIR,
70
70
  BLOCKLET_TENANT_MODES,
71
71
  PROJECT,
72
+ BLOCKLET_INTERFACE_TYPE_DOCKER,
72
73
  } = require('@blocklet/constant');
73
74
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
74
75
  const { getBlockletEngine } = require('@blocklet/meta/lib/engine');
@@ -98,6 +99,7 @@ const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-
98
99
  const { isInServerlessMode } = require('@abtnode/util/lib/serverless');
99
100
  const md5 = require('@abtnode/util/lib/md5');
100
101
 
102
+ const ensureDockerEndpointHealthy = require('@abtnode/util/lib/ensure-docker-endpoint-healthy');
101
103
  const { validate: validateEngine, get: getEngine } = require('../blocklet/manager/engine');
102
104
 
103
105
  const isRequirementsSatisfied = require('./requirement');
@@ -689,7 +691,7 @@ const startBlockletProcess = async (
689
691
  const nextOptions =
690
692
  b.meta.group === 'static' || b.mode === BLOCKLET_MODES.DEVELOPMENT
691
693
  ? options
692
- : await parseDockerOptionsFromPm2(options, nodeInfo);
694
+ : await parseDockerOptionsFromPm2({ options, nodeInfo, meta: b.meta, ports: b.ports });
693
695
 
694
696
  await pm2.startAsync(nextOptions);
695
697
 
@@ -887,7 +889,9 @@ const _checkProcessHealthy = async (blocklet, { minConsecutiveTime, timeout, log
887
889
  const { processId } = env;
888
890
 
889
891
  const webInterface = (meta.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
890
- if (!webInterface) {
892
+ const dockerInterface = (meta.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_DOCKER);
893
+
894
+ if (!webInterface && !dockerInterface) {
891
895
  // TODO: how do we check healthy for service interfaces
892
896
  throw new Error(`Blocklet ${name} does not have any web interface`);
893
897
  }
@@ -915,7 +919,7 @@ const _checkProcessHealthy = async (blocklet, { minConsecutiveTime, timeout, log
915
919
  throw new Error('process not start within 10s');
916
920
  }
917
921
 
918
- const port = findInterfacePortByName({ meta, ports }, webInterface.name);
922
+ const port = findInterfacePortByName({ meta, ports }, (webInterface || dockerInterface).name);
919
923
  if (logToTerminal) {
920
924
  // eslint-disable-next-line no-console
921
925
  console.log(
@@ -925,7 +929,11 @@ const _checkProcessHealthy = async (blocklet, { minConsecutiveTime, timeout, log
925
929
  );
926
930
  }
927
931
  try {
928
- await ensureEndpointHealthy({ port, minConsecutiveTime, timeout });
932
+ if (webInterface) {
933
+ await ensureEndpointHealthy({ port, minConsecutiveTime, timeout });
934
+ } else {
935
+ await ensureDockerEndpointHealthy({ hostIP: dockerInterface.hostIP, port, minConsecutiveTime, timeout });
936
+ }
929
937
  } catch (error) {
930
938
  logger.error('ensure endpoint healthy failed', { port, minConsecutiveTime, timeout });
931
939
  throw error;
@@ -0,0 +1,31 @@
1
+ const dockerfile = `
2
+ # Base image
3
+ FROM node:22-alpine
4
+
5
+ RUN apk add --no-cache python3 make gcc g++ build-base socat curl gnupg wget su-exec shadow git ca-certificates cairo-dev pango-dev jpeg-dev giflib-dev curl build-essential
6
+
7
+ RUN ln -sf /usr/bin/python3 /usr/bin/python
8
+
9
+ ENV DOCKER_DATA /var/lib/blocklet
10
+ VOLUME /var/lib/blocklet
11
+
12
+ RUN mkdir -p /var/lib/blocklet
13
+
14
+ ENV NPM_CONFIG_CACHE=/var/lib/blocklet/.npm
15
+
16
+ RUN npm install -g pnpm nr
17
+
18
+ ENV PATH=/sbin:$PATH
19
+
20
+ WORKDIR /var/lib/blocklet
21
+ `.trim();
22
+
23
+ module.exports = {
24
+ dockerfile,
25
+ image: 'node_alpine_v3',
26
+ shell: 'su-exec',
27
+ network: '--network host',
28
+ baseDir: '/var/lib/blocklet',
29
+ installNodeModules: true,
30
+ runBaseScript: true,
31
+ };
@@ -0,0 +1,92 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const md5 = require('@abtnode/util/lib/md5');
4
+ const alpineDockerfile = require('./alpine-dockerfile');
5
+
6
+ function getBlockletCustomDockerfile({ appDir, dataDir, meta, ports }) {
7
+ const dockerMeta = meta.docker || {};
8
+ if (!dockerMeta.image && !dockerMeta.dockerfile) {
9
+ return alpineDockerfile;
10
+ }
11
+ let file = '';
12
+ if (dockerMeta.dockerfile) {
13
+ // 为了安全性, 默认不允许 build custom dockerfile
14
+ if (!process.env.CAN_BUILD_DOCKER_FILE) {
15
+ throw new Error('Cannot build dockerfile');
16
+ }
17
+ const dockerfilePath = path.join(appDir, dockerMeta.dockerfile);
18
+ if (!fs.existsSync(dockerfilePath)) {
19
+ throw new Error('Dockerfile not found');
20
+ }
21
+ file = fs.readFileSync(dockerfilePath, 'utf8').trim();
22
+ }
23
+
24
+ const hash = md5(file);
25
+ let shell;
26
+ if (dockerMeta.shell) {
27
+ shell = dockerMeta.shell;
28
+ } else if (/su-exec/.test(file)) {
29
+ shell = 'su-exec';
30
+ } else if (/gosu/.test(file)) {
31
+ shell = 'gosu';
32
+ } else {
33
+ shell = 'sh';
34
+ }
35
+ const interfaceMeta = (meta.interfaces || [])
36
+ .map((item) => (item.containerPort ? item : null))
37
+ .filter((item) => item !== null);
38
+
39
+ let baseDir = '/var/lib/blocklet';
40
+ if (dockerMeta.workdir) {
41
+ baseDir = dockerMeta.workdir;
42
+ } else {
43
+ const workDir = file.match(/WORKDIR\s+(.+)/)?.[1];
44
+ if (workDir) {
45
+ baseDir = workDir;
46
+ }
47
+ }
48
+
49
+ const volumes = (dockerMeta.volumes || [])
50
+ .map((volume) => {
51
+ const [a, b] = volume.split(':');
52
+ const hostDir = a.replace('$BLOCKLET_DATA_DIR', dataDir).replace('$BLOCKLET_APP_DIR', appDir);
53
+ return ` -v ${hostDir}:${b} `;
54
+ })
55
+ .join('');
56
+
57
+ let network = '';
58
+ if (interfaceMeta.length > 0) {
59
+ network = interfaceMeta
60
+ .map((item) => {
61
+ const port = ports[item.port];
62
+ return port ? ` -p ${item.hostIP || '127.0.0.1'}:${port}:${item.containerPort}` : '';
63
+ })
64
+ .join('')
65
+ .trim();
66
+ }
67
+
68
+ if (!network.length) {
69
+ network = `--network ${dockerMeta.network || 'host'}`;
70
+ }
71
+
72
+ // 防止 dockerImage 和 docker args, dockerScript 会注入 -v 等命令
73
+ const dockerArgs = [dockerMeta.command, dockerMeta.script, dockerMeta.image].filter(Boolean).join(' ');
74
+ if (/(\s|^)-v\s/.test(dockerArgs)) {
75
+ throw new Error('Detected forbidden volume mount (-v) in docker args');
76
+ }
77
+
78
+ return {
79
+ dockerfile: file,
80
+ image: dockerMeta.image || `blocklet_custom_${hash.slice(0, 16)}`,
81
+ shell,
82
+ baseDir,
83
+ volumes,
84
+ network,
85
+ runBaseScript: !!dockerMeta.runBaseScript,
86
+ args: dockerMeta.command || '',
87
+ script: dockerMeta.script || dockerMeta.installNodeModules ? '' : 'tail -f /dev/null',
88
+ installNodeModules: !!dockerMeta.installNodeModules,
89
+ };
90
+ }
91
+
92
+ module.exports = getBlockletCustomDockerfile;
@@ -3,57 +3,19 @@ const path = require('path');
3
3
  const { execSync } = require('child_process');
4
4
  const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
5
5
  const { checkDockerHasImage } = require('./check-docker-has-image');
6
+ const getBlockletCustomDockerfile = require('./blocklet-custom-dockerfile');
6
7
 
7
- const DOCKER_IMAGE_NAME = 'blocklet-node-v2';
8
-
9
- async function buildImage() {
8
+ async function buildImage({ image, dockerfile }) {
10
9
  const dir = process.env.ABT_NODE_DATA_DIR;
11
10
  if (!dir) {
12
11
  logger.error('ABT_NODE_DATA_DIR is not set');
13
12
  return;
14
13
  }
15
- if (await checkDockerHasImage(DOCKER_IMAGE_NAME)) {
14
+
15
+ if (await checkDockerHasImage(image)) {
16
16
  return;
17
17
  }
18
18
  const dockerfilePath = path.join(dir, 'Dockerfile');
19
- const dockerfileContent = `
20
- # Base image
21
- FROM node:22-alpine
22
-
23
- # Install necessary packages
24
- RUN apk add --no-cache python3 make gcc g++ build-base socat curl gnupg wget su-exec shadow git ca-certificates cairo-dev pango-dev jpeg-dev giflib-dev curl
25
-
26
- # Create a symlink for python if necessary
27
- RUN ln -sf /usr/bin/python3 /usr/bin/python
28
-
29
-
30
- # RUN install --verbose --directory --owner blocklet --group blocklet --mode 3777 /var/run/blocklet
31
-
32
- ENV DOCKER_DATA /var/lib/blocklet
33
- VOLUME /var/lib/blocklet
34
-
35
- RUN mkdir -p /var/lib/blocklet
36
-
37
- ENV NPM_CONFIG_CACHE=/var/lib/blocklet/.npm
38
-
39
- # Set appropriate ownership and permission
40
- RUN mkdir -p /var/lib/blocklet/.npm
41
- RUN mkdir -p /var/lib/blocklet/logs
42
- RUN mkdir -p /var/lib/blocklet/cache
43
- RUN mkdir -p /var/lib/blocklet/data
44
- RUN mkdir -p /var/lib/blocklet/blocklets
45
- RUN chmod -R 777 /var/lib/blocklet
46
-
47
- # Install global npm packages
48
- RUN npm install -g pnpm nr
49
-
50
- # Ensure correct PATH
51
- ENV PATH=/sbin:$PATH
52
-
53
- # Set working directory
54
- WORKDIR /var/lib/blocklet
55
-
56
- `.trim();
57
19
 
58
20
  try {
59
21
  await fs.access(dockerfilePath);
@@ -62,36 +24,37 @@ WORKDIR /var/lib/blocklet
62
24
  // File doesn't exist, no need to remove
63
25
  }
64
26
 
65
- await fs.writeFile(dockerfilePath, dockerfileContent);
27
+ await fs.writeFile(dockerfilePath, dockerfile);
66
28
  try {
67
29
  execSync(
68
- `docker build -f ${path.join(dir, 'Dockerfile')} --no-cache --build-arg uid=$(id -u) --build-arg gid=$(id -g) -t ${DOCKER_IMAGE_NAME} .`,
30
+ `docker build -f ${path.join(dir, 'Dockerfile')} --no-cache --build-arg uid=$(id -u) --build-arg gid=$(id -g) -t ${image} .`,
69
31
  {
70
32
  stdio: 'inherit',
71
33
  encoding: 'utf-8',
72
34
  }
73
35
  );
74
36
  } catch (error) {
75
- // 如果 docker 会报错: ERROR: failed to solve: failed to read dockerfile: open /var/snap/docker/common/var-lib-docker/tmp/buildkit-mount875872699/Dockerfile: no such file or directory
76
- // 表示 docker 没有权限访问 ABT_NODE_DATA_DIR 目录, 后续的一系列操作也会失败
77
37
  logger.error('Failed to build Docker image. Error:', error.message);
78
38
  throw error;
79
39
  }
80
40
  logger.info('Success build docker image');
81
41
  }
82
42
 
83
- let building = false;
43
+ const building = {};
84
44
 
85
- async function createDockerImage() {
86
- if (building) {
87
- await building;
88
- return;
45
+ async function createDockerImage(data) {
46
+ const dockerfileInfo = getBlockletCustomDockerfile(data);
47
+ if (building[dockerfileInfo.image]) {
48
+ await building[dockerfileInfo.image];
49
+ return dockerfileInfo;
50
+ }
51
+ if (dockerfileInfo.dockerfile) {
52
+ building[dockerfileInfo.image] = buildImage(dockerfileInfo);
53
+ await building[dockerfileInfo.image];
89
54
  }
90
- building = buildImage();
91
- await building;
55
+ return dockerfileInfo;
92
56
  }
93
57
 
94
58
  module.exports = {
95
59
  createDockerImage,
96
- DOCKER_IMAGE_NAME,
97
60
  };
@@ -4,10 +4,10 @@ const { stringify: stringifyEnvFile } = require('envfile');
4
4
  const path = require('path');
5
5
  const fs = require('fs/promises');
6
6
  const os = require('os');
7
- const { existsSync } = require('fs-extra');
7
+ const { existsSync, writeFileSync } = require('fs-extra');
8
8
  const { NODE_MODES } = require('@abtnode/constant');
9
9
  const parseDockerName = require('./parse-docker-name');
10
- const { DOCKER_IMAGE_NAME, createDockerImage } = require('./create-docker-image');
10
+ const { createDockerImage } = require('./create-docker-image');
11
11
  const { checkDockerInstalled, checkDockerInstalledCache } = require('./check-docker-installed');
12
12
 
13
13
  async function mkdirpWithPermissions(dir, mode = 0o755) {
@@ -20,87 +20,122 @@ async function mkdirpWithPermissions(dir, mode = 0o755) {
20
20
  }
21
21
  }
22
22
 
23
- async function parseDockerOptionsFromPm2(input, nodeInfo) {
24
- // serverless 模式下,不支持跳过 docker
25
- if (input.env.SKIP_DOCKER && nodeInfo.mode !== NODE_MODES.SERVERLESS) {
26
- return input;
27
- }
23
+ // 函数用于生成 root_script.sh user_script.sh
24
+ function createScripts({ rootCommands, userCommands, scriptDir, baseDir, uid, gid, shell }) {
25
+ const userScriptPath = path.join(scriptDir, 'user_script.sh');
26
+ const rootScriptPath = path.join(scriptDir, 'root_script.sh');
27
+ const execShell = shell === 'sh' || shell === 'bash' ? '' : `exec ${shell} ${uid}:${gid}`;
28
+ // 生成 root_script.sh 内容
29
+ const rootScriptContent = `
30
+ #!/bin/sh
31
+ ${rootCommands.filter(Boolean).join('\n')}
32
+ ${execShell} sh -c '${path.join(baseDir, 'dockerTemp', 'user_script.sh')}'
33
+ `.trim();
34
+
35
+ const userScriptContent = `
36
+ #!/bin/sh
37
+
38
+ ${userCommands.filter(Boolean).join('\n')}
39
+ `.trim();
40
+
41
+ writeFileSync(rootScriptPath, rootScriptContent, { mode: 0o755 });
42
+ writeFileSync(userScriptPath, userScriptContent, { mode: 0o755 });
43
+ }
28
44
 
29
- // 环境变量或配置项启用了 docker, 就尝试使用 docker
30
- const useDocker = input.env.USE_DOCKER || nodeInfo.enableDocker;
45
+ async function parseDockerOptionsFromPm2({ options, nodeInfo, meta, ports }) {
46
+ // Serverless mode does not support skipping Docker
47
+ if (options.env.SKIP_DOCKER && nodeInfo.mode !== NODE_MODES.SERVERLESS) {
48
+ return options;
49
+ }
31
50
 
51
+ // Use Docker if enabled via environment variable or config
52
+ const useDocker = options.env.USE_DOCKER || nodeInfo.enableDocker;
32
53
  if (!useDocker) {
33
- return input;
54
+ return options;
34
55
  }
35
56
 
36
- // 防止启用了 docker, 但是因为 docker 意外崩溃, 导致 blocklet 不使用 docker 进行运行
57
+ // Ensure Docker is installed
37
58
  if (!nodeInfo.isDockerInstalled) {
38
59
  checkDockerInstalledCache.checked = false;
39
60
  if (!(await checkDockerInstalled())) {
40
61
  throw new Error('Docker is not installed');
41
62
  }
42
63
  }
43
- await createDockerImage();
44
- const options = { ...input };
64
+
65
+ const dockerInfo = await createDockerImage({
66
+ appDir: options.env.BLOCKLET_APP_DIR,
67
+ dataDir: options.env.BLOCKLET_DATA_DIR,
68
+ meta,
69
+ ports,
70
+ });
71
+ const nextOptions = { ...options };
72
+ nextOptions.env = { ...nextOptions.env };
45
73
  const name = parseDockerName(options.name);
46
74
 
47
- // 要确保 BLOCKLET_APP_DIR 等环境变量在 docker 容器内正常使用
48
- options.env.BLOCKLET_DOCKER_CPUS = options.env.BLOCKLET_DOCKER_CPUS || '2';
49
- options.env.BLOCKLET_DOCKER_MEMORY = options.env.BLOCKLET_DOCKER_MEMORY || '2g';
50
- options.env.BLOCKLET_DOCKER_DISK_SIZE = options.env.BLOCKLET_DOCKER_DISK_SIZE || '0g';
51
- options.env.BLOCKLET_DOCKER_NAME = name;
52
- options.env.INTERNAL_HOST = 'host.docker.internal';
75
+ // Ensure environment variables are properly set within the Docker container
76
+ const envDefaults = {
77
+ BLOCKLET_DOCKER_CPUS: '2',
78
+ BLOCKLET_DOCKER_MEMORY: '2g',
79
+ BLOCKLET_DOCKER_DISK_SIZE: '0g',
80
+ BLOCKLET_DOCKER_NAME: name,
81
+ INTERNAL_HOST: 'host.docker.internal',
82
+ };
83
+ nextOptions.env = { ...nextOptions.env, ...envDefaults };
53
84
 
54
- let dockerEnv = { ...options.env };
55
- const baseDir = '/var/lib/blocklet';
85
+ const dockerEnv = { ...nextOptions.env };
86
+ const { baseDir } = dockerInfo;
56
87
  const serverDir = process.env.ABT_NODE_DATA_DIR;
88
+ const replaceDir = (dir) => dir.replace(serverDir, baseDir);
89
+
57
90
  dockerEnv.DOCKER_CONTAINER_SERVER_DIR = baseDir;
58
- dockerEnv.BLOCKLET_APP_DATA_DIR = options.env.BLOCKLET_APP_DATA_DIR.replace(serverDir, baseDir);
59
- dockerEnv.BLOCKLET_APP_DIR = options.env.BLOCKLET_APP_DIR.replace(serverDir, baseDir);
60
- dockerEnv.BLOCKLET_DATA_DIR = options.env.BLOCKLET_DATA_DIR.replace(serverDir, baseDir);
91
+ dockerEnv.BLOCKLET_APP_DATA_DIR = replaceDir(nextOptions.env.BLOCKLET_APP_DATA_DIR);
92
+ dockerEnv.BLOCKLET_APP_DIR = replaceDir(nextOptions.env.BLOCKLET_APP_DIR);
93
+ dockerEnv.BLOCKLET_DATA_DIR = replaceDir(nextOptions.env.BLOCKLET_DATA_DIR);
61
94
  dockerEnv.BLOCKLET_LOG_DIR = path.join(baseDir, 'logs');
62
95
  dockerEnv.BLOCKLET_CACHE_DIR = path.join(baseDir, 'cache');
63
- dockerEnv.BLOCKLET_APP_SHARE_DIR = options.env.BLOCKLET_APP_SHARE_DIR.replace(serverDir, baseDir);
64
- dockerEnv.BLOCKLET_SHARE_DIR = options.env.BLOCKLET_SHARE_DIR.replace(serverDir, baseDir);
65
- dockerEnv.BLOCKLET_DOCKER_NAME = name;
96
+ dockerEnv.BLOCKLET_APP_SHARE_DIR = replaceDir(nextOptions.env.BLOCKLET_APP_SHARE_DIR);
97
+ dockerEnv.BLOCKLET_SHARE_DIR = replaceDir(nextOptions.env.BLOCKLET_SHARE_DIR);
66
98
  dockerEnv.BLOCKLET_HOST = 'host.docker.internal';
67
99
  dockerEnv.USING_DOCKER = 'true';
68
100
  dockerEnv.LOCAL_USER_ID = process.getuid();
69
101
 
70
102
  let nodeModulesName = 'node_modules';
71
103
  let installScript = '';
72
- if (
73
- existsSync(path.join(options.env.BLOCKLET_APP_DIR, 'package.json')) &&
74
- existsSync(path.join(options.env.BLOCKLET_APP_DIR, 'blocklet-compact.js'))
75
- ) {
76
- const packageJson = JSON.parse(await fs.readFile(path.join(options.env.BLOCKLET_APP_DIR, 'package.json'), 'utf-8'));
104
+ const appDir = nextOptions.env.BLOCKLET_APP_DIR;
105
+ const packageJsonPath = path.join(appDir, 'package.json');
106
+ const compactJsPath = path.join(appDir, 'blocklet-compact.js');
107
+
108
+ if (dockerInfo.installNodeModules && existsSync(packageJsonPath) && existsSync(compactJsPath)) {
109
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
77
110
  if (packageJson.dependencies || packageJson.devDependencies) {
78
- dockerEnv.BLOCKLET_APP_PACKAGE_JSON = JSON.stringify(packageJson);
79
111
  nodeModulesName = 'docker_node_modules';
80
- installScript = 'pnpm install --unsafe-perm --fetch-timeout=60000 --fetch-retries=5 && ';
112
+ installScript = 'pnpm install --unsafe-perm --fetch-timeout=60000 --fetch-retries=5';
81
113
  }
82
114
  }
83
115
 
84
- dockerEnv = JSON.parse(JSON.stringify(dockerEnv).replace(new RegExp(serverDir, 'g'), baseDir));
85
- dockerEnv.DOCKER_HOST_SERVER_DIR = serverDir;
86
- const envVars = stringifyEnvFile(dockerEnv);
87
- const dockerEnvFile = path.join(
88
- serverDir,
89
- 'tmp',
90
- 'docker',
91
- options.env.BLOCKLET_REAL_NAME,
92
- COMPONENT_DOCKER_ENV_FILE_NAME
93
- );
116
+ // Replace all occurrences of serverDir with baseDir in dockerEnv
117
+ const dockerEnvString = JSON.stringify(dockerEnv).replace(new RegExp(serverDir, 'g'), baseDir);
118
+ const updatedDockerEnv = JSON.parse(dockerEnvString);
119
+
120
+ updatedDockerEnv.DOCKER_HOST_SERVER_DIR = serverDir;
121
+ const envVars = stringifyEnvFile(updatedDockerEnv);
122
+ const dockerTempDir = path.join(serverDir, 'tmp', 'docker', nextOptions.env.BLOCKLET_REAL_NAME);
123
+ const dockerEnvFile = path.join(dockerTempDir, COMPONENT_DOCKER_ENV_FILE_NAME);
94
124
  await mkdirpWithPermissions(path.dirname(dockerEnvFile));
95
125
  await fs.writeFile(dockerEnvFile, envVars);
96
126
 
97
- const runScript = /(npm|yarn|pnpm|bun)/g.test(options.script) ? options.script : `node ${options.script}`;
98
- const networkHost = '--network host';
99
- const diskSize =
100
- options.env.BLOCKLET_DOCKER_DISK_SIZE !== '0g' ? `--storage-opt size=${options.env.BLOCKLET_DOCKER_DISK_SIZE}` : '';
127
+ let runScript = dockerInfo.script || '';
128
+ if (!runScript && dockerInfo.installNodeModules) {
129
+ runScript = /(npm|yarn|pnpm|bun)/g.test(nextOptions.script) ? nextOptions.script : `node ${nextOptions.script}`;
130
+ }
101
131
 
102
- options.args = [];
103
- options.namespace = 'blocklets_docker';
132
+ const diskSizeOption =
133
+ nextOptions.env.BLOCKLET_DOCKER_DISK_SIZE !== '0g'
134
+ ? `--storage-opt size=${nextOptions.env.BLOCKLET_DOCKER_DISK_SIZE}`
135
+ : '';
136
+
137
+ nextOptions.args = [];
138
+ nextOptions.namespace = 'blocklets_docker';
104
139
  const uid = process.getuid();
105
140
  const gid = process.getgid();
106
141
 
@@ -112,43 +147,43 @@ async function parseDockerOptionsFromPm2(input, nodeInfo) {
112
147
  target: path.join(baseDir, 'blocklets'),
113
148
  canEdit: false,
114
149
  },
115
- { source: options.env.BLOCKLET_APP_DIR, target: dockerEnv.BLOCKLET_APP_DIR, canEdit: true },
116
- { source: options.env.BLOCKLET_DATA_DIR, target: dockerEnv.BLOCKLET_DATA_DIR, canEdit: true },
117
- { source: options.env.BLOCKLET_LOG_DIR, target: dockerEnv.BLOCKLET_LOG_DIR, canEdit: true },
118
- { source: options.env.BLOCKLET_CACHE_DIR, target: dockerEnv.BLOCKLET_CACHE_DIR, canEdit: true },
150
+ { source: nextOptions.env.BLOCKLET_APP_DIR, target: dockerEnv.BLOCKLET_APP_DIR, canEdit: true },
151
+ { source: nextOptions.env.BLOCKLET_DATA_DIR, target: dockerEnv.BLOCKLET_DATA_DIR, canEdit: true },
152
+ { source: nextOptions.env.BLOCKLET_LOG_DIR, target: dockerEnv.BLOCKLET_LOG_DIR, canEdit: true },
153
+ { source: nextOptions.env.BLOCKLET_CACHE_DIR, target: dockerEnv.BLOCKLET_CACHE_DIR, canEdit: true },
119
154
  {
120
- source: path.join(options.env.BLOCKLET_APP_DIR, nodeModulesName),
155
+ source: path.join(nextOptions.env.BLOCKLET_APP_DIR, nodeModulesName),
121
156
  target: path.join(dockerEnv.BLOCKLET_APP_DIR, 'node_modules'),
122
157
  canEdit: true,
123
158
  },
124
159
  {
125
- source: path.join(options.env.BLOCKLET_SHARE_DIR),
126
- target: path.join(dockerEnv.BLOCKLET_SHARE_DIR),
160
+ source: nextOptions.env.BLOCKLET_SHARE_DIR,
161
+ target: dockerEnv.BLOCKLET_SHARE_DIR,
127
162
  canEdit: false,
128
163
  },
129
164
  {
130
- source: path.join(options.env.BLOCKLET_APP_SHARE_DIR),
131
- target: path.join(dockerEnv.BLOCKLET_APP_SHARE_DIR),
165
+ source: nextOptions.env.BLOCKLET_APP_SHARE_DIR,
166
+ target: dockerEnv.BLOCKLET_APP_SHARE_DIR,
132
167
  canEdit: true,
133
168
  },
134
169
  {
135
- source: path.join(options.env.BLOCKLET_APP_DATA_DIR, '.config'),
170
+ source: path.join(nextOptions.env.BLOCKLET_APP_DATA_DIR, '.config'),
136
171
  target: path.join(dockerEnv.BLOCKLET_APP_DATA_DIR, '.config'),
137
172
  canEdit: false,
138
173
  },
139
174
  {
140
- source: path.join(options.env.BLOCKLET_APP_DATA_DIR, '.assets'),
175
+ source: path.join(nextOptions.env.BLOCKLET_APP_DATA_DIR, '.assets'),
141
176
  target: path.join(dockerEnv.BLOCKLET_APP_DATA_DIR, '.assets'),
142
177
  canEdit: false,
143
178
  },
144
179
  {
145
- source: path.join(options.env.BLOCKLET_APP_DATA_DIR, '.projects'),
180
+ source: path.join(nextOptions.env.BLOCKLET_APP_DATA_DIR, '.projects'),
146
181
  target: path.join(dockerEnv.BLOCKLET_APP_DATA_DIR, '.projects'),
147
182
  canEdit: true,
148
183
  },
149
184
  {
150
- source: path.join(options.env.BLOCKLET_APP_DATA_DIR),
151
- target: path.join(dockerEnv.BLOCKLET_APP_DATA_DIR),
185
+ source: nextOptions.env.BLOCKLET_APP_DATA_DIR,
186
+ target: dockerEnv.BLOCKLET_APP_DATA_DIR,
152
187
  canEdit: false,
153
188
  },
154
189
  ].filter((v) => {
@@ -158,49 +193,65 @@ async function parseDockerOptionsFromPm2(input, nodeInfo) {
158
193
  sourceDirs.add(v.source);
159
194
  return true;
160
195
  });
161
- let mount = '';
196
+
197
+ // Ensure necessary directories exist
162
198
  for (const bindMount of bindMounts) {
163
199
  if (!existsSync(bindMount.source)) {
164
200
  // eslint-disable-next-line no-await-in-loop
165
201
  await mkdirpWithPermissions(bindMount.source);
166
202
  }
167
- mount += ` -v ${bindMount.source}:${bindMount.target}:${bindMount.canEdit ? 'rw' : 'ro'} `;
168
203
  }
169
- await mkdirpWithPermissions(path.join(options.env.BLOCKLET_APP_DIR, 'node_modules'));
204
+ await mkdirpWithPermissions(path.join(nextOptions.env.BLOCKLET_APP_DIR, 'node_modules'));
170
205
 
171
- // mac 系统上,停止后等待 2 秒,不然 orbstack 会崩溃
206
+ // Construct volume arguments
207
+ const volumes = bindMounts
208
+ .map(({ source, target, canEdit }) => `-v ${source}:${target}:${canEdit ? 'rw' : 'ro'}`)
209
+ .join(' ');
210
+
211
+ // On macOS, wait 2 seconds after stopping to prevent Orbstack from crashing
172
212
  const awaitRestart = os.platform() === 'linux' ? '' : 'sleep 2 &&';
173
213
 
174
- options.script = `
214
+ createScripts({
215
+ rootCommands: [
216
+ `echo "start ${meta.name} in docker: ${dockerInfo.network}"`,
217
+ `mkdir -p ${baseDir} || :`,
218
+ `mkdir -p ${path.join(baseDir, 'blocklets')} || :`,
219
+ `mkdir -p ${dockerEnv.BLOCKLET_APP_DIR} || :`,
220
+ `mkdir -p ${dockerEnv.BLOCKLET_APP_DATA_DIR} || :`,
221
+ `mkdir -p ${dockerEnv.BLOCKLET_LOG_DIR} || :`,
222
+ `mkdir -p ${dockerEnv.BLOCKLET_CACHE_DIR} || :`,
223
+ 'mkdir -p /.local || :',
224
+ `chown ${uid}:${gid} /.local`,
225
+ `chown ${uid}:${gid} ${baseDir}`,
226
+ `chown ${uid}:${gid} /`,
227
+ ],
228
+ userCommands: [`cd ${dockerEnv.BLOCKLET_APP_DIR}`, installScript, runScript],
229
+ uid,
230
+ gid,
231
+ shell: dockerInfo.shell,
232
+ baseDir,
233
+ scriptDir: dockerTempDir,
234
+ });
235
+
236
+ nextOptions.script = `
175
237
  docker rm -fv ${name} > /dev/null 2>&1 || true && \
176
238
  ${awaitRestart} \
177
239
  docker run --name ${name} \
178
- ${mount} \
179
- ${diskSize} \
240
+ ${volumes} \
241
+ ${dockerInfo.volumes || ''} \
242
+ -v ${dockerTempDir}:${path.join(baseDir, 'dockerTemp')}:rw \
243
+ ${diskSizeOption} \
180
244
  --cpus="${dockerEnv.BLOCKLET_DOCKER_CPUS}" \
181
245
  --memory="${dockerEnv.BLOCKLET_DOCKER_MEMORY}" \
182
246
  --env-file ${dockerEnvFile} \
183
- ${networkHost} \
184
- ${DOCKER_IMAGE_NAME} \
185
- sh -c '
186
- mkdir -p /.local || : && \
187
- chown ${uid}:${gid} /.local && \
188
- chown ${uid}:${gid} ${baseDir} && \
189
- chown ${uid}:${gid} / && \
190
- exec su-exec ${uid}:${gid} sh -c "
191
- cd ${dockerEnv.BLOCKLET_APP_DIR} && \
192
- ${installScript} \
193
- ${runScript}
194
- "
195
- '`;
196
-
197
- options.env = {
198
- USING_DOCKER: 'true',
199
- };
247
+ ${dockerInfo.network} \
248
+ ${dockerInfo.image} ${dockerInfo.command || ''} \
249
+ ${dockerInfo.runBaseScript ? `sh ${path.join(baseDir, 'dockerTemp', 'root_script.sh')}` : ''}
250
+ `;
200
251
 
201
- options.exec_mode = 'fork_mode';
252
+ nextOptions.exec_mode = 'fork_mode';
202
253
 
203
- return options;
254
+ return nextOptions;
204
255
  }
205
256
 
206
257
  module.exports = parseDockerOptionsFromPm2;
@@ -282,7 +282,7 @@ const setupAppOwner = async ({ node, sessionId, justCreate = false, context }) =
282
282
  // create app owner passport
283
283
  const passport = createUserPassport(vc, { role });
284
284
 
285
- // add app owner to database
285
+ // add app owner to database, app owner can't federated account, so it can't have sourceAppPid field
286
286
  const doc = await node.loginUser({
287
287
  teamDid: appDid,
288
288
  user: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.32",
6
+ "version": "1.16.33-beta-20241001-015316-119b726d",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,19 +19,19 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
- "@abtnode/analytics": "1.16.32",
23
- "@abtnode/auth": "1.16.32",
24
- "@abtnode/certificate-manager": "1.16.32",
25
- "@abtnode/constant": "1.16.32",
26
- "@abtnode/cron": "1.16.32",
27
- "@abtnode/logger": "1.16.32",
28
- "@abtnode/models": "1.16.32",
29
- "@abtnode/queue": "1.16.32",
30
- "@abtnode/rbac": "1.16.32",
31
- "@abtnode/router-provider": "1.16.32",
32
- "@abtnode/static-server": "1.16.32",
33
- "@abtnode/timemachine": "1.16.32",
34
- "@abtnode/util": "1.16.32",
22
+ "@abtnode/analytics": "1.16.33-beta-20241001-015316-119b726d",
23
+ "@abtnode/auth": "1.16.33-beta-20241001-015316-119b726d",
24
+ "@abtnode/certificate-manager": "1.16.33-beta-20241001-015316-119b726d",
25
+ "@abtnode/constant": "1.16.33-beta-20241001-015316-119b726d",
26
+ "@abtnode/cron": "1.16.33-beta-20241001-015316-119b726d",
27
+ "@abtnode/logger": "1.16.33-beta-20241001-015316-119b726d",
28
+ "@abtnode/models": "1.16.33-beta-20241001-015316-119b726d",
29
+ "@abtnode/queue": "1.16.33-beta-20241001-015316-119b726d",
30
+ "@abtnode/rbac": "1.16.33-beta-20241001-015316-119b726d",
31
+ "@abtnode/router-provider": "1.16.33-beta-20241001-015316-119b726d",
32
+ "@abtnode/static-server": "1.16.33-beta-20241001-015316-119b726d",
33
+ "@abtnode/timemachine": "1.16.33-beta-20241001-015316-119b726d",
34
+ "@abtnode/util": "1.16.33-beta-20241001-015316-119b726d",
35
35
  "@arcblock/did": "1.18.135",
36
36
  "@arcblock/did-auth": "1.18.135",
37
37
  "@arcblock/did-ext": "^1.18.135",
@@ -42,13 +42,13 @@
42
42
  "@arcblock/pm2-events": "^0.0.5",
43
43
  "@arcblock/validator": "^1.18.135",
44
44
  "@arcblock/vc": "1.18.135",
45
- "@blocklet/constant": "1.16.32",
46
- "@blocklet/env": "1.16.32",
47
- "@blocklet/meta": "1.16.32",
48
- "@blocklet/resolver": "1.16.32",
49
- "@blocklet/sdk": "1.16.32",
50
- "@blocklet/store": "1.16.32",
51
- "@did-space/client": "^0.5.44",
45
+ "@blocklet/constant": "1.16.33-beta-20241001-015316-119b726d",
46
+ "@blocklet/env": "1.16.33-beta-20241001-015316-119b726d",
47
+ "@blocklet/meta": "1.16.33-beta-20241001-015316-119b726d",
48
+ "@blocklet/resolver": "1.16.33-beta-20241001-015316-119b726d",
49
+ "@blocklet/sdk": "1.16.33-beta-20241001-015316-119b726d",
50
+ "@blocklet/store": "1.16.33-beta-20241001-015316-119b726d",
51
+ "@did-space/client": "^0.5.47",
52
52
  "@fidm/x509": "^1.2.1",
53
53
  "@ocap/mcrypto": "1.18.135",
54
54
  "@ocap/util": "1.18.135",
@@ -105,5 +105,5 @@
105
105
  "jest": "^29.7.0",
106
106
  "unzipper": "^0.10.11"
107
107
  },
108
- "gitHead": "6b6c0abc72e892179a61d6daf7b17e884526db0b"
108
+ "gitHead": "5cac4c3579c14ebae18810b1f27ecbc63603088d"
109
109
  }