// [0.4.1] 이미 등록된 Agent 가 있으면 조용히 덮어쓰지 않는다(요구 1 — "이미 등록된 서버" 메시지). --force 는 새 토큰(재등록 허용)으로 바꿀 때만.
if(cfg.id&&!opt('force')){console.error(`이미 등록된 서버입니다 — Agent ${cfg.id}(${cfg.kind||'managed'}${cfg.serverId?`, 서버 ${cfg.serverId}`:''}) 가 ${cfg.registeredAt||'이전'} 에 ${cfg.halUrl} 에 등록되었습니다. 다시 설치할 필요가 없습니다. 새 Agent 로 바꾸려면 운영자 콘솔에서 "재등록 허용" 토큰을 받아 --force 로 실행하세요`);process.exit(2);}
try{constr=awaitclient.request('POST','/agent/v1/heartbeat',{version:VERSION,metrics:sample(cfg.roots),modules:{},clockSkewMs:0});console.log(`연결 정상: agent ${cfg.id} (${cfg.kind}) → ${cfg.halUrl}, 대기 작업 ${r.pendingJobs}, 시계 차이 ${client.clockSkewMs}ms${caFile?`, HAL CA ${caFile}(${caState.via||'미적용'})`:''}`);}catch(e){console.error(`HAL 연결 실패: ${e.status?e.message:friendlyFetchError(e,cfg.halUrl).message}`);process.exit(1);}
asyncfunctionmoduleStatus(f){constout={};for(const[m,unit]ofObject.entries(f.units||{})){constappDir=(f.appDirs||{})[m]||join(cfg.roots.srv,m);letversion=null;try{version=JSON.parse(readFileSync(join(appDir,'current','REVISION.json'),'utf8')).version;}catch(_){}if(!version){try{version=JSON.parse(readFileSync(join(appDir,'current','package.json'),'utf8')).version;}catch(_){}}/* 도입 모듈은 REVISION.json 의 Suite 버전(상류 package.json 버전은 목표와 다르다 — 실제 환경 결함 수정) */constactive=awaitsys.isActive(unit);out[m]={version,active,health:active?'ok':'down'};}out.agent={version:VERSION,active:true,health:'ok'};out.hawkeye={version:VERSION,active:true,health:'ok'};/* [0.4.1] 0.3.17 에서 바뀐 이름(observability → hawkeye)으로 보고 — 옛 이름 보고가 hawkeye 행을 stale 로 두던 결함 */if(existsSync(cfg.roots.backups))out.backup={version:VERSION,active:true,health:'ok'};returnout;}
asyncfunctionexecute(job){running++;try{constout=awaitrunner.execute(job);if(out?.result?.restartAfterReport)restartUnit=out.result.restartAfterReport;}finally{running--;}if(restartUnit){constu=restartUnit;restartUnit=null;log(`[agent] 자기 갱신 — ${u} 재시작`);setTimeout(()=>sys.systemctl('restart',u,{optional:true}),1500);}}
// 재시작 뒤: 보고 못 한 결과 전송, 미완료 작업 재개
awaitrunner.flushPending();try{constu=awaitclient.request('GET','/agent/v1/jobs/unfinished');for(constjofu.jobs||[]){log(`[agent] 미완료 작업 재개 ${j.id}${j.kind}`);execute(j);}}catch(e){log(`[agent] 미완료 작업 조회 실패: ${e.message}`);}
while(!stopping){try{constr=awaitclient.request('GET','/agent/v1/jobs/next?wait=25',undefined,{timeoutMs:40000});if(r.job)execute(r.job);}catch(e){log(`[agent] 작업 조회 실패: ${e.message}`);awaitnewPromise((s)=>setTimeout(s,5000));}}
process.exit(0);
}else{console.error(`알 수 없는 명령: ${cmd}`);process.exit(1);}