mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
6908807236
This adds a config file that's loaded very early on during startup. It enables us to save/load settings from within Grist's admin panel, that affect the startup of the FlexServer. The config file loading: - Is type-safe, - Validates the config file on startup - Provides a path to upgrade to future versions. It should be extensible from other versions of Grist (such as desktop), by overriding `getGlobalConfig` in stubs. ---- Some minor refactors needed to occur to make this possible. This includes: - Extracting config loading into its own module (out of FlexServer). - Cleaning up the `loadConfig` function in FlexServer into `loadLoginSystem` (which is what its main purpose was before).
228 lines
6.9 KiB
TypeScript
228 lines
6.9 KiB
TypeScript
/**
|
|
*
|
|
* A version of hosted grist that recombines a home server,
|
|
* a doc worker, and a static server on a single port.
|
|
*
|
|
*/
|
|
|
|
import {FlexServer, FlexServerOptions} from 'app/server/lib/FlexServer';
|
|
import {GristLoginSystem} from 'app/server/lib/GristServer';
|
|
import log from 'app/server/lib/log';
|
|
import {getGlobalConfig} from "app/server/lib/globalConfig";
|
|
|
|
// Allowed server types. We'll start one or a combination based on the value of GRIST_SERVERS
|
|
// environment variable.
|
|
export type ServerType = "home" | "docs" | "static" | "app";
|
|
const allServerTypes: ServerType[] = ["home", "docs", "static", "app"];
|
|
|
|
// Parse a comma-separate list of server types into an array, with validation.
|
|
export function parseServerTypes(serverTypes: string|undefined): ServerType[] {
|
|
// Split and filter out empty strings (including the one we get when splitting "").
|
|
const types = (serverTypes || "").trim().split(',').filter(part => Boolean(part));
|
|
|
|
// Check that parts is non-empty and only contains valid options.
|
|
if (!types.length) {
|
|
throw new Error(`No server types; should be a comma-separated list of ${allServerTypes.join(", ")}`);
|
|
}
|
|
for (const t of types) {
|
|
if (!allServerTypes.includes(t as ServerType)) {
|
|
throw new Error(`Invalid server type '${t}'; should be in ${allServerTypes.join(", ")}`);
|
|
}
|
|
}
|
|
return types as ServerType[];
|
|
}
|
|
|
|
function checkUserContentPort(): number | null {
|
|
// Check whether a port is explicitly set for user content.
|
|
if (process.env.GRIST_UNTRUSTED_PORT) {
|
|
return parseInt(process.env.GRIST_UNTRUSTED_PORT, 10);
|
|
}
|
|
// Checks whether to serve user content on same domain but on different port
|
|
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;
|
|
}
|
|
|
|
interface ServerOptions extends FlexServerOptions {
|
|
logToConsole?: boolean; // If set, messages logged to console (default: false)
|
|
// (but if options are not given at all in call to main,
|
|
// logToConsole is set to true)
|
|
externalStorage?: boolean; // If set, documents saved to external storage such as s3 (default is to check environment
|
|
// variables, which get set in various ways in dev/test entry points)
|
|
loginSystem?: () => Promise<GristLoginSystem>;
|
|
}
|
|
|
|
/**
|
|
* Start a server on the given port, including the functionality specified in serverTypes.
|
|
*/
|
|
export async function main(port: number, serverTypes: ServerType[],
|
|
options: ServerOptions = {}) {
|
|
const includeHome = serverTypes.includes("home");
|
|
const includeDocs = serverTypes.includes("docs");
|
|
const includeStatic = serverTypes.includes("static");
|
|
const includeApp = serverTypes.includes("app");
|
|
|
|
options.settings ??= await getGlobalConfig();
|
|
|
|
const server = new FlexServer(port, `server(${serverTypes.join(",")})`, options);
|
|
|
|
// We need to know early on whether we will be serving plugins or not.
|
|
if (includeHome) {
|
|
const userPort = checkUserContentPort();
|
|
server.setServesPlugins(userPort !== undefined);
|
|
} else {
|
|
server.setServesPlugins(false);
|
|
}
|
|
|
|
if (options.loginSystem) {
|
|
server.setLoginSystem(options.loginSystem);
|
|
}
|
|
|
|
server.addCleanup();
|
|
server.setDirectory();
|
|
|
|
if (process.env.GRIST_TEST_ROUTER) {
|
|
// Add a mock api for adding/removing doc workers from load balancer.
|
|
server.testAddRouter();
|
|
}
|
|
|
|
if (options.logToConsole !== false) { server.addLogging(); }
|
|
if (options.externalStorage === false) { server.disableExternalStorage(); }
|
|
await server.addLoginMiddleware();
|
|
|
|
if (includeDocs) {
|
|
// It is important that /dw and /v prefixes are accepted (if present) by health check
|
|
// in this case, since they are included in the url registered for the doc worker.
|
|
server.stripDocWorkerIdPathPrefixIfPresent();
|
|
server.addTagChecker();
|
|
}
|
|
|
|
server.addHealthCheck();
|
|
if (includeHome || includeApp) {
|
|
server.addBootPage();
|
|
}
|
|
server.denyRequestsIfNotReady();
|
|
|
|
if (includeHome || includeStatic || includeApp) {
|
|
server.setDirectory();
|
|
}
|
|
|
|
if (includeHome || includeStatic) {
|
|
server.addStaticAndBowerDirectories();
|
|
}
|
|
|
|
await server.initHomeDBManager();
|
|
server.addHosts();
|
|
|
|
server.addDocWorkerMap();
|
|
|
|
if (includeHome || includeStatic) {
|
|
await server.addAssetsForPlugins();
|
|
}
|
|
|
|
if (includeHome) {
|
|
server.addEarlyWebhooks();
|
|
}
|
|
|
|
if (includeHome || includeDocs || includeApp) {
|
|
server.addSessions();
|
|
}
|
|
|
|
server.addAccessMiddleware();
|
|
server.addApiMiddleware();
|
|
await server.addBillingMiddleware();
|
|
|
|
try {
|
|
await server.start();
|
|
|
|
if (includeHome) {
|
|
server.addUsage();
|
|
if (!includeDocs) {
|
|
server.addDocApiForwarder();
|
|
}
|
|
server.addJsonSupport();
|
|
server.addUpdatesCheck();
|
|
await server.addLandingPages();
|
|
// todo: add support for home api to standalone app
|
|
server.addHomeApi();
|
|
server.addBillingApi();
|
|
server.addNotifier();
|
|
await server.addTelemetry();
|
|
await server.addHousekeeper();
|
|
await server.addLoginRoutes();
|
|
server.addAccountPage();
|
|
server.addBillingPages();
|
|
server.addWelcomePaths();
|
|
server.addLogEndpoint();
|
|
server.addGoogleAuthEndpoint();
|
|
server.addInstallEndpoints();
|
|
}
|
|
|
|
if (includeDocs) {
|
|
server.addJsonSupport();
|
|
await server.addTelemetry();
|
|
await server.addDoc();
|
|
}
|
|
|
|
if (includeHome) {
|
|
server.addClientSecrets();
|
|
}
|
|
|
|
server.finalizeEndpoints();
|
|
await server.finalizePlugins(includeHome ? checkUserContentPort() : null);
|
|
server.checkOptionCombinations();
|
|
server.summary();
|
|
server.ready();
|
|
|
|
// Some tests have their timing perturbed by having this earlier
|
|
// TODO: update those tests.
|
|
if (includeDocs) {
|
|
await server.checkSandbox();
|
|
}
|
|
return server;
|
|
} catch(e) {
|
|
await server.close();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
|
|
export async function startMain() {
|
|
try {
|
|
|
|
const serverTypes = parseServerTypes(process.env.GRIST_SERVERS);
|
|
|
|
// No defaults for a port, since this server can serve very different purposes.
|
|
if (!process.env.GRIST_PORT) {
|
|
throw new Error("GRIST_PORT must be specified");
|
|
}
|
|
|
|
const port = parseInt(process.env.GRIST_PORT, 10);
|
|
|
|
const server = await main(port, serverTypes);
|
|
|
|
const opt = process.argv[2];
|
|
if (opt === '--testingHooks') {
|
|
await server.addTestingHooks();
|
|
}
|
|
|
|
return server;
|
|
} catch (e) {
|
|
log.error('mergedServer failed to start', e);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
startMain().catch((e) => log.error('mergedServer failed to start', e));
|
|
}
|