#!/usr/bin/env bash set -Eeuo pipefail readonly XUI_TAG="v3.10.2" readonly XUI_INSTALLER_SHA="9684bae4f769b2127fcceceb4f200045657381311867529e6ba76c6b58e2825d" readonly XUI_SCRIPT_SHA="3c7dabe7ea9edf0d950eff1caffbf203a1f8b52e98fc75e203372757f47998c0" readonly XUI_SERVICE_SHA="6e120b037ed0a1305331e31de21471d3e6485ea28133ccf85a45d1270258ff9e" readonly XRAY_COMMIT="05aa47266c94e6fb5ca48cad96afdb4becb5891f" readonly XRAY_INSTALLER_SHA="65e9e2accfc0b3b5dd24e7f2c552f35af3e581068155163d66d40f1d04fb6c95" readonly XRAY_TAG="v26.10.21" die() { printf '[x] %s\n' "$*" >&2; exit 1; } architecture() { case "$(uname -m)" in x86_64|x64|amd64) printf 'amd64\n' ;; armv8*|armv8|arm64|aarch64) printf 'arm64\n' ;; *) die "Unsupported architecture for pinned installer payloads: $(uname -m)" ;; esac } sha_for_arch() { case "$1:$2" in xui:amd64) printf '%s\n' bfda7921984e7a72555c38d4e861c7a8b67b3686b658608937de99682f1452d0 ;; xui:arm64) printf '%s\n' a867c65ce52ad49f6504dd4c9c395337dc92ba57ae478da4f1b7ddb624014ed5 ;; xray:amd64) printf '%s\n' 0eec3ed4163c273d8c8bf821f7d219e257921cf584aa3fb970715e458e631978 ;; xray:arm64) printf '%s\n' d1304946c486d98e6b886e9b931eaaaebb23835b30e217638037b7f2721d4e9c ;; *) die "Missing pinned digest for $1/$2" ;; esac } fetch_pinned() { local url="$1" dest="$2" expected_sha="$3" tmp tmp="${dest}.partial.$$" curl -fLsS --location --retry 2 --connect-timeout 15 --max-time 180 \ --proto '=https' --tlsv1.2 -o "$tmp" -- "$url" \ || die "Pinned download failed: $(basename "$dest")" [[ -s "$tmp" && "$(sha256sum "$tmp" | awk '{print $1}')" == "$expected_sha" ]] \ || die "SHA-256 verification failed: $(basename "$dest")" chmod 600 "$tmp" mv -- "$tmp" "$dest" } validate_prepared_dir() { local directory="$1" [[ -d "$directory" && ! -L "$directory" \ && "$(stat -c %u "$directory")" == 0 \ && "$(stat -c %a "$directory")" =~ ^[0-7]*[0-5][0-5]$ ]] \ || die "Prepared directory must be a root-owned directory not writable by group or others" } validate_prepared_file() { local file="$1" [[ -f "$file" && ! -L "$file" && "$(stat -c %u "$file")" == 0 \ && "$(stat -c %a "$file")" =~ ^[0-7]*[0-5][0-5]$ ]] \ || die "Prepared file is missing or writable by group/others: $(basename "$file")" } prepare() { local source="$1" destination="$2" mirror_base="${3:-}" arch xui_arch xray_arch local xui_sha xray_sha name expected arch="$(architecture)" case "$arch" in amd64) xui_arch=amd64; xray_arch=64 ;; arm64) xui_arch=arm64; xray_arch=arm64-v8a ;; esac case "$source" in github) [[ -z "$mirror_base" ]] || die "Do not pass a mirror base with github source" ;; forgejo) [[ "$mirror_base" =~ ^https://[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._/-]+)?$ \ && "$mirror_base" != *..* ]] \ || die "Forgejo mirror base must be a plain HTTPS URL without credentials, query, or traversal" mirror_base="${mirror_base%/}" ;; *) die "Usage: $0 prepare {github|forgejo} NEW_DEST_DIR [FORGEJO_HTTPS_BASE]" ;; esac [[ "$source" != forgejo || -n "$mirror_base" ]] || die "Forgejo HTTPS base is required" [[ ! -e "$destination" && ! -L "$destination" ]] \ || die "Destination must not already exist: $destination" mkdir -m 700 -- "$destination" destination="$(readlink -f -- "$destination")" xui_sha="$(sha_for_arch xui "$arch")" xray_sha="$(sha_for_arch xray "$arch")" if [[ "$source" == github ]]; then fetch_pinned "https://raw.githubusercontent.com/MHSanaei/3x-ui/${XUI_TAG}/install.sh" \ "$destination/xui-install.sh" "$XUI_INSTALLER_SHA" fetch_pinned "https://github.com/MHSanaei/3x-ui/releases/download/${XUI_TAG}/x-ui-linux-${xui_arch}.tar.gz" \ "$destination/x-ui-linux-${xui_arch}.tar.gz" "$xui_sha" fetch_pinned "https://raw.githubusercontent.com/MHSanaei/3x-ui/${XUI_TAG}/x-ui.sh" \ "$destination/x-ui.sh" "$XUI_SCRIPT_SHA" fetch_pinned "https://raw.githubusercontent.com/MHSanaei/3x-ui/${XUI_TAG}/x-ui.service.debian" \ "$destination/x-ui.service.debian" "$XUI_SERVICE_SHA" fetch_pinned "https://raw.githubusercontent.com/XTLS/Xray-install/${XRAY_COMMIT}/install-release.sh" \ "$destination/xray-install.sh" "$XRAY_INSTALLER_SHA" fetch_pinned "https://github.com/XTLS/Xray-core/releases/download/${XRAY_TAG}/Xray-linux-${xray_arch}.zip" \ "$destination/Xray-linux-${xray_arch}.zip" "$xray_sha" else for name in xui-install.sh "x-ui-linux-${xui_arch}.tar.gz" x-ui.sh \ x-ui.service.debian xray-install.sh "Xray-linux-${xray_arch}.zip"; do case "$name" in xui-install.sh) expected="$XUI_INSTALLER_SHA" ;; x-ui-linux-*.tar.gz) expected="$xui_sha" ;; x-ui.sh) expected="$XUI_SCRIPT_SHA" ;; x-ui.service.debian) expected="$XUI_SERVICE_SHA" ;; xray-install.sh) expected="$XRAY_INSTALLER_SHA" ;; Xray-linux-*.zip) expected="$xray_sha" ;; esac fetch_pinned "${mirror_base}/${name}" "$destination/$name" "$expected" done fi printf '%s %s\n' "$xui_sha" "x-ui-linux-${xui_arch}.tar.gz" \ > "$destination/x-ui-linux-${xui_arch}.tar.gz.sha256" printf 'SHA2-256= %s\n' "$xray_sha" \ > "$destination/Xray-linux-${xray_arch}.zip.dgst" chmod 600 "$destination"/* printf '[+] Verified pinned installers and release assets in %s\n' "$destination" } prepare_xray() { local source="$1" destination="$2" mirror_base="${3:-}" arch xray_arch xray_sha [[ ! -e "$destination" && ! -L "$destination" ]] \ || die "Destination must not already exist: $destination" arch="$(architecture)" case "$arch" in amd64) xray_arch=64 ;; arm64) xray_arch=arm64-v8a ;; esac xray_sha="$(sha_for_arch xray "$arch")" case "$source" in github) [[ -z "$mirror_base" ]] || die "Do not pass a mirror base with github source" ;; forgejo) [[ "$mirror_base" =~ ^https://[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._/-]+)?$ \ && "$mirror_base" != *..* ]] \ || die "Forgejo mirror base must be a plain HTTPS URL without credentials, query, or traversal" mirror_base="${mirror_base%/}" ;; *) die "Usage: $0 prepare-xray {github|forgejo} NEW_DEST_DIR [FORGEJO_HTTPS_BASE]" ;; esac [[ "$source" != forgejo || -n "$mirror_base" ]] || die "Forgejo HTTPS base is required" mkdir -m 700 -- "$destination" destination="$(readlink -f -- "$destination")" if [[ "$source" == github ]]; then fetch_pinned "https://raw.githubusercontent.com/XTLS/Xray-install/${XRAY_COMMIT}/install-release.sh" \ "$destination/xray-install.sh" "$XRAY_INSTALLER_SHA" fetch_pinned "https://github.com/XTLS/Xray-core/releases/download/${XRAY_TAG}/Xray-linux-${xray_arch}.zip" \ "$destination/Xray-linux-${xray_arch}.zip" "$xray_sha" else fetch_pinned "${mirror_base}/xray-install.sh" "$destination/xray-install.sh" "$XRAY_INSTALLER_SHA" fetch_pinned "${mirror_base}/Xray-linux-${xray_arch}.zip" \ "$destination/Xray-linux-${xray_arch}.zip" "$xray_sha" fi printf 'SHA2-256= %s\n' "$xray_sha" \ > "$destination/Xray-linux-${xray_arch}.zip.dgst" chmod 600 "$destination"/* printf '[+] Verified pinned Xray %s installer and payload in %s\n' "$XRAY_TAG" "$destination" } write_xui_curl_shim() { local directory="$1" shim_dir="$2" mkdir -m 700 -p -- "$shim_dir" cat > "$shim_dir/curl" <<'CURL_SHIM' #!/usr/bin/env bash set -Eeuo pipefail url="" out="" has_writeout=no previous="" for argument in "$@"; do if [[ "$previous" == -o || "$previous" == --output \ || "$previous" =~ ^-[A-Za-z]*o$ ]]; then out="$argument"; fi [[ "$argument" != -w && "$argument" != --write-out ]] || has_writeout=yes [[ "$argument" != http://* && "$argument" != https://* ]] || url="$argument" previous="$argument" done answer_file="" case "$url" in https://github.com/MHSanaei/3x-ui/releases/download/v3.10.2/x-ui-linux-*.tar.gz) answer_file="${SUPPLY_DIR}/$(basename "$url")" ;; https://github.com/MHSanaei/3x-ui/releases/download/v3.10.2/x-ui-linux-*.tar.gz.sha256) answer_file="${SUPPLY_DIR}/$(basename "$url")" ;; https://raw.githubusercontent.com/MHSanaei/3x-ui/v3.10.2/x-ui.sh) answer_file="${SUPPLY_DIR}/x-ui.sh" ;; https://raw.githubusercontent.com/MHSanaei/3x-ui/v3.10.2/x-ui.service.debian) answer_file="${SUPPLY_DIR}/x-ui.service.debian" ;; https://raw.githubusercontent.com/MHSanaei/3x-ui/v3.10.2/x-ui.rc) printf 'Unsupported platform data request\n' >&2; exit 1 ;; https://check-host.net/ip) [[ -x "${REAL_CURL_BIN:-}" ]] || { printf 'curl is unavailable for pinned data lookup\n' >&2; exit 1; } exec "$REAL_CURL_BIN" "$@" --proto '=https' --tlsv1.2 --max-time 5 ;; *) printf 'Blocked unpinned 3x-ui installer request: %s\n' "$url" >&2; exit 1 ;; esac [[ -r "$answer_file" ]] || { printf 'Pinned data file is missing: %s\n' "$answer_file" >&2; exit 1; } if [[ -n "$out" && "$out" != /dev/null ]]; then cp -- "$answer_file" "$out" fi if [[ "$has_writeout" == yes ]]; then printf '200'; fi CURL_SHIM chmod 700 "$shim_dir/curl" : "$directory" } install_xui() { local directory="$1" arch xui_arch shim_dir directory="$(readlink -f -- "$directory")" validate_prepared_dir "$directory" arch="$(architecture)" case "$arch" in amd64) xui_arch=amd64 ;; arm64) xui_arch=arm64 ;; esac for file in xui-install.sh "x-ui-linux-${xui_arch}.tar.gz" \ "x-ui-linux-${xui_arch}.tar.gz.sha256" x-ui.sh x-ui.service.debian; do validate_prepared_file "$directory/$file" done [[ "$(sha256sum "$directory/xui-install.sh" | awk '{print $1}')" == "$XUI_INSTALLER_SHA" \ && "$(sha256sum "$directory/x-ui.sh" | awk '{print $1}')" == "$XUI_SCRIPT_SHA" \ && "$(sha256sum "$directory/x-ui.service.debian" | awk '{print $1}')" == "$XUI_SERVICE_SHA" \ && "$(sha256sum "$directory/x-ui-linux-${xui_arch}.tar.gz" | awk '{print $1}')" == "$(sha_for_arch xui "$arch")" ]] \ || die "Prepared 3x-ui payload no longer matches the pins" shim_dir="$(mktemp -d /tmp/ochenstarik-xui-curl.XXXXXX)" trap 'rm -rf -- "$shim_dir"' EXIT local real_curl real_curl="$(command -v curl)" [[ -x "$real_curl" ]] || die "curl is required for the pinned data lookup" write_xui_curl_shim "$directory" "$shim_dir" (cd -- "$directory" && SUPPLY_DIR="$directory" REAL_CURL_BIN="$real_curl" PATH="$shim_dir:$PATH" \ XUI_NONINTERACTIVE=1 bash ./xui-install.sh "$XUI_TAG") rm -rf -- "$shim_dir" trap - EXIT } install_xray() { local directory="$1" arch xray_arch expected directory="$(readlink -f -- "$directory")" validate_prepared_dir "$directory" arch="$(architecture)" case "$arch" in amd64) xray_arch=64 ;; arm64) xray_arch=arm64-v8a ;; esac expected="$(sha_for_arch xray "$arch")" validate_prepared_file "$directory/xray-install.sh" validate_prepared_file "$directory/Xray-linux-${xray_arch}.zip" validate_prepared_file "$directory/Xray-linux-${xray_arch}.zip.dgst" [[ "$(sha256sum "$directory/xray-install.sh" | awk '{print $1}')" == "$XRAY_INSTALLER_SHA" \ && "$(sha256sum "$directory/Xray-linux-${xray_arch}.zip" | awk '{print $1}')" == "$expected" \ && "$(awk -F '= ' '/^SHA2-256=/ {print $2}' "$directory/Xray-linux-${xray_arch}.zip.dgst")" == "$expected" ]] \ || die "Prepared Xray payload no longer matches the pins" (cd -- "$directory" && bash ./xray-install.sh install --local "Xray-linux-${xray_arch}.zip") } case "${1:-}" in prepare) [[ $# -ge 3 && $# -le 4 ]] || die "Usage: $0 prepare {github|forgejo} NEW_DEST_DIR [FORGEJO_HTTPS_BASE]" prepare "$2" "$3" "${4:-}" ;; prepare-xray) [[ $# -ge 3 && $# -le 4 ]] || die "Usage: $0 prepare-xray {github|forgejo} NEW_DEST_DIR [FORGEJO_HTTPS_BASE]" prepare_xray "$2" "$3" "${4:-}" ;; install-xui) [[ $# == 2 ]] || die "Usage: $0 install-xui PREPARED_DIR" install_xui "$2" ;; install-xray) [[ $# == 2 ]] || die "Usage: $0 install-xray PREPARED_DIR" install_xray "$2" ;; *) die "Usage: $0 {prepare|prepare-xray|install-xui|install-xray} ..." ;; esac