mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 15:24:52 +09:00
Hosts join the cluster by running the installer, which shows a code and waits until the request is approved under Hosts > Add host in the web interface or with clusterioctl host approve. An admin can pass their user token to the installer to approve the host right away. The token is only used for that and is not saved. The controller only accepts hosts that have a record, and only approving a host creates one. Revoking a host deletes its record, so its token can no longer be used to reconnect. Hosts that have connected before keep working. Removes host generate-token, revoke-token and create-config from clusterioctl, bootstrap generate-host-token, the host token messages and the core.host.generate_token, revoke_token and create_config permissions, which are also stripped from stored roles. Adds clusterioctl host pending, approve, reject and revoke, and the core.host.approve and core.host.revoke permissions. Standalone installs add the host to the controller database directly, since the controller is not running during the install.
513 lines
14 KiB
TypeScript
513 lines
14 KiB
TypeScript
import { Type, Static } from "@sinclair/typebox";
|
|
import { JsonNumber, StringEnum, StringKey, jsonArray, plainJson } from "./composites.js";
|
|
import { type LogEntry, levels } from "../logging.js";
|
|
import { ControllerConfig } from "../config/index.js";
|
|
import { ExternalFactorioVersionSchema, LatestReleasesSchema } from "../external/index.js";
|
|
import { PluginDetails } from "./messages_plugin.js";
|
|
import HostPairingDetails from "./HostPairingDetails.js";
|
|
|
|
const LogEntrySchema = Type.Object({
|
|
"level": StringKey(levels),
|
|
"message": Type.String(),
|
|
"timestamp": Type.String(),
|
|
});
|
|
|
|
export class ControllerStopRequest {
|
|
declare ["constructor"]: typeof ControllerStopRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.stop" as const;
|
|
}
|
|
|
|
export class ControllerRestartRequest {
|
|
declare ["constructor"]: typeof ControllerRestartRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.restart" as const;
|
|
}
|
|
|
|
export class ControllerMarkRestartRequiredRequest {
|
|
declare ["constructor"]: typeof ControllerMarkRestartRequiredRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.restart" as const;
|
|
}
|
|
|
|
export class ControllerUpdateRequest {
|
|
declare ["constructor"]: typeof ControllerUpdateRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.update" as const;
|
|
}
|
|
|
|
export class ControllerConfigGetRequest {
|
|
declare ["constructor"]: typeof ControllerConfigGetRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.get_config" as const;
|
|
static Response = plainJson(ControllerConfig.jsonSchema);
|
|
}
|
|
|
|
export class ControllerConfigSetRequest {
|
|
declare ["constructor"]: typeof ControllerConfigSetRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.update_config" as const;
|
|
|
|
constructor(
|
|
public fields: Record<string, string | Record<string, unknown>>,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"fields": Type.Record(Type.String(), Type.Union([
|
|
Type.String(), Type.Record(Type.String(), Type.Unknown()),
|
|
])),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.fields);
|
|
}
|
|
}
|
|
|
|
export class ControllerConfigSetFieldRequest {
|
|
declare ["constructor"]: typeof ControllerConfigSetFieldRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.update_config" as const;
|
|
|
|
constructor(
|
|
public field: string,
|
|
public value: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"field": Type.String(),
|
|
"value": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.field, json.value);
|
|
}
|
|
}
|
|
|
|
export class ControllerConfigSetPropRequest {
|
|
declare ["constructor"]: typeof ControllerConfigSetPropRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.controller.update_config" as const;
|
|
|
|
constructor(
|
|
public field: string,
|
|
public prop: string,
|
|
public value?: unknown,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"field": Type.String(),
|
|
"prop": Type.String(),
|
|
"value": Type.Optional(Type.Unknown()),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.field, json.prop, json.value);
|
|
}
|
|
}
|
|
|
|
export class HostPairingListRequest {
|
|
declare ["constructor"]: typeof HostPairingListRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.host.approve" as const;
|
|
static Response = jsonArray(HostPairingDetails);
|
|
}
|
|
|
|
export class HostPairingUpdatesEvent {
|
|
declare ["constructor"]: typeof HostPairingUpdatesEvent;
|
|
static type = "event" as const;
|
|
static src = "controller" as const;
|
|
static dst = "control" as const;
|
|
static permission = "core.host.approve" as const;
|
|
|
|
constructor(
|
|
public updates: HostPairingDetails[],
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"updates": Type.Array(HostPairingDetails.jsonSchema),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.updates.map(update => HostPairingDetails.fromJSON(update)));
|
|
}
|
|
}
|
|
|
|
export class HostPairingApproveRequest {
|
|
declare ["constructor"]: typeof HostPairingApproveRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.host.approve" as const;
|
|
|
|
constructor(
|
|
/** Code of the pending request, case and dashes are ignored. */
|
|
public code: string,
|
|
/** Name to give the host instead of the one it asked for. */
|
|
public name?: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"code": Type.String(),
|
|
"name": Type.Optional(Type.String()),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.code, json.name);
|
|
}
|
|
|
|
/** Id of the host created. */
|
|
static Response = JsonNumber;
|
|
}
|
|
|
|
export class HostPairingRejectRequest {
|
|
declare ["constructor"]: typeof HostPairingRejectRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.host.approve" as const;
|
|
|
|
constructor(
|
|
/** Code of the pending request, case and dashes are ignored. */
|
|
public code: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"code": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.code);
|
|
}
|
|
}
|
|
|
|
export class GetFactorioCredentialsRequest {
|
|
declare ["constructor"]: typeof GetFactorioCredentialsRequest;
|
|
static type = "request" as const;
|
|
static src = ["host", "instance"] as const;
|
|
static dst = "controller" as const;
|
|
|
|
static Response = plainJson(Type.Object({
|
|
"username": Type.Optional(Type.String()),
|
|
"token": Type.Optional(Type.String()),
|
|
}));
|
|
}
|
|
|
|
export class LogSetSubscriptionsRequest {
|
|
declare ["constructor"]: typeof LogSetSubscriptionsRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.log.follow" as const;
|
|
|
|
constructor(
|
|
public all?: boolean,
|
|
public controller?: boolean,
|
|
public hostIds?: number[],
|
|
public instanceIds?: number[],
|
|
public maxLevel?: keyof typeof levels,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
all: Type.Optional(Type.Boolean()),
|
|
controller: Type.Optional(Type.Boolean()),
|
|
hostIds: Type.Array(Type.Integer()),
|
|
instanceIds: Type.Array(Type.Integer()),
|
|
maxLevel: Type.Optional(StringEnum(Object.keys(levels) as (keyof typeof levels)[])),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel);
|
|
}
|
|
}
|
|
|
|
export class LogQueryRequest {
|
|
declare ["constructor"]: typeof LogQueryRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.log.query" as const;
|
|
|
|
constructor(
|
|
public all: boolean,
|
|
public controller: boolean,
|
|
public hostIds: number[],
|
|
public instanceIds: number[],
|
|
public maxLevel: undefined | keyof typeof levels,
|
|
public limit: number,
|
|
public order: "asc" | "desc",
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
all: Type.Boolean(),
|
|
controller: Type.Boolean(),
|
|
hostIds: Type.Array(Type.Integer()),
|
|
instanceIds: Type.Array(Type.Integer()),
|
|
maxLevel: Type.Optional(StringEnum(Object.keys(levels) as (keyof typeof levels)[])),
|
|
limit: Type.Integer(),
|
|
order: StringEnum(["asc", "desc"]),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(
|
|
json.all, json.controller, json.hostIds, json.instanceIds, json.maxLevel, json.limit, json.order
|
|
);
|
|
}
|
|
|
|
static Response = class Response {
|
|
constructor(
|
|
public log: LogEntry[],
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"log": Type.Array(LogEntrySchema),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.log);
|
|
}
|
|
};
|
|
}
|
|
|
|
export class LogMessageEvent {
|
|
declare ["constructor"]: typeof LogMessageEvent;
|
|
static type = "event" as const;
|
|
static src = ["host", "controller"] as const;
|
|
static dst = ["controller", "control"] as const;
|
|
|
|
constructor(
|
|
public info: LogEntry,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"info": LogEntrySchema,
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.info);
|
|
}
|
|
}
|
|
|
|
export class SystemInfo {
|
|
constructor(
|
|
/**
|
|
* Id of the host these metrics originate from, or the string
|
|
* "controller" if these metrics are for the controller.
|
|
*/
|
|
public id: number | "controller",
|
|
public hostname: string,
|
|
public node: string,
|
|
public kernel: string,
|
|
public machine: string,
|
|
public cpuModel: string,
|
|
public coreRatios: number[],
|
|
public memoryCapacity: number,
|
|
public memoryAvailable: number,
|
|
public diskCapacity: number,
|
|
public diskAvailable: number,
|
|
public canRestart: boolean,
|
|
public restartRequired: boolean,
|
|
public systemStartedAtMs: number,
|
|
public processStartedAtMs: number,
|
|
/** Millisecond Unix timestamp this entry was last updated at */
|
|
public updatedAtMs: number,
|
|
public isDeleted: boolean,
|
|
/** Plugins on the machine when last collected, kept while it is offline */
|
|
public plugins?: PluginDetails[],
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"id": Type.Union([Type.Number(), Type.Literal("controller")]),
|
|
"hostname": Type.String(),
|
|
"node": Type.String(),
|
|
"kernel": Type.String(),
|
|
"machine": Type.String(),
|
|
"cpuModel": Type.String(),
|
|
"coreRatios": Type.Array(Type.Number()),
|
|
"memoryCapacity": Type.Number(),
|
|
"memoryAvailable": Type.Number(),
|
|
"diskCapacity": Type.Number(),
|
|
"diskAvailable": Type.Number(),
|
|
"canRestart": Type.Boolean(),
|
|
"restartRequired": Type.Boolean(),
|
|
"systemStartedAtMs": Type.Number(),
|
|
"processStartedAtMs": Type.Number(),
|
|
"updatedAtMs": Type.Number(),
|
|
"isDeleted": Type.Boolean(),
|
|
"plugins": Type.Optional(Type.Array(PluginDetails.jsonSchema)),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(
|
|
json.id,
|
|
json.hostname,
|
|
json.node,
|
|
json.kernel,
|
|
json.machine,
|
|
json.cpuModel,
|
|
json.coreRatios,
|
|
json.memoryCapacity,
|
|
json.memoryAvailable,
|
|
json.diskCapacity,
|
|
json.diskAvailable,
|
|
json.canRestart,
|
|
json.restartRequired,
|
|
json.systemStartedAtMs,
|
|
json.processStartedAtMs,
|
|
json.updatedAtMs,
|
|
json.isDeleted,
|
|
json.plugins?.map(plugin => PluginDetails.fromJSON(plugin)),
|
|
);
|
|
}
|
|
|
|
get cpuCapacity() {
|
|
return this.coreRatios.length;
|
|
}
|
|
|
|
get cpuUsed() {
|
|
return this.coreRatios.reduce((a, b) => a + b, 0);
|
|
}
|
|
|
|
get cpuAvailable() {
|
|
return this.cpuCapacity - this.cpuUsed;
|
|
}
|
|
|
|
get cpuRatio() {
|
|
return this.cpuUsed / this.cpuCapacity;
|
|
}
|
|
|
|
get memoryUsed() {
|
|
return this.memoryCapacity - this.memoryAvailable;
|
|
}
|
|
|
|
get memoryRatio() {
|
|
return (this.memoryCapacity - this.memoryAvailable) / this.memoryCapacity;
|
|
}
|
|
|
|
get diskUsed() {
|
|
return this.diskCapacity - this.diskAvailable;
|
|
}
|
|
|
|
get diskRatio() {
|
|
return (this.diskCapacity - this.diskAvailable) / this.diskCapacity;
|
|
}
|
|
}
|
|
|
|
export class SystemInfoRequest {
|
|
declare ["constructor"]: typeof SystemInfoRequest;
|
|
static type = "request" as const;
|
|
static src = "controller" as const;
|
|
static dst = "host" as const;
|
|
static Response = SystemInfo;
|
|
}
|
|
|
|
export class SystemInfoUpdateEvent {
|
|
declare ["constructor"]: typeof SystemInfoUpdateEvent;
|
|
static type = "event" as const;
|
|
static src = "controller" as const;
|
|
static dst = "control" as const;
|
|
static permission = "core.system.subscribe" as const;
|
|
|
|
constructor(
|
|
public updates: SystemInfo[],
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"updates": Type.Array(SystemInfo.jsonSchema),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.updates.map(update => SystemInfo.fromJSON(update)));
|
|
}
|
|
}
|
|
|
|
export class DebugDumpWsRequest {
|
|
declare ["constructor"]: typeof DebugDumpWsRequest;
|
|
static type = "request" as const;
|
|
static src = "control" as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.debug.dump_ws" as const;
|
|
}
|
|
|
|
export class DebugWsMessageEvent {
|
|
declare ["constructor"]: typeof DebugWsMessageEvent;
|
|
static type = "event" as const;
|
|
static src = "controller" as const;
|
|
static dst = "control" as const;
|
|
|
|
constructor(
|
|
public direction: string,
|
|
public content: string,
|
|
) { }
|
|
|
|
static jsonSchema = Type.Object({
|
|
"direction": Type.String(),
|
|
"content": Type.String(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.direction, json.content);
|
|
}
|
|
}
|
|
|
|
export class FactorioVersionsRequest {
|
|
declare ["constructor"]: typeof FactorioVersionsRequest;
|
|
static type = "request" as const;
|
|
static src = ["control", "host", "instance"] as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.external.get_factorio_versions" as const;
|
|
|
|
constructor(
|
|
public maxAgeMs: number = 5 * 60 * 1000, // Default 5 minutes
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"maxAgeMs": Type.Number(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.maxAgeMs);
|
|
}
|
|
|
|
static Response = plainJson(Type.Array(ExternalFactorioVersionSchema));
|
|
}
|
|
|
|
export class LatestReleasesRequest {
|
|
declare ["constructor"]: typeof LatestReleasesRequest;
|
|
static type = "request" as const;
|
|
static src = ["control", "host", "instance"] as const;
|
|
static dst = "controller" as const;
|
|
static permission = "core.external.get_latest_releases" as const;
|
|
|
|
constructor(
|
|
public maxAgeMs: number = 5 * 60 * 1000, // Default 5 minutes
|
|
) {}
|
|
|
|
static jsonSchema = Type.Object({
|
|
"maxAgeMs": Type.Number(),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
return new this(json.maxAgeMs);
|
|
}
|
|
|
|
static Response = plainJson(LatestReleasesSchema);
|
|
}
|