백업 · 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 시 자동 syncTier A — 로컬 (live)
| 항목 | 값 |
|---|---|
| Repo path | /Users/axe/.axe/backups/local/ |
| Restic version | 0.18.1+ |
| Password | macOS 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-runTier 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) |
| auth | SSH 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 (자동)
분기마다 자동으로:
- 임시 staging container 에 어제 backup 복원
frame integrity-check통과 여부 확인- 결과를 운영자 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