- 전 운영 문서 테이블명 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
119 lines
10 KiB
Text
119 lines
10 KiB
Text
{
|
||
"$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 갱신 — 코드 수정 불필요."
|
||
}
|
||
]
|
||
}
|