update pack

This commit is contained in:
mhsanaei
2025-09-13 15:07:00 +02:00
parent 5d07744c41
commit 7d9f01a621
23 changed files with 520 additions and 185 deletions

View File

@@ -11,10 +11,8 @@ const Protocols = {
const SSMethods = {
AES_256_GCM: 'aes-256-gcm',
AES_128_GCM: 'aes-128-gcm',
CHACHA20_POLY1305: 'chacha20-poly1305',
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
XCHACHA20_POLY1305: 'xchacha20-poly1305',
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
@@ -1139,8 +1137,8 @@ class Inbound extends XrayCommonClass {
get isSSMultiUser() {
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
}
get isSS2022(){
return this.method.substring(0,4) === "2022";
get isSS2022() {
return this.method.substring(0, 4) === "2022";
}
get serverName() {
@@ -1307,6 +1305,7 @@ class Inbound extends XrayCommonClass {
const security = forceTls == 'same' ? this.stream.security : forceTls;
const params = new Map();
params.set("type", this.stream.network);
params.set("encryption", this.settings.encryption);
switch (type) {
case "tcp":
const tcp = this.stream.tcp;
@@ -1748,7 +1747,7 @@ Inbound.Settings = class extends XrayCommonClass {
Inbound.VmessSettings = class extends Inbound.Settings {
constructor(protocol,
vmesses=[new Inbound.VmessSettings.Vmess()]) {
vmesses = [new Inbound.VmessSettings.Vmess()]) {
super(protocol);
this.vmesses = vmesses;
}
@@ -1771,7 +1770,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
}
}
static fromJson(json={}) {
static fromJson(json = {}) {
return new Inbound.VmessSettings(
Protocols.VMESS,
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
@@ -1853,13 +1852,17 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
constructor(
protocol,
vlesses = [new Inbound.VLESSSettings.VLESS()],
decryption = 'none',
fallbacks = []
decryption = "none",
encryption = "none",
fallbacks = [],
selectedAuth = undefined,
) {
super(protocol);
this.vlesses = vlesses;
this.decryption = decryption;
this.encryption = encryption;
this.fallbacks = fallbacks;
this.selectedAuth = selectedAuth;
}
addFallback() {
@@ -1870,21 +1873,40 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
this.fallbacks.splice(index, 1);
}
// decryption should be set to static value
static fromJson(json = {}) {
return new Inbound.VLESSSettings(
const obj = new Inbound.VLESSSettings(
Protocols.VLESS,
json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
json.decryption || 'none',
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),);
(json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
json.decryption,
json.encryption,
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []),
json.selectedAuth
);
return obj;
}
toJson() {
return {
const json = {
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
decryption: this.decryption,
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
};
if (this.decryption) {
json.decryption = this.decryption;
}
if (this.encryption) {
json.encryption = this.encryption;
}
if (this.fallbacks && this.fallbacks.length > 0) {
json.fallbacks = Inbound.VLESSSettings.toJsonArray(this.fallbacks);
}
if (this.selectedAuth) {
json.selectedAuth = this.selectedAuth;
}
return json;
}
};
@@ -1952,7 +1974,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
}
};
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
constructor(name='', alpn='', path='', dest='', xver=0) {
constructor(name = '', alpn = '', path = '', dest = '', xver = 0) {
super();
this.name = name;
this.alpn = alpn;
@@ -2098,7 +2120,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
};
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
constructor(name='', alpn='', path='', dest='', xver=0) {
constructor(name = '', alpn = '', path = '', dest = '', xver = 0) {
super();
this.name = name;
this.alpn = alpn;
@@ -2404,20 +2426,20 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
super(protocol);
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.pubKey = secretKey.length > 0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
this.peers.push(new Inbound.WireguardSettings.Peer(null, null, '', ['10.0.0.' + (this.peers.length + 2)]));
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
static fromJson(json = {}) {
return new Inbound.WireguardSettings(
Protocols.WIREGUARD,
json.mtu,
@@ -2429,7 +2451,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
toJson() {
return {
mtu: this.mtu?? undefined,
mtu: this.mtu ?? undefined,
secretKey: this.secretKey,
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
@@ -2438,16 +2460,16 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
constructor(privateKey, publicKey, psk = '', allowedIPs = ['10.0.0.2/32'], keepAlive = 0) {
super();
this.privateKey = privateKey
this.publicKey = publicKey;
if (!this.publicKey){
if (!this.publicKey) {
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
}
this.psk = psk;
allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
allowedIPs.forEach((a, index) => {
if (a.length > 0 && !a.includes('/')) allowedIPs[index] += '/32';
})
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;

View File

@@ -13,10 +13,8 @@ const Protocols = {
const SSMethods = {
AES_256_GCM: 'aes-256-gcm',
AES_128_GCM: 'aes-128-gcm',
CHACHA20_POLY1305: 'chacha20-poly1305',
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
XCHACHA20_POLY1305: 'xchacha20-poly1305',
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
@@ -813,7 +811,7 @@ class Outbound extends CommonClass {
var settings;
switch (protocol) {
case Protocols.VLESS:
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '');
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '', url.searchParams.get('encryption') ?? 'none');
break;
case Protocols.Trojan:
settings = new Outbound.TrojanSettings(address, port, userData);
@@ -1039,13 +1037,13 @@ Outbound.VmessSettings = class extends CommonClass {
}
};
Outbound.VLESSSettings = class extends CommonClass {
constructor(address, port, id, flow, encryption = 'none') {
constructor(address, port, id, flow, encryption) {
super();
this.address = address;
this.port = port;
this.id = id;
this.flow = flow;
this.encryption = encryption
this.encryption = encryption;
}
static fromJson(json = {}) {
@@ -1064,7 +1062,7 @@ Outbound.VLESSSettings = class extends CommonClass {
vnext: [{
address: this.address,
port: this.port,
users: [{ id: this.id, flow: this.flow, encryption: 'none', }],
users: [{ id: this.id, flow: this.flow, encryption: this.encryption }],
}],
};
}

View File

@@ -39,6 +39,9 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g = g.Group("/server")
g.Use(a.checkLogin)
g.GET("/getDb", a.getDb)
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
g.POST("/status", a.status)
g.POST("/getXrayVersion", a.getXrayVersion)
g.POST("/stopXrayService", a.stopXrayService)
@@ -46,7 +49,6 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/installXray/:version", a.installXray)
g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
g.POST("/importDB", a.importDB)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
g.POST("/getNewmldsa65", a.getNewmldsa65)
@@ -207,3 +209,12 @@ func (a *ServerController) getNewEchCert(c *gin.Context) {
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewVlessEnc(c *gin.Context) {
out, err := a.serverService.GetNewVlessEnc()
if err != nil {
jsonMsg(c, "Failed to generate vless encryption config", err)
return
}
jsonObj(c, out, nil)
}

View File

@@ -211,6 +211,11 @@
<a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item>
<!-- vless settings -->
<template v-if="outbound.protocol === Protocols.VLESS">
<a-form-item label='encryption'>
<a-input v-model.trim="outbound.settings.encryption"></a-input>
</a-form-item>
</template>
<template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -422,6 +427,9 @@
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ECH Config List">
<a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>

View File

@@ -20,7 +20,29 @@
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality">
<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Authentication">
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option>
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="decryption">
<a-input v-model.trim="inbound.settings.decryption"></a-input>
</a-form-item>
<a-form-item label="encryption">
<a-input v-model="inbound.settings.encryption"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-space>
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button>
<a-button danger @click="clearVlessEnc">Clear</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks">
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>

View File

@@ -40,7 +40,10 @@
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
<a-space>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
<a-button danger @click="clearX25519Cert">Clear</a-button>
</a-space>
</a-form-item>
<a-form-item label="mldsa65 Seed">
<a-input v-model="inbound.stream.reality.mldsa65Seed"></a-input>
@@ -49,7 +52,10 @@
<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-space>
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
<a-button danger @click="clearMldsa65">Clear</a-button>
</a-space>
</a-form-item>
</template>
{{end}}

View File

@@ -94,13 +94,15 @@
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
</a-form-item>
<a-form-item label='ECH force query'>
<a-select v-model="inbound.stream.tls.echForceQuery"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select v-model="inbound.stream.tls.echForceQuery" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
<a-space>
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
<a-button danger @click="clearEchCert">Clear</a-button>
</a-space>
</a-form-item>
</template>

View File

@@ -71,9 +71,18 @@
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
<br />
<td>Authentication</td>
<a-tag :color="inbound.settings.selectedAuth ? 'green' : 'red'">[[ inbound.settings.selectedAuth ?
inbound.settings.selectedAuth : '' ]]</a-tag>
<br />
{{ i18n "encryption" }}
<a-tag :color="inbound.settings.encryption ? 'green' : 'red'">[[ inbound.settings.encryption ?
inbound.settings.encryption : '' ]]</a-tag>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : ''
]]</a-tag>
</template>
</template>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">

View File

@@ -132,6 +132,10 @@
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
},
clearX25519Cert() {
this.inbound.stream.reality.privateKey = '';
this.inbound.stream.reality.settings.publicKey = '';
},
async getNewmldsa65() {
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewmldsa65');
@@ -142,6 +146,10 @@
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
},
clearMldsa65() {
this.inbound.stream.reality.mldsa65Seed = '';
this.inbound.stream.reality.settings.mldsa65Verify = '';
},
async getNewEchCert() {
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
@@ -152,6 +160,36 @@
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
},
clearEchCert() {
this.inbound.stream.tls.echServerKeys = '';
this.inbound.stream.tls.settings.echConfigList = '';
},
async getNewVlessEnc() {
inModal.loading(true);
const msg = await HttpUtil.get('/server/getNewVlessEnc');
inModal.loading(false);
if (!msg.success) {
return;
}
const auths = msg.obj.auths || [];
const selected = inModal.inbound.settings.selectedAuth;
const block = auths.find(a => a.label === selected);
if (!block) {
console.error("No auth block for", selected);
return;
}
inModal.inbound.settings.decryption = block.decryption;
inModal.inbound.settings.encryption = block.encryption;
},
clearVlessEnc() {
this.inbound.settings.decryption = 'none';
this.inbound.settings.encryption = 'none';
this.inbound.settings.selectedAuth = undefined;
}
},
});

View File

@@ -11,6 +11,7 @@ import (
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
@@ -279,6 +280,8 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
switch osName {
case "darwin":
osName = "macos"
case "windows":
osName = "windows"
}
switch arch {
@@ -322,19 +325,23 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
}
func (s *ServerService) UpdateXray(version string) error {
// 1. Stop xray before doing anything
if err := s.StopXrayService(); err != nil {
logger.Warning("failed to stop xray before update:", err)
}
// 2. Download the zip
zipFileName, err := s.downloadXRay(version)
if err != nil {
return err
}
defer os.Remove(zipFileName)
zipFile, err := os.Open(zipFileName)
if err != nil {
return err
}
defer func() {
zipFile.Close()
os.Remove(zipFileName)
}()
defer zipFile.Close()
stat, err := zipFile.Stat()
if err != nil {
@@ -345,19 +352,14 @@ func (s *ServerService) UpdateXray(version string) error {
return err
}
s.xrayService.StopXray()
defer func() {
err := s.xrayService.RestartXray(true)
if err != nil {
logger.Error("start xray failed:", err)
}
}()
// 3. Helper to extract files
copyZipFile := func(zipName string, fileName string) error {
zipFile, err := reader.Open(zipName)
if err != nil {
return err
}
defer zipFile.Close()
os.MkdirAll(filepath.Dir(fileName), 0755)
os.Remove(fileName)
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
if err != nil {
@@ -368,11 +370,23 @@ func (s *ServerService) UpdateXray(version string) error {
return err
}
err = copyZipFile("xray", xray.GetBinaryPath())
// 4. Extract correct binary
if runtime.GOOS == "windows" {
targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
err = copyZipFile("xray.exe", targetBinary)
} else {
err = copyZipFile("xray", xray.GetBinaryPath())
}
if err != nil {
return err
}
// 5. Restart xray
if err := s.xrayService.RestartXray(true); err != nil {
logger.Error("start xray failed:", err)
return err
}
return nil
}
@@ -614,3 +628,43 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
"echConfigList": configList,
}, nil
}
func (s *ServerService) GetNewVlessEnc() (any, error) {
cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
var auths []map[string]string
var current map[string]string
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Authentication:") {
if current != nil {
auths = append(auths, current)
}
current = map[string]string{
"label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")),
}
} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 && current != nil {
key := strings.Trim(parts[0], `" `)
val := strings.Trim(parts[1], `" `)
current[key] = val
}
}
}
if current != nil {
auths = append(auths, current)
}
return map[string]any{
"auths": auths,
}, nil
}

View File

@@ -3,6 +3,7 @@ package service
import (
"encoding/json"
"errors"
"runtime"
"sync"
"x-ui/logger"
@@ -32,7 +33,16 @@ func (s *XrayService) GetXrayErr() error {
if p == nil {
return nil
}
return p.GetErr()
err := p.GetErr()
if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return nil
}
return err
}
func (s *XrayService) GetXrayResult() string {
@@ -45,7 +55,15 @@ func (s *XrayService) GetXrayResult() string {
if p == nil {
return ""
}
result = p.GetResult()
if runtime.GOOS == "windows" && result == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return ""
}
return result
}