1 | const debug = require('debug')
|
2 | const cluster = require('cluster')
|
3 | const physicalCpuCount = require('physical-cpu-count')
|
4 | const eventToPromise = require('event-to-promise')
|
5 | const {redirect} = require('./redirect')
|
6 | const {lock, unlock} = require('./helpers/pid')
|
7 | const {normaliseManifest} = require('./helpers/normaliseManifest')
|
8 |
|
9 |
|
10 | const recursiveReaddir = require('recursive-readdir')
|
11 | const {Configuration} = require('./configuration/Configuration')
|
12 | const {Parser} = require('expr-eval')
|
13 |
|
14 | function evaluateToNumber (expression) {
|
15 | if (typeof expression === 'number') return expression
|
16 | return Parser.evaluate(
|
17 | expression,
|
18 | {max_physical_cpu_cores: physicalCpuCount}
|
19 | )
|
20 | }
|
21 |
|
22 | module.exports.Master = class Master {
|
23 | constructor (options, argv) {
|
24 | this.workers = []
|
25 | this.options = options
|
26 | this.argv = argv
|
27 | this.log = debug(process.title)
|
28 | }
|
29 |
|
30 | stop () {
|
31 | console.log('')
|
32 | process.exit()
|
33 | }
|
34 |
|
35 | async start () {
|
36 | const options = this.options
|
37 |
|
38 | lock()
|
39 | process.on('exit', unlock)
|
40 | process.on('SIGINT', this.stop.bind(this))
|
41 | process.on('SIGHUP', this.reload.bind(this))
|
42 |
|
43 | await this._load()
|
44 |
|
45 | if (options.http.redirect === true) {
|
46 | this.redirector = await redirect({
|
47 | http: options.http,
|
48 | acme: options.acme
|
49 | })
|
50 | }
|
51 |
|
52 | this.log('Server started')
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | }
|
66 |
|
67 | async _load () {
|
68 | const files = {}
|
69 | for (const host of this.options.hosts) {
|
70 | const {root, domain, manifest} = host
|
71 |
|
72 |
|
73 |
|
74 | files[domain] = {root, index: await recursiveReaddir(root)}
|
75 |
|
76 | host.manifest = normaliseManifest(manifest)
|
77 | }
|
78 |
|
79 | const workers = []
|
80 | const maxWorkerCount = evaluateToNumber(this.options.workers.count)
|
81 | if (maxWorkerCount < 1) throw new Error('Worker count must be at least 1')
|
82 | cluster.setupMaster({exec: require.resolve('./worker.js')})
|
83 | while (workers.length < maxWorkerCount) workers.push(cluster.fork())
|
84 | await Promise.all(workers.map((worker) => eventToPromise(worker, 'online')))
|
85 | workers.forEach((worker) => worker.send({options: this.options, files}))
|
86 | await Promise.all(workers.map((worker) => eventToPromise(worker, 'listening')))
|
87 | this.workers = workers
|
88 |
|
89 | for (const {root, domain} of this.options.hosts) {
|
90 | let authority = domain
|
91 | if (this.options.https.port !== 443) {
|
92 | authority += ':' + this.options.https.port
|
93 | }
|
94 | this.log(`Serving ${root} on port https://${authority}`)
|
95 | }
|
96 | }
|
97 |
|
98 | async reload () {
|
99 | this.log('Reloading configuration')
|
100 | try {
|
101 | const configuration = new Configuration(this.argv)
|
102 | this.options = await configuration.load()
|
103 | } catch (error) {
|
104 | this.log(error)
|
105 | return
|
106 | }
|
107 |
|
108 | const retirees = this.workers
|
109 | await this._load()
|
110 | for (const retiree of retirees) {
|
111 | retiree.kill()
|
112 | }
|
113 | }
|
114 | }
|