Files
ts-scripts/libraries/deno-server-control-1.6.0-mod.ts
2026-02-05 00:24:29 +08:00

221 lines
6.5 KiB
TypeScript

// 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;
}
function isFile(url: URL | string): boolean {
url = url instanceof URL ? url : new URL(url);
return url.protocol === "file:";
}
async function loadServerControlConfig(
metaUrl: string,
serverControlConfigFile?: string,
): Promise<ServerControlConfig> {
let fullServerControlConfigFile: string;
if (!isFile(metaUrl)) {
fullServerControlConfigFile = "server-control.json";
} else {
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;
}
}