Skip to content

Commit 20d17e4

Browse files
committed
First pass at daemon
1 parent 89c4896 commit 20d17e4

File tree

9 files changed

+357
-0
lines changed

9 files changed

+357
-0
lines changed

bin/mount.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const p = require('path')
2+
const request = require('request-promise-native')
3+
const chalk = require('chalk')
4+
5+
const { loadMetadata, createMetadata } = require('../lib/metadata')
6+
7+
exports.command = 'mount <mnt> [key]'
8+
exports.desc = 'Mount a hyperdrive at the specified mountpoint.'
9+
exports.builder = {
10+
sparse: {
11+
description: 'Create a sparse content feed.',
12+
type: 'boolean',
13+
default: true
14+
},
15+
sparseMetadata: {
16+
description: 'Create a sparse metadata feed.',
17+
type: 'boolean',
18+
default: true
19+
}
20+
}
21+
exports.handler = async function (argv) {
22+
try {
23+
let metadata = await loadMetadata()
24+
let body = {
25+
sparse: argv.sparse,
26+
sparseMetadata: argv.sparseMetadata,
27+
key: argv.key,
28+
mnt: p.resolve(argv.mnt)
29+
}
30+
let rsp = await request(`${metadata.endpoint}/mount`, {
31+
method: 'POST',
32+
json: true,
33+
auth: {
34+
bearer: metadata.token
35+
},
36+
body,
37+
resolveWithFullResponse: true,
38+
})
39+
if (rsp.statusCode === 201) {
40+
let { key, mnt } = rsp.body
41+
console.log(chalk.green(`Mounted ${key} at ${mnt}`))
42+
} else {
43+
console.error(chalk.red(`Could not mount hyperdrive: ${rsp.body}`))
44+
}
45+
} catch (err) {
46+
console.error(chalk.red(`Could not mount hyperdrive: ${err}`))
47+
}
48+
}
49+

bin/start.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const p = require('path')
2+
const { URL } = require('url')
3+
const request = require('request-promise-native')
4+
const chalk = require('chalk')
5+
const { Monitor } = require('forever-monitor')
6+
const forever = require('forever')
7+
8+
const { loadMetadata, createMetadata } = require('../lib/metadata')
9+
10+
exports.command = 'start'
11+
exports.desc = 'Start the Hypermount daemon.'
12+
exports.builder = {
13+
port: {
14+
description: 'The HTTP port that the daemon will bind to.',
15+
type: 'number',
16+
default: 3101
17+
},
18+
replicationPort: {
19+
description: 'The port that the hypercore replicator will bind to.',
20+
type: 'number',
21+
default: 3102
22+
}
23+
}
24+
exports.handler = async function (argv) {
25+
let metadata = await loadMetadata()
26+
if (metadata) {
27+
try {
28+
await request.get(new URL('/status', metadata.endpoint).toString(), {
29+
auth: {
30+
bearer: metadata.token
31+
}
32+
})
33+
} catch (err) {
34+
await start(argv)
35+
}
36+
} else {
37+
await start(argv)
38+
}
39+
}
40+
41+
async function start (argv) {
42+
let endpoint = `http://localhost:${argv.port}`
43+
await createMetadata(endpoint)
44+
forever.startDaemon(p.join(__dirname, '..', 'index.js'), {
45+
max: 1,
46+
logFile: './hypermount.log',
47+
outFile: './hypermount.log',
48+
errFile: './hypermount.log',
49+
args: ['--replicationPort', argv.replicationPort, '--port', argv.port]
50+
})
51+
console.log(chalk.green(`Daemon started at ${endpoint}`))
52+
}

bin/status.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const request = require('request-promise-native')
2+
const chalk = require('chalk')
3+
4+
const { loadMetadata, createMetadata } = require('../lib/metadata')
5+
6+
exports.command = 'status'
7+
exports.desc = 'Get information about the hypermount daemon.'
8+
exports.handler = async function (argv) {
9+
try {
10+
let metadata = await loadMetadata()
11+
let rsp = await request.get(new URL('/status', metadata.endpoint).toString(), {
12+
auth: {
13+
bearer: metadata.token
14+
},
15+
resolveWithFullResponse: true
16+
})
17+
if (rsp.statusCode === 200) {
18+
console.log(chalk.green('The daemon is up and running!'))
19+
} else {
20+
console.log(chalk.orange('Cannot get the deamon\'s status. Did you start it?'))
21+
}
22+
} catch (err) {
23+
console.error(chalk.red(`Could not get server status: ${err}`))
24+
}
25+
}
26+

bin/stop.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
exports.command = 'stop'
2+
exports.desc = 'Stop the Hypermount daemon.'
3+
exports.handler = function (argv) {
4+
console.log('stop called for args', argv)
5+
}
6+

bin/unmount.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const p = require('path')
2+
const request = require('request-promise-native')
3+
const chalk = require('chalk')
4+
5+
const { loadMetadata, createMetadata } = require('../lib/metadata')
6+
7+
exports.command = 'unmount <mnt>'
8+
exports.desc = 'Unmount the hyperdrive that was mounted at the specified mountpoint.'
9+
exports.handler = async function (argv) {
10+
try {
11+
let metadata = await loadMetadata()
12+
let body = {
13+
mnt: p.resolve(argv.mnt)
14+
}
15+
let rsp = await request(`${metadata.endpoint}/unmount`, {
16+
method: 'POST',
17+
json: true,
18+
auth: {
19+
bearer: metadata.token
20+
},
21+
body,
22+
resolveWithFullResponse: true,
23+
})
24+
if (rsp.statusCode === 200) {
25+
console.log(chalk.green(`Unmounted hyperdrive at ${argv.mnt}`))
26+
} else {
27+
console.error(chalk.red(`Could not unmount hyperdrive: ${rsp.body}`))
28+
}
29+
} catch (err) {
30+
console.error(chalk.red(`Could not unmount hyperdrive: ${err}`))
31+
}
32+
}

cli.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env node
2+
3+
const datEncoding = require('dat-encoding')
4+
const forever = require('forever-monitor')
5+
const yargs = require('yargs')
6+
7+
yargs.commandDir('bin')
8+
.demandCommand()
9+
.help()
10+
.argv

index.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const datEncoding = require('dat-encoding')
2+
const corestore = require('corestore')
3+
const hyperdrive = require('hyperdrive')
4+
const hyperfuse = require('hyperdrive-fuse')
5+
const express = require('express')
6+
const level = require('level')
7+
const argv = require('yargs').argv
8+
9+
const { loadMetadata } = require('./lib/metadata')
10+
11+
class Hypermount {
12+
constructor (store) {
13+
this.store = store
14+
}
15+
16+
mount (key, mnt, opts) {
17+
if (typeof opts === 'function') return this.mount(key, mnt, null, opts)
18+
opts = opts || {}
19+
20+
const factory = (key, coreOpts) => {
21+
coreOpts.seed = (opts.seed !== undefined) ? opts.seed : true
22+
return this.store.get(key, coreOpts)
23+
}
24+
const drive = hyperdrive(factory, key, {
25+
...opts,
26+
factory: true,
27+
sparse: (opts.sparse !== undefined) ? opts.sparse : true,
28+
sparseMetadata: (opts.sparseMetadata !== undefined) ? opts.sparseMetadata : true
29+
})
30+
31+
return hyperfuse.mount(drive, mnt)
32+
}
33+
34+
unmount (mnt) {
35+
return hyperfuse.unmount(mnt)
36+
}
37+
38+
close () {
39+
return this.store.close()
40+
}
41+
}
42+
43+
async function start () {
44+
const metadata = await loadMetadata()
45+
const store = corestore(argv.storage || './storage', {
46+
network: {
47+
port: argv.replicationPort || 3006
48+
}
49+
})
50+
const hypermount = new Hypermount(store)
51+
const app = express()
52+
53+
app.use(express.json())
54+
app.use((req, res, next) => {
55+
if (!req.headers.authorization) return res.sendStatus(403)
56+
if (!req.headers.authorization === `Bearer ${metadata.token}`) return res.sendStatus(403)
57+
return next()
58+
})
59+
60+
app.post('/mount', async (req, res) => {
61+
try {
62+
console.log('req.body:', req.body)
63+
let { key, mnt } = await hypermount.mount(req.body.key, req.body.mnt, req.body)
64+
return res.status(201).json({ key, mnt })
65+
} catch (err) {
66+
console.error('Mount error:', err)
67+
return res.sendStatus(500)
68+
}
69+
})
70+
app.post('/unmount', async (req, res) => {
71+
try {
72+
await hypermount.unmount(req.body.mnt)
73+
return res.sendStatus(200)
74+
} catch (err) {
75+
console.error('Unmount error:', err)
76+
return res.sendStatus(500)
77+
}
78+
})
79+
app.post('/close', async (req, res) => {
80+
try {
81+
await store.close()
82+
server.close()
83+
return res.sendStatus(200)
84+
} catch (err) {
85+
console.error('Close error:', err)
86+
return res.sendStatus(500)
87+
}
88+
})
89+
app.get('/status', async (req, res) => {
90+
return res.sendStatus(200)
91+
})
92+
93+
await store.ready()
94+
var server = app.listen(argv.port || 3005)
95+
}
96+
97+
if (require.main === module) {
98+
start()
99+
}

lib/metadata.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const p = require('path')
2+
const os = require('os')
3+
const fs = require('fs-extra')
4+
5+
const sodium = require('sodium-universal')
6+
7+
const METADATA_FILE_PATH = p.join(os.homedir(), '.hypermount')
8+
9+
async function loadMetadata () {
10+
try {
11+
let contents = await fs.readFile(METADATA_FILE_PATH)
12+
if (contents) contents = JSON.parse(contents)
13+
return contents
14+
} catch (err) {
15+
return null
16+
}
17+
}
18+
19+
async function createMetadata (endpoint) {
20+
const rnd = Buffer.allocUnsafe(64)
21+
sodium.randombytes_buf(rnd)
22+
return fs.writeFile(METADATA_FILE_PATH, JSON.stringify({
23+
token: rnd.toString('hex'),
24+
endpoint
25+
}))
26+
}
27+
28+
async function deleteMetadata () {
29+
return fs.unlink(METADATA_FILE_PATH)
30+
}
31+
32+
module.exports = {
33+
createMetadata,
34+
loadMetadata,
35+
deleteMetadata
36+
}

package.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "hypermount",
3+
"version": "1.0.0",
4+
"description": "A FUSE-mountable distributed filesystem, built on Hyperdrive",
5+
"main": "index.js",
6+
"bin": {
7+
"hypermount": "./cli.js"
8+
},
9+
"scripts": {
10+
"test": "tape test/*.js"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/andrewosh/hypermount.git"
15+
},
16+
"keywords": [
17+
"hyperdrive",
18+
"fuse",
19+
"daemon"
20+
],
21+
"author": "Andrew Osheorff <[email protected]>",
22+
"license": "MIT",
23+
"bugs": {
24+
"url": "https://github.com/andrewosh/hypermount/issues"
25+
},
26+
"homepage": "https://github.com/andrewosh/hypermount#readme",
27+
"dependencies": {
28+
"chalk": "^2.4.2",
29+
"corestore": "^1.2.2",
30+
"dat-encoding": "^5.0.1",
31+
"express": "^4.16.4",
32+
"forever": "^0.15.3",
33+
"forever-monitor": "^1.7.1",
34+
"fs-extra": "^7.0.1",
35+
"hyperdrive": "git+https://github.com/andrewosh/hyperdrive.git#fuse-testing",
36+
"hyperdrive-fuse": "git+https://github.com/andrewosh/hyperdrive-fuse",
37+
"level": "^4.0.0",
38+
"request": "^2.88.0",
39+
"request-promise-native": "^1.0.7",
40+
"sodium-universal": "^2.0.0",
41+
"yargs": "^13.2.1"
42+
},
43+
"devDependencies": {
44+
"standard": "^12.0.1",
45+
"tape": "^4.10.1"
46+
}
47+
}

0 commit comments

Comments
 (0)