#!/bin/sh
set -eu
umask 022
# 容器自身启动日志：写在 /etc（= overlay upper，持久化），宿主通过挂载的rootfs
# 读取它。用途：心跳缺失时判断guest启动到哪一步——区分「init卡住」与「已就绪
# 但helper链路断」。
BOOTLOG=/etc/compat-boot.log
: > "$BOOTLOG" 2>/dev/null || true
blog() { printf '%s ikuai-compat-init: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo -)" "$1" >> "$BOOTLOG" 2>/dev/null || true; }
blog "start"
mkdir -p /tmp/run /tmp/lock /tmp/log /tmp/state /tmp/.uci /tmp/resolv.conf.d /run/ikuai
chmod 1777 /tmp
# 品牌标识：让ubus call system board / LuCI显示iKuaiWrt的机型名。
mkdir -p /tmp/sysinfo
printf '%s\n' 'iKuai OpenWrt Compatibility Layer' > /tmp/sysinfo/model
printf '%s\n' 'iKuai' > /tmp/sysinfo/board_name
# 管理网络由宿主manager注入，禁止启动宿主引导及设备探测。
if [ -f /etc/ikuai-compat-resolv.conf ]; then cp /etc/ikuai-compat-resolv.conf /tmp/resolv.conf; fi
# opkg源收敛：把 /etc/ikuaiwrt-opkg-mirror（主要源，产品默认CERNET）应用到
# distfeeds.conf。容器 /etc是可写上层，升级后旧镜像会被持久化，故每次启动收敛
# 一次；用户所选的主要源（含自定义）因此得以保留。
mirror="$(cat /etc/ikuaiwrt-opkg-mirror 2>/dev/null || true)"
if [ -n "$mirror" ] && [ -f /etc/opkg/distfeeds.conf ]; then
	sed -i -E "s#(^src/[a-z]+[[:space:]]+[^[:space:]]+[[:space:]]+)https?://[^[:space:]]+/releases/#\1${mirror}/releases/#" /etc/opkg/distfeeds.conf
	rm -f /etc/opkg/distfeeds.conf.bak
	uci -q set system.@imm_init[0].opkg_mirror="$mirror" 2>/dev/null || true
	uci -q commit system 2>/dev/null || true
fi
blog "mirror-applied"
if [ -f /etc/compat-root-password ]; then
	password="$(cat /etc/compat-root-password)"
	[ "${#password}" -ge 8 ] || exit 1
	printf '%s\n%s\n' "$password" "$password" | passwd root >/tmp/compat-password.log 2>&1
	unset password
	rm -f /etc/compat-root-password
fi
blog "password-set"
# 容器不设br-lan（连接模型：mgmt(/30)→宿主，宿主NAT）。本脚本清理旧版残留br-lan。
sh /sbin/ikuai-compat-lan >/dev/null 2>&1 || true
blog "net-clean"
# 兼容门面（详见 /sbin/ikuai-compat-facade）：默认隐藏fw4，使插件回退iptables-legacy；
# 实验特性FIREWALL_STACK=fw4时保留原生nftables栈。
sh /sbin/ikuai-compat-facade >/dev/null 2>&1 || true
blog "facade-ready"
# DHCP守护（详见 /sbin/ikuai-compat-guard）：容器绝不提供DHCP服务——关闭dnsmasq的
# DHCP、停用odhcpd，并尽力丢弃本机发出的DHCP服务端报文（防火墙启动后会再次加固）。
sh /sbin/ikuai-compat-guard >/dev/null 2>&1 || true
blog "dhcp-guard"
# 首次初始化：把软件源列表拉下来并缓存。
# 为什么必须做：/var/opkg-lists是空的（从未成功更新过源），此时opkg list只能列出
# 已装的包、且没有描述字段——控制台【软件包管理】的「说明」列因此整列空白，用户看到
# 的就是"为什么说明是空的"。第一次安装时拉一次，之后有缓存就不再自动跑（用户可在
# 控制台手动点【更新软件源】）。
#
#   必须"讲礼貌"：opkg 全程持有 /var/lock/opkg.lock，而控制台自己的 opkg 调用
#   （列已装包、查可安装包、取描述）也在用同一把锁。抢锁会让控制台弹出一条
#   「Could not lock /var/lock/opkg.lock」的红字错误——曾真实发生过。所以：
#   * 先等一会儿，让容器启动和平台自己的 opkg 工作先做完；
#   * 每轮先探测锁是否存在，有人在用就跳过这一轮，绝不硬抢；
#   * 轮次少（3 次），失败就留给用户手动点【更新软件源】，下次开机再自动试。
#
#   判据只用「持久目录里有没有列表」，不再用"曾经预热过"的标记文件：标记会撒谎
#   ——0.2.94 的标记是在列表还写去 /var（tmpfs，会被清空）时留下的，结果标记存在
#   而列表没了，预热被永久跳过。文件本身在不在，才是事实。
if ! ls /etc/opkg-lists/* >/dev/null 2>&1; then
	(
		for i in 1 2 3; do
			sleep 45
			if [ -e /var/lock/opkg.lock ]; then continue; fi
			if opkg update >/tmp/opkg-first.log 2>&1 && ls /etc/opkg-lists/* >/dev/null 2>&1; then
				break
			fi
		done
	) >/dev/null 2>&1 &
fi
blog "opkg-warm"
blog "ready"
# ★ x86/64 的 procd 不自带 ubusd 启动。官方 procd 二进制实测：x86_64 版没有
#   /sbin/ubusd 这条路径，aarch64_generic 版有（同版本同 hash，见 procd 25.03）。
#   而 procd 只有连上 ubus 后才会从 STATE_UBUS 前进到 STATE_INIT、进而执行
#   /etc/rc.d —— ubusd 不在，整个引导就停在第 2 阶段（症状：容器在跑、PID1=procd，
#   但 guest 里一个服务都不起、LuCI 就绪不了、iptables 0 条）。
#   正解是让 ubusd 就位，之后由 procd 自己走完剩下的引导。arm64 的 procd 会自己
#   起 ubusd，看守届时即退出，不会重复启动。
(
	i=0
	while [ "$i" -lt 8 ]; do
		if [ -S /var/run/ubus/ubus.sock ] || [ -S /var/run/ubus.sock ]; then
			exit 0
		fi
		pidof ubusd >/dev/null 2>&1 && exit 0
		i=$((i + 1))
		sleep 1
	done
	blog "procd-ubusd-missing"
	/sbin/ubusd >/dev/null 2>&1 &
) >/dev/null 2>&1 &
exec /sbin/procd -S
