'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=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=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();