v1.12.1 전달 이후의 v1.12.2·v1.12.3 변경을 함께 반영합니다.
dpworldapp 공유 DB(device/protocol)·계약·원자적 쓰기 계약은 무변경.
■ v1.12.2 — 신규/빈 디바이스 DB-init 안정화 (fail-soft)
펌웨어 업데이트 직후 동기화 문제로 device_config/protocol_config 가 비어
있을 때, 웹이 그 값을 생성·덮어써 dpworldapp 의 시딩을 선점하는 문제를 차단.
협력사 DB-init fix(2ca0ac1)를 포함하되, 미초기화 시 예외를 던져 fresh device
를 중단시키던 부분을 fail-soft 로 대체(렌더는 적용, DB 쓰기만 skip).
- db_manager: config_initialized(key) 단일 판정 신규
(존재 AND 비어있지 않은 dict; 빈 {} = 미초기화).
- apply_engine: 미초기화 시 raise 제거 → 렌더/networkctl 은 적용하되 DB 미러
쓰기만 skip(journal warn). _write_db / _write_db_restore 동일 적용.
_lkg_bookkeeping: all-absent baseline 의 LKG 승격 방지(fresh device).
- server: 미초기화 응답 통일 — GET 204, POST 409 not_initialized(code 필드).
- netmodel(F3): 비-dict / malformed WIFI_SSID 내성 — crash 대신 안전 degrade.
- migrations: contract_canonical 을 device / protocol 로 분리, 각 키가 시딩될
때까지 독립 defer(seed-ordering). 기존 통합 플래그 하위호환 유지.
- frontend(F2): device/protocolConfigAbsent 가드 — 로드 시 부재(204)였던
키를 Save All 로 생성하지 않도록 차단하고 안내 토스트 표시.
■ v1.12.3 — Wi-Fi AP 채널 선택 + 네트워크 즉시적용 검증
- ap_engine: DFS 인지 채널 선택 — STA 가 DFS/no-IR 5GHz(52–64·100–144)에
있어도 AP 가 맹목 추종하지 않고 비-DFS 채널 선택(2.4GHz STA 는 ch1–13 만
추종). parse_iw_link_beacon_int 추가 — MCC(AP≠STA 채널) 시 hostapd
beacon_int 을 STA BSS 와 일치(QCA6490 "STA/AP BI must match").
AP 상태에 라디오-다운 사유(country_unset / channel_dfs / mcc_bi) 추가.
- ap_renderer: render_hostapd 에 beacon_int 파라미터 추가(기본 100).
- 즉시적용: 기존 systemctl start 방식 유지 — 디바이스 실측으로 재확인,
관련 내부 재작업 시도는 검토 후 원복(순변화 없음).
■ 기타
- api.js: 저장 실패 시 서버 errors 메시지를 사용자에게 노출.
- 문서/버전: VERSION·constants.js → v1.12.3, RELEASE-NOTES §0,
DELIVERABLE-MANIFEST, BSP-INTEGRATION 갱신.
- MemoryMax 48M→128M(v1.11.16) 주석 정합화(fw_routes / watchdog / server).
1 month ago
24 changed files with 482 additions and 154 deletions
| `deploy/dpworld-*.sh` | 각 유닛의 `ExecStart=` 가 가리키는 경로 |
| `deploy/dpworld-*.sh` | 각 유닛의 `ExecStart=` 가 가리키는 경로 |
| `deploy/dpworld-network-apply-ondemand.conf` | 해당 유닛이 참조하는 경로 |
| `deploy/nginx.conf` | Optional nginx sample site config. BSP/firmware가 nginx를 별도로 소유하면 생략 가능 |
| `deploy/nginx.conf` | Optional nginx sample site config. BSP/firmware가 nginx를 별도로 소유하면 생략 가능 |
권장 전체 기능 설치 목록:
권장 전체 기능 설치 목록:
- 항상 설치: `src/`, `deploy/web-configurator.service`
- 항상 설치: `src/`, `deploy/web-configurator.service`
- 네트워크 apply 사용 시 (**권장 = 직접 교체**): `deploy/dpworld-network-apply-hardened.sh` 의 내용을 **`/usr/bin/dpworld-network-apply.sh` 원본 이름으로 설치**(펌웨어 base 스크립트 교체)하고 `deploy/dpworld-network-apply-ondemand.conf` 를 함께 설치. firmware-native base 유닛(`dpworld-network-apply.service`/`dpworld-network-seed.service`)이 그 경로를 호출하므로 자동 적용됨. **이 방식에서는 `.service.d` 드롭인을 설치하지 않습니다** (드롭인은 라이브 후적용 fallback 전용 — §7, `docs/firmware-boot-hardening.md`)
- 네트워크 apply 사용 시 (**권장 = 직접 교체**): `deploy/dpworld-network-apply-hardened.sh` 의 내용을 **`/usr/bin/dpworld-network-apply.sh` 원본 이름으로 설치**(펌웨어 base 스크립트 교체). firmware-native base 유닛(`dpworld-network-apply.service`/`dpworld-network-seed.service`)이 그 경로를 호출하므로 자동 적용됨. **이 방식에서는 `20-hardened.conf` ExecStart override 드롭인은 설치하지 않습니다** (`scripts/deploy.ps1` 라이브 후적용 fallback 전용 — §7, `docs/firmware-boot-hardening.md`). 단, 온디맨드 apply 는 웹이 `systemctl start dpworld-network-apply.service` 로 호출하는데, boot-seed `Requires=` 가 seed(boot-only oneshot)를 `--boot` 모드로 재트리거합니다. seed 도 하드닝본이라 드롭인이 없어도 안전 통과하여 `systemctl start` 는 성공하지만(필수 아님), 그 불필요한 `seed --boot` 재실행을 회피하려면 **`10-ondemand.conf` 드롭인 베이킹을 권장**합니다(완화 overlay) — `deploy/dpworld-network-apply-ondemand.conf` 를 `/lib/systemd/system/dpworld-network-apply.service.d/10-ondemand.conf` 로 설치(§7, 아래 참고).
- Wi-Fi AP 사용 시: `deploy/dpworld-ap-apply.service`, `deploy/dpworld-ap-seed.service`, `deploy/dpworld-hostapd-ap0.service`, `deploy/dpworld-udhcpd-ap0.service`, `deploy/dpworld-ap-apply.sh`를 함께 설치
- Wi-Fi AP 사용 시: `deploy/dpworld-ap-apply.service`, `deploy/dpworld-ap-seed.service`, `deploy/dpworld-hostapd-ap0.service`, `deploy/dpworld-udhcpd-ap0.service`, `deploy/dpworld-ap-apply.sh`를 함께 설치
- 복구 유닛 사용 시: `deploy/dpworld-net-recover.service` 설치
- 복구 유닛 사용 시: `deploy/dpworld-net-recover.service` 설치
- `*.sh`는 `0755` 실행 권한으로 `/usr/bin/`에 설치
- `*.sh`는 `0755` 실행 권한으로 `/usr/bin/`에 설치
@ -126,13 +125,21 @@ do_install() {
# 네트워크-적용 하드닝 (권장 = 직접 교체):
# 네트워크-적용 하드닝 (권장 = 직접 교체):
# 펌웨어 base 스크립트 /usr/bin/dpworld-network-apply.sh 를 하드닝본으로 교체한다.
# 펌웨어 base 스크립트 /usr/bin/dpworld-network-apply.sh 를 하드닝본으로 교체한다.
# firmware-native 한 dpworld-network-apply.service / dpworld-network-seed.service 가
# firmware-native 한 dpworld-network-apply.service / dpworld-network-seed.service 가
# 이 경로를 호출하므로 apply·--boot 모두 자동으로 하드닝 동작을 탄다. 드롭인은 설치하지 않는다.
# 이 경로를 호출하므로 apply·--boot 모두 자동으로 하드닝 동작을 탄다.
# ExecStart를 바꾸는 20-hardened.conf override 드롭인은 설치하지 않는다(scripts/deploy.ps1 fallback 전용).
# (파일 소유권은 BSP에서 조정 — firmware recipe의 base 파일을 이 패키지가 덮어쓰도록
# (파일 소유권은 BSP에서 조정 — firmware recipe의 base 파일을 이 패키지가 덮어쓰도록
# bbappend 또는 recipe 우선순위로 정리. 자세히 docs/firmware-boot-hardening.md)
# bbappend 또는 recipe 우선순위로 정리. 자세히 docs/firmware-boot-hardening.md)
# 펌웨어 원본을 교체할 수 없는 경우에만, 하드닝본을 원래 -hardened 이름으로 설치하고
# 펌웨어 원본을 교체할 수 없는 경우에만, 하드닝본을 원래 -hardened 이름으로 설치하고
@ -181,13 +188,20 @@ curl -fsS http://127.0.0.1/ | head # optional: nginx 경유 사용 시
라이브 재로드 경로가 있어 QCA6490에서 PMU 워치독 재부팅 루프를 유발한 이력이 있습니다.
라이브 재로드 경로가 있어 QCA6490에서 PMU 워치독 재부팅 루프를 유발한 이력이 있습니다.
`deploy/dpworld-network-apply-hardened.sh` 가 이를 제거한 **완전 대체본**입니다.
`deploy/dpworld-network-apply-hardened.sh` 가 이를 제거한 **완전 대체본**입니다.
> ★ **불변식(권장이 아니라 필수)**: canonical `/usr/bin/dpworld-network-apply.sh` 는 **반드시 하드닝본**이어야 합니다(①/② 중 하나로 보장). 웹의 온디맨드 apply(`systemctl start dpworld-network-apply.service`)는 이 경로 + `Requires=seed`(→ seed `--boot`)를 타므로, un-hardened 로 남기면 `modprobe -r wlan` → 부트루프가 재발합니다. 아래 표의 '권장'은 ①vs② **방식 선택**의 권장일 뿐, 하드닝 자체는 선택이 아닙니다(Codex Q1 잔여 리스크).
| | ① 직접 교체 (BSP 베이킹, **권장**) | ② override (라이브 후적용, fallback) |
| | ① 직접 교체 (BSP 베이킹, **권장**) | ② override (라이브 후적용, fallback) |
|---|---|---|
|---|---|---|
| 대상 | 펌웨어 이미지를 빌드하는 협력사 | 이미 구워진 디바이스(우리 `deploy.ps1`) |
| 대상 | 펌웨어 이미지를 빌드하는 협력사 | 이미 구워진 디바이스(우리 `deploy.ps1`) |
| 방법 | 하드닝본 내용을 **`/usr/bin/dpworld-network-apply.sh` 원본 이름**으로 설치(원본 교체) | 하드닝본을 `…-hardened.sh` 로 두고 `.service.d/20-hardened.conf` 드롭인 2개로 ExecStart 교체 |
| 방법 | 하드닝본 내용을 **`/usr/bin/dpworld-network-apply.sh` 원본 이름**으로 설치(원본 교체) | 하드닝본을 `…-hardened.sh` 로 두고 `.service.d/20-hardened.conf` 드롭인 2개로 ExecStart 교체 |
| 효과 | base 유닛이 그대로 하드닝 스크립트를 실행 | base 유닛의 ExecStart가 하드닝본으로 바뀜 |
| 효과 | base 유닛이 그대로 하드닝 스크립트를 실행 | base 유닛의 ExecStart가 하드닝본으로 바뀜 |
> **온디맨드 드롭인 `10-ondemand.conf` (권장 bake — OPTIONAL 완화, 필수 아님)**: web-configurator/watchdog 는 온디맨드 네트워크 적용을 `systemctl start dpworld-network-apply.service` 로 호출합니다. 이 base 유닛은 `Requires=dpworld-network-seed.service`(boot-only oneshot)를 걸어 온디맨드 start 시 seed 를 `--boot` 로 재트리거하지만, seed 도 하드닝본이라 드롭인이 없어도 안전 통과합니다(systemctl start 성공, 'Dependency failed' 0). 이 드롭인은 `Requires=` 를 비워 그 불필요한 `seed --boot` 재실행만 회피하는 완화 overlay 입니다. `deploy/dpworld-network-apply-ondemand.conf` → `/lib/systemd/system/dpworld-network-apply.service.d/10-ondemand.conf` 로 설치(①·② 양쪽 모두 권장).
>
> **참고 (2026-07-01 .56 실측)**: 이 드롭인은 현재 .56 이미지에 미baked. 없어도 `systemctl start dpworld-network-apply.service` 는 성공함(Requires=seed 로 seed --boot 가 재트리거되나 seed 도 하드닝본이라 안전 통과). 다만 매 런타임 apply 마다 seed --boot 가 재실행되는 부수효과가 있어, 이 드롭인(또는 seed RemainAfterExit=yes) baking 이 권장됨.
절차 상세: `docs/firmware-boot-hardening.md` "펌웨어 베이킹 (BSP) — 원본 교체 방법".
절차 상세: `docs/firmware-boot-hardening.md` "펌웨어 베이킹 (BSP) — 원본 교체 방법".
### 이 패키지가 소유·제공하는 스크립트 (모두 포함됨)
### 이 패키지가 소유·제공하는 스크립트 (모두 포함됨)
@ -195,6 +209,7 @@ curl -fsS http://127.0.0.1/ | head # optional: nginx 경유 사용 시
> fallback(②) 사용 시에는 `…-hardened.sh` 이름으로 설치하고 `.service.d` 드롭인 2개(`deploy/dpworld-network-apply.service.d/`, `deploy/dpworld-network-seed.service.d/`)를 함께 둡니다.
> fallback(②) 사용 시에는 `…-hardened.sh` 이름으로 설치하고 `.service.d` 드롭인 2개(`deploy/dpworld-network-apply.service.d/`, `deploy/dpworld-network-seed.service.d/`)를 함께 둡니다.
- **프로토콜 레지스터 필드를 디바이스 config-reader 계약에 정렬 (Modbus/OPC-UA)**:
- Modbus 레지스터 `idt` 를 디바이스가 기대하는 **공백형**으로 저장 (`unsignedInteger`→`unsigned integer`, `unsignedShort`→`unsigned short`). UI 드롭다운 라벨은 그대로 유지(저장 시에만 변환, 로드 시 역변환). 이전에는 camelCase 로 저장돼 **Modbus 설정 전체가 로드 실패**할 수 있었습니다.
- **Modbus·OPC-UA 레지스터 폼에서 `Shift`/`Expr`/`Mask` 제거** — 해당 프로토콜에서는 디바이스가 사용하지 않는 필드입니다(`Expr` 은 저장되나 값에 미적용, `Shift`/`Mask` 는 Modbus/OPC-UA 레지스터 필드가 아님). **CAN 은 유지**(실제 적용됨).
- **ODT `string` 옵션**: Modbus·CAN 에서 제거(디바이스가 Modbus 는 거부, CAN 은 출력 없음), **OPC-UA 는 유지**. 백엔드도 Modbus/CAN 의 `string` 저장을 400 으로 거부.
- **레지스터 매핑 테이블 UI 정돈**: 컬럼 헤더를 축약어 없이 풀네임으로 통일 — Modbus `Field / Address / Input Type / Output Type / Default Value`, OPC-UA `Field / Namespace / Node ID / Output Type / Default Value`, CAN `Field / CAN ID / Output Type / Default Value / Shift / Expr / Mask`. CAN Baud Rate 드롭다운에서 미사용 `NONE` 항목을 표시에서 숨김(내부 데이터 계약·firmware 값 검증은 유지).
### Added
- 기존 디바이스 DB 의 위 표기를 **부팅 시 1회 자동 정합**(멱등 마이그레이션, 이전 계약-정합 마이그레이션을 거친 디바이스에서도 실행되도록 별도 플래그).
### Note
- 설치 경로(`/usr/lib/web-configurator`)·기본 포트(`:9090`) 유지. dpworldapp 공유 DB·계약·타입 무변경(레지스터 표기 정합만).
## [v1.12.1] — 2026-06-29
## [v1.12.1] — 2026-06-29
### Added
### Added
- **텔레메트리 Uplink (Ethernet 전환)**: dpworldapp 클라우드 텔레메트리(프로토콜·업데이트·RTCM) 송신을 웹 UI 에서 Wi-Fi↔Ethernet 전환. 호스트 라우트(/32)만 사용 — dpworldapp·OS 설정 무접촉. 워치독 자가복원 + 관리 인터페이스 보호.
- **텔레메트리 Uplink (Ethernet 전환)**: dpworldapp 클라우드 텔레메트리(프로토콜·업데이트·RTCM) 송신을 웹 UI 에서 Wi-Fi↔Ethernet 전환. 호스트 라우트(/32)만 사용 — dpworldapp·OS 설정 무접촉. 워치독 자가복원 + 관리 인터페이스 보호.
- **대상**: 디바이스 BSP 이미지 포함용 (DP World Smart Solutions · IoT 디바이스)
- **대상**: 디바이스 BSP 이미지 포함용 (DP World Smart Solutions · IoT 디바이스)
- **패키지**: 내부 git 태그 `v1.12.1` (커밋 히스토리가 없는 **클린 스냅샷**)
- **패키지**: 내부 git 태그 `v1.12.4` (커밋 히스토리가 없는 **클린 스냅샷**)
- **이전 버전**: v1.12.0
- **이전 전달**: v1.12.1 — 본 전달은 v1.12.2·v1.12.3·v1.12.4 변경을 함께 포함합니다.
---
---
## 1. 한눈에 보기
## 0. v1.12.1 → v1.12.4 신규/변경 (이번 전달의 핵심)
이번 전달은 v1.12.1 위에 **v1.12.2·v1.12.3·v1.12.4** 를 함께 반영한 것입니다.
### v1.12.4 — 프로토콜 레지스터 필드를 디바이스 계약에 정합 (Modbus/OPC-UA)
- **Modbus `idt` 공백형 저장**: 레지스터 입력 데이터타입을 디바이스 config-reader 가 기대하는 공백형으로 저장합니다(`unsignedInteger`→`unsigned integer`, `unsignedShort`→`unsigned short`). UI 라벨은 그대로 두고 저장 시에만 변환하며, 로드 시 역변환합니다. 이전에는 camelCase 로 저장돼 **Modbus 설정 전체가 로드 실패**할 수 있었습니다.
- **Modbus·OPC-UA 폼에서 `Shift`/`Expr`/`Mask` 제거**: 해당 프로토콜에서 디바이스가 사용하지 않는 필드입니다(`Expr` 은 저장되나 값에 미적용, `Shift`/`Mask` 는 레지스터 필드가 아님). **CAN 은 유지**(CAN 에서는 실제 적용됨).
- **ODT `string`**: Modbus·CAN 에서 제거(디바이스가 Modbus 는 거부, CAN 은 출력 없음), OPC-UA 는 유지(완전 지원). 잘못된 저장은 400 으로 거부.
- 기존 디바이스 DB 의 위 표기는 **부팅 시 1회 자동 정합**(멱등 마이그레이션, 이전 계약-정합 마이그레이션을 거친 디바이스에서도 실행되도록 별도 플래그).
- dpworldapp 공유 DB·계약·타입 무변경(레지스터 표기 정합만). 설치 경로(`/usr/lib/web-configurator`)·기본 포트(`:9090`) 유지.
- **레지스터 테이블 UI 정돈**: 3개 매핑 표(Modbus/OPC-UA/CAN)의 컬럼 헤더를 축약어 없이 풀네임으로 통일(`Output Type`·`Default Value`·`Input Type`·`Namespace`·`Node ID`·`Address`). CAN Baud Rate 드롭다운에서 미사용 `NONE` 항목을 표시에서 숨김(데이터 계약·firmware 값 검증은 유지).
### v1.12.2 — 신규/빈 디바이스 DB-init 안정화 (fail-soft)
펌웨어 업데이트 직후 동기화 문제로 `device_config`/`protocol_config` 가 비어 있을 때, **웹이 그 값을 생성·덮어쓰지 않도록** 하여 dpworldapp 의 시딩을 선점하지 않습니다. 단일 판정(`config_initialized`)을 아래에 일관 적용합니다.
| 경로 | 미초기화 시 동작 |
|---|---|
| 설정 조회(GET) | 빈 `{}` 를 정상 설정처럼 주지 않고 **204** 반환 |
| 설정 저장(POST) | **409 `not_initialized`** — 빈 폼 저장으로 실제 설정을 덮어쓰는 것 차단 |
| 네트워크 즉시적용 | DB 미러 쓰기는 **skip**(경고 기록)하되 **렌더 파일 적용은 계속**(연결 복구 보존) |
| 마이그레이션·서버 시작 | DB 부재/락에도 **크래시 없이 degraded** 로 계속 |
> ※ 협력사 자체 DB-init fix(`2ca0ac1`)를 **포함·강화**한 것입니다. 특히 미초기화 시 **예외를 던져 fresh device 를 중단시키던 부분**을, 렌더는 적용하고 DB 쓰기만 건너뛰는 **fail-soft** 로 대체했습니다.
### v1.12.3 — Wi-Fi AP 채널 선택 + 네트워크 즉시적용 검증
- **Wi-Fi AP DFS 인지 채널 선택**: STA(내부 클라이언트)가 DFS/NO-IR 5GHz 채널(52–64·100–144)에 있어도 AP 가 그 채널을 맹목 추종하지 않고 **비-DFS 채널을 선택**합니다(2.4GHz STA 는 ch1–13 만 추종). AP 상태에 라디오-다운 사유(`country_unset`/`channel_dfs`/`mcc_bi`)를 추가해 진단을 명확히 했습니다.
- **네트워크 즉시적용**: 기존 방식(`systemctl start dpworld-network-apply.service`)을 **유지**합니다. 디바이스 실측으로 정상 동작을 재확인했고, 관련 내부 재작업 시도는 검토 후 **원복**했습니다(순변화 없음).
- (참고) 즉시적용마다 boot-seed 스크립트가 한 번 더 실행되는 부수효과가 있으나 적용 자체는 성공합니다. 상세/불변식(하드닝본 필수 등)은 `BSP-INTEGRATION.md` 를 참조하세요.
---
## 1. (v1.12.1 참고) 한눈에 보기
아래 1~장은 **v1.12.1 전달 시점**의 내용입니다.
이번 릴리스는 **텔레메트리 Uplink(Ethernet 전환) 기능 추가 + 전체 안정화** 릴리스입니다.
이번 릴리스는 **텔레메트리 Uplink(Ethernet 전환) 기능 추가 + 전체 안정화** 릴리스입니다.
- **신규 — 텔레메트리 Uplink**: dpworldapp 의 클라우드 텔레메트리(프로토콜·업데이트·RTCM 서버) 송신 경로를
- **신규 — 텔레메트리 Uplink**: dpworldapp 의 클라우드 텔레메트리(프로토콜·업데이트·RTCM 서버) 송신 경로를