Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/backend/data.ts
Views: 687
/*1Where Data is Stored:23We centralize here determination of all directories on the file system4where data is stored for any of the components of CoCalc, run in any way.56All information here must be determinable when this module is initialized,7e.g., from environment variables or heuristics involving the file system.8In particular, nothing here can be impacted by command line flags9or content of a database.10*/1112import Dict = NodeJS.Dict;1314const DEFINITION = `CoCalc Environment Variables:15- root -- if COCALC_ROOT is set then it; otherwise use [cocalc-source]/src/.16- data -- if the environment variable DATA is set, use that. Otherwise, use {root}/data17- pgdata -- if env var PGDATA is set, use that; otherwise, it is {data}/postgres: where data data is stored (if running locally)18- pghost - if env var PGHOST is set, use that; otherwise, it is {data}/postgres/socket: what database connects to19- projects -- If env var PROJECTS is set, use that; otherwise, it is {data}"/projects/[project_id]";20This is where project home directories are (or shared files for share server), and it MUST21contain the string "[project_id]".22- secrets -- if env var SECRETS is set, use that; otherwise, it is {data}/secrets: where to store secrets23- logs -- if env var LOGS is set, use that; otherwise, {data}/logs: directory in which to store logs24`;2526import { join, resolve } from "path";27import { ConnectionOptions } from "node:tls";28import { readFileSync } from "fs";29import { isEmpty } from "lodash";3031function determineRootFromPath(): string {32const cur = __dirname;33const search = "/src/";34const i = cur.lastIndexOf(search);35const root = resolve(cur.slice(0, i + search.length - 1));36process.env.COCALC_ROOT = root;37return root;38}3940// Each field value in this interface is to be treated as though it originated from a raw41// environment variable. These environment variables are used to configure CoCalc's SSL connection42// to the database.43//44interface CoCalcSSLEnvConfig extends Dict<string> {45SMC_DB_SSL?: string;46SMC_DB_SSL_CA_FILE?: string;47SMC_DB_SSL_CLIENT_CERT_FILE?: string;48SMC_DB_SSL_CLIENT_KEY_FILE?: string;49SMC_DB_SSL_CLIENT_KEY_PASSPHRASE?:string;50}5152// This interface is used to specify environment variables to be passed to the "psql" command for53// SSL configuration.54//55// See https://www.postgresql.org/docs/current/libpq-envars.html for more information.56//57export interface PsqlSSLEnvConfig {58// We could also add "verify-ca" here, but it's probably best to assume that we'd like the59// most secure option out of the box. The differences between "verify-ca" and "verify-full"60// can be found here: https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-CLIENTCERT61//62PGSSLMODE?: "verify-full" | "require";63// This typing is redundant but included for clarity.64//65PGSSLROOTCERT?: "system" | string;66PGSSLCERT?: string;67PGSSLKEY?: string;68}6970// A full list of property types and SSL config options can be found here:71//72// http://nodejs.org/api/tls.html#tls_tls_connect_options_callback73//74// We extend the existing ConnectionOptions interface to include certificate file paths, since these75// are used when connecting to Postgres outside of Node (e.g., for raw psql queries).76//77export type SSLConfig = ConnectionOptions & {78caFile?: string;79clientCertFile?: string;80clientKeyFile?: string;81} | boolean | undefined;8283/**84* Converts an environment-variable-driven SSLEnvConfig into a superset of the SSL context expected85* by node when generating SSL connections.86*87* @param env88*/89export function sslConfigFromCoCalcEnv(env: CoCalcSSLEnvConfig = process.env): SSLConfig {90const sslConfig: SSLConfig = {};9192if (env.SMC_DB_SSL_CA_FILE) {93sslConfig.caFile = env.SMC_DB_SSL_CA_FILE;94sslConfig.ca = readFileSync(env.SMC_DB_SSL_CA_FILE);95}9697if (env.SMC_DB_SSL_CLIENT_CERT_FILE) {98sslConfig.clientCertFile = env.SMC_DB_SSL_CLIENT_CERT_FILE;99sslConfig.cert = readFileSync(env.SMC_DB_SSL_CLIENT_CERT_FILE);100}101102if (env.SMC_DB_SSL_CLIENT_KEY_FILE) {103sslConfig.clientKeyFile = env.SMC_DB_SSL_CLIENT_KEY_FILE104sslConfig.key = readFileSync(env.SMC_DB_SSL_CLIENT_KEY_FILE);105}106107if (env.SMC_DB_SSL_CLIENT_KEY_PASSPHRASE) {108sslConfig.passphrase = env.SMC_DB_SSL_CLIENT_KEY_PASSPHRASE;109}110111return isEmpty(sslConfig) ? (env.SMC_DB_SSL?.toLowerCase() === "true") : sslConfig;112}113114/**115* Converts a provided SSLConfig object into (a subset of) its corresponding `psql` environment116* variables. See117*118* http://nodejs.org/api/tls.html#tls_tls_connect_options_callback119*120* for more information about these options.121*122* @param config123*/124export function sslConfigToPsqlEnv(config: SSLConfig): PsqlSSLEnvConfig {125if (!config) {126return {};127} else if (config === true) {128return {129PGSSLMODE: "require",130};131}132133// If SSL config is anything other than a boolean, require CA validation134//135const psqlArgs: PsqlSSLEnvConfig = {136PGSSLMODE: "verify-full",137};138139// Server CA. Uses CA file when provided and system certs otherwise.140//141if (config.caFile) {142psqlArgs.PGSSLROOTCERT = `${config.caFile}`;143} else {144psqlArgs.PGSSLROOTCERT = "system";145}146147// Client cert148//149if (config.clientCertFile) {150psqlArgs.PGSSLCERT = `${config.clientCertFile}`;151}152153// Client key154//155if (config.clientKeyFile) {156psqlArgs.PGSSLKEY = `${config.clientKeyFile}`;157}158159return psqlArgs;160}161162export const root: string = process.env.COCALC_ROOT ?? determineRootFromPath();163export const data: string = process.env.DATA ?? join(root, "data");164export const pguser: string = process.env.PGUSER ?? "smc";165export const pgdata: string = process.env.PGDATA ?? join(data, "postgres");166export const pghost: string = process.env.PGHOST ?? join(pgdata, "socket");167export const pgssl = sslConfigFromCoCalcEnv();168export const pgdatabase: string =169process.env.SMC_DB ?? process.env.PGDATABASE ?? "smc";170export const projects: string =171process.env.PROJECTS ?? join(data, "projects", "[project_id]");172export const secrets: string = process.env.SECRETS ?? join(data, "secrets");173export const logs: string = process.env.LOGS ?? join(data, "logs");174export const blobstore: "disk" | "sqlite" =175(process.env.COCALC_JUPYTER_BLOBSTORE_IMPL as any) ?? "sqlite";176177export let apiKey: string = process.env.API_KEY ?? "";178export let apiServer: string = process.env.API_SERVER ?? "";179180// Delete API_KEY from environment to reduce chances of it leaking, e.g., to181// spawned terminal subprocess.182// Important note: It's critical that only one version of the @cocalc/backend183// package is being used, or some parts of the code will get the API_KEY and184// others will not.185delete process.env.API_KEY;186187export function setApi({ key, server }: { key?: string; server?: string }) {188if (key != null) {189apiKey = key;190}191if (server != null) {192checkApiServer(server);193apiServer = server;194}195}196197function sanityChecks() {198// Do a sanity check on projects:199if (!projects.includes("[project_id]")) {200throw Error(201`${DEFINITION}\n\nenv variable PROJECTS must contain "[project_id]" but it is "${process.env.PROJECTS}"`,202);203}204if ((blobstore as any) != "sqlite" && (blobstore as any) != "disk") {205throw Error(206"If set, COCALC_JUPYTER_BLOBSTORE_IMPL must be 'sqlite' or 'disk'",207);208}209checkApiServer(apiServer);210}211212function checkApiServer(server) {213if (!server) return;214if (server.endsWith("/")) {215throw Error("API_SERVER must not end in /");216}217if (!server.startsWith("http://") && !server.startsWith("https://")) {218throw Error("API_SERVER must start with http:// or https://");219}220}221222sanityChecks();223224225