2026-04-15 12:50:27 +00:00
#!/usr/bin/env bash
set -euo pipefail
2026-05-11 09:56:35 +00:00
VERSION_ARG = ""
WITH_CORE = "both"
FORCE_NETCORE = 0
BUILD_FROM = ""
XRAY_VER = " ${ XRAY_VER :- } "
SING_VER = " ${ SING_VER :- } "
2026-04-15 12:50:27 +00:00
MIN_KERNEL = "5.10"
2026-05-11 09:56:35 +00:00
PKGROOT = "v2rayN-publish"
PROJECT_HINT = "v2rayN.Desktop/v2rayN.Desktop.csproj"
RPM_TOPDIR = " ${ HOME } /rpmbuild "
2026-05-14 11:27:31 +00:00
DOTNET_RISCV_VERSION = "10.0.108"
2026-05-11 09:56:35 +00:00
DOTNET_RISCV_BASE = "https://github.com/xujiegb/dotnet-riscv/releases/download"
2026-04-15 12:50:27 +00:00
DOTNET_RISCV_FILE = " dotnet-sdk- ${ DOTNET_RISCV_VERSION } -linux-riscv64.tar.gz "
DOTNET_SDK_URL = " ${ DOTNET_RISCV_BASE } / ${ DOTNET_RISCV_VERSION } / ${ DOTNET_RISCV_FILE } "
2026-05-11 09:56:35 +00:00
OS_ID = ""
OS_NAME = ""
OS_VERSION_ID = ""
HOST_ARCH = ""
SCRIPT_DIR = ""
PROJECT = ""
VERSION = ""
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
declare -a BUILT_RPMS = ( )
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
die( ) {
echo " $* " >& 2
exit 1
}
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
parse_args( ) {
local first_arg = " ${ 1 :- } "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
if [ [ -n " $first_arg " && " $first_arg " != --* ] ] ; then
VERSION_ARG = " $first_arg "
shift || true
fi
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
while [ [ $# -gt 0 ] ] ; do
case " $1 " in
--with-core) WITH_CORE = " ${ 2 :- both } " ; shift 2 ; ;
--xray-ver) XRAY_VER = " ${ 2 :- } " ; shift 2 ; ;
--singbox-ver) SING_VER = " ${ 2 :- } " ; shift 2 ; ;
--netcore) FORCE_NETCORE = 1; shift ; ;
--buildfrom) BUILD_FROM = " ${ 2 :- } " ; shift 2 ; ;
*)
[ [ -n " ${ VERSION_ARG :- } " ] ] || VERSION_ARG = " $1 "
shift
; ;
esac
done
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
if [ [ -n " ${ VERSION_ARG :- } " && -n " ${ BUILD_FROM :- } " ] ] ; then
die " You cannot specify both an explicit version and --buildfrom at the same time.
Provide either a version ( e.g. 7.14.0) OR --buildfrom 1| 2| 3."
fi
2026-04-15 12:50:27 +00:00
}
2026-05-11 09:56:35 +00:00
detect_environment( ) {
local current_kernel = ""
local lowest = ""
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
. /etc/os-release
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
OS_ID = " ${ ID :- } "
OS_NAME = " ${ NAME :- $OS_ID } "
OS_VERSION_ID = " ${ VERSION_ID :- } "
HOST_ARCH = " $( uname -m) "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
case " $OS_ID " in
rhel| rocky| almalinux| fedora| centos)
echo " Detected supported system: ${ OS_NAME :- $OS_ID } ${ OS_VERSION_ID :- } "
; ;
*)
die " Unsupported system: ${ OS_NAME :- unknown } ( ${ OS_ID :- unknown } ).
This script only supports: RHEL / Rocky / AlmaLinux / Fedora / CentOS."
; ;
esac
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
case " $HOST_ARCH " in
riscv64) ; ;
*) die "Only supports riscv64" ; ;
esac
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
current_kernel = " $( uname -r) "
lowest = " $( printf '%s\n%s\n' " $MIN_KERNEL " " $current_kernel " | sort -V | head -n1) "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
[ [ " $lowest " = = " $MIN_KERNEL " ] ] || die " Kernel $current_kernel is below $MIN_KERNEL "
echo " [OK] Kernel $current_kernel verified. "
2026-04-15 12:50:27 +00:00
}
2026-05-11 09:56:35 +00:00
install_dependencies( ) {
local install_ok = 0
local tmp_dotnet = ""
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
if command -v dnf >/dev/null 2>& 1; then
sudo dnf -y install \
rpm-build rpmdevtools curl unzip tar jq rsync git python3 \
glibc-devel kernel-headers libatomic file ca-certificates libicu \
&& install_ok = 1
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
mkdir -p " $HOME /.dotnet "
tmp_dotnet = " $( mktemp -d) "
curl -fL " $DOTNET_SDK_URL " -o " $tmp_dotnet / $DOTNET_RISCV_FILE "
tar -C " $HOME /.dotnet " -xzf " $tmp_dotnet / $DOTNET_RISCV_FILE "
rm -rf " $tmp_dotnet "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
export PATH = " $HOME /.dotnet: $PATH "
export DOTNET_ROOT = " $HOME /.dotnet "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
dotnet --info >/dev/null 2>& 1 || install_ok = 0
2026-04-15 12:50:27 +00:00
fi
2026-05-11 09:56:35 +00:00
if [ [ " $install_ok " -ne 1 ] ] ; then
echo " Could not auto-install dependencies for ' $OS_ID '. Make sure these are available: "
echo "dotnet-riscv SDK, curl, unzip, tar, rsync, git, python3, rpm, rpmdevtools, rpm-build (on Red Hat branch)"
exit 1
2026-04-15 12:50:27 +00:00
fi
}
2026-05-11 09:56:35 +00:00
prepare_workspace( ) {
SCRIPT_DIR = " $( cd -- " $( dirname -- " ${ BASH_SOURCE [0] } " ) " && pwd ) "
cd " $SCRIPT_DIR "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
if [ [ -f .gitmodules ] ] ; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
PROJECT = " $PROJECT_HINT "
[ [ -f " $PROJECT " ] ] || PROJECT = " $( find . -maxdepth 3 -name 'v2rayN.Desktop.csproj' | head -n1 || true ) "
[ [ -f " $PROJECT " ] ] || die "v2rayN.Desktop.csproj not found"
}
2026-04-15 12:50:27 +00:00
choose_channel( ) {
2026-05-11 09:56:35 +00:00
local ch = "latest"
local sel = ""
2026-04-15 12:50:27 +00:00
if [ [ -n " ${ BUILD_FROM :- } " ] ] ; then
case " $BUILD_FROM " in
2026-05-11 09:56:35 +00:00
1) echo "latest" ; return 0 ; ;
2) echo "prerelease" ; return 0 ; ;
3) echo "keep" ; return 0 ; ;
*) die " [ERROR] Invalid --buildfrom value: ${ BUILD_FROM } . Use 1|2|3. " ; ;
2026-04-15 12:50:27 +00:00
esac
fi
if [ [ -t 0 ] ] ; then
echo "[?] Choose v2rayN release channel:" >& 2
echo " 1) Latest (stable) [default]" >& 2
echo " 2) Pre-release (preview)" >& 2
echo " 3) Keep current (do nothing)" >& 2
printf "Enter 1, 2 or 3 [default 1]: " >& 2
if read -r sel </dev/tty; then
case " ${ sel :- } " in
2) ch = "prerelease" ; ;
3) ch = "keep" ; ;
esac
fi
fi
echo " $ch "
}
get_latest_tag_latest( ) {
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases/latest" \
| jq -re '.tag_name' \
| sed 's/^v//'
}
get_latest_tag_prerelease( ) {
curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20" \
| jq -re 'first(.[] | select(.prerelease == true) | .tag_name)' \
| sed 's/^v//'
}
2026-05-11 09:56:35 +00:00
sync_submodules( ) {
if [ [ -f .gitmodules ] ] ; then
git submodule sync --recursive || true
git submodule update --init --recursive || true
fi
}
2026-04-15 12:50:27 +00:00
git_try_checkout( ) {
2026-05-11 09:56:35 +00:00
local want = " $1 "
local ref = ""
2026-04-15 12:50:27 +00:00
if git rev-parse --git-dir >/dev/null 2>& 1; then
git fetch --tags --force --prune --depth= 1 || true
2026-05-11 09:56:35 +00:00
git rev-parse " refs/tags/ ${ want } " >/dev/null 2>& 1 && ref = " $want "
2026-04-15 12:50:27 +00:00
if [ [ -n " $ref " ] ] ; then
echo " [OK] Found ref ' ${ ref } ', checking out... "
2026-05-11 09:56:35 +00:00
git checkout -f " $ref "
sync_submodules
2026-04-15 12:50:27 +00:00
return 0
fi
fi
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
return 1
}
apply_channel_or_keep( ) {
2026-05-11 09:56:35 +00:00
local ch = " $1 "
local tag = ""
2026-04-15 12:50:27 +00:00
if [ [ " $ch " = = "keep" ] ] ; then
echo "[*] Keep current repository state (no checkout)."
VERSION = " $( git describe --tags --abbrev= 0 2>/dev/null || echo '0.0.0+git' ) "
VERSION = " ${ VERSION #v } "
return 0
fi
echo " [*] Resolving ${ ch } tag from GitHub releases... "
2026-05-11 09:56:35 +00:00
case " $ch " in
latest) tag = " $( get_latest_tag_latest || true ) " ; ;
prerelease) tag = " $( get_latest_tag_prerelease || true ) " ; ;
*) die " Failed to resolve latest tag for channel ' ${ ch } '. " ; ;
esac
[ [ -n " $tag " ] ] || die " Failed to resolve latest tag for channel ' ${ ch } '. "
2026-04-15 12:50:27 +00:00
echo " [*] Latest tag for ' ${ ch } ': ${ tag } "
2026-05-11 09:56:35 +00:00
git_try_checkout " $tag " || die " Failed to checkout ' ${ tag } '. "
2026-04-15 12:50:27 +00:00
VERSION = " ${ tag #v } "
}
2026-05-11 09:56:35 +00:00
resolve_version( ) {
if git rev-parse --git-dir >/dev/null 2>& 1; then
if [ [ -n " ${ VERSION_ARG :- } " ] ] ; then
local clean_ver = " ${ VERSION_ARG #v } "
if git_try_checkout " $clean_ver " ; then
VERSION = " $clean_ver "
else
echo " [WARN] Tag ' ${ VERSION_ARG } ' not found. "
apply_channel_or_keep " $( choose_channel) "
fi
2026-04-15 12:50:27 +00:00
else
2026-05-11 09:56:35 +00:00
apply_channel_or_keep " $( choose_channel) "
2026-04-15 12:50:27 +00:00
fi
else
2026-05-11 09:56:35 +00:00
echo "Current directory is not a git repo; proceeding on current tree."
VERSION = " ${ VERSION_ARG :- 0 .0.0 } "
fi
VERSION = " ${ VERSION #v } "
echo " [*] GUI version resolved as: ${ VERSION } "
}
xray_url_for_rid( ) {
local rid = " $1 "
local ver = " $2 "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
case " $rid " in
linux-riscv64) echo " https://github.com/XTLS/Xray-core/releases/download/v ${ ver } /Xray-linux-riscv64.zip " ; ;
*) return 1 ; ;
esac
}
singbox_url_for_rid( ) {
local rid = " $1 "
local ver = " $2 "
case " $rid " in
linux-riscv64) echo " https://github.com/SagerNet/sing-box/releases/download/v ${ ver } /sing-box- ${ ver } -linux-riscv64.tar.gz " ; ;
*) return 1 ; ;
esac
}
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
bundle_url_for_rid( ) {
local rid = " $1 "
case " $rid " in
linux-riscv64) echo "https://raw.githubusercontent.com/2dust/v2rayN-core-bin/refs/heads/master/v2rayN-linux-riscv64.zip" ; ;
*) return 1 ; ;
esac
}
2026-04-15 12:50:27 +00:00
download_xray( ) {
2026-05-11 09:56:35 +00:00
local outdir = " $1 "
local rid = " $2 "
local ver = " ${ XRAY_VER :- } "
local url = ""
local tmp = ""
2026-04-15 12:50:27 +00:00
mkdir -p " $outdir "
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
if [ [ -z " $ver " ] ] ; then
ver = " $( curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \
2026-05-11 09:56:35 +00:00
| grep -Eo '"tag_name":\s*"v[^"]+"' \
| sed -E 's/.*"v([^"]+)".*/\1/' \
| head -n1) " || true
2026-04-15 12:50:27 +00:00
fi
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
[ [ -n " $ver " ] ] || { echo "[xray] Failed to get version" ; return 1; }
2026-05-11 09:56:35 +00:00
url = " $( xray_url_for_rid " $rid " " $ver " ) " || { echo " [xray] Unsupported RID: $rid " ; return 1; }
2026-04-15 12:50:27 +00:00
echo " [+] Download xray: $url "
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
tmp = " $( mktemp -d) "
2026-05-11 09:56:35 +00:00
curl -fL " $url " -o " $tmp /xray.zip " || { rm -rf " $tmp " ; return 1; }
unzip -q " $tmp /xray.zip " -d " $tmp " || { rm -rf " $tmp " ; return 1; }
install -m 755 " $tmp /xray " " $outdir /xray " || { rm -rf " $tmp " ; return 1; }
2026-04-15 12:50:27 +00:00
rm -rf " $tmp "
}
download_singbox( ) {
2026-05-11 09:56:35 +00:00
local outdir = " $1 "
local rid = " $2 "
local ver = " ${ SING_VER :- } "
local url = ""
local tmp = ""
local bin = ""
local cronet = ""
2026-04-15 12:50:27 +00:00
mkdir -p " $outdir "
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
if [ [ -z " $ver " ] ] ; then
ver = " $( curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \
| grep -Eo '"tag_name":\s*"v[^"]+"' \
| sed -E 's/.*"v([^"]+)".*/\1/' \
| head -n1) " || true
fi
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
[ [ -n " $ver " ] ] || { echo "[sing-box] Failed to get version" ; return 1; }
2026-05-11 09:56:35 +00:00
url = " $( singbox_url_for_rid " $rid " " $ver " ) " || { echo " [sing-box] Unsupported RID: $rid " ; return 1; }
2026-04-15 12:50:27 +00:00
echo " [+] Download sing-box: $url "
2026-05-11 09:56:35 +00:00
2026-04-15 12:50:27 +00:00
tmp = " $( mktemp -d) "
2026-05-11 09:56:35 +00:00
curl -fL " $url " -o " $tmp /singbox.tar.gz " || { rm -rf " $tmp " ; return 1; }
tar -C " $tmp " -xzf " $tmp /singbox.tar.gz " || { rm -rf " $tmp " ; return 1; }
2026-04-15 12:50:27 +00:00
bin = " $( find " $tmp " -type f -name 'sing-box' | head -n1 || true ) "
[ [ -n " $bin " ] ] || { echo "[!] sing-box unpack failed" ; rm -rf " $tmp " ; return 1; }
2026-05-11 09:56:35 +00:00
install -m 755 " $bin " " $outdir /sing-box " || { rm -rf " $tmp " ; return 1; }
2026-04-15 12:50:27 +00:00
cronet = " $( find " $tmp " -type f -name 'libcronet*.so*' | head -n1 || true ) "
2026-05-11 09:56:35 +00:00
[ [ -n " $cronet " ] ] && install -m 644 " $cronet " " $outdir /libcronet.so " || true
2026-04-15 12:50:27 +00:00
rm -rf " $tmp "
}
unify_geo_layout( ) {
local outroot = " $1 "
2026-05-11 09:56:35 +00:00
local n
local names = (
geosite.dat
geoip.dat
geoip-only-cn-private.dat
Country.mmdb
geoip.metadb
2026-04-15 12:50:27 +00:00
)
2026-05-11 09:56:35 +00:00
mkdir -p " $outroot /bin "
2026-04-15 12:50:27 +00:00
for n in " ${ names [@] } " ; do
if [ [ -f " $outroot /bin/xray/ $n " ] ] ; then
mv -f " $outroot /bin/xray/ $n " " $outroot /bin/ $n "
fi
done
}
download_geo_assets( ) {
local outroot = " $1 "
local bin_dir = " $outroot /bin "
local srss_dir = " $bin_dir /srss "
2026-05-11 09:56:35 +00:00
local f = ""
2026-04-15 12:50:27 +00:00
mkdir -p " $bin_dir " " $srss_dir "
echo " [+] Download Xray Geo to ${ bin_dir } "
2026-05-11 09:56:35 +00:00
curl -fsSL -o " $bin_dir /geosite.dat " "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geosite.dat"
curl -fsSL -o " $bin_dir /geoip.dat " "https://github.com/Loyalsoldier/V2ray-rules-dat/releases/latest/download/geoip.dat"
curl -fsSL -o " $bin_dir /geoip-only-cn-private.dat " "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat"
curl -fsSL -o " $bin_dir /Country.mmdb " "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb"
2026-04-15 12:50:27 +00:00
echo "[+] Download sing-box rule DB & rule-sets"
2026-05-11 09:56:35 +00:00
curl -fsSL -o " $bin_dir /geoip.metadb " "https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.metadb"
for f in geoip-private.srs geoip-cn.srs geoip-facebook.srs geoip-fastly.srs geoip-google.srs geoip-netflix.srs geoip-telegram.srs geoip-twitter.srs; do
curl -fsSL -o " $srss_dir / $f " " https://raw.githubusercontent.com/2dust/sing-box-rules/refs/heads/rule-set-geoip/ $f "
2026-04-15 12:50:27 +00:00
done
2026-05-11 09:56:35 +00:00
for f in geosite-cn.srs geosite-gfw.srs geosite-google.srs geosite-greatfire.srs geosite-geolocation-cn.srs geosite-category-ads-all.srs geosite-private.srs; do
curl -fsSL -o " $srss_dir / $f " " https://raw.githubusercontent.com/2dust/sing-box-rules/refs/heads/rule-set-geosite/ $f "
2026-04-15 12:50:27 +00:00
done
unify_geo_layout " $outroot "
}
2026-05-11 09:56:35 +00:00
populate_assets_zip_mode( ) {
local outroot = " $1 "
local rid = " $2 "
2026-04-15 12:50:27 +00:00
local url = ""
2026-05-11 09:56:35 +00:00
local tmp = ""
local nested_dir = ""
url = " $( bundle_url_for_rid " $rid " ) " || { echo " [!] Bundle unsupported RID: $rid " ; return 1; }
2026-04-15 12:50:27 +00:00
echo " [+] Try v2rayN bundle archive: $url "
2026-05-11 09:56:35 +00:00
tmp = " $( mktemp -d) "
curl -fL " $url " -o " $tmp /v2rayn.zip " || { echo "[!] Bundle download failed" ; rm -rf " $tmp " ; return 1; }
unzip -q " $tmp /v2rayn.zip " -d " $tmp " || { echo "[!] Bundle unzip failed" ; rm -rf " $tmp " ; return 1; }
2026-04-15 12:50:27 +00:00
if [ [ -d " $tmp /bin " ] ] ; then
mkdir -p " $outroot /bin "
rsync -a " $tmp /bin/ " " $outroot /bin/ "
else
rsync -a " $tmp / " " $outroot / "
fi
rm -f " $outroot /v2rayn.zip " 2>/dev/null || true
find " $outroot " -type d -name "mihomo" -prune -exec rm -rf { } + 2>/dev/null || true
nested_dir = " $( find " $outroot " -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true ) "
if [ [ -n " $nested_dir " && -d " $nested_dir /bin " ] ] ; then
mkdir -p " $outroot /bin "
rsync -a " $nested_dir /bin/ " " $outroot /bin/ "
rm -rf " $nested_dir "
fi
unify_geo_layout " $outroot "
2026-05-11 09:56:35 +00:00
rm -rf " $tmp "
2026-04-15 12:50:27 +00:00
echo " [+] Bundle extracted to $outroot "
}
2026-05-11 09:56:35 +00:00
populate_assets_netcore_mode( ) {
local outroot = " $1 "
local rid = " $2 "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
mkdir -p " $outroot /bin/xray " " $outroot /bin/sing_box "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
if [ [ " $WITH_CORE " = = "xray" || " $WITH_CORE " = = "both" ] ] ; then
download_xray " $outroot /bin/xray " " $rid " || echo "[!] xray download failed (skipped)"
fi
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
if [ [ " $WITH_CORE " = = "sing-box" || " $WITH_CORE " = = "both" ] ] ; then
download_singbox " $outroot /bin/sing_box " " $rid " || echo "[!] sing-box download failed (skipped)"
fi
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
download_geo_assets " $outroot " || echo "[!] Geo rules download failed (skipped)"
}
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
stage_runtime_assets( ) {
local outroot = " $1 "
local rid = " $2 "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
mkdir -p " $outroot /bin/xray " " $outroot /bin/sing_box "
2026-04-15 12:50:27 +00:00
if [ [ " $FORCE_NETCORE " -eq 0 ] ] ; then
2026-05-11 09:56:35 +00:00
if populate_assets_zip_mode " $outroot " " $rid " ; then
2026-04-15 12:50:27 +00:00
echo "[*] Using v2rayN bundle archive."
else
echo "[*] Bundle failed, fallback to separate core + rules."
2026-05-11 09:56:35 +00:00
populate_assets_netcore_mode " $outroot " " $rid "
2026-04-15 12:50:27 +00:00
fi
else
echo "[*] --netcore specified: use separate core + rules."
2026-05-11 09:56:35 +00:00
populate_assets_netcore_mode " $outroot " " $rid "
2026-04-15 12:50:27 +00:00
fi
2026-05-11 09:56:35 +00:00
}
describe_target( ) {
local short = " $1 "
case " $short " in
riscv64) printf '%s\n%s\n%s\n' "linux-riscv64" "riscv64" "riscv64" ; ;
*) echo " Unknown arch ' $short ' (use riscv64) " >& 2; return 1 ; ;
esac
}
publish_binary( ) {
local rid = " $1 "
2026-05-15 07:09:04 +00:00
dotnet clean " $PROJECT " -c Release
rm -rf " $( dirname " $PROJECT " ) /bin/Release/net10.0 " || true
dotnet restore " $PROJECT "
dotnet publish " $PROJECT " -c Release -r " $rid " -p:PublishSingleFile= false -p:SelfContained= true
2026-05-11 09:56:35 +00:00
}
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
write_spec_file( ) {
local specfile = " $1 "
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
cat > " $specfile " <<'SPEC'
2026-04-15 12:50:27 +00:00
%global debug_package %{ nil}
%undefine _debuginfo_subpackages
%undefine _debugsource_packages
%global __requires_exclude ^liblttng-ust\. so\. .*$
Name: v2rayN
Version: __VERSION__
Release: 1%{ ?dist}
Summary: v2rayN ( Avalonia) GUI client for Linux ( riscv64)
License: GPL-3.0-only
URL: https://github.com/2dust/v2rayN
BugURL: https://github.com/2dust/v2rayN/issues
ExclusiveArch: riscv64
Source0: __PKGROOT__.tar.gz
Requires: cairo, pango, openssl, mesa-libEGL, mesa-libGL
Requires: glibc >= 2.34
Requires: fontconfig >= 2.13.1
Requires: desktop-file-utils >= 0.26
Requires: xdg-utils >= 1.1.3
Requires: coreutils >= 8.32
Requires: bash >= 5.1
Requires: freetype >= 2.10
%description
v2rayN Linux for Red Hat Enterprise Linux
Support vless / vmess / Trojan / http / socks / Anytls / Hysteria2 / Shadowsocks / tuic / WireGuard
Support Red Hat Enterprise Linux / Fedora Linux / Rocky Linux / AlmaLinux / CentOS
For more information, Please visit our website
https://github.com/2dust/v2rayN
%prep
%setup -q -n __PKGROOT__
%build
%install
install -dm0755 %{ buildroot} /opt/v2rayN
cp -a * %{ buildroot} /opt/v2rayN/
find %{ buildroot} /opt/v2rayN -type d -exec chmod 0755 { } +
find %{ buildroot} /opt/v2rayN -type f -exec chmod 0644 { } +
[ -f %{ buildroot} /opt/v2rayN/v2rayN ] && chmod 0755 %{ buildroot} /opt/v2rayN/v2rayN || :
install -dm0755 %{ buildroot} %{ _bindir}
install -m0755 /dev/stdin %{ buildroot} %{ _bindir} /v2rayn << 'EOF'
#!/usr/bin/bash
set -euo pipefail
DIR = "/opt/v2rayN"
if [ [ -x " $DIR /v2rayN " ] ] ; then exec " $DIR /v2rayN " " $@ " ; fi
for dll in v2rayN.Desktop.dll v2rayN.dll; do
if [ [ -f " $DIR / $dll " ] ] ; then exec /usr/bin/dotnet " $DIR / $dll " " $@ " ; fi
done
echo " v2rayN launcher: no executable found in $DIR " >& 2
ls -l " $DIR " >& 2 || true
exit 1
EOF
install -dm0755 %{ buildroot} %{ _datadir} /applications
install -m0644 /dev/stdin %{ buildroot} %{ _datadir} /applications/v2rayn.desktop << 'EOF'
[ Desktop Entry]
Type = Application
Name = v2rayN
Comment = v2rayN for Red Hat Enterprise Linux
Exec = v2rayn
Icon = v2rayn
Terminal = false
Categories = Network;
EOF
install -dm0755 %{ buildroot} %{ _datadir} /icons/hicolor/256x256/apps
install -m0644 %{ _builddir} /__PKGROOT__/v2rayn.png %{ buildroot} %{ _datadir} /icons/hicolor/256x256/apps/v2rayn.png
%post
/usr/bin/update-desktop-database %{ _datadir} /applications >/dev/null 2>& 1 || true
/usr/bin/gtk-update-icon-cache -f %{ _datadir} /icons/hicolor >/dev/null 2>& 1 || true
%postun
/usr/bin/update-desktop-database %{ _datadir} /applications >/dev/null 2>& 1 || true
/usr/bin/gtk-update-icon-cache -f %{ _datadir} /icons/hicolor >/dev/null 2>& 1 || true
%files
%{ _bindir} /v2rayn
/opt/v2rayN
%{ _datadir} /applications/v2rayn.desktop
%{ _datadir} /icons/hicolor/256x256/apps/v2rayn.png
SPEC
2026-05-11 09:56:35 +00:00
sed -i " s/__VERSION__/ ${ VERSION } /g " " $specfile "
sed -i " s/__PKGROOT__/ ${ PKGROOT } /g " " $specfile "
}
package_binary( ) {
local short = " $1 "
local rid = " $2 "
local rpm_target = " $3 "
local archdir = " $4 "
local pubdir = ""
local workdir = ""
local specfile = ""
local sourcedir = ""
local specdir = ""
local project_dir = ""
local icon_candidate = ""
local f = ""
2026-05-15 07:09:04 +00:00
pubdir = " $( dirname " $PROJECT " ) /bin/Release/net10.0/ ${ rid } /publish "
2026-05-11 09:56:35 +00:00
[ [ -d " $pubdir " ] ] || { echo " Publish directory not found: $pubdir " ; return 1; }
workdir = " $( mktemp -d) "
trap '[[ -n "${workdir:-}" ]] && rm -rf "$workdir"' RETURN
mkdir -p " $workdir / $PKGROOT "
cp -a " $pubdir /. " " $workdir / $PKGROOT / "
project_dir = " $( cd " $( dirname " $PROJECT " ) " && pwd ) "
icon_candidate = " $project_dir /v2rayN.png "
[ [ -f " $icon_candidate " ] ] || { echo " Required icon not found: $icon_candidate " ; return 1; }
cp " $icon_candidate " " $workdir / $PKGROOT /v2rayn.png "
stage_runtime_assets " $workdir / $PKGROOT " " $rid "
rpmdev-setuptree
sourcedir = " ${ RPM_TOPDIR } /SOURCES "
specdir = " ${ RPM_TOPDIR } /SPECS "
specfile = " ${ specdir } /v2rayN.spec "
mkdir -p " $sourcedir " " $specdir "
tar -C " $workdir " -czf " $sourcedir / $PKGROOT .tar.gz " " $PKGROOT "
write_spec_file " $specfile "
rpmbuild -ba " $specfile " --target " $rpm_target "
2026-04-15 12:50:27 +00:00
echo " Build done for $short . RPM at: "
2026-05-11 09:56:35 +00:00
for f in " ${ RPM_TOPDIR } /RPMS/ ${ archdir } /v2rayN- ${ VERSION } -1 " *.rpm; do
2026-04-15 12:50:27 +00:00
[ [ -e " $f " ] ] || continue
echo " $f "
BUILT_RPMS += ( " $f " )
done
}
2026-05-11 09:56:35 +00:00
select_targets( ) {
printf '%s\n' riscv64
}
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
build_one_target( ) {
local short = " $1 "
local meta = ( )
local rid = ""
local rpm_target = ""
local archdir = ""
2026-04-15 12:50:27 +00:00
2026-05-11 09:56:35 +00:00
mapfile -t meta < <( describe_target " $short " ) || return 1
rid = " ${ meta [0] } "
rpm_target = " ${ meta [1] } "
archdir = " ${ meta [2] } "
echo " [*] Building for target: $short (RID= $rid , RPM --target $rpm_target ) "
publish_binary " $rid "
package_binary " $short " " $rid " " $rpm_target " " $archdir "
}
print_summary( ) {
local rp = ""
echo ""
echo "================ Build Summary ================"
if [ [ " ${# BUILT_RPMS [@] } " -gt 0 ] ] ; then
for rp in " ${ BUILT_RPMS [@] } " ; do
echo " $rp "
done
else
echo "No RPMs detected in summary (check build logs above)."
fi
echo "=============================================="
}
main( ) {
local targets = ( )
local arch = ""
parse_args " $@ "
detect_environment
install_dependencies
prepare_workspace
resolve_version
mapfile -t targets < <( select_targets)
for arch in " ${ targets [@] } " ; do
build_one_target " $arch "
2026-04-15 12:50:27 +00:00
done
2026-05-11 09:56:35 +00:00
print_summary
}
2026-05-14 11:27:31 +00:00
main " $@ "