├── .dockerignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── Dockerfile
├── README.md
├── docker-compose.yml
├── index.js
├── lib
├── config.js
├── datWrapper.js
├── errorPlugin.js
├── initServer.js
├── routes.js
└── swarmManager.js
├── package.json
├── run.js
├── test
├── fixtures
│ ├── dummy.txt
│ └── swarmMan
│ │ ├── clone
│ │ └── .gitkeep
│ │ ├── d1
│ │ └── a.txt
│ │ └── d2
│ │ └── b.txt
├── helpers
│ ├── index.js
│ └── testContext.js
├── test-datShare.js
└── test-swarmManager.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Example user template template
3 | ### Example user template
4 |
5 | # IntelliJ project files
6 | .idea
7 | *.iml
8 | out
9 | gen### JetBrains template
10 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
11 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
12 |
13 | # User-specific stuff
14 | .idea/**/workspace.xml
15 | .idea/**/tasks.xml
16 | .idea/**/dictionaries
17 | .idea/**/shelf
18 |
19 | # Sensitive or high-churn files
20 | .idea/**/dataSources/
21 | .idea/**/dataSources.ids
22 | .idea/**/dataSources.local.xml
23 | .idea/**/sqlDataSources.xml
24 | .idea/**/dynamic.xml
25 | .idea/**/uiDesigner.xml
26 | .idea/**/dbnavigator.xml
27 |
28 | # Gradle
29 | .idea/**/gradle.xml
30 | .idea/**/libraries
31 |
32 | # CMake
33 | cmake-build-debug/
34 | cmake-build-release/
35 |
36 | # Mongo Explorer plugin
37 | .idea/**/mongoSettings.xml
38 |
39 | # File-based project format
40 | *.iws
41 |
42 | # IntelliJ
43 | out/
44 |
45 | # mpeltonen/sbt-idea plugin
46 | .idea_modules/
47 |
48 | # JIRA plugin
49 | atlassian-ide-plugin.xml
50 |
51 | # Cursive Clojure plugin
52 | .idea/replstate.xml
53 |
54 | # Crashlytics plugin (for Android Studio and IntelliJ)
55 | com_crashlytics_export_strings.xml
56 | crashlytics.properties
57 | crashlytics-build.properties
58 | fabric.properties
59 |
60 | # Editor-based Rest Client
61 | .idea/httpRequests
62 | ### Node template
63 | # Logs
64 | logs
65 | *.log
66 | npm-debug.log*
67 | yarn-debug.log*
68 | yarn-error.log*
69 |
70 | # Runtime data
71 | pids
72 | *.pid
73 | *.seed
74 | *.pid.lock
75 |
76 | # Directory for instrumented libs generated by jscoverage/JSCover
77 | lib-cov
78 |
79 | # Coverage directory used by tools like istanbul
80 | coverage
81 |
82 | # nyc test coverage
83 | .nyc_output
84 |
85 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
86 | .grunt
87 |
88 | # Bower dependency directory (https://bower.io/)
89 | bower_components
90 |
91 | # node-waf configuration
92 | .lock-wscript
93 |
94 | # Compiled binary addons (https://nodejs.org/api/addons.html)
95 | build/Release
96 |
97 | # Dependency directories
98 | node_modules/
99 | jspm_packages/
100 |
101 | # TypeScript v1 declaration files
102 | typings/
103 |
104 | # Optional npm cache directory
105 | .npm
106 |
107 | # Optional eslint cache
108 | .eslintcache
109 |
110 | # Optional REPL history
111 | .node_repl_history
112 |
113 | # Output of 'npm pack'
114 | *.tgz
115 |
116 | # Yarn Integrity file
117 | .yarn-integrity
118 |
119 | # dotenv environment variables file
120 | .env
121 |
122 | # next.js build output
123 | .next
124 |
125 | data/
126 | .eslintrc.json
127 | .gitignore
128 | .prettierrc
129 | datTracker.db
130 | docker-compose.yml
131 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["plugin:prettier/recommended"],
4 | "parserOptions": {
5 | "ecmaVersion": 9
6 | },
7 | "env": {
8 | "es6": true,
9 | "node": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5 |
6 | # User-specific stuff
7 | .idea/**/workspace.xml
8 | .idea/**/tasks.xml
9 | .idea/**/dictionaries
10 | .idea/**/shelf
11 |
12 | # Sensitive or high-churn files
13 | .idea/**/dataSources/
14 | .idea/**/dataSources.ids
15 | .idea/**/dataSources.local.xml
16 | .idea/**/sqlDataSources.xml
17 | .idea/**/dynamic.xml
18 | .idea/**/uiDesigner.xml
19 | .idea/**/dbnavigator.xml
20 |
21 | # Gradle
22 | .idea/**/gradle.xml
23 | .idea/**/libraries
24 |
25 | # CMake
26 | cmake-build-debug/
27 | cmake-build-release/
28 |
29 | # Mongo Explorer plugin
30 | .idea/**/mongoSettings.xml
31 |
32 | # File-based project format
33 | *.iws
34 |
35 | # IntelliJ
36 | out/
37 |
38 | # mpeltonen/sbt-idea plugin
39 | .idea_modules/
40 |
41 | # JIRA plugin
42 | atlassian-ide-plugin.xml
43 |
44 | # Cursive Clojure plugin
45 | .idea/replstate.xml
46 |
47 | # Crashlytics plugin (for Android Studio and IntelliJ)
48 | com_crashlytics_export_strings.xml
49 | crashlytics.properties
50 | crashlytics-build.properties
51 | fabric.properties
52 |
53 | # Editor-based Rest Client
54 | .idea/httpRequests
55 | ### Example user template template
56 | ### Example user template
57 |
58 | # IntelliJ project files
59 | .idea
60 | *.iml
61 | out
62 | gen### Node template
63 | # Logs
64 | logs
65 | *.log
66 | npm-debug.log*
67 | yarn-debug.log*
68 | yarn-error.log*
69 |
70 | # Runtime data
71 | pids
72 | *.pid
73 | *.seed
74 | *.pid.lock
75 |
76 | # Directory for instrumented libs generated by jscoverage/JSCover
77 | lib-cov
78 |
79 | # Coverage directory used by tools like istanbul
80 | coverage
81 |
82 | # nyc test coverage
83 | .nyc_output
84 |
85 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
86 | .grunt
87 |
88 | # Bower dependency directory (https://bower.io/)
89 | bower_components
90 |
91 | # node-waf configuration
92 | .lock-wscript
93 |
94 | # Compiled binary addons (https://nodejs.org/api/addons.html)
95 | build/Release
96 |
97 | # Dependency directories
98 | node_modules/
99 | jspm_packages/
100 |
101 | # TypeScript v1 declaration files
102 | typings/
103 |
104 | # Optional npm cache directory
105 | .npm
106 |
107 | # Optional eslint cache
108 | .eslintcache
109 |
110 | # Optional REPL history
111 | .node_repl_history
112 |
113 | # Output of 'npm pack'
114 | *.tgz
115 |
116 | # Yarn Integrity file
117 | .yarn-integrity
118 |
119 | # dotenv environment variables file
120 | .env
121 |
122 | # next.js build output
123 | .next
124 |
125 | redis/dump.rdb
126 | data
127 | dbDir
128 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": true,
4 | "tabWidth": 2,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:11.13.0
2 |
3 | VOLUME /data
4 |
5 | ENV ROOT_DIR /data
6 | ENV SWARM_PORT 3282
7 | ENV NODE_ENV production
8 | ENV DEBUG "SwarmManager"
9 |
10 | EXPOSE 3282
11 | EXPOSE 3000
12 |
13 | RUN mkdir -p /usr/src/app
14 | WORKDIR /usr/src/app
15 |
16 | COPY package.json /usr/src/app
17 | RUN yarn install
18 |
19 | COPY . /usr/src/app
20 |
21 | #RUN useradd -ms /bin/bash -u 1000 datuser
22 |
23 | USER node
24 |
25 | CMD node run.js -r $ROOT_DIR -s $SWARM_PORT
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | dat-share
2 | =======================
3 |
4 | Webrecorders dat integration backend.
5 |
6 | ### Installation
7 | To use this project you must first install its dependencies
8 |
9 | ```bash
10 | $ yarn install
11 | # or "npm install"
12 | ```
13 |
14 | ### Usage
15 | dat-share provides a cli to help you use this project.
16 |
17 | The commands available to you are displayed below
18 |
19 | ```bash
20 | $ ./run.js --help
21 | Usage: run [options]
22 |
23 | Options:
24 | -V, --version output the version number
25 | -p, --port [port] The port the api server is to bind to (default: 3000)
26 | -h, --host [host] The host address the server is listen on (default: "127.0.0.1")
27 | -s, --swarm-port [port] The port the swarm is to bind to (default: 3282)
28 | -r, --rootDir
The root directory that contains the contents to be shared via dat
29 | -l --log should logging be enabled for both the api server and swarm manager
30 | --help output usage information
31 | ```
32 |
33 | Some configuration of the server can be done via the environment variables listed below
34 | - `SWARM_API_HOST`: the host the api server will use (e.g. 127.0.0.1)
35 | - `SWARM_API_PORT`: the port the api server will listen on (e.g. 3000)
36 | - `SWARM_PORT`: the port the swarm will listen on (e.g. 3282)
37 | - `SWARM_ROOT`: the root directory that contains the contents to be shared via dat
38 | - `LOG`: should logging be enabled (exists **yes**, does not exists **no**)
39 | - `Debug=SwarmManager`: enables logging of the actions performed by the swarm manager only
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | dat-share:
5 | build: .
6 | image: webrecorder/dat-share
7 | ports:
8 | - "3282:3282"
9 | - "3000:3000"
10 | volumes:
11 | - ./data/storage:/data
12 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | exports.DatWrapper = require('./lib/datWrapper');
2 | exports.SwarmManager = require('./lib/swarmManager');
3 | exports.initServer = require('./lib/initServer');
4 |
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const program = require('commander');
3 | const chalk = require('chalk').default;
4 | const fs = require('fs-extra');
5 | const pkg = require('../package');
6 |
7 | /**
8 | * Prints a warning for invalid environment config values
9 | * @param {string} key
10 | * @param {string} value
11 | * @param {*} defaultValue
12 | */
13 | function invalidValue(key, value, defaultValue) {
14 | console.log(chalk.bold.red(`Invalid value for ${key}: ${value}`));
15 | console.log(chalk.bold.red(`Using default value: ${defaultValue}`));
16 | }
17 |
18 | function convertEnvInt(key, defaultValue) {
19 | const envValue = process.env[key];
20 | let value = defaultValue;
21 | if (envValue != null) {
22 | try {
23 | value = parseInt(envValue, 10);
24 | } catch (e) {
25 | invalidValue(key, envValue, defaultValue);
26 | }
27 | if (isNaN(value)) {
28 | invalidValue(key, envValue, defaultValue);
29 | value = defaultValue;
30 | }
31 | }
32 | return value;
33 | }
34 |
35 | function validateArgs(prog) {
36 | const rootDir = prog.rootDir;
37 | let isError = false;
38 | if (!rootDir) {
39 | console.log(
40 | chalk.bold.red('The rootDir argument was not supplied and is required')
41 | );
42 | isError = true;
43 | } else if (!fs.pathExistsSync(rootDir)) {
44 | console.log(
45 | chalk.bold.red(
46 | `The directory specified by the rootDir argument (${rootDir}) does not exist`
47 | )
48 | );
49 | isError = true;
50 | } else if (!fs.statSync(rootDir).isDirectory()) {
51 | console.log(
52 | chalk.bold.red(
53 | `The value for the rootDir argument (${rootDir}) is not a directory`
54 | )
55 | );
56 | isError = true;
57 | }
58 |
59 | if (!isError && isNaN(prog.swarmPort)) {
60 | isError = true;
61 | console.log(
62 | chalk.bold.red('The value for the swarmPort argument is not a number')
63 | );
64 | }
65 |
66 | if (!isError && isNaN(prog.port)) {
67 | isError = true;
68 | console.log(
69 | chalk.bold.red('The value for the port argument is not a number')
70 | );
71 | }
72 |
73 | if (isError) {
74 | program.help(chalk.bold.red);
75 | }
76 | }
77 |
78 | /**
79 | * Returns the default port the api server will listen on.
80 | * If the env variable SWARM_MAN_API_HOST is set returns it's value
81 | * otherwise returns 127.0.0.1
82 | * @return {string}
83 | */
84 | function getDefaultHost() {
85 | if (process.env.SWARM_API_HOST != null) {
86 | return process.env.SWARM_API_HOST;
87 | }
88 | return process.env.NODE_ENV === 'production' ? '0.0.0.0' : '127.0.0.1';
89 | }
90 |
91 | program
92 | .version(pkg.version)
93 | .option(
94 | '-p, --port [port]',
95 | 'The port the api server is to bind to',
96 | convertEnvInt('SWARM_API_PORT', 3000)
97 | )
98 | .option(
99 | '-h, --host [host]',
100 | 'The host address the server is listen on',
101 | getDefaultHost()
102 | )
103 | .option(
104 | '-s, --swarm-port [port]',
105 | 'The port the swarm is to bind to',
106 | convertEnvInt('SWARM_PORT', 3282)
107 | )
108 | .option(
109 | '-r, --rootDir ',
110 | 'The root directory that contains the contents to be shared via dat',
111 | process.env.SWARM_ROOT
112 | )
113 | .option(
114 | '-l --log',
115 | 'should logging be enabled for both the api server and swarm manager'
116 | )
117 | .parse(process.argv);
118 |
119 | if (process.env.NODE_ENV !== 'test') validateArgs(program);
120 |
121 | if (program.log) {
122 | process.env.DEBUG = 'SwarmManager';
123 | }
124 |
125 | module.exports = {
126 | host: program.host,
127 | port: program.port,
128 | log: program.log,
129 | swarmManager: {
130 | port: program.swarmPort,
131 | rootDir: program.rootDir,
132 | },
133 | fastifyOpts: {
134 | trustProxy: true,
135 | logger: program.log,
136 | },
137 | };
138 |
--------------------------------------------------------------------------------
/lib/datWrapper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 | const fs = require('fs-extra');
4 | const DatStorage = require('dat-storage');
5 | const hyperdrive = require('hyperdrive');
6 | const importFiles = require('dat-node/lib/import-files');
7 | const S3HybridStorage = require('dat-s3-hybrid-storage');
8 |
9 | /**
10 | * @desc Wrapper around both dat-node and dat-json that provides ease of use
11 | * for both
12 | */
13 | class DatWrapper {
14 | /**
15 | *
16 | * @param {Hyperdrive} drive
17 | * @param {function(): Promise} importer
18 | */
19 | constructor(drive, importer) {
20 | /**
21 | * @desc The wrapped hyperdrive
22 | * @type {Hyperdrive}
23 | */
24 | this.drive = drive;
25 |
26 | /**
27 | * @desc Source url or File dir for importing files, or null for read-only
28 | * @type {function(): Promise}
29 | */
30 | this.importFiles = importer;
31 |
32 | /**
33 | * @desc are we sharing this dat (joined the swarm)
34 | * @type {boolean}
35 | * @private
36 | */
37 | this._sharing = false;
38 | }
39 |
40 | /**
41 | * @desc Create a Dat instance, archive storage, and ready the archive.
42 | * @param {string} dir - Path to the directory of the dat
43 | * @param {string} fullDir - Full path to directory of the dat
44 | * @param {Object} [options = {}] - Dat-node options and any hyperdrive init options.
45 | * @param {string|Buffer} [options.key] - Hyperdrive key
46 | * @param {Boolean} [options.latest = true] - Create storage if it does not exit.
47 | * @param {string} [options.dir]
48 | * @return {Promise} The new Dat instance
49 | */
50 | static async create(dir, fullDir, options = {}) {
51 | let storage = null;
52 | let drive = null;
53 | let importer = null;
54 |
55 | const opts = { latest: true, dir: fullDir, ...options };
56 |
57 | // S3 SUPPORT!
58 | if (process.env.S3_ROOT) {
59 | await fs.ensureDir(fullDir);
60 |
61 | const srcUrl = process.env.S3_ROOT + dir;
62 | const s3_storage = new S3HybridStorage(srcUrl, fullDir);
63 |
64 | storage = s3_storage.storage();
65 |
66 | drive = hyperdrive(storage, opts);
67 |
68 | importer = function() {
69 | return s3_storage.importer().importFiles(drive);
70 | };
71 | } else {
72 | storage = DatStorage(fullDir);
73 |
74 | drive = hyperdrive(storage, opts);
75 |
76 | importer = function() {
77 | return new Promise(resolve => {
78 | importFiles(drive, fullDir, { indexing: true }, () => {
79 | resolve();
80 | });
81 | });
82 | };
83 | }
84 |
85 | const hasDat = fs.existsSync(path.join(fullDir, '.dat'));
86 |
87 | await new Promise((resolve, reject) => {
88 | drive.on('error', reject);
89 | drive.ready(() => {
90 | drive.removeListener('error', reject);
91 | drive.resumed = !!(hasDat || (drive.metadata.has(0) && drive.version));
92 | resolve();
93 | });
94 | });
95 |
96 | return new DatWrapper(drive, importer);
97 | }
98 |
99 | /**
100 | * @desc Are we sharing this dat (joined the swarm)
101 | * @return {boolean} - True if this dat has joined the swarm otherwise false
102 | */
103 | sharing() {
104 | return this._sharing;
105 | }
106 |
107 | /**
108 | * @desc Stat a path in the archive
109 | * @param {string} archivePath - The path in the archive to stat
110 | * @return {Promise