lightweight-server/ochenstarik-server-supply-chain.sh
ochenstarik-ui 9f06756cef
Some checks failed
Bash syntax / syntax (push) Failing after 11s
Bash syntax / syntax (pull_request) Failing after 2s
fix: close installer exposure and pin verified payloads
2026-10-23 12:33:19 +07:00

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