2번 모듈 신규(raspberry_pi4_sht30/): SHT30 온습도 → cafe24, raw-body X-Signature HMAC, 폐쇄망 오프라인 설치(vendor/install), systemd, CRC/포맷 자가점검. firmware sht30_fw wire 포맷과 byte-parity 검증. 1번 모듈 통합(raspberry_pi4/): 누수+카메라를 동일 서버로. 레거시 sha256(key+json) body-sig → X-Signature HMAC 전환, 사진 canonical=device_id|sensor_id|log_id|timestamp. 서버 통합(php/): api/sensor_data.php 누수 이벤트 수신, api/photo_upload.php 신규(HMAC·finfo·서버생성 파일명·nonce 멱등·sensor 결속), photo.php 인증 사진서빙, dashboard.php 누수 패널+사진, sms_send.php 누수 SMS, config.php 태그/요약. DB: sql/migration_add_leak.sql(가산·멱등), schema_sht30.sql 통합 스키마. 보안 하드닝(외부 펜테스트 대비, 확정 CRITICAL/HIGH 0): cron_heartbeat api_key fail-closed, setup_mfa 부트스트랩 토큰 post-enrollment 잠금, var/ PHP 실행차단, HSTS. 문서/테스트: SYSTEM_OVERVIEW.md, security_hardening_test.php 갱신. 참고: 사용자 요청으로 세션 외 기존 미커밋 변경도 함께 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
23 KiB
23 KiB
변경 이력
Unreleased - 기능 추가 (고가치 M: 신뢰성·관측성)
경보가 조용히 죽지 않도록 보장 + 그것을 검증하는 신뢰성·관측성 묶음. 모두 cron/서버/ CLI 기반(새 상호작용 UI 없음), graceful degrade. (#5 임계·수신자 화면편집은 core config 로드를 DB 의존으로 바꿔 신뢰성 회귀 위험이 있어 별도 설계로 보류.)
M#1 수집 거부 사유 카운터 (관측성)
config.php:record_ingest_rejection()/read_ingest_rejections()(파일 flock 기반, DB 무관 → 위조요청 폭주에도 안전).api/sensor_data.php의 모든 거부 분기(403 서명/400 시각/ 413 본문/503 키/필드/sensor_id)를 사유별 일자 집계하고, 대시보드 상단에 사유별 건수(서명불일치→키/펌웨어, 시각오차→SNTP/RTC 등 한국어 라벨)로 노출한다. "왜 데이터가 안 들어오는가"를 서버 로그 없이 분별.
M#2 발송 실패 가시화 + 오프라인 재시도 (신뢰성)
dashboard.php: 최근 1시간 내 경보/오프라인/에스컬레이션 SMS 가 실패하면 '경보 SMS 발송 실패 — 미수신 가능' 배너.cron_heartbeat.php: 일회성 오프라인 SMS 발송이 실패하면offline_alerted를 0 으로 되돌려 다음 주기에 재시도한다(SMS_RETRY_OFFLINE). 게이트웨이 일시오류로 장비 다운을 영영 못 알리던 갭을 닫음.
M#3 미복귀 경보 에스컬레이션 (신뢰성, opt-in)
sms_send.php:maybe_escalate_alerts(): 임계 경보가ESCALATE_AFTER_SEC(기본 30분) 이상 미복귀로 지속되면 2차(야간) 수신자SMS_RECIPIENTS_TIER2에게[에스컬레이션]1회 통지(에피소드당 1회 — 최신 경보-상태가 경보일 때만). TIER2 가 비면 비활성. cron 호출.- 대화형 ack 버튼 UI 는 보류(별도 POST 표면 — 후속).
M#4 센서 고착(live-but-stuck) 검출 (자가신뢰성)
sms_send.php:maybe_detect_stuck_sensors(): 최근STUCK_DETECT_SAMPLES(6) 측정의 온도· 습도 표준편차가 둘 다STUCK_DETECT_EPSILON(0.01, 분해능 이하) 미만이면 소자 고장(같은 값 반복)을 의심해[센서고착]1회(쿨다운). 하트비트(수신 끊김)로는 못 잡는 무성실패를 보완 — 단일 센서가 거짓말하면 감시 전체가 무력화되므로 핵심.
M#6 카오스 드릴 (검증)
SMS_FORCE_FAIL게이트(send_sms_cafe24가 실제 전송 없이 실패 반환)와tests/chaos_drill.php(CLI) — 위 실패-의존 기능이 동작하는지 손시연 없이 검증. 라이브 종단 드릴 절차는 파일 헤더에 문서화.
테스트
tests/threshold_sms_test.php:stddev_pop단위 테스트(고착 판정).tests/security_hardening_test.php: M#1~#6 소스 회귀 고정.run_tests.sh: 드릴 포함.
보류(별도 설계 필요)
- 경보 ack 버튼 UI(대화형 POST), 임계·수신자 화면편집(#5, core config DB-override — DB 장애 시 config 로드 실패 → 사이트 다운 위험), 풀 카오스 하네스(prod DB 주입).
Unreleased - 기능 추가 (①시계동기 ②경보 생존성/결로/개인정보)
전수 아이디에이션에서 도출한 우선순위 1~2(①+②)를 구현했다. 외부 의존 0, 대부분 기존 파이프라인 재사용.
① 폐쇄망 시계동기 — 인터넷 NTP 의존 제거 (잠재 전면장애 해소)
firmware/common/app_config.h:APP_SNTP_SERVER기본값을pool.ntp.org(인터넷)에서 LAN NTP 자리표시자(192.0.2.123, RFC5737)로 교체하고 폐쇄망 필수 설정임을 명시했다. 진짜 폐쇄망에서 인터넷 NTP 는 도달 불가 → SNTP 실패 → RTC 미설정 →timestamp=0→ TLS 차단 + 서버 신선도 위반 전량거부 = 보고 전면장애였다(제약 위반 상태 해소).api/sensor_data.php: 200 응답에server_now(서버 Unix 시각)를 추가했다. 펌웨어가 RTC 드리프트를 서버시각으로 미세보정(LSI 드리프트/간헐 NTP 대비)하는 후속 펌웨어 작업의 입력이다(서버측 선반영; 보안 영향 없음).- TODO(운영): 배포 전
APP_SNTP_SERVER를 실제 LAN NTP IP 로 설정. 최초 시각 확보는 LAN NTP(또는 배터리 RTC)가 필요하며,server_now소비(RTC 보정)는 펌웨어 후속 과제.
- TODO(운영): 배포 전
②a SMS 잔액 사전경보
sms_send.php: Cafe24 응답의 잔여 건수를 파싱(send_sms_cafe24가remaining반환)하고, 임계(SMS_BALANCE_WARN_THRESHOLD, 기본 30) 이하이면 쿨다운(SMS_BALANCE_WARN_COOLDOWN_SEC) 적용[SMS 잔액부족]경고를 1회 발송한다(maybe_warn_sms_balance). 추가 외부호출 0.config.php:latest_sms_balance()+ops_checks.php에 'SMS 잔여 건수' 점검 추가.
②b 일일 헬스(생존신호) SMS — 데드맨
sms_send.php:maybe_send_daily_health_sms(): 하루 1통[정기점검]요약(정상 센서 수/ 최근 수신/24h 경보/SMS 잔여)을 발송한다.HEALTH_SMS_HOUR(기본 9시) 이후 첫 호출에서, 날짜 dedup + 네임드 락으로 중복 없이 1회.cron_heartbeat.php가 호출. '무소식=정상' 과 '무소식=경보경로 전체고장'의 모호성을 제거(관리자는 "안 오면 점검"만 기억).
②c 이슬점/결로 경보
sms_send.php: Magnus 이슬점(dew_point_c)·결로마진(condensation_margin_c)을 추가하고,(현재온도−이슬점) ≤ METRIC_DEWPOINT_MARGIN_C(기본 3℃)이면 새 종류condensation_risk/[결로주의]경보를 발화한다.evaluate_metric_thresholds(말미 추가)·metric_is_recovered(결로 해소 히스테리시스 포함)·send_metric_alert_sms(라벨)·sensor_data.php(표시값=결로마진) 에 통합. 온도·습도가 각각 정상이어도 잡히는 결합 위험(차가운 랙면 결로)을 단일 SHT30 산술만으로 포착 — 누수감지기 후신인 이 시스템의 본래 목적에 부합.
②d 수신번호 개인정보 마스킹
config.php:mask_phone()(010-****-1234)을dashboard.php·setup_wizard.php의 수신번호 표시에 적용했다. 발송 기록(sms_log)은 보존하되 화면 표시만 최소화(개인정보 최소표시).
테스트
tests/threshold_sms_test.php: 이슬점/결로마진/해소·mask_phone경계 테스트(기존 임계 벡터는 결로 미발화 확인 — 회귀 없음).tests/security_hardening_test.php: ①+② 소스 회귀 고정(SNTP 비-인터넷, server_now, 잔액/헬스/결로/마스킹 상수·함수·호출).
Unreleased - 개선 구현 (다중 에이전트 전수 점검 반영)
전체 서브시스템(펌웨어/PHP/DB/문서/빌드)을 다중 에이전트로 매핑·교차검증하여 도출한 개선 목록을 구현했다. 호환성 유지(가산/멱등) 를 원칙으로 했으며, 새 DB 테이블은 없어도 동작하도록 graceful degrade 한다.
보안 / 신뢰성 (서버 PHP)
- 재전송(replay) 멱등 방어 추가.
api/sensor_data.php가 인증·신선도를 통과한 요청 서명(X-Signature)을ingest_nonce테이블에 원자적으로 기록한다. 시계오차 창 (INGEST_MAX_CLOCK_SKEW_SEC) 안에서 같은 서명 본문이 다시 오면(펌웨어 재시도/캡처 재생) 중복 저장·중복 SMS 없이 멱등 200 으로 응답한다. 저장 실패 시 nonce 를 되돌려 재시도가 재처리되게 한다(유실 방지). 테이블이 없으면 자동 비활성 (INGEST_NONCE_ENABLED). - 임계 경보 SMS 중복 발송 경합 제거.
sms_send.php:send_metric_alert_sms()의 쿨다운 검사→발송→기록 구간을(센서,종류)단위 MySQL 네임드 락(GET_LOCK)으로 직렬화한다. 락 미지원 환경에서는 종전 동작으로 폴백한다(가용성 우선). - SMS 길이 캡. Cafe24 단문('S', EUC-KR ≤90B) 한도를 넘는 메시지를 발송 전
멀티바이트 경계 보존하며 자른다(
sms_truncate_euckr,SMS_MAX_EUCKR_BYTES). 위치/ 센서명이 길 때 게이트웨이의 무성 절단/거부를 막는다. - 경보 태그 단일 출처화.
[고온경보]등 상태 태그를config.php의SMS_TAG_*상수와sms_summary_select_sql()헬퍼로 일원화하고,sms_send/dashboard/monthly_report의 하드코딩 LIKE 리터럴을 제거했다. 태그를 한 곳만 바꿔도 쿨다운/복구/대시보드/월간 보고서가 일관 유지된다(문자열 어긋남 무성 장애 방지). - 세션 절대 수명 캡 추가 + 모든 인증 페이지로 확대. 유휴 타임아웃(
SESSION_TIMEOUT) 만으로 연속 활동 세션이 무한정 유지되던 문제를 로그인 시각 기준 절대 수명 (SESSION_ABSOLUTE_TIMEOUT, 기본 12시간)으로 보완했다. 타임아웃 검사를admin_security.php:admin_enforce_session_timeout()공통 헬퍼로 일원화하고dashboard.php·monthly_report.php·security_evidence.php·setup_wizard.php전부에서 호출한다(이전엔 대시보드에서만 검사돼 다른 인증 페이지로 우회 가능했던 갭을 닫음). - 반사형 XSS 차단.
test_mobile.php가 User-Agent 를 그대로 반사하던 것을text/plain+htmlspecialchars로 막았다. - 입력 검증.
sensor_data.php가sensor_id범위(TINYINT 1..255)를 경계에서 검사해 500 대신 깔끔한 400 을 반환한다. - 운영 자가진단 보강.
ops_checks.php가 잔존MFA_SETUP_TOKEN과 활성화된 레거시 서명 폴백(ALLOW_LEGACY_BODY_SIGNATURE)을 경고한다.config.local.example.php에ALLOW_LEGACY_BODY_SIGNATURE => false를 명시했다.
DB
sql/schema_sht30.sql·sql/migration_drop_leak.sql: 재전송 멱등용ingest_nonce테이블을 가산·멱등(CREATE TABLE IF NOT EXISTS)으로 추가했다.
펌웨어 / 빌드 (폐쇄망)
- 호스트 C 패리티 추가(미검증 갭 해소).
firmware/test/host/sig_cli.c와parity_test.py가 펌웨어 실제 서명 구현(common/sig.c)을 호스트 컴파일·실행하여 Python 레퍼런스(reference.py)·서버(verify_signature_raw)와 바이트 단위 일치를 검증한다(C↔Python↔PHP 3자). 이전엔 C 가 어떤 테스트에서도 컴파일되지 않았다. - 빌드 시 자리표시자 CA 차단.
build.ps1이server_ca.c의 PLACEHOLDER 를 검출하면 빌드를 중단한다(부팅 시bsp_fatal로 정지하던 것을 빌드 단계에서 선차단;-AllowPlaceholderCA로 강행 가능). - 벤더 핀 엄격 모드.
vendor.sh(VENDOR_STRICT=1)·vendor.ps1(-Strict)가 SHA-256 핀 미설정(TODO) 의존성이 있으면 실패한다(릴리스 재현성 강제). - stale 주석 정정.
net.c/lwipopts.h의 RNGTODO(hw)주석을 정정했다 — HW RNGbsp_rand32()는bsp.c에 이미 구현되어 weak 폴백을 덮어쓴다(예측성 완화는 이미 적용됨).sig.h/secrets.h.example의sha256(key||body)표기를 HMAC 로 정정.
문서
- 서명 방식 문서 드리프트 일괄 정정(16개 파일). 운영/보안 문서의
sha256(API_KEY+body)(키-접두) 표기를 실제 구현인HMAC-SHA256(키=API_KEY, 메시지=요청본문)으로 정정했다. SECURITY_DOCUMENT_CONSISTENCY_AUDIT.md의 거짓 일치 인증(false all-clear) 을 바로잡았다(서명 행을 HMAC 로 갱신 + §2.1 추가).out_of_range가 펌웨어 plausibility 전용이며 DB(sensor_metric.metric_status)에는normal/high_temp/low_temp/high_humidity/low_humidity만 저장됨을 명시 (wiring_diagram.md,HARDWARE.md,PORTING_NOTES.md, 펌웨어README.md).BUILD_OFFLINE.md의 비실존 스크립트/툴체인 이름 정정(vendor_deps.* → vendor.*,arm-none-eabi.cmake → arm-none-eabi-toolchain.cmake). 웹루트 경로가 펌웨어app_config.h:APP_API_PATH와 일치해야 함을 교차참조.- HWP 보안대책서 과대 스코프 경고를 명문화(온습도 전용; 화재/누수/정전/카메라
미구현). 원본
.hwp는 운영자가 동일하게 정정 필요(hwp_extracted_text.txt배너 +SECURITY_PLAN_HWP_REVIEW/PASS_READINESS).
테스트
run_tests.sh(전체 러너; php/python3/gcc 부재 시 SKIP 명시) 추가.tests/threshold_sms_test.php:sms_truncate_euckr()경계 테스트(EUC-KR 멀티바이트).tests/security_hardening_test.php: 위 개선(nonce/락/SMS캡/태그상수/세션캡/XSS/ops) 소스 회귀 고정.
운영자 필수 조치 (코드로 끝낼 수 없는 항목)
- (CRITICAL)
firmware/certs/server_ca.c를 실제 Cafe24 루트 CA 로 교체(자리표시자 상태로는 부팅 불가; 이제 빌드 단계에서도 차단됨). - (HIGH) 원본
.hwp보안대책서를 온습도 전용 스코프로 정정 후 제출. vendor.{sh,ps1}의 SHA-256 핀을 네트워크 빌드 머신에서 채우기(이후VENDOR_STRICT로 강제). 신규/전환 DB 에ingest_nonce생성(스키마/마이그레이션 재실행). 운영config.local.php에서MFA_SETUP_TOKEN비우기,ALLOW_LEGACY_BODY_SIGNATURE=false확인.
Unreleased - 보안 하드닝 (전체 코드리뷰 반영)
⚠️ 호환성 주의 (BREAKING)
- 요청 서명을 키 접두 SHA256 → HMAC-SHA256 으로 전환했습니다. 길이확장(length-
extension) 공격에 견디기 위함입니다.
X-Signature = HMAC-SHA256(key=API_KEY, msg=raw_body). 서버(verify_signature_raw)·펌웨어(sig.c)·호스트 레퍼런스(reference.py)를 동시에 바꿨으므로, 펌웨어 재플래시와 서버 배포를 함께 진행해야 합니다(구 펌웨어 ↔ 신 서버는 서명 불일치로 거부됨). 호스트 패리티 테스트와 ARM-Wconversion -Werror컴파일로 검증했습니다.
서버 (PHP)
api/sensor_data.php: 재전송(replay) 방어를 추가했습니다 — 본문timestamp가INGEST_MAX_CLOCK_SKEW_SEC(기본 600초) 범위를 벗어나거나 0 이하이면 거부합니다.api/sensor_data.php: 운영 비밀값 강제(fail-closed).API_KEY가 예시 기본값이면 수신을 503 으로 거부합니다(api_key_is_default()).api/sensor_data.php: 디바이스 문자열(device_id/sensor_name/device_location/metric_type) 의 제어문자를 제거해 SMS/로그 라인 주입을 막습니다.cron_heartbeat.php: 오프라인 SMS 중복 발송 경합을 제거했습니다 —offline_alerted0→1 전이를 원자적 조건부 UPDATE 로 선점한 프로세스만 발송합니다.monthly_report.php: CSV 내보내기의 수식 주입(formula injection)을 방지합니다(자유 텍스트 셀이= + - @등으로 시작하면 무력화).retention_cleanup.php: 감사로그 회전을 배타 락(flock)으로 묶어 동시 기록 유실을 막습니다.login.php: 브루트포스 카운터를 원자적 읽기-수정-쓰기로 변경해 동시 실패 시 카운트 덮어쓰기(lost update)를 막습니다.config.local.example.php: 예시 TOTP 비밀키를 공개 테스트 값에서 명백히 무효한 플레이스홀더로 교체했습니다(MFA 가 실제 비밀키 설정 전까지 fail-closed).
DB
sql/migration_drop_leak.sql: information_schema 가드 프로시저로 멱등화했습니다(재실행/ 부분 적용 DB 에서도 오류 없이 진행).
보안 리뷰 후속 수정 (다중 에이전트 검증에서 발견)
- (HIGH) 예시 TOTP 비밀키 회귀 수정.
config.local.example.php의ADMIN_TOTP_SECRET플레이스홀더(REPLACE_WITH_UNIQUE_BASE32_SECRET)가 Base32 로 디코드되어(REPLACEWITHUNIQUEBASE32SECRET→ 18바이트 키) 실제 코드275426를 생성 = 공개된 시드로 MFA 가 활성화되는 문제였습니다. 빈 문자열''로 교체해 fail-closed(키 미설정 시 로그인 차단)로 만들고, 회귀 테스트로 고정했습니다. - (MEDIUM) 레거시 서명 다운그레이드 경로 차단.
api/sensor_data.php가X-Signature헤더가 없으면 약한 키-접두 sha256(verify_signature)로 폴백하던 것을,ALLOW_LEGACY_BODY_SIGNATURE(기본 false)로 게이트했습니다. 기본값에서는 HMAC 헤더가 없으면 거부합니다(운영 STM32 클라이언트는 HMAC 만 사용). - (LOW) 인증 전 본문 크기 상한.
INGEST_MAX_BODY_BYTES(기본 8KB) 초과 본문은 json_decode/HMAC 이전에 413 으로 거부 — 미인증 증폭형 DoS 차단. - (LOW) heartbeat throttle 원자화.
check_offline_sensors_throttled()의 비원자 read-then-write 를 flock 기반 원자 판정으로 교체 — 동시 호출 시 중복 오프라인 스캔 방지. - (LOW)
CRON_SECRET강화.md5('cron_'.API_KEY)→hash_hmac('sha256','cron',API_KEY). - 알려진 잔여(문서화): 600초 창 내 재전송 dedup 부재, 운영 키 보유자에 의한 ingest
flooding(호스트 레벨 rate-limit/WAF 권장),
.htaccessApache 전용.
Unreleased - SHT30 온습도 전용 전환
시스템 범위
- 시스템을 서버실 온습도(SHT30) 24시간 모니터링 + 임계 초과 SMS + 웹 대시보드 전용으로 전환했습니다. 누수 감지/카메라/사진/사고대응(incident) 기능은 전면 제거했습니다.
- 단일 보드 구성으로 단순화:
sht30_fw(sensor_id=2,device_id=stm32-sht30-01)만 사용합니다. 누수 보드(board_leak/leak_fw)는 제거했습니다.
서버 (PHP/DB)
- 온습도 임계 SMS를 추가했습니다: 고온 30℃ / 저온 10℃ / 고습 70% / 저습 20%, 복귀 히스테리시스 ±1℃·±3%, 동일 종류 쿨다운 1800초(30분). 임계는 서버 판정값(
php/config.phpMETRIC_*,config.local.phpoverride)이라 폐쇄망에서 펌웨어 재플래시 없이 변경 가능합니다. - 측정값을
sensor_metric에 저장하고metric_status로 경보/정상복귀를 판정합니다. 장비 오프라인/복구는HEARTBEAT_TIMEOUT_SEC(기본 1200초=20분)으로 판정합니다. - 사진 업로드/열람 경로(
api/photo_upload.php,photo.php)와 사고 대응(incidents.php)을 제거했습니다. - 신규 설치용 통합 스키마를
sql/schema_sht30.sql로 정리했습니다(테이블:sensor_log,sensor_status,sensor_metric,sms_log). 기존 누수 설치 전환용sql/migration_drop_leak.sql을 추가해 레거시 누수 컬럼/테이블(leak_photo,leak_incident,is_leak등)을 정리합니다.
펌웨어 (STM32F407 sht30_fw)
- 누수 이벤트 본문에서
is_leak와이어 필드를 제거하고 온습도 측정 보고로 단일화했습니다. - SHT30 I2C 배선을 확정했습니다: I2C1 PB6(SCL)/PB7(SDA), 주소 0x44, 3.3V/GND, 4.7kΩ 풀업. 측정 주기
APP_SHT30_REPORT_INTERVAL_SEC=300(5분). - 레거시 4.7MΩ 누수 풀다운 회로 및 e-con 5C-08 결선 안내를 폐기했습니다.
문서
- README, 설치/운영 문서, 배선도, 정리 보고서를 SHT30 온습도 전용으로 재작성했습니다. 누수/사진/카메라/사고대응 관련 절·표를 온습도 임계 SMS/쿨다운/오프라인 기준으로 치환했습니다.
Unreleased - 메인 MCU 마이그레이션 (Raspberry Pi → STM32F407VGT6)
서버 (PHP)
sensor_data.php인증을 이중 모드로 확장했습니다:X-Signature헤더 기반 raw-body 서명(신규, STM32 펌웨어용)과 기존 본문 필드 서명(RPi, 하위 호환)을 모두 수용합니다.config.php에verify_signature_raw()를 추가했습니다 —sha256(API_KEY . 요청본문)로 검증하여 JSON 재직렬화/부동소수 포맷 차이로 인한 서명 불일치(R1)를 제거합니다.- raw-body/레거시 서명 회귀 테스트
tests/raw_body_signature_test.php를 추가했습니다.
펌웨어 (신규 firmware/, STM32F407VGT6 / Cortex-M4F)
- 메인 MCU를 Raspberry Pi Zero 2 W(Linux/Python)에서 STM32F407VGT6 베어메탈로 이식했습니다. 구성: 유선 Ethernet(LAN8720 RMII) + LwIP + mbedTLS(HTTPS) + FreeRTOS. WiFi/카메라는 제거.
- 두 보드 변형:
BOARD_LEAK(누수, sensor_id=1),BOARD_SHT30(온습도, sensor_id=2). RPi 2대 구성을 미러링. - 이식 가능한 코어(
sha256_sw/sig/jsonbody/sht30_convert/httpapi)는 호스트 패리티(Python↔PHPverify_signature_raw) + ARM 크로스컴파일(-Werror -Wconversion)로 검증했습니다. - 다중 에이전트 코드리뷰에서 확인된 결함을 수정: TLS 인증서 유효기간 검증 활성화(RTC/SNTP 시간 공급), FreeRTOS 정적메모리 콜백 중복정의 제거, LwIP 코어락 위반(PHY 링크/DNS) 수정, 로그 UART PA2 충돌 → USART3(PD8/PD9) 이동, EXTI 핸들러 추가, HW RNG 기반
bsp_rand32. - 폐쇄망 빌드: 의존성은
firmware/third_party에 벤더링, 오프라인 빌드/플래시 절차 문서화(firmware/docs/BUILD_OFFLINE.md). docs/stm32f407_migration_plan.md(계획+구현 현황),firmware/docs/{HARDWARE,PORTING_NOTES}.md를 추가했습니다.- 레거시 RPi 펌웨어(
raspberry_pi/)와 systemd 유닛(systemd/)을 제거했습니다(STM32 펌웨어로 대체).
v2605 - 2026-05-19
- v2605 정리 전 원본을
_backup/pre_v2605_20260519/에 보관했습니다. - PHP/RPi 운영 비밀값을 코드 하드코딩에서 로컬 설정/환경 변수 방식으로 분리했습니다.
config.local.example.php,config.example.py,leak-sensor.env.example을 추가했습니다.- 누수경보 SMS 쿨다운이 실제 발송 로직에 적용되도록 수정했습니다.
- 누수 지속 SMS를 시간 기반 5분 정책으로 정리했습니다.
- heartbeat 체크 주기와 오프라인 판정 시간을 분리했습니다.
- 사진 업로드가 실제
leak_alert로그 ID를 검증하도록 강화했습니다. - 신규 설치용 통합 DB 스키마
sql/schema_v2605.sql을 추가했습니다. - README와 보고서를 실제 코드 기준으로 최신화했습니다.
- 누수 사고 대응 테이블과 대시보드 사고 패널을 추가했습니다.
- 누수 사고
확인함,출동 중,조치 완료,오탐상태 변경을 지원합니다. - 미확인 누수 사고 SMS 에스컬레이션을 추가했습니다.
- 대시보드 첫 화면을 위험 상태 우선 구조로 재배치했습니다.
- 운영 자가진단 패널을 추가해 설정값, 마이그레이션, 권한, SMS 실패를 표시합니다.
- 30일 운영 요약을 추가해 사고 상태, 평균 확인/조치 시간, SMS 실패, 사진 저장 현황을 보여줍니다.
- 설치/운영 점검 화면
php/setup_wizard.php를 추가했습니다. - 보안대책서 첨부용 증적 보고서
php/security_evidence.php와 운영·보안 체크리스트를 추가했습니다. - 월간 보고서 화면
php/monthly_report.php를 추가했습니다. - 누수 사진 타임라인 다중 촬영을 추가했습니다.
- 사진 타임라인 DB 컬럼과 기존 설치용
sql/migration_photo_timeline.sql을 추가했습니다. - Pi 증적 수집 스크립트를 추가해 API 키 원문 없이 서비스/포트/환경파일 상태를 확인할 수 있게 했습니다.