#!/bin/bash # Prepare the OpenWrt build dependencies. # # Supported distributions: # - Debian / Ubuntu / Linux Mint / Raspberry Pi OS (apt) # - Fedora / RHEL / CentOS / Rocky / AlmaLinux (dnf, yum) # - Arch / Manjaro / EndeavourOS (pacman) # - openSUSE / SLES (zypper) # - Alpine (apk) # # Package lists follow https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem # Packages that do not exist on the running release are skipped instead of # aborting the whole installation. # # Usage: ./prepare-openwrt-env.sh [-y] [-n] [--with-go] # -y, --yes do not ask for confirmation # -n, --dry-run only print what would be installed # --with-go also install the Go toolchain # # Author: Zhe Yuan set -euo pipefail umask 022 ASSUME_YES=false DRY_RUN=false WITH_GO=false while [ $# -gt 0 ]; do case "$1" in -y|--yes) ASSUME_YES=true ;; -n|--dry-run) DRY_RUN=true ;; --with-go) WITH_GO=true ;; -h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "[ERROR] Unknown option: $1" >&2 exit 1 ;; esac shift done echo "========================================" echo " OpenWrt Build Environment Preparation" echo "========================================" # --- Detect architecture --- ARCH="$(uname -m)" echo "[INFO] Architecture: $ARCH" # --- Detect distribution --- DISTRO="unknown" DISTRO_LIKE="" VERSION_ID="unknown" PRETTY_NAME="unknown" if [ -r /etc/os-release ]; then # shellcheck disable=SC1091 . /etc/os-release DISTRO="${ID:-unknown}" DISTRO_LIKE="${ID_LIKE:-}" VERSION_ID="${VERSION_ID:-unknown}" PRETTY_NAME="${PRETTY_NAME:-$DISTRO $VERSION_ID}" else echo "[ERROR] Cannot detect the distribution (no /etc/os-release)." >&2 exit 1 fi echo "[INFO] Distribution: $PRETTY_NAME (id=$DISTRO)" # --- Detect package manager --- PKG_MGR="" for candidate in apt-get dnf yum pacman zypper apk; do if command -v "$candidate" >/dev/null 2>&1; then PKG_MGR="$candidate" break fi done if [ -z "$PKG_MGR" ]; then echo "[ERROR] No supported package manager found." >&2 echo "[ERROR] Supported: apt-get, dnf, yum, pacman, zypper, apk." >&2 exit 1 fi echo "[INFO] Package manager: $PKG_MGR" # --- Privilege escalation --- SUDO="" if [ "$(id -u)" -ne 0 ]; then if command -v sudo >/dev/null 2>&1; then SUDO="sudo" else echo "[ERROR] Not running as root and 'sudo' is not available." >&2 exit 1 fi fi is_x86_64() { [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ] } is_arm64() { [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ] } # --------------------------------------------------------------------------- # Package lists # # REQUIRED_PACKAGES installation fails if one of them cannot be installed # OPTIONAL_PACKAGES nice to have; silently skipped when unavailable # --------------------------------------------------------------------------- REQUIRED_PACKAGES="" OPTIONAL_PACKAGES="" REFRESH_CMD="" INSTALL_CMD="" case "$PKG_MGR" in apt-get) REFRESH_CMD="$SUDO apt-get update" INSTALL_CMD="$SUDO apt-get install -y --no-install-recommends" REQUIRED_PACKAGES="build-essential clang flex bison g++ gawk gcc gettext git \ file wget unzip rsync swig time python3 python3-dev python3-setuptools \ libncurses-dev libssl-dev zlib1g-dev libelf-dev make patch diffutils" # Extras from the OpenWrt 25.xx / Debian Trixie list. Names come and go # between releases, so everything here is filtered before installing. OPTIONAL_PACKAGES="bc binutils-gold ccache ecj fastjar help2man libbsd-dev \ liblzma-dev meson mold mtd-utils ninja-build pbzip2 pigz pkg-config \ subversion texinfo u-boot-tools xsltproc xxd zstd bzip2 quilt asciidoc \ device-tree-compiler java-propose-classpath python3-distutils \ python3-distutils-extra libncurses5-dev" if is_x86_64; then OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES gcc-multilib g++-multilib glibc-source" elif is_arm64; then # arm64 has no native multilib; the cross variants are optional and the # available set differs a lot between releases (e.g. the s390x variants # were dropped in Ubuntu 26.04). OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES \ gcc-multilib-i686-linux-gnu g++-multilib-i686-linux-gnu \ gcc-multilib-x86-64-linux-gnu g++-multilib-x86-64-linux-gnu \ gcc-multilib-x86-64-linux-gnux32 g++-multilib-x86-64-linux-gnux32 \ gcc-multilib-s390x-linux-gnu g++-multilib-s390x-linux-gnu \ libc6-dev-i386-cross libc6-dev-i386-amd64-cross libc6-dev-i386-x32-cross \ glibc-source" fi if [ "$WITH_GO" = true ]; then OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES golang golang-go" fi ;; dnf|yum) REFRESH_CMD="$SUDO $PKG_MGR makecache" INSTALL_CMD="$SUDO $PKG_MGR install -y --setopt=install_weak_deps=False" REQUIRED_PACKAGES="gcc gcc-c++ make git-core patch diffutils file wget \ unzip rsync tar which bzip2 ncurses-devel openssl-devel zlib-devel \ python3 python3-devel python3-setuptools" OPTIONAL_PACKAGES="bash-completion elfutils-libelf-devel zlib-static \ glibc-static swig quilt ccache perl-base perl-Data-Dumper \ perl-File-Compare perl-File-Copy perl-FindBin perl-IPC-Cmd perl-JSON-PP \ perl-lib perl-Thread-Queue perl-Time-Piece libxslt bc texinfo \ dtc zstd pkgconf-pkg-config" if [ "$WITH_GO" = true ]; then OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES golang" fi ;; pacman) REFRESH_CMD="$SUDO pacman -Sy" INSTALL_CMD="$SUDO pacman -S --needed --noconfirm" REQUIRED_PACKAGES="base-devel bash bzip2 git libelf libxslt ncurses openssl \ rsync swig time unzip util-linux wget which zlib python python-setuptools" OPTIONAL_PACKAGES="ccache quilt pkgconf diffutils patch tar make gawk \ subversion asciidoc dtc zstd" if [ "$WITH_GO" = true ]; then OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go" fi ;; zypper) REFRESH_CMD="$SUDO zypper --non-interactive refresh" INSTALL_CMD="$SUDO zypper --non-interactive install --no-recommends" REQUIRED_PACKAGES="bash bc binutils bzip2 flex gawk gcc gcc-c++ \ gettext-tools git-core make ncurses-devel patch rsync unzip util-linux \ wget zlib-devel libopenssl-devel python3-devel python3-setuptools" OPTIONAL_PACKAGES="asciidoc fastjar intltool libxslt-tools mercurial \ perl-ExtUtils-MakeMaker sdcc quilt ccache libelf-devel swig zstd dtc" if [ "$WITH_GO" = true ]; then OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go" fi ;; apk) REFRESH_CMD="$SUDO apk update" INSTALL_CMD="$SUDO apk add" REQUIRED_PACKAGES="argp-standalone bash bc binutils bzip2 coreutils \ diffutils elfutils-dev findutils flex g++ gawk gcc gettext git grep gzip \ linux-headers make ncurses-dev openssl-dev patch perl python3-dev rsync \ tar unzip util-linux wget zlib-dev" OPTIONAL_PACKAGES="asciidoc cdrkit intltool libxslt musl-fts-dev \ musl-libintl musl-obstack-dev swig quilt ccache zstd" if [ "$WITH_GO" = true ]; then OPTIONAL_PACKAGES="$OPTIONAL_PACKAGES go" fi ;; esac # --------------------------------------------------------------------------- # Availability filtering # --------------------------------------------------------------------------- pkg_available() { local pkg="$1" case "$PKG_MGR" in apt-get) # apt-cache policy prints no "Candidate:" line for purely virtual names # and "(none)" when no installable version exists in the repositories. local policy="" policy="$(apt-cache policy "$pkg" 2>/dev/null || true)" case "$policy" in *"Candidate: (none)"*) return 1 ;; *"Candidate:"*) return 0 ;; *) return 1 ;; esac ;; dnf) dnf -q info "$pkg" >/dev/null 2>&1 ;; yum) yum -q info "$pkg" >/dev/null 2>&1 ;; pacman) pacman -Si "$pkg" >/dev/null 2>&1 || pacman -Sg "$pkg" >/dev/null 2>&1 ;; zypper) zypper --non-interactive --quiet search --match-exact "$pkg" >/dev/null 2>&1 ;; apk) [ -n "$(apk search -x "$pkg" 2>/dev/null)" ] ;; *) return 0 ;; esac } filter_available() { local pkg local kept="" local dropped="" for pkg in $1; do if pkg_available "$pkg"; then kept="$kept $pkg" else dropped="$dropped $pkg" fi done FILTER_KEPT="${kept# }" FILTER_DROPPED="${dropped# }" } echo echo "[INFO] Refreshing package metadata..." if [ "$DRY_RUN" = true ]; then echo "[DRY-RUN] $REFRESH_CMD" else $REFRESH_CMD fi echo "[INFO] Checking package availability for this release..." filter_available "$REQUIRED_PACKAGES" REQUIRED_AVAILABLE="$FILTER_KEPT" REQUIRED_MISSING="$FILTER_DROPPED" filter_available "$OPTIONAL_PACKAGES" OPTIONAL_AVAILABLE="$FILTER_KEPT" OPTIONAL_MISSING="$FILTER_DROPPED" INSTALL_PACKAGES="$REQUIRED_AVAILABLE $OPTIONAL_AVAILABLE" INSTALL_PACKAGES="$(echo "$INSTALL_PACKAGES" | tr -s '[:space:]' ' ' | sed 's/^ //;s/ $//')" if [ -z "$INSTALL_PACKAGES" ]; then echo "[ERROR] No installable package found. Are the repositories configured?" >&2 exit 1 fi echo echo "========================================" echo "📦 Packages to install:" echo "$INSTALL_PACKAGES" | tr ' ' '\n' | sed 's/^/ /' echo "========================================" if [ -n "$REQUIRED_MISSING" ]; then echo echo "[WARN] Required packages not offered by this release (build may fail):" echo " $REQUIRED_MISSING" fi if [ -n "$OPTIONAL_MISSING" ]; then echo echo "[INFO] Optional packages unavailable here, skipped:" echo " $OPTIONAL_MISSING" fi if [ "$DRY_RUN" = true ]; then echo echo "[DRY-RUN] $INSTALL_CMD $INSTALL_PACKAGES" exit 0 fi if [ "$ASSUME_YES" != true ]; then echo read -r -p "Proceed with installation? [Y/n]: " reply case "$reply" in [nN]|[nN][oO]) echo "[INFO] Aborted by user."; exit 0 ;; esac fi echo echo "[INFO] Installing packages..." FAILED_PACKAGES="" if ! $INSTALL_CMD $INSTALL_PACKAGES; then echo echo "[WARN] Bulk installation failed. Retrying package by package..." for pkg in $INSTALL_PACKAGES; do if ! $INSTALL_CMD "$pkg" >/dev/null 2>&1; then FAILED_PACKAGES="$FAILED_PACKAGES $pkg" echo " [FAIL] $pkg" fi done FAILED_PACKAGES="${FAILED_PACKAGES# }" fi echo echo "========================================" if [ -n "$FAILED_PACKAGES" ]; then echo "⚠️ Setup finished with errors." echo " Failed packages: $FAILED_PACKAGES" else echo "✅ OpenWrt build environment setup complete!" fi echo "========================================" echo if [ "$WITH_GO" != true ]; then echo "👉 [NOTE] The Go toolchain is only needed for Go based packages" echo " (e.g. xray-core, v2ray). Install it with:" echo " ./prepare-openwrt-env.sh --with-go" echo fi if is_arm64; then echo "👉 [NOTE] arm64 has no native multilib. The cross-multilib packages are" echo " optional and only needed for x86 targets; missing ones were skipped." echo fi echo "👉 Next step:" echo " ./prepare-openwrt.sh # clone and set up the OpenWrt source" echo