Refactor: update action with docker

This commit is contained in:
Unitwk 2024-05-10 17:23:57 +08:00
parent ef63ae4528
commit 1349d31042
7 changed files with 422 additions and 326 deletions

View File

@ -1,6 +1,6 @@
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import { DockerProcessAdapter } from "./docker_start";
import { DockerProcessAdapter } from "../../../service/docker_process_service";
export default class DockerResizeCommand extends InstanceCommand {
constructor() {

View File

@ -6,87 +6,13 @@ import logger from "../../../service/log";
import { EventEmitter } from "events";
import { IInstanceProcess } from "../../instance/interface";
import fs from "fs-extra";
import { commandStringToArray } from "../base/command_parser";
import path from "path";
import { t } from "i18next";
import DockerPullCommand from "./docker_pull";
import os from "os";
// user identity function
const processUserUid = process.getuid ? process.getuid : () => 0;
const processGroupGid = process.getgid ? process.getgid : () => 0;
// Error exception at startup
class StartupDockerProcessError extends Error {
constructor(msg: string) {
super(msg);
}
}
interface IDockerProcessAdapterStartParam {
isTty: boolean;
h: number;
w: number;
}
// process adapter
export class DockerProcessAdapter extends EventEmitter implements IInstanceProcess {
pid?: number | string;
private stream?: NodeJS.ReadWriteStream;
constructor(public container: Docker.Container) {
super();
}
// Once the program is actually started, no errors can block the next startup process
public async start(param: IDockerProcessAdapterStartParam) {
try {
await this.container.start();
const { isTty, h, w } = param;
if (isTty) {
this.container.resize({ h, w });
}
this.pid = this.container.id;
const stream = (this.stream = await this.container.attach({
stream: true,
stdout: true,
stderr: true,
stdin: true
}));
stream.on("data", (data) => this.emit("data", data));
stream.on("error", (data) => this.emit("data", data));
this.wait();
} catch (error: any) {
this.kill();
throw error;
}
}
public write(data?: string) {
if (this.stream && data) this.stream.write(data);
}
public async kill(s?: string) {
await this.container.kill();
return true;
}
public async destroy() {
try {
await this.container.remove();
} catch (error: any) {}
}
private wait() {
this.container.wait(async (v) => {
await this.destroy();
this.emit("exit", v);
});
}
}
import {
DockerProcessAdapter,
SetupDockerContainer,
StartupDockerProcessError
} from "../../../service/docker_process_service";
export default class DockerStartCommand extends InstanceCommand {
constructor() {
@ -106,146 +32,13 @@ export default class DockerStartCommand extends InstanceCommand {
throw error;
}
// Command text parsing
let commandList: string[] = [];
if (instance.config.startCommand.trim()) {
commandList = commandStringToArray(instance.config.startCommand);
} else {
commandList = [];
}
const cwd = instance.absoluteCwdPath();
// Parsing port open
// 25565:25565/tcp 8080:8080/tcp
const portMap = instance.config.docker.ports || [];
const publicPortArray: any = {};
const exposedPorts: any = {};
for (const iterator of portMap) {
const elem = iterator.split("/");
if (elem.length != 2) throw new Error(t("TXT_CODE_1cf6fc4b"));
const ports = elem[0];
const protocol = elem[1];
//Host (host) port: container port
const publicAndPrivatePort = ports.split(":");
if (publicAndPrivatePort.length != 2) throw new Error(t("TXT_CODE_2029027e"));
publicPortArray[`${publicAndPrivatePort[1]}/${protocol}`] = [
{ HostPort: publicAndPrivatePort[0] }
];
exposedPorts[`${publicAndPrivatePort[1]}/${protocol}`] = {};
}
// resolve extra path mounts
const extraVolumes = instance.config.docker.extraVolumes || [];
const extraBinds: { hostPath: string; containerPath: string }[] = [];
for (const item of extraVolumes) {
if (!item) throw new Error($t("TXT_CODE_ae441ea3"));
const paths = item.split("|");
if (paths.length < 2) throw new Error($t("TXT_CODE_dca030b8"));
const hostPath = path.normalize(paths[0]);
const containerPath = path.normalize(paths[1]);
extraBinds.push({ hostPath, containerPath });
}
// memory limit
let maxMemory = undefined;
if (instance.config.docker.memory) maxMemory = instance.config.docker.memory * 1024 * 1024;
// CPU usage calculation
let cpuQuota = undefined;
let cpuPeriod = undefined;
if (instance.config.docker.cpuUsage) {
cpuQuota = instance.config.docker.cpuUsage * 10 * 1000;
cpuPeriod = 1000 * 1000;
}
// Check the number of CPU cores
let cpusetCpus = undefined;
if (instance.config.docker.cpusetCpus) {
const arr = instance.config.docker.cpusetCpus.split(",");
arr.forEach((v) => {
if (isNaN(Number(v))) throw new Error($t("TXT_CODE_instance.invalidCpu", { v }));
});
cpusetCpus = instance.config.docker.cpusetCpus;
// Note: check
}
// container name check
let containerName =
instance.config.docker.containerName || `MCSM-${instance.instanceUuid.slice(0, 6)}`;
if (containerName && (containerName.length > 64 || containerName.length < 2)) {
throw new Error($t("TXT_CODE_instance.invalidContainerName", { v: containerName }));
}
// Whether to use TTY mode
const isTty = instance.config.terminalOption.pty;
const workingDir = instance.config.docker.workingDir ?? "/workspace/";
// output startup log
logger.info("----------------");
logger.info(`Session ${source}: Request to start an instance`);
logger.info(`UUID: [${instance.instanceUuid}] [${instance.config.nickname}]`);
logger.info(`NAME: [${containerName}]`);
logger.info(`COMMAND: ${commandList.join(" ")}`);
logger.info(`CWD: ${cwd}, WORKING_DIR: ${workingDir}`);
logger.info(`NET_MODE: ${instance.config.docker.networkMode}`);
logger.info(`OPEN_PORT: ${JSON.stringify(publicPortArray)}`);
logger.info(`BINDS: ${JSON.stringify([`${cwd}->${workingDir}`, ...extraBinds])}`);
logger.info(`NET_ALIASES: ${JSON.stringify(instance.config.docker.networkAliases)}`);
logger.info(`MEM_LIMIT: ${maxMemory || "--"} MB`);
logger.info(`TYPE: Docker Container`);
logger.info("----------------");
// Start Docker container creation and running
const docker = new Docker();
const container = await docker.createContainer({
name: containerName,
Hostname: containerName,
Image: instance.config.docker.image,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: isTty,
WorkingDir: workingDir,
Cmd: commandList ? commandList : undefined,
OpenStdin: true,
StdinOnce: false,
ExposedPorts: exposedPorts,
Env: instance.config.docker?.env || [],
HostConfig: {
Memory: maxMemory,
AutoRemove: true,
CpusetCpus: cpusetCpus,
CpuPeriod: cpuPeriod,
CpuQuota: cpuQuota,
PortBindings: publicPortArray,
NetworkMode: instance.config.docker.networkMode,
Mounts: [
{
Type: "bind",
Source: cwd,
Target: workingDir
},
...extraBinds.map((v) => {
return {
Type: "bind" as Docker.MountType,
Source: v.hostPath,
Target: v.containerPath
};
})
]
},
NetworkingConfig: {
EndpointsConfig: {
[instance.config.docker.networkMode || "bridge"]: {
Aliases: instance.config.docker.networkAliases
}
}
}
});
const containerWrapper = new SetupDockerContainer(instance);
await containerWrapper.start();
// Docker docks to the process adapter
const processAdapter = new DockerProcessAdapter(container);
const isTty = instance.config.terminalOption.pty;
const workingDir = instance.config.docker.workingDir ?? "/workspace/";
const processAdapter = new DockerProcessAdapter(containerWrapper.getContainer());
await processAdapter.start({
isTty,
w: instance.config.terminalOption.ptyWindowCol,

View File

@ -1,93 +1,17 @@
import { $t } from "../../../i18n";
import { killProcess } from "common";
import { ChildProcess, ChildProcessWithoutNullStreams, exec, spawn } from "child_process";
import { ChildProcess } from "child_process";
import logger from "../../../service/log";
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import { commandStringToArray } from "../base/command_parser";
import iconv from "iconv-lite";
import { AsyncTask, IAsyncTaskJSON } from "../../../service/async_task_service";
export class InstanceUpdateAction extends AsyncTask {
public pid?: number;
public process?: ChildProcessWithoutNullStreams;
constructor(public readonly instance: Instance) {
super();
}
public async onStart() {
let updateCommand = this.instance.config.updateCommand;
updateCommand = updateCommand.replace(/\{mcsm_workspace\}/gm, this.instance.config.cwd);
logger.info(
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
);
logger.info(
$t("TXT_CODE_general_update.updateCmd", { instanceUuid: this.instance.instanceUuid })
);
logger.info(updateCommand);
this.instance.println(
$t("TXT_CODE_general_update.update"),
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
);
// command parsing
const commandList = commandStringToArray(updateCommand);
const commandExeFile = commandList[0];
const commandParameters = commandList.slice(1);
if (commandList.length === 0) {
return this.instance.failure(new Error($t("TXT_CODE_general_update.cmdFormatErr")));
}
// start the update command
const process = spawn(commandExeFile, commandParameters, {
cwd: this.instance.config.cwd,
stdio: "pipe",
windowsHide: true
});
if (!process || !process.pid) {
return this.instance.println(
$t("TXT_CODE_general_update.err"),
$t("TXT_CODE_general_update.updateFailed")
);
}
// process & pid
this.pid = process.pid;
this.process = process;
process.stdout.on("data", (text) => {
this.instance.print(iconv.decode(text, this.instance.config.oe));
});
process.stderr.on("data", (text) => {
this.instance.print(iconv.decode(text, this.instance.config.oe));
});
process.on("exit", (code) => {
if (code === 0) {
this.stop();
} else {
this.error(new Error($t("TXT_CODE_general_update.updateErr")));
}
});
}
public async onStop() {
if (this.pid && this.process) {
killProcess(this.pid, this.process);
}
}
public onError(err: Error): void {}
public toObject(): IAsyncTaskJSON {
throw new Error("Method not implemented.");
}
}
import Docker from "dockerode";
import { SetupDockerContainer } from "../../../service/docker_process_service";
import { InstanceUpdateAction } from "../../../service/instance_update_action";
export default class GeneralUpdateCommand extends InstanceCommand {
private pid?: number;
private process?: ChildProcess;
private container?: Docker.Container;
constructor() {
super("GeneralUpdateCommand");
@ -110,9 +34,18 @@ export default class GeneralUpdateCommand extends InstanceCommand {
instance.asynchronousTask = this;
instance.status(Instance.STATUS_BUSY);
const instanceUpdateAction = new InstanceUpdateAction(instance);
await instanceUpdateAction.start();
await instanceUpdateAction.wait();
if (instance.config.docker.image) {
// Docker Update Command Mode
const containerWrapper = new SetupDockerContainer(instance);
await containerWrapper.start();
await containerWrapper.attach(instance);
await containerWrapper.wait();
} else {
// Host Update Command Mode
const instanceUpdateAction = new InstanceUpdateAction(instance);
await instanceUpdateAction.start();
await instanceUpdateAction.wait();
}
} catch (err: any) {
instance.println(
$t("TXT_CODE_general_update.update"),
@ -136,8 +69,15 @@ export default class GeneralUpdateCommand extends InstanceCommand {
$t("TXT_CODE_general_update.update"),
$t("TXT_CODE_general_update.killProcess")
);
if (instance.config.docker.image) {
try {
await this.container?.kill();
} catch (error) {}
return await this.container?.remove();
}
if (this.pid && this.process) {
killProcess(this.pid, this.process);
return killProcess(this.pid, this.process);
}
}
}

View File

@ -1,15 +1,14 @@
import EventEmitter from "events";
import logger from "../log";
export interface IAsyncTaskJSON {
[key: string]: any;
}
export type IAsyncTaskJSON = any;
export interface IAsyncTask extends EventEmitter {
// The taskId must be complex enough to prevent other users from accessing the information
taskId: string;
type: string;
start(): Promise<boolean | void>;
stop(): Promise<boolean | void>;
start(): Promise<void>;
stop(): Promise<void>;
status(): number;
toObject(): IAsyncTaskJSON;
}
@ -28,35 +27,31 @@ export abstract class AsyncTask extends EventEmitter implements IAsyncTask {
super();
}
public start() {
public async start() {
this._status = AsyncTask.STATUS_RUNNING;
try {
const r = this.onStart();
await this.onStart();
this.emit("started");
return r;
} catch (error: any) {
this.error(error);
return Promise.reject(error);
throw error;
}
}
public stop() {
public async stop() {
if (this._status === AsyncTask.STATUS_STOP) return Promise.resolve();
try {
const r = this.onStop();
return r;
} catch (error) {
return Promise.reject(error);
await this.onStop();
} finally {
if (this._status !== AsyncTask.STATUS_ERROR) this._status = AsyncTask.STATUS_STOP;
this.emit("stopped");
}
}
public error(err: Error) {
public async error(err: Error) {
this._status = AsyncTask.STATUS_ERROR;
logger.error(`AsyncTask - ID: ${this.taskId} TYPE: ${this.type} Error:`, err);
this.onError(err);
await this.onError(err);
this.emit("error", err);
this.stop();
}
@ -78,7 +73,7 @@ export abstract class AsyncTask extends EventEmitter implements IAsyncTask {
public abstract onStart(): Promise<void>;
public abstract onStop(): Promise<void>;
public abstract onError(err: Error): void;
public abstract onError(err: Error): Promise<void>;
public abstract toObject(): IAsyncTaskJSON;
}

View File

@ -5,16 +5,14 @@ import fs from "fs-extra";
import Instance from "../../entity/instance/instance";
import InstanceSubsystem from "../system_instance";
import InstanceConfig from "../../entity/instance/Instance_config";
import { $t, i18next } from "../../i18n";
import { $t } from "../../i18n";
import path from "path";
import { getFileManager } from "../file_router_service";
import { IAsyncTaskJSON, TaskCenter, AsyncTask } from "./index";
import logger from "../log";
import { t } from "i18next";
import type { IJsonData } from "common/global";
import GeneralUpdateCommand, {
InstanceUpdateAction
} from "../../entity/commands/general/general_update";
import { InstanceUpdateAction } from "../instance_update_action";
export class QuickInstallTask extends AsyncTask {
public static TYPE = "QuickInstallTask";
@ -150,8 +148,6 @@ export class QuickInstallTask extends AsyncTask {
} catch (error: any) {}
}
onError(): void {}
toObject(): IAsyncTaskJSON {
return JSON.parse(
JSON.stringify({
@ -163,6 +159,8 @@ export class QuickInstallTask extends AsyncTask {
})
);
}
async onError() {}
}
export function createQuickInstallTask(

View File

@ -0,0 +1,285 @@
import { t } from "i18next";
import { commandStringToArray } from "../entity/commands/base/command_parser";
import DockerPullCommand from "../entity/commands/docker/docker_pull";
import Instance from "../entity/instance/instance";
import path from "path";
import { $t } from "../i18n";
import logger from "./log";
import Docker from "dockerode";
import { EventEmitter } from "stream";
import { IInstanceProcess } from "../entity/instance/interface";
import { AsyncTask } from "./async_task_service";
import iconv from "iconv-lite";
// Error exception at startup
export class StartupDockerProcessError extends Error {
constructor(msg: string) {
super(msg);
}
}
export interface IDockerProcessAdapterStartParam {
isTty: boolean;
h: number;
w: number;
}
export class SetupDockerContainer extends AsyncTask {
private container?: Docker.Container;
constructor(public readonly instance: Instance, public readonly startCommand?: string) {
super();
}
public async onStart() {
const instance = this.instance;
const customCommand = this.startCommand;
// Docker Image check
try {
await instance.forceExec(new DockerPullCommand());
} catch (error: any) {
throw error;
}
// Command text parsing
let commandList: string[] = [];
if (instance.config?.startCommand?.trim() || customCommand?.trim()) {
commandList = commandStringToArray(customCommand ?? instance.config.startCommand);
} else {
commandList = [];
}
const cwd = instance.absoluteCwdPath();
// Parsing port open
// 25565:25565/tcp 8080:8080/tcp
const portMap = instance.config.docker.ports || [];
const publicPortArray: any = {};
const exposedPorts: any = {};
for (const iterator of portMap) {
const elem = iterator.split("/");
if (elem.length != 2) throw new Error($t("TXT_CODE_1cf6fc4b"));
const ports = elem[0];
const protocol = elem[1];
//Host (host) port: container port
const publicAndPrivatePort = ports.split(":");
if (publicAndPrivatePort.length != 2) throw new Error(t("TXT_CODE_2029027e"));
publicPortArray[`${publicAndPrivatePort[1]}/${protocol}`] = [
{ HostPort: publicAndPrivatePort[0] }
];
exposedPorts[`${publicAndPrivatePort[1]}/${protocol}`] = {};
}
// resolve extra path mounts
const extraVolumes = instance.config.docker.extraVolumes || [];
const extraBinds: { hostPath: string; containerPath: string }[] = [];
for (const item of extraVolumes) {
if (!item) throw new Error($t("TXT_CODE_ae441ea3"));
const paths = item.split("|");
if (paths.length < 2) throw new Error($t("TXT_CODE_dca030b8"));
const hostPath = path.normalize(paths[0]);
const containerPath = path.normalize(paths[1]);
extraBinds.push({ hostPath, containerPath });
}
// memory limit
let maxMemory = undefined;
if (instance.config.docker.memory) maxMemory = instance.config.docker.memory * 1024 * 1024;
// CPU usage calculation
let cpuQuota = undefined;
let cpuPeriod = undefined;
if (instance.config.docker.cpuUsage) {
cpuQuota = instance.config.docker.cpuUsage * 10 * 1000;
cpuPeriod = 1000 * 1000;
}
// Check the number of CPU cores
let cpusetCpus = undefined;
if (instance.config.docker.cpusetCpus) {
const arr = instance.config.docker.cpusetCpus.split(",");
arr.forEach((v) => {
if (isNaN(Number(v))) throw new Error($t("TXT_CODE_instance.invalidCpu", { v }));
});
cpusetCpus = instance.config.docker.cpusetCpus;
// Note: check
}
// container name check
let containerName =
instance.config.docker.containerName || `MCSM-${instance.instanceUuid.slice(0, 6)}`;
if (containerName && (containerName.length > 64 || containerName.length < 2)) {
throw new Error($t("TXT_CODE_instance.invalidContainerName", { v: containerName }));
}
// Whether to use TTY mode
const isTty = instance.config.terminalOption.pty;
const workingDir = instance.config.docker.workingDir ?? "/workspace/";
// output startup log
logger.info("----------------");
logger.info(`[SetupDockerContainer]`);
logger.info(`UUID: [${instance.instanceUuid}] [${instance.config.nickname}]`);
logger.info(`NAME: [${containerName}]`);
logger.info(`COMMAND: ${commandList.join(" ")}`);
logger.info(`CWD: ${cwd}, WORKING_DIR: ${workingDir}`);
logger.info(`NET_MODE: ${instance.config.docker.networkMode}`);
logger.info(`OPEN_PORT: ${JSON.stringify(publicPortArray)}`);
logger.info(`BINDS: ${JSON.stringify([`${cwd}->${workingDir}`, ...extraBinds])}`);
logger.info(`NET_ALIASES: ${JSON.stringify(instance.config.docker.networkAliases)}`);
logger.info(`MEM_LIMIT: ${maxMemory || "--"} MB`);
logger.info(`TYPE: Docker Container`);
logger.info("----------------");
// Start Docker container creation and running
const docker = new Docker();
this.container = await docker.createContainer({
name: containerName,
Hostname: containerName,
Image: instance.config.docker.image,
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: isTty,
WorkingDir: workingDir,
Cmd: commandList ? commandList : undefined,
OpenStdin: true,
StdinOnce: false,
ExposedPorts: exposedPorts,
Env: instance.config.docker?.env || [],
HostConfig: {
Memory: maxMemory,
AutoRemove: true,
CpusetCpus: cpusetCpus,
CpuPeriod: cpuPeriod,
CpuQuota: cpuQuota,
PortBindings: publicPortArray,
NetworkMode: instance.config.docker.networkMode,
Mounts: [
{
Type: "bind",
Source: cwd,
Target: workingDir
},
...extraBinds.map((v) => {
return {
Type: "bind" as Docker.MountType,
Source: v.hostPath,
Target: v.containerPath
};
})
]
},
NetworkingConfig: {
EndpointsConfig: {
[instance.config.docker.networkMode || "bridge"]: {
Aliases: instance.config.docker.networkAliases
}
}
}
});
// Listen to events
this.container.wait(async (v) => {
this.stop();
});
}
public async onStop() {
try {
await this.container?.kill();
} catch (error) {}
try {
await this.container?.remove();
} catch (error) {}
}
public getContainer() {
if (!this.container) throw new Error("Function getContainer(): Failed, Container is Null!");
return this.container;
}
public async attach(instance: Instance) {
const outputCode = instance.config.terminalOption.pty ? "utf-8" : instance.config.oe;
const container = this.container;
if (!container) throw new Error("Attach Failed, Container is Null!");
try {
await container.start();
const stream = await container.attach({
stream: true,
stdout: true,
stderr: true,
stdin: true
});
stream.on("data", (text: any) => instance.print(iconv.decode(text, outputCode)));
stream.on("error", (text: any) => instance.print(iconv.decode(text, outputCode)));
await container.wait();
} catch (error) {
container.remove().catch(() => {});
throw error;
}
}
public async onError(err: Error) {}
public toObject() {}
}
// SubProcess adapter for Instance
export class DockerProcessAdapter extends EventEmitter implements IInstanceProcess {
pid?: number | string;
private stream?: NodeJS.ReadWriteStream;
constructor(public container: Docker.Container) {
super();
}
// Once the program is actually started, no errors can block the next startup process
public async start(param: IDockerProcessAdapterStartParam) {
try {
await this.container.start();
const { isTty, h, w } = param;
if (isTty) {
this.container.resize({ h, w });
}
this.pid = this.container.id;
const stream = (this.stream = await this.container.attach({
stream: true,
stdout: true,
stderr: true,
stdin: true
}));
stream.on("data", (data) => this.emit("data", data));
stream.on("error", (data) => this.emit("data", data));
this.wait();
} catch (error: any) {
this.kill();
throw error;
}
}
public write(data?: string) {
if (this.stream && data) this.stream.write(data);
}
public async kill(s?: string) {
await this.container.kill();
return true;
}
public async destroy() {
try {
await this.container.remove();
} catch (error: any) {}
}
private wait() {
this.container.wait(async (v) => {
await this.destroy();
this.emit("exit", v);
});
}
}

View File

@ -0,0 +1,85 @@
import { $t } from "../i18n";
import { killProcess } from "common";
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import logger from "../service/log";
import Instance from "../entity/instance/instance";
import { commandStringToArray } from "../entity/commands/base/command_parser";
import iconv from "iconv-lite";
import { AsyncTask, IAsyncTaskJSON } from "../service/async_task_service";
export class InstanceUpdateAction extends AsyncTask {
public pid?: number;
public process?: ChildProcessWithoutNullStreams;
constructor(public readonly instance: Instance) {
super();
}
public async onStart() {
let updateCommand = this.instance.config.updateCommand;
updateCommand = updateCommand.replace(/\{mcsm_workspace\}/gm, this.instance.config.cwd);
logger.info(
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
);
logger.info(
$t("TXT_CODE_general_update.updateCmd", { instanceUuid: this.instance.instanceUuid })
);
logger.info(updateCommand);
this.instance.println(
$t("TXT_CODE_general_update.update"),
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
);
// command parsing
const commandList = commandStringToArray(updateCommand);
const commandExeFile = commandList[0];
const commandParameters = commandList.slice(1);
if (commandList.length === 0) {
return this.instance.failure(new Error($t("TXT_CODE_general_update.cmdFormatErr")));
}
// start the update command
const process = spawn(commandExeFile, commandParameters, {
cwd: this.instance.config.cwd,
stdio: "pipe",
windowsHide: true
});
if (!process || !process.pid) {
return this.instance.println(
$t("TXT_CODE_general_update.err"),
$t("TXT_CODE_general_update.updateFailed")
);
}
// process & pid
this.pid = process.pid;
this.process = process;
process.stdout.on("data", (text) => {
this.instance.print(iconv.decode(text, this.instance.config.oe));
});
process.stderr.on("data", (text) => {
this.instance.print(iconv.decode(text, this.instance.config.oe));
});
process.on("exit", (code) => {
if (code === 0) {
this.stop();
} else {
this.error(new Error($t("TXT_CODE_general_update.updateErr")));
}
});
}
public async onStop() {
if (this.pid && this.process) {
killProcess(this.pid, this.process);
}
}
public async onError(err: Error) {}
public toObject(): IAsyncTaskJSON {
throw new Error("Method not implemented.");
}
}