4
import path from 'path';
6
import bcrypt from 'bcrypt-nodejs';
7
import chalk from 'chalk';
8
import ensureArray from 'ensure-array';
9
import expandTilde from 'expand-tilde';
10
import express from 'express';
11
import httpProxy from 'http-proxy';
12
import escapeRegExp from 'lodash/escapeRegExp';
13
import isEqual from 'lodash/isEqual';
14
import set from 'lodash/set';
15
import size from 'lodash/size';
16
import trimEnd from 'lodash/trimEnd';
17
import uniqWith from 'lodash/uniqWith';
18
import webappengine from 'webappengine';
19
import settings from './config/settings';
20
import app from './app';
21
import cncengine from './services/cncengine';
22
import monitor from './services/monitor';
23
import config from './services/configstore';
24
import { ensureString } from './lib/ensure-type';
25
import logger, { setLevel } from './lib/logger';
26
import urljoin from './lib/urljoin';
28
const log = logger('init');
30
const createServer = (options, callback) => {
31
options = { ...options };
34
const verbosity = options.verbosity;
37
if (verbosity === 1) {
38
set(settings, 'verbosity', verbosity);
41
if (verbosity === 2) {
42
set(settings, 'verbosity', verbosity);
45
if (verbosity === 3) {
46
set(settings, 'verbosity', verbosity);
51
const rcfile = path.resolve(options.configFile || settings.rcfile);
54
log.info(`Loading configuration from ${chalk.yellow(JSON.stringify(rcfile))}`);
58
settings.rcfile = rcfile;
61
if (!config.get('secret')) {
63
const secret = bcrypt.genSaltSync();
64
config.set('secret', secret);
67
settings.secret = config.get('secret', settings.secret);
71
const watchDirectory = options.watchDirectory || config.get('watchDirectory');
74
if (fs.existsSync(watchDirectory)) {
75
log.info(`Watching ${chalk.yellow(JSON.stringify(watchDirectory))} for file changes`);
78
monitor.start({ watchDirectory: watchDirectory });
80
log.error(`The directory ${chalk.yellow(JSON.stringify(watchDirectory))} does not exist.`);
86
const accessTokenLifetime = options.accessTokenLifetime || config.get('accessTokenLifetime');
88
if (accessTokenLifetime) {
89
set(settings, 'accessTokenLifetime', accessTokenLifetime);
94
const allowRemoteAccess = options.allowRemoteAccess || config.get('allowRemoteAccess', false);
96
if (allowRemoteAccess) {
97
if (size(config.get('users')) === 0) {
98
log.warn('You\'ve enabled remote access to the server. It\'s recommended to create an user account to protect against malicious attacks.');
101
set(settings, 'allowRemoteAccess', allowRemoteAccess);
105
const { port = 0, host, backlog } = options;
106
const mountPoints = uniqWith([
107
...ensureArray(options.mountPoints),
108
...ensureArray(config.get('mountPoints'))
109
], isEqual).filter(mount => {
110
if (!mount || !mount.route || mount.route === '/') {
111
log.error(`Must specify a valid route path ${JSON.stringify(mount.route)}.`);
119
mountPoints.forEach(mount => {
120
if (ensureString(mount.target).match(/^(http|https):\/\//i)) {
121
log.info(`Starting a proxy server to proxy all requests starting with ${chalk.yellow(mount.route)} to ${chalk.yellow(mount.target)}`);
126
server: (options) => {
137
const { route = '/' } = { ...options };
138
const routeWithoutTrailingSlash = trimEnd(route, '/');
139
const target = mount.target;
140
const targetPathname = url.parse(target).pathname;
141
const proxyPathPattern = new RegExp('^' + escapeRegExp(urljoin(targetPathname, routeWithoutTrailingSlash)), 'i');
143
log.debug(`> route=${chalk.yellow(route)}`);
144
log.debug(`> routeWithoutTrailingSlash=${chalk.yellow(routeWithoutTrailingSlash)}`);
145
log.debug(`> target=${chalk.yellow(target)}`);
146
log.debug(`> targetPathname=${chalk.yellow(targetPathname)}`);
147
log.debug(`> proxyPathPattern=RegExp(${chalk.yellow(proxyPathPattern)})`);
149
const proxy = httpProxy.createProxyServer({
159
proxy.on('proxyReq', (proxyReq, req, res, options) => {
160
const originalPath = proxyReq.path || '';
161
proxyReq.path = originalPath
162
.replace(proxyPathPattern, targetPathname)
165
log.debug(`proxy.on('proxyReq'): modifiedPath=${chalk.yellow(proxyReq.path)}, originalPath=${chalk.yellow(originalPath)}`);
168
proxy.on('proxyRes', (proxyRes, req, res) => {
169
log.debug(`proxy.on('proxyRes'): headers=${JSON.stringify(proxyRes.headers, true, 2)}`);
172
const app = express();
177
app.all(urljoin(routeWithoutTrailingSlash, '*'), (req, res) => {
179
log.debug(`proxy.web(): url=${chalk.yellow(url)}`);
185
app.all(routeWithoutTrailingSlash, (req, res, next) => {
188
if (url.indexOf(routeWithoutTrailingSlash) === 0 &&
189
url.indexOf(routeWithoutTrailingSlash + '/') < 0) {
190
const redirectUrl = routeWithoutTrailingSlash + '/' + url.slice(routeWithoutTrailingSlash.length);
191
log.debug(`redirect: url=${chalk.yellow(url)}, redirectUrl=${chalk.yellow(redirectUrl)}`);
192
res.redirect(301, redirectUrl);
204
const directory = expandTilde(ensureString(mount.target)).trim();
206
log.info(`Mounting a directory ${chalk.yellow(JSON.stringify(directory))} to serve requests starting with ${chalk.yellow(mount.route)}`);
209
log.error(`The directory path ${chalk.yellow(JSON.stringify(directory))} must not be empty.`);
212
if (!path.isAbsolute(directory)) {
213
log.error(`The directory path ${chalk.yellow(JSON.stringify(directory))} must be absolute.`);
216
if (!fs.existsSync(directory)) {
217
log.error(`The directory path ${chalk.yellow(JSON.stringify(directory))} does not exist.`);
235
webappengine({ port, host, backlog, routes })
236
.on('ready', (server) => {
238
cncengine.start(server, options.controller || config.get('controller', ''));
240
const address = server.address().address;
241
const port = server.address().port;
243
callback && callback(null, {
249
if (address !== '0.0.0.0') {
250
log.info('Starting the server at ' + chalk.yellow(`http://${address}:${port}`));
254
dns.lookup(os.hostname(), { family: 4, all: true }, (err, addresses) => {
256
log.error('Can\'t resolve host name:', err);
260
addresses.forEach(({ address, family }) => {
261
log.info('Starting the server at ' + chalk.yellow(`http://${address}:${port}`));
265
.on('error', (err) => {
266
callback && callback(err);