UNPKG

@clusterio/host

Version:

Implementation of Clusterio host server

312 lines 12.2 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HostConnector = void 0; exports.bootstrap = bootstrap; /** * Clusterio host * * Connects to the controller and hosts Factorio servers that can * communicate with the cluster. It is remotely controlled by {@link * module:controller/controller}. * * @module host/host * @author Danielv123, Hornwitser * @example * npx clusteriohost run */ const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const yargs_1 = __importDefault(require("yargs")); const set_blocking_1 = __importDefault(require("set-blocking")); const package_json_1 = require("./package.json"); const winston_1 = __importDefault(require("winston")); require("winston-daily-rotate-file"); // internal libraries const lib = __importStar(require("@clusterio/lib")); const lib_1 = require("@clusterio/lib"); const Host_1 = __importDefault(require("./src/Host")); let host; void new lib.Gauge("clusterio_host_pending_requests", "Count of pending link requests currently waiting in memory on the host.", { labels: ["host_id"], callback: (gauge) => { if (!host) { return; } let count = host.pendingRequestCount; for (const connection of host.instanceConnections.values()) { count += connection.pendingRequestCount; count += connection.instance.pendingRequestCount; } gauge.labels(String(host.config.get("host.id"))).set(count); }, }); class HostConnector extends lib.WebSocketClientConnector { hostConfig; pluginInfos; constructor(hostConfig, tlsCa, pluginInfos) { super(hostConfig.get("host.controller_url"), hostConfig.get("host.max_reconnect_delay"), tlsCa); this.hostConfig = hostConfig; this.pluginInfos = pluginInfos; } register() { lib_1.logger.info("Connector | registering host"); let plugins = {}; for (let pluginInfo of this.pluginInfos) { plugins[pluginInfo.name] = pluginInfo.version; } this.sendHandshake(new lib.MessageRegisterHost(new lib.RegisterHostData(this.hostConfig.get("host.controller_token"), package_json_1.version, this.hostConfig.get("host.id"), plugins))); } } exports.HostConnector = HostConnector; async function startHost() { // argument parsing // eslint-disable-next-line node/no-sync const args = yargs_1.default .scriptName("host") .usage("$0 <command> [options]") .option("log-level", { nargs: 1, describe: "Log level to print to stdout", default: "info", choices: ["none"].concat(Object.keys(lib_1.levels)), type: "string", }) .option("log-directory", { nargs: 1, describe: "Directory to place logs in", default: "logs", type: "string", }) .option("config", { nargs: 1, describe: "host config file to use", default: "config-host.json", type: "string", }) .option("plugin-list", { nargs: 1, describe: "File containing list of plugins available with their install path", default: "plugin-list.json", type: "string", }) .command("plugin", "Manage available plugins", lib.pluginCommand) .command("config", "Manage Host config", lib.configCommand) .command("run", "Run host", yargs => { yargs.option("can-restart", { type: "boolean", nargs: 0, default: false, describe: "Indicate that a process monitor will restart this host on failure", }); yargs.option("recovery", { type: "boolean", nargs: 0, default: false, describe: "Start the host in recovery mode with all plugins disabled and controller disconnected", }); }) .demandCommand(1, "You need to specify a command to run") .strict() .parseSync(); lib_1.logger.add(new winston_1.default.transports.DailyRotateFile({ format: winston_1.default.format.json(), filename: "host-%DATE%.log", utc: true, dirname: path_1.default.join(args.logDirectory, "host"), })); if (args.logLevel !== "none") { lib_1.logger.add(new lib_1.ConsoleTransport({ level: args.logLevel, format: new lib.TerminalFormat(), filter: (info) => info.instance_id === undefined, })); } lib.handleUnhandledErrors(); let command = args._[0]; if (command === "run") { lib_1.logger.info(`Starting Clusterio host ${package_json_1.version}`); if (args.recovery) { lib_1.logger.warn("Host recovery mode enabled. Some features will be disabled."); } } lib_1.logger.info(`Loading available plugins from ${args.pluginList}`); let pluginList = await lib.loadPluginList(args.pluginList); // If the command is plugin management we don't try to load plugins if (command === "plugin") { await lib.handlePluginCommand(args, pluginList, args.pluginList); return; } lib_1.logger.info("Loading Plugin info"); let pluginInfos = await lib.loadPluginInfos(pluginList); lib.registerPluginMessages(pluginInfos); lib.addPluginConfigFields(pluginInfos); let hostConfig; const hostConfigPath = args.config; lib_1.logger.info(`Loading config from ${hostConfigPath}`); try { hostConfig = await lib.HostConfig.fromFile("host", hostConfigPath); } catch (err) { if (err.code === "ENOENT") { lib_1.logger.info("Config not found, initializing new config"); hostConfig = new lib.HostConfig("host", undefined, hostConfigPath); } else { throw new lib.StartupError(`Failed to load ${args.config}: ${err.stack ?? err.message ?? err}`); } } hostConfig.set("host.version", package_json_1.version); // Allows tracking last loaded version if (command === "config") { await lib.handleConfigCommand(args, hostConfig); return; } // If we get here the command was run await fs_extra_1.default.ensureDir(hostConfig.get("host.instances_directory")); await fs_extra_1.default.ensureDir(hostConfig.get("host.mods_directory")); await fs_extra_1.default.ensureDir("modules"); // Set the process title, shows up as the title of the CMD window on windows // and as the process name in ps/top on linux. process.title = "clusteriohost"; // make sure we have the controller access token if (hostConfig.get("host.controller_token") === "enter token here") { lib_1.logger.fatal("ERROR invalid config!"); lib_1.logger.fatal("Controller requires an access token for socket operations. As clusterio\n" + "hosts depends upon this, please set your token using the command npx\n" + "clusteriohost config set host.controller_token <token>. You can generate an\n" + "auth token using npx clusterioctl generate-host-token."); process.exitCode = 1; return; } // make sure url ends with / if (!hostConfig.get("host.controller_url").endsWith("/")) { lib_1.logger.fatal("ERROR invalid config!"); lib_1.logger.fatal("host.controller_url must end with '/'"); process.exitCode = 1; return; } let tlsCa; let tlsCaPath = hostConfig.get("host.tls_ca"); if (tlsCaPath) { tlsCa = await fs_extra_1.default.readFile(tlsCaPath, "utf8"); } let hostConnector = new HostConnector(hostConfig, tlsCa, pluginInfos); host = new Host_1.default(hostConnector, hostConfig, tlsCa, pluginInfos, Boolean(args.canRestart), Boolean(args.recovery), ...await Host_1.default.bootstrap(hostConfig)); // Handle interrupts let secondSigint = false; process.on("SIGINT", () => { if (secondSigint) { (0, set_blocking_1.default)(true); lib_1.logger.fatal("Caught second interrupt, terminating immediately"); process.exit(1); } secondSigint = true; lib_1.logger.info("Caught interrupt signal, shutting down"); host.shutdown(); }); let secondSigterm = false; process.on("SIGTERM", () => { if (secondSigterm) { (0, set_blocking_1.default)(true); lib_1.logger.fatal("Caught second termination, terminating immediately"); process.exit(1); } secondSigterm = true; lib_1.logger.info("Caught termination signal, shutting down"); host.shutdown(); }); process.on("SIGHUP", () => { lib_1.logger.info("Terminal closed, shutting down"); host.shutdown(); }); try { await host.loadPlugins(); } catch (err) { await host.shutdown(); throw err; } hostConnector.once("connect", () => { lib_1.logger.add(new lib.LinkTransport({ link: host })); }); await hostConnector.connect(); lib_1.logger.info("Started host"); } function bootstrap() { // eslint-disable-next-line no-console console.warn(` +==========================================================+ I WARNING: This is the development branch for the 2.0 I I version of clusterio. Expect things to break. I +==========================================================+ `); startHost().catch(err => { if (err instanceof lib.WebSocketError) { // Already handled by connector.on("error") within Host } else if (err instanceof lib.StartupError) { lib_1.logger.fatal(` +----------------------------------+ | Unable to to start clusteriohost | +----------------------------------+ ${err.stack}`); } else if (err instanceof lib.PluginError) { lib_1.logger.fatal(` ${err.pluginName} plugin threw an unexpected error during startup, please report it to the plugin author. ------------------------------------------------------ ${err.original.stack}`); } else { lib_1.logger.fatal(` +------------------------------------------------------------+ | Unexpected error occured while starting host, please | | report it to https://github.com/clusterio/clusterio/issues | +------------------------------------------------------------+ ${err.stack}`); } if (err instanceof lib.AuthenticationFailed) { process.exitCode = 8; } else { process.exitCode = 1; } }); } if (module === require.main) { bootstrap(); } //# sourceMappingURL=host.js.map