InfraGuard

백업 리포트 콜백 (report-callback)

대상 호스트의 백업 스크립트가 실행 결과를 POST /api/v1/backup/report 로 보고하면 infr_backup_run(+backup_history)에 기록되고 잡 상태가 갱신되는 무상태 인제스트 계약입니다.

개요 — 무엇을 하나

InfraGuard 는 원격 crontab 을 직접 쓰지 않습니다. 대신 SE 가 배치한 백업 스크립트(또는 백업 전용 서버의 pm2 러너)가 실행을 마친 뒤 결과를 앱으로 되돌려 보고합니다. 이 콜백이 백업 리포트 계약입니다.

POST {APP_URL}/api/v1/backup/report      (인증: infr.apikey · 무상태 · 세션/CSRF 없음)

콜백을 받으면 App\Services\Backup\BackupReportService 가 세 가지를 원자적으로 수행합니다.

  1. infr_backup_run insert — 신규 SOT(복구지점 시계열). status(done/failed/…)·bytes·artifact_path·checksum 으로 stats-design 레거시 backup_history 의 결함(성공/실패 구분 불가, 용량·아티팩트 부재)을 해소합니다.
  2. 레거시 backup_history 호환 write — Overview 의 백업 성공률·요약 KPI 가 계속 동작하도록 (legacy 커넥션에) 함께 기록합니다. 실패해도 흡수(신규 write 는 이미 성공).
  3. infr_backup_job 갱신 — last_run_at·last_status·next_run_at(스케줄 "N U" 로 재계산)을 업데이트합니다.

이 계약은 "실행"이 아니라 "보고"만 담당합니다. 어떻게 실행되는지(스크립트 생성)는 백업 스크립트 빌더, 무엇을 언제 백업할지는 백업 스케줄 보드를 보세요. 결과가 쌓이는 화면은 Runs 이고, 발생한 복구지점으로 복원 검증은 복구 & 검증에서 합니다.


언제 쓰나


화면 구성

이 항목은 화면이 아니라 통합 계약입니다. 관련 UI 는 세 곳입니다.

위치 하는 일 등급
Administration ▸ API 발급 (/admin/api-keys) 콜백 인증용 API 키 발급(평문 1회 노출)·비활성/삭제 admin(≥10)
Backup ▸ Runs 콜백이 적재한 infr_backup_run 목록·성공/실패·소요 스파크라인 staff(≥2)
Backup ▸ Script Builder report 호출 한 줄(+ INFR_* emit)을 포함한 스크립트 생성 staff(≥2)

사용법

1) 백업 스코프 API 키 발급

Administration ▸ API 발급에서 키를 발급합니다. 발급 시 평문 전체 키(igk_…)가 딱 한 번 노출되므로 그 자리에서 복사해 러너/스크립트에 넣습니다. DB 에는 해시(key_hash)만 저장됩니다.

인시던트 리포터와 섞지 말고 백업 전용 키를 따로 발급하세요. /api/v1/backup/report 는 append-only 라 스코프를 강제하지 않지만(활성 키면 통과), 키를 분리하면 유출 시 폐기 반경(blast radius)을 좁힐 수 있습니다.

2-A) pm2 러너로 중앙 실행 (권장)

scripts/infr_backup_runner.cjs 는 백업 전용 서버에서 pm2 스케줄(단발) 로 매 분 재기동되며, 중앙 MySQL 에서 실행 대상(due) 잡을 골라 최신 스크립트를 돌리고 결과를 자동 보고합니다.

파일 상단 CONFIG 블록만 채웁니다(env 를 읽지 않음).

const CONFIG = {
  DB_HOST: '203.0.113.10',       // 중앙 MySQL(백업 잡·스크립트를 읽어옴)
  DB_PORT: '3306',
  DB_USER: 'infraguard',         // 읽기 권한이면 충분
  DB_PASSWORD: 'YOUR_DB_PASSWORD',
  DB_NAME: 'design',
  APP_URL: 'https://infraguard.example.com',   // 리포트 대상(앱)
  INFR_API_KEY: 'igk_YOUR_BACKUP_KEY',         // API 발급의 백업 스코프 키
  SHELL: '/bin/sh',
};

pm2 등록(단발 · 매 분 재기동):

npm i mysql2                       # 유일한 npm 의존(mysqldump/rsync/ssh 는 OS 바이너리)
pm2 start infr_backup_runner.cjs --name infr-backup-runner \
  --no-autorestart --cron-restart "* * * * *"
pm2 save

실제 백업 주기는 각 잡의 schedule(→ next_run_at)이 결정합니다. 러너는 매 분 깨어나 due 인 것만 골라 실행합니다. 러너 자체는 상주하지 않고 매 실행이 새 프로세스(DB 연결 1개 open/close · 누수 없음)입니다.

2-B) 스크립트가 직접 report 호출

러너를 쓰지 않는다면, 대상 호스트의 백업 스크립트 말미에서 직접 콜백을 부릅니다. 빌더 골격은 이 부분을 자동으로 넣어 줍니다.

#!/bin/sh
set -eu
OUT="/data/backup/design_$(date +%Y%m%d%H%M).sql"
mysqldump --defaults-extra-file=/etc/infr/design.cnf --opt --routines --events design > "$OUT"

# ── 러너/빌더가 파싱하는 결과 emit 라인 ──
echo "INFR_ARTIFACT=$OUT"
echo "INFR_BYTES=$(wc -c < "$OUT")"
echo "INFR_CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)"

# ── report 콜백(러너를 쓰지 않는 경우 직접 호출) ──
curl -s -X POST "https://infraguard.example.com/api/v1/backup/report" \
  -H "Authorization: Bearer igk_YOUR_BACKUP_KEY" \
  -d "job_id=12&status=done&backup_type=db&bytes=$(wc -c < "$OUT")&duration_sec=$SECONDS&artifact_path=$OUT"

3) 연결 확인

배포 후 키·연결을 확인하려면 ping 을 먼저 두드립니다.

curl -s https://infraguard.example.com/api/v1/ping \
  -H "Authorization: Bearer igk_YOUR_BACKUP_KEY"
# → {"ok":true, ...}

report 계약 (요청 필드)

폼 인코딩 또는 JSON 바디로 보냅니다. 서버가 검증·정규화합니다.

필드 필수 타입/제약 설명
job_id ✔ integer ≥ 1 대상 infr_backup_job.id. 없으면 422.
status string ≤ 16 running·done·failed·skipped. 그 외 값은 done 으로 정규화. 기본 done.
backup_type string ≤ 16 db·web·volume·custom. 그 외는 db. (레거시 write 는 volume/custom→db)
bytes integer ≥ 0 아티팩트 용량.
artifact_path string ≤ 255 산출물 경로. artifact_name 은 basename() 으로 자동 파생.
checksum string ≤ 80 무결성 해시(체크섬).
duration_sec integer ≥ 0 소요(초). started_at = finished_at − duration 으로 역산.
target_path string ≤ 255 저장 디렉터리(레거시 호환용). 비면 dirname(artifact_path).
server_ip string ≤ 45 실행 호스트 IP. 비우면 서버가 요청 IP 로 보정.
has_schedule bool 스케줄 유래 여부(기본 1).
message string ≤ 2000 실패 사유·로그 꼬리(러너는 stderr 마지막 400자).
backup_id integer 레거시 backup_history 참조(선택).

응답

성공 시 201 Created:

{ "ok": true, "run_id": 8123, "status": "done", "key": "backup-runner" }

멱등(중복 재시도 방어)

같은 job_id + artifact_path + status 조합이 최근 2분 내 이미 기록됐으면 새 행을 만들지 않고 기존 run 을 그대로 반환합니다. cron/러너 재시도로 인한 이중 적재를 막습니다.

실패 처리와 재시도


데이터 소스

대상 커넥션 역할
infr_backup_run 기본(infr) 신규 복구지점 SOT(insert). Runs/Recovery 화면 원천.
infr_backup_job 기본(infr) last_run_at·last_status·next_run_at 갱신.
infr_generated_script 기본(infr) 러너가 실행하는 최신 스크립트 본문.
backup_history legacy 레거시 호환 write(Overview KPI 유지). 실패는 흡수+로그.
infr_api_key 기본(infr) 콜백 인증(key_hash 상수시간 대조)·사용 흔적(last_used_at·use_count).

관련 파일:


권한

작업 게이트
POST /api/v1/backup/report infr.apikey(활성 키면 통과 · 스코프 미강제 · 키별 분당 120회 레이트리밋)
API 키 발급/비활성/삭제 (/admin/api-keys) admin(≥10) — infr.level:10 + 컨트롤러 self-gate + 감사
키 평문 복구(러너 메시업 주입, secret_enc) level≥5 + 감사
Backup 화면(Board/Runs/Recovery/Builder) 조회·쓰기 staff(≥2) — infr.level:2

인증 헤더는 둘 중 하나: Authorization: Bearer igk_… 또는 X-Infr-Api-Key: igk_…. 저장된 해시와 상수시간으로 비교하며, 키 모델을 request attribute 로 stash 합니다.

스크립트·크론·로그에 DB 비밀번호나 API 키를 평문으로 남기지 마세요. mysqldump 는 --defaults-extra-file(0600), rsync/ssh 는 신뢰망 키를 쓰고, INFR_API_KEY 는 러너 CONFIG 또는 관리자 코드 메시업 주입(감사)으로만 전달합니다.


문제해결 · FAQ

증상 원인 · 조치
401 {"error":"missing_api_key"} 헤더 누락. Authorization: Bearer igk_… 또는 X-Infr-Api-Key 확인.
401 {"error":"invalid_api_key"} 키가 비활성이거나 해시 불일치. 재발급하거나 활성 상태 확인.
429 {"error":"rate_limited"} 키별 분당 120회 초과. Retry-After 초 뒤 재시도. 여러 잡을 한 러너 tick 에 몰지 말 것.
422 {"error":"validation"} job_id 누락/비정수 등. 필드 표 참조.
러너 로그 mysql2 미설치 백업 서버에서 npm i mysql2.
러너 로그 중앙 DB … 미설정 → 종료 CONFIG 의 DB_HOST/DB_USER/DB_NAME 빈 값 또는 {{TOKEN}} 미치환.
러너 로그 리포트 전송 실패 → 다음 tick 재시도 APP_URL/INFR_API_KEY 확인·방화벽·TLS. 다음 tick 에 재시도됨.
Runs 에 결과가 안 보임 job_id 가 실제 존재하는지, 2분 멱등 창에 중복 흡수됐는지 확인.
Overview 백업 KPI 만 비어 있음 레거시 backup_history write 실패(흡수+경고 로그). storage/logs 에서 backup_history compat write failed 확인.
같은 백업이 매 tick 반복 실행 report POST 가 앱에 도달하지 못해 next_run_at 미갱신. 러너→앱 네트워크 점검.

관련 항목

API 키 발급 화면
API 키 발급 화면