Cafe24 PHP/MySQL 서버부터 STM32F407 MCU 펌웨어까지, 폐쇄망(air-gapped) 배포 절차
HARDWARE.md 기준으로 정확히 생성한 SVG입니다.
Cafe24 관리자 콘솔·phpMyAdmin·플래싱 툴·실제 대시보드 화면처럼 운영 환경에서만 캡처할 수 있는 화면은
“📷 캡처 위치” 자리표시로 두었습니다. 각 자리표시의 캡션이 가리키는 화면을 직접 캡처해 그 자리에 넣으면 됩니다.
(저장소의 기존 캡처는 구버전 v2605 누수감지 UI라 현재 화면과 다릅니다 — 부록 참조.)
단일 STM32F407 보드(sht30_fw, sensor_id=2)가 SHT30 센서를 I²C로 5분마다 읽어,
본문을 HMAC-SHA256으로 서명해 TLS 1.2로 Cafe24 PHP 서버에 POST합니다. 서버가 임계를 재판정하여
고온/저온/고습/저습·결로·오프라인 상황을 SMS로 알리고 웹 대시보드/보고서를 제공합니다.
| 구분 | 설치 위치 | 역할 |
|---|---|---|
| Cafe24 서버 | public_html/raspi_leck_detecter/ | 센서 API 수신, DB 저장, 임계 판정, SMS, 대시보드/보고서 |
| Cafe24 MySQL | Cafe24 DB | 센서 로그 · 온습도 측정값 · SMS 로그 · 장비 상태 |
| STM32F407VGT6 | 펌웨어 플래시 (sht30_fw) | SHT30 I²C 측정, 5분 주기 보고, raw-body HMAC 서명 |
| 서버 cron | Cafe24 cron 또는 외부 스케줄러 | cron_heartbeat.php 주기 실행(오프라인 감지·헬스·에스컬레이션) |
raspi_leck_detecter 는 레거시 이름이지만 펌웨어
app_config.h 의 APP_API_PATH 와 반드시 일치해야 합니다. 폴더명을 바꾸려면 펌웨어도 함께 바꿔 재빌드해야 합니다.| 서버측(Cafe24) | 장치측(STM32) |
|---|---|
|
• Cafe24 PHP 호스팅 • Cafe24 MySQL DB(이름/계정/비밀번호) • Cafe24 SMS 서비스 계정 + secure key + 발신번호 • 운영 담당자 SMS 수신번호 • HTTPS 도메인/경로 + Cafe24 루트 CA |
• STM32F407VGT6 보드(DISCOVERY 계열/커스텀 PCB) • SHT30 모듈(한진데이터 P4422-3, I²C) • LAN8720 Ethernet PHY(RMII) + LAN 케이블 • I²C 풀업 4.7kΩ × 2 (모듈 내장 시 생략) • 외부 3.3V USB-UART 어댑터(USART3 콘솔) • 폐쇄망 빌드 머신(arm-none-eabi-gcc, CMake, Ninja, ST-Link 툴) |
config.local.php 의 API_KEY 와 펌웨어
secrets.h 의 APP_API_KEY 는 바이트 단위로 동일해야 합니다. 서명
X-Signature = lowercase_hex(HMAC-SHA256(key=API_KEY, msg=요청본문)) 검증의 기준입니다.Cafe24 FTP(또는 파일 관리자)로 웹 루트 아래에 PHP 트리를 업로드합니다.
public_html/raspi_leck_detecter/
.htaccess config.php config.local.php ← 서버에서만 생성
config.local.example.php
login.php dashboard.php setup_wizard.php
setup_mfa.php admin_security.php ops_checks.php
sms_send.php cron_heartbeat.php monthly_report.php
security_evidence.php retention_cleanup.php setup_hash.php
blocked.php test_mobile.php
api/
sensor_data.php
var/
.gitkeep ← PHP가 쓰기 가능해야 함(상태/감사/카운터 파일)
config.local.php 와 펌웨어 secrets.h 는 비밀값이므로
저장소/공유에 올리지 않습니다. php/var/ 는 .htaccess 로 외부 접근이 차단됩니다(확인 필수).
public_html/raspi_leck_detecter/ 업로드 완료된 파일 목록 화면config.local.phpconfig.local.example.php 를 복사해 config.local.php 를 만들고 실제 운영 값으로 교체합니다.
<?php
return [
'DB_HOST' => 'localhost',
'DB_PORT' => 3306,
'DB_NAME' => 'your_db_name',
'DB_USER' => 'your_db_user',
'DB_PASS' => 'your_db_password',
'API_KEY' => 'replace-with-long-random-secret', // 펌웨어 APP_API_KEY 와 동일!
'ALLOW_LEGACY_BODY_SIGNATURE' => false, // HMAC 전용(레거시 RPi 없으면 false)
'SMS_USER_ID' => 'your-cafe24-sms-user-id',
'SMS_SECURE' => 'your-cafe24-sms-secure-key',
'SMS_SENDER' => '01000000000',
'SMS_RECIPIENTS' => ['01000000000'],
'SMS_RECIPIENTS_TIER2' => [], // 미복귀 30분 시 2차(야간) 통지(opt-in)
'ADMIN_USER' => 'admin',
'ADMIN_PASSWORD_HASH' => '', // setup_hash.php 출력으로 채움(빈값=로그인 차단)
'ADMIN_TOTP_SECRET' => '', // setup_mfa.php 등록 후 채움(빈값=로그인 차단)
'MFA_SETUP_TOKEN' => 'replace-with-temporary-random-token', // 등록 후 비울 것
// 온습도 임계(서버 판정 → SMS). 기본값은 config.php 의 METRIC_*
'METRIC_TEMP_HIGH_C' => 30, 'METRIC_TEMP_LOW_C' => 10,
'METRIC_RH_HIGH' => 70, 'METRIC_RH_LOW' => 20,
];
ADMIN_PASSWORD_HASH 또는 ADMIN_TOTP_SECRET 가
비어 있으면 관리자 로그인이 차단됩니다(보안). 4·6단계에서 채웁니다.Cafe24 호스팅 관리 → MySQL → phpMyAdmin. 좌측에서 운영 DB(DB_NAME)를 클릭합니다.
신규 설치는 sql/schema_sht30.sql 전체를 복사해 SQL 탭에 붙여넣고 실행.
생성 테이블: sensor_log, sensor_status, sensor_metric, sms_log
(+ 재전송 방어용 ingest_nonce).
| SQL 파일 | 적용 상황 |
|---|---|
sql/schema_sht30.sql | 신규 설치(통합 스키마) |
sql/migration_drop_leak.sql | 기존 누수(v2605) 설치 → 온습도 전용 전환. 비가역 — 실행 전 DB 전체 백업 |
setup_wizard.php 점검 결과를 먼저 확인하세요.
schema_sht30.sql 붙여넣고 [실행]한 결과(“쿼리 실행 성공” + 좌측 4~5개 테이블 생성된 화면)raspi_leck_detecter/var/ 가 PHP에서 쓰기 가능해야 합니다(브루트포스/하트비트/감사로그/거부 카운터 파일 저장).
php setup_hash.php "새관리자비밀번호"
출력된 bcrypt 해시를 config.local.php 의 ADMIN_PASSWORD_HASH 에 넣습니다.
MFA_SETUP_TOKEN 을 임시 설정한 뒤 아래 주소로 접속해 인증 앱(Google/Microsoft Authenticator, Authy)에 등록합니다.
https://your-domain.example/raspi_leck_detecter/setup_mfa.php?token=임시토큰
화면의 Base32 키를 인증 앱에 등록 → 6자리 코드 검증 → 표시된 ADMIN_TOTP_SECRET 줄을
config.local.php 에 반영 → 등록 후 MFA_SETUP_TOKEN 을 빈 값으로 바꿉니다.
setup_mfa.php 등록 화면(Base32 수동 입력 키 + 6자리 코드 “확인됨” 상태). 비밀키 노출 주의 — 캡처 시 키 일부 가림 권장https://your-domain.example/raspi_leck_detecter/setup_wizard.php
확인 항목: DB 연결 · 필수 테이블 · API 키 교체 · 관리자 해시 · 관리자 TOTP · SMS 수신자 ·
var/ 쓰기 권한 · 테스트 SMS 발송.
setup_wizard.php 점검 결과(모든 항목 ✅ + 테스트 SMS “발송 성공”). 수신번호는 마스킹되어 표시됨cron_heartbeat.php 를 1분 간격으로 실행합니다(오프라인 감지 + 일일 헬스 SMS + 미복귀 에스컬레이션).
* * * * * php /home/hosting_user/public_html/raspi_leck_detecter/cron_heartbeat.php
https://your-domain.example/raspi_leck_detecter/cron_heartbeat.php?key=<CRON_SECRET>
CRON_SECRET = HMAC-SHA256("cron", API_KEY) 입니다(코드가 파생). 절대 경로는 Cafe24 파일 관리자/phpinfo()로 확인.
마지막 수신 후 HEARTBEAT_TIMEOUT_SEC(기본 1200초=20분) 초과 시 오프라인 판정.
cron_heartbeat.php 가 1분 주기로 등록된 목록LQFP-100 기준. SHT30 I²C, LAN8720 RMII, USART3 콘솔, 상태 LED를 결선합니다.
| SHT30 | STM32 핀 | 비고 |
|---|---|---|
| VDD | 3.3V | SCL/SDA 각각 4.7kΩ → 3.3V 풀업(모듈 내장 시 생략). 100kHz 권장. 명령 0x2C06 → ~20ms → 6바이트 read |
| SCL | PB6 | |
| SDA | PB7 | |
| GND / ADDR | GND |
| STM32 핀 | RMII 신호 | STM32 핀 | RMII 신호 |
|---|---|---|---|
| PA1 | ETH_REF_CLK (50MHz 입력) | PC4 | ETH_RXD0 |
| PA2 | ETH_MDIO ⚠️ (USART2 금지) | PC5 | ETH_RXD1 |
| PC1 | ETH_MDC | PB11 | ETH_TX_EN |
| PA7 | ETH_CRS_DV | PB12/PB13 | ETH_TXD0 / TXD1 |
.bin 이 에어갭을
넘어 전달됩니다. 설치 호스트는 어떤 것도 직접 가져오지 않습니다.bash firmware/scripts/vendor.sh # 또는 vendor.ps1
# 릴리스 빌드는 핀(SHA-256) 강제:
VENDOR_STRICT=1 bash firmware/scripts/vendor.sh
STM32CubeF4 HAL/CMSIS · FreeRTOS-Kernel · lwip(+sntp) · mbedtls 를 firmware/third_party/ 에 고정 버전으로 배치. 빌드·런타임 외부 fetch 0건.
secrets.hcp firmware/common/secrets.h.example firmware/common/secrets.h
# APP_API_KEY 를 서버 config.local.php 의 API_KEY 와 바이트 단위로 동일하게 채운다.
secrets.h 는 커밋 금지. 산출 .bin 에 키가 임베드되므로 .bin 자체를 비밀로 취급합니다.certs/server_ca.c (필수)openssl s_client -connect your-domain.example:443 -servername your-domain.example -showcerts </dev/null
# → 루트 CA 를 PEM 으로 저장 후 certs/server_ca.c 의 SERVER_CA_PEM 에 반영
# (SERVER_CA_PEM_LEN = sizeof(...) , 종결 NUL 포함)
server_ca.c 는 PLACEHOLDER라 tls_init()가
실패해 부팅이 정지(fail-closed)합니다. build.ps1 이 PLACEHOLDER를 감지하면 빌드를 중단합니다 — 반드시 실제 CA로 교체하세요.app_config.h| 설정 | 값 |
|---|---|
APP_API_HOST / APP_API_PATH | 운영 도메인 / /raspi_leck_detecter/api/sensor_data.php |
APP_SNTP_SERVER ⚠️ | 폐쇄망 LAN NTP 서버 IP(기본 192.0.2.123 자리표시자 — 반드시 교체) |
APP_SHT30_I2C_ADDR / APP_SHT30_REPORT_INTERVAL_SEC | 0x44 / 300(5분) |
pool.ntp.org)는 폐쇄망에서 도달 불가 →
시간 미동기 → timestamp=0 → TLS 차단 + 서버 전량 거부. 반드시 사내 LAN NTP IP로 설정하세요.cmake -S firmware -B firmware/build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=firmware/cmake/arm-none-eabi-toolchain.cmake
cmake --build firmware/build --target sht30_fw # → sht30_fw.elf / .bin / .hex
(선택) 호스트 패리티 게이트로 C↔Python↔PHP 서명 일치 확인: python3 firmware/test/host/parity_test.py
cmake --build … --target sht30_fw 성공 로그(산출물 sht30_fw.bin/.hex/.elf + arm-none-eabi-size 출력)sha256sum firmware/build/sht30_fw.bin > FW_HASHES.txt # 빌드 머신
# USB 등 신뢰 매체로 격리 호스트에 전달 후 재계산 대조
pwsh firmware/scripts/flash.ps1 # 권장(자동 탐지)
# 대안:
st-flash write sht30_fw.bin 0x08000000
STM32_Programmer_CLI -c port=SWD -w sht30_fw.hex -v -rst
st-flash의 “Flash written / verified” 결과)외부 USB-UART 어댑터를 연결하고 부팅 시퀀스를 관찰합니다:
bsp_init → 링크업 → DHCP(또는 static) → SNTP 시간 동기
→ TLS 핸드셰이크 → 첫 startup POST → 서버 HTTP 200
I²C1 0x44 응답 · 첫 온/습도 측정 · 상태 LED(PD12) 패턴을 함께 확인. 서명 불일치(403)면
API_KEY↔APP_API_KEY, TLS 실패면 server_ca.c/SNTP를 재점검.
서버에서 아래 화면을 확인합니다.
https://your-domain.example/raspi_leck_detecter/setup_wizard.php
https://your-domain.example/raspi_leck_detecter/dashboard.php
https://your-domain.example/raspi_leck_detecter/monthly_report.php
https://your-domain.example/raspi_leck_detecter/security_evidence.php
setup_wizard.php 필수 점검 항목 통과 + 테스트 SMS 수신dashboard.php 에 최신 온/습도와 센서 상태(sensor_id=2) 표시sensor_metric 에 주기 측정값(periodic) 저장 · STM32 콘솔에 서버 200 기록metric_status 경보 + SMS 수신 / 월간 보고서에 내역 조회
dashboard.php 화면(상단 “정상 감시 중” + 2번 센서 SHT30 온/습도 카드 + 30일 요약). 데스크톱·모바일 2장 권장| 증상 | 확인 지점 |
|---|---|
| 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. 대시보드 ‘수집 거부/발송 실패’ 배너 확인 |
| 온습도 미표시 | I²C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, sensor_metric, 펌웨어 측정 로그 |
| TLS 실패 | server_ca.c 실제 CA 반영, SNTP 시간 동기(APP_SNTP_SERVER=LAN NTP) |
| 데이터 전혀 안 들어옴 | 대시보드 수집 거부 사유 카운터(서명불일치→키/펌웨어, 시각오차→SNTP/RTC)로 원인 분별 |
| 오프라인 알림 과다/지연 | HEARTBEAT_TIMEOUT_SEC(1200초), cron 주기 |
API_KEY = 펌웨어 APP_API_KEYADMIN_TOTP_SECRETstm32-sht30-01)와 설치 위치본 HTML 파일은 docs/ 폴더 기준 상대경로로 아래 자산을 참조할 수 있습니다(폴더 동반 시 표시됨):
| 경로 | 내용 | 상태 |
|---|---|---|
examples/captures/admin_dashboard_overview.png | 관리자 대시보드 개요 | ⚠️ v2605 누수 UI |
examples/captures/admin_dashboard_mobile.png | 대시보드 모바일 | ⚠️ v2605 |
examples/captures/admin_dashboard_incident.png | 경보/사고 카드 | ⚠️ v2605(사고/사진) |
evidence/mfa_setup_page_capture.png | MFA 등록 페이지 | 참고(개념 동일) |
examples/assets/sht30_sensor2_wiring.svg | 배선도 | ⚠️ Raspberry Pi 기준 |
assets/security_plan/generated/security_plan_target_architecture.png | 목표 아키텍처 | 보안계획 다이어그램 |
현재 시스템의 정확한 아키텍처·배선은 본문 그림 1·7(생성 SVG)을 기준으로 하세요.
서버실 온습도(SHT30) 모니터링 시스템 v2606 · 설치 가이드 · 폐쇄망(air-gapped) 배포 · 인쇄(Ctrl/Cmd+P) 지원