70 lines
3.8 KiB
JavaScript
70 lines
3.8 KiB
JavaScript
'use strict';
|
|
|
|
const fs=require('fs');
|
|
const path=require('path');
|
|
const {spawn}=require('child_process');
|
|
|
|
const ROOT=path.resolve(__dirname,'..');
|
|
const SERVER_FILE=path.join(ROOT,'server.js');
|
|
const IDENTITY_FILE=path.resolve(process.env.LINK_FIELD_SERVICE_IDENTITY_FILE||path.join(process.env.LINK_FIELD_SERVICE_DIR||ROOT,'service.identity.json'));
|
|
const NONCE=String(process.env.LINK_FIELD_SERVICE_NONCE||'');
|
|
const LOG_FILE=String(process.env.LINK_FIELD_LOG_FILE||'');
|
|
const PORT_FILE=path.join(String(process.env.LINK_FIELD_PUBLIC_DIR||ROOT),'.linkfield-port');
|
|
const RESTART_WINDOW_MS=5*60*1000;
|
|
const MAX_RESTARTS=10;
|
|
const MAX_LOG_BYTES=Math.max(4096,Number(process.env.LINK_FIELD_LOG_MAX_BYTES)||10*1024*1024);
|
|
const LOG_ROTATE_INTERVAL_MS=Math.max(100,Number(process.env.LINK_FIELD_LOG_ROTATE_INTERVAL_MS)||30_000);
|
|
const LOG_GENERATIONS=5;
|
|
|
|
let child=null,stopping=false,restarts=[];
|
|
|
|
function writeIdentity(){
|
|
const value={pid:process.pid,root:ROOT,nonce:NONCE,heartbeatAt:Date.now(),childPid:child?.pid||null};
|
|
const temporary=`${IDENTITY_FILE}.${process.pid}.tmp`;
|
|
fs.mkdirSync(path.dirname(IDENTITY_FILE),{recursive:true,mode:0o700});
|
|
fs.writeFileSync(temporary,`${JSON.stringify(value)}\n`,{encoding:'utf8',mode:0o600});
|
|
fs.renameSync(temporary,IDENTITY_FILE);
|
|
}
|
|
|
|
function removeIdentity(){
|
|
try{const value=JSON.parse(fs.readFileSync(IDENTITY_FILE,'utf8'));if(value?.pid===process.pid&&value?.nonce===NONCE)fs.unlinkSync(IDENTITY_FILE)}catch(error){if(error?.code!=='ENOENT')console.warn(`Service identity cleanup warning: ${error.message}`)}
|
|
try{fs.unlinkSync(PORT_FILE)}catch(error){if(error?.code!=='ENOENT')console.warn(`Bridge-port cleanup warning: ${error.message}`)}
|
|
}
|
|
|
|
function rotateLiveLog(){
|
|
if(!LOG_FILE)return;
|
|
let stat;try{stat=fs.statSync(LOG_FILE)}catch(error){if(error?.code==='ENOENT')return;throw error}
|
|
if(stat.size<MAX_LOG_BYTES)return;
|
|
try{fs.unlinkSync(`${LOG_FILE}.${LOG_GENERATIONS}`)}catch(error){if(error?.code!=='ENOENT')throw error}
|
|
for(let index=LOG_GENERATIONS-1;index>=1;index--)try{fs.renameSync(`${LOG_FILE}.${index}`,`${LOG_FILE}.${index+1}`)}catch(error){if(error?.code!=='ENOENT')throw error}
|
|
fs.copyFileSync(LOG_FILE,`${LOG_FILE}.1`);fs.truncateSync(LOG_FILE,0);
|
|
}
|
|
|
|
function startChild(){
|
|
if(stopping)return;
|
|
const now=Date.now();restarts=restarts.filter(value=>now-value<RESTART_WINDOW_MS);
|
|
if(restarts.length>=MAX_RESTARTS){console.error(`LinkField stopped after ${MAX_RESTARTS} crashes in five minutes.`);removeIdentity();process.exit(1);return}
|
|
restarts.push(now);
|
|
child=spawn(process.execPath,[SERVER_FILE],{cwd:ROOT,stdio:'inherit',env:{...process.env,LINK_FIELD_SUPERVISED:'1'}});
|
|
writeIdentity();
|
|
child.once('exit',(code,signal)=>{
|
|
const lifetime=Date.now()-now;child=null;writeIdentity();
|
|
if(stopping){removeIdentity();process.exit(0);return}
|
|
console.error(`LinkField server exited (${signal||code}); restarting.`);
|
|
if(lifetime>60_000)restarts=[];
|
|
setTimeout(startChild,Math.min(10_000,500*Math.max(1,restarts.length)));
|
|
});
|
|
}
|
|
|
|
function shutdown(signal){
|
|
if(stopping)return;stopping=true;console.log(`LinkField supervisor received ${signal}; stopping.`);
|
|
if(child&&child.exitCode==null){child.kill('SIGTERM');const timer=setTimeout(()=>{if(child&&child.exitCode==null)child.kill('SIGKILL')},5000);timer.unref?.()}
|
|
else{removeIdentity();process.exit(0)}
|
|
}
|
|
|
|
process.once('SIGTERM',()=>shutdown('SIGTERM'));
|
|
process.once('SIGINT',()=>shutdown('SIGINT'));
|
|
process.once('exit',removeIdentity);
|
|
const heartbeat=setInterval(()=>{try{writeIdentity()}catch(error){console.error(`Service heartbeat failed: ${error.message}`)}},1000);heartbeat.unref?.();
|
|
const logRotation=setInterval(()=>{try{rotateLiveLog()}catch(error){console.error(`Live log rotation failed: ${error.message}`)}},LOG_ROTATE_INTERVAL_MS);logRotation.unref?.();
|
|
startChild();
|