#!/usr/bin/env bash set -euo pipefail IMMORTALWRT_VERSION="${IMMORTALWRT_VERSION:-v25.12.1}" IMMORTALWRT_URL="${IMMORTALWRT_URL:-https://github.com/immortalwrt/immortalwrt.git}" MIHOMO_FEED_URL="${MIHOMO_FEED_URL:-https://git.laysense.com/SerinaNya/mihomo-openwrt.git}" JOBS="${JOBS:-$(nproc)}" ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" WORK_DIR="${WORK_DIR:-${ROOT_DIR}/.build}" SOURCE_DIR="${WORK_DIR}/immortalwrt" DIST_DIR="${ROOT_DIR}/dist" APT_PACKAGES=( ack antlr3 asciidoc autoconf automake autopoint binutils bison build-essential bzip2 ccache clang cmake cpio curl device-tree-compiler ecj fastjar flex gawk gettext gcc-multilib g++-multilib git gnutls-dev gperf haveged help2man intltool lib32gcc-s1 libc6-dev-i386 libelf-dev libglib2.0-dev libgmp3-dev libltdl-dev libmpc-dev libmpfr-dev libncurses-dev libpython3-dev libreadline-dev libssl-dev libtool libyaml-dev libz-dev lld llvm lrzsz mkisofs msmtp nano ninja-build p7zip p7zip-full patch pkgconf python3 python3-pip python3-ply python3-docutils python3-pyelftools qemu-utils re2c rsync scons squashfs-tools subversion swig texinfo uglifyjs upx-ucl unzip vim wget xmlto xxd zlib1g-dev zstd ) log() { printf '\n==> %s\n' "$*" } die() { printf 'Error: %s\n' "$*" >&2 exit 1 } check_host() { [[ "${EUID}" -ne 0 ]] || die "Run this script as an unprivileged user, not root." [[ "$(uname -m)" == "x86_64" ]] || die "This script supports x86_64 build hosts only." [[ "${ROOT_DIR}" != *" "* ]] || die "The repository path must not contain spaces." [[ "${WORK_DIR}" != *" "* ]] || die "WORK_DIR must not contain spaces." if LC_ALL=C grep -q '[^ -~]' <<<"${ROOT_DIR}${WORK_DIR}"; then die "Repository and work paths must contain ASCII characters only." fi mkdir -p "${WORK_DIR}" local probe probe="$(mktemp -d "${WORK_DIR}/.case-test.XXXXXX")" touch "${probe}/lowercase" if [[ -e "${probe}/LOWERCASE" ]]; then rm -rf "${probe}" die "The build filesystem must be case-sensitive." fi rm -rf "${probe}" } install_dependencies() { if [[ "${SKIP_DEPS:-0}" == "1" ]]; then log "Skipping APT dependency installation" return fi command -v apt-get >/dev/null 2>&1 || die "apt-get is required for automatic dependency installation." command -v sudo >/dev/null 2>&1 || die "sudo is required to install APT dependencies." log "Installing ImmortalWrt build dependencies" sudo apt-get update sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y "${APT_PACKAGES[@]}" } prepare_source() { if [[ ! -d "${SOURCE_DIR}/.git" ]]; then log "Cloning ImmortalWrt ${IMMORTALWRT_VERSION}" git clone -b "${IMMORTALWRT_VERSION}" --single-branch --filter=blob:none \ "${IMMORTALWRT_URL}" "${SOURCE_DIR}" else git -C "${SOURCE_DIR}" diff --quiet || die "ImmortalWrt checkout has modified tracked files." git -C "${SOURCE_DIR}" diff --cached --quiet || die "ImmortalWrt checkout has staged changes." log "Refreshing ImmortalWrt ${IMMORTALWRT_VERSION}" git -C "${SOURCE_DIR}" fetch --depth=1 origin \ "refs/tags/${IMMORTALWRT_VERSION}:refs/tags/${IMMORTALWRT_VERSION}" git -C "${SOURCE_DIR}" checkout --detach "${IMMORTALWRT_VERSION}" fi } prepare_feeds() { log "Configuring feeds" if [[ ! -f "${SOURCE_DIR}/feeds.conf" ]]; then cp "${SOURCE_DIR}/feeds.conf.default" "${SOURCE_DIR}/feeds.conf" fi sed -i '/^src-git mihomo /d' "${SOURCE_DIR}/feeds.conf" printf 'src-git mihomo %s\n' "${MIHOMO_FEED_URL}" >>"${SOURCE_DIR}/feeds.conf" ( cd "${SOURCE_DIR}" ./scripts/feeds update packages mihomo ./scripts/feeds install -p packages golang ./scripts/feeds install -f -p mihomo mihomo ) } configure_target() { log "Configuring the x86/64 target" cat >"${SOURCE_DIR}/.config" <<'EOF' CONFIG_TARGET_x86=y CONFIG_TARGET_x86_64=y CONFIG_TARGET_x86_64_DEVICE_generic=y CONFIG_PACKAGE_mihomo=m EOF make -C "${SOURCE_DIR}" defconfig grep -q '^CONFIG_TARGET_x86_64=y$' "${SOURCE_DIR}/.config" || die "x86/64 target configuration failed." grep -q '^CONFIG_PACKAGE_mihomo=m$' "${SOURCE_DIR}/.config" || die "mihomo package selection failed." } build_package() { log "Building mihomo with ${JOBS} parallel jobs" make -C "${SOURCE_DIR}" package/feeds/mihomo/mihomo/download -j"${JOBS}" V=s make -C "${SOURCE_DIR}" package/feeds/mihomo/mihomo/clean if [[ -d "${SOURCE_DIR}/bin/packages" ]]; then find "${SOURCE_DIR}/bin/packages" -type f -name 'mihomo-*.apk' -delete fi make -C "${SOURCE_DIR}" package/feeds/mihomo/mihomo/compile -j"${JOBS}" V=s } collect_artifacts() { log "Collecting APK artifacts" mkdir -p "${DIST_DIR}" find "${DIST_DIR}" -maxdepth 1 -type f -name 'mihomo-*.apk' -delete local artifacts=() while IFS= read -r -d '' artifact; do artifacts+=("${artifact}") done < <(find "${SOURCE_DIR}/bin/packages" -type f -name 'mihomo-*.apk' -print0) ((${#artifacts[@]} > 0)) || die "Build completed but no mihomo APK was found." cp -v "${artifacts[@]}" "${DIST_DIR}/" log "Build complete" printf 'APK output directory: %s\n' "${DIST_DIR}" } check_host install_dependencies prepare_source prepare_feeds configure_target build_package collect_artifacts