70 lines
7.0 KiB
Markdown
70 lines
7.0 KiB
Markdown
# 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 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시간 보관 |
|
||
|
|
|
||
|
|
## 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
|
||
|
|
```
|
||
|
|
부트스트랩은 HAL 설치·보관소·유닛·nginx·릴리스 등록(승인)·운영자 설정 토큰까지만 한다. 이어서 운영자 계정 생성 → 서버 생성·등록 토큰 → `agent-install.sh` → 서버 승인. 첫 Agent 승인 시 `bootstrap_state.completed_at` 이 기록되고 수동 설치가 잠긴다. 그 뒤 `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` 과 임시 루트로 실제 시스템을 건드리지 않는다.
|