271 lines
12 KiB
Bash
Executable file
271 lines
12 KiB
Bash
Executable file
#!/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
|