POSA_LEAKSMS/raspberry_pi4_pms7003/install.sh
cw2007 ec78be0bda feat(pms7003): 누수 노드에 미세먼지(PM2.5/PM10) 통합
센서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 커밋 차단
2026-07-19 10:46:33 +09:00

160 lines
6.5 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# Particulate-matter (PMS7003) module v2 (Raspberry Pi 4 Model B) install script
# Target OS: Raspberry Pi OS Bookworm (64-bit, aarch64)
#
# Air-gapped support:
# - If debs/ or wheelhouse/ is present, automatically switch to "offline mode".
# - In offline mode, never access the network. If the bundle is only partial,
# do not silently fall back to online; stop with a clear error (air-gapped rule). Pre-
# download is done on the build machine via vendor.sh.
# - Can be forced via the OFFLINE=1/0 environment variable.
# - Log file owner: SERVICE_USER (default pi).
#
# This script also enables the UART (enable_uart=1) and DISABLES the serial login console
# (so the PMS7003 has exclusive use of /dev/serial0), and adds the user to the dialout group.
# =============================================================================
set -euo pipefail
cd "$(dirname "$0")"
SERVICE_USER="${SERVICE_USER:-pi}"
echo "=========================================="
echo " Particulate-matter (PMS7003) module v2 (Pi 4) install"
echo "=========================================="
# 0) Board model notice (not enforced)
if [ -r /proc/device-tree/model ]; then
MODEL="$(tr -d '\0' < /proc/device-tree/model)"
echo "[INFO] Board: ${MODEL}"
case "${MODEL}" in
*"Raspberry Pi 4"*) : ;;
*) echo "[WARN] This folder is for Raspberry Pi 4 Model B only (you may continue anyway)." ;;
esac
fi
# Offline mode detection
OFFLINE="${OFFLINE:-}"
if [ -z "${OFFLINE}" ]; then
if compgen -G "debs/*.deb" >/dev/null || [ -d wheelhouse ]; then
OFFLINE=1
echo "[INFO] Offline bundle detected -> air-gapped offline install mode (no network)"
else
OFFLINE=0
echo "[INFO] No offline bundle -> online install mode (internet required)"
fi
fi
# 1) System packages: requests + venv
echo "[1/4] Installing system packages..."
if [ "${OFFLINE}" = "1" ]; then
if ! compgen -G "debs/*.deb" >/dev/null; then
echo "[ERROR] Offline mode but debs/ is empty. Place the vendor.sh output (debs/) in this folder and re-run." >&2
exit 1
fi
echo " Offline .deb install (apt-get --no-download - dependency ordering/resolution automatic, no network)"
# --no-install-recommends: vendor.sh does not bundle Recommends (--no-recommends), so match that.
sudo apt-get install -y --no-install-recommends --no-download ./debs/*.deb
else
echo " Online apt install (internet required)"
sudo apt-get update
sudo apt-get install -y python3-requests python3-venv
fi
# 2) Enable UART + disable the serial login console
echo "[2/4] Enabling UART (and disabling the serial console)..."
# Boot config path: Bookworm=/boot/firmware/config.txt, older=/boot/config.txt
BOOT_CFG=""
for c in /boot/firmware/config.txt /boot/config.txt; do
if [ -f "$c" ]; then BOOT_CFG="$c"; break; fi
done
if [ -n "${BOOT_CFG}" ]; then
if ! grep -q "^enable_uart=1" "${BOOT_CFG}"; then
echo " Adding enable_uart=1 to ${BOOT_CFG} (applied after reboot)"
echo "enable_uart=1" | sudo tee -a "${BOOT_CFG}" >/dev/null
NEED_REBOOT=1
else
echo " enable_uart=1 already set"
fi
else
echo "[WARN] config.txt not found - enable UART manually via raspi-config"
fi
# Disable the serial login console so /dev/serial0 is free for the sensor.
# Bookworm=/boot/firmware/cmdline.txt, older=/boot/cmdline.txt
CMDLINE=""
for c in /boot/firmware/cmdline.txt /boot/cmdline.txt; do
if [ -f "$c" ]; then CMDLINE="$c"; break; fi
done
if [ -n "${CMDLINE}" ] && grep -q "console=serial0" "${CMDLINE}"; then
echo " Removing 'console=serial0,115200' from ${CMDLINE} (frees the UART for PMS7003)"
sudo sed -i 's/console=serial0,[0-9]* //' "${CMDLINE}"
NEED_REBOOT=1
fi
# Disable the getty on the primary UART (harmless if already masked).
sudo systemctl disable --now serial-getty@ttyS0.service 2>/dev/null || true
sudo systemctl disable --now serial-getty@ttyAMA0.service 2>/dev/null || true
# Add the service user to the dialout group (/dev/serial0 access permission)
if getent group dialout >/dev/null 2>&1; then
sudo usermod -aG dialout "${SERVICE_USER}" || echo "[WARN] usermod failed - check SERVICE_USER=${SERVICE_USER}"
fi
# 3) Python venv + requests + pyserial
echo "[3/4] Setting up Python virtual environment..."
if [ ! -d venv ]; then
python3 -m venv --system-site-packages venv
echo " venv created"
else
echo " venv already exists - skipping"
fi
# shellcheck disable=SC1091
source venv/bin/activate
if [ "${OFFLINE}" = "1" ]; then
if [ ! -d wheelhouse ]; then
echo "[ERROR] Offline mode but wheelhouse/ is missing. Place the vendor.sh output (wheelhouse/) and re-run." >&2
exit 1
fi
echo " Offline wheel install (--no-index - no network)"
pip install --no-index --find-links=wheelhouse -r requirements.txt
else
echo " Online pip install (internet required)"
pip install --upgrade pip
pip install -r requirements.txt
fi
echo " Packages installed"
# 4) Log file (least privilege: owner-only write)
echo "[4/4] Creating log file..."
sudo mkdir -p /var/log
sudo touch /var/log/pms7003_sensor.log
if ! sudo chown "${SERVICE_USER}:${SERVICE_USER}" /var/log/pms7003_sensor.log; then
echo "[WARN] chown failed - check whether SERVICE_USER=${SERVICE_USER} exists"
fi
sudo chmod 640 /var/log/pms7003_sensor.log
# Lock config_local.py immediately (contains API_KEY - prevent world-readable until harden)
# Hardening (harden_pi.sh) tightens it again later, but to avoid exposure even in
# that window, lock it to 600 right at install time. Protected regardless of harden.
if [ -f config_local.py ]; then
chmod 600 config_local.py
sudo chown "${SERVICE_USER}:${SERVICE_USER}" config_local.py 2>/dev/null || true
echo " config_local.py set to permission 600 (owner ${SERVICE_USER})"
fi
# config_local notice (fail closed - if missing, execution stops immediately)
if [ ! -f config_local.py ]; then
echo ""
echo "[!] config_local.py is missing. Run the following, then fill in real values:"
echo " cp config_local.example.py config_local.py"
fi
echo ""
echo "=========================================="
echo " Install complete! (OFFLINE=${OFFLINE})"
echo "=========================================="
if [ "${NEED_REBOOT:-0}" = "1" ]; then
echo " [!] UART was just enabled / serial console disabled -> reboot to apply: 'sudo reboot'."
fi
echo " Port check: ls -l /dev/serial0 (should symlink to ttyS0 or ttyAMA0)"
echo " Run: source venv/bin/activate && python3 pms7003_sensor.py"
echo " Service: see the systemd section in README.md"
echo ""