센서1·3(누수 노드)에 PMS7003 를 온습도와 동일 sensor_id 로 통합하고 metric_type='pms7003'
로 구분해 수집한다. LED 는 스위칭 없이 상시 점등 방식으로 확정.
- raspberry_pi4_pms7003/: PMS7003 노드 풀세트(드라이버·센서·install·systemd·vendor·배선·연결테스트·프레임 단위테스트)
- 서버: evaluate_metric_thresholds() 에 pm25/pm10 선택 인자 추가(온습도 전용 호출 후방호환),
sensor_data.php 수집 경로에 pms7003 metric 반영, config.php/예시에 PM 임계 추가
- SQL: migration_pms7003.sql · schema_sht30.sql 정합, tools/migration_leak_add_{pms7003,sht30} 마이그레이터
- tests/pm_threshold_test.php + run_tests.sh 러너 등록
- .gitignore: 노드별 config_local.py 커밋 차단
169 lines
8.8 KiB
Bash
169 lines
8.8 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# migrate.sh — 기존 누수 노드 → 누수+온습도(SHT30) 결합 노드 원샷 마이그레이션
|
|
#
|
|
# 대상: 이미 설치·하드닝(harden_pi.sh)까지 끝난 누수 전용 Pi 4 노드 (Pi-A/Pi-C).
|
|
# SSH 는 꺼져 있으므로 현장에서 USB 로 이 폴더를 통째로 가져가 실행한다.
|
|
#
|
|
# 하는 일 (한 번에):
|
|
# 1) 기존 누수 서비스 설치 경로 자동 탐지 (leak-sensor.service → 폴백 /home/pi/raspberry_pi4)
|
|
# 2) 기존 config_local.py 에서 API_URL / API_KEY / DEVICE_LOCATION / sensor_id 자동 추출
|
|
# → sht30/config_local.py 자동 생성 (동일 sensor_id·동일 위치 라벨, 10분 주기)
|
|
# ★ 동일-id 통합 원칙: 온습도는 같은 노드 누수 센서와 같은 sensor_id 로 보고한다.
|
|
# 3) /home/pi/sht30 에 모듈 복사 → install.sh 실행 (debs/·wheelhouse/ 동봉 시 완전 오프라인)
|
|
# 4) systemd 서비스(sht30-sensor) 등록·기동 + 권한 잠금(config_local.py 600)
|
|
# 5) 검증: I2C 인식(0x44/0x45)·서비스 상태. I2C 첫 활성화면 재부팅 안내.
|
|
#
|
|
# 폐쇄망 준비물 (인터넷 되는 빌드 PC에서 먼저):
|
|
# cd sht30 && bash vendor.sh → debs/, wheelhouse/ 생성 → USB 에 그대로 포함
|
|
#
|
|
# 실행 (노드 현장, 모니터+키보드):
|
|
# sudo bash migrate.sh
|
|
#
|
|
# 서버 측 사전 작업(이미 되어 있으면 생략):
|
|
# config.local.php 의 'METRIC_SENSOR_IDS' 에 이 노드의 sensor_id 추가.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
[ "$(id -u)" -eq 0 ] || { echo "[ERROR] sudo 로 실행하세요: sudo bash migrate.sh"; exit 1; }
|
|
cd "$(dirname "$0")"
|
|
|
|
SERVICE_USER="${SERVICE_USER:-pi}"
|
|
DEST_DIR="${DEST_DIR:-/home/${SERVICE_USER}/sht30}"
|
|
|
|
echo "=============================================="
|
|
echo " 누수 노드 → 누수+온습도 결합 마이그레이션"
|
|
echo "=============================================="
|
|
|
|
# ── 0) 사전 점검: 페이로드·오프라인 번들 ─────────────────────────────────────
|
|
[ -d sht30 ] || { echo "[ERROR] sht30/ 페이로드 폴더가 없습니다. USB 복사가 불완전합니다."; exit 1; }
|
|
if compgen -G "sht30/debs/*.deb" >/dev/null && [ -d sht30/wheelhouse ]; then
|
|
echo "[INFO] 오프라인 번들(debs/·wheelhouse/) 확인 — 네트워크 없이 설치합니다."
|
|
else
|
|
echo "[WARN] 오프라인 번들이 없습니다. 폐쇄망 노드면 여기서 중단하고"
|
|
echo " 빌드 PC에서 'cd sht30 && bash vendor.sh' 후 USB 를 다시 만드세요."
|
|
echo " (온라인 노드라면 Enter 로 계속 — 인터넷 설치 시도)"
|
|
read -r -p "계속하려면 Enter, 중단은 Ctrl+C: " _
|
|
fi
|
|
|
|
# ── 1) 기존 누수 설치 경로 탐지 ──────────────────────────────────────────────
|
|
LEAK_DIR="${LEAK_DIR:-}"
|
|
if [ -z "${LEAK_DIR}" ] && systemctl cat leak-sensor.service >/dev/null 2>&1; then
|
|
LEAK_DIR="$(systemctl cat leak-sensor.service | sed -n 's/^WorkingDirectory=//p' | head -1)"
|
|
fi
|
|
if [ -z "${LEAK_DIR}" ] || [ ! -d "${LEAK_DIR}" ]; then
|
|
LEAK_DIR="/home/${SERVICE_USER}/raspberry_pi4"
|
|
fi
|
|
if [ ! -f "${LEAK_DIR}/config_local.py" ]; then
|
|
echo "[ERROR] 기존 누수 설치를 찾지 못했습니다 (${LEAK_DIR}/config_local.py 없음)."
|
|
echo " LEAK_DIR=/실제/경로 sudo -E bash migrate.sh 로 경로를 지정하세요."
|
|
exit 1
|
|
fi
|
|
echo "[1/5] 기존 누수 설치 확인: ${LEAK_DIR}"
|
|
|
|
# 이미 마이그레이션된 노드 보호(멱등): 서비스가 살아 있으면 재설치 여부 확인
|
|
if systemctl is-active --quiet sht30-sensor 2>/dev/null; then
|
|
echo "[WARN] sht30-sensor 서비스가 이미 실행 중입니다. 계속하면 코드·설정을 갱신합니다."
|
|
read -r -p "계속하려면 Enter, 중단은 Ctrl+C: " _
|
|
fi
|
|
|
|
# ── 2) 기존 설정에서 값 추출 → sht30 config_local.py 생성 ────────────────────
|
|
echo "[2/5] 기존 config_local.py 에서 서버·센서 정보 추출..."
|
|
# config_local.py 는 순수 변수 파일(외부 import 없음) — 안전하게 로드해 필드만 추출.
|
|
mapfile -t CFG < <(python3 - "${LEAK_DIR}" <<'PYEOF'
|
|
import sys, importlib.util
|
|
d = sys.argv[1]
|
|
spec = importlib.util.spec_from_file_location("leak_cfg", d + "/config_local.py")
|
|
m = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(m)
|
|
sensors = getattr(m, "SENSORS", None)
|
|
sid = sensors[0]["id"] if sensors else 1
|
|
print(m.API_URL)
|
|
print(m.API_KEY)
|
|
print(m.DEVICE_LOCATION)
|
|
print(sid)
|
|
PYEOF
|
|
)
|
|
API_URL="${CFG[0]}"; API_KEY="${CFG[1]}"; DEVICE_LOCATION="${CFG[2]}"; SENSOR_ID="${CFG[3]}"
|
|
case "${SENSOR_ID}" in (*[!0-9]*|'') echo "[ERROR] sensor_id 추출 실패: '${SENSOR_ID}'"; exit 1;; esac
|
|
echo " sensor_id=${SENSOR_ID}, location='${DEVICE_LOCATION}' (API_KEY 는 표시하지 않음)"
|
|
|
|
# ── 3) 모듈 복사 + config_local.py 생성 ──────────────────────────────────────
|
|
echo "[3/5] ${DEST_DIR} 에 모듈 복사..."
|
|
mkdir -p "${DEST_DIR}"
|
|
cp -r sht30/. "${DEST_DIR}/"
|
|
# 시크릿 포함 파일은 생성 시점부터 소유자 전용(600)
|
|
umask 077
|
|
cat > "${DEST_DIR}/config_local.py" <<CFGEOF
|
|
# =============================================================================
|
|
# config_local.py — migrate.sh 가 기존 누수 설정에서 자동 생성 (커밋 금지)
|
|
# 동일 sensor_id(${SENSOR_ID}) 통합: 온습도를 누수 센서와 같은 id 로 보고한다.
|
|
# =============================================================================
|
|
API_URL = "${API_URL}"
|
|
API_KEY = "${API_KEY}"
|
|
|
|
DEVICE_ID = "rpi4-sht30-id${SENSOR_ID}"
|
|
DEVICE_LOCATION = "${DEVICE_LOCATION}" # 누수 config_local.py 와 문자열 동일(라벨 플랩 방지)
|
|
|
|
SENSOR_ID = ${SENSOR_ID} # ★ 누수와 동일 id
|
|
SENSOR_NAME = "Sensor ${SENSOR_ID} (SHT30)"
|
|
REPORT_INTERVAL = 600 # 누수 노드 온습도 = 10분 주기
|
|
CFGEOF
|
|
umask 022
|
|
chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DEST_DIR}"
|
|
chmod 600 "${DEST_DIR}/config_local.py"
|
|
echo " config_local.py 생성 완료 (600, ${SERVICE_USER} 소유)"
|
|
|
|
# ── 4) install.sh 실행 (I2C 활성화·venv·오프라인 패키지) + 서비스 등록 ───────
|
|
echo "[4/5] SHT30 설치 스크립트 실행..."
|
|
# venv 소유권이 서비스 계정과 일치하도록 서비스 계정으로 실행(내부 sudo 는 NOPASSWD).
|
|
sudo -u "${SERVICE_USER}" env SERVICE_USER="${SERVICE_USER}" bash -c "cd '${DEST_DIR}' && bash install.sh"
|
|
|
|
# systemd 유닛: WorkingDirectory/ExecStart 를 실제 경로로 치환해 설치
|
|
sed -e "s|^WorkingDirectory=.*|WorkingDirectory=${DEST_DIR}|" \
|
|
-e "s|^ExecStart=.*|ExecStart=${DEST_DIR}/venv/bin/python sht30_sensor.py|" \
|
|
-e "s|^User=.*|User=${SERVICE_USER}|" \
|
|
-e "s|^Group=.*|Group=${SERVICE_USER}|" \
|
|
"${DEST_DIR}/systemd/sht30-sensor.service" > /etc/systemd/system/sht30-sensor.service
|
|
systemctl daemon-reload
|
|
systemctl enable sht30-sensor >/dev/null
|
|
echo " systemd 유닛 등록 완료 (sht30-sensor)"
|
|
|
|
# ── 5) 기동·검증 ─────────────────────────────────────────────────────────────
|
|
echo "[5/5] 기동·검증..."
|
|
NEED_REBOOT=0
|
|
if [ ! -e /dev/i2c-1 ]; then
|
|
NEED_REBOOT=1
|
|
echo " [!] /dev/i2c-1 이 아직 없습니다 — I2C 첫 활성화. 재부팅 후 서비스가 자동 시작됩니다."
|
|
else
|
|
if command -v i2cdetect >/dev/null 2>&1; then
|
|
if i2cdetect -y 1 2>/dev/null | grep -qE "44|45"; then
|
|
echo " I2C 센서 인식 확인 (0x44/0x45)"
|
|
else
|
|
echo " [WARN] i2cdetect 에서 0x44/0x45 가 보이지 않습니다 — 배선(WIRING) 확인 필요."
|
|
fi
|
|
fi
|
|
systemctl restart sht30-sensor
|
|
sleep 3
|
|
if systemctl is-active --quiet sht30-sensor; then
|
|
echo " sht30-sensor 서비스 실행 중"
|
|
else
|
|
echo " [ERROR] 서비스 기동 실패 — journalctl -u sht30-sensor -n 30 으로 확인하세요."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "=============================================="
|
|
echo " 마이그레이션 완료 — 누수+온습도 결합 노드"
|
|
echo "=============================================="
|
|
echo " sensor_id : ${SENSOR_ID} (누수와 동일 id 통합 보고)"
|
|
echo " 설치 경로 : ${DEST_DIR} · 보고 주기 10분"
|
|
echo " 서버 확인 : 대시보드 실시간 현황에서 '${SENSOR_ID}번 센서' 카드에"
|
|
echo " 누수·온습도 칩이 함께 보이면 정상."
|
|
echo " 서버 설정 : config.local.php METRIC_SENSOR_IDS 에 ${SENSOR_ID} 포함 여부 확인."
|
|
if [ "${NEED_REBOOT}" = "1" ]; then
|
|
echo ""
|
|
echo " ⚠️ 지금 'sudo reboot' 하세요 — I2C 는 재부팅 후 활성화되고 서비스는 자동 시작됩니다."
|
|
fi
|
|
echo ""
|