You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
16 KiB
366 lines
16 KiB
|
1 month ago
|
#!/bin/sh
|
||
|
|
# dpworld-network-apply.sh — firmware-safe network apply script
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# 목적:
|
||
|
|
# dpworldapp 또는 WebConfigurator가 /home/root/network/에 생성한 네트워크
|
||
|
|
# 렌더 파일을 systemd-networkd, wpa_supplicant, WiFi country 설정에
|
||
|
|
# 동기화한다.
|
||
|
|
#
|
||
|
|
# 배포 위치:
|
||
|
|
# firmware image: /usr/bin/dpworld-network-apply.sh
|
||
|
|
# file mode: root:root, 0755, LF, POSIX /bin/sh
|
||
|
|
#
|
||
|
|
# 호출:
|
||
|
|
# dpworld-network-apply.sh
|
||
|
|
# on-demand 적용. /run 동기화 후 networkctl reload/reconfigure,
|
||
|
|
# WPA 변경 시 wpa_cli reconfigure를 수행한다.
|
||
|
|
#
|
||
|
|
# dpworld-network-apply.sh --boot
|
||
|
|
# 부팅 seed. /home/root/network가 비어 있으면
|
||
|
|
# /etc/dpworld/network-defaults를 복사하고, /run/systemd/network를
|
||
|
|
# 먼저 동기화한다. networkd가 이미 active인 boot-order race에서는
|
||
|
|
# live address reconcile까지 수행한다.
|
||
|
|
#
|
||
|
|
# dpworld-network-apply.sh --country-now
|
||
|
|
# 호환 인자. 이 빌드 제안본에서는 live module unload를 하지 않는다.
|
||
|
|
# country는 modprobe.d에 기록하고 reboot-required marker로 처리한다.
|
||
|
|
#
|
||
|
|
# 안전 정책:
|
||
|
|
# - 실행 가능한 modprobe -r 경로를 두지 않는다. QCA6490/cnss_pci에서
|
||
|
|
# module unload가 hang과 watchdog reset으로 이어진 이력이 있기 때문이다.
|
||
|
|
# - country 변경은 reboot-deferred가 기본이다.
|
||
|
|
# - reboot-required 판단은 wpa_cli get country가 아니라
|
||
|
|
# /sys/module/wlan/parameters/country_code 또는 iw reg get 기준으로 한다.
|
||
|
|
# - set -e를 쓰지 않는다. 실패한 외부 명령은 로그에 남기고 가능한 후속
|
||
|
|
# 처리를 계속한다.
|
||
|
|
# - 기존 렌더 파일이 있으면 /etc/dpworld/network-defaults로 덮어쓰지 않는다.
|
||
|
|
#
|
||
|
|
# 상태 파일:
|
||
|
|
# /opt/dpworld-network/apply.log 적용 로그. 512KB 초과 시 apply.log.1로 자체 rotate
|
||
|
|
# /opt/dpworld-network/applied/ 마지막 적용 기준선
|
||
|
|
# /opt/dpworld-network/reboot-required country 재부팅 필요 marker
|
||
|
|
# /run/dpworld-network/apply.lock 중복 실행 방지 lock
|
||
|
|
# /run/dpworld-network/country-now 호환 marker
|
||
|
|
#
|
||
|
|
# 한계:
|
||
|
|
# eth1 IP 변경 후 confirm/rollback 같은 세션 보존 로직은 이 셸 스크립트가
|
||
|
|
# 담당하지 않는다. WebConfigurator apply engine이 있으면 그쪽에서 처리한다.
|
||
|
|
# standalone으로 eth1 주소를 바꿀 때는 콘솔 또는 대체 접속 경로를 확보한다.
|
||
|
|
#
|
||
|
|
# POSIX /bin/sh (BusyBox ash). bashism 금지.
|
||
|
|
set -u
|
||
|
|
|
||
|
|
# ─────────────────────────────── 인자 ────────────────────────────────
|
||
|
|
MODE_BOOT=0
|
||
|
|
COUNTRY_NOW=0
|
||
|
|
for a in "$@"; do
|
||
|
|
case "$a" in
|
||
|
|
--boot) MODE_BOOT=1 ;;
|
||
|
|
--country-now) COUNTRY_NOW=1 ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
MODE_NAME=$([ "$MODE_BOOT" -eq 1 ] && echo boot || echo ondemand)
|
||
|
|
|
||
|
|
# ─────────────────────────────── 경로 ────────────────────────────────
|
||
|
|
NETWORK_DIR="/home/root/network"
|
||
|
|
DEFAULT_DIR="/etc/dpworld/network-defaults"
|
||
|
|
RUNTIME_DIR="/run/systemd/network"
|
||
|
|
MODPROBE_CONF="/etc/modprobe.d/10-wlan.conf"
|
||
|
|
|
||
|
|
STATE_DIR="/opt/dpworld-network" # 영속(/opt, After=local-fs.target 보장) — shadow/로그/마커
|
||
|
|
SHADOW_DIR="${STATE_DIR}/applied"
|
||
|
|
LOG_FILE="${STATE_DIR}/apply.log"
|
||
|
|
REBOOT_MARK="${STATE_DIR}/reboot-required"
|
||
|
|
|
||
|
|
RUN_STATE_DIR="/run/dpworld-network" # tmpfs — 락/일회성 마커
|
||
|
|
LOCK_DIR="${RUN_STATE_DIR}/apply.lock"
|
||
|
|
LOCK_PID="${LOCK_DIR}/pid"
|
||
|
|
COUNTRY_NOW_MARK="${RUN_STATE_DIR}/country-now" # 엔진 호환: 있으면 country-now
|
||
|
|
|
||
|
|
NET_RENDERS="10-wlan0.network 10-eth0.network 10-eth1.network"
|
||
|
|
WPA_RENDER="${NETWORK_DIR}/wpa_supplicant-wlan0.conf"
|
||
|
|
COUNTRY_RENDER="${NETWORK_DIR}/wifi-country-code"
|
||
|
|
PERSIST_JSON="${NETWORK_DIR}/network_config.json"
|
||
|
|
ROOTFS_MIN_KB=20480
|
||
|
|
LOG_MAX_BYTES=524288
|
||
|
|
|
||
|
|
# 엔진 마커가 있으면 country-now 로 승격 (공존 호환)
|
||
|
|
[ -f "$COUNTRY_NOW_MARK" ] && COUNTRY_NOW=1
|
||
|
|
|
||
|
|
# ──────────────────────────── 로깅 / 실행 ─────────────────────────────
|
||
|
|
log() {
|
||
|
|
up=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo '?') # RTC 불신뢰 → uptime 병기
|
||
|
|
line="[$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null) up=${up}s ${MODE_NAME}] $*"
|
||
|
|
echo "$line" >&2
|
||
|
|
if [ -d "$STATE_DIR" ]; then
|
||
|
|
if [ -f "$LOG_FILE" ] && [ "$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)" -gt "$LOG_MAX_BYTES" ]; then
|
||
|
|
mv -f "$LOG_FILE" "${LOG_FILE}.1" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
echo "$line" >> "$LOG_FILE" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
run() { # 외부 명령 + rc 로깅. 절대 스크립트를 중단시키지 않음.
|
||
|
|
"$@"; rc=$?
|
||
|
|
[ "$rc" -ne 0 ] && log " cmd rc=${rc}: $*"
|
||
|
|
return "$rc"
|
||
|
|
}
|
||
|
|
|
||
|
|
seed_defaults_if_empty() {
|
||
|
|
if ! run mkdir -p "$NETWORK_DIR"; then
|
||
|
|
log " defaults: cannot create ${NETWORK_DIR} - skip"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if find "$NETWORK_DIR" -mindepth 1 -maxdepth 1 2>/dev/null | read -r _; then
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ ! -d "$DEFAULT_DIR" ]; then
|
||
|
|
log " defaults: ${NETWORK_DIR} empty but ${DEFAULT_DIR} missing - skip"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if run cp -a "${DEFAULT_DIR}/." "$NETWORK_DIR/"; then
|
||
|
|
log " defaults: seeded ${NETWORK_DIR} from ${DEFAULT_DIR}"
|
||
|
|
else
|
||
|
|
log " defaults: failed to seed ${NETWORK_DIR} from ${DEFAULT_DIR}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# ──────────────────────────── 헬퍼 ───────────────────────────────────
|
||
|
|
runtime_target() {
|
||
|
|
case "$1" in
|
||
|
|
10-wlan0.network) echo "${RUNTIME_DIR}/05-dpworld-wlan0.network" ;;
|
||
|
|
10-eth0.network) echo "${RUNTIME_DIR}/05-dpworld-eth0.network" ;;
|
||
|
|
10-eth1.network) echo "${RUNTIME_DIR}/05-dpworld-eth1.network" ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
iface_of() {
|
||
|
|
case "$1" in
|
||
|
|
10-wlan0.network) echo wlan0 ;;
|
||
|
|
10-eth0.network) echo eth0 ;;
|
||
|
|
10-eth1.network) echo eth1 ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
render_addr() { # 렌더 파일의 static Address (없으면 빈값=DHCP)
|
||
|
|
sed -n 's#^[[:space:]]*Address[[:space:]]*=[[:space:]]*\([0-9][0-9.]*\)/.*#\1#p' "$1" 2>/dev/null | head -n1
|
||
|
|
}
|
||
|
|
live_addr() {
|
||
|
|
ip -4 -o addr show dev "$1" 2>/dev/null | awk '{split($4,a,"/"); print a[1]}' | head -n1
|
||
|
|
}
|
||
|
|
desired_country() {
|
||
|
|
cc=""
|
||
|
|
[ -r "$COUNTRY_RENDER" ] && cc=$(tr -d ' \r\n\t' < "$COUNTRY_RENDER" 2>/dev/null | tr 'a-z' 'A-Z')
|
||
|
|
if [ -z "$cc" ] && [ -r "$PERSIST_JSON" ]; then
|
||
|
|
cc=$(sed -n 's/.*"country_code"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$PERSIST_JSON" 2>/dev/null \
|
||
|
|
| head -n1 | tr -d ' \r\n\t' | tr 'a-z' 'A-Z')
|
||
|
|
fi
|
||
|
|
echo "$cc"
|
||
|
|
}
|
||
|
|
valid_cc() { case "$1" in [A-Z][A-Z]) return 0 ;; *) return 1 ;; esac; }
|
||
|
|
modprobe_country() {
|
||
|
|
[ -r "$MODPROBE_CONF" ] || return 0
|
||
|
|
sed -n 's/^[[:space:]]*options[[:space:]]\+wlan[[:space:]]\+country_code=\([A-Za-z]\{2\}\).*$/\1/p' \
|
||
|
|
"$MODPROBE_CONF" 2>/dev/null | head -n1 | tr 'a-z' 'A-Z'
|
||
|
|
}
|
||
|
|
live_country() { # 라디오 실효 country (ground truth). 모듈 로드 파라미터 우선, iw reg 폴백.
|
||
|
|
# ※ wpa_cli get country 는 wpa conf 렌더값일 뿐 라디오 실효값이 아님 → 사용 금지(거짓 양성: 오류②).
|
||
|
|
c=""
|
||
|
|
[ -r /sys/module/wlan/parameters/country_code ] && \
|
||
|
|
c=$(tr -d ' \r\n\t' < /sys/module/wlan/parameters/country_code 2>/dev/null | tr 'a-z' 'A-Z')
|
||
|
|
if { [ -z "$c" ] || [ "$c" = "00" ] || [ "$c" = "(NULL)" ]; } && command -v iw >/dev/null 2>&1; then
|
||
|
|
c=$(iw reg get 2>/dev/null | sed -n 's/^country \([A-Z][A-Z]\):.*/\1/p' | grep -v '^00$' | head -n1)
|
||
|
|
fi
|
||
|
|
echo "$c"
|
||
|
|
}
|
||
|
|
|
||
|
|
note_country_now_deferred() {
|
||
|
|
log " country-now requested, but live module unload is disabled on this hardware; reboot-required 유지"
|
||
|
|
rm -f "$COUNTRY_NOW_MARK" 2>/dev/null || true
|
||
|
|
}
|
||
|
|
|
||
|
|
# ──────────────────────── 락 (mkdir 원자 + PID stale 회수) ─────────────
|
||
|
|
acquire_lock() {
|
||
|
|
i=0
|
||
|
|
while [ "$i" -lt 12 ]; do
|
||
|
|
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
||
|
|
echo "$$" > "$LOCK_PID" 2>/dev/null || true
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
holder=$(cat "$LOCK_PID" 2>/dev/null || echo "")
|
||
|
|
if [ -n "$holder" ] && ! kill -0 "$holder" 2>/dev/null; then
|
||
|
|
log "stale lock (pid ${holder} dead) — 회수"
|
||
|
|
rm -f "$LOCK_PID" 2>/dev/null; rmdir "$LOCK_DIR" 2>/dev/null
|
||
|
|
continue # 즉시 재시도 (i 증가 없음)
|
||
|
|
fi
|
||
|
|
i=$((i+1)); sleep 1
|
||
|
|
done
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
mkdir -p "$RUN_STATE_DIR" 2>/dev/null || true
|
||
|
|
if ! acquire_lock; then
|
||
|
|
log "다른 apply 실행 중 — 락 획득 실패. 멱등하므로 종료(0)."
|
||
|
|
exit 0
|
||
|
|
fi
|
||
|
|
cleanup() { rm -rf "$LOCK_DIR" 2>/dev/null || true; }
|
||
|
|
trap cleanup EXIT INT TERM
|
||
|
|
|
||
|
|
mkdir -p "$SHADOW_DIR" 2>/dev/null || true
|
||
|
|
log "START (boot=${MODE_BOOT} country_now=${COUNTRY_NOW})"
|
||
|
|
|
||
|
|
seed_defaults_if_empty
|
||
|
|
|
||
|
|
# ──────── 1) /run 채우기 (양 모드) — tmpfs 는 부팅마다 비므로 항상 동기화 ────────
|
||
|
|
NETWORKD_DIRTY=0
|
||
|
|
CHANGED_IFACES=""
|
||
|
|
install -d "$RUNTIME_DIR" 2>/dev/null || true
|
||
|
|
|
||
|
|
for stale in 10-wlan0.network 10-eth0.network 10-eth1.network; do # 펌웨어식 잔여 이름 제거
|
||
|
|
[ -e "${RUNTIME_DIR}/${stale}" ] && { run rm -f "${RUNTIME_DIR}/${stale}"; NETWORKD_DIRTY=1; }
|
||
|
|
done
|
||
|
|
|
||
|
|
for r in $NET_RENDERS; do
|
||
|
|
src="${NETWORK_DIR}/${r}"; tgt=$(runtime_target "$r"); ifc=$(iface_of "$r")
|
||
|
|
if [ -f "$src" ]; then
|
||
|
|
if [ ! -f "$tgt" ] || ! cmp -s "$src" "$tgt"; then
|
||
|
|
if run install -m 0644 "$src" "$tgt"; then
|
||
|
|
NETWORKD_DIRTY=1; CHANGED_IFACES="${CHANGED_IFACES} ${ifc}"
|
||
|
|
log " runtime updated: ${tgt}"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
elif [ -e "$tgt" ]; then
|
||
|
|
run rm -f "$tgt"; NETWORKD_DIRTY=1; CHANGED_IFACES="${CHANGED_IFACES} ${ifc}"
|
||
|
|
log " runtime removed (source gone): ${tgt}"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# ──────── 2) wpa 변경 감지 (shadow 기준 — /run 사본이 없는 유일 파일) P1 ────────
|
||
|
|
WPA_CHANGED=0
|
||
|
|
if [ -f "$WPA_RENDER" ]; then
|
||
|
|
if [ ! -f "${SHADOW_DIR}/wpa_supplicant-wlan0.conf" ] || \
|
||
|
|
! cmp -s "$WPA_RENDER" "${SHADOW_DIR}/wpa_supplicant-wlan0.conf"; then
|
||
|
|
WPA_CHANGED=1
|
||
|
|
log " wpa render changed (SSID/PW/security/country header)"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ──────── 3) country: modprobe.d 동기화 + 라디오 실효 기준 reboot 판정 (P2 / 오류①② 修) ────────
|
||
|
|
# 오류①: wlan 모듈은 부팅 초기(modules-load)에 로드되고 seed 는 그 뒤에 modprobe.d 를 갱신 →
|
||
|
|
# country 변경이 그 부팅엔 반영 안 됨(라디오는 옛값). 다음 부팅에야 적용.
|
||
|
|
# 오류②: 적용 검증을 wpa conf 값(wpa_cli)으로 하면 거짓 양성 → 반드시 라디오 실효값(live_country)으로.
|
||
|
|
DC=$(desired_country)
|
||
|
|
if [ -n "$DC" ] && valid_cc "$DC"; then
|
||
|
|
CUR=$(modprobe_country)
|
||
|
|
# (a) modprobe.d 를 목표로 동기화 (다를 때만). 런타임 modprobe -r 없음(deferred).
|
||
|
|
if [ "$DC" != "$CUR" ]; then
|
||
|
|
rootfs_free=$(df -k / 2>/dev/null | awk 'END{print $4}'); rootfs_free=${rootfs_free:-0}
|
||
|
|
if [ "$rootfs_free" -lt "$ROOTFS_MIN_KB" ]; then
|
||
|
|
log " country: rootfs 여유 ${rootfs_free}KB < ${ROOTFS_MIN_KB}KB — modprobe.d 쓰기 보류 (P6)"
|
||
|
|
elif printf 'options wlan country_code=%s\n' "$DC" > "${MODPROBE_CONF}.tmp.$$" 2>/dev/null \
|
||
|
|
&& mv -f "${MODPROBE_CONF}.tmp.$$" "$MODPROBE_CONF" 2>/dev/null; then
|
||
|
|
log " country: modprobe.d ${CUR:-none} -> ${DC}"
|
||
|
|
else
|
||
|
|
rm -f "${MODPROBE_CONF}.tmp.$$" 2>/dev/null || true
|
||
|
|
log " country: modprobe.d 쓰기 실패 (disk?) — 변경 보류"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
# (b) reboot-required 판정은 "라디오 실효 country" 기준 (모듈이 실제 무엇으로 떠있나).
|
||
|
|
# modprobe.d 가 목표와 같아도 라디오가 다르면(=아직 그 값으로 모듈이 안 올라옴) 재부팅 필요.
|
||
|
|
LC=$(live_country)
|
||
|
|
if [ -n "$LC" ] && [ "$LC" = "$DC" ]; then
|
||
|
|
[ -f "$REBOOT_MARK" ] && { rm -f "$REBOOT_MARK" 2>/dev/null; log " country: 라디오 실효=${LC} == 목표 — reboot-required 해제"; }
|
||
|
|
else
|
||
|
|
: > "$REBOOT_MARK" 2>/dev/null || true
|
||
|
|
log " country: 라디오 실효=${LC:-unknown} != 목표 ${DC} → 재부팅 필요 (reboot-required)"
|
||
|
|
if [ "$MODE_BOOT" -eq 0 ] && [ "$COUNTRY_NOW" -eq 1 ]; then
|
||
|
|
note_country_now_deferred
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
elif [ -n "$DC" ]; then
|
||
|
|
log " country: 잘못된 코드 '${DC}' 무시"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ──────── 4) 라이브 적용 + 라이브-vs-의도 reconcile (P3 + L1) ────────
|
||
|
|
# on-demand: 항상 수행.
|
||
|
|
# boot: 정상 순서면 networkd 미기동 → 자연 적용에 맡기고 생략. 단 boot-ordering race 로
|
||
|
|
# networkd 가 이미 active 면(우리 /run 설치 전에 링크 구성) 라이브가 옛 값으로 굳으므로 reconcile.
|
||
|
|
DO_LIVE=0
|
||
|
|
if [ "$MODE_BOOT" -eq 0 ]; then
|
||
|
|
DO_LIVE=1
|
||
|
|
elif systemctl is-active --quiet systemd-networkd.service 2>/dev/null; then
|
||
|
|
DO_LIVE=1
|
||
|
|
log " boot race 감지: networkd 가 이미 active → 라이브 reconcile 수행"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "$DO_LIVE" -eq 1 ]; then
|
||
|
|
# L1: 라이브-vs-렌더 정합성 — /run 이 렌더와 같아도 "라이브 주소 != 렌더 주소"면 reconfigure
|
||
|
|
# 강제. 펌웨어 reconcile_live_networkd_state 복원 (이게 빠져서 cold-boot race 후 옛 IP 가
|
||
|
|
# 그대로 남았음). /run-vs-렌더 비교만으로는 못 잡는 케이스를 닫는다.
|
||
|
|
RECON=""
|
||
|
|
for r in $NET_RENDERS; do
|
||
|
|
src="${NETWORK_DIR}/${r}"; ifc=$(iface_of "$r")
|
||
|
|
[ -f "$src" ] || continue
|
||
|
|
want=$(render_addr "$src"); [ -n "$want" ] || continue # DHCP — 비교 생략
|
||
|
|
have=$(live_addr "$ifc")
|
||
|
|
if [ "$want" != "$have" ]; then
|
||
|
|
RECON="${RECON} ${ifc}"
|
||
|
|
log " reconcile: ${ifc} live=${have:-none} != desired=${want} → reconfigure"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# reconfigure 대상 = /run 변경 iface ∪ 라이브 불일치 iface
|
||
|
|
TARGETS=$(printf '%s\n' $CHANGED_IFACES $RECON | grep -v '^$' | sort -u)
|
||
|
|
if [ -n "$TARGETS" ]; then
|
||
|
|
if systemctl is-active --quiet systemd-networkd.service 2>/dev/null; then
|
||
|
|
run networkctl reload # P3
|
||
|
|
for ifc in $TARGETS; do
|
||
|
|
if command -v ip >/dev/null 2>&1 && ! ip link show dev "$ifc" >/dev/null 2>&1; then
|
||
|
|
log " ${ifc} 미존재 — reconfigure 생략"; continue
|
||
|
|
fi
|
||
|
|
run networkctl reconfigure "$ifc"
|
||
|
|
done
|
||
|
|
else
|
||
|
|
log " systemd-networkd 비활성 — reload/reconfigure 생략"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "$WPA_CHANGED" -eq 1 ]; then # P1
|
||
|
|
if command -v wpa_cli >/dev/null 2>&1 && wpa_cli -i wlan0 ping >/dev/null 2>&1; then
|
||
|
|
run wpa_cli -i wlan0 reconfigure
|
||
|
|
else
|
||
|
|
run systemctl try-restart wpa_supplicant@wlan0.service
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── 4.5) 검증 요약 (관측만 — 롤백 안 함). 재테스트 판정용. ──
|
||
|
|
if [ -n "$TARGETS" ] || [ "$WPA_CHANGED" -eq 1 ]; then
|
||
|
|
sleep 2 # 주소 settle
|
||
|
|
for r in $NET_RENDERS; do
|
||
|
|
src="${NETWORK_DIR}/${r}"; ifc=$(iface_of "$r")
|
||
|
|
[ -f "$src" ] || continue
|
||
|
|
want=$(render_addr "$src")
|
||
|
|
[ -n "$want" ] || { log " verify ${ifc}: DHCP/no-addr"; continue; }
|
||
|
|
have=$(live_addr "$ifc")
|
||
|
|
if [ "$want" = "$have" ]; then
|
||
|
|
log " verify ${ifc}: OK (${have})"
|
||
|
|
else
|
||
|
|
log " verify ${ifc}: MISMATCH want=${want} live=${have:-none}"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
if [ "$WPA_CHANGED" -eq 1 ]; then
|
||
|
|
st=$(wpa_cli -i wlan0 status 2>/dev/null | sed -n 's/^wpa_state=//p')
|
||
|
|
ss=$(wpa_cli -i wlan0 status 2>/dev/null | sed -n 's/^ssid=//p')
|
||
|
|
log " verify wlan0: wpa_state=${st:-?} ssid=${ss:-?}"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ──────── 5) shadow 갱신 (적용 기준선) ────────
|
||
|
|
for f in $NET_RENDERS wpa_supplicant-wlan0.conf wifi-country-code network_config.json; do
|
||
|
|
[ -f "${NETWORK_DIR}/${f}" ] && cp -f "${NETWORK_DIR}/${f}" "${SHADOW_DIR}/${f}" 2>/dev/null || true
|
||
|
|
done
|
||
|
|
|
||
|
|
log "DONE (networkd_dirty=${NETWORKD_DIRTY} wpa_changed=${WPA_CHANGED}$([ -f "$REBOOT_MARK" ] && echo ' reboot_required=1'))"
|
||
|
|
exit 0
|