You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/app/server/devServerMain.ts

191 lines
7.3 KiB

/**
*
* Run a home server, doc worker, and static server as a single process for regular
* development work.
*
* PORT -- this sets the main web server port (defaults to 8080)
* HOME_PORT -- this sets the main home server port (defaults to 9000)
* STATIC_PORT -- port for the static resource server (defaults to 9001)
* DOC_PORT -- comma separated ports for doc workers (defaults to 9002)
* TEST_CLEAN_DATABASE -- reset the database(s) before starting
* GRIST_SINGLE_PORT -- if set, just a single combined server on HOME_PORT
* DOC_WORKER_COUNT -- if set, makes sure there are at least this number of
* doc workers. Will add ports incrementally after the last
* worker added with DOC_PORT.
*
* If you run more than one doc worker, you'll need to have a redis server running
* and REDIS_URL set (e.g. to redis://localhost).
*
*/
import {updateDb} from 'app/server/lib/dbUtils';
import {FlexServer} from 'app/server/lib/FlexServer';
import log from 'app/server/lib/log';
import {main as mergedServerMain} from 'app/server/mergedServerMain';
import {promisifyAll} from 'bluebird';
import * as fse from 'fs-extra';
import * as path from 'path';
import {createClient, RedisClient} from 'redis';
promisifyAll(RedisClient.prototype);
function getPort(envVarName: string, fallbackPort: number): number {
const val = process.env[envVarName];
return val ? parseInt(val, 10) : fallbackPort;
}
// Checks whether to serve user content on same domain but on different port
function checkUserContentPort(): number | null {
if (process.env.APP_UNTRUSTED_URL && process.env.APP_HOME_URL) {
const homeUrl = new URL(process.env.APP_HOME_URL);
const pluginUrl = new URL(process.env.APP_UNTRUSTED_URL);
// If the hostname of both home and plugin url are the same,
// but the ports are different
if (homeUrl.hostname === pluginUrl.hostname &&
homeUrl.port !== pluginUrl.port) {
const port = parseInt(pluginUrl.port || '80', 10);
return port;
}
}
return null;
}
export async function main() {
log.info("==========================================================================");
log.info("== devServer");
log.info("devServer starting. Please do not set any ports in environment :-)");
log.info("Server will be available at http://localhost:8080");
process.env.GRIST_HOSTED = "true";
if (!process.env.GRIST_ADAPT_DOMAIN) {
process.env.GRIST_ADAPT_DOMAIN = "true";
}
// Experimental plugins are enabled by default for devs
if (!process.env.GRIST_EXPERIMENTAL_PLUGINS) {
process.env.GRIST_EXPERIMENTAL_PLUGINS = "1";
}
// For tests, it is useful to start with the database in a known state.
// If TEST_CLEAN_DATABASE is set, we reset the database before starting.
if (process.env.TEST_CLEAN_DATABASE) {
const {createInitialDb} = require('test/gen-server/seed');
await createInitialDb();
if (process.env.REDIS_URL) {
await createClient(process.env.REDIS_URL).flushdbAsync();
}
} else {
await updateDb();
}
// In V1, we no longer create a config.json file automatically if it is missing.
// It is convenient to do that in the dev and test environment.
const appRoot = path.dirname(path.dirname(__dirname));
const instDir = process.env.GRIST_INST_DIR || appRoot;
if (process.env.GRIST_INST_DIR) {
const fileName = path.join(instDir, 'config.json');
if (!(await fse.pathExists(fileName))) {
const config = {
untrustedContentOrigin: 'notset',
};
await fse.writeFile(fileName, JSON.stringify(config, null, 2));
}
}
if (!process.env.GOOGLE_CLIENT_ID) {
log.warn('GOOGLE_CLIENT_ID is not defined, Google Drive Plugin will not work.');
}
if (!process.env.GOOGLE_API_KEY) {
log.warn('GOOGLE_API_KEY is not defined, Url plugin will not be able to access public files.');
}
if (process.env.GRIST_SINGLE_PORT) {
log.info("==========================================================================");
log.info("== mergedServer");
const port = getPort("HOME_PORT", 8080);
if (!process.env.APP_HOME_URL) {
process.env.APP_HOME_URL = `http://localhost:${port}`;
}
const server = await mergedServerMain(port, ["home", "docs", "static"]);
await server.addTestingHooks();
// If plugin content is served from same host but on different port,
// run webserver on that port
const userPort = checkUserContentPort();
if (userPort !== null) {
log.info("==========================================================================");
log.info("== userContent");
await server.startCopy('pluginServer', userPort);
}
return;
}
// The home server and web server(s) are effectively identical in Grist deployments
// now, but remain distinct in some test setups.
const homeServerPort = getPort("HOME_PORT", 9000);
const webServerPort = getPort("PORT", 8080);
if (!process.env.APP_HOME_URL) {
// All servers need to know a "main" URL for Grist. This is generally
// that of the web server. In some test setups, the web server port is left
// at 0 to be auto-allocated, but for those tests it suffices to use the home
// server port.
process.env.APP_HOME_URL = `http://localhost:${webServerPort || homeServerPort}`;
}
// Bring up the static resource server
log.info("==========================================================================");
log.info("== staticServer");
const staticPort = getPort("STATIC_PORT", 9001);
process.env.APP_STATIC_URL = `http://localhost:${staticPort}`;
await mergedServerMain(staticPort, ["static"]);
// Bring up a home server
log.info("==========================================================================");
log.info("== homeServer");
const home = await mergedServerMain(homeServerPort, ["home"]);
// If a distinct webServerPort is specified, we listen also on that port, though serving
// exactly the same content. This is handy for testing CORS issues.
if (webServerPort !== 0 && webServerPort !== homeServerPort) {
await home.startCopy('webServer', webServerPort);
}
// If plugin content is served from same host but on different port,
// run webserver on that port
const userPort = checkUserContentPort();
if (userPort !== null) {
log.info("==========================================================================");
log.info("== userContent");
await home.startCopy('pluginServer', userPort);
}
// Bring up the docWorker(s)
log.info("==========================================================================");
log.info("== docWorker");
const ports = (process.env.DOC_PORT || '9002').split(',').map(port => parseInt(port, 10));
if (process.env.DOC_WORKER_COUNT) {
const n = parseInt(process.env.DOC_WORKER_COUNT, 10);
while (ports.length < n) {
ports.push(ports[ports.length - 1] + 1);
}
}
log.info(`== ports ${ports.join(',')}`);
if (ports.length > 1 && !process.env.REDIS_URL) {
throw new Error('Need REDIS_URL=redis://localhost or similar for multiple doc workers');
}
const workers = new Array<FlexServer>();
for (const port of ports) {
workers.push(await mergedServerMain(port, ["docs"]));
}
await home.addTestingHooks(workers);
}
if (require.main === module) {
main().catch((e) => {
log.error("devServer failed to start %s", e);
process.exit(1);
});
}