summaryrefslogtreecommitdiff
path: root/packages/backend/src/boot
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-11-12 02:02:25 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-11-12 02:02:25 +0900
commit0e4a111f81cceed275d9bec2695f6e401fb654d8 (patch)
tree40874799472fa07416f17b50a398ac33b7771905 /packages/backend/src/boot
parentupdate deps (diff)
downloadmisskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.gz
misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.bz2
misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.zip
refactoring
Resolve #7779
Diffstat (limited to 'packages/backend/src/boot')
-rw-r--r--packages/backend/src/boot/index.ts79
-rw-r--r--packages/backend/src/boot/master.ts194
-rw-r--r--packages/backend/src/boot/worker.ts20
3 files changed, 293 insertions, 0 deletions
diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts
new file mode 100644
index 0000000000..1723c32dd0
--- /dev/null
+++ b/packages/backend/src/boot/index.ts
@@ -0,0 +1,79 @@
+import * as cluster from 'cluster';
+import * as chalk from 'chalk';
+import Xev from 'xev';
+
+import Logger from '@/services/logger';
+import { envOption } from '../env';
+
+// for typeorm
+import 'reflect-metadata';
+import { masterMain } from './master';
+import { workerMain } from './worker';
+
+const logger = new Logger('core', 'cyan');
+const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
+const ev = new Xev();
+
+/**
+ * Init process
+ */
+export default async function() {
+ process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
+
+ if (cluster.isPrimary || envOption.disableClustering) {
+ await masterMain();
+
+ if (cluster.isPrimary) {
+ ev.mount();
+ }
+ }
+
+ if (cluster.isWorker || envOption.disableClustering) {
+ await workerMain();
+ }
+
+ // ユニットテスト時にMisskeyが子プロセスで起動された時のため
+ // それ以外のときは process.send は使えないので弾く
+ if (process.send) {
+ process.send('ok');
+ }
+}
+
+//#region Events
+
+// Listen new workers
+cluster.on('fork', worker => {
+ clusterLogger.debug(`Process forked: [${worker.id}]`);
+});
+
+// Listen online workers
+cluster.on('online', worker => {
+ clusterLogger.debug(`Process is now online: [${worker.id}]`);
+});
+
+// Listen for dying workers
+cluster.on('exit', worker => {
+ // Replace the dead worker,
+ // we're not sentimental
+ clusterLogger.error(chalk.red(`[${worker.id}] died :(`));
+ cluster.fork();
+});
+
+// Display detail of unhandled promise rejection
+if (!envOption.quiet) {
+ process.on('unhandledRejection', console.dir);
+}
+
+// Display detail of uncaught exception
+process.on('uncaughtException', err => {
+ try {
+ logger.error(err);
+ } catch { }
+});
+
+// Dying away...
+process.on('exit', code => {
+ logger.info(`The process is going to exit with code ${code}`);
+});
+
+//#endregion
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
new file mode 100644
index 0000000000..eeb7017cb0
--- /dev/null
+++ b/packages/backend/src/boot/master.ts
@@ -0,0 +1,194 @@
+import * as fs from 'fs';
+import { fileURLToPath } from 'url';
+import { dirname } from 'path';
+import * as os from 'os';
+import * as cluster from 'cluster';
+import * as chalk from 'chalk';
+import * as portscanner from 'portscanner';
+import { getConnection } from 'typeorm';
+
+import Logger from '@/services/logger';
+import loadConfig from '@/config/load';
+import { Config } from '@/config/types';
+import { lessThan } from '@/prelude/array';
+import { envOption } from '../env';
+import { showMachineInfo } from '@/misc/show-machine-info';
+import { initDb } from '../db/postgre';
+
+//const _filename = fileURLToPath(import.meta.url);
+const _filename = __filename;
+const _dirname = dirname(_filename);
+
+const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
+
+const logger = new Logger('core', 'cyan');
+const bootLogger = logger.createSubLogger('boot', 'magenta', false);
+
+function greet() {
+ if (!envOption.quiet) {
+ //#region Misskey logo
+ const v = `v${meta.version}`;
+ console.log(' _____ _ _ ');
+ console.log(' | |_|___ ___| |_ ___ _ _ ');
+ console.log(' | | | | |_ -|_ -| \'_| -_| | |');
+ console.log(' |_|_|_|_|___|___|_,_|___|_ |');
+ console.log(' ' + chalk.gray(v) + (' |___|\n'.substr(v.length)));
+ //#endregion
+
+ console.log(' Misskey is an open-source decentralized microblogging platform.');
+ console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo'));
+
+ console.log('');
+ console.log(chalk`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);
+ }
+
+ bootLogger.info('Welcome to Misskey!');
+ bootLogger.info(`Misskey v${meta.version}`, null, true);
+}
+
+function isRoot() {
+ // maybe process.getuid will be undefined under not POSIX environment (e.g. Windows)
+ return process.getuid != null && process.getuid() === 0;
+}
+
+/**
+ * Init master process
+ */
+export async function masterMain() {
+ let config!: Config;
+
+ // initialize app
+ try {
+ greet();
+ showEnvironment();
+ await showMachineInfo(bootLogger);
+ showNodejsVersion();
+ config = loadConfigBoot();
+ await connectDb();
+ await validatePort(config);
+ } catch (e) {
+ bootLogger.error('Fatal error occurred during initialization', null, true);
+ process.exit(1);
+ }
+
+ bootLogger.succ('Misskey initialized');
+
+ if (!envOption.disableClustering) {
+ await spawnWorkers(config.clusterLimit);
+ }
+
+ bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true);
+
+ if (!envOption.noDaemons) {
+ require('../daemons/server-stats').default();
+ require('../daemons/queue-stats').default();
+ require('../daemons/janitor').default();
+ }
+}
+
+const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10));
+const requiredNodejsVersion = [11, 7, 0];
+const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion);
+
+function showEnvironment(): void {
+ const env = process.env.NODE_ENV;
+ const logger = bootLogger.createSubLogger('env');
+ logger.info(typeof env === 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`);
+
+ if (env !== 'production') {
+ logger.warn('The environment is not in production mode.');
+ logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true);
+ }
+
+ logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`);
+}
+
+function showNodejsVersion(): void {
+ const nodejsLogger = bootLogger.createSubLogger('nodejs');
+
+ nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`);
+
+ if (!satisfyNodejsVersion) {
+ nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true);
+ process.exit(1);
+ }
+}
+
+function loadConfigBoot(): Config {
+ const configLogger = bootLogger.createSubLogger('config');
+ let config;
+
+ try {
+ config = loadConfig();
+ } catch (exception) {
+ if (typeof exception === 'string') {
+ configLogger.error(exception);
+ process.exit(1);
+ }
+ if (exception.code === 'ENOENT') {
+ configLogger.error('Configuration file not found', null, true);
+ process.exit(1);
+ }
+ throw exception;
+ }
+
+ configLogger.succ('Loaded');
+
+ return config;
+}
+
+async function connectDb(): Promise<void> {
+ const dbLogger = bootLogger.createSubLogger('db');
+
+ // Try to connect to DB
+ try {
+ dbLogger.info('Connecting...');
+ await initDb();
+ const v = await getConnection().query('SHOW server_version').then(x => x[0].server_version);
+ dbLogger.succ(`Connected: v${v}`);
+ } catch (e) {
+ dbLogger.error('Cannot connect', null, true);
+ dbLogger.error(e);
+ process.exit(1);
+ }
+}
+
+async function validatePort(config: Config): Promise<void> {
+ const isWellKnownPort = (port: number) => port < 1024;
+
+ async function isPortAvailable(port: number): Promise<boolean> {
+ return await portscanner.checkPortStatus(port, '127.0.0.1') === 'closed';
+ }
+
+ if (config.port == null || Number.isNaN(config.port)) {
+ bootLogger.error('The port is not configured. Please configure port.', null, true);
+ process.exit(1);
+ }
+
+ if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) {
+ bootLogger.error('You need root privileges to listen on well-known port on Linux', null, true);
+ process.exit(1);
+ }
+
+ if (!await isPortAvailable(config.port)) {
+ bootLogger.error(`Port ${config.port} is already in use`, null, true);
+ process.exit(1);
+ }
+}
+
+async function spawnWorkers(limit: number = 1) {
+ const workers = Math.min(limit, os.cpus().length);
+ bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`);
+ await Promise.all([...Array(workers)].map(spawnWorker));
+ bootLogger.succ('All workers started');
+}
+
+function spawnWorker(): Promise<void> {
+ return new Promise(res => {
+ const worker = cluster.fork();
+ worker.on('message', message => {
+ if (message !== 'ready') return;
+ res();
+ });
+ });
+}
diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts
new file mode 100644
index 0000000000..362fa3f26b
--- /dev/null
+++ b/packages/backend/src/boot/worker.ts
@@ -0,0 +1,20 @@
+import * as cluster from 'cluster';
+import { initDb } from '../db/postgre';
+
+/**
+ * Init worker process
+ */
+export async function workerMain() {
+ await initDb();
+
+ // start server
+ await require('../server').default();
+
+ // start job queue
+ require('../queue').default();
+
+ if (cluster.isWorker) {
+ // Send a 'ready' message to parent process
+ process.send!('ready');
+ }
+}