적대적 보안 감사에서 확정된 medium 6건 보완 (critical/high 0): - systemd 유닛(양 노드): NoNewPrivileges + ProtectSystem=strict/ ProtectHome=read-only/ProtectKernel*/ProtectControlGroups/RestrictSUIDSGID/ LockPersonality/RestrictRealtime/RestrictAddressFamilies(+AF_NETLINK) 샌드박싱. - harden_pi.sh: SSH 차단을 마지막 단계([6/6])로 재정렬 — 앞 단계 실패 시 set -e 로 SSH 가 살아있는 상태에서 중단(원격 복구 가능). nftables·unattended-upgrades 를 오프라인(debs/ --no-download)으로도 설치 지원(폐쇄망 harden). 미발견 secret 경고. - install.sh(양 노드): config_local.py 설치 즉시 chmod 600 (harden 전 노출창 제거). - vendor.sh(양 노드): .deb 를 파일명이 아닌 dpkg-deb Architecture 필드로 실검사 (arm64/all 만 허용, 그 외 hard-fail). nftables·unattended-upgrades 를 번들에 포함. 검증(3-리뷰어 워크플로)이 잡은 회귀 동반 수정: - [critical] ProtectHome=read-only 가 lgpio 의 작업디렉터리(.lgd-nfy 파이프) 쓰기를 막아 add_event_detect(누수 인터럽트 등록) 실패 → 데몬 크래시루프 → 감지 불능. Environment=LG_WD=/tmp 로 PrivateTmp 쓰기가능 /tmp 이동(누수 노드 한정, SHT30 무관). - [medium] 오프라인 설치에 --no-install-recommends 추가(vendor --no-recommends 정합, 네트워크 폴백 방지) — install.sh x2 + harden_pi.sh. - SupplementaryGroups=video gpio 명시 · nft|head 에 ||true(SIGPIPE 중단 방지) · online harden 분기에 apt-get update. ※ 온디바이스 런타임 테스트 1회 필요(빌드머신 Windows 에선 systemd 실행 불가): 실 Pi 에서 systemctl start 후 add_event_detect 콜백·카메라 촬영·업로드 정상 확인.
150 lines
6.3 KiB
Bash
150 lines
6.3 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# 온습도(SHT30) 모듈 v2 (Raspberry Pi 4 Model B) 설치 스크립트 — 2번 모듈
|
|
# 대상 OS: Raspberry Pi OS Bookworm (64-bit, aarch64)
|
|
#
|
|
# 폐쇄망(air-gapped) 지원:
|
|
# - debs/ 또는 wheelhouse/ 가 있으면 자동으로 "오프라인 모드"로 전환.
|
|
# - 오프라인 모드에서는 절대 네트워크에 접근하지 않는다. 번들이 일부만 있으면
|
|
# 온라인으로 몰래 빠지지 않고 명확한 오류로 중단한다(폐쇄망 규칙). 사전
|
|
# 다운로드는 vendor.sh 로 빌드 머신에서 수행.
|
|
# - 환경변수 OFFLINE=1/0 으로 강제 지정 가능.
|
|
# - 로그 파일 소유자: SERVICE_USER (기본 pi).
|
|
#
|
|
# 이 스크립트는 I2C 도 활성화한다(dtparam=i2c_arm=on + i2c-dev 로드 + i2c 그룹).
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
|
|
SERVICE_USER="${SERVICE_USER:-pi}"
|
|
|
|
echo "=========================================="
|
|
echo " 온습도(SHT30) 모듈 v2 (Pi 4) 설치 — 2번 모듈"
|
|
echo "=========================================="
|
|
|
|
# 0) 보드 모델 안내 (강제 아님)
|
|
if [ -r /proc/device-tree/model ]; then
|
|
MODEL="$(tr -d '\0' < /proc/device-tree/model)"
|
|
echo "[INFO] 보드: ${MODEL}"
|
|
case "${MODEL}" in
|
|
*"Raspberry Pi 4"*) : ;;
|
|
*) echo "[WARN] 이 폴더는 Raspberry Pi 4 Model B 전용입니다 (계속 진행은 가능)." ;;
|
|
esac
|
|
fi
|
|
|
|
# 오프라인 모드 판정
|
|
OFFLINE="${OFFLINE:-}"
|
|
if [ -z "${OFFLINE}" ]; then
|
|
if compgen -G "debs/*.deb" >/dev/null || [ -d wheelhouse ]; then
|
|
OFFLINE=1
|
|
echo "[INFO] 오프라인 번들 감지 → 폐쇄망 오프라인 설치 모드 (네트워크 미사용)"
|
|
else
|
|
OFFLINE=0
|
|
echo "[INFO] 오프라인 번들 없음 → 온라인 설치 모드 (인터넷 필요)"
|
|
fi
|
|
fi
|
|
|
|
# 1) 시스템 패키지: requests + venv + i2c-tools
|
|
echo "[1/4] 시스템 패키지 설치..."
|
|
if [ "${OFFLINE}" = "1" ]; then
|
|
if ! compgen -G "debs/*.deb" >/dev/null; then
|
|
echo "[ERROR] 오프라인 모드인데 debs/ 가 비어있습니다. vendor.sh 산출물(debs/)을 이 폴더에 두고 재실행하세요." >&2
|
|
exit 1
|
|
fi
|
|
echo " 오프라인 .deb 설치 (apt-get --no-download — 의존성 순서·해결 자동, 네트워크 미사용)"
|
|
# --no-install-recommends: vendor.sh 가 Recommends 를 안 담으므로(--no-recommends) 맞춘다.
|
|
sudo apt-get install -y --no-install-recommends --no-download ./debs/*.deb
|
|
else
|
|
echo " 온라인 apt 설치 (인터넷 필요)"
|
|
sudo apt-get update
|
|
sudo apt-get install -y python3-requests python3-venv i2c-tools
|
|
fi
|
|
|
|
# 2) I2C 활성화 (dtparam + 모듈 로드 + 그룹)
|
|
echo "[2/4] I2C 활성화..."
|
|
# 부트 설정 경로: Bookworm=/boot/firmware/config.txt, 구버전=/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 "^dtparam=i2c_arm=on" "${BOOT_CFG}"; then
|
|
echo " ${BOOT_CFG} 에 dtparam=i2c_arm=on 추가 (재부팅 후 적용)"
|
|
echo "dtparam=i2c_arm=on" | sudo tee -a "${BOOT_CFG}" >/dev/null
|
|
NEED_REBOOT=1
|
|
else
|
|
echo " dtparam=i2c_arm=on 이미 설정됨"
|
|
fi
|
|
else
|
|
echo "[WARN] config.txt 를 찾지 못함 — raspi-config 로 수동 I2C 활성화 필요"
|
|
fi
|
|
# i2c-dev 모듈을 즉시 로드 + 부팅 시 자동 로드 등록
|
|
sudo modprobe i2c-dev 2>/dev/null || echo "[WARN] i2c-dev 즉시 로드 실패 (재부팅 후 적용될 수 있음)"
|
|
if [ -d /etc/modules-load.d ] && ! grep -qrs "i2c-dev" /etc/modules-load.d/ 2>/dev/null; then
|
|
echo "i2c-dev" | sudo tee /etc/modules-load.d/i2c.conf >/dev/null
|
|
fi
|
|
# 서비스 사용자를 i2c 그룹에 추가 (/dev/i2c-1 접근 권한)
|
|
if getent group i2c >/dev/null 2>&1; then
|
|
sudo usermod -aG i2c "${SERVICE_USER}" || echo "[WARN] usermod 실패 — SERVICE_USER=${SERVICE_USER} 확인"
|
|
fi
|
|
|
|
# 3) Python venv + requests + smbus2
|
|
echo "[3/4] Python 가상환경 설정..."
|
|
if [ ! -d venv ]; then
|
|
python3 -m venv --system-site-packages venv
|
|
echo " venv 생성 완료"
|
|
else
|
|
echo " venv 이미 존재 — 건너뜀"
|
|
fi
|
|
# shellcheck disable=SC1091
|
|
source venv/bin/activate
|
|
if [ "${OFFLINE}" = "1" ]; then
|
|
if [ ! -d wheelhouse ]; then
|
|
echo "[ERROR] 오프라인 모드인데 wheelhouse/ 가 없습니다. vendor.sh 산출물(wheelhouse/)을 두고 재실행하세요." >&2
|
|
exit 1
|
|
fi
|
|
echo " 오프라인 wheel 설치 (--no-index — 네트워크 미사용)"
|
|
pip install --no-index --find-links=wheelhouse -r requirements.txt
|
|
else
|
|
echo " 온라인 pip 설치 (인터넷 필요)"
|
|
pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
fi
|
|
echo " 패키지 설치 완료"
|
|
|
|
# 4) 로그 파일 (최소 권한: 소유자만 쓰기)
|
|
echo "[4/4] 로그 파일 생성..."
|
|
sudo mkdir -p /var/log
|
|
sudo touch /var/log/sht30_sensor.log
|
|
if ! sudo chown "${SERVICE_USER}:${SERVICE_USER}" /var/log/sht30_sensor.log; then
|
|
echo "[WARN] chown 실패 — SERVICE_USER=${SERVICE_USER} 존재 여부 확인"
|
|
fi
|
|
sudo chmod 640 /var/log/sht30_sensor.log
|
|
|
|
# config_local.py 즉시 잠금 (API_KEY 포함 — harden 전까지 world-readable 방지)
|
|
# 하드닝(harden_pi.sh)이 나중에 다시 조이지만, 그 사이 창(window)에도 노출되지
|
|
# 않도록 설치 시점에 바로 600 으로 잠근다. 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 권한 600 (소유자 ${SERVICE_USER}) 설정"
|
|
fi
|
|
|
|
# config_local 안내 (fail closed — 없으면 실행 시 즉시 중단됨)
|
|
if [ ! -f config_local.py ]; then
|
|
echo ""
|
|
echo "[!] config_local.py 가 없습니다. 다음을 실행 후 실제 값으로 채우세요:"
|
|
echo " cp config_local.example.py config_local.py"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " 설치 완료! (OFFLINE=${OFFLINE})"
|
|
echo "=========================================="
|
|
if [ "${NEED_REBOOT:-0}" = "1" ]; then
|
|
echo " ⚠️ I2C 를 방금 활성화했습니다 → 'sudo reboot' 후 센서가 인식됩니다."
|
|
fi
|
|
echo " 센서 확인: i2cdetect -y 1 (0x44 또는 0x45 가 보여야 함)"
|
|
echo " 실행: source venv/bin/activate && python3 sht30_sensor.py"
|
|
echo " 서비스: README.md 의 systemd 섹션 참고"
|
|
echo ""
|