diff --git a/package.json b/package.json index e7cfec839b..f21339f20c 100644 --- a/package.json +++ b/package.json @@ -194,15 +194,16 @@ "execa": "^2.0.4", "form-data": "^2.5.1", "hat": "0.0.3", - "ipfsd-ctl": "~0.45.0", "interface-ipfs-core": "^0.111.1", + "ipfsd-ctl": "~0.45.0", "libp2p-websocket-star": "~0.10.2", "ncp": "^2.0.0", "p-event": "^4.1.0", "qs": "^6.5.2", "rimraf": "^3.0.0", "sinon": "^7.4.2", - "stream-to-promise": "^2.2.0" + "stream-to-promise": "^2.2.0", + "temp-write": "^4.0.0" }, "optionalDependencies": { "prom-client": "^11.5.3", diff --git a/src/cli/commands/daemon.js b/src/cli/commands/daemon.js index f3f61f80e6..938fa3b005 100644 --- a/src/cli/commands/daemon.js +++ b/src/cli/commands/daemon.js @@ -1,8 +1,10 @@ 'use strict' const os = require('os') +const fs = require('fs') const toUri = require('multiaddr-to-uri') const { ipfsPathHelp } = require('../utils') +const debug = require('debug')('ipfs:cli:daemon') module.exports = { command: 'daemon', @@ -12,6 +14,15 @@ module.exports = { builder (yargs) { return yargs .epilog(ipfsPathHelp) + .option('init', { + type: 'boolean', + default: false, + desc: 'Initialize ipfs with default settings if not already initialized.' + }) + .option('init-config', { + type: 'string', + desc: 'Path to existing configuration file to be loaded during --init.' + }) .option('enable-sharding-experiment', { type: 'boolean', default: false @@ -46,9 +57,23 @@ module.exports = { const repoPath = argv.getRepoPath() + let config = {} + // read and parse config file + if (argv.initConfig) { + try { + const raw = fs.readFileSync(argv.initConfig) + config = JSON.parse(raw) + } catch (error) { + debug(error) + throw new Error('Default config couldn\'t be found or content isn\'t valid JSON.') + } + } + // Required inline to reduce startup time const Daemon = require('../../cli/daemon') const daemon = new Daemon({ + init: argv.init, + config, silent: argv.silent, repo: process.env.IPFS_PATH, offline: argv.offline, diff --git a/src/cli/commands/init.js b/src/cli/commands/init.js index 78095d25e4..74e62ac344 100644 --- a/src/cli/commands/init.js +++ b/src/cli/commands/init.js @@ -1,15 +1,17 @@ 'use strict' +const fs = require('fs') +const debug = require('debug')('ipfs:cli:init') const { ipfsPathHelp } = require('../utils') module.exports = { - command: 'init [config] [options]', + command: 'init [default-config] [options]', describe: 'Initialize a local IPFS node', builder (yargs) { return yargs .epilog(ipfsPathHelp) - .positional('config', { - describe: 'Node config, this should JSON and will be merged with the default config. Check https://github.com/ipfs/js-ipfs#optionsconfig', + .positional('default-config', { + describe: 'Initialize with the given configuration. Path to the config file. Check https://github.com/ipfs/js-ipfs#optionsconfig', type: 'string' }) .option('bits', { @@ -34,6 +36,18 @@ module.exports = { argv.resolve((async () => { const path = argv.getRepoPath() + let config = {} + // read and parse config file + if (argv.defaultConfig) { + try { + const raw = fs.readFileSync(argv.defaultConfig) + config = JSON.parse(raw) + } catch (error) { + debug(error) + throw new Error('Default config couldn\'t be found or content isn\'t valid JSON.') + } + } + argv.print(`initializing ipfs node at ${path}`) // Required inline to reduce startup time @@ -44,7 +58,7 @@ module.exports = { repo: new Repo(path), init: false, start: false, - config: argv.config || {} + config }) try { diff --git a/src/cli/daemon.js b/src/cli/daemon.js index 7181e8c1e7..10a757e482 100644 --- a/src/cli/daemon.js +++ b/src/cli/daemon.js @@ -55,7 +55,7 @@ class Daemon { } // start the daemon - const ipfsOpts = Object.assign({ init: false }, this._options, { start: true, libp2p }) + const ipfsOpts = Object.assign({ }, this._options, { start: true, libp2p }) const ipfs = new IPFS(ipfsOpts) await new Promise((resolve, reject) => { diff --git a/test/cli/daemon.js b/test/cli/daemon.js index eb05d981aa..840aedeecb 100644 --- a/test/cli/daemon.js +++ b/test/cli/daemon.js @@ -9,10 +9,29 @@ const os = require('os') const path = require('path') const hat = require('hat') const fs = require('fs') +const tempWrite = require('temp-write') const pkg = require('../../package.json') const skipOnWindows = isWindows() ? it.skip : it +const daemonReady = (daemon, fn) => { + let r = null + const p = new Promise((resolve, reject) => { + daemon.stdout.on('data', async (data) => { + if (data.toString().includes('Daemon is ready')) { + try { + r = await fn() + } catch (err) { + reject(err) + } + daemon.kill() + } + }) + daemon.stderr.on('data', () => reject(new Error('Daemon didnt start'))) + daemon.then(() => resolve(r)).catch(reject) + }) + return p +} const checkLock = (repo) => { // skip on windows // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530 @@ -265,4 +284,34 @@ describe('daemon', () => { expect(err.stdout).to.include(`Node.js version: ${process.versions.node}`) } }) + + it('should init', async function () { + this.timeout(100 * 1000) + const daemon = ipfs('daemon --init') + let stdout = '' + + daemon.stdout.on('data', (data) => { + stdout += data.toString('utf8') + + if (stdout.includes('Daemon is ready')) { + daemon.kill() + } + }) + + try { + await daemon + throw new Error('Did not kill process') + } catch (err) { + expect(err.killed).to.be.true() + } + }) + + it('should init with custom config', async function () { + this.timeout(100 * 1000) + const configPath = tempWrite.sync('{"Addresses": {"API": "/ip4/127.0.0.1/tcp/9999"}}', 'config.json') + const daemon = ipfs(`daemon --init --init-config ${configPath}`) + + const r = await daemonReady(daemon, () => ipfs('config \'Addresses.API\'')) + expect(r).to.be.eq('/ip4/127.0.0.1/tcp/9999') + }) }) diff --git a/test/cli/init.js b/test/cli/init.js index ab9082a731..e317c6b145 100644 --- a/test/cli/init.js +++ b/test/cli/init.js @@ -8,9 +8,10 @@ const clean = require('../utils/clean') const hat = require('hat') const ipfsExec = require('../utils/ipfs-exec') const os = require('os') +const tempWrite = require('temp-write') describe('init', function () { - this.timeout(40 * 1000) + this.timeout(100 * 1000) let repoPath let ipfs @@ -33,8 +34,6 @@ describe('init', function () { afterEach(() => clean(repoPath)) it('basic', function () { - this.timeout(40 * 1000) - return ipfs('init').then((out) => { expect(repoDirSync('blocks')).to.have.length.above(2) expect(repoExistsSync('config')).to.equal(true) @@ -48,8 +47,6 @@ describe('init', function () { }) it('bits', function () { - this.timeout(40 * 1000) - return ipfs('init --bits 1024').then(() => { expect(repoDirSync('blocks')).to.have.length.above(2) expect(repoExistsSync('config')).to.equal(true) @@ -58,8 +55,6 @@ describe('init', function () { }) it('empty', function () { - this.timeout(40 * 1000) - return ipfs('init --bits 1024 --empty-repo true').then(() => { expect(repoDirSync('blocks')).to.have.length(2) expect(repoExistsSync('config')).to.equal(true) @@ -68,11 +63,18 @@ describe('init', function () { }) it('should present ipfs path help when option help is received', function (done) { - this.timeout(100 * 1000) - ipfs('init --help').then((res) => { expect(res).to.have.string('export IPFS_PATH=/path/to/ipfsrepo') done() }) }) + + it('default config argument', () => { + const configPath = tempWrite.sync('{"Addresses": {"API": "/ip4/127.0.0.1/tcp/9999"}}', 'config.json') + return ipfs(`init ${configPath}`).then((res) => { + const configRaw = fs.readFileSync(path.join(repoPath, 'config')).toString() + const config = JSON.parse(configRaw) + expect(config.Addresses.API).to.be.eq('/ip4/127.0.0.1/tcp/99999') + }) + }) })