// 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 { 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 { 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 { 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 { 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; } }