@clusterio/host
Version:
Implementation of Clusterio host server
312 lines • 12.2 kB
JavaScript
;
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