6.8 KiB
Firmware OTA 서브시스템 가이드
살아있는 문서(living guide) — 앱 v1.11.10 기준. 코드:
src/firmware/*.py. 아키텍처 전체는architecture.md§3.1.2.
웹 UI 에서 펌웨어 ZIP 을 올려 dpworldapp 의 FW-MMI 채널(TCP 8990)로 플래시한다.
플래시 전 config 백업, 재부팅 후 복원 검사까지 한 흐름으로 오케스트레이션한다.
오케스트레이터는 FirmwareController(단일 인스턴스, thread-safe).
1. staging → flash 흐름
upload(ZIP) → stage(추출·sha256) → preflight(게이트) → flash(백업→전송→commit) → reboot → restore-check
- upload — 요청 본문(펌웨어 ZIP)을 디스크 임시파일에 1MB 청크로 스트리밍.
버퍼는 반드시 디스크 백엔드(
/opt/fw_upload) —MemoryMax=48M·no-swap 환경에서 tmpfs(/tmp)에 수백 MB 를 버퍼하면 cgroup OOM 으로 서비스가 죽는다. - stage — ZIP 추출 → 확장자로 컴포넌트 자동 식별 → 각 파일 sha256 계산 →
/opt/fw_staging. zip-bomb/디스크 채움 가드(컴포넌트당·전체 압축 해제 크기 상한, 중복 파일명 거부, free-space 확인). sidecar(.fw_build_dates.json)에 sha256+build_date 를 증분 기록 → 서비스 재시작 시_recover_staging가 sha256 재검증으로 안전 복구(불일치/미기록 파일은 거부·삭제). - preflight — 게이트 체크리스트. critical 전부 통과해야
ok. - flash — 백그라운드 워커가 백업 → 컴포넌트 전송 → commit → reboot 트리거.
- restore-check — 재부팅·재접속 후 운영자가 호출 → reseed 감지 시 config 복원 + dpworldapp 재시작.
컴포넌트 식별 (확장자 → wire signature)
| 확장자 | role | signature |
|---|---|---|
.rom |
bootloader | BTL |
.img |
kernel | KRN |
.ext4 |
rootfs | RTF |
.dtb |
dtb | DTB |
commit 은 별도 message-only 프레임(CPU signature, payload 0).
2. phase stepper (UI 상태)
fw_controller._PHASES — 순서 의미 있음. 각 phase 는 pending/active/done/error,
tier 는 na/warn/ok/error 로 매핑.
| key | label | 의미 |
|---|---|---|
upload |
Upload | ZIP 수신 |
verify |
Verify | sha256 계산/검증 |
preflight |
Pre-flight | 게이트 체크 |
backup |
Backup | 플래시 전 config 백업 |
flash |
Flash | 컴포넌트 전송 |
commit |
Commit | 슬롯 기록·커밋(마지막 컴포넌트 100% 시 시작) |
reboot |
Reboot | 장치 재부팅(~10s) |
config |
Config check | 재부팅 후 복원 검사 |
상태머신: idle→staging→staged→flashing→rebooting→done (실패 시 failed).
flashing/staging/rebooting/done 중에는 새 upload/flash 를 거부(race 방지).
preflight 게이트 항목
staging(컴포넌트 staged, critical)space(여유공간 headroom 128MB, critical) — staged 바이트는 이미 디스크에 있으므로 중복 계상 안 함fw_port(dpworldapp FW 포트 8990 도달, critical)db(config DB 존재, non-critical — flash 는 되지만 restore-check 에 필요)slot(활성 A/B 슬롯, informational)
3. config 안전 (config_safety)
- 플래시 전 백업:
backup_config가device_config/protocol_config(+ DB)를/opt/config_backups/<타임스탬프>/로 저장.backupphase 에서 수행. - 재부팅 후 복원 검사(
detect_and_restore): 펌웨어 flash 가 config 를 factory default 로 reseed 했는지 필드 단위로 감지한다. "reseed" 판정 = 사용자 구별 필드가 대거 factory default 로 되돌아갔고(≥80%, 최소 2개) 새 편집 흔적이 전혀 없을 때(do-no-harm). 이 경우에만 백업본을 단일 트랜잭션(BEGIN IMMEDIATE)으로 원자 복원하고 dpworldapp 을 재시작한다. 애매하면 복원하지 않음(fail-safe toward no-restore). - DB 가 읽기 불가/잠금/비어 있어도 500 으로 죽지 않고 사유를 보고한다.
- restore-check 응답은 정직성 원칙: dpworldapp 재시작이 실제 실패하면
restart_error를 명시하고 "재시작됨" 으로 거짓 보고하지 않는다(v1.5.4.5 STAB-2).
4. TCP 8990 dpworldapp 채널 (wire protocol)
fw_client 가 protocol 프레이밍으로 8990 에 전송한다(server.py: FW_HOST/FW_PORT,
기본 127.0.0.1:8990). dpworldapp 유닛 상태와 무관하게 서빙되는 포트다.
- 프레임 = ASCII signature + 8바이트 little-endian size +
<size>payload. message-only step 은 size 0, payload 없음. - ACK: 성공
SUCCESS, 실패FW_FAIL(deployed firmware behavior 확인).classify_ack가 success 우선 분류. - per-step ACK 타임아웃(ms): BTL/KRN/RTF 20000, DTB 10000, CPU(commit) 120000.
5. A/B rootfs 슬롯 + /opt 영속 (flash 생존)
- 장치는 A/B rootfs 슬롯(p5=A, p6=B)을 가지며 flash 마다 flip 한다. 활성 슬롯은
/proc/cmdline의root=로 best-effort 탐지(_detect_slot). status 에slot.before/after노출. - commit 성공 후 staging(
/opt/fw_staging)은 공간 회수를 위해 삭제(슬롯에 이미 기록됨). config 백업(/opt/config_backups)은 보존. /opt(p12)·/home/root(p11)는 flash 후에도 영속 — 업로드 버퍼·staging·config 백업·DB 가 여기에 있어 flash 를 견딘다. 단, web-configurator/AP 의 systemd 유닛 등 rootfs(/lib) 자산은 flash 마다 wipe 된다(§6).
6. ⚠ flash 생존 caveat
펌웨어 flash 는 rootfs 슬롯을 통째로 교체하므로 rootfs(/lib/systemd 유닛, /usr/bin 스크립트)에
설치된 web-configurator·Wi-Fi AP·network-apply 자산이 매번 wipe 된다. flash 후에는 이들을
재설치해야 한다. 근본 자동화는 BSP 이미지 베이킹이 정답(백로그) — 메모리 web-survives-flash 참조.
7. API
| Method | Path | 비고 |
|---|---|---|
GET |
/api/firmware/status |
phase stepper + 컴포넌트 진행률 + slot + backup + message/error |
POST |
/api/firmware/preflight |
게이트 체크리스트({ok, checks}) |
POST |
/api/firmware/upload |
ZIP 스트리밍 업로드(per-read 소켓 타임아웃) → stage. {ok, components} |
POST |
/api/firmware/flash |
staged 컴포넌트 플래시 시작. 진행 중이면 409 |
POST |
/api/firmware/restore-check |
reseed 감지 → 복원 + dpworldapp 재시작 |
오류는 sanitize 되어 내부 fs 경로/예외 내부를 노출하지 않는다(공간부족 507, 잘못된 ZIP 400 등).
API 인증 없음: :9090 은 현재 미인증(문서화된 threat boundary). 운영자 로그인은 설계 백로그 —
architecture.md§10.