-
Notifications
You must be signed in to change notification settings - Fork 23
/
manager.js
155 lines (134 loc) · 5.01 KB
/
manager.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
const fs = require('fs').promises
const p = require('path')
const mkdirp = require('mkdirp')
const pm2 = require('pm2')
const { HyperdriveClient } = require('hyperdrive-daemon-client')
const constants = require('hyperdrive-daemon-client/lib/constants')
const HyperdriveDaemon = require('.')
async function start (opts = {}) {
const initialOpts = opts
opts = { ...constants, ...opts }
opts.endpoint = `localhost:${opts.port}`
if (opts.env && !opts.env.PATH) {
opts.env = { ...opts.env, PATH: process.env.PATH }
}
const client = new HyperdriveClient({ endpoint: opts.endpoint, storage: initialOpts.storage || opts.root })
const running = await new Promise((resolve, reject) => {
client.ready(err => {
if (!err) return resolve(true)
if (err.versionMismatch) return reject(new Error(`Daemon is already running with incompatible version: ${err.version}`))
return resolve(false)
})
})
if (running) return { opts }
await new Promise((resolve, reject) => {
const storagePath = p.join(opts.storage, 'storage')
mkdirp(storagePath, err => {
if (err) return reject(new Error(`Could not create storage directory: ${storagePath}`))
return resolve()
})
})
opts.memoryOnly = opts['memory-only']
opts.noAnnounce = opts['no-announce']
opts.noDebug = opts['no-debug']
opts.logLevel = opts['log-level']
/**
* HACK
* If 'pm2' detects a space in the 'script' path, it assumes the call is something like "python foo.py".
* When that's the case, it transforms the call into `/bin/bash -c python foo.py`.
* This creates a problem for some hyperdrive apps because they may have spaces in their install paths.
* The resulting call ends up being `${interpreter} /bin/bash -c ${script}`, which is wrong.
* (To add a little more complexity, it does *not* do this on Windows.)
*
* To solve that, we craft the pm2 call to use '/bin/bash -c' correctly.
* -prf
*/
const IS_WINDOWS = (process.platform === 'win32' || process.platform === 'win64' || /^(msys|cygwin)$/.test(process.env.OSTYPE))
var script = p.join(__dirname, 'index.js')
var args = []
if (opts.port) args.push('--port', opts.port)
if (opts.storage) args.push('--storage', opts.storage)
if (opts.logLevel) args.push('--log-level', opts.logLevel)
if (opts.memoryOnly) args.push('--memory-only')
if (opts.noAnnounce) args.push('--no-announce')
if (opts.noDebug) args.push('--no-debug')
if (opts.bootstrap === false) args.push('--bootstrap', false)
else if (Array.isArray(opts.bootstrap) && opts.bootstrap.length) args.push('--bootstrap', opts.bootstrap.join(','))
var interpreter = opts.interpreter || process.execPath
var interpreterArgs = [`--max-old-space-size=${opts.heapSize}`]
if (!IS_WINDOWS) {
const execArg = [interpreter, interpreterArgs, script].concat(args).map(escapeStringArg).join(' ')
args = ['-c', execArg]
script = 'bash'
interpreter = undefined
interpreterArgs = undefined
}
const description = {
script,
args,
interpreter,
interpreterArgs,
name: opts.processName || 'hyperdrive',
env: opts.env || process.env,
output: opts.unstructuredLog,
error: opts.structuredLog,
killTimeout: 10000,
autorestart: false
}
try {
if (opts.structuredLog === constants.structuredLog) {
await fs.rename(constants.structuredLog, constants.structuredLog.replace('.json', '.old.json'))
}
if (opts.unstructuredLog === constants.unstructuredLog) {
await fs.rename(constants.unstructuredLog, constants.unstructuredLog.replace('.log', '.old.log'))
}
} catch (err) {
// If the log file couldn't be rotated, it's OK.
}
if (opts.foreground) {
return startForeground(description, opts)
} else {
return startDaemon(description, opts)
}
function startForeground (description, opts) {
const daemon = new HyperdriveDaemon({ ...opts, metadata: null, main: true })
process.title = 'hyperdrive'
process.removeAllListeners('SIGINT')
process.removeAllListeners('SIGTERM')
daemon.start()
return { opts, description }
}
function startDaemon (description, opts) {
return new Promise((resolve, reject) => {
pm2.connect(!!opts.noPM2DaemonMode, err => {
if (err) return reject(new Error('Could not connect to the process manager to start the daemon.'))
pm2.start(description, err => {
pm2.disconnect()
if (err) return reject(err)
return resolve({ opts, description })
})
})
})
}
}
async function stop (name, port) {
name = name || constants.processName
port = port || constants.port
return new Promise((resolve, reject) => {
pm2.connect(err => {
if (err) return reject(new Error('Could not connect to the process manager to stop the daemon.'))
pm2.delete(name, err => {
pm2.disconnect()
if (err) return reject(err)
return resolve()
})
})
})
}
module.exports = {
start,
stop
}
function escapeStringArg (v) {
return (typeof v === 'string' && v.includes(' ')) ? `"${v}"` : v
}