Files
zzabisagent-labandClaude Fable 5.1 2b31543a86 HAL 0.4.1: 서버 등록 TLS 수정·"이미 등록된 서버" 안내, HAL 배포가 HAL 서버의 Node Agent·HawkEye 를 함께 설치해 부트스트랩을 닫음
- hal/tls.mjs: 공개 주소 TLS 신뢰 확인(시스템 CA → 실패면 체인·공개키 핀), 신뢰 앵커(체인 자체 서명 / <data>/tls/hal-ca.pem), 10분 캐시, 핀 설치 명령(curl -k --pinnedpubkey … --hal-pin). 공개 경로 GET /agent/v1/hal-ca.pem · /agent/v1/tls, CLI tls-probe
- deploy/agent-install.sh: --hal-pin(핀으로 CA 받기)·--ca·--insecure, curl --cacert / Node NODE_EXTRA_CA_CERTS / register --ca / 유닛 Environment, 연결 실패 원인·조치 한 줄, 이미 등록된 서버 안내
- agent/halca.mjs + agent/index.mjs·config.mjs: caFile 런타임 적용(tls.setDefaultCACertificates), fetch 실패를 읽을 수 있는 한 줄로(종료 1), 이미 등록(종료 2, --force), HAL 거부(종료 3), heartbeat observability → hawkeye
- core.registerAgent: 같은 서버의 두 번째 토큰 409 server_already_registered / 쓴 토큰 409 token_used("이미 등록된 서버"), 거부 토큰 미소비, 재등록 허용(replace: 기존 Agent 해지, 감사 agent.replace), 마이그레이션 hal_0011_token_replace, API/CLI --replace --json(재인증), ops.js 재등록 확인·핀 안내
- deploy/hal-host-agent.sh(server-create --profile hal → server-token --json → agent-install loopback → server-approve), bootstrap-hal.sh 6/7 단계(--no-agent), CLI server-create·server-approve, SERVER_PROFILES.hal
- core.deploymentStatus/selfHealth.deployment, 배너 "부트스트랩 완료 · 기능 단위 서버 배포 대기: …" / "진행 중", 배포 릴리스 화면 문구, heartbeat canonicalModuleName(0.3.17 잔여 결함)
- 개발 지시 v0.4.1(Oz)·VERSION_HISTORY 0.4.1·README·docs/HAL-AGENT §6·RUNBOOK §11·AI 스킬, 도입 모듈 변경 없음(Webmaker 1.9.0·AriMail 1.1.0)
- test/hal-041.test.mjs RED → GREEN 7/7(실제 자체 서명 TLS 픽스처·별도 프로세스 CLI), 기존 배너·프로필·버전 테스트 갱신, 전체 32파일 통과, 로컬 E2E 보고서 2026-11-06

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 08:40:09 +00:00

10 KiB

HAL ↔ Node Agent 프로토콜·설치·운영 (v0.1.1)

Dev-Order §5(아키텍처)·§28·§29·§32·§33·§34 의 백엔드 구현. 코드: hal/, agent/, deploy/, scripts/release.mjs, portal/hal/client.mjs.

1. 구성

구성요소 위치 역할
HAL /srv/ariweb/hal/current, 127.0.0.1:8900, nginx hal.ariweb.net 서버·Agent 등록부, 릴리스 등록부, 내구성 작업 엔진(lib/jobs), 모듈 배포, 포털 API, 운영자 API, 긴급 복구, 재해복구, 공개 상태
Node Agent /srv/ariweb/agent/current, 유닛 ariweb-agent heartbeat(30초, 지표·모듈 버전·시계 차이), 작업 long-poll·실행(단계 저장·재개), 모듈 설치, 사이트 배포·백업·이전
External Pull Agent 고객 외부 서버, 같은 코드 --external --site <id> site.deploy / site.remove / site.check / site.rollback 만, 사이트 하나에 귀속

2. 인증

  • Agent·포털·HAL 간 모든 요청은 lib/serviceauth HMAC: X-Service-Key-Id(agent:<id> / portal / hal), X-Service-Ts(±5분), X-Service-Sign.
  • Agent 비밀은 등록 응답에서 한 번만 전달되어 Agent 보관소(data/vault.json, 키 agent-secret)에, HAL 쪽은 service:agent:<id> 로 보관. 표에는 해시만.
  • 큰 전송(POST /agent/v1/transfers/:id)은 빈 본문 서명 + X-Transfer-Sha256 헤더, HAL 이 받은 뒤 해시를 다시 계산해 비교.

3. Agent 프로토콜(/agent/v1)

경로 설명
GET install.sh 설치 스크립트(deploy/agent-install.sh)
GET hal-ca.pem [0.4.1] HAL 공개 주소가 사설 CA·자체 서명일 때의 신뢰 앵커 PEM(공개, 무서명). 시스템 CA 로 신뢰되면 404 no_private_ca
GET tls [0.4.1] 공개 주소 TLS 신뢰 상태(trusted·reason·pin·caAvailable, ?refresh=1)
GET bootstrap-artifact?module=agent&version= + X-Registration-Token 등록 전 Agent 코드 내려받기(유효 토큰 확인, 소비하지 않음)
POST register {token, facts, version} 일회용 토큰(10분) → {agentId, secret(1회), serverId, kind, siteId}
POST heartbeat {version, metrics, modules, clockSkewMs, facts, sitesHosted} 지표 저장(90일+), 서버 last_seen·용량·모듈 실제 버전 갱신. 90초 무응답 → offline
GET jobs/next?wait=25 long-poll. {job:{id, kind, payload, halJobId}} 또는 null
GET jobs/unfinished 재시작 뒤 이어서 할 running 작업 목록
POST jobs/:id/progress {progress, steps, log} → {cancelRequested} / POST jobs/:id/result {status: done|failed|needs_user, result, error, errorCode, userMessage}
GET releases/:version/artifact?module= tar.gz 스트리밍, X-Sha256
POST/GET transfers/:id HAL 경유 파일 전송(이전·원격 백업), 24시간 보관
GET settings/:module [0.4.0] 자기 서버 범위의 HAL 관리 설정(유효값·비밀값·hash). 작업 module.configure 가 부른다 — docs/HAL-SETTINGS.md

4. 포털 API(/portal/v1, 키 portal) — docs/HAL-JOBS.md

부트스트랩이 끝나기 전에는 health 외 503 bootstrap_incomplete. 작업 상태 변화는 outbox 를 통해 포털 POST /internal/hal-events 로 밀린다(실패 시 지수 백오프, 7일 보관).

5. 작업 모델

  • HAL 작업(lib/jobs) 한 개가 Agent 작업(agent_jobs) 하나 이상을 배정하고 결과를 기다린다(core.awaitAgentJob: 진행률 반영, 취소 전달, Agent 10분 무응답 → 사용자 확인 필요).
  • 복합 작업: backup.create(생성 → 저장 서버로 복사 → 검증), backup.restore(가져오기 → 보호 시점 → 격리 확인 → 복원 → 외부 확인, 되돌리기 지원), site.relocate(precheck → prepare → precopy → verify_copy → final_sync → verify_new → switch → external_check → cleanup; verify_new 실패 시 전환하지 않음, 되돌리기 지원).
  • 운영자 작업: module.deploy/module.rollback/module.health/module.reconcile 은 서버마다 자식 작업(module.deploy.target)을 만들고 childrenSummary 로 대상별 성공·실패·대기를 보인다. 일부 실패는 partial_failure 로 끝난다.
  • 배포 순서(§32.2): 릴리스 승인 → HAL 자체 건강 → 부트스트랩 완료 → hal 모듈이 그 릴리스로 실행 중일 때만 다른 모듈 배포(hal_first).

6. 설치(부트스트랩)와 첫 Agent

# 작업 PC: main 에 병합된 커밋에서
node scripts/release.mjs build            # dist/0.1.1/*.tar.gz + manifest.json (테스트 통과 필요)
scp -r dist/0.1.1 hostinger:/tmp/ariweb-dist
# 대상 서버(ariweb.net 호스트, master, sudo):
bash /tmp/ariweb-dist/… (artifact 안의 deploy/bootstrap-hal.sh) --dist /tmp/ariweb-dist --host hal.ariweb.net --cert /etc/letsencrypt/live/wh-wild-ariweb.net

[0.4.1] 부트스트랩은 HAL 설치·보관소·유닛·nginx·릴리스 등록(승인)에 이어 이 서버의 Node Agent·HawkEye 를 함께 설치·등록·승인해 부트스트랩을 닫는다(deploy/hal-host-agent.sh: server-create --profile hal → server-token --json → agent-install.sh --hal http://127.0.0.1:8900 --role control(loopback, DNS·인증서 무관) → server-approve). 그래서 설치 직후 운영자 콘솔은 "부트스트랩 미완료" 가 아니라 "부트스트랩 완료 · 기능 단위 서버 배포 대기: 고객 포털, Webmaker, AriMail, 백업" 을 보인다(/api/health.deployment.pending). Web·Mail·Backup·Portal 은 설치하지 않는다 — 역할별 통합 배포(새 서버)로 추가한다. 0.4.0 이하로 설치된 HAL 서버는 cd /srv/ariweb/hal/current && bash deploy/hal-host-agent.sh 를 한 번 실행한다(--no-agent 로 부트스트랩하면 이전처럼 첫 Agent 승인 때 닫힌다). 마지막에 운영자 설정 토큰을 낸다.

사설 CA·자체 서명 인증서로 HAL 을 운영할 때(0.4.1): HAL 은 기동 때와 토큰 발급 때 자기 공개 주소에 접속해 시스템 CA 신뢰 여부를 확인한다(hal/tls.mjs, CLI node hal/index.mjs tls-probe). 신뢰되지 않으면 운영자 콘솔이 만드는 설치 명령에 서버 공개키 핀이 붙는다:

curl -fsSL -k --pinnedpubkey 'sha256//…' https://hal.local.test/agent/v1/install.sh | bash -s -- --hal https://hal.local.test --token … --hal-pin 'sha256//…'

-k 는 CA 검증만 끄고 --pinnedpubkey 는 서버 공개키가 핀과 다르면 자료를 받기 전에 끊는다. 스크립트는 같은 핀으로 GET /agent/v1/hal-ca.pem(체인 안의 자체 서명 인증서, 또는 HAL 서버의 <data>/tls/hal-ca.pem 이 있으면 그 파일)을 받아 <app-dir>/data/hal-ca.pem 에 두고, 이후 curl 은 --cacert, Node 는 NODE_EXTRA_CA_CERTS + agent.json caFile(Agent 가 기동 때 tls.setDefaultCACertificates 로 더함), 유닛은 Environment=NODE_EXTRA_CA_CERTS=… 로 같은 CA 를 쓴다. 운영자가 CA 파일을 직접 줄 때는 --ca <pem>. 루트를 체인에 싣지 않는 사설 CA 는 HAL 서버에 <data>/tls/hal-ca.pem 을 두면 된다.

이미 등록된 서버(0.4.1): 같은 서버에 두 번째 토큰으로 등록하면 HAL 이 409 server_already_registered("이미 등록된 서버입니다(서버·Agent·등록 시각) — 다시 설치할 필요가 없습니다") 로 거부하고 토큰을 소비하지 않는다. 쓴 토큰을 다시 쓰면 409 token_used 에 같은 문구. 설치 스크립트는 agent.json 이 있으면 등록을 건너뛰고 "이미 등록된 서버" 를 안내한다. 서버를 다시 설치해 새 Agent 로 바꾸려면 운영자 콘솔 › 서버 › 등록 토큰에서 "재등록 허용"(재인증) 토큰을 발급한다 — 기존 Agent 는 해지된다(CLI server-token --server <id> --replace). Agent 쪽 연결 오류는 스택 대신 원인·조치 한 줄로 나온다(등록 실패: HAL(…) 인증서를 확인할 수 없습니다(SELF_SIGNED_CERT_IN_CHAIN) — …).

그 뒤 portal·webmaker·arimail 은 운영자 API POST /ops/api/modules/<name>/deploy {version, serverIds} 또는 역할별 통합 배포로 배포한다.

도입 모듈 배치: Agent facts 가 서버의 기존 유닛(webmaker, arihakdang-mail/arimail/heart-mail)과 WorkingDirectory 를 읽어 그 appDir(/srv/webmaker, /srv/arihakdang-mail …)의 releases/<ver> 에 풀고 current 를 바꾼다. Webmaker 는 원본 remote-update.sh 순서(데이터 백업 → 교체 → 재시작 → unit-check), AriMail 은 원본 deploy.sh 순서(DB 백업 → 교체 → db-migrate → 재시작 → health → haraka 재시작)를 Agent 가 수행한다.

7. 운영자 접근(§25)

/ops/* 는 허용 CIDR(config.opsAllowCidrs) 안이거나 접근권 쿠키(aw_ops_grant, 사용자·기기 귀속, 최대 72시간)가 있어야 하며 아니면 로그인 화면도 403. 접근권: node hal/index.mjs grant --login <id> --hours 8 또는 허용 네트워크 안의 운영자가 POST /ops/api/access-grants. 고위험 작업은 재인증(10분)과 3단계 확인(confirmImpact, confirmIdentifiers, confirmExecute).

8. 긴급 복구(§33)·재해복구(§34)

  • POST /ops/api/emergency 선언(HAL 정상이고 열린 critical 사건이 없으면 거부) → approve 1·2·3단계(서로 다른 입력) → credential(최대 60분, 1회 표시) → perform(허용 범위·허용 행동만; 일반 배포 거부) → verify(자격 폐기) → reconcile(목표·실제 비교) → close.
  • DR: PUT /ops/api/dr/config(RTO·RPO·주기·보존·외부 보관), 사본 POST /ops/api/dr/snapshots(VACUUM INTO + vault.json + config.json, vault.key 제외), 복원 요청→승인→시작(기존 제어 중지 확인 문구)→deploy/hal-restore.sh→완료 확인(운영자 로그인·HAL 제어·HTTPS·고객 서비스), 훈련 기록과 측정 RTO/RPO. readiness.ready 가 true 여야 정식 운영 준비 완료.
  • 하나의 HAL: 기동 때 bootstrap_state.live_instance_id/live_lease_at 임대(20초 갱신, 60초 유효). 다른 인스턴스의 임대가 살아 있으면 기동 거부(--takeover 는 복원 절차에서만).

9. 테스트

node --no-warnings test/hal.test.mjs, node --no-warnings test/agent.test.mjs. Agent 테스트는 ARIWEB_AGENT_STUB_CMDS=1 과 임시 루트로 실제 시스템을 건드리지 않는다.