diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1f77a59..47772cb 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,10 +1,7 @@ { "permissions": { "allow": [ - "Bash(tail:*)", - "Bash(cd \"C:/Users/Posafe/leak_sms_v2/windows_checker\" && pyinstaller --onefile --noconsole --name sensor_checker sensor_checker.py 2>&1)", - "Bash(cd \"C:/Users/Posafe/leak_sms_v2/windows_checker\" && python sensor_checker.py 2>&1)", - "Bash(sort -k2)" + "Bash(tail:*)" ], "deny": [ "Bash(rm -rf:*)", @@ -14,13 +11,12 @@ "Bash(chmod 777:*)", "Bash(dd:*)", "Bash(mkfs:*)", - "Bash(:(){ :|:& };:)", "Bash(git push --force:*)", "Bash(git push -f:*)", - "Bash(curl:* | sh)", - "Bash(curl:* | bash)", - "Bash(wget:* | sh)", - "Bash(wget:* | bash)", + "Bash(curl* | sh)", + "Bash(curl* | bash)", + "Bash(wget* | sh)", + "Bash(wget* | bash)", "Bash(ssh:*)", "Bash(scp:*)", "Read(./**/config.local.php)", diff --git a/.tours/architect-full-system.tour b/.tours/architect-full-system.tour new file mode 100644 index 0000000..ec30cdc --- /dev/null +++ b/.tours/architect-full-system.tour @@ -0,0 +1,119 @@ +{ + "$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 갱신 — 코드 수정 불필요." + } + ] +} diff --git a/README.md b/README.md index e69de29..71a68e9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,23 @@ +# 서버실 통합 모니터링 시스템 (누수 + 온습도) + +Raspberry Pi 4 센서 모듈 2대 — **1번: 누수 감지 + 카메라**, **2번: SHT30 온습도** — 와 +선택적 **3번째 노드**(신규 지점, sensor_id 3)가 **동일한 cafe24 PHP 서버**로 HTTPS 보고 +(raw-body 서명 `X-Signature = HMAC-SHA256(API_KEY, 요청본문)`)를 하고, 서버가 MySQL 저장, +임계/누수 판정, SMS 발송, 통합 대시보드(`dashboard.php`) 표시를 담당하는 단일 시스템입니다. + +## 모듈 구성 + +| 폴더 | 역할 | sensor_id | +|---|---|---| +| `raspberry_pi4/` | 누수 감지(e-con 5C-08) + Pi Camera 촬영·사진 업로드 | 1 (신규 지점 노드는 3) | +| `raspberry_pi4_sht30/` | SHT30 온습도 측정(I2C) | 2 | +| `php/` | cafe24 서버 — API 수신, DB 저장, 판정/SMS, 대시보드 | — | +| `sql/` | DB 스키마(`schema_sht30.sql` = 신규 설치 완결) + 마이그레이션 | — | +| `firmware/` | STM32F407 마이그레이션 트랙(별도) — **RPi4 배포와 무관** | — | + +## 문서 + +- [SYSTEM_OVERVIEW.md](./SYSTEM_OVERVIEW.md) — 전체 아키텍처·모듈 매핑·서명 스킴 (기준 문서) +- [docs/DEPLOYMENT_PLAN_RPI4x3.md](./docs/DEPLOYMENT_PLAN_RPI4x3.md) — RPi4 × 3 + cafe24 신규 설치 배포 계획 +- [docs/INSTALL_PI_SERVER.md](./docs/INSTALL_PI_SERVER.md) — cafe24 서버 설치 절차 (STM32 펌웨어 중심) +- [docs/OPERATIONS_SECURITY_CHECKLIST.md](./docs/OPERATIONS_SECURITY_CHECKLIST.md) — 운영·보안 체크리스트 diff --git a/SYSTEM_OVERVIEW.md b/SYSTEM_OVERVIEW.md index d732c86..757b0c0 100644 --- a/SYSTEM_OVERVIEW.md +++ b/SYSTEM_OVERVIEW.md @@ -25,7 +25,7 @@ | 구분 | 폴더 | 보드 | 센서 | sensor_id | 서버 보고 | |---|---|---|---|---|---| | **1번 모듈** | `raspberry_pi4/` | Raspberry Pi 4 | e-con 5C-08 누수 + Pi Camera | 1 | `leak_alert`/`leak_recovery`/`periodic` + `photo_upload.php` | -| **2번 모듈** | `raspberry_pi4_sht30/` | Raspberry Pi 4 | SHT30 온습도 (I2C) | 2 | `startup`/`periodic` + 온도/습도 (`sensor_metric`) | +| **2번 모듈** | `raspberry_pi4_sht30/` | Raspberry Pi 4 | SHT30 온습도 (I2C) | 2 | `startup`/`periodic` + 온도/습도 (`leak_sensor_metric`) | 두 모듈 모두: - 비밀값은 `config_local.py`(미커밋)에만 — `cp config_local.example.py config_local.py` 후 채움. @@ -36,8 +36,8 @@ ## 서버 (cafe24 `php/`) - 웹 루트(예): `public_html/raspi_leck_detecter/` — `docs/INSTALL_PI_SERVER.md` 참고. -- DB 스키마: `sql/schema_sht30.sql` (신규) 또는 `sql/migration_drop_leak.sql` (전환). -- 대시보드: `dashboard.php` — `sensor_status`/`sensor_metric`/`sms_log` 표시, 기본 차트 sensor 2. +- DB 스키마: `sql/schema_sht30.sql` — 신규 설치는 이 파일 하나로 완결(테이블 전체 `leak_` 접두사). +- 대시보드: `dashboard.php` — `leak_sensor_status`/`leak_sensor_metric`/`leak_sms_log` 표시, 기본 차트 sensor 2. - 인증: `X-Signature = HMAC-SHA256(API_KEY, raw_body)`. 모든 모듈의 `API_KEY` = 서버 `config.local.php` 의 `API_KEY`. --- @@ -54,21 +54,20 @@ 본문 JSON 을 고정 필드 순서 문자열로 만들어 그 바이트에 서명하고 그대로 POST 합니다. (구버전의 `sha256(API_KEY + 정렬 JSON)` body-field 서명은 폐기 — 서버 기본 설정이 거부.) 사진 업로드 서명 canonical = `device_id|sensor_id|log_id|timestamp` (공백 없음). -- **스키마(가산·멱등):** `sensor_log.is_leak` / `sensor_status.is_leak` 컬럼, `event_type` 의 +- **스키마(가산·멱등):** `leak_sensor_log.is_leak` / `leak_sensor_status.is_leak` 컬럼, `event_type` 의 `leak_alert`/`leak_recovery`, `leak_incident`(사고 추적) + `leak_photo`(사진 메타) 테이블을 - 다시 추가했습니다. `sensor_metric`(SHT30)은 그대로 보존합니다. -- **수신:** `api/sensor_data.php` 가 누수 이벤트를 받아 `sensor_log`/`sensor_status` 갱신, + 다시 추가했습니다. `leak_sensor_metric`(SHT30)은 그대로 보존합니다. +- **수신:** `api/sensor_data.php` 가 누수 이벤트를 받아 `leak_sensor_log`/`leak_sensor_status` 갱신, `leak_incident` 개시/종료, `leak_alert` 응답에 `log_id` 반환. SHT30 측정 경로는 100% 유지. - **사진:** 새 `api/photo_upload.php` (서명·신선도·MIME(finfo)·크기 상한·서버 생성 무작위 파일명). - **SMS:** `sms_send.php` 에 `send_leak_alert_sms`(쿨다운) / `send_leak_recovery_sms`(경보 후에만). -### 운영 적용 절차 (기존 SHT30 서버에 누수 통합) +### 운영 적용 절차 (신규 설치) -1. 기존 서버에서 **`sql/migration_add_leak.sql` 1회 실행**(가산·멱등 — 운영 데이터 보존, 재실행 안전). - 신규(통합) 설치는 `sql/schema_sht30.sql` 하나만 실행하면 됩니다(이미 누수 요소 포함). +1. **`sql/schema_sht30.sql` 1회 실행** — 이 파일 하나로 완결(누수 요소 포함, 테이블 전체 `leak_` 접두사). + (구버전 무접두 테이블 대상 마이그레이션 파일들은 v2607 테이블 리네임과 함께 폐기되었습니다.) 2. 아래 서버 파일을 (재)배포: - `api/sensor_data.php`, `api/photo_upload.php`, `sms_send.php`, `dashboard.php`, `config.php`, - `sql/migration_add_leak.sql`. + `api/sensor_data.php`, `api/photo_upload.php`, `sms_send.php`, `dashboard.php`, `config.php`. 3. **쓰기 가능한 `php/var/photos/`** 디렉터리 확보(웹서버 사용자 쓰기 권한; 없으면 업로드 시 자동 생성 시도). 4. 1번 모듈 `raspberry_pi4/config_local.py` 의 `API_URL`/`PHOTO_UPLOAD_URL` 을 이 통합 서버로, `API_KEY` 를 서버 `config.local.php` 의 `API_KEY` 와 동일하게 설정. diff --git a/docs/DEPLOYMENT_PLAN_RPI4x3.md b/docs/DEPLOYMENT_PLAN_RPI4x3.md new file mode 100644 index 0000000..1aa77d9 --- /dev/null +++ b/docs/DEPLOYMENT_PLAN_RPI4x3.md @@ -0,0 +1,279 @@ +# 배포 계획: 누수+온습도 통합 모니터링 시스템 (cafe24 서버 1 + Raspberry Pi 4 × 3) + +기준 저장소: `C:/Users/USER/Desktop/complete/leak_sms_v3` +확정 조건: RPi4 3대 모두 신품(OS 없음, SD 새로 플래시), 설치 현장 인터넷 가용(오프라인 vendor 번들은 선택이나 재현성 위해 권장), cafe24 서버 신규 설치. + +--- + +## 1. 전체 로드맵 + +**순서 원칙: 서버 먼저.** 모든 Pi의 `test_connection.py` 검증과 API_KEY 발급이 서버에 의존하므로 서버가 선행되어야 한다 (`raspberry_pi4/config_local.example.py`, `raspberry_pi4_sht30/README.md` — API_KEY는 서버 `config.local.php`와 byte-단위 동일해야 함). + +``` +[0단계] cafe24 서버 신규 설치 ──────────────► API_KEY 확정, 엔드포인트 URL 확정 + │ + ├─ (병렬 가능) SD 카드 3장 OS 플래시 + 공통 OS 프로비저닝 + │ ※ 서버와 의존성 없음 — 서버 작업과 동시 진행 가능 + │ + ▼ +[1단계] Pi-A (모듈1: 누수+카메라, sensor_id=1) ┐ +[2단계] Pi-B (모듈2: SHT30, sensor_id=2) ├─ 서로 독립, 병렬 설치 가능 +[3단계] Pi-C (sensor_id=3, 신규 지점) ┘ (단, Pi-C는 센서 종류 결정 선행 — §6) + │ + ▼ +[4단계] end-to-end 검증 (대시보드 / SMS / 사진 / 오프라인 감지) + 보안 체크리스트 (§7) +``` + +**병렬 가능 구간:** +- 서버 설치(0단계)와 Pi 3대의 OS 플래시·공통 프로비저닝(§3)은 완전 병렬. +- Pi-A / Pi-B / Pi-C의 모듈 설치는 서로 독립 — 서버의 API_KEY만 확정되면 동시 진행 가능. +- (선택) 오프라인 vendor 번들 생성(`vendor.sh`)은 인터넷 연결된 aarch64 Bookworm Pi 1대에서 선행 — 첫 번째로 부팅한 Pi에서 만들어 나머지에 복사하는 흐름 권장 (`raspberry_pi4/vendor.sh`, `raspberry_pi4_sht30/vendor.sh`의 .deb은 타깃 아키텍처에서만 받을 수 있음). + +**Pi-C 블로커:** 센서 종류(누수 / SHT30 / 둘 다) 미결정 → §6의 작업량 비교로 먼저 결정 필요. + +--- + +## 2. 0단계: cafe24 서버 신규 설치 + +### 2.1 DB 스키마 +- [ ] cafe24 phpMyAdmin(또는 MySQL 콘솔)에서 **`sql/schema_sht30.sql` 1회 실행**. 신규 설치는 이 파일 하나로 완결 — leak_sensor_log, leak_sensor_status, leak_sensor_metric, leak_sms_log, leak_ingest_nonce, leak_incident, leak_photo 및 leak 컬럼/ENUM 전부 포함 (`sql/schema_sht30.sql:25-167`). +- [ ] 마이그레이션 파일 없음 — v2607 테이블 리네임(`leak_` 접두사)과 함께 폐기. 신규 설치는 스키마 파일 하나로 완결. + +### 2.2 파일 업로드 +- [ ] `php/` 전체를 웹루트 `public_html//`에 업로드 (`docs/INSTALL_PI_SERVER.md` §3, `php/.htaccess` 헤더). +- [ ] **주의: `docs/INSTALL_PI_SERVER.md:33-56`의 업로드 목록은 STALE** — `api/photo_upload.php`, `photo.php`, `var/.htaccess`, `var/index.html`, `var/photos/`가 누락되어 있음. 누수 사진 파이프라인에 필수이므로 **php/ 트리 전체**를 기준으로 업로드할 것. +- [ ] `php/var/` 를 PHP 쓰기 가능하게 설정 (heartbeat_last.txt, ingest_rejections.json, login_attempts.json, admin_audit.log 저장). 코드 기준 디렉터리 0750 / 파일 0640 (`php/api/photo_upload.php:303,321`) — cafe24 공유호스팅에서 755/775가 필요할 수 있음(갭, §8). `var/photos/`는 첫 업로드 시 자동 생성 + .htaccess 가드 자동 작성 (`php/api/photo_upload.php:139-153, 301-321`). +- [ ] 웹에서 `/var/` 접근이 403인지 확인 (`php/.htaccess:14` + `php/var/.htaccess`). + +### 2.3 config.local.php / API_KEY +- [ ] `php/config.local.example.php` 복사 → `config.local.php` 작성: + - `DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASS` — cafe24 MySQL 자격증명 + - `API_KEY` — **긴 랜덤 비밀키 생성.** 기본값 `'change-this-api-key'`이면 ingest가 503으로 거부됨(fail-closed, `php/api/sensor_data.php:73-77`). 이 키가 Pi 3대 전부의 `config_local.py` API_KEY와 byte-단위 동일해야 함. + - `ALLOW_LEGACY_BODY_SIGNATURE` — **false 유지** (`php/config.php:60`, `php/ops_checks.php:101`) + - `SMS_USER_ID / SMS_SECURE / SMS_SENDER / SMS_RECIPIENTS` — cafe24 SMS 계정, **사전 등록된 발신번호**, 잔액 충전 필요 (게이트웨이 `php/config.php:66`) + - 임계값: `METRIC_TEMP_HIGH_C=27`, `METRIC_RH_HIGH=60`, `METRIC_RH_LOW=-1`(저습 비활성), `METRIC_DEWPOINT_MARGIN_C=0`(결로 비활성) — 예시값 기준 (`php/config.local.example.php:44-50`, commit c8b448c) + +### 2.4 관리자 인증 (fail-closed — 미설정 시 로그인 자체가 불가) +- [ ] `php setup_hash.php "새-관리자-비밀번호"` 실행 → bcrypt 출력물을 `ADMIN_PASSWORD_HASH`에 (`docs/INSTALL_PI_SERVER.md:135-141`) +- [ ] MFA 등록: `MFA_SETUP_TOKEN`에 임시 랜덤값 설정 → `setup_mfa.php?token=<토큰>` 접속 → TOTP 앱 등록 → 표시된 `ADMIN_TOTP_SECRET`을 config.local.php에 → **`MFA_SETUP_TOKEN` 반드시 비우기** (`docs/INSTALL_PI_SERVER.md:100-106`, `php/ops_checks.php:100`) +- [ ] `ADMIN_TOTP_SECRET`은 서버에서 생성하기 전까지 **절대 자리표시자 값을 넣지 말 것** (`php/config.local.example.php:33-40`) + +### 2.5 HTTPS / cron +- [ ] HTTPS 리다이렉트는 **cafe24 호스팅 관리 패널에서** 설정 — .htaccess 리다이렉트는 리버스프록시 뒤에서 무한루프 위험으로 의도적으로 비활성 (`php/.htaccess:6-8`, `docs/SOURCE_SECURITY_EVIDENCE.md` §2.1). 세션 쿠키가 Secure라 HTTPS 없이는 로그인 불가. +- [ ] cron 1분 주기: CLI형 `* * * * * php /home//public_html//cron_heartbeat.php`. HTTP형 사용 시 **`?key=` 필수** — `CRON_SECRET = hash_hmac('sha256','cron',API_KEY)` (`php/config.php:199`, `php/cron_heartbeat.php:163-169`). 이 값은 UI 어디에도 표시되지 않아 직접 계산해야 함(갭). `docs/INSTALL_PI_SERVER.md:165`의 URL 예시는 key 파라미터가 빠져 있어 그대로 쓰면 403. +- [ ] (선택) 보존기간 정리: `php retention_cleanup.php --execute` CLI 전용 (`php/retention_cleanup.php:11-16`) — cron 등록 안 하면 365일 보존이 실제로 집행되지 않음. + +### 2.6 서버 검증 +- [ ] `login.php` 로그인(비밀번호+TOTP) → `setup_wizard.php`에서 전체 점검: DB 연결, 필수 테이블, API_KEY 교체 여부, 관리자 해시/TOTP, MFA 토큰 제거, 레거시 서명 비활성, SMS 수신자, 임계값, var 쓰기권한, **테스트 SMS 발송 버튼** (`php/setup_wizard.php:29-45`, `php/ops_checks.php:44-111`) +- [ ] 주의: setup_wizard는 4개 코어 테이블만 검사 — `leak_incident`/`leak_photo`/`leak_ingest_nonce` 누락은 감지 못 하고 조용히 기능 저하됨 (`php/ops_checks.php:51`). 스키마 실행 후 phpMyAdmin에서 7개 테이블 존재를 직접 확인할 것. +- [ ] CORS 오리진이 `https://pores.kr`로 하드코딩되어 있음 (`php/api/sensor_data.php:49`, `php/api/photo_upload.php:29`) — 다른 도메인 배포 시 수정 필요(갭). + +--- + +## 3. 공통: 새 Pi OS 프로비저닝 (3대 공통) + +### 3.1 문서화된 요구사항 (repo에 근거 있음) +| 항목 | 값 | 근거 | +|---|---|---| +| OS | Raspberry Pi OS **Bookworm 64-bit (aarch64)** | `raspberry_pi4/install.sh:4`, `raspberry_pi4_sht30/install.sh:4`, 양쪽 README §2 | +| Python | 시스템 3.11 (Bookworm 기본; wheel이 cp311로 핀) | `vendor.sh` TARGET_PYVER=311 | +| 사용자 | `pi` (sudo 가능) — systemd 유닛 User=pi, 경로 /home/pi/* 하드코딩 | 양쪽 `systemd/*.service`, install.sh SERVICE_USER 기본값 | +| 네트워크 | 유선 Gigabit 또는 WiFi, 서버로 HTTPS egress | 양쪽 README §2 | +| 시각 동기 | NTP 필수 — 서버가 타임스탬프 ±600초 밖이면 거부 | `raspberry_pi4_sht30/README.md:61`, `php/config.php:45` | +| I2C (Pi-B) | install.sh가 자동 처리 (dtparam=i2c_arm=on 등) — raspi-config는 수동 폴백만 | `raspberry_pi4_sht30/install.sh:62-88` | + +### 3.2 문서에 없어 현장에서 결정해야 하는 것 (전부 갭 — repo에 절차 없음) +- **SD 이미징 절차 없음**: OS 이미지 버전/체크섬/Imager 사용법 미문서화. → 결정: Raspberry Pi Imager 등으로 Bookworm 64-bit(Lite 권장 여부도 미명시)를 플래시하되, 사용한 이미지 버전을 기록해 3대 동일하게 맞출 것. +- **`pi` 사용자 생성 절차 없음**: Bookworm 이미지는 기본 `pi` 계정이 없음 — Imager 커스텀 설정 또는 userconf.txt로 `pi` 계정을 만들어야 systemd 유닛/경로가 무수정으로 동작. 비밀번호 정책 미문서화. +- **SSH/헤드리스 활성화 절차 없음**: Imager 설정 또는 boot 파티션 `ssh` 파일 방식 중 결정 필요. +- **호스트네임 규약 없음**: DEVICE_ID(rpi4-leak-01 등)는 애플리케이션 식별자일 뿐 OS 호스트네임 아님. → 결정 권장: 호스트네임을 DEVICE_ID와 일치시켜 운영 혼선 방지. +- **네트워크 설정(고정 IP vs DHCP, WiFi 프로비저닝) 없음.** +- **로케일/타임존, apt 보안 업데이트 정책, OS 하드닝 절차 없음** (§7·§8 참조). + +### 3.3 (선택·권장) 오프라인 vendor 번들 +현장에 인터넷이 있으므로 install.sh 온라인 경로로도 설치 가능하나, 재현성을 위해: +- [ ] 첫 부팅한 Pi(인터넷 연결, aarch64 Bookworm) 1대에서 각 모듈의 `./vendor.sh` 실행 → `wheelhouse/` + `debs/` 생성. **.deb은 반드시 타깃 아키텍처(aarch64 Bookworm)에서만 받을 수 있음** (`vendor.sh:7-10,55`) — x86 빌드머신 불가. +- [ ] 생성된 번들을 나머지 Pi의 모듈 폴더에 복사 → `install.sh`가 자동으로 오프라인 모드 진입(불완전 번들은 하드 에러, 온라인 폴백 없음). +- [ ] 갭: 번들 무결성 해시 검증 절차 없음 — sha256sum 목록을 직접 만들어 전달 권장. + +--- + +## 4. Pi-A: 모듈 1 (누수 + Pi Camera, sensor_id=1) + +소스: `raspberry_pi4/` — 근거는 `raspberry_pi4/README.md`, `install.sh`, `config.py`, `systemd/leak-sensor.service`. + +### 4.1 배선 (README.md §2.1) +- [ ] e-con 5C-08 누수센서 (~1.5MΩ 고임피던스): 센서 입력 → **GPIO17(BCM) = 물리핀 11**, 3.3V → 핀 1, GND → 핀 6 +- [ ] **외부 4.7MΩ 풀다운 필수**, 내부 풀 OFF 유지 (`config.py` USE_INTERNAL_PULL=False → PUD_OFF; 내부 50kΩ 풀다운이 1.5MΩ 센서 신호를 죽임 — README.md:49-53) +- [ ] 로직: ACTIVE_LOW=False — 건조=LOW, 누수=HIGH; 디바운스 500ms (`config.py:20-24`) +- [ ] Pi Camera: Pi 4의 **15핀 1.0mm CSI 커넥터** (Zero 2 W용 22핀 리본 비호환 — 15핀 리본 또는 어댑터 필요). 전원 USB-C 5V/3A(카메라 사용 시 여유 권장). + +### 4.2 설치 +- [ ] 폴더를 `/home/pi/raspberry_pi4`에 배치 (유닛 파일이 경로 하드코딩 — `systemd/leak-sensor.service:12-13,26-27`; 다르면 유닛 수정) +- [ ] `cp config_local.example.py config_local.py` 후 5개 값 기입: + - `API_URL` = `https://<서버>//api/sensor_data.php` + - `PHOTO_UPLOAD_URL` = `.../api/photo_upload.php` + - `API_KEY` = 서버 config.local.php와 byte-단위 동일 + - `DEVICE_ID` = 예: `rpi4-leak-01`, `DEVICE_LOCATION` = 설치 장소 + - 누락 시 fail-closed로 기동 거부 (`config.py:69-83`); 절대 커밋 금지 (`.gitignore`) +- [ ] `chmod +x install.sh && ./install.sh` — 온라인: apt로 `python3-rpi-lgpio python3-requests python3-venv rpicam-apps`(또는 libcamera-apps) 설치; 오프라인 번들 있으면 자동 오프라인. venv(--system-site-packages) + `requests==2.32.3`, 로그파일 `/var/log/leak_sensor.log` (640, pi 소유) 생성. +- [ ] **주의(모순 문서)**: `raspberry_pi4/README.md` §3(57-68행)의 "php/ 서버는 누수 이벤트 거부, 레거시 서버로 향하라"는 안내는 **STALE** — `config_local.example.py:8-16`과 머지된 PR #1 기준 **통합 서버가 정답**. README §3을 따르지 말 것. + +### 4.3 systemd +- [ ] `sudo cp systemd/leak-sensor.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable leak-sensor && sudo systemctl start leak-sensor` +- [ ] 유닛 특성: User=pi, **SupplementaryGroups=video**(카메라 접근 필수), Restart=always/10s, PrivateTmp=true, network-online.target 이후 기동 + +### 4.4 End-to-end 검증 +- [ ] `source venv/bin/activate && python3 test_connection.py` — 3단계: test.php POST / 무서명 POST(거부 확인) / HMAC 서명 POST(200 확인) +- [ ] 수동 실행 `python3 leak_sensor.py` → startup 이벤트가 서버 sensor_log에 기록되는지 대시보드 확인 +- [ ] **카메라 사전 검증(갭 — 문서화된 절차 없음)**: 설치 절차에 카메라 감지 확인이 없어 첫 누수 때에야 실패가 드러남. `rpicam-still`로 수동 테스트샷을 찍어볼 것을 권장. +- [ ] 누수 모의 시험: 센서 단락 → 대시보드 누수 카드(sensor_id=1 전용, `php/dashboard.php:256-264`) 표시 + `[누수감지]` SMS + leak_incident 행 + `photo.php?id=N`으로 사진 열람(관리자 세션 필요) +- [ ] `systemctl status leak-sensor`, `journalctl -u leak-sensor -f` 정상 확인 + +--- + +## 5. Pi-B: 모듈 2 (SHT30 온습도, sensor_id=2) + +소스: `raspberry_pi4_sht30/` — 근거는 해당 README.md, install.sh, config.py, systemd/sht30-sensor.service. + +### 5.1 배선 (README.md §2.1) +- [ ] Sensirion SHT30 (한진데이타 P4422-3), I2C 버스 1: VCC → **3.3V 핀 1 (5V 금지)**, GND → 핀 6 등, SDA → GPIO2/핀 3, SCL → GPIO3/핀 5 +- [ ] **ADDR → GND = 주소 0x44** (VDD면 0x45 — `config.py` I2C_ADDR와 일치시킬 것) +- [ ] SDA/SCL에 3.3V 풀업 4.7kΩ ×2 (모듈 온보드 풀업 있으면 생략) + +### 5.2 설치 +- [ ] 폴더를 `/home/pi/raspberry_pi4_sht30`에 배치 (유닛 경로 하드코딩) +- [ ] `cp config_local.example.py config_local.py` → `API_URL`(Pi-A와 동일 엔드포인트) / `API_KEY`(동일 키) / `DEVICE_ID`(예: rpi4-sht30-01) / `DEVICE_LOCATION` 기입 (fail-closed) +- [ ] `./install.sh` — [1/4] apt: `python3-requests python3-venv i2c-tools`; **[2/4] I2C 자동 활성화**: `/boot/firmware/config.txt`에 dtparam=i2c_arm=on, i2c-dev 모듈 로드+영속화, `usermod -aG i2c pi`; [3/4] venv + `requests==2.32.3, smbus2==0.5.0`; [4/4] `/var/log/sht30_sensor.log` +- [ ] install.sh가 리부트 안내를 출력하면 `sudo reboot` (I2C 신규 활성화 시 — README §4.2). **주의(갭)**: `usermod -aG i2c`도 재로그인/리부트 후 반영 — 리부트 없이 곧바로 test_sensor.py를 돌리면 /dev/i2c-1 권한 오류 가능. + +### 5.3 systemd +- [ ] `sudo cp systemd/sht30-sensor.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable sht30-sensor && sudo systemctl start sht30-sensor` +- [ ] 유닛 특성: User=pi, **SupplementaryGroups=i2c**, Restart=always/10s, PrivateTmp=true. install.sh는 유닛을 설치하지 않으므로 이 수동 단계 누락 금지(갭). + +### 5.4 End-to-end 검증 +- [ ] `i2cdetect -y 1` → **0x44** 표시 확인 (README §5) +- [ ] `python3 test_sensor.py` (CRC/포맷 자가검사 + 1회 측정) — **주의: 하드웨어 없이도 시뮬레이션으로 통과하므로**, i2cdetect 확인을 반드시 선행. 시뮬레이션 폴백이 배선 오류를 가릴 수 있음(갭): 서비스 로그에 시뮬레이션 WARN이 없는지 `journalctl`로 확인할 것. +- [ ] `python3 test_connection.py` — 무서명 400/403, 서명 200; 403이면 API_KEY 불일치, 400이면 NTP/타임스탬프 의심 +- [ ] 수동 실행 후 startup 이벤트 → 이후 300초 주기 보고. 대시보드에서 sensor_id=2 카드 + 차트(기본 sensor 2, `php/dashboard.php:125-129`) + leak_sensor_metric 5분 축적 확인 +- [ ] 임계값 시험: 고온/고습 유발 → 태그된 SMS + 30분 쿨다운 + 회복 SMS 확인 + +--- + +## 6. Pi-C: sensor_id=3 신규 지점 (extension-pattern 기준) + +근거: `docs/solutions/best-practices/sensor-telemetry-extension-pattern-2026-05-21.md` (단, 이 문서 자체가 legacy 선언 — 현행 코드 기준으로 재검증된 아래 내용을 따를 것). + +### 6.1 그냥 되는 것 (서버 무변경) +- **등록 절차 없음**: sensor_id는 HMAC 서명된 본문으로 도착, 1..255 범위검사 후 `leak_sensor_status`에 UPSERT로 자동 등록 (`php/api/sensor_data.php:199-205, 316-340`). SQL 마이그레이션 불필요. +- 온습도 보고 시 metric 저장, **임계값 SMS/회복 SMS(센서별 쿨다운)**, 오프라인/온라인 SMS, 에스컬레이션, stuck 감지, 월간 리포트 GROUP BY — 전부 sensor_id 단위로 자동 동작 (`php/cron_heartbeat.php:22-83`, `php/sms_send.php:426-509`). +- 대시보드 **센서 상태 카드 목록**에는 sensor 3이 자동 표시 (`php/dashboard.php:87, 635-690`). +- 누수 이벤트도 서버 처리 자체는 됨: `[누수감지]` SMS + leak_incident 생성 + 사고 테이블 표시 (`php/sms_send.php:546-608`, `php/api/sensor_data.php:371-411`, incident 쿼리는 무필터). + +### 6.2 코드/스키마 작업이 필요한 것 +| 항목 | 내용 | 근거 | +|---|---|---| +| 차트 | sensor_id=2 하드코딩, 선택 UI 없음 — sensor 3 이력은 저장되지만 차트로 못 봄 | `php/dashboard.php:126-129` | +| 최근 온습도 카드 | 차트 센서(=2)의 최신값만 표시 | `php/dashboard.php:218-220, 596-601` | +| 30일 평균 | 전 센서 혼합 집계(GROUP BY 없음) — 지점이 다른 sensor 3이 들어오면 평균이 무의미해짐 | `php/dashboard.php:210-216` | +| 누수 상태 카드 | sensor_id===1 하드코딩 — Pi-C가 누수형이면 실시간 카드 미표시 | `php/dashboard.php:256-264, 693` | +| 임계값/쿨다운 | METRIC_* 전역 단일값 — 지점별 임계 필요 시 코드/스키마 변경 | `php/config.php:134-153` | +| 하트비트 타임아웃 | 전역 1200초 — **누수형 Pi-C(보고 주기 3600초)는 매 사이클 오프라인 오탐** → 전역 상향 시 5분 센서의 감지 지연 | `php/config.php:197`, `raspberry_pi4/config.py` REPORT_INTERVAL=3600 | +| SENSOR_ID 위치 (SHT30형) | git 추적되는 `config.py:16-17`을 직접 수정해야 함 — config_local.py에 넣어도 무시됨(import 목록 고정, `config.py:70-76`) | drift 위험 | + +### 6.3 센서 종류별 작업량 비교 + +| | **SHT30형 (권장 최소)** | **누수형** | **둘 다 (한 Pi)** | +|---|---|---|---| +| Pi측 | `raspberry_pi4_sht30/` 복사, `config.py` SENSOR_ID=3/SENSOR_NAME 수정(추적 파일!), config_local.py 작성(DEVICE_ID 예: rpi4-sht30-02) | `raspberry_pi4/` 복사, `config.py` SENSORS에 `{"id":3,"gpio":22,...}` 추가(주석 예시 기존재, `config.py:17`), config_local.py + PHOTO_UPLOAD_URL | 두 모듈 폴더 모두 설치 — 핀 충돌 없음(PR_BODY.md §7). 단 두 모듈이 각각 sensor_id를 가지므로 id 3·4 두 개를 쓸지 결정 필요(갭 — 문서화된 패턴 없음) | +| 서버측 필수 | **없음** | **없음** (수신·SMS·incident 동작) | 없음 | +| 서버측 권장 수정 | 차트/최근값 카드가 sensor 2 고정 → 보고 싶으면 dashboard.php 수정; 30일 평균 혼합 문제 | + 누수 카드 sensor 1 고정 수정, **HEARTBEAT_TIMEOUT_SEC 충돌 해결(가장 큰 이슈)** | 양쪽 전부 | +| 결론 | **작업량 최소** — 대시보드 차트만 감수하면 무수정 운영 가능 | 서버 무수정 시 오프라인 오탐 + 대시보드 비가시 감수 필요 | 최대 작업량 | + +공통 주의: sensor_status는 sensor_id 단독 키 — Pi-C가 실수로 id 1/2를 재사용하면 기존 지점 상태를 **조용히 덮어씀** (`php/api/sensor_data.php:316-340`). DEVICE_ID는 표시용일 뿐 인증 정체성이 아님. + +--- + +## 7. 배포 시점 보안 체크리스트 (repo 근거 항목만) + +**서버 (근거: `docs/OPERATIONS_SECURITY_CHECKLIST.md`, `docs/SOURCE_SECURITY_EVIDENCE.md`, `php/ops_checks.php`)** +- [ ] API_KEY 기본값 교체 확인 (기본값이면 ingest 503 — `php/api/sensor_data.php:73-77`) +- [ ] `ALLOW_LEGACY_BODY_SIGNATURE=false` (`php/ops_checks.php:101`) +- [ ] MFA_SETUP_TOKEN 비움 확인 (`php/ops_checks.php:100`) +- [ ] HTTPS를 cafe24 패널에서 강제 + 인증서/리다이렉트 화면 증적 캡처 (`docs/SOURCE_SECURITY_EVIDENCE.md` §2.1 — .htaccess는 의도적으로 리다이렉트 안 함, **조용히 누락되기 쉬운 수동 단계**) +- [ ] 위조 서명 403 / 정상 서명 200 테스트 증적 (`docs/SOURCE_SECURITY_EVIDENCE.md` §5) +- [ ] config.local.php 등 민감 파일 직접 접근 차단 확인 (`php/.htaccess` FilesMatch — `docs/SOURCE_SECURITY_EVIDENCE.md` §2.8) +- [ ] `php/var/admin_audit.log`에 로그인/로그아웃/MFA 이벤트 기록 확인 +- [ ] `curl -I https://<도메인>//login.php` 응답 헤더 점검 (checklist §3) +- [ ] SMS 잔액/발신번호/수신자 최신성 확인; 백업 + 복원 테스트(`php scripts/backup_evidence.php`) — checklist §9 + +**Pi 3대 공통** +- [ ] `config_local.py` 커밋 금지(.gitignore 확인); 노출 시 서버·전 노드 동시 API_KEY 교체 (양쪽 README §7) +- [ ] 단일 공유 API_KEY 구조 인지: Pi 1대 탈취 = 전 지점 ingest 자격 노출, 기기별 폐기 불가 (extension-pattern 갭) — 지점 추가마다 노출면 확대 +- [ ] 기기 인바운드 차단 증적: `ss -lntup`으로 불필요 리스닝 데몬 없음 확인 (`docs/SOURCE_SECURITY_EVIDENCE.md` §4 — 단 달성 방법은 미문서화, §8 갭) +- [ ] `systemctl status leak-sensor`(및 sht30-sensor) 화면 증적 (§5 최소 증적 세트) +- [ ] NTP 동기 확인 (±600초 초과 시 전 POST 거부) + +**repo에 없어 자체 수립 필요 (문서화 갭 — §8):** Pi 계정/SSH 하드닝, 호스트 방화벽(ufw 등), 시크릿 파일 권한(chmod 600), OS 패치 정책. + +--- + +## 8. 문서화 갭 / 결정 필요 사항 (통합·우선순위) + +### P0 — 배포를 잘못되게 만들 수 있는 것 +1. **STALE 문서 모순 — 모듈1 대상 서버**: `raspberry_pi4/README.md` §3·§7은 "php/ 서버가 누수 거부 / 레거시 sha256 서명"이라 함. 현행은 통합 서버 + raw-body HMAC (`config_local.example.py:8-16`, SYSTEM_OVERVIEW.md, PR #1). README만 보고 배포하면 오설정. → README §3/§7 갱신 필요. +2. **`docs/INSTALL_PI_SERVER.md` 업로드 목록 STALE**: photo_upload.php/photo.php/var 가드 누락 (33-56행) — 그대로 따르면 누수 사진 파이프라인 결손. 또한 이 문서 자체가 STM32(v2606) 중심이며 RPi4 신규 설치 통합 가이드는 부재 (root README.md는 0바이트). +3. **cron URL 오류**: INSTALL_PI_SERVER.md:165에 `?key=` 누락 → 403. CRON_SECRET 표시 UI 없음, 수동 계산 필요. +4. **신품 Pi 프로비저닝 절차 전무**: SD 이미징, `pi` 계정 생성(Bookworm 기본 미존재), SSH 활성화, 호스트네임, 네트워크 설정 — 전부 미문서화. **결정 필요**: 이미지 버전, 계정/비밀번호 정책, 고정IP vs DHCP, 호스트네임 규약. +5. **Pi-C 센서 종류 결정**: §6.3 비교 기준으로 결정. 누수형 선택 시 HEARTBEAT_TIMEOUT_SEC 충돌(전역 1200초 vs 보고 3600초)을 반드시 먼저 해결. + +### P1 — 운영 중 사고로 이어질 수 있는 것 +6. **Pi OS 하드닝 부재**: 계정/SSH/방화벽/시크릿 파일 권한(600)/패치 정책이 4개 보안 문서 어디에도 없음 — `docs/SOURCE_SECURITY_EVIDENCE.md`도 "코드로 증명 불가, 기관 자료 필요"로만 표기. → 자체 표준 수립 필요. +7. **단일 공유 API_KEY / 센서 레지스트리 부재**: 키 보유자는 임의 sensor_id 생성·기존 id 덮어쓰기 가능; 기기별 폐기 불가. 지점이 늘수록(Pi-C) 위험 증가 — device_id↔sensor_id 서버측 바인딩 검토 권장. +8. **시뮬레이션 폴백이 오류 은폐**: SHT30 모듈은 I2C 실패 시 그럴듯한 더미값을 운영 서버로 전송(WARN 로그만) — FORCE_REAL 옵션 없음. 배포 검증 시 journalctl 확인을 절차화할 것. +9. **NTP 의존 미절차화**: 서버가 ±600초 강제하는데 시각동기 확인 단계가 설치 절차에 없음 (현장 인터넷 가용이라 기본 NTP로 충족 예상되나 확인 단계 추가 권장). +10. **카메라 검증 단계 부재** (Pi-A): 첫 누수 때에야 실패 발견 — 설치 시 테스트샷 절차 추가 필요. + +### P2 — 정리/개선 +11. **설정 규약 이원화**: `docs/SHT30_SENSOR2_SETUP.md` §3과 `php/ops_checks.php:97`은 `/etc/sht30-sensor.env`를 참조하나 실제 모듈은 `config_local.py`만 읽음 — 문서 갱신 필요. +12. **SENSOR_ID가 추적 파일(config.py)에 위치** (SHT30형): config_local 오버라이드 허용하는 소규모 수정 권장 (Pi-C에서 실익). +13. **systemd 유닛 경로/User=pi 하드코딩** 2개 파일 — 비표준 배치 시 수동 수정 2곳. +14. **센서 폐기 UI 없음**: leak_sensor_status 행 수동 SQL 삭제 전까지 영구 오프라인 표시. +15. **임계값 문서 드리프트**: SOURCE_SECURITY_EVIDENCE.md §2.4(30℃/70%)와 현행 권장값(27℃/60%, commit c8b448c) 불일치. +16. **retention_cleanup cron 미문서화**; **오프라인 번들 해시 검증 부재**(--require-hashes 없음); **CORS pores.kr 잔재**; **로그 권한 실패 시 조용한 콘솔 폴백**(양 모듈); **var/ 권한 비트 미문서화**(cafe24에서 setup_wizard '상태 파일 폴더' 체크로 확인). +17. **firmware/(STM32) 트랙은 이번 배포와 무관** — RPi4 배포에 불필요 (`docs/stm32f407_migration_plan.md`는 별도 마이그레이션 트랙, 링크/실행 미검증 상태). + +--- + +## 9. [확정] Pi-C = 누수 + 카메라 (sensor_id 3) — 결정 반영 부록 + +> ✅ **v2607 구현 완료:** §9.2 필수 코드 작업 3건 전부 반영됨 — REPORT_INTERVAL=600(하트비트 오탐 해소), 대시보드 LEAK_SENSOR_IDS 루프(누수 카드 다중 지점), config_local.py SENSORS 오버라이드(+형식 검증 fail-closed). 테이블은 전부 leak_ 접두사로 리네임됨. 배포는 tools/make_deploy_bundles.py 번들 사용. + +사용자 결정(2026-07-02): Pi-C는 **누수 + 카메라** 구성. + +### 9.1 누수형 선택의 이점 (SHT30형 대비) +- 대시보드 차트 sensor 2 하드코딩 문제(§6.2) **해당 없음** — Pi-C는 metric을 안 보냄. +- 30일 평균 혼합 오염(§6.2) **해당 없음** — 동일 이유. +- SENSOR_ID 추적파일(config.py) 문제는 누수 모듈도 동일하나, `raspberry_pi4/config.py:13` SENSORS 리스트에 id=3 항목 추가 형태라 구조는 명확. + +### 9.2 필수 코드 작업 (검증 완료 — file:line 확인됨) +1. **하트비트 오탐 (P0, Pi-A에도 이미 존재하는 버그)** + - `php/cron_heartbeat.php:25-30` — leak_sensor_status 전체를 전역 `HEARTBEAT_TIMEOUT_SEC`(기본 1200s, `php/config.php:197`)로 검사. 필터 없음. + - `raspberry_pi4/config.py:35` — 누수 모듈 정상 주기 보고 3600s. + - 결과: 누수 노드(id 1, id 3)는 **매 사이클 오프라인 SMS 오탐 → 복귀 SMS 반복**. + - 권장 수정(택1): + - (a) **최소 변경**: 누수 모듈 `REPORT_INTERVAL` 3600 → 600 (Pi-A·Pi-C 공통). 주기 POST는 소형 JSON이라 부하 무시 가능. 서버 무수정. + - (b) **구조적**: sensor_status에 per-sensor timeout 컬럼 추가 + cron_heartbeat/dashboard 반영 (스키마+코드 변경). +2. **대시보드 누수 카드 (Pi-C 가시성)** + - `php/dashboard.php:260` — `(int)$s['sensor_id'] === 1` 하드코딩. Pi-C 누수 상태 카드 미표시. + - 수정: is_leak 값이 있는 모든 센서에 대해 카드 렌더 (사고 테이블은 이미 무필터 — 수정 불요). +3. **Pi-C 설정값** + - `raspberry_pi4/config.py` SENSORS에 `{"id": 3, ...}` (gpio 17 유지 — 별도 Pi이므로 핀 충돌 없음), DEVICE_ID 예: `rpi4-leak-02`, DEVICE_LOCATION = 신규 지점명. + +### 9.3 Pi-C 추가 하드웨어 (구매 확인 필요) +- e-con 5C-08 누수센서 1개 + 4.7MΩ 풀다운 저항 +- Pi Camera (15핀 CSI 리본) 1개 +- SD카드·USB-C 5V/3A 전원 (3대 공통) + +### 9.4 보안 유의 (extension-pattern 갭) +- 단일 공유 API_KEY로 지점 3개 — 노출면 확대. sensor_status는 sensor_id 단독 키이므로 Pi-C가 id 1/2 오설정 시 기존 지점 상태를 조용히 덮어씀 (`php/api/sensor_data.php:316-340`). 배포 전 config 교차 점검 필수. diff --git a/docs/INSTALL_GUIDE.html b/docs/INSTALL_GUIDE.html index d071eeb..2ee4e51 100644 --- a/docs/INSTALL_GUIDE.html +++ b/docs/INSTALL_GUIDE.html @@ -281,13 +281,12 @@ return [

스키마 실행

신규 설치는 sql/schema_sht30.sql 전체를 복사해 SQL 탭에 붙여넣고 실행. - 생성 테이블: sensor_log, sensor_status, sensor_metric, sms_log - (+ 재전송 방어용 ingest_nonce).

+ 생성 테이블: leak_sensor_log, leak_sensor_status, leak_sensor_metric, leak_sms_log + (+ 재전송 방어용 leak_ingest_nonce).

-
SQL 파일적용 상황
sql/schema_sht30.sql신규 설치(통합 스키마)
sql/migration_drop_leak.sql기존 누수(v2605) 설치 → 온습도 전용 전환. 비가역 — 실행 전 DB 전체 백업
중복 실행 주의. 이미 적용한 마이그레이션을 다시 실행하면 오류가 날 수 있습니다. @@ -301,7 +300,7 @@ return [
캡처 위치 — 그림 3 → screenshots/03-db-phpmyadmin.png
phpMyAdmin SQL 탭에 schema_sht30.sql 붙여넣고 [실행]한 결과(“쿼리 실행 성공” + 좌측 4~5개 테이블 생성된 화면)
-
그림 3. DB 스키마 실행 결과 (sensor_log/sensor_status/sensor_metric/sms_log/ingest_nonce)
+
그림 3. DB 스키마 실행 결과 (leak_sensor_log/leak_sensor_status/leak_sensor_metric/leak_sms_log/leak_ingest_nonce)
@@ -575,7 +574,7 @@ https://your-domain.example/raspi_leck_detecter/security_evidence.php
  • setup_wizard.php 필수 점검 항목 통과 + 테스트 SMS 수신
  • dashboard.php 에 최신 온/습도와 센서 상태(sensor_id=2) 표시
  • -
  • sensor_metric 에 주기 측정값(periodic) 저장 · STM32 콘솔에 서버 200 기록
  • +
  • leak_sensor_metric 에 주기 측정값(periodic) 저장 · STM32 콘솔에 서버 200 기록
  • 임계 초과 테스트 시 metric_status 경보 + SMS 수신 / 월간 보고서에 내역 조회
  • @@ -599,8 +598,8 @@ https://your-domain.example/raspi_leck_detecter/security_evidence.phpAPI 403서버 API_KEY ↔ 펌웨어 APP_API_KEY 일치, raw-body 서명 API 500Cafe24 PHP 오류 로그, DB 접속 정보, 테이블 생성 여부 로그인 불가ADMIN_PASSWORD_HASH, ADMIN_TOTP_SECRET, 인증 앱 시간 동기 - SMS 미발송Cafe24 SMS 계정·secure key·발신번호 등록·잔액, sms_log. 대시보드 ‘수집 거부/발송 실패’ 배너 확인 - 온습도 미표시I²C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, sensor_metric, 펌웨어 측정 로그 + SMS 미발송Cafe24 SMS 계정·secure key·발신번호 등록·잔액, leak_sms_log. 대시보드 ‘수집 거부/발송 실패’ 배너 확인 + 온습도 미표시I²C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, leak_sensor_metric, 펌웨어 측정 로그 TLS 실패server_ca.c 실제 CA 반영, SNTP 시간 동기(APP_SNTP_SERVER=LAN NTP) 데이터 전혀 안 들어옴대시보드 수집 거부 사유 카운터(서명불일치→키/펌웨어, 시각오차→SNTP/RTC)로 원인 분별 오프라인 알림 과다/지연HEARTBEAT_TIMEOUT_SEC(1200초), cron 주기 diff --git a/docs/INSTALL_GUIDE.standalone.html b/docs/INSTALL_GUIDE.standalone.html index 6c5d941..a57e4b1 100644 --- a/docs/INSTALL_GUIDE.standalone.html +++ b/docs/INSTALL_GUIDE.standalone.html @@ -281,13 +281,12 @@ return [

    스키마 실행

    신규 설치는 sql/schema_sht30.sql 전체를 복사해 SQL 탭에 붙여넣고 실행. - 생성 테이블: sensor_log, sensor_status, sensor_metric, sms_log - (+ 재전송 방어용 ingest_nonce).

    + 생성 테이블: leak_sensor_log, leak_sensor_status, leak_sensor_metric, leak_sms_log + (+ 재전송 방어용 leak_ingest_nonce).

    -
    SQL 파일적용 상황
    sql/schema_sht30.sql신규 설치(통합 스키마)
    sql/migration_drop_leak.sql기존 누수(v2605) 설치 → 온습도 전용 전환. 비가역 — 실행 전 DB 전체 백업
    중복 실행 주의. 이미 적용한 마이그레이션을 다시 실행하면 오류가 날 수 있습니다. @@ -301,7 +300,7 @@ return [
    캡처 위치 — 그림 3 → screenshots/03-db-phpmyadmin.png
    phpMyAdmin SQL 탭에 schema_sht30.sql 붙여넣고 [실행]한 결과(“쿼리 실행 성공” + 좌측 4~5개 테이블 생성된 화면)
    -
    그림 3. DB 스키마 실행 결과 (sensor_log/sensor_status/sensor_metric/sms_log/ingest_nonce)
    +
    그림 3. DB 스키마 실행 결과 (leak_sensor_log/leak_sensor_status/leak_sensor_metric/leak_sms_log/leak_ingest_nonce)
    @@ -575,7 +574,7 @@ https://your-domain.example/raspi_leck_detecter/security_evidence.php
  • setup_wizard.php 필수 점검 항목 통과 + 테스트 SMS 수신
  • dashboard.php 에 최신 온/습도와 센서 상태(sensor_id=2) 표시
  • -
  • sensor_metric 에 주기 측정값(periodic) 저장 · STM32 콘솔에 서버 200 기록
  • +
  • leak_sensor_metric 에 주기 측정값(periodic) 저장 · STM32 콘솔에 서버 200 기록
  • 임계 초과 테스트 시 metric_status 경보 + SMS 수신 / 월간 보고서에 내역 조회
  • @@ -599,8 +598,8 @@ https://your-domain.example/raspi_leck_detecter/security_evidence.phpAPI 403서버 API_KEY ↔ 펌웨어 APP_API_KEY 일치, raw-body 서명 API 500Cafe24 PHP 오류 로그, DB 접속 정보, 테이블 생성 여부 로그인 불가ADMIN_PASSWORD_HASH, ADMIN_TOTP_SECRET, 인증 앱 시간 동기 - SMS 미발송Cafe24 SMS 계정·secure key·발신번호 등록·잔액, sms_log. 대시보드 ‘수집 거부/발송 실패’ 배너 확인 - 온습도 미표시I²C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, sensor_metric, 펌웨어 측정 로그 + SMS 미발송Cafe24 SMS 계정·secure key·발신번호 등록·잔액, leak_sms_log. 대시보드 ‘수집 거부/발송 실패’ 배너 확인 + 온습도 미표시I²C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, leak_sensor_metric, 펌웨어 측정 로그 TLS 실패server_ca.c 실제 CA 반영, SNTP 시간 동기(APP_SNTP_SERVER=LAN NTP) 데이터 전혀 안 들어옴대시보드 수집 거부 사유 카운터(서명불일치→키/펌웨어, 시각오차→SNTP/RTC)로 원인 분별 오프라인 알림 과다/지연HEARTBEAT_TIMEOUT_SEC(1200초), cron 주기 diff --git a/docs/INSTALL_PI_SERVER.md b/docs/INSTALL_PI_SERVER.md index 2aacc83..2f0b607 100644 --- a/docs/INSTALL_PI_SERVER.md +++ b/docs/INSTALL_PI_SERVER.md @@ -2,6 +2,8 @@ 이 문서는 서버실 온습도(SHT30) 모니터링 시스템 v2606을 실제 운영 환경에 배포할 때 STM32F407VGT6 펌웨어와 Cafe24 PHP/MySQL 서버에 각각 무엇을 설치하고 설정해야 하는지 정리한 설치용 문서입니다. +> **참고:** 이 문서는 **STM32 펌웨어 중심**의 설치 문서입니다. Raspberry Pi 4 모듈(누수+카메라 / SHT30) 배포자는 [docs/DEPLOYMENT_PLAN_RPI4x3.md](./DEPLOYMENT_PLAN_RPI4x3.md)와 [SYSTEM_OVERVIEW.md](../SYSTEM_OVERVIEW.md)를 먼저 참고하세요. 서버 설치 절차(§3~§6)는 공통입니다. + ## 1. 설치 대상 | 구분 | 설치 위치 | 역할 | @@ -28,19 +30,20 @@ ## 3. 서버에 설치할 파일 -Cafe24 웹 루트 아래에 다음 구조로 PHP 파일을 업로드합니다. +Cafe24 웹 루트 아래에 **repo 의 `php/` 트리 전체를 그대로 업로드**합니다. 개별 파일을 선별해 올리면 누수 사진 파이프라인(`api/photo_upload.php`, `photo.php`, `var/photos/`)이 빠지기 쉽습니다. ```text -public_html/raspi_leck_detecter/ +public_html/raspi_leck_detecter/ ← php/ 트리 전체 업로드 .htaccess blocked.php config.php - config.local.php + config.local.php ← 서버에서 example 복사 후 작성(저장소에 없음) config.local.example.php ops_checks.php sms_send.php cron_heartbeat.php dashboard.php + photo.php ← 누수 사진 조회(관리자 인증 필요) setup_mfa.php admin_security.php setup_wizard.php @@ -49,10 +52,17 @@ public_html/raspi_leck_detecter/ login.php retention_cleanup.php setup_hash.php + test_mobile.php api/ sensor_data.php + photo_upload.php ← 누수 사진 업로드 API var/ .gitkeep + .htaccess ← var/ 웹 직접 접근 차단(403) + index.html + photos/ ← 누수 사진 저장 폴더(.htaccess/index.html 포함, + .htaccess 없으면 첫 업로드 시 자동 생성 시도) + index.html ``` 운영 서버에는 `config.local.php`가 반드시 필요합니다. `config.local.example.php`를 복사해서 만들고 실제 운영 값으로 교체합니다. @@ -113,16 +123,7 @@ https://your-domain.example/raspi_leck_detecter/setup_mfa.php?token=임시토큰 SOURCE /path/to/sql/schema_sht30.sql; ``` -phpMyAdmin에서는 [schema_sht30.sql](../sql/schema_sht30.sql) 내용을 복사해서 SQL 실행 창에 붙여넣습니다. 생성 테이블은 `sensor_log`, `sensor_status`, `sensor_metric`, `sms_log`입니다. - -기존 누수 설치를 온습도 전용으로 전환하는 경우에는 전환 마이그레이션을 적용합니다. - -| SQL 파일 | 적용 상황 | -|---|---| -| [schema_sht30.sql](../sql/schema_sht30.sql) | 신규 설치(통합 스키마) | -| [migration_drop_leak.sql](../sql/migration_drop_leak.sql) | 기존 누수 설치 → 온습도 전용 전환(레거시 컬럼/테이블 정리, `sensor_metric` 보장) | - -이미 적용된 마이그레이션을 중복 실행하면 DB 오류가 날 수 있으므로, 기존 설치에서는 `setup_wizard.php`의 점검 결과를 먼저 확인합니다. +phpMyAdmin에서는 [schema_sht30.sql](../sql/schema_sht30.sql) 내용을 복사해서 SQL 실행 창에 붙여넣습니다. 생성 테이블은 총 7개 — `leak_sensor_log`, `leak_sensor_status`, `leak_sensor_metric`, `leak_sms_log`, `leak_ingest_nonce`(재전송 방어), `leak_incident`, `leak_photo`(누수 사고/사진) — 이며, 신규 설치는 이 파일 하나로 완결됩니다(누수 컬럼/테이블 포함). 모든 테이블은 `leak_` 접두사를 사용해 같은 DB 의 다른 테이블과 격리됩니다. ## 5. 서버 권한 및 관리자 설정 @@ -149,7 +150,7 @@ https://your-domain.example/raspi_leck_detecter/setup_wizard.php 점검 화면에서 확인할 항목은 다음과 같습니다. - DB 연결 -- 필수 테이블 존재 여부(`sensor_log`, `sensor_status`, `sensor_metric`, `sms_log`) +- 필수 테이블 존재 여부(`leak_sensor_log`, `leak_sensor_status`, `leak_sensor_metric`, `leak_sms_log`) - API 키 기본값 교체 여부 - 관리자 해시 설정 여부 - 관리자 TOTP 설정 여부 @@ -162,10 +163,12 @@ https://your-domain.example/raspi_leck_detecter/setup_wizard.php 장비 오프라인 감지를 위해 `cron_heartbeat.php`를 주기 실행합니다. Cafe24 cron 또는 외부 스케줄러에서 다음 URL을 1분 간격으로 호출합니다. ```text -https://your-domain.example/raspi_leck_detecter/cron_heartbeat.php +https://your-domain.example/raspi_leck_detecter/cron_heartbeat.php?key= ``` -서버 CLI cron을 사용할 수 있다면 다음처럼 구성할 수 있습니다. +HTTP 호출에는 `?key=` 파라미터가 **필수**입니다(누락 시 403). `CRON_SECRET`은 `API_KEY`에서 파생되며 다음으로 계산합니다: `php -r "echo hash_hmac('sha256','cron','');"` + +서버 CLI cron을 사용할 수 있다면 다음처럼 구성할 수 있습니다(CLI 실행에는 key가 필요 없습니다). ```cron * * * * * php /home/hosting_user/public_html/raspi_leck_detecter/cron_heartbeat.php @@ -223,7 +226,7 @@ https://your-domain.example/raspi_leck_detecter/security_evidence.php - `dashboard.php`에 최신 온도/습도와 센서 상태(`sensor_id=2`)가 표시 - `security_evidence.php`에서 운영 점검 결과와 보안통제 매트릭스 조회 가능 - STM32 콘솔 로그에 서버 200 응답 기록 확인 -- `sensor_metric`에 주기 측정값(periodic) 저장 +- `leak_sensor_metric`에 주기 측정값(periodic) 저장 - 임계 초과 테스트 시 `metric_status` 경보 기록 및 SMS 수신 - 월간 보고서에서 임계 경보/복귀/오프라인 내역 조회 가능 @@ -234,8 +237,8 @@ https://your-domain.example/raspi_leck_detecter/security_evidence.php | API 403 | 서버 `API_KEY`와 펌웨어 `APP_API_KEY` 일치 여부, raw-body 서명 | | API 500 | Cafe24 PHP 오류 로그, DB 접속 정보, 테이블 생성 여부 | | 로그인 불가 | `ADMIN_PASSWORD_HASH`, `ADMIN_TOTP_SECRET`, 인증 앱 시간 동기화 | -| SMS 미발송 | Cafe24 SMS 계정, secure key, 발신번호 등록, 잔액, `sms_log` | -| 온습도 미표시 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `sensor_metric` 테이블, 펌웨어 측정 로그 | +| SMS 미발송 | Cafe24 SMS 계정, secure key, 발신번호 등록, 잔액, `leak_sms_log` | +| 온습도 미표시 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `leak_sensor_metric` 테이블, 펌웨어 측정 로그 | | 임계 경보 미발송 | `config.php`/`config.local.php`의 `METRIC_*`, `METRIC_ALERT_COOLDOWN_SEC`(1800초) | | TLS 실패 | `firmware/certs/server_ca.c` 실제 CA 반영, SNTP 시간 동기(인증서 유효기간 검증) | | 오프라인 알림 과다/지연 | `HEARTBEAT_CHECK_INTERVAL_SEC`, `HEARTBEAT_TIMEOUT_SEC`(기본 1200초), cron 주기 | diff --git a/docs/OPERATIONS_SECURITY_CHECKLIST.md b/docs/OPERATIONS_SECURITY_CHECKLIST.md index 2a7a035..2dd4cb6 100644 --- a/docs/OPERATIONS_SECURITY_CHECKLIST.md +++ b/docs/OPERATIONS_SECURITY_CHECKLIST.md @@ -10,7 +10,7 @@ | 주기 | 점검 목적 | 보관 증적 | |---|---|---| | 일일 | 온습도 임계 경보, 장비 오프라인, SMS 실패 여부 확인 | 대시보드 화면 또는 이상 없음 기록 | -| 주간 | STM32 보고 수신 상태, 최근 측정값, 측정 공백 여부 확인 | 대시보드 온습도 추이, `sensor_metric` 최근 기록 | +| 주간 | STM32 보고 수신 상태, 최근 측정값, 측정 공백 여부 확인 | 대시보드 온습도 추이, `leak_sensor_metric` 최근 기록 | | 월간 | 보안통제 매트릭스 점검, 백업, 수신자 현행화 | 보안 증적 보고서, 월간 보고서, 백업 목록 | | 월간 | 관리자 MFA, 감사로그, 보관기간 정리 확인 | MFA 등록 확인표, `php/var/admin_audit.log`, retention 실행 결과 | | 변경 시 | API 키, 서버 경로, 수신자, 임계값, 네트워크 변경 검증 | 변경 승인 기록, 재점검 결과 | @@ -23,9 +23,9 @@ | 설치 점검 | `setup_wizard.php` 접속 | 중요 항목 0건 또는 조치계획 기록 | 설치 점검 캡처 | | 보안 증적 | `security_evidence.php` 접속 | 운영 점검 결과와 보안통제 매트릭스 출력 | Markdown 다운로드 | | 월간 보고 | `monthly_report.php` 조회 | 임계 경보, 복귀, 오프라인, SMS 현황 표시 | PDF 또는 CSV | -| 장비 상태 | `sensor_status`(`sensor_id=2`) | 최근 수신 시각이 `HEARTBEAT_TIMEOUT_SEC` 이내 | 대시보드 센서 카드 | -| 측정 수집 | `sensor_metric` | 5분 주기 측정값 누적 | 온습도 추이 캡처 | -| SMS | 최근 실패 | 반복 실패 없음 | `sms_log` 또는 대시보드 | +| 장비 상태 | `leak_sensor_status`(`sensor_id=2`) | 최근 수신 시각이 `HEARTBEAT_TIMEOUT_SEC` 이내 | 대시보드 센서 카드 | +| 측정 수집 | `leak_sensor_metric` | 5분 주기 측정값 누적 | 온습도 추이 캡처 | +| SMS | 최근 실패 | 반복 실패 없음 | `leak_sms_log` 또는 대시보드 | | 임계 설정 | `METRIC_*` | 기관 기준과 일치 | `config.local.php` 발췌(값만) | | 백업 | DB/설정 백업 | 정해진 주기대로 백업 존재 | 백업 파일 목록 | | 관리자 MFA | `ADMIN_TOTP_SECRET` | 인증 앱 등록 완료 | MFA 등록 확인표 | @@ -65,7 +65,7 @@ USART3(PD8/PD9, 115200) 콘솔 로그로 다음을 확인합니다. |---|---|---| | 임계 초과 감지 | 측정값이 임계 초과 시 `metric_status` 경보가 기록되는가 | 감지 시각/항목 | | SMS 발송 | 수신자가 임계 경보 SMS를 받는가 | 수신 시각 | -| 쿨다운 | 동일 종류 경보가 30분 내 중복 발송되지 않는가 | `sms_log` 간격 | +| 쿨다운 | 동일 종류 경보가 30분 내 중복 발송되지 않는가 | `leak_sms_log` 간격 | | 정상복귀 | 히스테리시스 포함 정상 회복 시 복귀 SMS 1회 발송되는가 | 복귀 시각 | | 보고서 반영 | 월간 보고서에 경보/복귀가 반영되는가 | 보고서 파일 | @@ -76,7 +76,7 @@ USART3(PD8/PD9, 115200) 콘솔 로그로 다음을 확인합니다. | 임계 경보 발생 | 당일 원인 확인 후 조치 결과 기록(공조/환기/장비 점검) | | SMS 반복 실패 | Cafe24 SMS 잔액, 발신번호, secure key, 수신자 확인 | | 장비 오프라인 | 전원, LAN 연결, PHY 링크, 펌웨어 콘솔 로그 확인 | -| 온습도 미수집 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `sensor_metric` 테이블 확인 | +| 온습도 미수집 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `leak_sensor_metric` 테이블 확인 | | HTTPS/TLS 미확인 | Cafe24 SSL 설정, 펌웨어 CA 반영, SNTP 시간 동기 확인 | | 백업 미수행 | 백업 주기 재설정 및 다음 월 점검에 포함 | diff --git a/docs/README.md b/docs/README.md index 27382c2..db8230c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,8 +51,7 @@ php/ blocked.php # 차단 안내 화면 api/sensor_data.php # STM32 센서 데이터 수신 sql/ - schema_sht30.sql # 신규 설치용 통합 스키마 - migration_drop_leak.sql # 기존 누수 설치 → 온습도 전용 전환 마이그레이션 + schema_sht30.sql # 신규 설치용 통합 스키마 (이 파일 하나로 완결) _backup/ pre_v2605_20260519/ # 정리 전 원본 백업 ``` @@ -69,7 +68,7 @@ _backup/ SOURCE /path/to/sql/schema_sht30.sql; ``` - phpMyAdmin에서 실행할 때는 `sql/schema_sht30.sql` 내용을 복사해 실행합니다. 생성 테이블은 `sensor_log`, `sensor_status`, `sensor_metric`, `sms_log`입니다. + phpMyAdmin에서 실행할 때는 `sql/schema_sht30.sql` 내용을 복사해 실행합니다. 생성 테이블은 `leak_sensor_log`, `leak_sensor_status`, `leak_sensor_metric`, `leak_sms_log`입니다. 3. PHP 파일을 업로드합니다. @@ -126,29 +125,21 @@ _backup/ * * * * * php /home/hosting_user/public_html/raspi_leck_detecter/cron_heartbeat.php ``` -## 기존 누수 설치 → 온습도 전용 전환 +## 설치 스키마 -1. 현재 운영 파일과 DB를 백업합니다. -2. `php/config.local.example.php` 기준으로 `config.local.php`를 갱신합니다(METRIC_* 임계 확인). -3. DB에 전환 마이그레이션을 적용합니다. - - ```sql - SOURCE /path/to/sql/migration_drop_leak.sql; - ``` - - 이 마이그레이션은 레거시 누수 컬럼/테이블(`leak_photo`, `leak_incident`, `is_leak` 등)을 정리하고 `sensor_metric`을 보장합니다. -4. 신규 설치라면 위 마이그레이션 대신 `sql/schema_sht30.sql`만 사용합니다. +신규 설치는 `sql/schema_sht30.sql` 하나만 실행합니다(누수+온습도 통합, 테이블 전체 `leak_` 접두사). +구버전 무접두 테이블 대상 마이그레이션 파일들은 v2607 테이블 리네임과 함께 폐기되었습니다(git 이력 참고). ## 이벤트 정책 | 이벤트 | 조건 | DB | SMS | |---|---|---|---| -| `startup` | 기기 시작 첫 측정 | `sensor_log` | X | -| `periodic` | 정상 주기 측정(5분) | `sensor_metric` | X | -| 고온/저온/고습/저습 경보 | 측정값이 임계 초과 | `sensor_metric.metric_status` | O (종류별 30분 쿨다운) | -| 정상복귀 | 경보 후 히스테리시스 포함 정상 회복 | `sensor_metric` | O (1회) | -| 장비 오프라인 | 마지막 수신 후 `HEARTBEAT_TIMEOUT_SEC`(기본 1200초=20분) 초과 | `sensor_status` | O | -| 장비 복구 | 오프라인 후 재수신 | `sensor_status` | O | +| `startup` | 기기 시작 첫 측정 | `leak_sensor_log` | X | +| `periodic` | 정상 주기 측정(5분) | `leak_sensor_metric` | X | +| 고온/저온/고습/저습 경보 | 측정값이 임계 초과 | `leak_sensor_metric.metric_status` | O (종류별 30분 쿨다운) | +| 정상복귀 | 경보 후 히스테리시스 포함 정상 회복 | `leak_sensor_metric` | O (1회) | +| 장비 오프라인 | 마지막 수신 후 `HEARTBEAT_TIMEOUT_SEC`(기본 1200초=20분) 초과 | `leak_sensor_status` | O | +| 장비 복구 | 오프라인 후 재수신 | `leak_sensor_status` | O | ## 운영 기준 @@ -161,7 +152,7 @@ _backup/ - 로그인 성공/실패, 로그아웃, MFA 등록 검증은 `php/var/admin_audit.log`에 감사로그로 기록합니다. - heartbeat 체크 실행 주기와 오프라인 판정 시간(`HEARTBEAT_TIMEOUT_SEC`, 기본 1200초)은 분리되어 있습니다. - 대시보드는 위험 상태를 첫 화면에 우선 표시하고, 최신 온습도, 운영 자가진단, 30일 운영 요약을 함께 보여줍니다. -- 운영 자가진단은 API 키, 관리자 해시, SMS 수신자, 필수 테이블(`sensor_metric` 포함), 상태 파일 폴더 권한, 최근 SMS 실패를 확인합니다. +- 운영 자가진단은 API 키, 관리자 해시, SMS 수신자, 필수 테이블(`leak_sensor_metric` 포함), 상태 파일 폴더 권한, 최근 SMS 실패를 확인합니다. - 월간 보고서는 온습도 임계 경보, 정상복귀, 오프라인/복구, SMS 현황을 조회하고 CSV/인쇄/PDF로 출력할 수 있습니다. - 보관기간 정리는 `php/retention_cleanup.php --dry-run`으로 삭제 대상을 먼저 확인한 뒤, 운영 백업 완료 후 `--execute`로 수행합니다. - 실행-검토 과정에서 나온 실수와 교훈은 `docs/solutions/`에 남겨 다음 개선 사이클에서 반복하지 않습니다. @@ -198,10 +189,10 @@ php scripts/backup_evidence.php --backup-dir /path/to/backups --restore-test "YY | 증상 | 확인 지점 | |---|---| | API 403 | 펌웨어 `APP_API_KEY`와 서버 `API_KEY` 일치 여부, raw-body 서명 | -| API 500 | Cafe24 PHP 오류 로그, DB 접속 정보, `sensor_metric` 등 테이블 생성 여부 | +| API 500 | Cafe24 PHP 오류 로그, DB 접속 정보, `leak_sensor_metric` 등 테이블 생성 여부 | | 로그인 불가 | `config.local.php`의 `ADMIN_PASSWORD_HASH`, `ADMIN_TOTP_SECRET`, 인증 앱 시간 동기화 | -| SMS 미발송 | Cafe24 SMS 인증값, 발신번호 등록, 잔액, `sms_log` | -| 온습도 미표시 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `sensor_metric` 테이블, 펌웨어 측정 로그 | +| SMS 미발송 | Cafe24 SMS 인증값, 발신번호 등록, 잔액, `leak_sms_log` | +| 온습도 미표시 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `leak_sensor_metric` 테이블, 펌웨어 측정 로그 | | 임계 경보 미발송 | `config.php`/`config.local.php`의 `METRIC_*` 임계, `METRIC_ALERT_COOLDOWN_SEC`(30분) | | 오프라인 알림 지연 | `HEARTBEAT_CHECK_INTERVAL_SEC`, `HEARTBEAT_TIMEOUT_SEC`(기본 1200초) | | 대시보드 진단 경고 | `config.local.php`, DB 마이그레이션, `php/var` 권한 | diff --git a/docs/SECURITY_DOCUMENT_CONSISTENCY_AUDIT.md b/docs/SECURITY_DOCUMENT_CONSISTENCY_AUDIT.md index dc2dc32..b793df6 100644 --- a/docs/SECURITY_DOCUMENT_CONSISTENCY_AUDIT.md +++ b/docs/SECURITY_DOCUMENT_CONSISTENCY_AUDIT.md @@ -14,9 +14,9 @@ | raw-body API 서명 검증 (HMAC-SHA256) | `config.php` `verify_signature_raw()`=`hash_hmac('sha256', $raw_body, API_KEY)`, 펌웨어 `sig.c`/`secrets.h` (`X-Signature`) | 반영 (서명 방식 정정 후 일치, 아래 §2.1 참조) | | 측정값 범위 검증 | `api/sensor_data.php` (온도 -40~125℃ / 습도 0~100%) | 반영 | | 서버 임계 판정·쿨다운 | `config.php` `METRIC_*`(고온30/저온10·고습70/저습20, 30분 쿨다운, 히스테리시스), `api/sensor_data.php` | 반영 | -| 임계 경보/정상복귀 SMS | `api/sensor_data.php`, `sms_log` (`[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`) | 반영 | -| 장비 오프라인/복구 SMS | `cron_heartbeat.php`, `sensor_status.offline_alerted`, `sms_log` | 반영 | -| 측정 이력 저장 | `sensor_metric` 테이블 | 반영 | +| 임계 경보/정상복귀 SMS | `api/sensor_data.php`, `leak_sms_log` (`[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`) | 반영 | +| 장비 오프라인/복구 SMS | `cron_heartbeat.php`, `leak_sensor_status.offline_alerted`, `leak_sms_log` | 반영 | +| 측정 이력 저장 | `leak_sensor_metric` 테이블 | 반영 | | 보관기간 정리 | `retention_cleanup.php --dry-run`, `--execute` (SMS/센서/측정/감사로그) | 반영 | | 백업·복구 증빙 | `scripts/backup_evidence.php` | 반영 | | 보안 증적 보고서 | `security_evidence.php?format=md` | 반영 | diff --git a/docs/SECURITY_EVIDENCE_PACKAGE.md b/docs/SECURITY_EVIDENCE_PACKAGE.md index e33f78a..1a770e4 100644 --- a/docs/SECURITY_EVIDENCE_PACKAGE.md +++ b/docs/SECURITY_EVIDENCE_PACKAGE.md @@ -16,7 +16,7 @@ | 5 | 네트워크 증적 | 방화벽 정책, 단말 인바운드 차단 확인, HTTPS 인증서 화면 | | 6 | 물리 설치 증적 | STM32 보드, SHT30 센서 설치 사진과 장비 ID | | 7 | 관리자 접근통제 증적 | MFA 등록 화면, 로그인 화면, 감사로그 존재 확인 | -| 8 | 측정/임계 경보 증적 | 측정값 저장(`sensor_metric`), 종류별 경보·정상복귀 SMS(`sms_log`) 화면 | +| 8 | 측정/임계 경보 증적 | 측정값 저장(`leak_sensor_metric`), 종류별 경보·정상복귀 SMS(`leak_sms_log`) 화면 | | 9 | 운영 절차 증적 | 운영·보안 점검표, 보관기간 정리 dry-run, 백업 파일 목록, 수신자 현행화 확인 | | 10 | 소스코드 기반 설명 | `SOURCE_SECURITY_EVIDENCE.md`의 기술통제 설명 | @@ -79,7 +79,7 @@ security-evidence-YYYYMMDD/ - [ ] `security_evidence.php` 점검 결과에 중요 실패 항목이 있으면 조치 결과 또는 예외 사유를 적는다. - [ ] 단말 증적에는 환경변수 존재 여부만 있고 원문 값은 없다. - [ ] MFA 증빙은 운영 비밀키가 아닌 증빙용 임시 키 또는 마스킹된 키를 사용한다. -- [ ] 측정/경보 증빙은 측정값 저장(`sensor_metric`)과 종류별 경보·정상복귀 SMS(`sms_log`)를 함께 보여준다. +- [ ] 측정/경보 증빙은 측정값 저장(`leak_sensor_metric`)과 종류별 경보·정상복귀 SMS(`leak_sms_log`)를 함께 보여준다. - [ ] 실제 도메인은 `https://`로 접속되는 화면을 포함한다. - [ ] 네트워크 증적에는 단말로 들어오는 불필요한 포트가 없다는 근거가 포함된다. - [ ] 월간 보고서에는 생성 시각과 보고 범위가 보인다. diff --git a/docs/SECURITY_PLAN_ATTACHMENT_GUIDE.md b/docs/SECURITY_PLAN_ATTACHMENT_GUIDE.md index d0dc930..6e16212 100644 --- a/docs/SECURITY_PLAN_ATTACHMENT_GUIDE.md +++ b/docs/SECURITY_PLAN_ATTACHMENT_GUIDE.md @@ -210,7 +210,7 @@ DB 백업 주기: 매일 / 매주 / 매월 | 관리자 인증 | 관리자 로그인 화면, MFA 등록 증빙, 감사로그 | 비밀번호 해시, TOTP MFA, 세션 보호, 실패 제한 코드 설명 | | 측정/임계 | 임계 운영 기준(`METRIC_*`) | 측정값 범위 검증, 서버 임계 판정·쿨다운 설명 | | 개인정보 | SMS 수신자 관리 기준 | SMS 설정 구조, 로그 테이블 설명 | -| 로그관리 | 로그 보관기간, 관리자 감사로그 | `sensor_log`, `sensor_metric`, `sms_log`, `php/var/admin_audit.log` 설명 | +| 로그관리 | 로그 보관기간, 관리자 감사로그 | `leak_sensor_log`, `leak_sensor_metric`, `leak_sms_log`, `php/var/admin_audit.log` 설명 | | 백업 | DB/설정 백업 주기, 복구 테스트 결과 | `backup_evidence.php` 결과와 백업·복구 문구 | | 패치 | 월간 패치 점검 담당자 | 점검표 양식 | | 물리보안 | 설치 사진, 자산 정보 | 자산대장 양식 | diff --git a/docs/SECURITY_PLAN_HWP_REVIEW.md b/docs/SECURITY_PLAN_HWP_REVIEW.md index 0c4a5f9..09d0fe0 100644 --- a/docs/SECURITY_PLAN_HWP_REVIEW.md +++ b/docs/SECURITY_PLAN_HWP_REVIEW.md @@ -14,7 +14,7 @@ 현재 HWP 보안대책서는 사업 목적, 추진체계, 관리적·물리적·기술적 보안대책의 큰 틀은 갖추고 있습니다. 다만 실제 구현된 시스템과 비교하면 본문이 과거 범위(화재·누수·현장 사진·사고 대응)를 기준으로 작성되어 있어, 현재 구현 범위인 **서버실 온습도(SHT30) 모니터링 + 임계 SMS 경보 + 장비 오프라인 알림**으로 정정해야 합니다. - 사업 범위가 `화재/누수/장비 전원 이상`으로 넓게 쓰여 있으나, 현재 구현은 `서버실 온습도(SHT30) 측정 + 임계(고온/저온/고습/저습) SMS 경보 + 정상복귀 SMS + 장비 오프라인/복구 SMS + 대시보드`입니다. 누수센서·카메라·현장 사진 기능은 제거되었습니다. -- 구성도에는 큰 흐름이 있으나, 실제 구성요소인 STM32 보드, SHT30 온습도 센서, Cafe24 PHP API, MySQL, 측정 이력 테이블(`sensor_metric`), SMS 이력(`sms_log`), 월간 보고서가 명확히 표현되어 있지 않습니다. +- 구성도에는 큰 흐름이 있으나, 실제 구성요소인 STM32 보드, SHT30 온습도 센서, Cafe24 PHP API, MySQL, 측정 이력 테이블(`leak_sensor_metric`), SMS 이력(`leak_sms_log`), 월간 보고서가 명확히 표현되어 있지 않습니다. - API 키, 서명 검증, `config.local.php`, `firmware/common/secrets.h`(`APP_API_KEY`), 서버 임계 판정(`config.php`의 `METRIC_*`), 관리자 TOTP MFA, 관리자 감사로그 같은 실제 보안 구현 항목이 본문에 구체적으로 반영되어야 합니다. - 로그 보관기간, SMS 수신자 개인정보 관리, `retention_cleanup.php` 기반 정리 절차, 백업/복구 증빙, 장애 대응 절차가 선언 수준에 머물러 있어 운영 기준표가 필요합니다. - '현장 사진/시설정보'는 더 이상 처리하지 않으므로 해당 절은 삭제하고, 통제 공백처럼 보이지 않도록 **온습도 임계 경보 + 오발송 방지(30분 쿨다운 / 복구 히스테리시스)**로 대체 서술합니다. @@ -35,7 +35,7 @@ | 권한 및 계정관리 (L106~L110) | 관리자 계정 최소 부여, 비밀번호 정책 | `ADMIN_PASSWORD_HASH`, `ADMIN_TOTP_SECRET`, `MFA_SETUP_TOKEN`, 세션 쿠키 옵션, 로그인 실패 잠금, 감사로그, 비밀번호/TOTP 교체 절차 추가 | 높음 | | 단말기 보안대책 (L135~L143) | 기본 계정 변경, 하드코딩 금지 | 비밀값은 `firmware/common/secrets.h`(`APP_API_KEY`)에 두고 미커밋(.gitignore)으로 빌드 머신에서만 보관, API 키 서버 `config.php`의 `API_KEY`와 일치, 불필요 기능 비활성화 추가. (카메라 권한 관리 문구 불필요) | 높음 | | 플랫폼/서비스 보안대책 (L150~L156) | 인증, 패치, 기본 서비스 제거 | API 서명 검증, 측정값 범위 검증(온도 -40~125℃ / 습도 0~100%), DB prepared statement, TOTP MFA, `config.local.php` 비공개 관리 추가. (사진 업로드/MIME/매직바이트 검증 문구 삭제) | 높음 | -| 로그관리 (L94~L95) | 로그 보관기간 정의, 위변조 방지 | `sensor_log`, `sensor_metric`, `sms_log`, 서버 `php/var/admin_audit.log`, `retention_cleanup.php` 기준으로 보관·점검 기준 추가. (`leak_incident`/`leak_photo`/Pi 로그 삭제) | 높음 | +| 로그관리 (L94~L95) | 로그 보관기간 정의, 위변조 방지 | `leak_sensor_log`, `leak_sensor_metric`, `leak_sms_log`, 서버 `php/var/admin_audit.log`, `retention_cleanup.php` 기준으로 보관·점검 기준 추가. (`leak_incident`/`leak_photo`/Pi 로그 삭제) | 높음 | | 장비 설치·보호 (L114, L118~L120) | "화재·누수 감지센서, IoT 보드 및 **카메라**" | "SHT30 온습도 센서, STM32 보드"로 교체. 카메라 문구 삭제. 자산대장 등록은 유지 | 높음 | ## 3. HWP에 추가해야 할 장·절 @@ -50,7 +50,7 @@ | SHT30 온습도 센서 | I2C 온습도 측정(5분 주기) | 서버실 고정 설치, 임의 탈거 방지, 자산대장 등록 | | PHP API | `api/sensor_data.php` | API 키 서명 검증, HTTPS, 측정값 범위 검증, 서버 임계 판정 | | 임계 판정 | `config.php`의 `METRIC_*` 상수 | 고온30/저온10℃·고습70/저습20% 임계, 복구 히스테리시스(1.0℃/3.0%), 30분 쿨다운 | -| MySQL DB | `sensor_log`, `sensor_status`, `sensor_metric`, `sms_log` | 최소 권한 DB 계정, 백업, 보관기간 관리 | +| MySQL DB | `leak_sensor_log`, `leak_sensor_status`, `leak_sensor_metric`, `leak_sms_log` | 최소 권한 DB 계정, 백업, 보관기간 관리 | | 대시보드 | `dashboard.php` | 관리자 로그인, TOTP MFA, 세션 보호, 온습도/경보 현황 표시 | | MFA 등록 | `setup_mfa.php` | 임시 등록 토큰, 인증 앱 등록 검증, 외부 QR/API 미사용 | | 설치 점검 | `setup_wizard.php` | DB/권한/SMS/임계 설정 점검 | @@ -69,7 +69,7 @@ 2. STM32 보드가 측정값을 수집하여 정상 보고(`periodic`/`startup`)를 구성한다. 3. 단말이 HTTPS로 PHP API에 측정값을 전송한다(인바운드 포트 미개방, 아웃바운드 POST만). 4. 서버가 API 서명, API 키 일치, 필수 필드, 측정값 범위(온도 -40~125℃ / 습도 0~100%)를 검증한다. -5. 서버가 운영 임계(`METRIC_*`)로 고온/저온/고습/저습을 재판정하여 `sensor_metric`에 저장하고, 정상 보고는 `sensor_status`를 갱신한다. +5. 서버가 운영 임계(`METRIC_*`)로 고온/저온/고습/저습을 재판정하여 `leak_sensor_metric`에 저장하고, 정상 보고는 `leak_sensor_status`를 갱신한다. 6. 임계 이탈 시 30분 쿨다운과 복구 히스테리시스를 적용하여 종류별 SMS를 발송하고, 임계 안쪽으로 회복하면 정상복귀 SMS를 발송한다. 7. heartbeat 타임아웃이 지나면 장비 오프라인 SMS를, 보고 재개 시 복구 SMS를 발송한다. 8. 대시보드와 월간 보고서에서 온습도·경보·SMS·오프라인 현황을 표시한다. @@ -85,12 +85,12 @@ | 단계 | 처리 내용 | 증적 | |---|---|---| -| 측정 | SHT30 측정값 수신, `sensor_metric` 기록, 임계 판정 | `sensor_metric.metric_status` | -| 경보 | 임계 이탈 시 종류별 SMS 발송(쿨다운 적용) | `sms_log` (`[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`) | +| 측정 | SHT30 측정값 수신, `leak_sensor_metric` 기록, 임계 판정 | `leak_sensor_metric.metric_status` | +| 경보 | 임계 이탈 시 종류별 SMS 발송(쿨다운 적용) | `leak_sms_log` (`[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`) | | 확인 | 담당자가 대시보드에서 온습도/경보 상태 확인 | 대시보드 캡처 | | 조치 | 공조/환기 등 현장 점검 및 환경 정상화 | 운영 점검 기록 | -| 복구 | 임계 안쪽으로 회복 시 정상복귀 SMS | `sms_log` 복구 메시지 | -| 오프라인 | heartbeat 타임아웃 시 오프라인 SMS, 복구 시 복구 SMS | `cron_heartbeat.php` 실행 로그, `sms_log` | +| 복구 | 임계 안쪽으로 회복 시 정상복귀 SMS | `leak_sms_log` 복구 메시지 | +| 오프라인 | heartbeat 타임아웃 시 오프라인 SMS, 복구 시 복구 SMS | `cron_heartbeat.php` 실행 로그, `leak_sms_log` | | 보고 | 월간 보고서에 경보·SMS·오프라인 현황 반영 | `monthly_report.php` | ### 3.4 설치·운영 점검 절차 @@ -100,8 +100,8 @@ | 점검 항목 | 확인 방법 | 불합격 시 조치 | |---|---|---| | DB 연결 | 설치 점검 화면 | `config.local.php` DB 설정 수정 | -| 필수 테이블 | 설치 점검 화면 | `sql/schema_sht30.sql` 또는 `sql/migration_drop_leak.sql` 적용 | -| 측정 이력 테이블 | 설치 점검 화면 | `sensor_metric` 생성 확인 | +| 필수 테이블 | 설치 점검 화면 | `sql/schema_sht30.sql` 적용 | +| 측정 이력 테이블 | 설치 점검 화면 | `leak_sensor_metric` 생성 확인 | | 임계 설정 | 설치 점검 화면 | `config.php`/`config.local.php`의 `METRIC_*` 값 확인 | | API 키 | 설치 점검 화면 | 서버와 단말의 키 일치 확인 | | 관리자 해시 | 설치 점검 화면 | `php setup_hash.php`로 해시 생성 | @@ -120,9 +120,9 @@ HWP의 "보관기간은 최소한으로 설정" 문구를 아래처럼 구체화 | 데이터 | 보관 위치 | 권장 보관기간 | 파기/정리 기준 | |---|---|---:|---| -| 센서 이벤트 로그 | `sensor_log` | 365일 | 백업 후 기간 초과분 삭제(`retention_cleanup.php`) | -| 온습도 측정 이력 | `sensor_metric` | 365일 | 기간 초과분 삭제 | -| SMS 발송 로그 | `sms_log` | 365일 | 개인정보 최소화 후 기간 초과분 삭제 | +| 센서 이벤트 로그 | `leak_sensor_log` | 365일 | 백업 후 기간 초과분 삭제(`retention_cleanup.php`) | +| 온습도 측정 이력 | `leak_sensor_metric` | 365일 | 기간 초과분 삭제 | +| SMS 발송 로그 | `leak_sms_log` | 365일 | 개인정보 최소화 후 기간 초과분 삭제 | | 관리자 감사로그 | `php/var/admin_audit.log` | 365일 | 기간 초과분 정리 | | 설정 비밀값 | `config.local.php`, `firmware/common/secrets.h`(`APP_API_KEY`, 미커밋) | 운영 중 | 담당자 변경 또는 유출 의심 시 즉시 교체 | @@ -196,7 +196,7 @@ HWP의 "보관기간은 최소한으로 설정" 문구를 아래처럼 구체화 교체 문구: -> 본 시스템은 현장 사진 등 시설정보를 수집·저장하지 않으며, 처리하는 개인정보는 SMS 수신자 휴대전화번호로 한정한다. 수신자 번호는 알림 발송 목적에 한하여 최소 수집·이용하고, 담당자 변경 시 즉시 현행화한다. 온습도 임계 경보는 동일 종류 30분 쿨다운과 복구 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. SMS 발송 이력(`sms_log`)은 장애 대응·감사 목적 범위에서만 열람하고, 보관기간 경과 시 삭제 또는 비식별 처리한다. +> 본 시스템은 현장 사진 등 시설정보를 수집·저장하지 않으며, 처리하는 개인정보는 SMS 수신자 휴대전화번호로 한정한다. 수신자 번호는 알림 발송 목적에 한하여 최소 수집·이용하고, 담당자 변경 시 즉시 현행화한다. 온습도 임계 경보는 동일 종류 30분 쿨다운과 복구 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. SMS 발송 이력(`leak_sms_log`)은 장애 대응·감사 목적 범위에서만 열람하고, 보관기간 경과 시 삭제 또는 비식별 처리한다. ## 6. 추가 증적 목록 diff --git a/docs/SECURITY_PLAN_PASS_READINESS.md b/docs/SECURITY_PLAN_PASS_READINESS.md index a229b36..5720b3f 100644 --- a/docs/SECURITY_PLAN_PASS_READINESS.md +++ b/docs/SECURITY_PLAN_PASS_READINESS.md @@ -100,10 +100,10 @@ HWP 추가 문구: | 전송구간 | 센서 데이터 탈취·변조 | HTTPS/TLS 사용, API 서명 검증 | `sensor_data.php`, 단말 펌웨어 | 실제 HTTPS 화면, `security_evidence.php?format=md` | | API 인증 | 임의 측정값 주입 | 공유 API 키와 요청 서명 검증 | PHP API, `firmware/common/secrets.h`(`APP_API_KEY`) | 403 테스트 결과, 보안 증적 보고서 | | 측정값 검증 | 비정상 측정값 저장 | 필수 필드·측정값 범위(온도 -40~125℃ / 습도 0~100%) 검증 | `sensor_data.php` | 비정상 요청 차단 테스트 결과 | -| 임계 경보 | 오발송·미발송 | 서버 임계 판정(`METRIC_*`), 30분 쿨다운, 복구 히스테리시스 | `config.php`, `sensor_data.php` | `sms_log`, `sensor_metric` 상태 | +| 임계 경보 | 오발송·미발송 | 서버 임계 판정(`METRIC_*`), 30분 쿨다운, 복구 히스테리시스 | `config.php`, `sensor_data.php` | `leak_sms_log`, `leak_sensor_metric` 상태 | | 비밀값 | DB/SMS/API 키 유출 | 서버는 `config.local.php`, STM32 단말은 `firmware/common/secrets.h`(`APP_API_KEY`)로 분리, 저장소 미포함(.gitignore) | 서버/단말 설정 | STM32 콘솔 로그, .gitignore 설정, 저장소 검색 결과 | | 관리자 인증 | 대시보드 무단 접근 | 비밀번호 해시, TOTP MFA, 세션 쿠키 보호, 로그인 실패 제한, 감사로그 | `login.php`, `setup_mfa.php`, `admin_security.php`, `dashboard.php`, `security_evidence.php` | 로그인 화면, MFA 등록 증빙, 감사로그, 보안 증적 보고서 | -| 개인정보 | SMS 수신자 번호 노출 | 최소 수집, 담당자 변경 시 현행화, 로그 접근 제한 | `config.local.php`, `sms_log` | 수신자 목록 관리대장 | +| 개인정보 | SMS 수신자 번호 노출 | 최소 수집, 담당자 변경 시 현행화, 로그 접근 제한 | `config.local.php`, `leak_sms_log` | 수신자 목록 관리대장 | | 로그관리 | 추적 불가 | 센서/측정/SMS 로그와 관리자 감사로그 저장, 보관기간 정리 | MySQL, `php/var/admin_audit.log`, `retention_cleanup.php` | 월간 보고서, 단말 증적 보고서, 감사로그, retention dry-run 결과 | | 백업 | 장애 시 복구 불가 | DB·설정 파일 백업, 복구 점검, 백업 증빙 생성 | 운영 절차, `scripts/backup_evidence.php` | 백업 이력, 복구 테스트 결과, backup-evidence 보고서 | | 패치 | 취약한 OS/PHP 사용 | 단말 OS, PHP, DB 정기 업데이트 | 운영 절차 | `OPERATIONS_SECURITY_CHECKLIST.md`, 월간 점검표 | @@ -126,13 +126,13 @@ HWP에 아래 문구를 추가합니다. ### 5.2 측정값 검증 및 서버 임계 판정 -> 센서 데이터 API는 POST JSON 요청과 필수 필드(장비 ID·센서 ID·이벤트 유형·타임스탬프)를 검증하고, 온도(-40~125℃)·습도(0~100%) 범위를 벗어난 값은 무효 처리한다. 임계 판정은 펌웨어가 아니라 서버 `config.php`의 운영 임계(`METRIC_*`: 고온30/저온10℃, 고습70/저습20%)로 재수행하여 `sensor_metric`에 상태를 기록한다. 동일 종류 경보는 30분 쿨다운, 복구는 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 SMS 오발송을 방지한다. +> 센서 데이터 API는 POST JSON 요청과 필수 필드(장비 ID·센서 ID·이벤트 유형·타임스탬프)를 검증하고, 온도(-40~125℃)·습도(0~100%) 범위를 벗어난 값은 무효 처리한다. 임계 판정은 펌웨어가 아니라 서버 `config.php`의 운영 임계(`METRIC_*`: 고온30/저온10℃, 고습70/저습20%)로 재수행하여 `leak_sensor_metric`에 상태를 기록한다. 동일 종류 경보는 30분 쿨다운, 복구는 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 SMS 오발송을 방지한다. 확인 증적: -- 정상 측정값 저장 결과(`sensor_metric`) +- 정상 측정값 저장 결과(`leak_sensor_metric`) - 비정상 메소드/필드 누락/범위 초과 차단 결과 -- 임계 초과 시 종류별 SMS, 회복 시 정상복귀 SMS 발송 이력(`sms_log`) +- 임계 초과 시 종류별 SMS, 회복 시 정상복귀 SMS 발송 이력(`leak_sms_log`) ### 5.3 관리자 페이지 접근통제 @@ -164,7 +164,7 @@ STM32 단말 firmware/common/secrets.h: 저장소 미커밋(.gitignore), 빌드 확인 증적: -- `sensor_status` 마지막 보고 시각 +- `leak_sensor_status` 마지막 보고 시각 - `cron_heartbeat.php` 실행 로그 - 오프라인 SMS 발송 이력 @@ -213,7 +213,7 @@ HWP에 아래 책임표를 추가합니다. |---|---|---:|---| | SMS 수신자 번호 | 개인정보 | 담당자 재직/업무 담당 기간 | 담당자 변경 즉시 삭제·수정 | | SMS 발송 로그 | 개인정보 포함 가능 | 1년 | DB 삭제 또는 비식별 처리 | -| 온습도 측정 이력 | 운영 데이터 | 1년 | 기간 초과 DB 삭제(`sensor_metric`) | +| 온습도 측정 이력 | 운영 데이터 | 1년 | 기간 초과 DB 삭제(`leak_sensor_metric`) | | 센서 로그 | 운영 로그 | 1년 | 기간 초과 DB 삭제 | | 관리자 감사로그 | 감사/운영 이력 | 1년 | 기간 초과분 정리 | | API 키/SMS 인증키 | 인증정보 | 운영 중 | 교체 후 구 키 폐기 | @@ -266,9 +266,9 @@ HWP에 아래 책임표를 추가합니다. | 정보 | 예시 | 목적 | 접근자 | 저장 위치 | |---|---|---|---|---| -| 담당자 휴대전화번호 | SMS 수신자 번호 | 이상상태 알림 | 운영 관리자 | `config.local.php`, `sms_log` | -| 온습도 측정값 | 온도, 습도, 시각, 임계 상태 | 환경 모니터링 | 운영 관리자 | `sensor_metric`, MySQL | -| 센서 이벤트 | 센서 ID, 상태, 시각 | 장애 감지 | 운영 관리자 | `sensor_log`, MySQL | +| 담당자 휴대전화번호 | SMS 수신자 번호 | 이상상태 알림 | 운영 관리자 | `config.local.php`, `leak_sms_log` | +| 온습도 측정값 | 온도, 습도, 시각, 임계 상태 | 환경 모니터링 | 운영 관리자 | `leak_sensor_metric`, MySQL | +| 센서 이벤트 | 센서 ID, 상태, 시각 | 장애 감지 | 운영 관리자 | `leak_sensor_log`, MySQL | | 관리자 계정 | 관리자 ID, 비밀번호 해시, TOTP 비밀키 | 대시보드 접근 | 운영 관리자 | `config.local.php`, `php/var/admin_audit.log` | | 장비 식별자 | Device ID, 위치명 | 장비 관리 | 운영 관리자 | 단말 설정, DB | @@ -278,7 +278,7 @@ HWP에 아래 책임표를 추가합니다. ### 9.3 경보 오발송 방지 문구 -> 본 시스템은 현장 사진 등 시설정보를 수집·저장하지 않는다. 온습도 임계 경보는 서버에서 운영 임계(`METRIC_*`)로 판정하며, 동일 종류 경보는 30분 쿨다운, 정상복귀 판정은 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. 임계 초과·정상복귀·장비 오프라인·복구 이력은 `sms_log`에 기록하며, 보관기간 경과 시 정리한다. +> 본 시스템은 현장 사진 등 시설정보를 수집·저장하지 않는다. 온습도 임계 경보는 서버에서 운영 임계(`METRIC_*`)로 판정하며, 동일 종류 경보는 30분 쿨다운, 정상복귀 판정은 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. 임계 초과·정상복귀·장비 오프라인·복구 이력은 `leak_sms_log`에 기록하며, 보관기간 경과 시 정리한다. ## 10. 보안성 검토 제출 관점의 필수 첨부물 @@ -317,7 +317,7 @@ HWP 부록에 아래 체크리스트를 넣습니다. | 11 | 장비 오프라인 감지가 동작하는가 | heartbeat 기준 알림 | | | | 12 | DB 백업과 복구 점검을 수행했는가 | 복구 테스트 완료 | | | | 13 | 단말이 부팅 후 자동 보고를 시작하는가 | 전원 인가 시 startup 보고·주기 보고 동작 | | | -| 14 | 온습도 측정·저장이 검증되었는가 | `sensor_metric` 측정 이력 저장 | | | +| 14 | 온습도 측정·저장이 검증되었는가 | `leak_sensor_metric` 측정 이력 저장 | | | | 15 | 월간 보고서가 생성되는가 | 월별 조회/CSV 가능 | | | | 16 | 관리자 감사로그가 남는가 | 로그인 성공/실패, 로그아웃 기록 | | | | 17 | 보관기간 정리 절차가 있는가 | `retention_cleanup.php --dry-run` 결과 확인 | | | @@ -336,8 +336,8 @@ HWP 부록에 아래 체크리스트를 넣습니다. | 월간 보고서 화면 캡처 | 경보 처리 및 SMS/온습도 현황 확인 | | API 정상/비정상 테스트 로그 | 정상 요청 저장, 비정상 키 거부 확인 | | 테스트 SMS 수신 화면 | 담당자 알림 경로 확인 | -| 측정값 저장 결과 | `sensor_metric` 기록 및 임계 상태 확인 | -| 임계 경보/복구 SMS 결과 | 종류별 경보·정상복귀 SMS 발송(`sms_log`) 확인 | +| 측정값 저장 결과 | `leak_sensor_metric` 기록 및 임계 상태 확인 | +| 임계 경보/복구 SMS 결과 | 종류별 경보·정상복귀 SMS 발송(`leak_sms_log`) 확인 | | 관리자 감사로그 | 로그인 성공/실패, 로그아웃 기록 확인 | | 보관기간 정리 dry-run | 측정/로그/감사로그 정리 대상 확인 | | 백업·복구 증빙 보고서 | 백업 파일 목록, 설정/감사로그 존재, 복구 테스트 결과 | diff --git a/docs/SHT30_SENSOR2_SETUP.md b/docs/SHT30_SENSOR2_SETUP.md index f5e1063..0c1f286 100644 --- a/docs/SHT30_SENSOR2_SETUP.md +++ b/docs/SHT30_SENSOR2_SETUP.md @@ -10,12 +10,6 @@ SOURCE sql/schema_sht30.sql; ``` - 기존 누수 설치를 온습도 전용으로 전환하는 경우에는 전환 마이그레이션을 적용합니다. - - ```sql - SOURCE sql/migration_drop_leak.sql; - ``` - 2. 서버 파일을 배포합니다. ```text @@ -26,7 +20,7 @@ sql/schema_sht30.sql ``` - API는 `sht30` 측정을 수신하면 `sensor_status`를 갱신하고 측정값을 `sensor_metric`에 저장합니다. 서버가 `METRIC_*` 임계로 고온/저온/고습/저습을 판정하고, 초과 시 종류별 30분 쿨다운으로 SMS를 발송합니다. + API는 `sht30` 측정을 수신하면 `leak_sensor_status`를 갱신하고 측정값을 `leak_sensor_metric`에 저장합니다. 서버가 `METRIC_*` 임계로 고온/저온/고습/저습을 판정하고, 초과 시 종류별 30분 쿨다운으로 SMS를 발송합니다. ## 2. 기본 구성 — STM32F407VGT6 (`sht30_fw`) @@ -112,8 +106,8 @@ SHT30_I2C_ADDR=0x44 첫 정상 보고 후: -- `sensor_status`에 `sensor_id=2` 항목이 생성됩니다. -- `sensor_metric`에 온도/습도 이력이 누적됩니다. +- `leak_sensor_status`에 `sensor_id=2` 항목이 생성됩니다. +- `leak_sensor_metric`에 온도/습도 이력이 누적됩니다. - `dashboard.php`가 센서 카드에 최신 온도/습도와 임계 상태를 표시합니다. 장비가 `HEARTBEAT_TIMEOUT_SEC`(기본 1200초=20분)을 초과해 보고를 멈추면 서버가 오프라인으로 판정하고 SMS를 발송합니다. diff --git a/docs/SOURCE_SECURITY_EVIDENCE.md b/docs/SOURCE_SECURITY_EVIDENCE.md index 25e4f83..9356f67 100644 --- a/docs/SOURCE_SECURITY_EVIDENCE.md +++ b/docs/SOURCE_SECURITY_EVIDENCE.md @@ -103,14 +103,14 @@ HWP 반영 문구: 사용자가 제공해야 하는 증적: -- 정상 측정값 저장 화면 또는 DB 기록(`sensor_metric`) +- 정상 측정값 저장 화면 또는 DB 기록(`leak_sensor_metric`) - 비정상 메소드/필드 누락/범위 초과 요청 차단 테스트 결과 ### 2.4 서버 임계 판정 및 SMS 오발송 방지 HWP 반영 문구: -> 온습도 임계 판정은 펌웨어가 아니라 서버에서 수행한다. 서버는 `config.php`의 운영 임계(`METRIC_*`: 고온30/저온10℃, 고습70/저습20%)로 고온·저온·고습·저습을 재판정하여 `sensor_metric.metric_status`에 기록하고, 임계 이탈 시 종류별 SMS를 발송한다. 동일 종류 경보는 30분 쿨다운을 적용하고, 정상복귀 판정은 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. 임계 안쪽으로 회복하면 정상복귀 SMS를 발송한다. +> 온습도 임계 판정은 펌웨어가 아니라 서버에서 수행한다. 서버는 `config.php`의 운영 임계(`METRIC_*`: 고온30/저온10℃, 고습70/저습20%)로 고온·저온·고습·저습을 재판정하여 `leak_sensor_metric.metric_status`에 기록하고, 임계 이탈 시 종류별 SMS를 발송한다. 동일 종류 경보는 30분 쿨다운을 적용하고, 정상복귀 판정은 히스테리시스(온도 1.0℃, 습도 3.0%)를 적용하여 경계 채터링과 SMS 오발송을 방지한다. 임계 안쪽으로 회복하면 정상복귀 SMS를 발송한다. 코드 근거: @@ -119,13 +119,13 @@ HWP 반영 문구: - `METRIC_TEMP_HYSTERESIS_C`(1.0), `METRIC_RH_HYSTERESIS`(3.0) - `METRIC_ALERT_COOLDOWN_SEC`(1800, 30분) - `php/api/sensor_data.php` - - `evaluate_metric_thresholds()`로 서버 재판정 → `sensor_metric` + - `evaluate_metric_thresholds()`로 서버 재판정 → `leak_sensor_metric` - 경보 종류(`high_temp`/`low_temp`/`high_humidity`/`low_humidity`)별 SMS, 쿨다운/복구 판정 - 임계 이탈 시 `send_metric_alert_sms()`, 회복 시 `send_metric_recovery_sms()` 사용자가 제공해야 하는 증적: -- 임계 초과 시 종류별 경보 SMS 발송 이력(`sms_log`: `[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`) +- 임계 초과 시 종류별 경보 SMS 발송 이력(`leak_sms_log`: `[고온경보]`/`[저온경보]`/`[고습경보]`/`[저습경보]`) - 정상복귀 SMS 발송 이력 - 쿨다운 적용으로 동일 경보가 30분 내 재발송되지 않는 결과 @@ -140,15 +140,15 @@ HWP 반영 문구: - `php/config.php` - `HEARTBEAT_TIMEOUT_SEC`(1200), `HEARTBEAT_CHECK_INTERVAL_SEC`(300) - `php/cron_heartbeat.php` - - 마지막 보고 시각 기준 오프라인 판정, `sensor_status.offline_alerted` 관리 + - 마지막 보고 시각 기준 오프라인 판정, `leak_sensor_status.offline_alerted` 관리 - `php/api/sensor_data.php` - 오프라인 상태였던 센서가 다시 보고하면 복구 SMS 발송 사용자가 제공해야 하는 증적: -- `sensor_status` 마지막 보고 시각 +- `leak_sensor_status` 마지막 보고 시각 - `cron_heartbeat.php` 실행 로그 -- 오프라인/복구 SMS 발송 이력(`sms_log`) +- 오프라인/복구 SMS 발송 이력(`leak_sms_log`) ### 2.6 비밀값 코드 분리 @@ -270,6 +270,6 @@ curl -I https://example.com/sht30_monitor/login.php 2. `setup_wizard.php` 점검 화면 3. API 정상/403 테스트 결과 4. 단말 `systemctl status leak-sensor` 화면 -5. 측정값 저장(`sensor_metric`) 및 임계 경보 SMS(`sms_log`) 화면 +5. 측정값 저장(`leak_sensor_metric`) 및 임계 경보 SMS(`leak_sms_log`) 화면 이 5개가 있으면 소스코드 기반 보안대책과 운영 적용 증적을 어느 정도 연결할 수 있습니다. diff --git a/docs/examples/admin_dashboard_capture_source.html b/docs/examples/admin_dashboard_capture_source.html index a6c3f54..ef7e17a 100644 --- a/docs/examples/admin_dashboard_capture_source.html +++ b/docs/examples/admin_dashboard_capture_source.html @@ -117,7 +117,7 @@

    운영 자가진단

    API 키 · 정상RPi와 PHP API_KEY 일치
    -
    SHT30 metric table · 정상sensor_metric 저장 가능
    +
    SHT30 metric table · 정상leak_sensor_metric 저장 가능
    최근 SMS 실패 · 확인 필요최근 7일 실패 1건
    diff --git a/docs/examples/admin_dashboard_user_manual.html b/docs/examples/admin_dashboard_user_manual.html index 301e554..feb2ad9 100644 --- a/docs/examples/admin_dashboard_user_manual.html +++ b/docs/examples/admin_dashboard_user_manual.html @@ -316,7 +316,7 @@
    상단 상태바정상이면 운영 경고 없음. 주의나 위험이면 바로 하단 세부 항목을 확인한다.
    요약 카드등록 센서, 임계 경보, 오프라인, 30일 임계 SMS, SMS 실패를 빠르게 확인한다.
    -
    운영 자가진단API 키, DB 테이블, sensor_metric 저장, SMS 실패 등 운영 준비 상태를 보여준다.
    +
    운영 자가진단API 키, DB 테이블, leak_sensor_metric 저장, SMS 실패 등 운영 준비 상태를 보여준다.
    @@ -401,7 +401,7 @@ 센서 온습도 미표시 SHT30 데이터가 저장되지 않았거나 측정이 실패했다. - sensor_metric 테이블, sht30-sensor 서비스, I2C 주소 0x44/0x45 확인. + leak_sensor_metric 테이블, sht30-sensor 서비스, I2C 주소 0x44/0x45 확인. SMS 실패 diff --git a/docs/examples/sht30_sensor2_setup.html b/docs/examples/sht30_sensor2_setup.html index 440b316..7f177bb 100644 --- a/docs/examples/sht30_sensor2_setup.html +++ b/docs/examples/sht30_sensor2_setup.html @@ -317,7 +317,7 @@
    설치 목표

    SHT30 온도와 습도를 5분마다 서버로 보내고 대시보드에 최신값을 표시한다.

    -

    두 장비 모두 SHT30 센서를 I2C로 읽어 `sensor_metric`에 온습도 이력을 남기며, 임계 초과 시 서버에서 경보 SMS를 발송한다.

    +

    두 장비 모두 SHT30 센서를 I2C로 읽어 `leak_sensor_metric`에 온습도 이력을 남기며, 임계 초과 시 서버에서 경보 SMS를 발송한다.