Merge pull request #114 from xeefei/3x-ui

3X UI
This commit is contained in:
心隨緣動 2025-07-28 03:16:14 +08:00 committed by GitHub
commit fbeb480f8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 116 additions and 26 deletions

View File

@ -97,7 +97,7 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.7.24/"
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.7.26/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip

View File

@ -27,7 +27,7 @@ case $1 in
esac
mkdir -p build/bin
cd build/bin
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.7.24/Xray-linux-${ARCH}.zip"
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.7.26/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}"

5
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/shirou/gopsutil/v4 v4.25.6
github.com/valyala/fasthttp v1.63.0
github.com/xlzd/gotp v0.1.0
github.com/xtls/xray-core v1.250608.1-0.20250724021908-4f45c5faa5f1
github.com/xtls/xray-core v1.250726.0
go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.40.0
golang.org/x/text v0.27.0
@ -58,6 +58,7 @@ require (
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/miekg/dns v1.1.67 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
@ -80,7 +81,7 @@ require (
github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/xtls/reality v0.0.0-20250723121014-c6320729d93b // indirect
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.2 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect

10
go.sum
View File

@ -1,7 +1,5 @@
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
@ -183,10 +181,10 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/xtls/reality v0.0.0-20250723121014-c6320729d93b h1:HOOsQYu7/EzvpegY7uHiaeI9H/6OsHAOkREnJthdUW8=
github.com/xtls/reality v0.0.0-20250723121014-c6320729d93b/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
github.com/xtls/xray-core v1.250608.1-0.20250724021908-4f45c5faa5f1 h1:5YFdWAwmW/pB7aMltnUmsRC8QY37817Q2m9WqVUVxPU=
github.com/xtls/xray-core v1.250608.1-0.20250724021908-4f45c5faa5f1/go.mod h1:dqSs+9mmzvOZRFxTaS0ktkQYG8TD9naApQPdnp/MUzA=
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
github.com/xtls/xray-core v1.250726.0 h1:uTUHUt/CQ1JQLip1pLkiwoS0pMvl6oCHJgur4M4orWQ=
github.com/xtls/xray-core v1.250726.0/go.mod h1:z2vn2o30flYEgpSz1iEhdZP1I46UZ3+gXINZyohH3yE=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=

View File

@ -7,6 +7,7 @@ yellow='\033[0;33m'
plain='\033[0m'
cur_dir=$(pwd)
show_ip_service_lists=("https://api.ipify.org" "https://4.ident.me")
# check root
[[ $EUID -ne 0 ]] && echo -e "${red}致命错误: ${plain} 请使用 root 权限运行此脚本\n" && exit 1
@ -228,6 +229,7 @@ echo ""
install_x-ui() {
cd /usr/local/
# Download resources
if [ $# == 0 ]; then
last_version=$(curl -Ls "https://api.github.com/repos/xeefei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ ! -n "$last_version" ]]; then
@ -272,7 +274,9 @@ install_x-ui() {
exit 1
fi
fi
wget -O /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
# Stop x-ui service and remove old resources
if [[ -e /usr/local/x-ui/ ]]; then
systemctl stop x-ui
rm /usr/local/x-ui/ -rf
@ -283,19 +287,20 @@ install_x-ui() {
echo ""
tar zxvf x-ui-linux-$(arch).tar.gz
rm x-ui-linux-$(arch).tar.gz -f
cd x-ui
chmod +x x-ui
chmod +x x-ui.sh
# Check the system's architecture and rename the file accordingly
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
mv bin/xray-linux-$(arch) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
chmod +x x-ui bin/xray-linux-$(arch)
cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/xeefei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh
# Update x-ui cli and se set permission
mv -f /usr/bin/x-ui-temp /usr/bin/x-ui
chmod +x /usr/bin/x-ui
sleep 2
echo -e "${green}------->>>>>>>>>>>保存成功${plain}"
@ -303,6 +308,7 @@ install_x-ui() {
echo ""
config_after_install
cp -f x-ui.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable x-ui
systemctl start x-ui

View File

@ -263,6 +263,7 @@ func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"]
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
// Set random data
rltyData["spiderX"] = "/" + random.Seq(15)

View File

@ -732,6 +732,7 @@ class RealityStreamSettings extends XrayCommonClass {
maxClient = '',
maxTimediff = 0,
shortIds = RandomUtil.randomShortIds(),
mldsa65Seed = '',
settings = new RealityStreamSettings.Settings()
) {
super();
@ -744,6 +745,7 @@ class RealityStreamSettings extends XrayCommonClass {
this.maxClient = maxClient;
this.maxTimediff = maxTimediff;
this.shortIds = Array.isArray(shortIds) ? shortIds.join(",") : shortIds;
this.mldsa65Seed = mldsa65Seed;
this.settings = settings;
}
@ -754,7 +756,8 @@ class RealityStreamSettings extends XrayCommonClass {
json.settings.publicKey,
json.settings.fingerprint,
json.settings.serverName,
json.settings.spiderX
json.settings.spiderX,
json.settings.mldsa65Verify,
);
}
return new RealityStreamSettings(
@ -767,6 +770,7 @@ class RealityStreamSettings extends XrayCommonClass {
json.maxClient,
json.maxTimediff,
json.shortIds,
json.mldsa65Seed,
settings,
);
}
@ -782,6 +786,7 @@ class RealityStreamSettings extends XrayCommonClass {
maxClient: this.maxClient,
maxTimediff: this.maxTimediff,
shortIds: this.shortIds.split(","),
mldsa65Seed: this.mldsa65Seed,
settings: this.settings,
};
}
@ -792,13 +797,15 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
publicKey = '',
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
serverName = '',
spiderX = '/'
spiderX = '/',
mldsa65Verify = ''
) {
super();
this.publicKey = publicKey;
this.fingerprint = fingerprint;
this.serverName = serverName;
this.spiderX = spiderX;
this.mldsa65Verify = mldsa65Verify;
}
static fromJson(json = {}) {
return new RealityStreamSettings.Settings(
@ -806,6 +813,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
json.fingerprint,
json.serverName,
json.spiderX,
json.mldsa65Verify
);
}
toJson() {
@ -814,6 +822,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
fingerprint: this.fingerprint,
serverName: this.serverName,
spiderX: this.spiderX,
mldsa65Verify: this.mldsa65Verify
};
}
};

View File

@ -50,6 +50,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.GET("/getDb", a.getDb)
g.POST("/importDB", a.importDB)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
g.POST("/getNewmldsa65", a.getNewmldsa65)
}
func (a *ServerController) refreshStatus() {
@ -198,3 +199,12 @@ func (a *ServerController) getNewX25519Cert(c *gin.Context) {
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewmldsa65(c *gin.Context) {
cert, err := a.serverService.GetNewmldsa65()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewmldsa65Error"), err)
return
}
jsonObj(c, cert, nil)
}

View File

@ -52,5 +52,14 @@
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert随机获取新证书</a-button>
</a-form-item>
<a-form-item label="mldsa65 Seed">
<a-input v-model="inbound.stream.reality.mldsa65Seed"></a-input>
</a-form-item>
<a-form-item label="mldsa65 Verify">
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
</a-form-item>
</template>
{{end}}

View File

@ -141,7 +141,17 @@
}
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
}
},
async getNewmldsa65() {
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewmldsa65');
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
},
},
});

View File

@ -234,23 +234,31 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
}
// IP fetching with caching
showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"}
showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"}
if s.cachedIPv4 == "" {
s.cachedIPv4 = getPublicIP("https://api.ipify.org")
if s.cachedIPv4 == "N/A" {
s.cachedIPv4 = getPublicIP("https://4.ident.me")
for _, ip4Service := range showIp4ServiceLists {
s.cachedIPv4 = getPublicIP(ip4Service)
if s.cachedIPv4 != "N/A" {
break
}
}
}
if s.cachedIPv6 == "" && !s.noIPv6 {
s.cachedIPv6 = getPublicIP("https://api6.ipify.org")
if s.cachedIPv6 == "N/A" {
s.cachedIPv6 = getPublicIP("https://6.ident.me")
if s.cachedIPv6 == "N/A" {
s.noIPv6 = true
for _, ip6Service := range showIp6ServiceLists {
s.cachedIPv6 = getPublicIP(ip6Service)
if s.cachedIPv6 != "N/A" {
break
}
}
}
if s.cachedIPv6 == "N/A" {
s.noIPv6 = true
}
status.PublicIP.IPv4 = s.cachedIPv4
status.PublicIP.IPv6 = s.cachedIPv6
@ -709,3 +717,29 @@ func (s *ServerService) GetNewX25519Cert() (any, error) {
return keyPair, nil
}
func (s *ServerService) GetNewmldsa65() (any, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
SeedLine := strings.Split(lines[0], ":")
VerifyLine := strings.Split(lines[1], ":")
seed := strings.TrimSpace(SeedLine[1])
verify := strings.TrimSpace(VerifyLine[1])
keyPair := map[string]any{
"seed": seed,
"verify": verify,
}
return keyPair, nil
}

View File

@ -260,6 +260,7 @@
"resetInboundClientTrafficSuccess" = "تم إعادة تعيين حركة المرور"
"trafficGetError" = "خطأ في الحصول على حركات المرور"
"getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519."
"getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65."
[pages.inbounds.stream.general]
"request" = "طلب"

View File

@ -259,7 +259,7 @@
"resetInboundClientTrafficSuccess" = "Traffic has been reset."
"trafficGetError" = "Error getting traffics."
"getNewX25519CertError" = "Error while obtaining the X25519 certificate."
"getNewmldsa65Error" = "Error while obtaining mldsa65."
[pages.inbounds.stream.general]
"request" = "Request"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "El tráfico ha sido reiniciado"
"trafficGetError" = "Error al obtener los tráficos"
"getNewX25519CertError" = "Error al obtener el certificado X25519."
"getNewmldsa65Error" = "Error al obtener el certificado mldsa65."
[pages.inbounds.stream.general]
"request" = "Pedido"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "ترافیک بازنشانی شد"
"trafficGetError" = "خطا در دریافت ترافیک‌ها"
"getNewX25519CertError" = "خطا در دریافت گواهی X25519."
"getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65."
[pages.inbounds.stream.general]
"request" = "درخواست"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "Lalu lintas telah direset"
"trafficGetError" = "Gagal mendapatkan data lalu lintas"
"getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519."
"getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65."
[pages.inbounds.stream.general]
"request" = "Permintaan"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "トラフィックがリセットされました"
"trafficGetError" = "トラフィックの取得中にエラーが発生しました"
"getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。"
"getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。"
[pages.inbounds.stream.general]
"request" = "リクエスト"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "O tráfego foi reiniciado"
"trafficGetError" = "Erro ao obter tráfegos"
"getNewX25519CertError" = "Erro ao obter o certificado X25519."
"getNewmldsa65Error" = "Erro ao obter o certificado mldsa65."
[pages.inbounds.stream.general]
"request" = "Requisição"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "Трафик сброшен"
"trafficGetError" = "Ошибка получения данных о трафике"
"getNewX25519CertError" = "Ошибка при получении сертификата X25519."
"getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65."
[pages.inbounds.stream.general]
"request" = "Запрос"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "Trafik sıfırlandı"
"trafficGetError" = "Trafik bilgisi alınırken hata oluştu"
"getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu."
"getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu."
[pages.inbounds.stream.general]
"request" = "İstek"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "Трафік скинуто"
"trafficGetError" = "Помилка отримання даних про трафік"
"getNewX25519CertError" = "Помилка при отриманні сертифіката X25519."
"getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65."
[pages.inbounds.stream.general]
"request" = "Запит"

View File

@ -262,6 +262,7 @@
"resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng"
"trafficGetError" = "Lỗi khi lấy thông tin lưu lượng"
"getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519."
"getNewmldsa65Error" = "Lỗi khi lấy chúng tôi mldsa65."
[pages.inbounds.stream.general]
"request" = "Lời yêu cầu"

View File

@ -267,6 +267,7 @@
"resetInboundClientTrafficSuccess" = "流量已重置"
"trafficGetError" = "获取流量数据时出错"
"getNewX25519CertError" = "获取X25519证书时出错。"
"getNewmldsa65Error" = "获取mldsa65证书时出错。"
[pages.inbounds.stream.general]
"request" = "请求"

View File

@ -264,6 +264,7 @@
"resetInboundClientTrafficSuccess" = "流量已重置"
"trafficGetError" = "取得流量資料時發生錯誤"
"getNewX25519CertError" = "取得X25519憑證時發生錯誤。"
"getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。"
[pages.inbounds.stream.general]
"request" = "請求"