updates
This commit is contained in:
382
libraries/deno-commons-1.6.0-mod.ts
Normal file
382
libraries/deno-commons-1.6.0-mod.ts
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
// Compatible with Deno 1.6.0 (works on CentOS 7)
|
||||||
|
// https://play.hatter.me/doc/showDocDetail.jssp?id=8879
|
||||||
|
// https://github.com/denoland/deno/releases/tag/v1.6.0
|
||||||
|
// https://play.hatter.me/doc/showDocDetail.jssp?id=8881
|
||||||
|
// https://play.hatter.me/doc/showDocDetail.jssp?id=8882
|
||||||
|
|
||||||
|
class Term {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
blink(message: string): string {
|
||||||
|
return `\x1b[5m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bold(message: string): string {
|
||||||
|
return `\x1b[1m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
under(message: string): string {
|
||||||
|
return `\x1b[4m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
red(message: string): string {
|
||||||
|
return `\x1b[31m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
green(message: string): string {
|
||||||
|
return `\x1b[32m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
yellow(message: string): string {
|
||||||
|
return `\x1b[33m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
blue(message: string): string {
|
||||||
|
return `\x1b[34m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
pink(message: string): string {
|
||||||
|
return `\x1b[35m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
cyan(message: string): string {
|
||||||
|
return `\x1b[36m${message}\x1b[0m`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const term = new Term();
|
||||||
|
|
||||||
|
function pad(message: string, length: number): string {
|
||||||
|
if (message.length >= length) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
return message + " ".repeat(length - message.length);
|
||||||
|
}
|
||||||
|
const LOGGER_PREFIX_LEN: number = 8;
|
||||||
|
class Logger {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
success(...data: any[]) {
|
||||||
|
this.log(
|
||||||
|
term.bold(term.green(`[${pad("SUCCESS", LOGGER_PREFIX_LEN)}]`)),
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
error(...data: any[]) {
|
||||||
|
this.log(
|
||||||
|
term.bold(term.red(`[${pad("ERROR", LOGGER_PREFIX_LEN)}]`)),
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
warn(...data: any[]) {
|
||||||
|
this.log(
|
||||||
|
term.bold(term.yellow(`[${pad("WARN", LOGGER_PREFIX_LEN)}]`)),
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
warning(...data: any[]) {
|
||||||
|
this.log(
|
||||||
|
term.blink(
|
||||||
|
term.bold(term.yellow(`[${pad("WARN", LOGGER_PREFIX_LEN)}]`)),
|
||||||
|
),
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
info(...data: any[]) {
|
||||||
|
this.log(term.bold(`[${pad("INFO", LOGGER_PREFIX_LEN)}]`), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
debug(...data: any[]) {
|
||||||
|
this.log(`[${pad("DEBUG", LOGGER_PREFIX_LEN)}]`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
log(prefix: string, data: any[]) {
|
||||||
|
const args = [prefix];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
args.push(data[i]);
|
||||||
|
}
|
||||||
|
console.log.apply(console, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const log = new Logger();
|
||||||
|
|
||||||
|
export function getHomeDir(): string | null {
|
||||||
|
if (Deno.build.os === "windows") {
|
||||||
|
const userProfile = Deno.env.get("USERPROFILE");
|
||||||
|
if (userProfile) {
|
||||||
|
return userProfile;
|
||||||
|
}
|
||||||
|
const homeDrive = Deno.env.get("HOMEDRIVE");
|
||||||
|
const homePath = Deno.env.get("HOMEPATH");
|
||||||
|
if (homeDrive && homePath) {
|
||||||
|
return homeDrive + homePath;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Deno.env.get("HOME") || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveFilename(filename: string): string {
|
||||||
|
if (filename.startsWith("~/")) {
|
||||||
|
return getHomeDir() + filename.substring(1);
|
||||||
|
}
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCmdArray(command: string, args?: string[]): string[] {
|
||||||
|
const cmdArray = [command];
|
||||||
|
if (args) {
|
||||||
|
for (const arg of args) {
|
||||||
|
cmdArray.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("Command: ", cmdArray);
|
||||||
|
return cmdArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function execCommandShell(
|
||||||
|
command: string,
|
||||||
|
args?: string[],
|
||||||
|
): Promise<number> {
|
||||||
|
const process = Deno.run({
|
||||||
|
cmd: toCmdArray(command, args),
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
});
|
||||||
|
const status = await process.status();
|
||||||
|
process.close();
|
||||||
|
return status.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProcessOutput {
|
||||||
|
code: number;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
|
||||||
|
constructor(code: number, stdout: string, stderr: string) {
|
||||||
|
this.code = code;
|
||||||
|
this.stdout = stdout;
|
||||||
|
this.stderr = stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSuccess(): ProcessOutput {
|
||||||
|
if (this.code !== 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to execute command, exit code: ${this.code}\n- stdout: ${this.stdout}\n- stderr: ${this.stderr}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStdoutAsStringThenTrim(): string {
|
||||||
|
return this.stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
getStdoutAsJson(): any {
|
||||||
|
return JSON.parse(this.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStderrAsJson(): any {
|
||||||
|
return JSON.parse(this.stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function execCommand(
|
||||||
|
command: string,
|
||||||
|
args?: string[],
|
||||||
|
): Promise<ProcessOutput> {
|
||||||
|
const process = Deno.run({
|
||||||
|
cmd: toCmdArray(command, args),
|
||||||
|
stdout: "piped",
|
||||||
|
stderr: "piped",
|
||||||
|
});
|
||||||
|
|
||||||
|
const { code } = await process.status();
|
||||||
|
const stdout = await process.output();
|
||||||
|
const stderr = await process.stderrOutput();
|
||||||
|
|
||||||
|
return new ProcessOutput(
|
||||||
|
code,
|
||||||
|
new TextDecoder().decode(stdout),
|
||||||
|
new TextDecoder().decode(stderr),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchWithTimeout(
|
||||||
|
input: URL | Request | string,
|
||||||
|
timeout?: number,
|
||||||
|
initCallback?: (init: RequestInit) => RequestInit,
|
||||||
|
): Promise<Response> {
|
||||||
|
const fetchTimeout = timeout || 10000;
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const timeoutHandler = setTimeout(() => {
|
||||||
|
abortController.abort();
|
||||||
|
}, fetchTimeout);
|
||||||
|
let init: RequestInit = {};
|
||||||
|
init.signal = abortController.signal;
|
||||||
|
if (initCallback) {
|
||||||
|
init = initCallback(init);
|
||||||
|
}
|
||||||
|
const response = await fetch(input, init);
|
||||||
|
clearTimeout(timeoutHandler);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sleep(timeoutMillis: number): Promise<void> {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, timeoutMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readFileToString(
|
||||||
|
filename: string,
|
||||||
|
): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
return await Deno.readTextFile(resolveFilename(filename));
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error && e.name == "NotFound") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProcessBar {
|
||||||
|
interval?: number;
|
||||||
|
message: string;
|
||||||
|
constructor(message?: string) {
|
||||||
|
this.message = message || "Processing";
|
||||||
|
}
|
||||||
|
async call<T>(cb: () => Promise<T>, clearLine?: boolean): Promise<T> {
|
||||||
|
this.start();
|
||||||
|
try {
|
||||||
|
return await cb();
|
||||||
|
} finally {
|
||||||
|
this.stop(clearLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write(message: string): void {
|
||||||
|
Deno.stdout.writeSync(new TextEncoder().encode(message));
|
||||||
|
}
|
||||||
|
start(): void {
|
||||||
|
const startMs = new Date().getTime();
|
||||||
|
let count = 0;
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
const dots = ".".repeat(((count++) % 10) + 1);
|
||||||
|
const costMs = new Date().getTime() - startMs;
|
||||||
|
const time = `${Math.floor(costMs / 1000)}s`;
|
||||||
|
this.write(`\r${this.message} ${time} ${dots}\x1b[K`);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
stop(clearLine?: boolean): void {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.write(clearLine ? "\r\x1b[K" : "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Process {
|
||||||
|
user: string;
|
||||||
|
pid: number;
|
||||||
|
cpu: number;
|
||||||
|
mem: number;
|
||||||
|
vsz: number;
|
||||||
|
rss: number;
|
||||||
|
tty: string;
|
||||||
|
stat: string;
|
||||||
|
start: string;
|
||||||
|
time: string;
|
||||||
|
command: string;
|
||||||
|
constructor(
|
||||||
|
user: string,
|
||||||
|
pid: number,
|
||||||
|
cpu: number,
|
||||||
|
mem: number,
|
||||||
|
vsz: number,
|
||||||
|
rss: number,
|
||||||
|
tty: string,
|
||||||
|
stat: string,
|
||||||
|
start: string,
|
||||||
|
time: string,
|
||||||
|
command: string,
|
||||||
|
) {
|
||||||
|
this.user = user;
|
||||||
|
this.pid = pid;
|
||||||
|
this.cpu = cpu;
|
||||||
|
this.mem = mem;
|
||||||
|
this.vsz = vsz;
|
||||||
|
this.rss = rss;
|
||||||
|
this.tty = tty;
|
||||||
|
this.stat = stat;
|
||||||
|
this.start = start;
|
||||||
|
this.time = time;
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseProcessLine(line: string): Process | null {
|
||||||
|
const processMatchRegex =
|
||||||
|
/^\s*([\w+\-_]+)\s+(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\d+)\s+(\d+)\s+([\w\/\\?]+)\s+([\w+<>]+)\s+([\w:]+)\s+([\d:]+)\s+(.*)$/;
|
||||||
|
// "app 3622 0.2 24.0 2932504 453004 ? Sl Jan25 23:04 /usr/lib/jvm/jdk-25/bin/java -Dfastjson.parser.safeMode=true......";
|
||||||
|
// USER PID CPU MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||||
|
const matcher = line.match(processMatchRegex);
|
||||||
|
if (!matcher) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const user = matcher[1];
|
||||||
|
const pid = parseInt(matcher[2]);
|
||||||
|
const cpu = Number(matcher[3]);
|
||||||
|
const mem = Number(matcher[4]);
|
||||||
|
const vsz = parseInt(matcher[5]);
|
||||||
|
const rss = parseInt(matcher[6]);
|
||||||
|
const tty = matcher[7];
|
||||||
|
const stat = matcher[8];
|
||||||
|
const start = matcher[9];
|
||||||
|
const time = matcher[10];
|
||||||
|
const command = matcher[11];
|
||||||
|
return new Process(
|
||||||
|
user,
|
||||||
|
pid,
|
||||||
|
cpu,
|
||||||
|
mem,
|
||||||
|
vsz,
|
||||||
|
rss,
|
||||||
|
tty,
|
||||||
|
stat,
|
||||||
|
start,
|
||||||
|
time,
|
||||||
|
command,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertArg(url: URL | string) {
|
||||||
|
url = url instanceof URL ? url : new URL(url);
|
||||||
|
if (url.protocol !== "file:") {
|
||||||
|
throw new TypeError("Must be a file URL.");
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function posixFromFileUrl(url: URL | string): string {
|
||||||
|
url = assertArg(url);
|
||||||
|
return decodeURIComponent(
|
||||||
|
url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromFileUrl(url: URL | string): string {
|
||||||
|
return posixFromFileUrl(url);
|
||||||
|
}
|
||||||
210
libraries/deno-server-control-1.6.0-mod.ts
Normal file
210
libraries/deno-server-control-1.6.0-mod.ts
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// Compatible with Deno 1.6.0 (works on CentOS 7)
|
||||||
|
// https://play.hatter.me/doc/showDocDetail.jssp?id=8879
|
||||||
|
// https://github.com/denoland/deno/releases/tag/v1.6.0
|
||||||
|
// https://play.hatter.me/doc/showDocDetail.jssp?id=8881
|
||||||
|
// https://play.hatter.me/doc/showDocDetail.jssp?id=8882
|
||||||
|
|
||||||
|
import {
|
||||||
|
execCommand,
|
||||||
|
execCommandShell,
|
||||||
|
fetchWithTimeout,
|
||||||
|
fromFileUrl,
|
||||||
|
log,
|
||||||
|
parseProcessLine,
|
||||||
|
Process,
|
||||||
|
ProcessBar,
|
||||||
|
readFileToString,
|
||||||
|
sleep,
|
||||||
|
} from "https://global.hatter.ink/script/get/@1/deno-commons-1.6.0-mod.ts";
|
||||||
|
|
||||||
|
interface HealthCheck {
|
||||||
|
url?: string;
|
||||||
|
count?: number; // default 30
|
||||||
|
timeoutMillis?: number; // default 1000
|
||||||
|
intervalMillis?: number; // default 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServerControlConfig {
|
||||||
|
appId?: string;
|
||||||
|
startup?: string;
|
||||||
|
healthCheck?: HealthCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listJavaProcesses(
|
||||||
|
serverControlConfig: ServerControlConfig,
|
||||||
|
): Promise<Process[]> {
|
||||||
|
const appId = serverControlConfig.appId;
|
||||||
|
const processOutput = await execCommand("ps", ["aux"]);
|
||||||
|
processOutput.assertSuccess();
|
||||||
|
const stdout = processOutput.getStdoutAsStringThenTrim();
|
||||||
|
const lines = stdout.split("\n");
|
||||||
|
const processes = lines.filter((line) => {
|
||||||
|
return line.includes("java") && line.includes("hatterserver") &&
|
||||||
|
line.includes(`-DAPPID=${appId}`);
|
||||||
|
}).map((line) => {
|
||||||
|
return parseProcessLine(line);
|
||||||
|
});
|
||||||
|
const filterProcesses: Process[] = [];
|
||||||
|
for (const process of processes) {
|
||||||
|
if (process !== null) {
|
||||||
|
filterProcesses.push(process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filterProcesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkServerStarted(
|
||||||
|
serverControlConfig: ServerControlConfig,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const healthCheck = serverControlConfig.healthCheck;
|
||||||
|
const healthCheckUrl = healthCheck?.url;
|
||||||
|
if (!healthCheck || !healthCheckUrl) {
|
||||||
|
log.error("Health check URL is not configured!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const count = healthCheck.count || 30;
|
||||||
|
const intervalMillis = healthCheck.intervalMillis || 1000;
|
||||||
|
const timeoutMillis = healthCheck.timeoutMillis || 1000;
|
||||||
|
let startServerSuccess = false;
|
||||||
|
await new ProcessBar("Starting server").call(async () => {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
try {
|
||||||
|
const response = await fetchWithTimeout(
|
||||||
|
healthCheckUrl,
|
||||||
|
timeoutMillis,
|
||||||
|
);
|
||||||
|
if (response.status === 200) {
|
||||||
|
startServerSuccess = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// IGNORE
|
||||||
|
}
|
||||||
|
await sleep(intervalMillis);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (startServerSuccess) {
|
||||||
|
log.success("Server started!");
|
||||||
|
} else {
|
||||||
|
log.warn("Server failed!");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadServerControlConfig(
|
||||||
|
metaUrl: string,
|
||||||
|
serverControlConfigFile?: string,
|
||||||
|
): Promise<ServerControlConfig> {
|
||||||
|
const fullServerControlConfigFile = serverControlConfigFile ||
|
||||||
|
(fromFileUrl(metaUrl).replace(".ts", ".json"));
|
||||||
|
log.debug(
|
||||||
|
`Read server control config file: ${fullServerControlConfigFile}`,
|
||||||
|
);
|
||||||
|
const serverControlConfigJson = await readFileToString(
|
||||||
|
fullServerControlConfigFile,
|
||||||
|
);
|
||||||
|
if (serverControlConfigJson === null) {
|
||||||
|
throw new Error(`Read file ${fullServerControlConfigFile} failed.`);
|
||||||
|
}
|
||||||
|
return JSON.parse(
|
||||||
|
serverControlConfigJson,
|
||||||
|
) as ServerControlConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleStatus(
|
||||||
|
serverControlConfig: ServerControlConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
const processes = await listJavaProcesses(serverControlConfig);
|
||||||
|
if (processes.length === 0) {
|
||||||
|
log.warn("No process are running!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.success(
|
||||||
|
`Find ${processes.length} process(es), pid: \n-> ${
|
||||||
|
processes.map((p) => p.pid).join("-> ")
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleStop(serverControlConfig: ServerControlConfig) {
|
||||||
|
const processes = await listJavaProcesses(serverControlConfig);
|
||||||
|
if (processes.length === 0) {
|
||||||
|
log.info("No process are running!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (processes.length > 1) {
|
||||||
|
log.warn(
|
||||||
|
`Too many processes are running, pid(s): ${
|
||||||
|
processes.map((p) => p.pid).join(", ")
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pid = processes[0].pid;
|
||||||
|
log.warn(`Kill pid: ${pid}`);
|
||||||
|
await execCommandShell("kill", [`${pid}`]);
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRestart(serverControlConfig: ServerControlConfig) {
|
||||||
|
let processes = await listJavaProcesses(serverControlConfig);
|
||||||
|
if (processes.length > 1) {
|
||||||
|
log.warn(
|
||||||
|
`Too many processes are running, pid(s): ${
|
||||||
|
processes.map((p) => p.pid).join(", ")
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!serverControlConfig.startup) {
|
||||||
|
log.error("Startup command is not configured!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (processes.length > 0) {
|
||||||
|
await execCommandShell("kill", [`${processes[0].pid}`]);
|
||||||
|
await sleep(500);
|
||||||
|
processes = await listJavaProcesses(serverControlConfig);
|
||||||
|
}
|
||||||
|
log.info(`Run command: ${serverControlConfig.startup} &`);
|
||||||
|
await execCommandShell("sh", ["-c", `${serverControlConfig.startup} &`]);
|
||||||
|
log.success("Start server, checking ...");
|
||||||
|
await checkServerStarted(serverControlConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function serverControlMain(metaUrl: string) {
|
||||||
|
const args = Deno.args;
|
||||||
|
if (args.length === 0) {
|
||||||
|
log.error(`No args.
|
||||||
|
|
||||||
|
server-control.ts status
|
||||||
|
server-control.ts kill|stop
|
||||||
|
server-control.ts re|restart`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const serverControlConfigFile = Deno.env.get("SERVER_CONTROL_CONFIG_FILE");
|
||||||
|
const serverControlConfig = await loadServerControlConfig(
|
||||||
|
metaUrl,
|
||||||
|
serverControlConfigFile,
|
||||||
|
);
|
||||||
|
if (!serverControlConfig.appId) {
|
||||||
|
log.error("Config appId not found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0]) {
|
||||||
|
case "status":
|
||||||
|
await handleStatus(serverControlConfig);
|
||||||
|
return;
|
||||||
|
case "kill":
|
||||||
|
case "stop":
|
||||||
|
await handleStop(serverControlConfig);
|
||||||
|
return;
|
||||||
|
case "re":
|
||||||
|
case "restart":
|
||||||
|
await handleRestart(serverControlConfig);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
log.warn("Argument error!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,567 +8,7 @@
|
|||||||
|
|
||||||
// deno-1.6.0 run --allow-all server-control.ts ??
|
// deno-1.6.0 run --allow-all server-control.ts ??
|
||||||
|
|
||||||
class Term {
|
import {log} from "https://global.hatter.ink/script/get/@1/deno-commons-1.6.0-mod.ts";
|
||||||
constructor() {
|
import {serverControlMain} from "https://global.hatter.ink/script/get/@3/deno-server-control-1.6.0-mod.ts";
|
||||||
}
|
|
||||||
|
|
||||||
blink(message: string): string {
|
serverControlMain(import.meta.url).catch((e) => log.error(e));
|
||||||
return `\x1b[5m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
bold(message: string): string {
|
|
||||||
return `\x1b[1m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
under(message: string): string {
|
|
||||||
return `\x1b[4m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
red(message: string): string {
|
|
||||||
return `\x1b[31m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
green(message: string): string {
|
|
||||||
return `\x1b[32m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
yellow(message: string): string {
|
|
||||||
return `\x1b[33m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
blue(message: string): string {
|
|
||||||
return `\x1b[34m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
pink(message: string): string {
|
|
||||||
return `\x1b[35m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
cyan(message: string): string {
|
|
||||||
return `\x1b[36m${message}\x1b[0m`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const term = new Term();
|
|
||||||
|
|
||||||
function pad(message: string, length: number): string {
|
|
||||||
if (message.length >= length) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
return message + " ".repeat(length - message.length);
|
|
||||||
}
|
|
||||||
const LOGGER_PREFIX_LEN: number = 8;
|
|
||||||
class Logger {
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
success(...data: any[]) {
|
|
||||||
this.log(
|
|
||||||
term.bold(term.green(`[${pad("SUCCESS", LOGGER_PREFIX_LEN)}]`)),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
error(...data: any[]) {
|
|
||||||
this.log(
|
|
||||||
term.bold(term.red(`[${pad("ERROR", LOGGER_PREFIX_LEN)}]`)),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
warn(...data: any[]) {
|
|
||||||
this.log(
|
|
||||||
term.bold(term.yellow(`[${pad("WARN", LOGGER_PREFIX_LEN)}]`)),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
warning(...data: any[]) {
|
|
||||||
this.log(
|
|
||||||
term.blink(
|
|
||||||
term.bold(term.yellow(`[${pad("WARN", LOGGER_PREFIX_LEN)}]`)),
|
|
||||||
),
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
info(...data: any[]) {
|
|
||||||
this.log(term.bold(`[${pad("INFO", LOGGER_PREFIX_LEN)}]`), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
debug(...data: any[]) {
|
|
||||||
this.log(`[${pad("DEBUG", LOGGER_PREFIX_LEN)}]`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
log(prefix: string, data: any[]) {
|
|
||||||
const args = [prefix];
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
args.push(data[i]);
|
|
||||||
}
|
|
||||||
console.log.apply(console, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const log = new Logger();
|
|
||||||
|
|
||||||
function getHomeDir(): string | null {
|
|
||||||
if (Deno.build.os === "windows") {
|
|
||||||
const userProfile = Deno.env.get("USERPROFILE");
|
|
||||||
if (userProfile) {
|
|
||||||
return userProfile;
|
|
||||||
}
|
|
||||||
const homeDrive = Deno.env.get("HOMEDRIVE");
|
|
||||||
const homePath = Deno.env.get("HOMEPATH");
|
|
||||||
if (homeDrive && homePath) {
|
|
||||||
return homeDrive + homePath;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Deno.env.get("HOME") || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveFilename(filename: string): string {
|
|
||||||
if (filename.startsWith("~/")) {
|
|
||||||
return getHomeDir() + filename.substring(1);
|
|
||||||
}
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toCmdArray(command: string, args?: string[]): string[] {
|
|
||||||
const cmdArray = [command];
|
|
||||||
if (args) {
|
|
||||||
for (const arg of args) {
|
|
||||||
cmdArray.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug("Command: ", cmdArray);
|
|
||||||
return cmdArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function execCommandShell(
|
|
||||||
command: string,
|
|
||||||
args?: string[],
|
|
||||||
): Promise<number> {
|
|
||||||
const process = Deno.run({
|
|
||||||
cmd: toCmdArray(command, args),
|
|
||||||
stdout: "inherit",
|
|
||||||
stderr: "inherit",
|
|
||||||
});
|
|
||||||
const status = await process.status();
|
|
||||||
process.close();
|
|
||||||
return status.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProcessOutput {
|
|
||||||
code: number;
|
|
||||||
stdout: string;
|
|
||||||
stderr: string;
|
|
||||||
|
|
||||||
constructor(code: number, stdout: string, stderr: string) {
|
|
||||||
this.code = code;
|
|
||||||
this.stdout = stdout;
|
|
||||||
this.stderr = stderr;
|
|
||||||
}
|
|
||||||
|
|
||||||
assertSuccess(): ProcessOutput {
|
|
||||||
if (this.code !== 0) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to execute command, exit code: ${this.code}\n- stdout: ${this.stdout}\n- stderr: ${this.stderr}\n`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getStdoutAsStringThenTrim(): string {
|
|
||||||
return this.stdout.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
getStdoutAsJson(): any {
|
|
||||||
return JSON.parse(this.stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
getStderrAsJson(): any {
|
|
||||||
return JSON.parse(this.stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function execCommand(
|
|
||||||
command: string,
|
|
||||||
args?: string[],
|
|
||||||
): Promise<ProcessOutput> {
|
|
||||||
const process = Deno.run({
|
|
||||||
cmd: toCmdArray(command, args),
|
|
||||||
stdout: "piped",
|
|
||||||
stderr: "piped",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { code } = await process.status();
|
|
||||||
const stdout = await process.output();
|
|
||||||
const stderr = await process.stderrOutput();
|
|
||||||
|
|
||||||
return new ProcessOutput(
|
|
||||||
code,
|
|
||||||
new TextDecoder().decode(stdout),
|
|
||||||
new TextDecoder().decode(stderr),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchWithTimeout(
|
|
||||||
input: URL | Request | string,
|
|
||||||
timeout?: number,
|
|
||||||
initCallback?: (init: RequestInit) => RequestInit,
|
|
||||||
): Promise<Response> {
|
|
||||||
const fetchTimeout = timeout || 10000;
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const timeoutHandler = setTimeout(() => {
|
|
||||||
abortController.abort();
|
|
||||||
}, fetchTimeout);
|
|
||||||
let init: RequestInit = {};
|
|
||||||
init.signal = abortController.signal;
|
|
||||||
if (initCallback) {
|
|
||||||
init = initCallback(init);
|
|
||||||
}
|
|
||||||
const response = await fetch(input, init);
|
|
||||||
clearTimeout(timeoutHandler);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sleep(timeoutMillis: number): Promise<void> {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, timeoutMillis));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readFileToString(
|
|
||||||
filename: string,
|
|
||||||
): Promise<string | null> {
|
|
||||||
try {
|
|
||||||
return await Deno.readTextFile(resolveFilename(filename));
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error && e.name == "NotFound") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProcessBar {
|
|
||||||
interval?: number;
|
|
||||||
message: string;
|
|
||||||
constructor(message?: string) {
|
|
||||||
this.message = message || "Processing";
|
|
||||||
}
|
|
||||||
async call<T>(cb: () => Promise<T>, clearLine?: boolean): Promise<T> {
|
|
||||||
this.start();
|
|
||||||
try {
|
|
||||||
return await cb();
|
|
||||||
} finally {
|
|
||||||
this.stop(clearLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write(message: string): void {
|
|
||||||
Deno.stdout.writeSync(new TextEncoder().encode(message));
|
|
||||||
}
|
|
||||||
start(): void {
|
|
||||||
const startMs = new Date().getTime();
|
|
||||||
let count = 0;
|
|
||||||
this.interval = setInterval(() => {
|
|
||||||
const dots = ".".repeat(((count++) % 10) + 1);
|
|
||||||
const costMs = new Date().getTime() - startMs;
|
|
||||||
const time = `${Math.floor(costMs / 1000)}s`;
|
|
||||||
this.write(`\r${this.message} ${time} ${dots}\x1b[K`);
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
stop(clearLine?: boolean): void {
|
|
||||||
if (this.interval) {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
this.write(clearLine ? "\r\x1b[K" : "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Process {
|
|
||||||
user: string;
|
|
||||||
pid: number;
|
|
||||||
cpu: number;
|
|
||||||
mem: number;
|
|
||||||
vsz: number;
|
|
||||||
rss: number;
|
|
||||||
tty: string;
|
|
||||||
stat: string;
|
|
||||||
start: string;
|
|
||||||
time: string;
|
|
||||||
command: string;
|
|
||||||
constructor(
|
|
||||||
user: string,
|
|
||||||
pid: number,
|
|
||||||
cpu: number,
|
|
||||||
mem: number,
|
|
||||||
vsz: number,
|
|
||||||
rss: number,
|
|
||||||
tty: string,
|
|
||||||
stat: string,
|
|
||||||
start: string,
|
|
||||||
time: string,
|
|
||||||
command: string,
|
|
||||||
) {
|
|
||||||
this.user = user;
|
|
||||||
this.pid = pid;
|
|
||||||
this.cpu = cpu;
|
|
||||||
this.mem = mem;
|
|
||||||
this.vsz = vsz;
|
|
||||||
this.rss = rss;
|
|
||||||
this.tty = tty;
|
|
||||||
this.stat = stat;
|
|
||||||
this.start = start;
|
|
||||||
this.time = time;
|
|
||||||
this.command = command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseProcessLine(line: string): Process | null {
|
|
||||||
const processMatchRegex =
|
|
||||||
/^\s*([\w+\-_]+)\s+(\d+)\s+([\d.]+)\s+([\d.]+)\s+(\d+)\s+(\d+)\s+([\w\/\\?]+)\s+([\w+<>]+)\s+([\w:]+)\s+([\d:]+)\s+(.*)$/;
|
|
||||||
// "app 3622 0.2 24.0 2932504 453004 ? Sl Jan25 23:04 /usr/lib/jvm/jdk-25/bin/java -Dfastjson.parser.safeMode=true......";
|
|
||||||
// USER PID CPU MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
||||||
const matcher = line.match(processMatchRegex);
|
|
||||||
if (!matcher) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const user = matcher[1];
|
|
||||||
const pid = parseInt(matcher[2]);
|
|
||||||
const cpu = Number(matcher[3]);
|
|
||||||
const mem = Number(matcher[4]);
|
|
||||||
const vsz = parseInt(matcher[5]);
|
|
||||||
const rss = parseInt(matcher[6]);
|
|
||||||
const tty = matcher[7];
|
|
||||||
const stat = matcher[8];
|
|
||||||
const start = matcher[9];
|
|
||||||
const time = matcher[10];
|
|
||||||
const command = matcher[11];
|
|
||||||
return new Process(
|
|
||||||
user,
|
|
||||||
pid,
|
|
||||||
cpu,
|
|
||||||
mem,
|
|
||||||
vsz,
|
|
||||||
rss,
|
|
||||||
tty,
|
|
||||||
stat,
|
|
||||||
start,
|
|
||||||
time,
|
|
||||||
command,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HealthCheck {
|
|
||||||
url?: string;
|
|
||||||
count?: number; // default 30
|
|
||||||
timeoutMillis?: number; // default 1000
|
|
||||||
intervalMillis?: number; // default 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServerControlConfig {
|
|
||||||
appId?: string;
|
|
||||||
startup?: string;
|
|
||||||
healthCheck?: HealthCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listJavaProcesses(
|
|
||||||
serverControlConfig: ServerControlConfig,
|
|
||||||
): Promise<Process[]> {
|
|
||||||
const appId = serverControlConfig.appId;
|
|
||||||
const processOutput = await execCommand("ps", ["aux"]);
|
|
||||||
processOutput.assertSuccess();
|
|
||||||
const stdout = processOutput.getStdoutAsStringThenTrim();
|
|
||||||
const lines = stdout.split("\n");
|
|
||||||
const processes = lines.filter((line) => {
|
|
||||||
return line.includes("java") && line.includes("hatterserver") &&
|
|
||||||
line.includes(`-DAPPID=${appId}`);
|
|
||||||
}).map((line) => {
|
|
||||||
return parseProcessLine(line);
|
|
||||||
});
|
|
||||||
const filterProcesses: Process[] = [];
|
|
||||||
for (const process of processes) {
|
|
||||||
if (process !== null) {
|
|
||||||
filterProcesses.push(process);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filterProcesses;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkServerStarted(
|
|
||||||
serverControlConfig: ServerControlConfig,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const healthCheck = serverControlConfig.healthCheck;
|
|
||||||
const healthCheckUrl = healthCheck?.url;
|
|
||||||
if (!healthCheck || !healthCheckUrl) {
|
|
||||||
log.error("Health check URL is not configured!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const count = healthCheck.count || 30;
|
|
||||||
const intervalMillis = healthCheck.intervalMillis || 1000;
|
|
||||||
const timeoutMillis = healthCheck.timeoutMillis || 1000;
|
|
||||||
let startServerSuccess = false;
|
|
||||||
await new ProcessBar("Starting server").call(async () => {
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
try {
|
|
||||||
const response = await fetchWithTimeout(
|
|
||||||
healthCheckUrl,
|
|
||||||
timeoutMillis,
|
|
||||||
);
|
|
||||||
if (response.status === 200) {
|
|
||||||
startServerSuccess = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// IGNORE
|
|
||||||
}
|
|
||||||
await sleep(intervalMillis);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (startServerSuccess) {
|
|
||||||
log.success("Server started!");
|
|
||||||
} else {
|
|
||||||
log.warn("Server failed!");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertArg(url: URL | string) {
|
|
||||||
url = url instanceof URL ? url : new URL(url);
|
|
||||||
if (url.protocol !== "file:") {
|
|
||||||
throw new TypeError("Must be a file URL.");
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function posixFromFileUrl(url: URL | string): string {
|
|
||||||
url = assertArg(url);
|
|
||||||
return decodeURIComponent(
|
|
||||||
url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadServerControlConfig(
|
|
||||||
serverControlConfigFile?: string,
|
|
||||||
): Promise<ServerControlConfig> {
|
|
||||||
const fullServerControlConfigFile = serverControlConfigFile ||
|
|
||||||
(posixFromFileUrl(import.meta.url).replace(".ts", ".json"));
|
|
||||||
log.debug(
|
|
||||||
`Read server control config file: ${fullServerControlConfigFile}`,
|
|
||||||
);
|
|
||||||
const serverControlConfigJson = await readFileToString(
|
|
||||||
fullServerControlConfigFile,
|
|
||||||
);
|
|
||||||
if (serverControlConfigJson === null) {
|
|
||||||
throw new Error(`Read file ${fullServerControlConfigFile} failed.`);
|
|
||||||
}
|
|
||||||
return JSON.parse(
|
|
||||||
serverControlConfigJson,
|
|
||||||
) as ServerControlConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleStatus(
|
|
||||||
serverControlConfig: ServerControlConfig,
|
|
||||||
): Promise<void> {
|
|
||||||
const processes = await listJavaProcesses(serverControlConfig);
|
|
||||||
if (processes.length === 0) {
|
|
||||||
log.warn("No process are running!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.success(
|
|
||||||
`Find ${processes.length} process(es), pid: \n-> ${
|
|
||||||
processes.map((p) => p.pid).join("-> ")
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleStop(serverControlConfig: ServerControlConfig) {
|
|
||||||
const processes = await listJavaProcesses(serverControlConfig);
|
|
||||||
if (processes.length === 0) {
|
|
||||||
log.info("No process are running!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (processes.length > 1) {
|
|
||||||
log.warn(
|
|
||||||
`Too many processes are running, pid(s): ${
|
|
||||||
processes.map((p) => p.pid).join(", ")
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pid = processes[0].pid;
|
|
||||||
log.warn(`Kill pid: ${pid}`);
|
|
||||||
await execCommandShell("kill", [`${pid}`]);
|
|
||||||
await sleep(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRestart(serverControlConfig: ServerControlConfig) {
|
|
||||||
let processes = await listJavaProcesses(serverControlConfig);
|
|
||||||
if (processes.length > 1) {
|
|
||||||
log.warn(
|
|
||||||
`Too many processes are running, pid(s): ${
|
|
||||||
processes.map((p) => p.pid).join(", ")
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!serverControlConfig.startup) {
|
|
||||||
log.error("Startup command is not configured!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (processes.length > 0) {
|
|
||||||
await execCommandShell("kill", [`${processes[0].pid}`]);
|
|
||||||
await sleep(500);
|
|
||||||
processes = await listJavaProcesses(serverControlConfig);
|
|
||||||
}
|
|
||||||
log.info(`Run command: ${serverControlConfig.startup} &`);
|
|
||||||
await execCommandShell("sh", ["-c", `${serverControlConfig.startup} &`]);
|
|
||||||
log.success("Start server, checking ...");
|
|
||||||
await checkServerStarted(serverControlConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const args = Deno.args;
|
|
||||||
if (args.length === 0) {
|
|
||||||
log.error(`No args.
|
|
||||||
|
|
||||||
server-control.js status
|
|
||||||
server-control.js kill|stop
|
|
||||||
server-control.js re|restart`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const serverControlConfigFile = Deno.env.get("SERVER_CONTROL_CONFIG_FILE");
|
|
||||||
const serverControlConfig = await loadServerControlConfig(
|
|
||||||
serverControlConfigFile,
|
|
||||||
);
|
|
||||||
if (!serverControlConfig.appId) {
|
|
||||||
log.error("Config appId not found!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (args[0]) {
|
|
||||||
case "status":
|
|
||||||
await handleStatus(serverControlConfig);
|
|
||||||
return;
|
|
||||||
case "kill":
|
|
||||||
case "stop":
|
|
||||||
await handleStop(serverControlConfig);
|
|
||||||
return;
|
|
||||||
case "re":
|
|
||||||
case "restart":
|
|
||||||
await handleRestart(serverControlConfig);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
log.warn("Argument error!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((e) => log.error(e));
|
|
||||||
|
|||||||
Reference in New Issue
Block a user