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/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
+ ```