POSA_LEAKSMS/.tours/architect-full-system.tour
cw2007 fb3630d868 docs: 문서 현행화(leak_ 접두사·경로 스크럽) + 코드 투어 + settings deny 수정
- 전 운영 문서 테이블명 leak_ 접두사 반영, 삭제된 마이그레이션 참조 정리,
  구경로 raspi_leck_detecter→<WEB_DIR> 스크럽(DEPLOYMENT_PLAN).
- docs/DEPLOYMENT_PLAN_RPI4x3.md(신규): 3대 Pi + 신규 서버 배포 로드맵.
- .tours/architect-full-system.tour(신규): 19스텝 전체 아키텍처 투어.
- README.md(루트, 0바이트였음): 시스템 인덱스.
- .claude/settings.local.json: /doctor 지적 deny 규칙 5건 수정
  (파이프 4건 와일드카드형, 포크밤 삭제) + 죽은 allow 항목 3건 정리.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821XP36Q6t9eWKTPSEumnC
2026-07-02 18:52:15 +09:00

119 lines
10 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"$schema": "https://aka.ms/codetour-schema",
"title": "전체 시스템 아키텍처 투어 — 누수+온습도 모니터링 (RPi4×3 → cafe24)",
"description": "센서 노드(RPi4) → HMAC 서명 HTTPS 수집 → MySQL(leak_ 접두사 테이블) → SMS 경보/대시보드까지 전체 데이터 경로를 따라가는 아키텍처 투어. v2607 기준 (테이블 leak_ 접두사, sensor_id 3 신규 지점, 턴키 배포 번들).",
"ref": "feat/pi-c-sensor3-leak-node",
"steps": [
{
"file": "SYSTEM_OVERVIEW.md",
"line": 1,
"title": "1. 전체 그림",
"description": "**상황**: 2종 센서 모듈(누수+카메라 / SHT30 온습도)이 단일 cafe24 PHP 서버로 보고하는 통합 시스템. **구조**: 모든 모듈이 동일한 raw-body HMAC-SHA256 X-Signature 스킴 사용. **왜 중요**: 이 문서가 모듈↔서버 계약(서명·sensor_id·엔드포인트)의 단일 출처. **주의**: v2607부터 DB 테이블 전체가 leak_ 접두사 — 같은 DB의 타 시스템 테이블과 격리."
},
{
"directory": "raspberry_pi4",
"title": "2. 모듈 1 — 누수 + 카메라 (sensor_id 1, 3)",
"description": "**상황**: e-con 5C-08 저항형 누수센서(GPIO17, 외부 4.7MΩ 풀다운) + Pi Camera. **구조**: leak_sensor.py 단일 데몬 + config.py(비밀 아님) + config_local.py(비밀, 미커밋). **왜 중요**: Pi-A(sensor 1)와 Pi-C(sensor 3, 신규 지점)가 같은 코드 폴더를 씀 — 기기별 차이는 전부 config_local.py로."
},
{
"file": "raspberry_pi4/config.py",
"line": 13,
"title": "3. 센서 정의 + 보고 주기",
"description": "**상황**: SENSORS 리스트가 이 기기의 sensor_id/GPIO를 정의. **구조**: REPORT_INTERVAL=600초 — 서버 HEARTBEAT_TIMEOUT_SEC(1200초)의 절반이라 2회 누락 시 오프라인 판정. **주의**: 예전 3600초는 매시간 오프라인 오탐을 냈던 버그 — 주기를 늘릴 땐 반드시 서버 타임아웃과 함께 판단."
},
{
"file": "raspberry_pi4/config.py",
"line": 92,
"title": "4. 기기별 오버라이드 (Pi-C 핵심)",
"description": "**상황**: config_local.py에 SENSORS가 있으면 기본값을 덮어씀. **구조**: 2단 import — 필수 비밀값은 fail-closed(없으면 기동 거부), SENSORS는 선택. **왜 중요**: Pi-C(sensor_id 3)는 추적 파일(config.py)을 건드리지 않고 배포 가능 — 기기별 drift 방지. **주의**: sensor_id 1/2 재사용 시 기존 지점 상태를 조용히 덮어씀(leak_sensor_status는 sensor_id 단독 키)."
},
{
"file": "raspberry_pi4/leak_sensor.py",
"line": 194,
"title": "5. 클라이언트 서명 — raw-body HMAC",
"description": "**상황**: 요청 본문 바이트에 HMAC-SHA256(API_KEY)을 계산해 X-Signature 헤더로 전송. **구조**: 본문을 고정 필드 순서로 직렬화한 뒤 그 바이트 그대로 서명·전송 — 서버도 같은 바이트로 검증. **주의**: 구버전 sha256(API_KEY+정렬JSON) 스킴은 폐기 — 서버가 기본 거부(ALLOW_LEGACY_BODY_SIGNATURE=false)."
},
{
"file": "raspberry_pi4/leak_sensor.py",
"line": 451,
"title": "6. 보고 상태머신",
"description": "**상황**: 메인 루프의 주기 선택 — 정상 600초, 누수 중 10초(LEAK_REPORT_INTERVAL). **구조**: 누수 감지 → leak_alert 즉시 보고 + 사진 촬영/업로드 → 지속 재보고 → 복구 시 leak_recovery. **왜 중요**: SMS 중복 방지는 클라이언트가 아니라 서버 쿨다운이 담당 — 클라이언트는 상태만 계속 밀어넣는 단순 설계."
},
{
"file": "raspberry_pi4_sht30/sht30_sensor.py",
"line": 179,
"title": "7. 모듈 2 — SHT30 온습도 (sensor_id 2)",
"description": "**상황**: I2C(0x44) SHT30을 300초 주기로 측정·보고하는 메인 루프. **구조**: 측정 → CRC 검증 → 같은 HMAC 서명 → 같은 엔드포인트. 임계 판정은 전부 서버 몫. **주의**: smbus2 부재/I2C 실패 시 시뮬레이션 값으로 폴백(WARN 로그만) — 배포 검증 때 journalctl에서 시뮬레이션 경고 부재를 반드시 확인."
},
{
"file": "php/api/sensor_data.php",
"line": 73,
"title": "8. 수집 경계 — fail-closed 인증",
"description": "**상황**: 모든 센서 보고가 들어오는 단일 진입점. **구조**: API_KEY가 기본값이면 503 거부 → 크기 상한 → 타임스탬프 ±600초 → raw-body HMAC 검증 순서. **왜 중요**: 여기가 시스템의 유일한 신뢰 경계 — 이 파일 통과 = DB 기록 + SMS 발송 권한."
},
{
"file": "php/api/sensor_data.php",
"line": 112,
"title": "9. 재전송(replay) 멱등 방어",
"description": "**상황**: 서명 자체를 nonce로 leak_ingest_nonce에 기록. **구조**: 같은 서명 재수신 = 중복 → 재저장/재발송 생략. 시계오차 창 지나면 자동 정리. **주의**: 테이블 없으면 조용히 비활성(graceful degrade) — setup_wizard가 이 테이블 누락을 검사하지 않으므로 신규 설치 후 7개 테이블 존재를 직접 확인."
},
{
"file": "php/api/sensor_data.php",
"line": 371,
"title": "10. 누수 사고(incident) 추적",
"description": "**상황**: leak_alert 수신 시 leak_incident 에피소드 개시, leak_recovery 시 종료. **구조**: sensor_id 단위로 열린 사고를 찾아 갱신 — sensor_id 3도 코드 수정 없이 동작(자동 등록 upsert). **왜 중요**: 사고 단위 추적이라 SMS·사진·대시보드 이력이 에피소드로 묶임."
},
{
"file": "php/api/photo_upload.php",
"line": 221,
"title": "11. 사진 업로드 검증 체인",
"description": "**상황**: 누수 사진 전용 업로드 엔드포인트. **구조**: canonical 문자열(device_id|sensor_id|log_id|timestamp) HMAC 서명 → log_id 실존 확인 → finfo MIME(image/jpeg만) → 크기 상한 → 서버 생성 무작위 파일명. **주의**: 파일명을 클라이언트가 정하지 않음 — 경로 조작 원천 차단. 저장 위치 php/var/photos/는 웹에서 403."
},
{
"file": "php/sms_send.php",
"line": 546,
"title": "12. SMS 경보 상태머신",
"description": "**상황**: 누수 SMS 발송 함수 — 쿨다운(기본 30분) 적용, 복구 SMS는 경보 후에만. **구조**: 별도 상태 테이블 없이 leak_sms_log의 메시지 접두 태그([누수감지] 등)가 사실상 상태 키 — 태그는 config.php SMS_TAG_* 상수가 단일 출처. **주의**: 태그 문자열을 직접 쓰면 여러 파일의 LIKE 검색이 어긋나 무성 장애 — 반드시 상수 참조."
},
{
"file": "php/cron_heartbeat.php",
"line": 22,
"title": "13. 오프라인 감지 (cron 1분)",
"description": "**상황**: last_seen이 HEARTBEAT_TIMEOUT_SEC(1200초) 초과한 센서를 찾아 오프라인 SMS. **구조**: offline_alerted 0→1 원자적 선점(UPDATE ... WHERE offline_alerted=0)으로 다중 진입점 동시 실행에도 중복 발송 방지. **주의**: 타임아웃은 전 센서 공통 — 어떤 모듈의 보고 주기보다 길어야 오탐 없음."
},
{
"file": "php/config.php",
"line": 217,
"title": "14. LEAK_SENSOR_IDS — 지점 확장점",
"description": "**상황**: 대시보드 누수 카드 표시 대상 sensor_id 목록 (기본 [1]). **구조**: config.local.php에서 [1,3] 오버라이드 — 1..255 정수만 통과, 잘못된 값은 버리고 비면 [1] 복귀. **왜 중요**: 신규 누수 지점 추가 시 서버 코드 수정 없이 이 설정 하나로 대시보드 노출."
},
{
"file": "php/dashboard.php",
"line": 700,
"title": "15. 대시보드 — 누수 카드 렌더",
"description": "**상황**: LEAK_SENSOR_IDS 루프로 지점별 누수 카드 렌더 (v2607 이전엔 sensor_id 1 하드코딩). **구조**: 우선순위 오프라인 > 누수 > 정상, 미수신이면 '데이터 없음' 카드. **주의**: 온습도 차트는 여전히 sensor 2 고정 — SHT30 지점을 추가하면 차트 선택 UI 작업 필요(누수 지점은 무관)."
},
{
"file": "php/login.php",
"line": 110,
"title": "16. 관리자 인증 — 비밀번호 + TOTP",
"description": "**상황**: bcrypt 해시 검증 + TOTP MFA. **구조**: ADMIN_TOTP_SECRET 미설정이면 로그인 자체가 불가(fail-closed) — setup_mfa.php?token= 으로 최초 1회 등록 후 MFA_SETUP_TOKEN을 반드시 비움. **주의**: 세션 쿠키 Secure라 HTTPS 없이는 로그인 불가 — cafe24 패널에서 HTTPS 강제가 선행 조건."
},
{
"file": "sql/schema_sht30.sql",
"line": 24,
"title": "17. 스키마 — 단일 파일, leak_ 네임스페이스",
"description": "**상황**: 신규 설치용 완전 스키마 — 테이블 7개(leak_sensor_log/status/metric, leak_sms_log, leak_ingest_nonce, leak_incident, leak_photo) 전부 이 파일 하나. **왜 중요**: v2607에서 구버전 마이그레이션 파일 폐기 — 이 파일이 유일한 DB 설치 경로. **주의**: 컬럼명은 접두사 없음(sensor_id, is_leak 등) — 테이블명만 leak_."
},
{
"file": "tools/make_deploy_bundles.py",
"line": 29,
"title": "18. 턴키 배포 번들 생성기",
"description": "**상황**: deploy/ 아래 서버 1 + Pi 3대 폴더를 생성 — API_KEY 자동 생성·4곳 동일 주입, 관리자 bcrypt, LF 정규화. **구조**: WEB_DIR/BASE_URL만 고치고 재실행하면 전체 재생성(.secrets/ 비밀 보존). **주의**: deploy/는 비밀 포함이라 gitignore — 절대 커밋 금지. 남는 수동 설정은 DB 계정·SMS 계정·TOTP 등록 3가지뿐."
},
{
"title": "다음 단계",
"description": "이제 센서 GPIO 인터럽트부터 SMS 발송까지 전체 경로를 파일 단위로 추적할 수 있다. 배포 절차는 docs/DEPLOYMENT_PLAN_RPI4x3.md, 기기별 설치는 deploy/*/SETUP.md, 보안 증적은 docs/SOURCE_SECURITY_EVIDENCE.md 참고. 신규 지점 추가 = Pi 폴더 복사 + config_local.py(SENSORS id) + 서버 LEAK_SENSOR_IDS 갱신 — 코드 수정 불필요."
}
]
}