/** * * 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(); 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); }); }