Skip to Content

백업 · DR

3-tier 전략

Tier A — 로컬 (실시간 보호) restic repo /Users/axe/.axe/backups/local/ 매일 03:00 KST, com.axe.backup.local Tier B — P2P ring (cross-customer 양방향) axe-macmini ↔ realchoice-macmini (SSH key, Tailscale) 매일 03:30 KST, com.axe.ring.push Tier C — Cold SSD (offline 보호) 외장 SSD rotation /Volumes/axe-cold-{1,2,3} mount 시 자동 sync

Tier A — 로컬 (live)

항목
Repo path/Users/axe/.axe/backups/local/
Restic version0.18.1+
PasswordmacOS Keychain (axe.backup.restic.local)
백업 대상frame-postgres dump + blueprint-postgres dump (D-config-17 cutover 후, 2026-05-15~) + hive-postgres dump (D-hive-backup, 2026-05-21~ — Phase 1 조직/휴가 + Phase 3 payroll v2 실데이터) + mysrt-postgres dump (B-mysrt-backup-decision, 2026-06-06~ — users/jobs/push_subscriptions; SRT는 train/예약의 SoT일 뿐 계정·잡설정 SoT 아님) + index-postgres dump (D-index-51, 2026-06-06~ — evidence_blob = 죽은 딜 OneDrive/Blueprint 원본 삭제 후 유일 사본, 312MB bytea → ~633MB pg_dumpall, restic content-dedup 으로 ~274MB stored) + .local/files/ (platform data)
빈도매일 03:00 KST (com.axe.backup.local launchd)
현재 크기~429 MiB raw-data (2026-06-06 — index-postgres 흡수 후, restic dedup 적용)
Excludes/Users/axe/.axe/bin/restic-excludes

명령어

# 수동 백업 axe backup --local # 백업 상태 확인 restic -r /Users/axe/.axe/backups/local snapshots \ --password-file <(security find-generic-password -w -s axe.backup.restic.local) # 복원 (dry-run) axe restore --customer axe --from local --as-of 2026-05-20 --dry-run

Tier B — P2P ring

axe ↔ realchoice 양방향 백업 (각자 다른 macmini 의 백업을 보관).

axe-macmini → realchoice-macmini 의 /Users/realchoice/peer-backups/axe/ realchoice-macmini → axe-macmini 의 /Users/axe/.axe/backups/peer/realchoice/
항목
전송restic sftp: backend (SSH key)
authSSH key (~/.ssh/id_ed25519, 비밀번호 없음)
빈도매일 03:30 KST (com.axe.ring.push launchd)
검증bidirectional_ssh 2026-05-15 검증 완료

customers.yaml 에 ring backup 메타 등록:

realchoice: ring_backup: ssh_user: "realchoice" ssh_fqdn: "realchoice-macmini.tail090015.ts.net" ssh_ip: "100.114.161.51" receive_dir_at_peer: "/Users/realchoice/peer-backups/axe/" receive_dir_at_self: "/Users/axe/.axe/backups/peer/realchoice/" restic_version: "0.18.1" disk_available_gib: 43 bidirectional_ssh: true

함정

  • Tailscale alone 으로는 충분하지 않음 — SSH key 가 별도 필요. Tailscale 인증 없어도 작동.
  • Cross-customer 백업 = 데이터 노출 risk — 양측 운영자 모두 합의 + restic 암호화로 컨텐츠 보호.

Tier C — Cold SSD (offline)

외장 SSD 를 분기별로 rotation. mount 가 감지되면 자동 sync.

항목
Mount path/Volumes/axe-cold-{1,2,3} (rotation)
Restic repo/Volumes/axe-cold-N/restic-repo/ (N = 1, 2, 3 — rotation index)
Password종이 메모 (vault 안에 넣지 말 것 — closed-loop 방지)
Rotation분기별 (Q1 = SSD #1, Q2 = SSD #2, …)
Drill분기마다 자동 restore drill (com.axe.restore-drill, Jan/Apr/Jul/Oct 15 03:00 KST)

왜 종이?

cold storage 의 password 를 self-host vault 에 넣으면 vault 가 사라졌을 때 cold storage 도 못 풉니다 (closed loop). 종이 + 운영자 머리 = 이중화.

복원 절차

시나리오 1 — frame DB 손상 (오타 / migration 사고)

# 1. 어제 03:00 KST 백업 복원 axe restore --customer axe --tier local --as-of 2026-05-20T03:00 --target frame-postgres # 2. frame 재시작 docker compose restart frame-mcp-blue frame-mcp-green # 3. 정합성 검사 docker exec frame-mcp-blue python -m frame.cli integrity-check --entity axec

시나리오 1b — blueprint DB 손상

# 1. 어제 03:00 KST 백업 복원 axe restore --customer axe --tier local --as-of 2026-05-20T03:00 --target blueprint-postgres # 2. blueprint app + mcp 재시작 (PR #337 이후 blue/green pair) docker compose -f /Users/axe/blueprint/docker/docker-compose.yml restart app-green blueprint-mcp-blue blueprint-mcp-green # 3. 정합성 검사 (Prisma client round-trip) docker exec blueprint-app-green node -e "const {PrismaClient}=require('@prisma/client'); new PrismaClient().workspace.count().then(n=>console.log('workspaces:',n))"

시나리오 1c — hive DB 손상

# 1. 어제 03:00 KST 백업 복원 axe restore --customer axe --tier local --as-of 2026-05-20T03:00 --target hive-postgres # 2. hive 재시작 (Phase 1 조직/휴가 + Phase 3 payroll v2) docker compose -f /Users/axe/hive/docker-compose.yml restart hive-postgres hive-mcp-blue hive-mcp-green # 3. 정합성 검사 (axec/axev 테넌트별 employee count round-trip) docker exec hive-postgres psql -U hive -d hive -c \ "SELECT 'axec' AS tenant, COUNT(*) FROM axec.employees UNION ALL SELECT 'axev', COUNT(*) FROM axev.employees;"

시나리오 2 — macmini 자체 손실 (도난, 화재)

# 1. 새 macmini 셋업 (Tailscale 설치, axe CLI install) # 2. ring peer 에서 받기 axe restore --customer axe --tier ring --from realchoice --as-of 2026-05-20 # 3. 또는 cold SSD 마운트 + restore axe restore --customer axe --tier cold --as-of 2026-05-20

시나리오 3 — 사일런트 corruption

frame integrity-check --entity axec 가 detect 하면:

# 가장 가까운 valid snapshot 으로 부분 복원 axe restore --customer axe --tier local --table journal_line --as-of <last-valid>

Restore drill (자동)

분기마다 자동으로:

  1. 임시 staging container 에 어제 backup 복원
  2. frame integrity-check 통과 여부 확인
  3. 결과를 운영자 Slack 으로 보고

drill 자체가 실패하면 cold SSD rotation 시도 또는 ring peer 복원.

함정 모음

함정결과회피
Vault 에 cold storage password 저장closed loop, vault 손실 시 cold 도 못 풂종이 메모
restic password 분실백업 영구 손실Keychain + 종이 이중화
backup excludes 누락 (.local/files)핵심 데이터 백업 안 됨restic-excludes 명시
docker exec frame-postgres pg_dump 만으로 신뢰container 죽으면 dump 못 함volume 자체 백업 병행
ring backup 단방향한쪽 손실 시 복구 불가bidirectional_ssh 검증 필수
index-postgres 백업 누락evidence_blob = 죽은 딜 OneDrive/Blueprint 원본 삭제 후 유일 사본 → 영구 소실axe-backup index 블록 (D-index-51) + index export-evidence round-trip drill
Last updated on