cryptpad-server 0.0.1
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/build/core.js +9183 -0
- package/build/core.worker.js +993 -0
- package/build/front.js +5815 -0
- package/build/front.worker.js +4619 -0
- package/build/http.js +5327 -0
- package/build/http.worker.js +4621 -0
- package/build/storage.cluster.js +8425 -0
- package/build/storage.js +12103 -0
- package/build/storage.worker.js +6544 -0
- package/index.js +169 -0
- package/package.json +68 -0
- package/readme.md +104 -0
package/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
const { fork } = require('child_process');
|
|
5
|
+
const Crypto = require('crypto');
|
|
6
|
+
const Path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const showHelp = () => {
|
|
9
|
+
console.log(`Usage ${process.argv[1]}:`);
|
|
10
|
+
console.log("\t--help, -h\tDisplay this help");
|
|
11
|
+
console.log("\t--type,-t\tSet the core type (if unset, starts every core)");
|
|
12
|
+
console.log("\t--index,-i\tSet the core node index (default: 0)");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const cliArgs = {};
|
|
18
|
+
let prev;
|
|
19
|
+
args.some(arg => {
|
|
20
|
+
if (arg === '--type' || arg === '-t') {
|
|
21
|
+
if (prev) { throw new Error('INVALID ARGS');}
|
|
22
|
+
prev = 'type';
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (arg === '--index' || arg === '-i') {
|
|
26
|
+
if (prev) { throw new Error('INVALID ARGS');}
|
|
27
|
+
prev = 'index';
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (arg === '--server' || arg === '-s') {
|
|
31
|
+
if (prev) { throw new Error('INVALID ARGS');}
|
|
32
|
+
prev = 'server';
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (arg === '--help' || arg === '-h') {
|
|
36
|
+
showHelp();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (/^--?/.test(arg)) {
|
|
40
|
+
prev = '';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!prev) { return; }
|
|
45
|
+
cliArgs[prev] = arg;
|
|
46
|
+
prev = '';
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const start = (serverConfig, infraConfig) => {
|
|
50
|
+
const Log = {
|
|
51
|
+
debug: console.debug,
|
|
52
|
+
error: console.error,
|
|
53
|
+
info: console.log,
|
|
54
|
+
verbose: console.info,
|
|
55
|
+
warn: console.warn
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let serverId;
|
|
59
|
+
const startNode = (type, index, forking, cb) => {
|
|
60
|
+
if (typeof (cb) !== 'function') { cb = () => { }; };
|
|
61
|
+
|
|
62
|
+
const nodeFile = './build/' + type + '.js';
|
|
63
|
+
const path = Path.join(__dirname, nodeFile);
|
|
64
|
+
const initConfig = {
|
|
65
|
+
myId: `${type}:${index}`,
|
|
66
|
+
index,
|
|
67
|
+
config: serverConfig,
|
|
68
|
+
infra: infraConfig
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//Log.info(`Starting: ${initConfig.myId}`);
|
|
72
|
+
if (forking) {
|
|
73
|
+
let nodeProcess = fork(path);
|
|
74
|
+
nodeProcess.send(initConfig);
|
|
75
|
+
nodeProcess.on('message', (message) => {
|
|
76
|
+
if (message.msg === 'READY') {
|
|
77
|
+
Log.info(`Started: ${type}:${message.index}`);
|
|
78
|
+
if (message.dev) {
|
|
79
|
+
Log.info('DEV mode enabled');
|
|
80
|
+
}
|
|
81
|
+
cb();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// FIXME
|
|
85
|
+
nodeProcess.on('error', (err) => {
|
|
86
|
+
Log.error('Child process stopped due to error.');
|
|
87
|
+
Log.error(err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
nodeProcess.on('exit', (err) => {
|
|
91
|
+
Log.error('Child process stopped due to error.');
|
|
92
|
+
Log.error(err);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
require(path).start(initConfig);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const coresReady = () => {
|
|
101
|
+
const promises = [];
|
|
102
|
+
infraConfig?.front?.forEach((data, index) => {
|
|
103
|
+
promises.push(new Promise(resolve => {
|
|
104
|
+
if (serverId && data.serverId !== serverId) { return resolve(); }
|
|
105
|
+
startNode('front', index, true, resolve);
|
|
106
|
+
}));
|
|
107
|
+
});
|
|
108
|
+
infraConfig?.storage?.forEach((data, index) => {
|
|
109
|
+
promises.push(new Promise(resolve => {
|
|
110
|
+
if (serverId && data.serverId !== serverId) { return resolve(); }
|
|
111
|
+
startNode('storage', index, true, resolve);
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
promises.push(new Promise(resolve => {
|
|
115
|
+
if (serverId && infraConfig?.public?.httpServerId !== serverId) { return resolve(); }
|
|
116
|
+
startNode('http', 0, true, resolve);
|
|
117
|
+
}));
|
|
118
|
+
Promise.all(promises).then(() => {
|
|
119
|
+
Log.info('CryptPad server ready');
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const startCores = () => {
|
|
124
|
+
if (!serverConfig?.private?.nodes_key) {
|
|
125
|
+
if (!serverConfig?.private) {
|
|
126
|
+
serverConfig.private = { };
|
|
127
|
+
}
|
|
128
|
+
serverConfig.private.nodes_key = Crypto.randomBytes(32).toString('base64');
|
|
129
|
+
}
|
|
130
|
+
const corePromises = infraConfig?.core.map((data, index) => new Promise((resolve, reject) => {
|
|
131
|
+
// hosted on another machine?
|
|
132
|
+
if (serverId && data.serverId !== serverId) { return resolve(); }
|
|
133
|
+
startNode('core', index, true, (err) => {
|
|
134
|
+
if (err) {
|
|
135
|
+
Log.error('START_CORE_ERROR', err);
|
|
136
|
+
return reject(err);
|
|
137
|
+
}
|
|
138
|
+
return resolve();
|
|
139
|
+
});
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
Promise.all(corePromises)
|
|
143
|
+
.then(() => { coresReady(); })
|
|
144
|
+
.catch((e) => { return Log.error('START_CORE_ERROR', e); });
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
// Start process
|
|
149
|
+
if (cliArgs.type) {
|
|
150
|
+
const type = cliArgs.type;
|
|
151
|
+
const index = Number(cliArgs.index || 0);
|
|
152
|
+
if (!serverConfig?.private?.nodes_key) {
|
|
153
|
+
throw Error('E_MISSINGKEY');
|
|
154
|
+
}
|
|
155
|
+
startNode(type, index, false, (err) => {
|
|
156
|
+
if (err) { return Log.error('START_NODE_ERROR', err); }
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
serverId = cliArgs.server;
|
|
160
|
+
startCores();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (require.main === module) {
|
|
165
|
+
const { config, infra } = require('./common/load-config');
|
|
166
|
+
start(config, infra);
|
|
167
|
+
} else {
|
|
168
|
+
module.exports = { start };
|
|
169
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cryptpad-server",
|
|
3
|
+
"description": "CryptPad Server",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"license": "AGPL-3.0+",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/cryptpad/scalable-server.git"
|
|
9
|
+
},
|
|
10
|
+
"funding": {
|
|
11
|
+
"type": "opencollective",
|
|
12
|
+
"url": "https://opencollective.com/cryptpad"
|
|
13
|
+
},
|
|
14
|
+
"main": "index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"build"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"body-parser": "^2.2.0",
|
|
20
|
+
"connect-gzip-static": "^4.2.1",
|
|
21
|
+
"cookie-parser": "^1.4.7",
|
|
22
|
+
"express": "~4.22.1",
|
|
23
|
+
"fs-extra": "^7.0.0",
|
|
24
|
+
"http-proxy-middleware": "^3.0.3",
|
|
25
|
+
"nthen": "0.1.8",
|
|
26
|
+
"otpauth": "^9.4.1",
|
|
27
|
+
"pull-stream": "^3.6.1",
|
|
28
|
+
"saferphore": "0.0.1",
|
|
29
|
+
"sodium-native": "^4.1.1",
|
|
30
|
+
"stream-to-pull-stream": "^1.7.2",
|
|
31
|
+
"tweetnacl": "^1.0.3",
|
|
32
|
+
"tweetnacl-util": "^0.15.1",
|
|
33
|
+
"ws": "^8.18.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@rollup/plugin-commonjs": "^28.0.0",
|
|
37
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
38
|
+
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
39
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
40
|
+
"@rollup/plugin-typescript": "^12.1.0",
|
|
41
|
+
"@types/node": "^24.1.0",
|
|
42
|
+
"chainpad-crypto": "^0.3.0",
|
|
43
|
+
"chainpad-netflux": "^1.3.0",
|
|
44
|
+
"eslint": "^10.0.2",
|
|
45
|
+
"globals": "^16.3.0",
|
|
46
|
+
"netflux-websocket": "^1.3.2",
|
|
47
|
+
"rollup": "^4.59.0",
|
|
48
|
+
"rollup-plugin-node-externals": "^8.1.2",
|
|
49
|
+
"tslib": "^2.7.0",
|
|
50
|
+
"typescript": "^5.6.2"
|
|
51
|
+
},
|
|
52
|
+
"overrides": {
|
|
53
|
+
"http-proxy": "npm:http-proxy-3@^1.23.2",
|
|
54
|
+
"serialize-javascript": "^7.0.4"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"lint": "eslint .",
|
|
58
|
+
"build": "rollup -c",
|
|
59
|
+
"build:front": "TYPE=front rollup -c",
|
|
60
|
+
"build:core": "TYPE=core rollup -c",
|
|
61
|
+
"build:storage": "TYPE=storage rollup -c",
|
|
62
|
+
"build:http": "TYPE=http rollup -c",
|
|
63
|
+
"tests": "node --test tests/",
|
|
64
|
+
"test": "node tests/runAll.js",
|
|
65
|
+
"start": "node index.js",
|
|
66
|
+
"dev": "DEV=1 node index.js"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!-- SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
|
2
|
+
|
|
3
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
-->
|
|
5
|
+
# Prototype: Scalable Server for CryptPad
|
|
6
|
+
|
|
7
|
+
This repository contains a distributed server prototype for CryptPad. This is
|
|
8
|
+
still in progress exploratory work.
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
### Taxonomy
|
|
13
|
+
|
|
14
|
+
The new server architecture is composed of three types of nodes:
|
|
15
|
+
|
|
16
|
+
- **Core:** these nodes take care of most of the computations and internal
|
|
17
|
+
communication handling. They are connected to the other type of servers.
|
|
18
|
+
- **Front:** these nodes catch the different queries from the
|
|
19
|
+
outside and forward them to the core nodes for processing.
|
|
20
|
+
- **Storage:** these nodes are responsible for accessing and serving a fraction
|
|
21
|
+
of the (encrypted) data for CryptPad. They are also doing light computation if
|
|
22
|
+
they can be done in place without too much pressure on them.
|
|
23
|
+
|
|
24
|
+
### Topology
|
|
25
|
+
|
|
26
|
+
The core nodes are connected to both ws and storage nodes, but the latter two
|
|
27
|
+
cannot communicate directly.
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
The configuration can be done using the `Config` argument, which will store the
|
|
32
|
+
graph topology of the nodes.
|
|
33
|
+
|
|
34
|
+
Using websockets for communication, this variable looks like this:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
let Config = {
|
|
38
|
+
infra: {
|
|
39
|
+
ws: [{
|
|
40
|
+
host: 'localhost',
|
|
41
|
+
port: 3010
|
|
42
|
+
}],
|
|
43
|
+
core: [{
|
|
44
|
+
host: 'localhost',
|
|
45
|
+
port: 3011
|
|
46
|
+
}, {
|
|
47
|
+
host: 'localhost',
|
|
48
|
+
port: 3012
|
|
49
|
+
}],
|
|
50
|
+
storage: [{
|
|
51
|
+
host: 'localhost',
|
|
52
|
+
port: 3014
|
|
53
|
+
}]
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The above configuration describes a network comprised of 4 nodes, having one
|
|
59
|
+
front node on port `3010`, 2 core servers on port `3011` and `3012`
|
|
60
|
+
and a storage node accessible via port `3014`.
|
|
61
|
+
|
|
62
|
+
In addition, launching a server requires setting the field `myId` with your
|
|
63
|
+
identifier, which will be of the form `type:id`. For instance, in the first core
|
|
64
|
+
node (the node listening on port `3011`), it would be `Config.myId = 'core:0'`.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
Before first use, you may want to install the dependencies with:
|
|
69
|
+
```bash
|
|
70
|
+
npm install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To spawn the server topology described in `ws-config.js`, run the start script:
|
|
74
|
+
```bash
|
|
75
|
+
npm run start
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Alternatively, you can the new servers manually. You first need to start the
|
|
79
|
+
`core` nodes with the following command.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
node core/index.js
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then you can start a `ws` and `storage` nodes in any order:
|
|
86
|
+
```bash
|
|
87
|
+
node storage/index.js
|
|
88
|
+
node front/index.js
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Tests
|
|
92
|
+
|
|
93
|
+
The directory [`tests`](tests/) contains some unit and integration tests scripts
|
|
94
|
+
and files.
|
|
95
|
+
|
|
96
|
+
- [`interface.test.js`](tests/interface.test.js): test the communication
|
|
97
|
+
interface by implementing a simple ping-pong protocol that computes the time it
|
|
98
|
+
takes to go back and forth in the network.
|
|
99
|
+
|
|
100
|
+
The aforementioned tests can be run with the following command (make sure to
|
|
101
|
+
have installed the dependencies with `npm install` beforehand):
|
|
102
|
+
```bash
|
|
103
|
+
npm run tests
|
|
104
|
+
```
|