Files
cloudron-box/src/server.js

408 lines
22 KiB
JavaScript
Raw Normal View History

'use strict';
exports = module.exports = {
start: start,
stop: stop
};
var accesscontrol = require('./accesscontrol.js'),
assert = require('assert'),
async = require('async'),
cloudron = require('./cloudron.js'),
config = require('./config.js'),
database = require('./database.js'),
eventlog = require('./eventlog.js'),
express = require('express'),
hat = require('./hat.js'),
http = require('http'),
middleware = require('./middleware'),
passport = require('passport'),
path = require('path'),
routes = require('./routes/index.js'),
ws = require('ws');
var gHttpServer = null;
2016-04-15 12:33:54 -07:00
var gSysadminHttpServer = null;
function initializeExpressSync() {
var app = express();
var httpServer = http.createServer(app);
const wsServer = new ws.Server({ noServer: true }); // in noServer mode, we have to handle 'upgrade' and call handleUpgrade
var QUERY_LIMIT = '1mb', // max size for json and urlencoded queries (see also client_max_body_size in nginx)
FIELD_LIMIT = 2 * 1024 * 1024; // max fields that can appear in multipart
2016-01-18 12:20:09 -08:00
var REQUEST_TIMEOUT = 10000; // timeout for all requests (see also setTimeout on the httpServer)
var json = middleware.json({ strict: true, limit: QUERY_LIMIT }), // application/json
urlencoded = middleware.urlencoded({ extended: false, limit: QUERY_LIMIT }); // application/x-www-form-urlencoded
app.set('views', path.join(__dirname, 'oauth2views'));
2016-02-18 18:00:23 +01:00
app.set('view options', { layout: true, debug: false });
app.set('view engine', 'ejs');
2016-05-31 11:14:59 -07:00
app.set('json spaces', 2); // pretty json
// for rate limiting
app.enable('trust proxy');
2017-04-10 14:17:26 +02:00
if (process.env.BOX_ENV !== 'test') {
app.use(middleware.morgan('Box :method :url :status :response-time ms - :res[content-length]', {
immediate: false,
// only log failed requests by default
skip: function (req, res) { return res.statusCode < 400; }
}));
}
var router = new express.Router();
router.del = router.delete; // amend router.del for readability further on
app
// the timeout middleware will respond with a 503. the request itself cannot be 'aborted' and will continue
// search for req.clearTimeout in route handlers to see places where this timeout is reset
.use(middleware.timeout(REQUEST_TIMEOUT, { respond: true }))
2017-10-31 12:04:26 -07:00
.use(json)
.use(urlencoded)
.use(middleware.cookieParser())
.use(middleware.cors({ origins: [ '*' ], allowCredentials: false }))
.use(middleware.session({
secret: hat(128), // we only use the session during oauth, and already have an in-memory session store, so we can safely change that during restarts
resave: true,
saveUninitialized: true,
cookie: {
path: '/',
httpOnly: true,
secure: process.env.BOX_ENV !== 'test',
maxAge: 600000
}
}))
.use(passport.initialize())
.use(passport.session())
.use(router)
.use(middleware.lastMile());
// NOTE: these limits have to be in sync with nginx limits
var FILE_SIZE_LIMIT = '256mb', // max file size that can be uploaded (see also client_max_body_size in nginx)
FILE_TIMEOUT = 60 * 1000; // increased timeout for file uploads (1 min)
var multipart = middleware.multipart({ maxFieldsSize: FIELD_LIMIT, limit: FILE_SIZE_LIMIT, timeout: FILE_TIMEOUT });
// scope middleware implicitly also adds bearer token verification
var cloudronScope = routes.accesscontrol.scope(accesscontrol.SCOPE_CLOUDRON);
var subscriptionScope = routes.accesscontrol.scope(accesscontrol.SCOPE_SUBSCRIPTION);
2019-05-04 11:45:03 -07:00
var appstoreScope = routes.accesscontrol.scope(accesscontrol.SCOPE_APPSTORE);
var profileScope = routes.accesscontrol.scope(accesscontrol.SCOPE_PROFILE);
var usersReadScope = routes.accesscontrol.scope(accesscontrol.SCOPE_USERS_READ);
var usersManageScope = routes.accesscontrol.scope(accesscontrol.SCOPE_USERS_MANAGE);
var appsReadScope = routes.accesscontrol.scope(accesscontrol.SCOPE_APPS_READ);
2018-09-05 23:02:06 -07:00
var appsManageScope = [ routes.accesscontrol.scope(accesscontrol.SCOPE_APPS_MANAGE), routes.apps.verifyOwnership ];
var settingsScope = routes.accesscontrol.scope(accesscontrol.SCOPE_SETTINGS);
var mailScope = routes.accesscontrol.scope(accesscontrol.SCOPE_MAIL);
2018-12-17 16:37:19 +01:00
var notificationsScope = [ routes.accesscontrol.scope(accesscontrol.SCOPE_PROFILE), routes.notifications.verifyOwnership ];
var clientsScope = routes.accesscontrol.scope(accesscontrol.SCOPE_CLIENTS);
var domainsReadScope = routes.accesscontrol.scope(accesscontrol.SCOPE_DOMAINS_READ);
var domainsManageScope = routes.accesscontrol.scope(accesscontrol.SCOPE_DOMAINS_MANAGE);
2018-10-30 20:56:43 -07:00
const isUnmanaged = routes.accesscontrol.isUnmanaged;
const verifyDomainLock = routes.domains.verifyDomainLock;
2019-01-25 14:57:07 -08:00
const verifySettingsLock = routes.settings.verifySettingsLock;
// csrf protection
var csrf = routes.oauth2.csrf();
// public routes
2018-12-11 15:29:47 -08:00
router.post('/api/v1/cloudron/setup', routes.provision.providerTokenAuth, routes.provision.setup); // only available until no-domain
router.post('/api/v1/cloudron/restore', routes.provision.restore); // only available until activated
router.post('/api/v1/cloudron/activate', routes.provision.setupTokenAuth, routes.provision.activate);
router.get ('/api/v1/cloudron/status', routes.provision.getStatus);
router.get ('/api/v1/cloudron/avatar', routes.settings.getCloudronAvatar); // this is a public alias for /api/v1/settings/cloudron_avatar
// developer routes
2017-11-23 23:41:01 +01:00
router.post('/api/v1/developer/login', routes.developer.login);
// cloudron routes
router.get ('/api/v1/cloudron/update', cloudronScope, routes.cloudron.getUpdateInfo);
router.post('/api/v1/cloudron/update', cloudronScope, routes.cloudron.update);
router.post('/api/v1/cloudron/prepare_dashboard_domain', cloudronScope, routes.cloudron.prepareDashboardDomain);
router.post('/api/v1/cloudron/set_dashboard_domain', cloudronScope, routes.cloudron.setDashboardAndMailDomain);
2018-12-10 20:20:53 -08:00
router.post('/api/v1/cloudron/renew_certs', cloudronScope, routes.cloudron.renewCerts);
router.post('/api/v1/cloudron/check_for_updates', cloudronScope, routes.cloudron.checkForUpdates);
router.get ('/api/v1/cloudron/reboot', cloudronScope, routes.cloudron.isRebootRequired);
router.post('/api/v1/cloudron/reboot', cloudronScope, routes.cloudron.reboot);
router.get ('/api/v1/cloudron/graphs', cloudronScope, routes.graphs.getGraphs);
router.get ('/api/v1/cloudron/disks', cloudronScope, routes.cloudron.getDisks);
2018-06-11 20:09:38 +02:00
router.get ('/api/v1/cloudron/logs/:unit', cloudronScope, routes.cloudron.getLogs);
router.get ('/api/v1/cloudron/logstream/:unit', cloudronScope, routes.cloudron.getLogStream);
2019-01-23 16:44:45 +01:00
router.get ('/api/v1/cloudron/eventlog', cloudronScope, routes.eventlog.list);
router.get ('/api/v1/cloudron/eventlog/:eventId', cloudronScope, routes.eventlog.get);
// subscription routes
router.post('/api/v1/subscription', subscriptionScope, routes.subscription.subscribeCloudron);
2019-05-03 20:17:40 -07:00
router.get ('/api/v1/subscription', subscriptionScope, routes.subscription.getSubscription);
// tasks
2018-12-08 20:12:23 -08:00
router.get ('/api/v1/tasks', settingsScope, routes.tasks.list);
2018-11-30 14:16:00 -08:00
router.get ('/api/v1/tasks/:taskId', settingsScope, routes.tasks.get);
2018-12-08 21:31:55 -08:00
router.get ('/api/v1/tasks/:taskId/logs', cloudronScope, routes.tasks.getLogs);
router.get ('/api/v1/tasks/:taskId/logstream', cloudronScope, routes.tasks.getLogStream);
router.post('/api/v1/tasks/:taskId/stop', settingsScope, routes.tasks.stopTask);
2018-12-17 16:37:19 +01:00
// notifications
router.get ('/api/v1/notifications', notificationsScope, routes.notifications.list);
router.get ('/api/v1/notifications/:notificationId', notificationsScope, routes.notifications.get);
router.post('/api/v1/notifications/:notificationId', notificationsScope, routes.notifications.ack);
2018-11-19 14:40:32 -08:00
// backups
router.get ('/api/v1/backups', settingsScope, routes.backups.list);
2019-04-13 18:09:04 -07:00
router.post('/api/v1/backups/create', settingsScope, routes.backups.startBackup);
router.post('/api/v1/backups/cleanup', settingsScope, routes.backups.cleanup);
2018-11-19 14:40:32 -08:00
// config route (for dashboard)
router.get ('/api/v1/config', profileScope, routes.cloudron.getConfig);
2018-04-26 19:57:44 -07:00
// working off the user behind the provided token
2018-05-13 21:52:48 -07:00
router.get ('/api/v1/profile', profileScope, routes.profile.get);
router.post('/api/v1/profile', profileScope, routes.profile.update);
router.post('/api/v1/profile/password', profileScope, routes.users.verifyPassword, routes.profile.changePassword);
router.post('/api/v1/profile/twofactorauthentication', profileScope, routes.profile.setTwoFactorAuthenticationSecret);
router.post('/api/v1/profile/twofactorauthentication/enable', profileScope, routes.profile.enableTwoFactorAuthentication);
router.post('/api/v1/profile/twofactorauthentication/disable', profileScope, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication);
2018-04-25 19:08:15 +02:00
2016-06-02 00:20:33 -07:00
// user routes
router.get ('/api/v1/users', usersReadScope, routes.users.list);
router.post('/api/v1/users', usersManageScope, routes.users.create);
2018-08-03 21:52:31 -07:00
router.get ('/api/v1/users/:userId', usersManageScope, routes.users.get); // this is manage scope because it returns non-restricted fields
router.del ('/api/v1/users/:userId', usersManageScope, routes.users.verifyPassword, routes.users.remove);
router.post('/api/v1/users/:userId', usersManageScope, routes.users.update);
router.post('/api/v1/users/:userId/password', usersManageScope, routes.users.changePassword);
router.put ('/api/v1/users/:userId/groups', usersManageScope, routes.users.setGroups);
2018-08-17 09:49:58 -07:00
router.post('/api/v1/users/:userId/send_invite', usersManageScope, routes.users.sendInvite);
router.post('/api/v1/users/:userId/create_invite', usersManageScope, routes.users.createInvite);
2018-06-28 16:48:04 -07:00
router.post('/api/v1/users/:userId/transfer', usersManageScope, routes.users.transferOwnership);
2016-02-09 13:34:01 -08:00
// Group management
router.get ('/api/v1/groups', usersReadScope, routes.groups.list);
router.post('/api/v1/groups', usersManageScope, routes.groups.create);
router.get ('/api/v1/groups/:groupId', usersManageScope, routes.groups.get);
router.put ('/api/v1/groups/:groupId/members', usersManageScope, routes.groups.updateMembers);
router.post('/api/v1/groups/:groupId', usersManageScope, routes.groups.update);
router.del ('/api/v1/groups/:groupId', usersManageScope, routes.users.verifyPassword, routes.groups.remove);
2016-02-09 13:34:01 -08:00
// form based login routes used by oauth2 frame
router.get ('/api/v1/session/login', csrf, routes.oauth2.loginForm);
router.post('/api/v1/session/login', csrf, routes.oauth2.login);
router.get ('/api/v1/session/logout', routes.oauth2.logout);
router.get ('/api/v1/session/callback', routes.oauth2.sessionCallback());
router.get ('/api/v1/session/password/resetRequest.html', csrf, routes.oauth2.passwordResetRequestSite);
router.post('/api/v1/session/password/resetRequest', csrf, routes.oauth2.passwordResetRequest);
router.get ('/api/v1/session/password/sent.html', routes.oauth2.passwordSentSite);
router.get ('/api/v1/session/password/reset.html', csrf, routes.oauth2.passwordResetSite);
router.post('/api/v1/session/password/reset', csrf, routes.oauth2.passwordReset);
router.get ('/api/v1/session/account/setup.html', csrf, routes.oauth2.accountSetupSite);
router.post('/api/v1/session/account/setup', csrf, routes.oauth2.accountSetup);
// oauth2 routes
router.get ('/api/v1/oauth/dialog/authorize', routes.oauth2.authorization());
router.post('/api/v1/oauth/token', routes.oauth2.token());
// client/token routes
router.get ('/api/v1/clients', clientsScope, routes.clients.getAll);
router.post('/api/v1/clients', clientsScope, routes.clients.add);
router.get ('/api/v1/clients/:clientId', clientsScope, routes.clients.get);
router.post('/api/v1/clients/:clientId', clientsScope, routes.clients.add);
router.del ('/api/v1/clients/:clientId', clientsScope, routes.clients.del);
router.get ('/api/v1/clients/:clientId/tokens', clientsScope, routes.clients.getTokens);
router.post('/api/v1/clients/:clientId/tokens', clientsScope, routes.clients.addToken);
router.del ('/api/v1/clients/:clientId/tokens', clientsScope, routes.clients.delTokens);
router.del ('/api/v1/clients/:clientId/tokens/:tokenId', clientsScope, routes.clients.delToken);
2019-05-04 11:45:03 -07:00
// appstore routes
router.get ('/api/v1/appstore/apps', appstoreScope, routes.appstore.getApps);
router.get ('/api/v1/appstore/apps/:appstoreId', appstoreScope, routes.appstore.getApp);
router.get ('/api/v1/appstore/apps/:appstoreId/versions/:versionId', appstoreScope, routes.appstore.getAppVersion);
// app routes
router.get ('/api/v1/apps', appsReadScope, routes.apps.getApps);
router.get ('/api/v1/apps/:id', appsManageScope, routes.apps.getApp);
router.get ('/api/v1/apps/:id/icon', routes.apps.getAppIcon);
router.post('/api/v1/apps/install', appsManageScope, routes.apps.installApp);
router.post('/api/v1/apps/:id/uninstall', appsManageScope, routes.users.verifyPassword, routes.apps.uninstallApp);
router.post('/api/v1/apps/:id/configure', appsManageScope, routes.apps.configureApp);
router.post('/api/v1/apps/:id/update', appsManageScope, routes.apps.updateApp);
router.post('/api/v1/apps/:id/restore', appsManageScope, routes.users.verifyPassword, routes.apps.restoreApp);
router.post('/api/v1/apps/:id/backup', appsManageScope, routes.apps.backupApp);
router.get ('/api/v1/apps/:id/backups', appsManageScope, routes.apps.listBackups);
router.post('/api/v1/apps/:id/stop', appsManageScope, routes.apps.stopApp);
router.post('/api/v1/apps/:id/start', appsManageScope, routes.apps.startApp);
router.get ('/api/v1/apps/:id/logstream', appsManageScope, routes.apps.getLogStream);
router.get ('/api/v1/apps/:id/logs', appsManageScope, routes.apps.getLogs);
router.get ('/api/v1/apps/:id/exec', appsManageScope, routes.apps.exec);
// websocket cannot do bearer authentication
2018-09-05 23:02:06 -07:00
router.get ('/api/v1/apps/:id/execws', routes.accesscontrol.websocketAuth.bind(null, [ accesscontrol.SCOPE_APPS_MANAGE ]), routes.apps.verifyOwnership, routes.apps.execWebSocket);
router.post('/api/v1/apps/:id/clone', appsManageScope, routes.apps.cloneApp);
router.get ('/api/v1/apps/:id/download', appsManageScope, routes.apps.downloadFile);
router.post('/api/v1/apps/:id/upload', appsManageScope, multipart, routes.apps.uploadFile);
2018-06-28 16:48:04 -07:00
router.post('/api/v1/apps/:id/owner', appsManageScope, routes.apps.setOwner);
2016-05-06 08:42:27 -07:00
// settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above)
2019-01-25 14:57:07 -08:00
router.get ('/api/v1/settings/:setting', settingsScope, verifySettingsLock, routes.settings.get);
router.post('/api/v1/settings/:setting', settingsScope, verifySettingsLock, (req, res, next) => {
return req.params.setting === 'cloudron_avatar' ? multipart(req, res, next) : next();
}, routes.settings.set);
2017-06-28 17:06:12 -05:00
// email routes
router.get ('/api/v1/mail/:domain', mailScope, routes.mail.getDomain);
router.post('/api/v1/mail', mailScope, routes.mail.addDomain);
router.get ('/api/v1/mail/:domain/stats', mailScope, routes.users.verifyPassword, routes.mail.getDomainStats);
router.del ('/api/v1/mail/:domain', mailScope, routes.users.verifyPassword, routes.mail.removeDomain);
router.get ('/api/v1/mail/:domain/status', mailScope, routes.mail.getStatus);
router.post('/api/v1/mail/:domain/mail_from_validation', mailScope, routes.mail.setMailFromValidation);
router.post('/api/v1/mail/:domain/catch_all', mailScope, routes.mail.setCatchAllAddress);
router.post('/api/v1/mail/:domain/relay', mailScope, routes.mail.setMailRelay);
router.post('/api/v1/mail/:domain/enable', mailScope, routes.mail.setMailEnabled);
router.post('/api/v1/mail/:domain/dns', mailScope, routes.mail.setDnsRecords);
router.post('/api/v1/mail/:domain/send_test_mail', mailScope, routes.mail.sendTestMail);
2018-12-06 10:23:10 -08:00
router.get ('/api/v1/mail/:domain/mailboxes', mailScope, routes.mail.listMailboxes);
router.get ('/api/v1/mail/:domain/mailboxes/:name', mailScope, routes.mail.getMailbox);
router.post('/api/v1/mail/:domain/mailboxes', mailScope, routes.mail.addMailbox);
router.post('/api/v1/mail/:domain/mailboxes/:name', mailScope, routes.mail.updateMailbox);
router.del ('/api/v1/mail/:domain/mailboxes/:name', mailScope, routes.mail.removeMailbox);
router.get ('/api/v1/mail/:domain/aliases', mailScope, routes.mail.listAliases);
router.get ('/api/v1/mail/:domain/aliases/:name', mailScope, routes.mail.getAliases);
router.put ('/api/v1/mail/:domain/aliases/:name', mailScope, routes.mail.setAliases);
router.get ('/api/v1/mail/:domain/lists', mailScope, routes.mail.getLists);
router.post('/api/v1/mail/:domain/lists', mailScope, routes.mail.addList);
router.get ('/api/v1/mail/:domain/lists/:name', mailScope, routes.mail.getList);
router.post('/api/v1/mail/:domain/lists/:name', mailScope, routes.mail.updateList);
router.del ('/api/v1/mail/:domain/lists/:name', mailScope, routes.mail.removeList);
// feedback
2018-12-19 10:54:33 -08:00
router.post('/api/v1/support/feedback', cloudronScope, isUnmanaged, routes.support.feedback);
router.get ('/api/v1/support/remote_support', cloudronScope, isUnmanaged, routes.support.getRemoteSupport);
router.post('/api/v1/support/remote_support', cloudronScope, isUnmanaged, routes.support.enableRemoteSupport);
2017-10-28 22:18:07 +02:00
// domain routes
router.post('/api/v1/domains', domainsManageScope, routes.domains.add);
router.get ('/api/v1/domains', domainsReadScope, routes.domains.getAll);
router.get ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.get); // this is manage scope because it returns non-restricted fields
router.put ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.update);
router.del ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.users.verifyPassword, routes.domains.del);
2017-10-28 22:18:07 +02:00
2018-11-15 19:59:08 +01:00
// addon routes
2018-12-02 18:46:34 -08:00
router.get ('/api/v1/services', cloudronScope, routes.services.getAll);
router.get ('/api/v1/services/:service', cloudronScope, routes.services.get);
router.post('/api/v1/services/:service', cloudronScope, routes.services.configure);
router.get ('/api/v1/services/:service/logs', cloudronScope, routes.services.getLogs);
router.get ('/api/v1/services/:service/logstream', cloudronScope, routes.services.getLogStream);
router.post('/api/v1/services/:service/restart', cloudronScope, routes.services.restart);
2018-11-15 19:59:08 +01:00
2016-06-01 16:31:47 -07:00
// disable server socket "idle" timeout. we use the timeout middleware to handle timeouts on a route level
2016-06-01 17:33:04 -07:00
// we rely on nginx for timeouts on the TCP level (see client_header_timeout)
2016-01-18 12:20:09 -08:00
httpServer.setTimeout(0);
// upgrade handler
httpServer.on('upgrade', function (req, socket, head) {
// create a node response object for express
var res = new http.ServerResponse({});
res.assignSocket(socket);
2017-08-17 11:56:51 +02:00
if (req.headers.upgrade === 'websocket') {
res.handleUpgrade = function (callback) {
wsServer.handleUpgrade(req, socket, head, callback);
};
2017-08-17 11:56:51 +02:00
} else {
res.sendUpgradeHandshake = function () { // could extend express.response as well
socket.write('HTTP/1.1 101 TCP Handshake\r\n' +
'Upgrade: tcp\r\n' +
'Connection: Upgrade\r\n' +
'\r\n');
};
}
// route through express middleware. if we provide no callback, express will provide a 'finalhandler'
// TODO: it's not clear if socket needs to be destroyed
app(req, res);
});
return httpServer;
}
// provides local webhooks for sysadmins
2016-04-15 12:33:54 -07:00
function initializeSysadminExpressSync() {
var app = express();
var httpServer = http.createServer(app);
var QUERY_LIMIT = '1mb'; // max size for json and urlencoded queries
var REQUEST_TIMEOUT = 10000; // timeout for all requests
var json = middleware.json({ strict: true, limit: QUERY_LIMIT }), // application/json
urlencoded = middleware.urlencoded({ extended: false, limit: QUERY_LIMIT }); // application/x-www-form-urlencoded
2016-04-15 12:33:54 -07:00
if (process.env.BOX_ENV !== 'test') app.use(middleware.morgan('Box Sysadmin :method :url :status :response-time ms - :res[content-length]', { immediate: false }));
var router = new express.Router();
router.del = router.delete; // amend router.del for readability further on
app
2017-10-31 12:04:26 -07:00
.use(middleware.timeout(REQUEST_TIMEOUT))
.use(json)
.use(urlencoded)
.use(router)
.use(middleware.lastMile());
2016-04-15 12:33:54 -07:00
// Sysadmin routes
router.post('/api/v1/backup', routes.sysadmin.backup);
router.post('/api/v1/update', routes.sysadmin.update);
router.post('/api/v1/retire', routes.sysadmin.retire);
2018-11-11 21:58:02 -08:00
router.post('/api/v1/apps/:id/import', routes.sysadmin.importAppDatabase);
2019-02-27 12:05:45 +01:00
// routes to test features otherwise hard to test
router.post('/api/v1/test/digest', routes.sysadmin.testDigest);
return httpServer;
}
function start(callback) {
assert.strictEqual(typeof callback, 'function');
assert.strictEqual(gHttpServer, null, 'Server is already up and running.');
2018-05-01 13:34:46 -07:00
routes.oauth2.initialize(); // init's the oauth server
gHttpServer = initializeExpressSync();
2016-04-15 12:33:54 -07:00
gSysadminHttpServer = initializeSysadminExpressSync();
async.series([
2018-06-15 17:31:28 -07:00
routes.accesscontrol.initialize, // hooks up authentication strategies into passport
database.initialize,
2017-01-09 12:35:39 -08:00
cloudron.initialize,
gHttpServer.listen.bind(gHttpServer, config.get('port'), '127.0.0.1'),
gSysadminHttpServer.listen.bind(gSysadminHttpServer, config.get('sysadminPort'), '127.0.0.1'),
eventlog.add.bind(null, eventlog.ACTION_START, { userId: null, username: 'boot' }, { version: config.version() })
], callback);
}
function stop(callback) {
assert.strictEqual(typeof callback, 'function');
if (!gHttpServer) return callback(null);
async.series([
cloudron.uninitialize,
database.uninitialize,
2018-06-15 17:31:28 -07:00
routes.accesscontrol.uninitialize,
gHttpServer.close.bind(gHttpServer),
2016-04-15 12:33:54 -07:00
gSysadminHttpServer.close.bind(gSysadminHttpServer)
], function (error) {
if (error) return callback(error);
routes.oauth2.uninitialize();
gHttpServer = null;
2016-04-15 12:33:54 -07:00
gSysadminHttpServer = null;
callback(null);
});
}