mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-18 14:55:49 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d14f381c2 | ||
|
|
088dd2e881 | ||
|
|
5909955f5d | ||
|
|
a3df597b5d | ||
|
|
49e43a2079 | ||
|
|
95afd3006a | ||
|
|
bcea56283f | ||
|
|
e70fb3daf5 | ||
|
|
322a74429d | ||
|
|
5b6daf9e70 | ||
|
|
e5184b81be | ||
|
|
35798c2c74 | ||
|
|
6781b0f7ae | ||
|
|
9d93468332 | ||
|
|
5e28644ec7 | ||
|
|
9b08f181a6 | ||
|
|
1a0c39693e | ||
|
|
18662b9c71 | ||
|
|
c39ad9cac1 | ||
|
|
b8f2195a3a | ||
|
|
57d437dff8 | ||
|
|
adead1cc39 | ||
|
|
41e4bb974b | ||
|
|
7dbb36a5d5 | ||
|
|
5b8ded95d2 | ||
|
|
1904a7b85c | ||
|
|
94c4becb34 | ||
|
|
8ba7f73736 | ||
|
|
8f5bead445 | ||
|
|
6bab6ce6c4 | ||
|
|
e835509acc | ||
|
|
d1606d1109 | ||
|
|
2ae72aa8d1 | ||
|
|
1b0828eb3e | ||
|
|
b6252151f4 | ||
|
|
832d561d52 | ||
|
|
d7fd3e2109 | ||
|
|
430ade7952 | ||
|
|
47ee9b503c | ||
|
|
4c98c241ed | ||
|
|
aa5ec7a343 | ||
|
|
7d9f01a621 | ||
|
|
5d07744c41 | ||
|
|
af54cca281 | ||
|
|
f2d4af1861 | ||
|
|
a3977ef6a1 | ||
|
|
3af4805eb6 | ||
|
|
1ceba486ce | ||
|
|
49990d7841 | ||
|
|
eb93fecdd1 | ||
|
|
8e992ff921 | ||
|
|
0456ed702e | ||
|
|
b11dc56cd0 | ||
|
|
2d60543026 | ||
|
|
221dda5a1a | ||
|
|
b50f7dd91a | ||
|
|
e752ad75be | ||
|
|
0ce3d1ad97 | ||
|
|
14b926582f | ||
|
|
be76dc993a | ||
|
|
2d659904d0 |
9
.github/workflows/docker.yml
vendored
9
.github/workflows/docker.yml
vendored
@@ -1,4 +1,9 @@
|
|||||||
name: Docker Image CI
|
name: Docker Image CI
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -7,10 +12,10 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|||||||
194
.github/workflows/release.yml
vendored
194
.github/workflows/release.yml
vendored
@@ -1,13 +1,29 @@
|
|||||||
name: Release X-UI
|
name: Release X-UI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- "*.*.*"
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/release.yml'
|
||||||
|
- '**.js'
|
||||||
|
- '**.css'
|
||||||
|
- '**.html'
|
||||||
|
- '**.sh'
|
||||||
|
- '**.go'
|
||||||
|
- 'go.mod'
|
||||||
|
- 'go.sum'
|
||||||
|
- 'x-ui.service'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
@@ -15,65 +31,51 @@ jobs:
|
|||||||
- arm64
|
- arm64
|
||||||
- armv7
|
- armv7
|
||||||
- armv6
|
- armv6
|
||||||
- armv5
|
|
||||||
- 386
|
- 386
|
||||||
|
- armv5
|
||||||
- s390x
|
- s390x
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabi
|
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
|
||||||
sudo apt install gcc-i686-linux-gnu
|
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
|
||||||
sudo apt install gcc-s390x-linux-gnu
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build x-ui
|
- name: Build x-ui
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
export GOARCH=${{ matrix.platform }}
|
export GOARCH=${{ matrix.platform }}
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
|
||||||
export GOARCH=arm64
|
case "${{ matrix.platform }}" in
|
||||||
export CC=aarch64-linux-gnu-gcc
|
amd64) BOOTLIN_ARCH="x86-64" ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
arm64) BOOTLIN_ARCH="aarch64" ;;
|
||||||
export GOARCH=arm
|
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
|
||||||
export GOARM=7
|
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
386) BOOTLIN_ARCH="x86-i686" ;;
|
||||||
export GOARCH=arm
|
s390x) BOOTLIN_ARCH="s390x-z13" ;;
|
||||||
export GOARM=6
|
esac
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
|
||||||
export GOARCH=arm
|
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
|
||||||
export GOARM=5
|
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
|
||||||
export CC=arm-linux-gnueabi-gcc
|
echo "Downloading: $TARBALL_URL"
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
cd /tmp
|
||||||
export GOARCH=386
|
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
|
||||||
export CC=i686-linux-gnu-gcc
|
tar -xf "$(basename "$TARBALL_URL")"
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
|
||||||
export GOARCH=s390x
|
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
|
||||||
export CC=s390x-linux-gnu-gcc
|
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
|
||||||
fi
|
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
|
||||||
|
cd -
|
||||||
go build -ldflags "-w -s" -o xui-release -v main.go
|
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
|
||||||
|
file xui-release
|
||||||
|
ldd xui-release || echo "Static binary confirmed"
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/
|
cp xui-release x-ui/
|
||||||
@@ -84,7 +86,7 @@ jobs:
|
|||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.7.26/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
@@ -101,14 +103,14 @@ jobs:
|
|||||||
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
|
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
|
||||||
unzip Xray-linux-arm32-v6.zip
|
unzip Xray-linux-arm32-v6.zip
|
||||||
rm -f Xray-linux-arm32-v6.zip
|
rm -f Xray-linux-arm32-v6.zip
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
|
||||||
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
|
|
||||||
unzip Xray-linux-arm32-v5.zip
|
|
||||||
rm -f Xray-linux-arm32-v5.zip
|
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
wget -q ${Xray_URL}Xray-linux-32.zip
|
wget -q ${Xray_URL}Xray-linux-32.zip
|
||||||
unzip Xray-linux-32.zip
|
unzip Xray-linux-32.zip
|
||||||
rm -f Xray-linux-32.zip
|
rm -f Xray-linux-32.zip
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||||
|
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
|
||||||
|
unzip Xray-linux-arm32-v5.zip
|
||||||
|
rm -f Xray-linux-arm32-v5.zip
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||||
wget -q ${Xray_URL}Xray-linux-s390x.zip
|
wget -q ${Xray_URL}Xray-linux-s390x.zip
|
||||||
unzip Xray-linux-s390x.zip
|
unzip Xray-linux-s390x.zip
|
||||||
@@ -124,12 +126,96 @@ jobs:
|
|||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
|
|
||||||
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: x-ui-linux-${{ matrix.platform }}
|
||||||
|
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
|
|
||||||
- name: Upload files to GH release
|
- name: Upload files to GH release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||||
|
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
|
overwrite: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# Windows Build
|
||||||
|
# =================================
|
||||||
|
build-windows:
|
||||||
|
name: Build for Windows
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- amd64
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Build X-UI for Windows
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$env:CGO_ENABLED="1"
|
||||||
|
$env:GOOS="windows"
|
||||||
|
$env:GOARCH="amd64"
|
||||||
|
go build -ldflags "-w -s" -o xui-release.exe -v main.go
|
||||||
|
|
||||||
|
mkdir x-ui
|
||||||
|
Copy-Item xui-release.exe x-ui\
|
||||||
|
mkdir x-ui\bin
|
||||||
|
cd x-ui\bin
|
||||||
|
|
||||||
|
# Download Xray for Windows
|
||||||
|
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
|
||||||
|
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
|
||||||
|
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
|
||||||
|
Remove-Item "Xray-windows-64.zip"
|
||||||
|
Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
|
||||||
|
Rename-Item xray.exe xray-windows-amd64.exe
|
||||||
|
cd ..
|
||||||
|
Copy-Item -Path ..\windows_files\* -Destination . -Recurse
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
- name: Package to Zip
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
|
||||||
|
|
||||||
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: x-ui-windows-amd64
|
||||||
|
path: ./x-ui-windows-amd64.zip
|
||||||
|
|
||||||
|
- name: Upload files to GH release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||||
|
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
file: x-ui-windows-amd64.zip
|
||||||
|
asset_name: x-ui-windows-amd64.zip
|
||||||
|
overwrite: true
|
||||||
|
prerelease: true
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,4 +14,5 @@ dist/
|
|||||||
release/
|
release/
|
||||||
/release.sh
|
/release.sh
|
||||||
/x-ui
|
/x-ui
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/dev/
|
||||||
|
|||||||
94
CONTRIBUTING.md
Normal file
94
CONTRIBUTING.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Contribution guide
|
||||||
|
|
||||||
|
This document describes **development-only** setup options for contributing to the x-ui project.
|
||||||
|
Production deployments are **out of scope** for this guide.
|
||||||
|
|
||||||
|
For safety, Docker-based development is **strongly recommended**.
|
||||||
|
Local installation should be performed **only inside a VM or disposable environment**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended: Docker development setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
- Docker Compose (v2+)
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/alireza0/x-ui.git
|
||||||
|
# or:
|
||||||
|
# git clone https://github.com/<your_fork_name>/x-ui.git
|
||||||
|
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explore panel
|
||||||
|
|
||||||
|
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
|
||||||
|
- Default credentials: `admin` / `admin`
|
||||||
|
|
||||||
|
To stop and remove containers or `ctrl` + `C`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
When using the Docker development setup, the [docker-compose.dev.yml](/docker-compose.dev.yml) file mounts the local database directory into the container:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./dev/db:/etc/x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
This means:
|
||||||
|
|
||||||
|
- [./dev/db](/dev/db/) on the host is used to persist panel state during development
|
||||||
|
- `/etc/x-ui` inside the container is the active database/config directory
|
||||||
|
- Data will survive container restarts but is isolated from the host system
|
||||||
|
- You can safely delete `./dev/db` to reset the development database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local development setup (unsafe on host OS)
|
||||||
|
|
||||||
|
This method runs scripts with root privileges and alters the host system.
|
||||||
|
Use **only inside a VM, container, or disposable test environment**.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Ubuntu/Debian or Fedora (actually anything with systemd, just use proper package manager)
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ubuntu / Debian
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade -y
|
||||||
|
sudo apt install -y golang unzip git wget
|
||||||
|
|
||||||
|
# Fedora
|
||||||
|
sudo dnf update -y
|
||||||
|
sudo dnf install -y golang unzip git wget
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup panel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/alireza0/x-ui.git
|
||||||
|
# or:
|
||||||
|
# git clone https://github.com/<your_fork_name>/x-ui.git
|
||||||
|
|
||||||
|
cd x-ui/
|
||||||
|
sudo bash x-ui.sh # Select -> 10. Start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explore panel
|
||||||
|
|
||||||
|
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
|
||||||
|
- Default credentials: `admin` / `admin`
|
||||||
@@ -14,7 +14,7 @@ case $1 in
|
|||||||
;;
|
;;
|
||||||
armv7 | arm | arm32)
|
armv7 | arm | arm32)
|
||||||
ARCH="arm32-v7a"
|
ARCH="arm32-v7a"
|
||||||
FNAME="arm32"
|
FNAME="arm"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
ARCH="64"
|
ARCH="64"
|
||||||
@@ -23,7 +23,7 @@ case $1 in
|
|||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.7.26/Xray-linux-${ARCH}.zip"
|
wget -q "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.24-alpine AS builder
|
FROM golang:1.25-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||||
|
|||||||
53
README.md
53
README.md
@@ -13,10 +13,9 @@
|
|||||||
|
|
||||||
[](https://www.buymeacoffee.com/alireza7)
|
[](https://www.buymeacoffee.com/alireza7)
|
||||||
|
|
||||||
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
<a href="https://nowpayments.io/donation/alireza7" target="_blank" rel="noreferrer noopener">
|
||||||
- Tezos (XTZ):
|
<img src="https://nowpayments.io/images/embeds/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
|
||||||
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
|
</a>
|
||||||
|
|
||||||
|
|
||||||
## Quick Overview
|
## Quick Overview
|
||||||
| Features | Enable? |
|
| Features | Enable? |
|
||||||
@@ -186,23 +185,6 @@ docker build -t x-ui .
|
|||||||
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
||||||
- Dark/Light theme
|
- Dark/Light theme
|
||||||
|
|
||||||
## Recommended OS
|
|
||||||
|
|
||||||
- Ubuntu 22.04+
|
|
||||||
- Debian 11+
|
|
||||||
- CentOS 8+
|
|
||||||
- OpenEuler 22.03+
|
|
||||||
- Fedora 36+
|
|
||||||
- Arch Linux
|
|
||||||
- Parch Linux
|
|
||||||
- Manjaro
|
|
||||||
- Armbian
|
|
||||||
- AlmaLinux 8.0+
|
|
||||||
- Rocky Linux 8+
|
|
||||||
- Oracle Linux 8+
|
|
||||||
- OpenSUSE Tubleweed
|
|
||||||
- Amazon Linux 2023
|
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||

|

|
||||||
@@ -226,7 +208,6 @@ docker build -t x-ui .
|
|||||||
| :----: | --------------------------------- | ----------------------------------------- |
|
| :----: | --------------------------------- | ----------------------------------------- |
|
||||||
| `GET` | `"/"` | Get all inbounds |
|
| `GET` | `"/"` | Get all inbounds |
|
||||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
|
||||||
| `POST` | `"/add"` | Add inbound |
|
| `POST` | `"/add"` | Add inbound |
|
||||||
| `POST` | `"/del/:id"` | Delete inbound |
|
| `POST` | `"/del/:id"` | Delete inbound |
|
||||||
| `POST` | `"/update/:id"` | Update inbound |
|
| `POST` | `"/update/:id"` | Update inbound |
|
||||||
@@ -241,11 +222,33 @@ docker build -t x-ui .
|
|||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
|
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
|
||||||
|
|
||||||
|
|
||||||
\*- The field `clientId` should be filled by:
|
\*- The field `clientId` should be filled by:
|
||||||
|
|
||||||
- `client.id` for VMess and VLESS
|
- `client.id` for VMess and VLESS
|
||||||
- `client.password` for Trojan
|
- `client.password` for Trojan
|
||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
|
|
||||||
|
- `/xui/API/server` base for following actions:
|
||||||
|
|
||||||
|
| Method | Path | Action |
|
||||||
|
| :----: | --------------------------------- | ----------------------------------------- |
|
||||||
|
| `GET` | `"/status"` | Get server status |
|
||||||
|
| `GET` | `"/getDb"` | Get database backup |
|
||||||
|
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||||
|
| `GET` | `"/getConfigJson"` | Get config.json |
|
||||||
|
| `GET` | `"/getXrayVersion"` | Get last xray versions |
|
||||||
|
| `GET` | `"/getNewVlessEnc"` | Get new vless enc |
|
||||||
|
| `GET` | `"/getNewX25519Cert"` | Get new x25519 cert |
|
||||||
|
| `GET` | `"/getNewmldsa65"` | Get new mldsa65 |
|
||||||
|
| `POST` | `"/getNewEchCert"` | Get new ech cert |
|
||||||
|
| `POST` | `"/importDB"` | Import database to x-ui |
|
||||||
|
| `POST` | `"/stopXrayService"` | Stop xray service |
|
||||||
|
| `POST` | `"/restartXrayService"` | Restart xray service |
|
||||||
|
| `POST` | `"/installXray/:version"` | Install specific version of xray |
|
||||||
|
| `POST` | `"/logs/:count"` | Get panel/xray logs |
|
||||||
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,10 +18,10 @@ var name string
|
|||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Debug LogLevel = "debug"
|
Debug LogLevel = "debug"
|
||||||
Info LogLevel = "info"
|
Info LogLevel = "info"
|
||||||
Warn LogLevel = "warn"
|
Warning LogLevel = "warning"
|
||||||
Error LogLevel = "error"
|
Error LogLevel = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
@@ -53,12 +55,32 @@ func GetBinFolderPath() string {
|
|||||||
return binFolderPath
|
return binFolderPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBaseDir() string {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
exeDir := filepath.Dir(exePath)
|
||||||
|
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
|
||||||
|
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return wd
|
||||||
|
}
|
||||||
|
return exeDir
|
||||||
|
}
|
||||||
|
|
||||||
func GetDBFolderPath() string {
|
func GetDBFolderPath() string {
|
||||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||||
if dbFolderPath == "" {
|
if dbFolderPath != "" {
|
||||||
dbFolderPath = "/etc/x-ui"
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
return dbFolderPath
|
if runtime.GOOS == "windows" {
|
||||||
|
return getBaseDir()
|
||||||
|
}
|
||||||
|
return "/etc/x-ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.8.10
|
1.10.2
|
||||||
@@ -7,9 +7,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -94,6 +95,17 @@ func InitDB(dbPath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CloseDB() error {
|
||||||
|
if db != nil {
|
||||||
|
sqlDB, err := db.DB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sqlDB.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetDB() *gorm.DB {
|
func GetDB() *gorm.DB {
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
@@ -120,3 +132,26 @@ func Checkpoint() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateSQLiteDB(dbPath string) error {
|
||||||
|
if _, err := os.Stat(dbPath); err != nil { // file must exist
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gdb, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{Logger: logger.Discard})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sqlDB, err := gdb.DB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sqlDB.Close()
|
||||||
|
var res string
|
||||||
|
if err := gdb.Raw("PRAGMA integrity_check;").Scan(&res).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res != "ok" {
|
||||||
|
return common.NewError("sqlite integrity check failed: " + res)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"github.com/alireza0/x-ui/util/json_util"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Protocol string
|
type Protocol string
|
||||||
@@ -79,3 +79,10 @@ type Client struct {
|
|||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
Reset int `json:"reset" form:"reset"`
|
Reset int `json:"reset" form:"reset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VLESSSettings struct {
|
||||||
|
Clients []Client `json:"clients"`
|
||||||
|
Decryption string `json:"decryption"`
|
||||||
|
Encryption string `json:"encryption"`
|
||||||
|
Fallbacks []any `json:"fallbacks"`
|
||||||
|
}
|
||||||
|
|||||||
15
docker-compose.dev.yml
Normal file
15
docker-compose.dev.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
xui:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
working_dir: /app
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- ./dev/db:/etc/x-ui
|
||||||
|
environment:
|
||||||
|
XUI_BIN_FOLDER: /app/dev/bin
|
||||||
|
XUI_DB_FOLDER: /etc/x-ui
|
||||||
|
XUI_LOG_LEVEL: debug
|
||||||
|
XUI_DEBUG: "true"
|
||||||
|
command: ["go","run","./main.go"]
|
||||||
109
go.mod
109
go.mod
@@ -1,41 +1,43 @@
|
|||||||
module x-ui
|
module github.com/alireza0/x-ui
|
||||||
|
|
||||||
go 1.24.5
|
go 1.25.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.3
|
github.com/gin-contrib/gzip v1.2.5
|
||||||
github.com/gin-contrib/sessions v1.0.4
|
github.com/gin-contrib/sessions v1.0.4
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6
|
github.com/shirou/gopsutil/v4 v4.26.1
|
||||||
github.com/xtls/xray-core v1.250726.0
|
github.com/xtls/xray-core v1.260206.0
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.27.0
|
golang.org/x/text v0.34.0
|
||||||
google.golang.org/grpc v1.74.2
|
google.golang.org/grpc v1.79.1
|
||||||
gorm.io/driver/sqlite v1.6.0
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.30.1
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/bytedance/sonic v1.13.2 // indirect
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
|
github.com/cloudflare/circl v1.6.3 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
@@ -44,50 +46,45 @@ require (
|
|||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.3 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.34 // indirect
|
||||||
github.com/miekg/dns v1.1.67 // indirect
|
github.com/miekg/dns v1.1.72 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
github.com/pires/go-proxyproto v0.9.2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.0 // indirect
|
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagernet/sing v0.5.1 // indirect
|
github.com/sagernet/sing v0.7.17 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
|
||||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 // indirect
|
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.16.0 // indirect
|
golang.org/x/arch v0.23.0 // indirect
|
||||||
golang.org/x/crypto v0.40.0 // indirect
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
golang.org/x/mod v0.25.0 // indirect
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/tools v0.34.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 // indirect
|
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
261
go.sum
261
go.sum
@@ -1,62 +1,65 @@
|
|||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
|
||||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||||
|
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
|
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
||||||
github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
|
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
||||||
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||||
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -80,167 +83,161 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
|
||||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
|
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||||
github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
github.com/sagernet/sing v0.7.17 h1:Jg4RUYIaQWTi7iY5ROHi3/Zsgxn4SPoRTwbdt35mt50=
|
||||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.17/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||||
|
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||||
|
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
|
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
|
||||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
|
||||||
github.com/xtls/xray-core v1.250726.0 h1:uTUHUt/CQ1JQLip1pLkiwoS0pMvl6oCHJgur4M4orWQ=
|
github.com/xtls/xray-core v1.260206.0 h1:gY8IV6u76CW93txL9QmacgZ0Udxr2Q3e9qUxXAhdHqI=
|
||||||
github.com/xtls/xray-core v1.250726.0/go.mod h1:z2vn2o30flYEgpSz1iEhdZP1I46UZ3+gXINZyohH3yE=
|
github.com/xtls/xray-core v1.260206.0/go.mod h1:GyFIgVGRJkt3eyV/NMcdxOKXcJPqGGpyupHzy16uJhU=
|
||||||
|
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=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
|
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||||
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h1:sfK5nHuG7lRFZ2FdTT3RimOqWBg8IrVm+/Vko1FVOsk=
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
|
|||||||
87
install.sh
87
install.sh
@@ -38,94 +38,25 @@ arch() {
|
|||||||
|
|
||||||
echo "arch: $(arch)"
|
echo "arch: $(arch)"
|
||||||
|
|
||||||
os_version=""
|
|
||||||
os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.')
|
|
||||||
|
|
||||||
if [[ "${release}" == "arch" ]]; then
|
|
||||||
echo "Your OS is Arch Linux"
|
|
||||||
elif [[ "${release}" == "parch" ]]; then
|
|
||||||
echo "Your OS is Parch Linux"
|
|
||||||
elif [[ "${release}" == "manjaro" ]]; then
|
|
||||||
echo "Your OS is Manjaro"
|
|
||||||
elif [[ "${release}" == "armbian" ]]; then
|
|
||||||
echo "Your OS is Armbian"
|
|
||||||
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
|
|
||||||
echo "Your OS is OpenSUSE Tumbleweed"
|
|
||||||
elif [[ "${release}" == "openEuler" ]]; then
|
|
||||||
if [[ ${os_version} -lt 2203 ]]; then
|
|
||||||
echo -e "${red} Please use OpenEuler 22.03 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "centos" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "ubuntu" ]]; then
|
|
||||||
if [[ ${os_version} -lt 2204 ]]; then
|
|
||||||
echo -e "${red} Please use Ubuntu 22 or higher version!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "fedora" ]]; then
|
|
||||||
if [[ ${os_version} -lt 36 ]]; then
|
|
||||||
echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "amzn" ]]; then
|
|
||||||
if [[ ${os_version} != "2023" ]]; then
|
|
||||||
echo -e "${red} Please use Amazon Linux 2023!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "debian" ]]; then
|
|
||||||
if [[ ${os_version} -lt 12 ]]; then
|
|
||||||
echo -e "${red} Please use Debian 12 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "almalinux" ]]; then
|
|
||||||
if [[ ${os_version} -lt 95 ]]; then
|
|
||||||
echo -e "${red} Please use AlmaLinux 9.5 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "rocky" ]]; then
|
|
||||||
if [[ ${os_version} -lt 95 ]]; then
|
|
||||||
echo -e "${red} Please use Rocky Linux 9.5 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "ol" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
|
||||||
echo "Please ensure you are using one of the following supported operating systems:"
|
|
||||||
echo "- Ubuntu 22.04+"
|
|
||||||
echo "- Debian 12+"
|
|
||||||
echo "- CentOS 8+"
|
|
||||||
echo "- OpenEuler 22.03+"
|
|
||||||
echo "- Fedora 36+"
|
|
||||||
echo "- Arch Linux"
|
|
||||||
echo "- Parch Linux"
|
|
||||||
echo "- Manjaro"
|
|
||||||
echo "- Armbian"
|
|
||||||
echo "- AlmaLinux 9.5+"
|
|
||||||
echo "- Rocky Linux 9.5+"
|
|
||||||
echo "- Oracle Linux 8+"
|
|
||||||
echo "- OpenSUSE Tumbleweed"
|
|
||||||
echo "- Amazon Linux 2023"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_dependencies() {
|
install_dependencies() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
apt-get update && apt-get install -y -q wget curl tar tzdata cron
|
||||||
;;
|
;;
|
||||||
centos | almalinux | rocky | ol)
|
centos | almalinux | rocky | ol)
|
||||||
yum -y update && yum install -y -q wget curl tar tzdata
|
yum -y update && yum install -y -q wget curl tar tzdata cronie
|
||||||
;;
|
;;
|
||||||
fedora | amzn)
|
fedora | amzn)
|
||||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
dnf -y update && dnf install -y -q wget curl tar tzdata cronie
|
||||||
;;
|
;;
|
||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
|
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata cronie
|
||||||
;;
|
;;
|
||||||
opensuse-tumbleweed)
|
opensuse-tumbleweed)
|
||||||
zypper refresh && zypper -q install -y wget curl tar timezone
|
zypper refresh && zypper -q install -y wget curl tar timezone cron
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update && apt install -y -q wget curl tar tzdata
|
apt-get update && apt install -y -q wget curl tar tzdata cron
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -225,10 +156,10 @@ install_x-ui() {
|
|||||||
else
|
else
|
||||||
last_version=$1
|
last_version=$1
|
||||||
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
|
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
|
||||||
echo -e "Beginning to install x-ui v$1"
|
echo -e "Beginning to install x-ui $1"
|
||||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}download x-ui v$1 failed,please check the version exists${plain}"
|
echo -e "${red}download x-ui $1 failed,please check the version exists${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -265,7 +196,7 @@ install_x-ui() {
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable x-ui
|
systemctl enable x-ui
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
|
echo -e "${green}x-ui ${last_version}${plain} installation finished, it is up and running now..."
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "You may access the Panel with following URL(s):${yellow}"
|
echo -e "You may access the Panel with following URL(s):${yellow}"
|
||||||
/usr/local/x-ui/x-ui uri
|
/usr/local/x-ui/x-ui uri
|
||||||
|
|||||||
32
main.go
32
main.go
@@ -12,17 +12,17 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/sub"
|
"github.com/alireza0/x-ui/sub"
|
||||||
"x-ui/web"
|
"github.com/alireza0/x-ui/util/sys"
|
||||||
"x-ui/web/global"
|
"github.com/alireza0/x-ui/web"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/global"
|
||||||
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
xrayCore "github.com/xtls/xray-core/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func runWebServer() {
|
func runWebServer() {
|
||||||
@@ -33,7 +33,7 @@ func runWebServer() {
|
|||||||
logger.InitLogger(logging.DEBUG)
|
logger.InitLogger(logging.DEBUG)
|
||||||
case config.Info:
|
case config.Info:
|
||||||
logger.InitLogger(logging.INFO)
|
logger.InitLogger(logging.INFO)
|
||||||
case config.Warn:
|
case config.Warning:
|
||||||
logger.InitLogger(logging.WARNING)
|
logger.InitLogger(logging.WARNING)
|
||||||
case config.Error:
|
case config.Error:
|
||||||
logger.InitLogger(logging.ERROR)
|
logger.InitLogger(logging.ERROR)
|
||||||
@@ -68,7 +68,7 @@ func runWebServer() {
|
|||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
// Trap shutdown signals
|
// Trap shutdown signals
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, sys.SIGUSR1)
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
|
|
||||||
@@ -99,6 +99,12 @@ func runWebServer() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case sys.SIGUSR1:
|
||||||
|
logger.Info("Received USR1 signal, restarting xray-core...")
|
||||||
|
err := server.RestartXray()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to restart xray-core:", err)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
server.Stop()
|
server.Stop()
|
||||||
subServer.Stop()
|
subServer.Stop()
|
||||||
@@ -473,9 +479,3 @@ func main() {
|
|||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startXray() {
|
|
||||||
conf := xrayCore.Config{}
|
|
||||||
core, _ := xrayCore.New(&conf)
|
|
||||||
core.Start()
|
|
||||||
}
|
|
||||||
|
|||||||
12
sub/sub.go
12
sub/sub.go
@@ -8,12 +8,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/web/middleware"
|
"github.com/alireza0/x-ui/web/middleware"
|
||||||
"x-ui/web/network"
|
"github.com/alireza0/x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/json_util"
|
"github.com/alireza0/x-ui/util/json_util"
|
||||||
"x-ui/util/random"
|
"github.com/alireza0/x-ui/util/random"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed default.json
|
//go:embed default.json
|
||||||
@@ -20,8 +20,7 @@ var defaultJson string
|
|||||||
type SubJsonService struct {
|
type SubJsonService struct {
|
||||||
configJson map[string]interface{}
|
configJson map[string]interface{}
|
||||||
defaultOutbounds []json_util.RawMessage
|
defaultOutbounds []json_util.RawMessage
|
||||||
fragment string
|
fragmentOrNoises bool
|
||||||
noises string
|
|
||||||
mux string
|
mux string
|
||||||
|
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
@@ -39,6 +38,31 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragmentOrNoises := false
|
||||||
|
if fragment != "" || noises != "" {
|
||||||
|
fragmentOrNoises = true
|
||||||
|
defaultOutboundsSettings := map[string]interface{}{
|
||||||
|
"domainStrategy": "UseIP",
|
||||||
|
"redirect": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if fragment != "" {
|
||||||
|
defaultOutboundsSettings["fragment"] = json_util.RawMessage(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
if noises != "" {
|
||||||
|
defaultOutboundsSettings["noises"] = json_util.RawMessage(noises)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDirectOutbound := map[string]interface{}{
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": defaultOutboundsSettings,
|
||||||
|
"tag": "direct_out",
|
||||||
|
}
|
||||||
|
jsonBytes, _ := json.MarshalIndent(defaultDirectOutbound, "", " ")
|
||||||
|
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||||
|
}
|
||||||
|
|
||||||
if rules != "" {
|
if rules != "" {
|
||||||
var newRules []interface{}
|
var newRules []interface{}
|
||||||
routing, _ := configJson["routing"].(map[string]interface{})
|
routing, _ := configJson["routing"].(map[string]interface{})
|
||||||
@@ -49,19 +73,10 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
|
|||||||
configJson["routing"] = routing
|
configJson["routing"] = routing
|
||||||
}
|
}
|
||||||
|
|
||||||
if fragment != "" {
|
|
||||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
|
||||||
}
|
|
||||||
|
|
||||||
if noises != "" {
|
|
||||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(noises))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SubJsonService{
|
return &SubJsonService{
|
||||||
configJson: configJson,
|
configJson: configJson,
|
||||||
defaultOutbounds: defaultOutbounds,
|
defaultOutbounds: defaultOutbounds,
|
||||||
fragment: fragment,
|
fragmentOrNoises: fragmentOrNoises,
|
||||||
noises: noises,
|
|
||||||
mux: mux,
|
mux: mux,
|
||||||
SubService: subService,
|
SubService: subService,
|
||||||
}
|
}
|
||||||
@@ -171,12 +186,12 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
case "tls":
|
case "tls":
|
||||||
if newStream["security"] != "tls" {
|
if newStream["security"] != "tls" {
|
||||||
newStream["security"] = "tls"
|
newStream["security"] = "tls"
|
||||||
newStream["tslSettings"] = map[string]interface{}{}
|
newStream["tlsSettings"] = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
case "none":
|
case "none":
|
||||||
if newStream["security"] != "none" {
|
if newStream["security"] != "none" {
|
||||||
newStream["security"] = "none"
|
newStream["security"] = "none"
|
||||||
delete(newStream, "tslSettings")
|
delete(newStream, "tlsSettings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
||||||
@@ -184,8 +199,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
var newOutbounds []json_util.RawMessage
|
var newOutbounds []json_util.RawMessage
|
||||||
|
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess", "vless":
|
case "vmess":
|
||||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client, ""))
|
||||||
|
case "vless":
|
||||||
|
var vlessSettings model.VLESSSettings
|
||||||
|
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||||
|
|
||||||
|
newOutbounds = append(newOutbounds,
|
||||||
|
s.genVnext(inbound, streamSettings, client, vlessSettings.Encryption))
|
||||||
case "trojan", "shadowsocks":
|
case "trojan", "shadowsocks":
|
||||||
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
||||||
}
|
}
|
||||||
@@ -216,8 +237,8 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
delete(streamSettings, "sockopt")
|
delete(streamSettings, "sockopt")
|
||||||
|
|
||||||
if s.fragment != "" {
|
if s.fragmentOrNoises {
|
||||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "penetrate": true}`)
|
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "direct_out", "tcpKeepAliveIdle": 100}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove proxy protocol
|
// remove proxy protocol
|
||||||
@@ -283,32 +304,43 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
|||||||
return rltyData
|
return rltyData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, encryption string) json_util.RawMessage {
|
||||||
outbound := Outbound{}
|
outbound := Outbound{}
|
||||||
usersData := make([]UserVnext, 1)
|
|
||||||
|
|
||||||
usersData[0].ID = client.ID
|
|
||||||
usersData[0].Level = 8
|
|
||||||
if inbound.Protocol == model.VLESS {
|
|
||||||
usersData[0].Flow = client.Flow
|
|
||||||
usersData[0].Encryption = "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
vnextData := make([]VnextSetting, 1)
|
|
||||||
vnextData[0] = VnextSetting{
|
|
||||||
Address: inbound.Listen,
|
|
||||||
Port: inbound.Port,
|
|
||||||
Users: usersData,
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
outbound.Protocol = string(inbound.Protocol)
|
||||||
outbound.Tag = "proxy"
|
outbound.Tag = "proxy"
|
||||||
|
|
||||||
if s.mux != "" {
|
if s.mux != "" {
|
||||||
outbound.Mux = json_util.RawMessage(s.mux)
|
outbound.Mux = json_util.RawMessage(s.mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
outbound.StreamSettings = streamSettings
|
outbound.StreamSettings = streamSettings
|
||||||
outbound.Settings = OutboundSettings{
|
|
||||||
Vnext: vnextData,
|
// Build standard Xray/V2Ray schema
|
||||||
|
user := map[string]any{
|
||||||
|
"id": client.ID,
|
||||||
|
"level": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
if inbound.Protocol == model.VLESS {
|
||||||
|
user["encryption"] = encryption
|
||||||
|
if client.Flow != "" {
|
||||||
|
user["flow"] = client.Flow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// VMess
|
||||||
|
user["alterId"] = 0
|
||||||
|
user["security"] = "auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
vnext := map[string]any{
|
||||||
|
"address": inbound.Listen,
|
||||||
|
"port": inbound.Port,
|
||||||
|
"users": []any{user},
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.Settings = map[string]any{
|
||||||
|
"vnext": []any{vnext},
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
@@ -346,8 +378,8 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
|||||||
outbound.Mux = json_util.RawMessage(s.mux)
|
outbound.Mux = json_util.RawMessage(s.mux)
|
||||||
}
|
}
|
||||||
outbound.StreamSettings = streamSettings
|
outbound.StreamSettings = streamSettings
|
||||||
outbound.Settings = OutboundSettings{
|
outbound.Settings = map[string]any{
|
||||||
Servers: serverData,
|
"servers": serverData,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
@@ -355,30 +387,11 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
Settings map[string]any `json:"settings,omitempty"`
|
||||||
Settings OutboundSettings `json:"settings,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundSettings struct {
|
|
||||||
Vnext []VnextSetting `json:"vnext,omitempty"`
|
|
||||||
Servers []ServerSetting `json:"servers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VnextSetting struct {
|
|
||||||
Address string `json:"address"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Users []UserVnext `json:"users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserVnext struct {
|
|
||||||
Encryption string `json:"encryption,omitempty"`
|
|
||||||
Flow string `json:"flow,omitempty"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Level int `json:"level"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerSetting struct {
|
type ServerSetting struct {
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/util/random"
|
"github.com/alireza0/x-ui/util/random"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
)
|
)
|
||||||
@@ -305,6 +305,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
var vlessSettings model.VLESSSettings
|
||||||
|
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||||
|
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
@@ -319,6 +322,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
port := inbound.Port
|
port := inbound.Port
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
|
if vlessSettings.Encryption != "" {
|
||||||
|
params["encryption"] = vlessSettings.Encryption
|
||||||
|
}
|
||||||
params["type"] = streamNetwork
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
switch streamNetwork {
|
switch streamNetwork {
|
||||||
@@ -429,6 +435,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
||||||
|
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
||||||
|
params["pqv"] = pqv
|
||||||
|
}
|
||||||
|
}
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,6 +630,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
||||||
|
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
||||||
|
params["pqv"] = pqv
|
||||||
|
}
|
||||||
|
}
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewErrorf(format string, a ...interface{}) error {
|
func NewErrorf(format string, a ...interface{}) error {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package random
|
package random
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"crypto/rand"
|
||||||
"time"
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -15,8 +15,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
numSeq[i] = rune('0' + i)
|
numSeq[i] = rune('0' + i)
|
||||||
}
|
}
|
||||||
@@ -39,11 +37,20 @@ func init() {
|
|||||||
func Seq(n int) string {
|
func Seq(n int) string {
|
||||||
runes := make([]rune, n)
|
runes := make([]rune, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
runes[i] = allSeq[rand.Intn(len(allSeq))]
|
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(allSeq))))
|
||||||
|
if err != nil {
|
||||||
|
panic("crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
|
runes[i] = allSeq[idx.Int64()]
|
||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Num(n int) int {
|
func Num(n int) int {
|
||||||
return rand.Intn(n)
|
bn := big.NewInt(int64(n))
|
||||||
|
r, err := rand.Int(rand.Reader, bn)
|
||||||
|
if err != nil {
|
||||||
|
panic("crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
|
return int(r.Int64())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,13 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var SIGUSR1 = syscall.SIGUSR1
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
stats, err := net.Connections("tcp")
|
stats, err := net.Connections("tcp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var SIGUSR1 = syscall.SIGUSR1
|
||||||
|
|
||||||
func getLinesNum(filename string) (int, error) {
|
func getLinesNum(filename string) (int, error) {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,9 +4,13 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var SIGUSR1 = syscall.Signal(0)
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
stats, err := net.Connections("tcp")
|
stats, err := net.Connections("tcp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ function getLang() {
|
|||||||
lang = window.navigator.language || window.navigator.userLanguage;
|
lang = window.navigator.language || window.navigator.userLanguage;
|
||||||
|
|
||||||
if (isSupportLang(lang)) {
|
if (isSupportLang(lang)) {
|
||||||
setCookie('lang', lang, 150);
|
setCookie('lang', lang);
|
||||||
} else {
|
} else {
|
||||||
setCookie('lang', 'en-US', 150);
|
setCookie('lang', 'en-US');
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setCookie('lang', 'en-US', 150);
|
setCookie('lang', 'en-US');
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ function setLang(lang) {
|
|||||||
lang = 'en-US';
|
lang = 'en-US';
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie('lang', lang, 150);
|
setCookie('lang', lang);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ const Protocols = {
|
|||||||
SOCKS: 'socks',
|
SOCKS: 'socks',
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
WIREGUARD: 'wireguard',
|
WIREGUARD: 'wireguard',
|
||||||
|
TUN: 'tun',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
|
||||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
@@ -320,14 +319,12 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
|
|||||||
class KcpStreamSettings extends XrayCommonClass {
|
class KcpStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
mtu = 1350,
|
mtu = 1350,
|
||||||
tti = 50,
|
tti = 20,
|
||||||
uplinkCapacity = 5,
|
uplinkCapacity = 5,
|
||||||
downlinkCapacity = 20,
|
downlinkCapacity = 20,
|
||||||
congestion = false,
|
congestion = false,
|
||||||
readBufferSize = 2,
|
readBufferSize = 1,
|
||||||
writeBufferSize = 2,
|
writeBufferSize = 1,
|
||||||
type = 'none',
|
|
||||||
seed = RandomUtil.randomSeq(10),
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
@@ -337,8 +334,6 @@ class KcpStreamSettings extends XrayCommonClass {
|
|||||||
this.congestion = congestion;
|
this.congestion = congestion;
|
||||||
this.readBuffer = readBufferSize;
|
this.readBuffer = readBufferSize;
|
||||||
this.writeBuffer = writeBufferSize;
|
this.writeBuffer = writeBufferSize;
|
||||||
this.type = type;
|
|
||||||
this.seed = seed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
@@ -350,8 +345,6 @@ class KcpStreamSettings extends XrayCommonClass {
|
|||||||
json.congestion,
|
json.congestion,
|
||||||
json.readBufferSize,
|
json.readBufferSize,
|
||||||
json.writeBufferSize,
|
json.writeBufferSize,
|
||||||
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
|
|
||||||
json.seed,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,10 +357,6 @@ class KcpStreamSettings extends XrayCommonClass {
|
|||||||
congestion: this.congestion,
|
congestion: this.congestion,
|
||||||
readBufferSize: this.readBuffer,
|
readBufferSize: this.readBuffer,
|
||||||
writeBufferSize: this.writeBuffer,
|
writeBufferSize: this.writeBuffer,
|
||||||
header: {
|
|
||||||
type: this.type,
|
|
||||||
},
|
|
||||||
seed: this.seed,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,6 +487,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
|
|||||||
noSSEHeader = false,
|
noSSEHeader = false,
|
||||||
xPaddingBytes = "100-1000",
|
xPaddingBytes = "100-1000",
|
||||||
mode = MODE_OPTION.AUTO,
|
mode = MODE_OPTION.AUTO,
|
||||||
|
xPaddingObfsMode = false,
|
||||||
|
xPaddingKey = '',
|
||||||
|
xPaddingHeader = '',
|
||||||
|
xPaddingPlacement = '',
|
||||||
|
xPaddingMethod = '',
|
||||||
|
uplinkHTTPMethod = '',
|
||||||
|
sessionPlacement = '',
|
||||||
|
sessionKey = '',
|
||||||
|
seqPlacement = '',
|
||||||
|
seqKey = '',
|
||||||
|
uplinkDataPlacement = '',
|
||||||
|
uplinkDataKey = '',
|
||||||
|
uplinkChunkSize = 0,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -509,6 +511,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
|
|||||||
this.noSSEHeader = noSSEHeader;
|
this.noSSEHeader = noSSEHeader;
|
||||||
this.xPaddingBytes = xPaddingBytes;
|
this.xPaddingBytes = xPaddingBytes;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.xPaddingObfsMode = xPaddingObfsMode;
|
||||||
|
this.xPaddingKey = xPaddingKey;
|
||||||
|
this.xPaddingHeader = xPaddingHeader;
|
||||||
|
this.xPaddingPlacement = xPaddingPlacement;
|
||||||
|
this.xPaddingMethod = xPaddingMethod;
|
||||||
|
this.uplinkHTTPMethod = uplinkHTTPMethod;
|
||||||
|
this.sessionPlacement = sessionPlacement;
|
||||||
|
this.sessionKey = sessionKey;
|
||||||
|
this.seqPlacement = seqPlacement;
|
||||||
|
this.seqKey = seqKey;
|
||||||
|
this.uplinkDataPlacement = uplinkDataPlacement;
|
||||||
|
this.uplinkDataKey = uplinkDataKey;
|
||||||
|
this.uplinkChunkSize = uplinkChunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
addHeader(name, value) {
|
addHeader(name, value) {
|
||||||
@@ -530,6 +545,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
|
|||||||
json.noSSEHeader,
|
json.noSSEHeader,
|
||||||
json.xPaddingBytes,
|
json.xPaddingBytes,
|
||||||
json.mode,
|
json.mode,
|
||||||
|
json.xPaddingObfsMode,
|
||||||
|
json.xPaddingKey,
|
||||||
|
json.xPaddingHeader,
|
||||||
|
json.xPaddingPlacement,
|
||||||
|
json.xPaddingMethod,
|
||||||
|
json.uplinkHTTPMethod,
|
||||||
|
json.sessionPlacement,
|
||||||
|
json.sessionKey,
|
||||||
|
json.seqPlacement,
|
||||||
|
json.seqKey,
|
||||||
|
json.uplinkDataPlacement,
|
||||||
|
json.uplinkDataKey,
|
||||||
|
json.uplinkChunkSize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,6 +572,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
|
|||||||
noSSEHeader: this.noSSEHeader,
|
noSSEHeader: this.noSSEHeader,
|
||||||
xPaddingBytes: this.xPaddingBytes,
|
xPaddingBytes: this.xPaddingBytes,
|
||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
|
xPaddingObfsMode: this.xPaddingObfsMode,
|
||||||
|
xPaddingKey: this.xPaddingKey,
|
||||||
|
xPaddingHeader: this.xPaddingHeader,
|
||||||
|
xPaddingPlacement: this.xPaddingPlacement,
|
||||||
|
xPaddingMethod: this.xPaddingMethod,
|
||||||
|
uplinkHTTPMethod: this.uplinkHTTPMethod,
|
||||||
|
sessionPlacement: this.sessionPlacement,
|
||||||
|
sessionKey: this.sessionKey,
|
||||||
|
seqPlacement: this.seqPlacement,
|
||||||
|
seqKey: this.seqKey,
|
||||||
|
uplinkDataPlacement: this.uplinkDataPlacement,
|
||||||
|
uplinkDataKey: this.uplinkDataKey,
|
||||||
|
uplinkChunkSize: this.uplinkChunkSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,11 +596,12 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
rejectUnknownSni = false,
|
rejectUnknownSni = false,
|
||||||
verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
|
|
||||||
disableSystemRoot = false,
|
disableSystemRoot = false,
|
||||||
enableSessionResumption = false,
|
enableSessionResumption = false,
|
||||||
certificates = [new TlsStreamSettings.Cert()],
|
certificates = [new TlsStreamSettings.Cert()],
|
||||||
alpn = [ALPN_OPTION.H3, ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
alpn = [ALPN_OPTION.H3, ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
||||||
|
echServerKeys = '',
|
||||||
|
echForceQuery = 'none',
|
||||||
settings = new TlsStreamSettings.Settings()
|
settings = new TlsStreamSettings.Settings()
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -568,11 +610,12 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
this.rejectUnknownSni = rejectUnknownSni;
|
this.rejectUnknownSni = rejectUnknownSni;
|
||||||
this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
|
|
||||||
this.disableSystemRoot = disableSystemRoot;
|
this.disableSystemRoot = disableSystemRoot;
|
||||||
this.enableSessionResumption = enableSessionResumption;
|
this.enableSessionResumption = enableSessionResumption;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
|
this.echServerKeys = echServerKeys;
|
||||||
|
this.echForceQuery = echForceQuery;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +635,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.serverName, json.settings.domains);
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.echConfigList);
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
@@ -600,11 +643,12 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
json.maxVersion,
|
json.maxVersion,
|
||||||
json.cipherSuites,
|
json.cipherSuites,
|
||||||
json.rejectUnknownSni,
|
json.rejectUnknownSni,
|
||||||
json.verifyPeerCertInNames,
|
|
||||||
json.disableSystemRoot,
|
json.disableSystemRoot,
|
||||||
json.enableSessionResumption,
|
json.enableSessionResumption,
|
||||||
certs,
|
certs,
|
||||||
json.alpn,
|
json.alpn,
|
||||||
|
json.echServerKeys,
|
||||||
|
json.echForceQuery,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -616,11 +660,12 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
rejectUnknownSni: this.rejectUnknownSni,
|
rejectUnknownSni: this.rejectUnknownSni,
|
||||||
verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
|
|
||||||
disableSystemRoot: this.disableSystemRoot,
|
disableSystemRoot: this.disableSystemRoot,
|
||||||
enableSessionResumption: this.enableSessionResumption,
|
enableSessionResumption: this.enableSessionResumption,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
|
echServerKeys: this.echServerKeys,
|
||||||
|
echForceQuery: this.echForceQuery,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -701,21 +746,25 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
constructor(
|
constructor(
|
||||||
allowInsecure = false,
|
allowInsecure = false,
|
||||||
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||||
|
echConfigList = '',
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
|
this.echConfigList = echConfigList;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
|
json.echConfigList,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
|
echConfigList: this.echConfigList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -845,6 +894,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
|||||||
V6Only = false,
|
V6Only = false,
|
||||||
tcpWindowClamp = 600,
|
tcpWindowClamp = 600,
|
||||||
interfaceName = "",
|
interfaceName = "",
|
||||||
|
trustedXForwardedFor = [],
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
@@ -863,6 +913,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
|||||||
this.V6Only = V6Only;
|
this.V6Only = V6Only;
|
||||||
this.tcpWindowClamp = tcpWindowClamp;
|
this.tcpWindowClamp = tcpWindowClamp;
|
||||||
this.interfaceName = interfaceName;
|
this.interfaceName = interfaceName;
|
||||||
|
this.trustedXForwardedFor = trustedXForwardedFor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
@@ -884,11 +935,12 @@ class SockoptStreamSettings extends XrayCommonClass {
|
|||||||
json.V6Only,
|
json.V6Only,
|
||||||
json.tcpWindowClamp,
|
json.tcpWindowClamp,
|
||||||
json.interface,
|
json.interface,
|
||||||
|
json.trustedXForwardedFor || [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
const result = {
|
||||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||||
tcpFastOpen: this.tcpFastOpen,
|
tcpFastOpen: this.tcpFastOpen,
|
||||||
mark: this.mark,
|
mark: this.mark,
|
||||||
@@ -906,6 +958,71 @@ class SockoptStreamSettings extends XrayCommonClass {
|
|||||||
tcpWindowClamp: this.tcpWindowClamp,
|
tcpWindowClamp: this.tcpWindowClamp,
|
||||||
interface: this.interfaceName,
|
interface: this.interfaceName,
|
||||||
};
|
};
|
||||||
|
if (this.trustedXForwardedFor && this.trustedXForwardedFor.length > 0) {
|
||||||
|
result.trustedXForwardedFor = this.trustedXForwardedFor;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UdpMask extends XrayCommonClass {
|
||||||
|
constructor(type = 'salamander', settings = {}) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
this.settings = this._getDefaultSettings(type, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDefaultSettings(type, settings = {}) {
|
||||||
|
switch (type) {
|
||||||
|
case 'salamander':
|
||||||
|
case 'mkcp-aes128gcm':
|
||||||
|
return { password: settings.password || '' };
|
||||||
|
case 'header-dns':
|
||||||
|
case 'xdns':
|
||||||
|
return { domain: settings.domain || '' };
|
||||||
|
case 'xicmp':
|
||||||
|
return { ip: settings.ip || '', id: settings.id ?? 0 };
|
||||||
|
case 'mkcp-original':
|
||||||
|
case 'header-dtls':
|
||||||
|
case 'header-srtp':
|
||||||
|
case 'header-utp':
|
||||||
|
case 'header-wechat':
|
||||||
|
case 'header-wireguard':
|
||||||
|
return {};
|
||||||
|
default:
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new UdpMask(
|
||||||
|
json.type || 'salamander',
|
||||||
|
json.settings || {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
settings: (this.settings && Object.keys(this.settings).length > 0) ? this.settings : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FinalMaskStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(udp = []) {
|
||||||
|
super();
|
||||||
|
this.udp = Array.isArray(udp) ? udp.map(u => new UdpMask(u.type, u.settings)) : [new UdpMask(udp.type, udp.settings)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new FinalMaskStreamSettings(json.udp || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
udp: this.udp.map(udp => udp.toJson())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,6 +1038,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
grpcSettings = new GrpcStreamSettings(),
|
grpcSettings = new GrpcStreamSettings(),
|
||||||
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
||||||
xhttpSettings = new xHTTPStreamSettings(),
|
xhttpSettings = new xHTTPStreamSettings(),
|
||||||
|
finalmask = new FinalMaskStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -935,9 +1053,24 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
this.httpupgrade = httpupgradeSettings;
|
this.httpupgrade = httpupgradeSettings;
|
||||||
this.xhttp = xhttpSettings;
|
this.xhttp = xhttpSettings;
|
||||||
|
this.finalmask = finalmask;
|
||||||
this.sockopt = sockopt;
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addUdpMask(type = 'salamander') {
|
||||||
|
this.finalmask.udp.push(new UdpMask(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
delUdpMask(index) {
|
||||||
|
if (this.finalmask.udp) {
|
||||||
|
this.finalmask.udp.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasFinalMask() {
|
||||||
|
return this.finalmask.udp && this.finalmask.udp.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
return this.security === "tls";
|
return this.security === "tls";
|
||||||
}
|
}
|
||||||
@@ -984,6 +1117,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||||
|
FinalMaskStreamSettings.fromJson(json.finalmask),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1002,6 +1136,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||||
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
||||||
|
finalmask: this.hasFinalMask ? this.finalmask.toJson() : undefined,
|
||||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1127,8 +1262,8 @@ class Inbound extends XrayCommonClass {
|
|||||||
get isSSMultiUser() {
|
get isSSMultiUser() {
|
||||||
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
|
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
|
||||||
}
|
}
|
||||||
get isSS2022(){
|
get isSS2022() {
|
||||||
return this.method.substring(0,4) === "2022";
|
return this.method.substring(0, 4) === "2022";
|
||||||
}
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
@@ -1172,14 +1307,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get kcpType() {
|
|
||||||
return this.stream.kcp.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
get kcpSeed() {
|
|
||||||
return this.stream.kcp.seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
get serviceName() {
|
get serviceName() {
|
||||||
return this.stream.grpc.serviceName;
|
return this.stream.grpc.serviceName;
|
||||||
}
|
}
|
||||||
@@ -1202,6 +1329,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canEnableVisionSeed() {
|
||||||
|
if (!this.canEnableTlsFlow()) return false;
|
||||||
|
const clients = this.settings?.vlesses;
|
||||||
|
if (!Array.isArray(clients)) return false;
|
||||||
|
return clients.some(c => c?.flow === TLS_FLOW_CONTROL.VISION || c?.flow === TLS_FLOW_CONTROL.VISION_UDP443);
|
||||||
|
}
|
||||||
|
|
||||||
canEnableReality() {
|
canEnableReality() {
|
||||||
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
if (![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||||
return ["tcp", "http", "grpc", "xhttp"].includes(this.network);
|
return ["tcp", "http", "grpc", "xhttp"].includes(this.network);
|
||||||
@@ -1248,8 +1382,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
} else if (network === 'kcp') {
|
} else if (network === 'kcp') {
|
||||||
const kcp = this.stream.kcp;
|
const kcp = this.stream.kcp;
|
||||||
obj.type = kcp.type;
|
|
||||||
obj.path = kcp.seed;
|
|
||||||
} else if (network === 'ws') {
|
} else if (network === 'ws') {
|
||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
obj.path = ws.path;
|
obj.path = ws.path;
|
||||||
@@ -1295,6 +1427,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
|
params.set("encryption", this.settings.encryption);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
const tcp = this.stream.tcp;
|
const tcp = this.stream.tcp;
|
||||||
@@ -1311,8 +1444,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
case "kcp":
|
case "kcp":
|
||||||
const kcp = this.stream.kcp;
|
const kcp = this.stream.kcp;
|
||||||
params.set("headerType", kcp.type);
|
|
||||||
params.set("seed", kcp.seed);
|
|
||||||
break;
|
break;
|
||||||
case "ws":
|
case "ws":
|
||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
@@ -1351,6 +1482,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||||
|
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||||
|
}
|
||||||
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
||||||
params.set("flow", flow);
|
params.set("flow", flow);
|
||||||
}
|
}
|
||||||
@@ -1370,6 +1504,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.mldsa65Verify)) {
|
||||||
|
params.set("pqv", this.stream.reality.settings.mldsa65Verify);
|
||||||
|
}
|
||||||
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
|
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
|
||||||
params.set("flow", flow);
|
params.set("flow", flow);
|
||||||
}
|
}
|
||||||
@@ -1410,8 +1547,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
case "kcp":
|
case "kcp":
|
||||||
const kcp = this.stream.kcp;
|
const kcp = this.stream.kcp;
|
||||||
params.set("headerType", kcp.type);
|
|
||||||
params.set("seed", kcp.seed);
|
|
||||||
break;
|
break;
|
||||||
case "ws":
|
case "ws":
|
||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
@@ -1447,6 +1582,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.stream.tls.settings.allowInsecure) {
|
if (this.stream.tls.settings.allowInsecure) {
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||||
|
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||||
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
@@ -1488,8 +1626,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
break;
|
break;
|
||||||
case "kcp":
|
case "kcp":
|
||||||
const kcp = this.stream.kcp;
|
const kcp = this.stream.kcp;
|
||||||
params.set("headerType", kcp.type);
|
|
||||||
params.set("seed", kcp.seed);
|
|
||||||
break;
|
break;
|
||||||
case "ws":
|
case "ws":
|
||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
@@ -1525,6 +1661,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.stream.tls.settings.allowInsecure) {
|
if (this.stream.tls.settings.allowInsecure) {
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||||
|
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||||
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
@@ -1544,6 +1683,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.mldsa65Verify)) {
|
||||||
|
params.set("pqv", this.stream.reality.settings.mldsa65Verify);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
@@ -1664,7 +1806,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
let streamSettings;
|
let streamSettings;
|
||||||
if (this.canEnableStream()) {
|
if (this.canEnableStream() || this.stream?.sockopt) {
|
||||||
streamSettings = this.stream.toJson();
|
streamSettings = this.stream.toJson();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -1696,6 +1838,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||||
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
||||||
|
case Protocols.TUN: return new Inbound.TunSettings(protocol);
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1710,6 +1853,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||||
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
||||||
|
case Protocols.TUN: return Inbound.TunSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1721,7 +1865,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
|
|
||||||
Inbound.VmessSettings = class extends Inbound.Settings {
|
Inbound.VmessSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
vmesses=[new Inbound.VmessSettings.Vmess()]) {
|
vmesses = [new Inbound.VmessSettings.Vmess()]) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.vmesses = vmesses;
|
this.vmesses = vmesses;
|
||||||
}
|
}
|
||||||
@@ -1744,7 +1888,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.VmessSettings(
|
return new Inbound.VmessSettings(
|
||||||
Protocols.VMESS,
|
Protocols.VMESS,
|
||||||
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
||||||
@@ -1826,13 +1970,19 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
constructor(
|
constructor(
|
||||||
protocol,
|
protocol,
|
||||||
vlesses = [new Inbound.VLESSSettings.VLESS()],
|
vlesses = [new Inbound.VLESSSettings.VLESS()],
|
||||||
decryption = 'none',
|
decryption = "none",
|
||||||
fallbacks = []
|
encryption = "none",
|
||||||
|
fallbacks = [],
|
||||||
|
selectedAuth = undefined,
|
||||||
|
testseed = [900, 500, 900, 256],
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.vlesses = vlesses;
|
this.vlesses = vlesses;
|
||||||
this.decryption = decryption;
|
this.decryption = decryption;
|
||||||
|
this.encryption = encryption;
|
||||||
this.fallbacks = fallbacks;
|
this.fallbacks = fallbacks;
|
||||||
|
this.selectedAuth = selectedAuth;
|
||||||
|
this.testseed = testseed;
|
||||||
}
|
}
|
||||||
|
|
||||||
addFallback() {
|
addFallback() {
|
||||||
@@ -1843,21 +1993,51 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
this.fallbacks.splice(index, 1);
|
this.fallbacks.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// decryption should be set to static value
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.VLESSSettings(
|
let testseed = [900, 500, 900, 256];
|
||||||
|
if (json.testseed && Array.isArray(json.testseed) && json.testseed.length >= 4) {
|
||||||
|
testseed = json.testseed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = new Inbound.VLESSSettings(
|
||||||
Protocols.VLESS,
|
Protocols.VLESS,
|
||||||
json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
|
(json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
|
||||||
json.decryption || 'none',
|
json.decryption,
|
||||||
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),);
|
json.encryption,
|
||||||
|
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []),
|
||||||
|
json.selectedAuth,
|
||||||
|
testseed
|
||||||
|
);
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
const json = {
|
||||||
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
|
||||||
|
if (hasFlow && this.testseed && this.testseed.length >= 4) {
|
||||||
|
json.testseed = this.testseed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1925,7 +2105,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
||||||
constructor(name='', alpn='', path='', dest='', xver=0) {
|
constructor(name = '', alpn = '', path = '', dest = '', xver = 0) {
|
||||||
super();
|
super();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
@@ -2071,7 +2251,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
||||||
constructor(name='', alpn='', path='', dest='', xver=0) {
|
constructor(name = '', alpn = '', path = '', dest = '', xver = 0) {
|
||||||
super();
|
super();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
@@ -2231,12 +2411,14 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
protocol,
|
protocol,
|
||||||
address,
|
address,
|
||||||
port,
|
port,
|
||||||
|
portMap = [],
|
||||||
network = 'tcp,udp',
|
network = 'tcp,udp',
|
||||||
followRedirect = false
|
followRedirect = false
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
this.portMap = portMap;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.followRedirect = followRedirect;
|
this.followRedirect = followRedirect;
|
||||||
}
|
}
|
||||||
@@ -2246,6 +2428,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
Protocols.DOKODEMO,
|
Protocols.DOKODEMO,
|
||||||
json.address,
|
json.address,
|
||||||
json.port,
|
json.port,
|
||||||
|
XrayCommonClass.toHeaders(json.portMap),
|
||||||
json.network,
|
json.network,
|
||||||
json.followRedirect,
|
json.followRedirect,
|
||||||
);
|
);
|
||||||
@@ -2255,6 +2438,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
return {
|
return {
|
||||||
address: this.address,
|
address: this.address,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
|
portMap: XrayCommonClass.toV2Headers(this.portMap, false),
|
||||||
network: this.network,
|
network: this.network,
|
||||||
followRedirect: this.followRedirect,
|
followRedirect: this.followRedirect,
|
||||||
};
|
};
|
||||||
@@ -2368,55 +2552,55 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
|||||||
mtu = 1420,
|
mtu = 1420,
|
||||||
secretKey = Wireguard.generateKeypair().privateKey,
|
secretKey = Wireguard.generateKeypair().privateKey,
|
||||||
peers = [new Inbound.WireguardSettings.Peer()],
|
peers = [new Inbound.WireguardSettings.Peer()],
|
||||||
kernelMode = false
|
noKernelTun = false
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
this.secretKey = secretKey;
|
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.peers = peers;
|
||||||
this.kernelMode = kernelMode;
|
this.noKernelTun = noKernelTun;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPeer() {
|
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) {
|
delPeer(index) {
|
||||||
this.peers.splice(index, 1);
|
this.peers.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}){
|
static fromJson(json = {}) {
|
||||||
return new Inbound.WireguardSettings(
|
return new Inbound.WireguardSettings(
|
||||||
Protocols.WIREGUARD,
|
Protocols.WIREGUARD,
|
||||||
json.mtu,
|
json.mtu,
|
||||||
json.secretKey,
|
json.secretKey,
|
||||||
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
|
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
|
||||||
json.kernelMode,
|
json.noKernelTun,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
mtu: this.mtu?? undefined,
|
mtu: this.mtu ?? undefined,
|
||||||
secretKey: this.secretKey,
|
secretKey: this.secretKey,
|
||||||
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||||
kernelMode: this.kernelMode,
|
noKernelTun: this.noKernelTun,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.WireguardSettings.Peer = 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();
|
super();
|
||||||
this.privateKey = privateKey
|
this.privateKey = privateKey
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
if (!this.publicKey){
|
if (!this.publicKey) {
|
||||||
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
||||||
}
|
}
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
allowedIPs.forEach((a,index) => {
|
allowedIPs.forEach((a, index) => {
|
||||||
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
|
if (a.length > 0 && !a.includes('/')) allowedIPs[index] += '/32';
|
||||||
})
|
})
|
||||||
this.allowedIPs = allowedIPs;
|
this.allowedIPs = allowedIPs;
|
||||||
this.keepAlive = keepAlive;
|
this.keepAlive = keepAlive;
|
||||||
@@ -2445,3 +2629,34 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Inbound.TunSettings = class extends Inbound.Settings {
|
||||||
|
constructor(
|
||||||
|
protocol,
|
||||||
|
name = 'xray0',
|
||||||
|
mtu = 1500,
|
||||||
|
userLevel = 0
|
||||||
|
) {
|
||||||
|
super(protocol);
|
||||||
|
this.name = name;
|
||||||
|
this.mtu = mtu;
|
||||||
|
this.userLevel = userLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new Inbound.TunSettings(
|
||||||
|
Protocols.TUN,
|
||||||
|
json.name ?? 'xray0',
|
||||||
|
json.mtu ?? json.MTU ?? 1500,
|
||||||
|
json.userLevel ?? 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
name: this.name || 'xray0',
|
||||||
|
mtu: this.mtu || 1500,
|
||||||
|
userLevel: this.userLevel || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,15 +8,14 @@ const Protocols = {
|
|||||||
Shadowsocks: "shadowsocks",
|
Shadowsocks: "shadowsocks",
|
||||||
Socks: "socks",
|
Socks: "socks",
|
||||||
HTTP: "http",
|
HTTP: "http",
|
||||||
Wireguard: "wireguard"
|
Wireguard: "wireguard",
|
||||||
|
Hysteria: "hysteria"
|
||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
|
||||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
@@ -165,14 +164,12 @@ class TcpStreamSettings extends CommonClass {
|
|||||||
class KcpStreamSettings extends CommonClass {
|
class KcpStreamSettings extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
mtu = 1350,
|
mtu = 1350,
|
||||||
tti = 50,
|
tti = 20,
|
||||||
uplinkCapacity = 5,
|
uplinkCapacity = 5,
|
||||||
downlinkCapacity = 20,
|
downlinkCapacity = 20,
|
||||||
congestion = false,
|
congestion = false,
|
||||||
readBufferSize = 2,
|
readBufferSize = 1,
|
||||||
writeBufferSize = 2,
|
writeBufferSize = 1,
|
||||||
type = 'none',
|
|
||||||
seed = '',
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
@@ -182,8 +179,6 @@ class KcpStreamSettings extends CommonClass {
|
|||||||
this.congestion = congestion;
|
this.congestion = congestion;
|
||||||
this.readBuffer = readBufferSize;
|
this.readBuffer = readBufferSize;
|
||||||
this.writeBuffer = writeBufferSize;
|
this.writeBuffer = writeBufferSize;
|
||||||
this.type = type;
|
|
||||||
this.seed = seed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
@@ -195,8 +190,6 @@ class KcpStreamSettings extends CommonClass {
|
|||||||
json.congestion,
|
json.congestion,
|
||||||
json.readBufferSize,
|
json.readBufferSize,
|
||||||
json.writeBufferSize,
|
json.writeBufferSize,
|
||||||
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
|
|
||||||
json.seed,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,10 +202,6 @@ class KcpStreamSettings extends CommonClass {
|
|||||||
congestion: this.congestion,
|
congestion: this.congestion,
|
||||||
readBufferSize: this.readBuffer,
|
readBufferSize: this.readBuffer,
|
||||||
writeBufferSize: this.writeBuffer,
|
writeBufferSize: this.writeBuffer,
|
||||||
header: {
|
|
||||||
type: this.type,
|
|
||||||
},
|
|
||||||
seed: this.seed,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,13 +343,19 @@ class TlsStreamSettings extends CommonClass {
|
|||||||
serverName = '',
|
serverName = '',
|
||||||
alpn = [],
|
alpn = [],
|
||||||
fingerprint = '',
|
fingerprint = '',
|
||||||
allowInsecure = false
|
allowInsecure = false,
|
||||||
|
echConfigList = '',
|
||||||
|
verifyPeerCertByName = 'cloudflare-dns.com',
|
||||||
|
pinnedPeerCertSha256 = '',
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
|
this.echConfigList = echConfigList;
|
||||||
|
this.verifyPeerCertByName = verifyPeerCertByName;
|
||||||
|
this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
@@ -369,6 +364,9 @@ class TlsStreamSettings extends CommonClass {
|
|||||||
json.alpn,
|
json.alpn,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
|
json.echConfigList,
|
||||||
|
json.verifyPeerCertByName,
|
||||||
|
json.pinnedPeerCertSha256,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +376,9 @@ class TlsStreamSettings extends CommonClass {
|
|||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
|
echConfigList: this.echConfigList,
|
||||||
|
verifyPeerCertByName: this.verifyPeerCertByName,
|
||||||
|
pinnedPeerCertSha256: this.pinnedPeerCertSha256
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +389,8 @@ class RealityStreamSettings extends CommonClass {
|
|||||||
fingerprint = '',
|
fingerprint = '',
|
||||||
serverName = '',
|
serverName = '',
|
||||||
shortId = '',
|
shortId = '',
|
||||||
spiderX = '/'
|
spiderX = '',
|
||||||
|
mldsa65Verify = ''
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
@@ -396,6 +398,7 @@ class RealityStreamSettings extends CommonClass {
|
|||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
this.shortId = shortId
|
this.shortId = shortId
|
||||||
this.spiderX = spiderX;
|
this.spiderX = spiderX;
|
||||||
|
this.mldsa65Verify = mldsa65Verify;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new RealityStreamSettings(
|
return new RealityStreamSettings(
|
||||||
@@ -404,6 +407,7 @@ class RealityStreamSettings extends CommonClass {
|
|||||||
json.serverName,
|
json.serverName,
|
||||||
json.shortId,
|
json.shortId,
|
||||||
json.spiderX,
|
json.spiderX,
|
||||||
|
json.mldsa65Verify,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -413,9 +417,106 @@ class RealityStreamSettings extends CommonClass {
|
|||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
shortId: this.shortId,
|
shortId: this.shortId,
|
||||||
spiderX: this.spiderX,
|
spiderX: this.spiderX,
|
||||||
|
mldsa65Verify: this.mldsa65Verify,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class HysteriaStreamSettings extends CommonClass {
|
||||||
|
constructor(
|
||||||
|
version = 2,
|
||||||
|
auth = '',
|
||||||
|
congestion = '',
|
||||||
|
up = '0',
|
||||||
|
down = '0',
|
||||||
|
udphopPort = '',
|
||||||
|
udphopIntervalMin = 30,
|
||||||
|
udphopIntervalMax = 30,
|
||||||
|
initStreamReceiveWindow = 8388608,
|
||||||
|
maxStreamReceiveWindow = 8388608,
|
||||||
|
initConnectionReceiveWindow = 20971520,
|
||||||
|
maxConnectionReceiveWindow = 20971520,
|
||||||
|
maxIdleTimeout = 30,
|
||||||
|
keepAlivePeriod = 0,
|
||||||
|
disablePathMTUDiscovery = false
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.version = version;
|
||||||
|
this.auth = auth;
|
||||||
|
this.congestion = congestion;
|
||||||
|
this.up = up;
|
||||||
|
this.down = down;
|
||||||
|
this.udphopPort = udphopPort;
|
||||||
|
this.udphopIntervalMin = udphopIntervalMin;
|
||||||
|
this.udphopIntervalMax = udphopIntervalMax;
|
||||||
|
this.initStreamReceiveWindow = initStreamReceiveWindow;
|
||||||
|
this.maxStreamReceiveWindow = maxStreamReceiveWindow;
|
||||||
|
this.initConnectionReceiveWindow = initConnectionReceiveWindow;
|
||||||
|
this.maxConnectionReceiveWindow = maxConnectionReceiveWindow;
|
||||||
|
this.maxIdleTimeout = maxIdleTimeout;
|
||||||
|
this.keepAlivePeriod = keepAlivePeriod;
|
||||||
|
this.disablePathMTUDiscovery = disablePathMTUDiscovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
let udphopPort = '';
|
||||||
|
let udphopIntervalMin = 30;
|
||||||
|
let udphopIntervalMax = 30;
|
||||||
|
if (json.udphop) {
|
||||||
|
udphopPort = json.udphop.port || '';
|
||||||
|
if (json.udphop.interval !== undefined) {
|
||||||
|
udphopIntervalMin = json.udphop.interval;
|
||||||
|
udphopIntervalMax = json.udphop.interval;
|
||||||
|
} else {
|
||||||
|
udphopIntervalMin = json.udphop.intervalMin || 30;
|
||||||
|
udphopIntervalMax = json.udphop.intervalMax || 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new HysteriaStreamSettings(
|
||||||
|
json.version,
|
||||||
|
json.auth,
|
||||||
|
json.congestion,
|
||||||
|
json.up,
|
||||||
|
json.down,
|
||||||
|
udphopPort,
|
||||||
|
udphopIntervalMin,
|
||||||
|
udphopIntervalMax,
|
||||||
|
json.initStreamReceiveWindow,
|
||||||
|
json.maxStreamReceiveWindow,
|
||||||
|
json.initConnectionReceiveWindow,
|
||||||
|
json.maxConnectionReceiveWindow,
|
||||||
|
json.maxIdleTimeout,
|
||||||
|
json.keepAlivePeriod,
|
||||||
|
json.disablePathMTUDiscovery
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
const result = {
|
||||||
|
version: this.version,
|
||||||
|
auth: this.auth,
|
||||||
|
congestion: this.congestion,
|
||||||
|
up: this.up,
|
||||||
|
down: this.down,
|
||||||
|
initStreamReceiveWindow: this.initStreamReceiveWindow,
|
||||||
|
maxStreamReceiveWindow: this.maxStreamReceiveWindow,
|
||||||
|
initConnectionReceiveWindow: this.initConnectionReceiveWindow,
|
||||||
|
maxConnectionReceiveWindow: this.maxConnectionReceiveWindow,
|
||||||
|
maxIdleTimeout: this.maxIdleTimeout,
|
||||||
|
keepAlivePeriod: this.keepAlivePeriod,
|
||||||
|
disablePathMTUDiscovery: this.disablePathMTUDiscovery
|
||||||
|
};
|
||||||
|
if (this.udphopPort) {
|
||||||
|
result.udphop = {
|
||||||
|
port: this.udphopPort,
|
||||||
|
intervalMin: this.udphopIntervalMin,
|
||||||
|
intervalMax: this.udphopIntervalMax
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class SockoptStreamSettings extends CommonClass {
|
class SockoptStreamSettings extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
dialerProxy = "",
|
dialerProxy = "",
|
||||||
@@ -424,6 +525,7 @@ class SockoptStreamSettings extends CommonClass {
|
|||||||
tcpMptcp = false,
|
tcpMptcp = false,
|
||||||
penetrate = false,
|
penetrate = false,
|
||||||
addressPortStrategy = Address_Port_Strategy.NONE,
|
addressPortStrategy = Address_Port_Strategy.NONE,
|
||||||
|
trustedXForwardedFor = [],
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.dialerProxy = dialerProxy;
|
this.dialerProxy = dialerProxy;
|
||||||
@@ -432,6 +534,7 @@ class SockoptStreamSettings extends CommonClass {
|
|||||||
this.tcpMptcp = tcpMptcp;
|
this.tcpMptcp = tcpMptcp;
|
||||||
this.penetrate = penetrate;
|
this.penetrate = penetrate;
|
||||||
this.addressPortStrategy = addressPortStrategy;
|
this.addressPortStrategy = addressPortStrategy;
|
||||||
|
this.trustedXForwardedFor = trustedXForwardedFor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
@@ -442,12 +545,13 @@ class SockoptStreamSettings extends CommonClass {
|
|||||||
json.tcpKeepAliveInterval,
|
json.tcpKeepAliveInterval,
|
||||||
json.tcpMptcp,
|
json.tcpMptcp,
|
||||||
json.penetrate,
|
json.penetrate,
|
||||||
json.addressPortStrategy
|
json.addressPortStrategy,
|
||||||
|
json.trustedXForwardedFor || []
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
const result = {
|
||||||
dialerProxy: this.dialerProxy,
|
dialerProxy: this.dialerProxy,
|
||||||
tcpFastOpen: this.tcpFastOpen,
|
tcpFastOpen: this.tcpFastOpen,
|
||||||
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||||
@@ -455,6 +559,71 @@ class SockoptStreamSettings extends CommonClass {
|
|||||||
penetrate: this.penetrate,
|
penetrate: this.penetrate,
|
||||||
addressPortStrategy: this.addressPortStrategy
|
addressPortStrategy: this.addressPortStrategy
|
||||||
};
|
};
|
||||||
|
if (this.trustedXForwardedFor && this.trustedXForwardedFor.length > 0) {
|
||||||
|
result.trustedXForwardedFor = this.trustedXForwardedFor;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UdpMask extends CommonClass {
|
||||||
|
constructor(type = 'salamander', settings = {}) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
this.settings = this._getDefaultSettings(type, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDefaultSettings(type, settings = {}) {
|
||||||
|
switch (type) {
|
||||||
|
case 'salamander':
|
||||||
|
case 'mkcp-aes128gcm':
|
||||||
|
return { password: settings.password || '' };
|
||||||
|
case 'header-dns':
|
||||||
|
case 'xdns':
|
||||||
|
return { domain: settings.domain || '' };
|
||||||
|
case 'xicmp':
|
||||||
|
return { ip: settings.ip || '', id: settings.id ?? 0 };
|
||||||
|
case 'mkcp-original':
|
||||||
|
case 'header-dtls':
|
||||||
|
case 'header-srtp':
|
||||||
|
case 'header-utp':
|
||||||
|
case 'header-wechat':
|
||||||
|
case 'header-wireguard':
|
||||||
|
return {}; // No settings needed
|
||||||
|
default:
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new UdpMask(
|
||||||
|
json.type || 'salamander',
|
||||||
|
json.settings || {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
settings: (this.settings && Object.keys(this.settings).length > 0) ? this.settings : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FinalMaskStreamSettings extends CommonClass {
|
||||||
|
constructor(udp = []) {
|
||||||
|
super();
|
||||||
|
this.udp = Array.isArray(udp) ? udp.map(u => new UdpMask(u.type, u.settings)) : [new UdpMask(udp.type, udp.settings)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new FinalMaskStreamSettings(json.udp || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
udp: this.udp.map(udp => udp.toJson())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,6 +639,8 @@ class StreamSettings extends CommonClass {
|
|||||||
grpcSettings = new GrpcStreamSettings(),
|
grpcSettings = new GrpcStreamSettings(),
|
||||||
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
||||||
xhttpSettings = new xHTTPStreamSettings(),
|
xhttpSettings = new xHTTPStreamSettings(),
|
||||||
|
hysteriaSettings = new HysteriaStreamSettings(),
|
||||||
|
finalmask = new FinalMaskStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -483,9 +654,25 @@ class StreamSettings extends CommonClass {
|
|||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
this.httpupgrade = httpupgradeSettings;
|
this.httpupgrade = httpupgradeSettings;
|
||||||
this.xhttp = xhttpSettings;
|
this.xhttp = xhttpSettings;
|
||||||
|
this.hysteria = hysteriaSettings;
|
||||||
|
this.finalmask = finalmask;
|
||||||
this.sockopt = sockopt;
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addUdpMask(type = 'salamander') {
|
||||||
|
this.finalmask.udp.push(new UdpMask(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
delUdpMask(index) {
|
||||||
|
if (this.finalmask.udp) {
|
||||||
|
this.finalmask.udp.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasFinalMask() {
|
||||||
|
return this.finalmask.udp && this.finalmask.udp.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
return this.security === 'tls';
|
return this.security === 'tls';
|
||||||
}
|
}
|
||||||
@@ -514,6 +701,8 @@ class StreamSettings extends CommonClass {
|
|||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||||
|
HysteriaStreamSettings.fromJson(json.hysteriaSettings),
|
||||||
|
FinalMaskStreamSettings.fromJson(json.finalmask),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -531,6 +720,8 @@ class StreamSettings extends CommonClass {
|
|||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||||
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
||||||
|
hysteriaSettings: network === 'hysteria' ? this.hysteria.toJson() : undefined,
|
||||||
|
finalmask: this.hasFinalMask ? this.finalmask.toJson() : undefined,
|
||||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -590,11 +781,12 @@ class Outbound extends CommonClass {
|
|||||||
set protocol(protocol) {
|
set protocol(protocol) {
|
||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this.settings = Outbound.Settings.getSettings(protocol);
|
this.settings = Outbound.Settings.getSettings(protocol);
|
||||||
this.stream = new StreamSettings();
|
this.stream = new StreamSettings(protocol === Protocols.Hysteria ? 'hysteria' : 'tcp');
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.Hysteria].includes(this.protocol)) return false;
|
||||||
|
if (this.protocol === Protocols.Hysteria) return this.stream.network === 'hysteria';
|
||||||
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.stream.network);
|
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,18 +798,24 @@ class Outbound extends CommonClass {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canEnableVisionSeed() {
|
||||||
|
if (!this.canEnableTlsFlow()) return false;
|
||||||
|
const flow = this.settings?.flow;
|
||||||
|
return flow === TLS_FLOW_CONTROL.VISION || flow === TLS_FLOW_CONTROL.VISION_UDP443;
|
||||||
|
}
|
||||||
|
|
||||||
canEnableReality() {
|
canEnableReality() {
|
||||||
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
return ["tcp", "http", "grpc", "xhttp"].includes(this.stream.network);
|
return ["tcp", "http", "grpc", "xhttp"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableStream() {
|
canEnableStream() {
|
||||||
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
|
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.Hysteria].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableMux() {
|
canEnableMux() {
|
||||||
// Disable Mux if flow is set
|
// Disable Mux if flow is set
|
||||||
if (this.settings.flow && this.settings.flow !== '') {
|
if (this.settings?.flow && this.settings.flow !== '') {
|
||||||
this.mux.enabled = false;
|
this.mux.enabled = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -639,10 +837,6 @@ class Outbound extends CommonClass {
|
|||||||
].includes(this.protocol);
|
].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasVnext() {
|
|
||||||
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasServers() {
|
hasServers() {
|
||||||
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
}
|
}
|
||||||
@@ -655,7 +849,8 @@ class Outbound extends CommonClass {
|
|||||||
Protocols.Trojan,
|
Protocols.Trojan,
|
||||||
Protocols.Shadowsocks,
|
Protocols.Shadowsocks,
|
||||||
Protocols.Socks,
|
Protocols.Socks,
|
||||||
Protocols.HTTP
|
Protocols.HTTP,
|
||||||
|
Protocols.Hysteria
|
||||||
].includes(this.protocol);
|
].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,13 +877,19 @@ class Outbound extends CommonClass {
|
|||||||
if (this.stream?.sockopt)
|
if (this.stream?.sockopt)
|
||||||
stream = { sockopt: this.stream.sockopt.toJson() };
|
stream = { sockopt: this.stream.sockopt.toJson() };
|
||||||
}
|
}
|
||||||
|
let settingsOut = this.settings instanceof CommonClass ? this.settings.toJson() : this.settings;
|
||||||
|
if (settingsOut && typeof settingsOut === 'object') {
|
||||||
|
Object.keys(settingsOut).forEach(k => {
|
||||||
|
if (settingsOut[k] === undefined || settingsOut[k] === null) delete settingsOut[k];
|
||||||
|
});
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
tag: this.tag == '' ? undefined : this.tag,
|
|
||||||
protocol: this.protocol,
|
protocol: this.protocol,
|
||||||
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
settings: settingsOut,
|
||||||
streamSettings: stream,
|
...(this.tag ? { tag: this.tag } : {}),
|
||||||
sendThrough: this.sendThrough != "" ? this.sendThrough : undefined,
|
...(stream ? { streamSettings: stream } : {}),
|
||||||
mux: this.mux?.enabled ? this.mux : undefined,
|
...(this.sendThrough ? { sendThrough: this.sendThrough } : {}),
|
||||||
|
...(this.mux?.enabled ? { mux: this.mux } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,6 +903,9 @@ class Outbound extends CommonClass {
|
|||||||
case Protocols.Trojan:
|
case Protocols.Trojan:
|
||||||
case 'ss':
|
case 'ss':
|
||||||
return this.fromParamLink(link);
|
return this.fromParamLink(link);
|
||||||
|
case 'hysteria2':
|
||||||
|
case Protocols.Hysteria:
|
||||||
|
return this.fromHysteriaLink(link);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -778,7 +982,8 @@ class Outbound extends CommonClass {
|
|||||||
let alpn = url.searchParams.get('alpn');
|
let alpn = url.searchParams.get('alpn');
|
||||||
let allowInsecure = url.searchParams.get('allowInsecure');
|
let allowInsecure = url.searchParams.get('allowInsecure');
|
||||||
let sni = url.searchParams.get('sni') ?? '';
|
let sni = url.searchParams.get('sni') ?? '';
|
||||||
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
|
let ech = url.searchParams.get('ech') ?? '';
|
||||||
|
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1, ech);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security == 'reality') {
|
if (security == 'reality') {
|
||||||
@@ -787,7 +992,8 @@ class Outbound extends CommonClass {
|
|||||||
let sni = url.searchParams.get('sni') ?? '';
|
let sni = url.searchParams.get('sni') ?? '';
|
||||||
let sid = url.searchParams.get('sid') ?? '';
|
let sid = url.searchParams.get('sid') ?? '';
|
||||||
let spx = url.searchParams.get('spx') ?? '';
|
let spx = url.searchParams.get('spx') ?? '';
|
||||||
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
let pqv = url.searchParams.get('pqv') ?? '';
|
||||||
|
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx, pqv);
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = /([^@]+):\/\/([^@]+)@(.+):(\d+)(.*)$/;
|
const regex = /([^@]+):\/\/([^@]+)@(.+):(\d+)(.*)$/;
|
||||||
@@ -803,7 +1009,7 @@ class Outbound extends CommonClass {
|
|||||||
var settings;
|
var settings;
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VLESS:
|
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;
|
break;
|
||||||
case Protocols.Trojan:
|
case Protocols.Trojan:
|
||||||
settings = new Outbound.TrojanSettings(address, port, userData);
|
settings = new Outbound.TrojanSettings(address, port, userData);
|
||||||
@@ -820,6 +1026,74 @@ class Outbound extends CommonClass {
|
|||||||
remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port;
|
remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port;
|
||||||
return new Outbound(remark, protocol, settings, stream);
|
return new Outbound(remark, protocol, settings, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromHysteriaLink(link) {
|
||||||
|
const regex = /^hysteria2?:\/\/([^@]+)@([^:?#]+):(\d+)([^#]*)(#.*)?$/;
|
||||||
|
const match = link.match(regex);
|
||||||
|
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
let [, password, address, port, params, hash] = match;
|
||||||
|
port = parseInt(port);
|
||||||
|
let urlParams = new URLSearchParams(params);
|
||||||
|
let stream = new StreamSettings('hysteria', 'none');
|
||||||
|
|
||||||
|
stream.hysteria.auth = password;
|
||||||
|
stream.hysteria.congestion = urlParams.get('congestion') ?? '';
|
||||||
|
stream.hysteria.up = urlParams.get('up') ?? '0';
|
||||||
|
stream.hysteria.down = urlParams.get('down') ?? '0';
|
||||||
|
stream.hysteria.udphopPort = urlParams.get('mport') ?? urlParams.get('udphopPort') ?? '';
|
||||||
|
if (urlParams.has('udphopInterval')) {
|
||||||
|
const interval = parseInt(urlParams.get('udphopInterval'));
|
||||||
|
stream.hysteria.udphopIntervalMin = interval;
|
||||||
|
stream.hysteria.udphopIntervalMax = interval;
|
||||||
|
} else {
|
||||||
|
stream.hysteria.udphopIntervalMin = parseInt(urlParams.get('udphopIntervalMin') ?? '30');
|
||||||
|
stream.hysteria.udphopIntervalMax = parseInt(urlParams.get('udphopIntervalMax') ?? '30');
|
||||||
|
}
|
||||||
|
if (urlParams.has('initStreamReceiveWindow')) {
|
||||||
|
stream.hysteria.initStreamReceiveWindow = parseInt(urlParams.get('initStreamReceiveWindow'));
|
||||||
|
}
|
||||||
|
if (urlParams.has('maxStreamReceiveWindow')) {
|
||||||
|
stream.hysteria.maxStreamReceiveWindow = parseInt(urlParams.get('maxStreamReceiveWindow'));
|
||||||
|
}
|
||||||
|
if (urlParams.has('initConnectionReceiveWindow')) {
|
||||||
|
stream.hysteria.initConnectionReceiveWindow = parseInt(urlParams.get('initConnectionReceiveWindow'));
|
||||||
|
}
|
||||||
|
if (urlParams.has('maxConnectionReceiveWindow')) {
|
||||||
|
stream.hysteria.maxConnectionReceiveWindow = parseInt(urlParams.get('maxConnectionReceiveWindow'));
|
||||||
|
}
|
||||||
|
if (urlParams.has('maxIdleTimeout')) {
|
||||||
|
stream.hysteria.maxIdleTimeout = parseInt(urlParams.get('maxIdleTimeout'));
|
||||||
|
}
|
||||||
|
if (urlParams.has('keepAlivePeriod')) {
|
||||||
|
stream.hysteria.keepAlivePeriod = parseInt(urlParams.get('keepAlivePeriod'));
|
||||||
|
}
|
||||||
|
if (urlParams.has('disablePathMTUDiscovery')) {
|
||||||
|
stream.hysteria.disablePathMTUDiscovery = urlParams.get('disablePathMTUDiscovery') === 'true';
|
||||||
|
}
|
||||||
|
if (urlParams.has('obfs')) {
|
||||||
|
stream.finalmask = new FinalMaskStreamSettings([{
|
||||||
|
type: urlParams.get('obfs'),
|
||||||
|
settings: { password: urlParams.get('obfs-password') ?? '' }
|
||||||
|
}] );
|
||||||
|
}
|
||||||
|
if (urlParams.has('security')){
|
||||||
|
stream.security = urlParams.get('security');
|
||||||
|
stream.tls = new TlsStreamSettings(
|
||||||
|
urlParams.get('sni'),
|
||||||
|
urlParams.get('alpn') ? urlParams.get('alpn').split(',') : [],
|
||||||
|
urlParams.get('fp') ?? undefined,
|
||||||
|
urlParams.get('insecure') ?? urlParams.get('allowInsecure') ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings = new Outbound.HysteriaSettings(address, port, 2);
|
||||||
|
|
||||||
|
let remark = hash ? decodeURIComponent(hash.substring(1)) : `out-hysteria-${port}`;
|
||||||
|
|
||||||
|
return new Outbound(remark, Protocols.Hysteria, settings, stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Outbound.Settings = class extends CommonClass {
|
Outbound.Settings = class extends CommonClass {
|
||||||
@@ -840,6 +1114,7 @@ Outbound.Settings = class extends CommonClass {
|
|||||||
case Protocols.Socks: return new Outbound.SocksSettings();
|
case Protocols.Socks: return new Outbound.SocksSettings();
|
||||||
case Protocols.HTTP: return new Outbound.HttpSettings();
|
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||||
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
||||||
|
case Protocols.Hysteria: return new Outbound.HysteriaSettings();
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -856,6 +1131,7 @@ Outbound.Settings = class extends CommonClass {
|
|||||||
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||||
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
||||||
|
case Protocols.Hysteria: return Outbound.HysteriaSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1009,54 +1285,66 @@ Outbound.VmessSettings = class extends CommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
if (ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VmessSettings();
|
if (ObjectUtil.isEmpty(json.address) || ObjectUtil.isEmpty(json.port)) return new Outbound.VmessSettings();
|
||||||
return new Outbound.VmessSettings(
|
return new Outbound.VmessSettings(
|
||||||
json.vnext[0].address,
|
json.address,
|
||||||
json.vnext[0].port,
|
json.port,
|
||||||
json.vnext[0].users[0].id,
|
json.id,
|
||||||
json.vnext[0].users[0].security,
|
json.security,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
vnext: [{
|
address: this.address,
|
||||||
address: this.address,
|
port: this.port,
|
||||||
port: this.port,
|
id: this.id,
|
||||||
users: [{ id: this.id, security: this.security }],
|
security: this.security,
|
||||||
}],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Outbound.VLESSSettings = class extends CommonClass {
|
Outbound.VLESSSettings = class extends CommonClass {
|
||||||
constructor(address, port, id, flow, encryption = 'none') {
|
constructor(address, port, id, flow, encryption, testpre = 0, testseed = [900, 500, 900, 256]) {
|
||||||
super();
|
super();
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
this.encryption = encryption
|
this.encryption = encryption;
|
||||||
|
this.testpre = testpre;
|
||||||
|
this.testseed = testseed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
if (ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VLESSSettings();
|
if (ObjectUtil.isEmpty(json.address) || ObjectUtil.isEmpty(json.port)) return new Outbound.VLESSSettings();
|
||||||
return new Outbound.VLESSSettings(
|
return new Outbound.VLESSSettings(
|
||||||
json.vnext[0].address,
|
json.address,
|
||||||
json.vnext[0].port,
|
json.port,
|
||||||
json.vnext[0].users[0].id,
|
json.id,
|
||||||
json.vnext[0].users[0].flow,
|
json.flow,
|
||||||
json.vnext[0].users[0].encryption,
|
json.encryption,
|
||||||
|
json.testpre || 0,
|
||||||
|
json.testseed && json.testseed.length >= 4 ? json.testseed : [900, 500, 900, 256]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
const result = {
|
||||||
vnext: [{
|
address: this.address,
|
||||||
address: this.address,
|
port: this.port,
|
||||||
port: this.port,
|
id: this.id,
|
||||||
users: [{ id: this.id, flow: this.flow, encryption: 'none', }],
|
flow: this.flow,
|
||||||
}],
|
encryption: this.encryption,
|
||||||
};
|
};
|
||||||
|
if (this.flow && this.flow !== '') {
|
||||||
|
if (this.testpre > 0) {
|
||||||
|
result.testpre = this.testpre;
|
||||||
|
}
|
||||||
|
if (this.testseed && this.testseed.length >= 4) {
|
||||||
|
result.testseed = this.testseed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Outbound.TrojanSettings = class extends CommonClass {
|
Outbound.TrojanSettings = class extends CommonClass {
|
||||||
@@ -1194,7 +1482,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
domainStrategy = '',
|
domainStrategy = '',
|
||||||
reserved = '',
|
reserved = '',
|
||||||
peers = [new Outbound.WireguardSettings.Peer()],
|
peers = [new Outbound.WireguardSettings.Peer()],
|
||||||
kernelMode = false,
|
noKernelTun = false,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
@@ -1205,7 +1493,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
this.domainStrategy = domainStrategy;
|
this.domainStrategy = domainStrategy;
|
||||||
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
|
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
|
||||||
this.peers = peers;
|
this.peers = peers;
|
||||||
this.kernelMode = kernelMode;
|
this.noKernelTun = noKernelTun;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPeer() {
|
addPeer() {
|
||||||
@@ -1225,7 +1513,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
json.domainStrategy,
|
json.domainStrategy,
|
||||||
json.reserved,
|
json.reserved,
|
||||||
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
|
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
|
||||||
json.kernelMode,
|
json.noKernelTun,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1238,7 +1526,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
|||||||
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
||||||
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
||||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||||
kernelMode: this.kernelMode,
|
noKernelTun: this.noKernelTun,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1278,4 +1566,30 @@ Outbound.WireguardSettings.Peer = class extends CommonClass {
|
|||||||
keepAlive: this.keepAlive ?? undefined,
|
keepAlive: this.keepAlive ?? undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Outbound.HysteriaSettings = class extends CommonClass {
|
||||||
|
constructor(address = '', port = 443, version = 2) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
if (Object.keys(json).length === 0) return new Outbound.HysteriaSettings();
|
||||||
|
return new Outbound.HysteriaSettings(
|
||||||
|
json.address,
|
||||||
|
json.port,
|
||||||
|
json.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
version: this.version
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -95,10 +95,13 @@ function getCookie(cname) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setCookie(cname, cvalue, exdays) {
|
function setCookie(cname, cvalue, exdays) {
|
||||||
const d = new Date();
|
let expires = "";
|
||||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
if (exdays > 0) {
|
||||||
let expires = "expires=" + d.toUTCString();
|
const d = new Date();
|
||||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||||
|
expires = "expires=" + d.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=" + axios.defaults.baseURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function usageColor(data, threshold, total) {
|
function usageColor(data, threshold, total) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -9,27 +9,36 @@ import (
|
|||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
|
serverController *ServerController
|
||||||
Tgbot service.Tgbot
|
Tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
func NewAPIController(g *gin.RouterGroup, s *ServerController) *APIController {
|
||||||
a := &APIController{}
|
a := &APIController{
|
||||||
|
serverController: s,
|
||||||
|
}
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/xui/API/inbounds")
|
api := g.Group("/xui/API")
|
||||||
g.Use(a.checkLogin)
|
api.Use(a.checkLogin)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundApi(api)
|
||||||
|
a.serverApi(api)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIController) inboundApi(api *gin.RouterGroup) {
|
||||||
|
inboundsApi := api.Group("/inbounds")
|
||||||
|
|
||||||
|
a.inboundController = &InboundController{}
|
||||||
|
|
||||||
inboundRoutes := []struct {
|
inboundRoutes := []struct {
|
||||||
Method string
|
Method string
|
||||||
Path string
|
Path string
|
||||||
Handler gin.HandlerFunc
|
Handler gin.HandlerFunc
|
||||||
}{
|
}{
|
||||||
{"GET", "/createbackup", a.createBackup},
|
|
||||||
{"GET", "/", a.inboundController.getInbounds},
|
{"GET", "/", a.inboundController.getInbounds},
|
||||||
{"GET", "/get/:id", a.inboundController.getInbound},
|
{"GET", "/get/:id", a.inboundController.getInbound},
|
||||||
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
||||||
@@ -48,7 +57,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range inboundRoutes {
|
for _, route := range inboundRoutes {
|
||||||
g.Handle(route.Method, route.Path, route.Handler)
|
inboundsApi.Handle(route.Method, route.Path, route.Handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIController) serverApi(api *gin.RouterGroup) {
|
||||||
|
serverApi := api.Group("/server")
|
||||||
|
|
||||||
|
serverRoutes := []struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Handler gin.HandlerFunc
|
||||||
|
}{
|
||||||
|
{"GET", "/status", a.serverController.status},
|
||||||
|
{"GET", "/getDb", a.serverController.getDb},
|
||||||
|
{"GET", "/createbackup", a.createBackup},
|
||||||
|
{"GET", "/getConfigJson", a.serverController.getConfigJson},
|
||||||
|
{"GET", "/getXrayVersion", a.serverController.getXrayVersion},
|
||||||
|
{"GET", "/getNewVlessEnc", a.serverController.getNewVlessEnc},
|
||||||
|
{"GET", "/getNewX25519Cert", a.serverController.getNewX25519Cert},
|
||||||
|
{"GET", "/getNewmldsa65", a.serverController.getNewmldsa65},
|
||||||
|
|
||||||
|
{"POST", "/getNewEchCert", a.serverController.getNewEchCert},
|
||||||
|
{"POST", "/importDB", a.serverController.importDB},
|
||||||
|
{"POST", "/stopXrayService", a.serverController.stopXrayService},
|
||||||
|
{"POST", "/restartXrayService", a.serverController.restartXrayService},
|
||||||
|
{"POST", "/installXray/:version", a.serverController.installXray},
|
||||||
|
{"POST", "/logs/:count", a.serverController.getLogs},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range serverRoutes {
|
||||||
|
serverApi.Handle(route.Method, route.Path, route.Handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/locale"
|
"github.com/alireza0/x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -99,8 +99,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := false
|
inbound, needRestart, err := a.inboundService.AddInbound(inbound)
|
||||||
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
|
||||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -113,8 +112,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
jsonMsg(c, I18nWeb(c, "delete"), err)
|
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.DelInbound(id)
|
||||||
needRestart, err = a.inboundService.DelInbound(id)
|
|
||||||
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -135,8 +133,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
needRestart := true
|
inbound, needRestart, err := a.inboundService.UpdateInbound(inbound)
|
||||||
inbound, needRestart, err = a.inboundService.UpdateInbound(inbound)
|
|
||||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -151,9 +148,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.AddInboundClient(data)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.AddInboundClient(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -172,9 +167,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.DelInboundClient(id, clientId)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -195,9 +188,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -216,9 +207,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -292,8 +281,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
|||||||
inbound.ClientStats[index].Enable = true
|
inbound.ClientStats[index].Enable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := false
|
inbound, needRestart, err := a.inboundService.AddInbound(inbound)
|
||||||
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
|
||||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -75,20 +75,12 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
|
||||||
if err != nil {
|
|
||||||
logger.Info("Unable to get session's max age from DB")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sessionMaxAge > 0 {
|
|
||||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
|
||||||
if err != nil {
|
|
||||||
logger.Info("Unable to set session's max age")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
logger.Infof("%s logged in successfully", user.Username)
|
if err == nil {
|
||||||
|
logger.Infof("%s logged in successfully", user.Username)
|
||||||
|
} else {
|
||||||
|
logger.Error("Unable to set login user")
|
||||||
|
}
|
||||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/global"
|
"github.com/alireza0/x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -39,17 +39,20 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
g = g.Group("/server")
|
g = g.Group("/server")
|
||||||
|
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
g.POST("/status", a.status)
|
g.GET("/status", a.status)
|
||||||
g.POST("/getXrayVersion", a.getXrayVersion)
|
g.GET("/getDb", a.getDb)
|
||||||
|
g.GET("/getConfigJson", a.getConfigJson)
|
||||||
|
g.GET("/getNewmldsa65", a.getNewmldsa65)
|
||||||
|
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
|
||||||
|
g.GET("/getXrayVersion", a.getXrayVersion)
|
||||||
|
g.GET("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
|
|
||||||
|
g.POST("/getNewEchCert", a.getNewEchCert)
|
||||||
g.POST("/stopXrayService", a.stopXrayService)
|
g.POST("/stopXrayService", a.stopXrayService)
|
||||||
g.POST("/restartXrayService", a.restartXrayService)
|
g.POST("/restartXrayService", a.restartXrayService)
|
||||||
g.POST("/installXray/:version", a.installXray)
|
g.POST("/installXray/:version", a.installXray)
|
||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
|
||||||
g.GET("/getDb", a.getDb)
|
|
||||||
g.POST("/importDB", a.importDB)
|
g.POST("/importDB", a.importDB)
|
||||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
|
||||||
g.POST("/getNewmldsa65", a.getNewmldsa65)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
@@ -196,3 +199,22 @@ func (a *ServerController) getNewmldsa65(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonObj(c, cert, nil)
|
jsonObj(c, cert, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewEchCert(c *gin.Context) {
|
||||||
|
sni := c.PostForm("sni")
|
||||||
|
cert, err := a.serverService.GetNewEchCert(sni)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get ech certificate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/entity"
|
"github.com/alireza0/x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/entity"
|
"github.com/alireza0/x-ui/web/entity"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
<a-input-number v-model.number="inbound.port" :min="1" :max="65535"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -100,6 +100,11 @@
|
|||||||
{{template "form/wireguard"}}
|
{{template "form/wireguard"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- tun -->
|
||||||
|
<template v-if="inbound.protocol === Protocols.TUN">
|
||||||
|
{{template "form/tun"}}
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
<template v-if="inbound.canEnableStream()">
|
<template v-if="inbound.canEnableStream()">
|
||||||
{{template "form/streamSettings"}}
|
{{template "form/streamSettings"}}
|
||||||
|
|||||||
@@ -146,8 +146,8 @@
|
|||||||
<a-form-item label='Workers'>
|
<a-form-item label='Workers'>
|
||||||
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
|
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Kernel Mode'>
|
<a-form-item label='No Kernel Tun'>
|
||||||
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
|
<a-switch v-model="outbound.settings.noKernelTun"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -205,12 +205,27 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Vnext (vless/vmess) settings -->
|
<!-- vless/vmess user settings -->
|
||||||
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||||
<a-form-item label='ID'>
|
<a-form-item label='ID'>
|
||||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- vmess settings -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.VMess">
|
||||||
|
<a-form-item label='Security'>
|
||||||
|
<a-select v-model="outbound.settings.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- vless settings -->
|
<!-- 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()">
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
<a-form-item label='Flow'>
|
<a-form-item label='Flow'>
|
||||||
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -219,6 +234,28 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- XTLS Vision Advanced Settings -->
|
||||||
|
<template v-if="outbound.canEnableVisionSeed()">
|
||||||
|
<a-form-item label="Vision Pre-Connect">
|
||||||
|
<a-input-number v-model.number="outbound.settings.testpre" :min="0" :max="10" :style="{ width: '100%' }" placeholder="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Vision Seed">
|
||||||
|
<a-row :gutter="8">
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number v-model.number="outbound.settings.testseed[0]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number v-model.number="outbound.settings.testseed[1]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number v-model.number="outbound.settings.testseed[2]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number v-model.number="outbound.settings.testseed[3]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||||
@@ -248,7 +285,14 @@
|
|||||||
<a-form-item label='UDP over TCP'>
|
<a-form-item label='UDP over TCP'>
|
||||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- hysteria settings -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Hysteria">
|
||||||
|
<a-form-item label='Version'>
|
||||||
|
<a-input-number v-model.number="outbound.settings.version" :min="2"
|
||||||
|
:max="2" disabled></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
@@ -256,12 +300,17 @@
|
|||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<template v-if="outbound.protocol != Protocols.Hysteria">
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="ws">WebSocket</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
<a-select-option value="xhttp">XHTTP</a-select-option>
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
|
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-select-option value="hysteria">Hysteria2</a-select-option>
|
||||||
|
</template>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.network === 'tcp'">
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
@@ -282,20 +331,6 @@
|
|||||||
|
|
||||||
<!-- kcp -->
|
<!-- kcp -->
|
||||||
<template v-if="outbound.stream.network === 'kcp'">
|
<template v-if="outbound.stream.network === 'kcp'">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
|
||||||
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="none">None</a-select-option>
|
|
||||||
<a-select-option value="srtp">SRTP</a-select-option>
|
|
||||||
<a-select-option value="utp">uTP</a-select-option>
|
|
||||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
|
||||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
|
||||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
|
||||||
<a-select-option value="dns">DNS</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
|
||||||
<a-input v-model="outbound.stream.kcp.seed"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -391,6 +426,129 @@
|
|||||||
<a-input-number v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
|
<a-input-number v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- hysteria -->
|
||||||
|
<template v-if="outbound.stream.network === 'hysteria'">
|
||||||
|
<a-form-item label='Auth Password'>
|
||||||
|
<a-input v-model.trim="outbound.stream.hysteria.auth"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Congestion'>
|
||||||
|
<a-select v-model="outbound.stream.hysteria.congestion"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>BBR (Auto)</a-select-option>
|
||||||
|
<a-select-option value="brutal">Brutal</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Upload Speed'>
|
||||||
|
<a-input v-model.trim="outbound.stream.hysteria.up"
|
||||||
|
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Download Speed'>
|
||||||
|
<a-input v-model.trim="outbound.stream.hysteria.down"
|
||||||
|
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='UDP Hop Port'>
|
||||||
|
<a-input v-model.trim="outbound.stream.hysteria.udphopPort"
|
||||||
|
placeholder="e.g., 1145-1919 or 11,13,15-17"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Hop Interval Min (s)'
|
||||||
|
v-if="outbound.stream.hysteria.udphopPort">
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.udphopIntervalMin"
|
||||||
|
:min="5"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Hop Interval Max (s)'
|
||||||
|
v-if="outbound.stream.hysteria.udphopPort">
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.udphopIntervalMax"
|
||||||
|
:min="5"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Init Stream Receive'>
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.initStreamReceiveWindow"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Max Stream Receive'>
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.maxStreamReceiveWindow"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Init Connection Receive'>
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.initConnectionReceiveWindow"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Max Connection Receive'>
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.maxConnectionReceiveWindow"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Max Idle Timeout (s)'>
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.maxIdleTimeout" :min="4"
|
||||||
|
:max="120"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Keep Alive Period (s)'>
|
||||||
|
<a-input-number
|
||||||
|
v-model.number="outbound.stream.hysteria.keepAlivePeriod" :min="0"
|
||||||
|
:max="60"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Disable Path MTU'>
|
||||||
|
<a-switch
|
||||||
|
v-model="outbound.stream.hysteria.disablePathMTUDiscovery"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- finalmask settings -->
|
||||||
|
<template v-if="outbound.canEnableStream()">
|
||||||
|
<a-form-item label="UDP Masks">
|
||||||
|
<a-button icon="plus" type="primary" size="small"
|
||||||
|
@click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : (outbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns'))"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<template
|
||||||
|
v-if="outbound.stream.finalmask.udp && outbound.stream.finalmask.udp.length > 0">
|
||||||
|
<a-form v-for="(mask, index) in outbound.stream.finalmask.udp"
|
||||||
|
:key="index" :colon="false"
|
||||||
|
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
|
||||||
|
<a-icon type="delete"
|
||||||
|
@click="() => outbound.stream.delUdpMask(index)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
<a-form-item label='Type'>
|
||||||
|
<a-select v-model="mask.type"
|
||||||
|
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option
|
||||||
|
v-if="outbound.protocol === Protocols.Hysteria"
|
||||||
|
value="salamander">
|
||||||
|
Salamander (Hysteria2)
|
||||||
|
</a-select-option>
|
||||||
|
<template v-if="outbound.stream.network === 'kcp'">
|
||||||
|
<a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
|
||||||
|
<a-select-option value="header-dns">Header DNS</a-select-option>
|
||||||
|
<a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
|
||||||
|
<a-select-option value="header-srtp">Header SRTP</a-select-option>
|
||||||
|
<a-select-option value="header-utp">Header uTP</a-select-option>
|
||||||
|
<a-select-option value="header-wechat">Header WeChat Video</a-select-option>
|
||||||
|
<a-select-option value="header-wireguard">Header WireGuard</a-select-option>
|
||||||
|
<a-select-option value="mkcp-original">mKCP Original</a-select-option>
|
||||||
|
</template>
|
||||||
|
<a-select-option
|
||||||
|
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(outbound.stream.network)"
|
||||||
|
value="xdns">
|
||||||
|
xDNS (Experimental)
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Password'
|
||||||
|
v-if="['salamander', 'mkcp-aes128gcm'].includes(mask.type)">
|
||||||
|
<a-input v-model.trim="mask.settings.password"
|
||||||
|
placeholder="Obfuscation password"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Domain'
|
||||||
|
v-if="['header-dns', 'xdns'].includes(mask.type)">
|
||||||
|
<a-input v-model.trim="mask.settings.domain"
|
||||||
|
placeholder="e.g., www.example.com"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
@@ -422,9 +580,20 @@
|
|||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</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-form-item label="Allow Insecure">
|
||||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Verify Peer Cert By Name">
|
||||||
|
<a-input v-model.trim="outbound.stream.tls.verifyPeerCertByName"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Pinned Peer Cert">
|
||||||
|
<a-input v-model.trim="outbound.stream.tls.pinnedPeerCertSha256"
|
||||||
|
placeholder="Enter SHA256 fingerprints (base64)">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
@@ -447,6 +616,9 @@
|
|||||||
<a-form-item label="Public Key">
|
<a-form-item label="Public Key">
|
||||||
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="mldsa65 Verify">
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.mldsa65Verify"></a-input>
|
||||||
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -477,6 +649,15 @@
|
|||||||
<a-form-item label="Penetrate">
|
<a-form-item label="Penetrate">
|
||||||
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
|
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Trusted X-Forwarded-For">
|
||||||
|
<a-select mode="tags" v-model="outbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
|
||||||
|
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
|
||||||
|
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
|
||||||
|
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- mux settings -->
|
<!-- mux settings -->
|
||||||
@@ -503,7 +684,7 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
<a-form-item style="margin: 10px 0">
|
<a-form-item style="margin: 10px 0">
|
||||||
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"></a-input>
|
||||||
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||||
|
|||||||
@@ -6,6 +6,19 @@
|
|||||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.portMap"}}'>
|
||||||
|
<a-button size="small" @click="inbound.settings.portMap.push({name: '', value: ''})">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
|
<a-input-group compact v-for="(pm, index) in inbound.settings.portMap">
|
||||||
|
<a-input style="width: 50%" v-model.trim="pm.name" placeholder='{{ i18n "pages.inbounds.port"}}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="pm.value" placeholder='{{ i18n "pages.inbounds.targetAddress" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="inbound.settings.portMap.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
@@ -17,4 +30,8 @@
|
|||||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<!-- sockopt -->
|
||||||
|
<template>
|
||||||
|
{{template "form/streamSockopt"}}
|
||||||
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
44
web/html/xui/form/protocol/tun.html
Normal file
44
web/html/xui/form/protocol/tun.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{{define "form/tun"}}
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.tun.nameDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
Interface Name
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="inbound.settings.name"
|
||||||
|
placeholder="xray0"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.tun.mtuDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
MTU
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model.number="inbound.settings.mtu" :min="1"
|
||||||
|
:max="9000" placeholder="1500"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.tun.userLevelDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "pages.xray.tun.userLevel" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model.number="inbound.settings.userLevel" :min="0"
|
||||||
|
placeholder="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
{{end}}
|
||||||
@@ -20,7 +20,29 @@
|
|||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||||
@@ -53,4 +75,33 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="inbound.canEnableVisionSeed()">
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label="Vision Seed">
|
||||||
|
<a-row :gutter="8">
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : 900" @change="(val) => updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : 500" @change="(val) => updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : 900" @change="(val) => updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : 256" @change="(val) => updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-space :size="8" :style="{ marginTop: '8px' }">
|
||||||
|
<a-button type="primary" @click="setRandomTestseed">
|
||||||
|
Rand
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="resetTestseed">
|
||||||
|
Reset
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||||
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
<a-form-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Kernel Mode'>
|
<a-form-item label='No Kernel Tun'>
|
||||||
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
|
<a-switch v-model="inbound.settings.noKernelTun"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Peers">
|
<a-form-item label="Peers">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
||||||
|
|||||||
@@ -40,7 +40,10 @@
|
|||||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<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>
|
||||||
<a-form-item label="mldsa65 Seed">
|
<a-form-item label="mldsa65 Seed">
|
||||||
<a-input v-model="inbound.stream.reality.mldsa65Seed"></a-input>
|
<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-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<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>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65535"></a-input-number>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||||
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
69
web/html/xui/form/stream/stream_finalmask.html
Normal file
69
web/html/xui/form/stream/stream_finalmask.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{{define "form/streamFinalMask"}}
|
||||||
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||||
|
:wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label="UDP Masks">
|
||||||
|
<a-button icon="plus" type="primary" size="small"
|
||||||
|
@click="inbound.stream.addUdpMask(inbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns')"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<template
|
||||||
|
v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0">
|
||||||
|
<a-form v-for="(mask, index) in inbound.stream.finalmask.udp"
|
||||||
|
:key="index" :colon="false"
|
||||||
|
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
|
||||||
|
<a-icon type="delete"
|
||||||
|
@click="() => inbound.stream.delUdpMask(index)"
|
||||||
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
|
</a-divider>
|
||||||
|
<a-form-item label='Type'>
|
||||||
|
<a-select v-model="mask.type"
|
||||||
|
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="mkcp-aes128gcm">
|
||||||
|
mKCP AES-128-GCM</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="header-dns">
|
||||||
|
Header DNS</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="header-dtls">
|
||||||
|
Header DTLS 1.2</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="header-srtp">
|
||||||
|
Header SRTP</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="header-utp">
|
||||||
|
Header uTP</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="header-wechat">
|
||||||
|
Header WeChat Video</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="header-wireguard">
|
||||||
|
Header WireGuard</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="mkcp-original">
|
||||||
|
mKCP Original</a-select-option>
|
||||||
|
<a-select-option
|
||||||
|
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(inbound.stream.network)"
|
||||||
|
value="xdns">
|
||||||
|
xDNS (Experimental)</a-select-option>
|
||||||
|
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||||
|
value="xicmp">
|
||||||
|
xICMP (Experimental)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Password'
|
||||||
|
v-if="['mkcp-aes128gcm'].includes(mask.type)">
|
||||||
|
<a-input v-model.trim="mask.settings.password"
|
||||||
|
placeholder="Obfuscation password"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Domain'
|
||||||
|
v-if="['header-dns', 'xdns'].includes(mask.type)">
|
||||||
|
<a-input v-model.trim="mask.settings.domain"
|
||||||
|
placeholder="e.g., www.example.com"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
|
{{end}}
|
||||||
@@ -1,48 +1,32 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="none">None</a-select-option>
|
|
||||||
<a-select-option value="srtp">SRTP</a-select-option>
|
|
||||||
<a-select-option value="utp">uTP</a-select-option>
|
|
||||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
|
||||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
|
||||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
|
||||||
<a-select-option value="dns">DNS</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<template slot="label">
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "reset" }}</span>
|
|
||||||
</template>
|
|
||||||
{{ i18n "password" }}
|
|
||||||
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576"
|
||||||
|
:max="1460"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='TTI (ms)'>
|
<a-form-item label='TTI (ms)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10"
|
||||||
|
:max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Uplink (MB/s)'>
|
<a-form-item label='Uplink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.upCap"
|
||||||
</a-form-item>
|
:min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='Downlink (MB/s)'>
|
<a-form-item label='Downlink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.downCap"
|
||||||
|
:min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Congestion'>
|
<a-form-item label='Congestion'>
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Read Buffer (MB)'>
|
<a-form-item label='Read Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"
|
||||||
|
:min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Write Buffer (MB)'>
|
<a-form-item label='Write Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"
|
||||||
|
:min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -44,6 +44,12 @@
|
|||||||
{{template "form/streamXHTTP"}}
|
{{template "form/streamXHTTP"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- finalmask - only for TCP, WS, HTTPUpgrade, XHTTP, mKCP -->
|
||||||
|
<template
|
||||||
|
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)">
|
||||||
|
{{template "form/streamFinalMask"}}
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- sockopt -->
|
<!-- sockopt -->
|
||||||
<template>
|
<template>
|
||||||
{{template "form/streamSockopt"}}
|
{{template "form/streamSockopt"}}
|
||||||
|
|||||||
@@ -1,24 +1,73 @@
|
|||||||
{{define "form/streamSockopt"}}
|
{{define "form/streamSockopt"}}
|
||||||
<a-divider style="margin:0;"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="TPROXY">
|
<a-form-item label="Sockopt">
|
||||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.sockoptSwitch">
|
<template v-if="inbound.stream.sockoptSwitch">
|
||||||
<a-form-item label="PROXY Protocol">
|
<a-form-item label="Route Mark">
|
||||||
|
<a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP Keep Alive Interval">
|
||||||
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP Keep Alive Idle">
|
||||||
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP Max Seg">
|
||||||
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP User Timeout">
|
||||||
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TCP Window Clamp">
|
||||||
|
<a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Proxy Protocol">
|
||||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Fast Open">
|
<a-form-item label="TCP Fast Open">
|
||||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Route Mark">
|
<a-form-item label="Multipath TCP">
|
||||||
<a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TPROXY">
|
<a-form-item label="Penetrate">
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="V6 Only">
|
||||||
|
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Domain Strategy'>
|
||||||
|
<a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='TCP Congestion'>
|
||||||
|
<a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="TProxy">
|
||||||
|
<a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="off">Off</a-select-option>
|
<a-select-option value="off">Off</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
<a-select-option value="tproxy">TPROXY</a-select-option>
|
<a-select-option value="tproxy">TProxy</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Dialer Proxy">
|
||||||
|
<a-input v-model="inbound.stream.sockopt.dialerProxy"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Interface Name">
|
||||||
|
<a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Trusted X-Forwarded-For">
|
||||||
|
<a-select mode="tags" v-model="inbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
|
||||||
|
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
|
||||||
|
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
|
||||||
|
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -39,6 +39,95 @@
|
|||||||
<a-form-item label="Padding Bytes">
|
<a-form-item label="Padding Bytes">
|
||||||
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
|
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Padding Obfs Mode">
|
||||||
|
<a-switch v-model="inbound.stream.xhttp.xPaddingObfsMode"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="inbound.stream.xhttp.xPaddingObfsMode">
|
||||||
|
<a-form-item label="Padding Key">
|
||||||
|
<a-input v-model.trim="inbound.stream.xhttp.xPaddingKey"
|
||||||
|
placeholder="x_padding"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Padding Header">
|
||||||
|
<a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader"
|
||||||
|
placeholder="X-Padding"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Padding Placement">
|
||||||
|
<a-select v-model="inbound.stream.xhttp.xPaddingPlacement"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>Default (queryInHeader)</a-select-option>
|
||||||
|
<a-select-option
|
||||||
|
value="queryInHeader">queryInHeader</a-select-option>
|
||||||
|
<a-select-option value="header">header</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Padding Method">
|
||||||
|
<a-select v-model="inbound.stream.xhttp.xPaddingMethod"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>Default (repeat-x)</a-select-option>
|
||||||
|
<a-select-option value="repeat-x">repeat-x</a-select-option>
|
||||||
|
<a-select-option value="tokenish">tokenish</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item label="Uplink HTTP Method">
|
||||||
|
<a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>Default (POST)</a-select-option>
|
||||||
|
<a-select-option value="POST">POST</a-select-option>
|
||||||
|
<a-select-option value="PUT">PUT</a-select-option>
|
||||||
|
<a-select-option value="GET">GET (packet-up only)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Session Placement">
|
||||||
|
<a-select v-model="inbound.stream.xhttp.sessionPlacement"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>Default (path)</a-select-option>
|
||||||
|
<a-select-option value="path">path</a-select-option>
|
||||||
|
<a-select-option value="header">header</a-select-option>
|
||||||
|
<a-select-option value="cookie">cookie</a-select-option>
|
||||||
|
<a-select-option value="query">query</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Session Key"
|
||||||
|
v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'">
|
||||||
|
<a-input v-model.trim="inbound.stream.xhttp.sessionKey"
|
||||||
|
placeholder="x_session"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Sequence Placement">
|
||||||
|
<a-select v-model="inbound.stream.xhttp.seqPlacement"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>Default (path)</a-select-option>
|
||||||
|
<a-select-option value="path">path</a-select-option>
|
||||||
|
<a-select-option value="header">header</a-select-option>
|
||||||
|
<a-select-option value="cookie">cookie</a-select-option>
|
||||||
|
<a-select-option value="query">query</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Sequence Key"
|
||||||
|
v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'">
|
||||||
|
<a-input v-model.trim="inbound.stream.xhttp.seqKey"
|
||||||
|
placeholder="x_seq"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Uplink Data Placement"
|
||||||
|
v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||||
|
<a-select v-model="inbound.stream.xhttp.uplinkDataPlacement"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value>Default (body)</a-select-option>
|
||||||
|
<a-select-option value="body">body</a-select-option>
|
||||||
|
<a-select-option value="header">header</a-select-option>
|
||||||
|
<a-select-option value="query">query</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Uplink Data Key"
|
||||||
|
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
|
||||||
|
<a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey"
|
||||||
|
placeholder="x_data"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Uplink Chunk Size"
|
||||||
|
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
|
||||||
|
<a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize"
|
||||||
|
:min="0" placeholder="0 (unlimited)"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="No SSE Header">
|
<a-form-item label="No SSE Header">
|
||||||
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
|
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -52,6 +52,13 @@
|
|||||||
<a-form-item label="Reject Unknown SNI">
|
<a-form-item label="Reject Unknown SNI">
|
||||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Disable System Root">
|
||||||
|
<a-switch v-model="inbound.stream.tls.disableSystemRoot"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Session Resumption">
|
||||||
|
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-divider :style="{ margin: '0' }"></a-divider>
|
||||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
@@ -87,6 +94,23 @@
|
|||||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<a-form-item label='ECH key'>
|
||||||
|
<a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='ECH config'>
|
||||||
|
<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-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label=" ">
|
||||||
|
<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>
|
</template>
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
|
|||||||
@@ -51,12 +51,7 @@
|
|||||||
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
|
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="inbound.isKcp">
|
|
||||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
|
|
||||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="inbound.isGrpc">
|
<template v-if="inbound.isGrpc">
|
||||||
<tr><td>grpc serviceName</td><td>
|
<tr><td>grpc serviceName</td><td>
|
||||||
<a-tooltip :title="[[ inbound.serviceName ]]">
|
<a-tooltip :title="[[ inbound.serviceName ]]">
|
||||||
@@ -71,9 +66,18 @@
|
|||||||
{{ i18n "security" }}
|
{{ i18n "security" }}
|
||||||
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||||
<br />
|
<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'">
|
<template v-if="inbound.stream.security != 'none'">
|
||||||
{{ i18n "domainName" }}
|
{{ i18n "domainName" }}
|
||||||
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : ''
|
||||||
|
]]</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||||
@@ -285,8 +289,8 @@
|
|||||||
<td>[[ inbound.settings.mtu ]]</td>
|
<td>[[ inbound.settings.mtu ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Kernel Mode</td>
|
<td>No Kernel Tun</td>
|
||||||
<td>[[ inbound.settings.kernelMode ]]</td>
|
<td>[[ inbound.settings.noKernelTun ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="(peer, index) in inbound.settings.peers">
|
<template v-for="(peer, index) in inbound.settings.peers">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -434,4 +438,4 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -26,6 +26,11 @@
|
|||||||
} else {
|
} else {
|
||||||
this.inbound = new Inbound();
|
this.inbound = new Inbound();
|
||||||
}
|
}
|
||||||
|
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
|
||||||
|
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed) || this.inbound.settings.testseed.length < 4) {
|
||||||
|
this.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (dbInbound) {
|
if (dbInbound) {
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
} else {
|
} else {
|
||||||
@@ -42,6 +47,27 @@
|
|||||||
loading(loading=true) {
|
loading(loading=true) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
|
updateTestseed(index, value) {
|
||||||
|
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||||
|
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed)) {
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256];
|
||||||
|
}
|
||||||
|
while (inModal.inbound.settings.testseed.length <= index) {
|
||||||
|
inModal.inbound.settings.testseed.push(0);
|
||||||
|
}
|
||||||
|
inModal.inbound.settings.testseed[index] = value;
|
||||||
|
},
|
||||||
|
setRandomTestseed() {
|
||||||
|
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||||
|
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4) {
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||||
|
}
|
||||||
|
inModal.inbound.settings.testseed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
|
||||||
|
},
|
||||||
|
resetTestseed() {
|
||||||
|
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||||
|
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
@@ -124,7 +150,7 @@
|
|||||||
},
|
},
|
||||||
async getNewX25519Cert(){
|
async getNewX25519Cert(){
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
const msg = await HttpUtil.get('/server/getNewX25519Cert');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
@@ -132,9 +158,13 @@
|
|||||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||||
},
|
},
|
||||||
|
clearX25519Cert() {
|
||||||
|
this.inbound.stream.reality.privateKey = '';
|
||||||
|
this.inbound.stream.reality.settings.publicKey = '';
|
||||||
|
},
|
||||||
async getNewmldsa65() {
|
async getNewmldsa65() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewmldsa65');
|
const msg = await HttpUtil.get('/server/getNewmldsa65');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
@@ -142,6 +172,66 @@
|
|||||||
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
||||||
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
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});
|
||||||
|
inModal.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
updateTestseed(index, value) {
|
||||||
|
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed)) {
|
||||||
|
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
|
||||||
|
}
|
||||||
|
while (this.inbound.settings.testseed.length <= index) {
|
||||||
|
this.inbound.settings.testseed.push(0);
|
||||||
|
}
|
||||||
|
this.$set(this.inbound.settings.testseed, index, value);
|
||||||
|
},
|
||||||
|
setRandomTestseed() {
|
||||||
|
const newSeed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
|
||||||
|
this.$set(this.inbound.settings, 'testseed', newSeed);
|
||||||
|
},
|
||||||
|
resetTestseed() {
|
||||||
|
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -925,7 +925,11 @@
|
|||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()){
|
||||||
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
} else if (inbound.stream?.sockopt) {
|
||||||
|
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||||
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/xui/inbound/add', data, inModal);
|
||||||
@@ -944,7 +948,11 @@
|
|||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()){
|
||||||
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
} else if (inbound.stream?.sockopt) {
|
||||||
|
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||||
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
|
|||||||
@@ -535,7 +535,7 @@
|
|||||||
this.loadingTip = tip;
|
this.loadingTip = tip;
|
||||||
},
|
},
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
const msg = await HttpUtil.get('/server/status');
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.setStatus(msg.obj);
|
this.setStatus(msg.obj);
|
||||||
}
|
}
|
||||||
@@ -545,7 +545,7 @@
|
|||||||
},
|
},
|
||||||
async openSelectV2rayVersion() {
|
async openSelectV2rayVersion() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getXrayVersion');
|
const msg = await HttpUtil.get('server/getXrayVersion');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
@@ -595,7 +595,7 @@
|
|||||||
},
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
const msg = await HttpUtil.get('server/getConfigJson');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -299,7 +299,14 @@
|
|||||||
v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
||||||
<setting-list-item style="padding: 10px 20px" type="text"
|
<setting-list-item style="padding: 10px 20px" type="text"
|
||||||
title='Interval' v-model="fragmentInterval"
|
title='Interval' v-model="fragmentInterval"
|
||||||
placeholder="10-20"></setting-list-item>
|
placeholder="10-20">
|
||||||
|
</setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>MaxSplit</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="fragmentMaxSplit" placeholder="300-400"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
@@ -470,33 +477,14 @@
|
|||||||
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
||||||
remarkSample: '',
|
remarkSample: '',
|
||||||
defaultFragment: {
|
defaultFragment: {
|
||||||
tag: "fragment",
|
packets: "tlshello",
|
||||||
protocol: "freedom",
|
length: "100-200",
|
||||||
settings: {
|
interval: "10-20",
|
||||||
domainStrategy: "AsIs",
|
maxSplit: "300-400"
|
||||||
fragment: {
|
|
||||||
packets: "tlshello",
|
|
||||||
length: "100-200",
|
|
||||||
interval: "10-20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
streamSettings: {
|
|
||||||
sockopt: {
|
|
||||||
tcpKeepAliveIdle: 100,
|
|
||||||
penetrate: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultNoises: {
|
|
||||||
tag: "noises",
|
|
||||||
protocol: "freedom",
|
|
||||||
settings: {
|
|
||||||
domainStrategy: "AsIs",
|
|
||||||
noises: [
|
|
||||||
{ type: "rand", packet: "10-20", delay: "10-16" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
defaultNoises: [
|
||||||
|
{ type: "rand", packet: "10-20", delay: "10-16" }
|
||||||
|
],
|
||||||
defaultMux: {
|
defaultMux: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
concurrency: 8,
|
concurrency: 8,
|
||||||
@@ -653,35 +641,45 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentPackets: {
|
fragmentPackets: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).packets : ""; },
|
||||||
set: function (v) {
|
set: function (v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.settings.fragment.packets = v;
|
newFragment.packets = v;
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentLength: {
|
fragmentLength: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).length : ""; },
|
||||||
set: function (v) {
|
set: function (v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.settings.fragment.length = v;
|
newFragment.length = v;
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fragmentInterval: {
|
fragmentInterval: {
|
||||||
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).interval : ""; },
|
||||||
set: function (v) {
|
set: function (v) {
|
||||||
if (v != "") {
|
if (v != "") {
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
newFragment.settings.fragment.interval = v;
|
newFragment.interval = v;
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
fragmentMaxSplit: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).maxSplit : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.maxSplit = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
noises: {
|
noises: {
|
||||||
get() {
|
get() {
|
||||||
return this.allSetting?.subJsonNoises != "";
|
return this.allSetting?.subJsonNoises != "";
|
||||||
@@ -696,13 +694,11 @@
|
|||||||
},
|
},
|
||||||
noisesArray: {
|
noisesArray: {
|
||||||
get() {
|
get() {
|
||||||
return this.noises ? JSON.parse(this.allSetting.subJsonNoises).settings.noises : [];
|
return this.noises ? JSON.parse(this.allSetting.subJsonNoises) : [];
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
if (this.noises) {
|
if (this.noises) {
|
||||||
const newNoises = JSON.parse(this.allSetting.subJsonNoises);
|
this.allSetting.subJsonNoises = JSON.stringify(value);
|
||||||
newNoises.settings.noises = value;
|
|
||||||
this.allSetting.subJsonNoises = JSON.stringify(newNoises);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ new Vue({
|
|||||||
publicKey: peer.public_key,
|
publicKey: peer.public_key,
|
||||||
endpoint: peer.endpoint.host,
|
endpoint: peer.endpoint.host,
|
||||||
}],
|
}],
|
||||||
kernelMode: false
|
noKernelTun: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -856,7 +856,7 @@
|
|||||||
tag: "direct",
|
tag: "direct",
|
||||||
protocol: "freedom"
|
protocol: "freedom"
|
||||||
},
|
},
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IpIfNonMatch", "IpOnDemand"],
|
||||||
log: {
|
log: {
|
||||||
loglevel: ["none", "debug", "info", "warning", "error"],
|
loglevel: ["none", "debug", "info", "warning", "error"],
|
||||||
access: ["none", "./access.log"],
|
access: ["none", "./access.log"],
|
||||||
@@ -1087,7 +1087,9 @@
|
|||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
case Protocols.VMess:
|
case Protocols.VMess:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
serverObj = o.settings.vnext;
|
if (o.settings && o.settings.address && o.settings.port) {
|
||||||
|
return [o.settings.address + ':' + o.settings.port];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Protocols.HTTP:
|
case Protocols.HTTP:
|
||||||
case Protocols.Socks:
|
case Protocols.Socks:
|
||||||
|
|||||||
@@ -220,13 +220,13 @@
|
|||||||
newRule = {};
|
newRule = {};
|
||||||
rule.type = "field";
|
rule.type = "field";
|
||||||
rule.domainMatcher = value.domainMatcher;
|
rule.domainMatcher = value.domainMatcher;
|
||||||
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
|
rule.domain = value.domain.length>0 ? value.domain.split(',').map(s => s.trim()) : [];
|
||||||
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
|
rule.ip = value.ip.length>0 ? value.ip.split(',').map(s => s.trim()) : [];
|
||||||
rule.port = value.port;
|
rule.port = value.port;
|
||||||
rule.sourcePort = value.sourcePort;
|
rule.sourcePort = value.sourcePort;
|
||||||
rule.network = value.network;
|
rule.network = value.network;
|
||||||
rule.source = value.source.length>0 ? value.source.split(',') : [];
|
rule.source = value.source.length>0 ? value.source.split(',').map(s => s.trim()) : [];
|
||||||
rule.user = value.user.length>0 ? value.user.split(',') : [];
|
rule.user = value.user.length>0 ? value.user.split(',').map(s => s.trim()) : [];
|
||||||
rule.inboundTag = value.inboundTag;
|
rule.inboundTag = value.inboundTag;
|
||||||
rule.protocol = value.protocol;
|
rule.protocol = value.protocol;
|
||||||
rule.attrs = Object.fromEntries(value.attrs);
|
rule.attrs = Object.fromEntries(value.attrs);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
)
|
)
|
||||||
@@ -20,7 +20,11 @@ func NewCheckCpuJob() *CheckCpuJob {
|
|||||||
|
|
||||||
// Here run is a interface method of Job interface
|
// Here run is a interface method of Job interface
|
||||||
func (j *CheckCpuJob) Run() {
|
func (j *CheckCpuJob) Run() {
|
||||||
threshold, _ := j.settingService.GetTgCpu()
|
threshold, err := j.settingService.GetTgCpu()
|
||||||
|
if err != nil || threshold <= 0 {
|
||||||
|
// If threshold cannot be retrieved or is not set, skip sending notifications
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// get latest status of server
|
// get latest status of server
|
||||||
percent, err := cpu.Percent(1*time.Second, false)
|
percent, err := cpu.Percent(1*time.Second, false)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import "x-ui/web/service"
|
import "github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
type CheckXrayRunningJob struct {
|
type CheckXrayRunningJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginStatus byte
|
type LoginStatus byte
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
@@ -48,10 +48,10 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
func createTemplateData(params []string, separator ...string) map[string]interface{} {
|
||||||
var sep string = "=="
|
var sep string = "=="
|
||||||
if len(seperator) > 0 {
|
if len(separator) > 0 {
|
||||||
sep = seperator[0]
|
sep = separator[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData := make(map[string]interface{})
|
templateData := make(map[string]interface{})
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -19,6 +19,10 @@ type InboundService struct {
|
|||||||
xrayApi xray.XrayAPI
|
xrayApi xray.XrayAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
safeBatchSize = 500
|
||||||
|
)
|
||||||
|
|
||||||
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
@@ -789,6 +793,11 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type newExpiryTime struct {
|
||||||
|
Email string
|
||||||
|
NewExpiryTime int64
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
// Empty onlineUsers
|
// Empty onlineUsers
|
||||||
@@ -805,9 +814,18 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
|||||||
emails = append(emails, traffic.Email)
|
emails = append(emails, traffic.Email)
|
||||||
}
|
}
|
||||||
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
||||||
err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
for i := 0; i < len(emails); i += safeBatchSize {
|
||||||
if err != nil {
|
end := i + safeBatchSize
|
||||||
return err
|
if end > len(emails) {
|
||||||
|
end = len(emails)
|
||||||
|
}
|
||||||
|
|
||||||
|
batchClientTraffics := make([]*xray.ClientTraffic, 0, end-i)
|
||||||
|
err = tx.Model(xray.ClientTraffic{}).Where("email IN ?", emails[i:end]).Find(&batchClientTraffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbClientTraffics = append(dbClientTraffics, batchClientTraffics...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid empty slice error
|
// Avoid empty slice error
|
||||||
@@ -815,7 +833,20 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
|
inboundExpiryTimeMap := make(map[int][]newExpiryTime, 0)
|
||||||
|
for index, t := range dbClientTraffics {
|
||||||
|
if t.ExpiryTime < 0 {
|
||||||
|
newClientExpiryTime := (time.Now().Unix() * 1000) - int64(t.ExpiryTime)
|
||||||
|
newExpiryTime := newExpiryTime{
|
||||||
|
Email: t.Email,
|
||||||
|
NewExpiryTime: newClientExpiryTime,
|
||||||
|
}
|
||||||
|
inboundExpiryTimeMap[t.InboundId] = append(inboundExpiryTimeMap[t.InboundId], newExpiryTime)
|
||||||
|
dbClientTraffics[index].ExpiryTime = newClientExpiryTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.adjustTraffics(tx, inboundExpiryTimeMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -836,66 +867,90 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set onlineUsers
|
// Set onlineUsers
|
||||||
p.SetOnlineClients(onlineClients)
|
if p != nil {
|
||||||
|
p.SetOnlineClients(onlineClients)
|
||||||
|
}
|
||||||
|
|
||||||
err = tx.Save(dbClientTraffics).Error
|
for i := 0; i < len(dbClientTraffics); i += safeBatchSize {
|
||||||
if err != nil {
|
end := i + safeBatchSize
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
if end > len(dbClientTraffics) {
|
||||||
|
end = len(dbClientTraffics)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(dbClientTraffics[i:end]).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) {
|
func (s *InboundService) adjustTraffics(tx *gorm.DB, inboundExpiryTimeMap map[int][]newExpiryTime) error {
|
||||||
inboundIds := make([]int, 0, len(dbClientTraffics))
|
if len(inboundExpiryTimeMap) == 0 {
|
||||||
for _, dbClientTraffic := range dbClientTraffics {
|
return nil
|
||||||
if dbClientTraffic.ExpiryTime < 0 {
|
}
|
||||||
inboundIds = append(inboundIds, dbClientTraffic.InboundId)
|
|
||||||
|
inboundIds := make([]int, 0)
|
||||||
|
for inId := range inboundExpiryTimeMap {
|
||||||
|
inboundIds = append(inboundIds, inId)
|
||||||
|
}
|
||||||
|
inbounds := make([]*model.Inbound, 0, len(inboundIds))
|
||||||
|
for i := 0; i < len(inboundIds); i += safeBatchSize {
|
||||||
|
end := i + safeBatchSize
|
||||||
|
if end > len(inboundIds) {
|
||||||
|
end = len(inboundIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
batchInbounds := make([]*model.Inbound, 0, end-i)
|
||||||
|
err := tx.Model(model.Inbound{}).Where("id IN ?", inboundIds[i:end]).Find(&batchInbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbounds = append(inbounds, batchInbounds...)
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients, ok := settings["clients"].([]interface{})
|
||||||
|
inbEmails := inboundExpiryTimeMap[inbounds[inbound_index].Id]
|
||||||
|
if ok {
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
for index := range inbEmails {
|
||||||
|
if c["email"] == inbEmails[index].Email {
|
||||||
|
c["expiryTime"] = inbEmails[index].NewExpiryTime
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inboundIds) > 0 {
|
for i := 0; i < len(inbounds); i += safeBatchSize {
|
||||||
var inbounds []*model.Inbound
|
end := i + safeBatchSize
|
||||||
err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error
|
if end > len(inbounds) {
|
||||||
if err != nil {
|
end = len(inbounds)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
for inbound_index := range inbounds {
|
|
||||||
settings := map[string]interface{}{}
|
|
||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
|
||||||
clients, ok := settings["clients"].([]interface{})
|
|
||||||
if ok {
|
|
||||||
var newClients []interface{}
|
|
||||||
for client_index := range clients {
|
|
||||||
c := clients[client_index].(map[string]interface{})
|
|
||||||
for traffic_index := range dbClientTraffics {
|
|
||||||
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
|
||||||
oldExpiryTime := c["expiryTime"].(float64)
|
|
||||||
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
|
||||||
c["expiryTime"] = newExpiryTime
|
|
||||||
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newClients = append(newClients, interface{}(c))
|
|
||||||
}
|
|
||||||
settings["clients"] = newClients
|
|
||||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
err := tx.Save(inbounds[i:end]).Error
|
||||||
}
|
|
||||||
}
|
|
||||||
err = tx.Save(inbounds).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("AddClientTraffic update inbounds ", err)
|
logger.Warning("AddClientTraffic update inbounds ", err)
|
||||||
logger.Error(inbounds)
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbClientTraffics, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||||
@@ -1088,7 +1143,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
clientTraffic.Email = client.Email
|
clientTraffic.Email = client.Email
|
||||||
clientTraffic.Total = client.TotalGB
|
clientTraffic.Total = client.TotalGB
|
||||||
clientTraffic.ExpiryTime = client.ExpiryTime
|
clientTraffic.ExpiryTime = client.ExpiryTime
|
||||||
clientTraffic.Enable = true
|
clientTraffic.Enable = client.Enable
|
||||||
clientTraffic.Up = 0
|
clientTraffic.Up = 0
|
||||||
clientTraffic.Down = 0
|
clientTraffic.Down = 0
|
||||||
clientTraffic.Reset = client.Reset
|
clientTraffic.Reset = client.Reset
|
||||||
@@ -1101,7 +1156,7 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
|
|||||||
result := tx.Model(xray.ClientTraffic{}).
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"enable": true,
|
"enable": client.Enable,
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime,
|
"expiry_time": client.ExpiryTime,
|
||||||
@@ -1404,6 +1459,9 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
|
if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
|
||||||
|
logger.Warningf("VACUUM failed: %v", dbErr)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PanelService struct{}
|
type PanelService struct{}
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/util/sys"
|
"github.com/alireza0/x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
"github.com/shirou/gopsutil/v4/disk"
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
@@ -243,7 +244,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
|||||||
}
|
}
|
||||||
var versions []string
|
var versions []string
|
||||||
for _, release := range releases {
|
for _, release := range releases {
|
||||||
if release.TagName >= "v1.8.0" {
|
if release.TagName >= "v26.1.23" {
|
||||||
versions = append(versions, release.TagName)
|
versions = append(versions, release.TagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,6 +280,8 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
switch osName {
|
switch osName {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
osName = "macos"
|
osName = "macos"
|
||||||
|
case "windows":
|
||||||
|
osName = "windows"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch arch {
|
switch arch {
|
||||||
@@ -322,19 +325,23 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) UpdateXray(version 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)
|
zipFileName, err := s.downloadXRay(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer os.Remove(zipFileName)
|
||||||
|
|
||||||
zipFile, err := os.Open(zipFileName)
|
zipFile, err := os.Open(zipFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer zipFile.Close()
|
||||||
zipFile.Close()
|
|
||||||
os.Remove(zipFileName)
|
|
||||||
}()
|
|
||||||
|
|
||||||
stat, err := zipFile.Stat()
|
stat, err := zipFile.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,19 +352,14 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.xrayService.StopXray()
|
// 3. Helper to extract files
|
||||||
defer func() {
|
|
||||||
err := s.xrayService.RestartXray(true)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("start xray failed:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
copyZipFile := func(zipName string, fileName string) error {
|
copyZipFile := func(zipName string, fileName string) error {
|
||||||
zipFile, err := reader.Open(zipName)
|
zipFile, err := reader.Open(zipName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
os.MkdirAll(filepath.Dir(fileName), 0755)
|
||||||
os.Remove(fileName)
|
os.Remove(fileName)
|
||||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -368,11 +370,23 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Restart xray
|
||||||
|
if err := s.xrayService.RestartXray(true); err != nil {
|
||||||
|
logger.Error("start xray failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,14 +395,39 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
|||||||
var lines []string
|
var lines []string
|
||||||
|
|
||||||
if syslog == "true" {
|
if syslog == "true" {
|
||||||
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
|
// Check if running on Windows - journalctl is not available
|
||||||
// Run the command
|
if runtime.GOOS == "windows" {
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate and sanitize count parameter
|
||||||
|
countInt, err := strconv.Atoi(count)
|
||||||
|
if err != nil || countInt < 1 || countInt > 10000 {
|
||||||
|
return []string{"Invalid count parameter - must be a number between 1 and 10000"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate level parameter - only allow valid syslog levels
|
||||||
|
validLevels := map[string]bool{
|
||||||
|
"0": true, "emerg": true,
|
||||||
|
"1": true, "alert": true,
|
||||||
|
"2": true, "crit": true,
|
||||||
|
"3": true, "err": true,
|
||||||
|
"4": true, "warning": true,
|
||||||
|
"5": true, "notice": true,
|
||||||
|
"6": true, "info": true,
|
||||||
|
"7": true, "debug": true,
|
||||||
|
}
|
||||||
|
if !validLevels[level] {
|
||||||
|
return []string{"Invalid level parameter - must be a valid syslog level"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use hardcoded command with validated parameters
|
||||||
|
cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
err := cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{"Failed to run journalctl command!"}
|
return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
|
||||||
}
|
}
|
||||||
lines = strings.Split(out.String(), "\n")
|
lines = strings.Split(out.String(), "\n")
|
||||||
} else {
|
} else {
|
||||||
@@ -481,14 +520,26 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
|||||||
return common.NewErrorf("Error saving db: %v", err)
|
return common.NewErrorf("Error saving db: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we can init db or not
|
// Close temp file before opening via sqlite
|
||||||
err = database.InitDB(tempPath)
|
if err = tempFile.Close(); err != nil {
|
||||||
if err != nil {
|
return common.NewErrorf("Error closing temporary db file: %v", err)
|
||||||
return common.NewErrorf("Error checking db: %v", err)
|
}
|
||||||
|
tempFile = nil
|
||||||
|
|
||||||
|
// Validate integrity (no migrations / side effects)
|
||||||
|
if err = database.ValidateSQLiteDB(tempPath); err != nil {
|
||||||
|
return common.NewErrorf("Invalid or corrupt db file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop Xray
|
// Stop Xray (ignore error but log)
|
||||||
s.StopXrayService()
|
if errStop := s.StopXrayService(); errStop != nil {
|
||||||
|
logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close existing DB to release file locks (especially on Windows)
|
||||||
|
if errClose := database.CloseDB(); errClose != nil {
|
||||||
|
logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
|
||||||
|
}
|
||||||
|
|
||||||
// Backup the current database for fallback
|
// Backup the current database for fallback
|
||||||
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
||||||
@@ -590,3 +641,67 @@ func (s *ServerService) GetNewmldsa65() (any, error) {
|
|||||||
|
|
||||||
return keyPair, nil
|
return keyPair, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
if len(lines) < 4 {
|
||||||
|
return nil, common.NewError("invalid ech cert")
|
||||||
|
}
|
||||||
|
|
||||||
|
configList := lines[1]
|
||||||
|
serverKeys := lines[3]
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"echServerKeys": serverKeys,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/util/random"
|
"github.com/alireza0/x-ui/util/random"
|
||||||
"x-ui/util/reflect_util"
|
"github.com/alireza0/x-ui/util/reflect_util"
|
||||||
"x-ui/web/entity"
|
"github.com/alireza0/x-ui/web/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed config.json
|
//go:embed config.json
|
||||||
@@ -359,7 +359,12 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
defaultLocation := defaultValueMap["timeLocation"]
|
defaultLocation := defaultValueMap["timeLocation"]
|
||||||
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
|
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
|
||||||
return time.LoadLocation(defaultLocation)
|
location, err = time.LoadLocation(defaultLocation)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to load default location, using UTC: %v", err)
|
||||||
|
return time.UTC, nil
|
||||||
|
}
|
||||||
|
return location, nil
|
||||||
}
|
}
|
||||||
return location, nil
|
return location, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/web/locale"
|
"github.com/alireza0/x-ui/web/locale"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/logger"
|
||||||
|
"github.com/alireza0/x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WarpService struct {
|
type WarpService struct {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
@@ -32,7 +33,19 @@ func (s *XrayService) GetXrayErr() error {
|
|||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return p.GetErr()
|
|
||||||
|
err := p.GetErr()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
func (s *XrayService) GetXrayResult() string {
|
||||||
@@ -45,7 +58,15 @@ func (s *XrayService) GetXrayResult() string {
|
|||||||
if p == nil {
|
if p == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
result = p.GetResult()
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type XraySettingService struct {
|
type XraySettingService struct {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package session
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -19,23 +19,10 @@ func init() {
|
|||||||
|
|
||||||
func SetLoginUser(c *gin.Context, user *model.User) error {
|
func SetLoginUser(c *gin.Context, user *model.User) error {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
s.Options(sessions.Options{
|
|
||||||
Path: "/",
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
|
||||||
s.Set(loginUser, user)
|
s.Set(loginUser, user)
|
||||||
return s.Save()
|
return s.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetMaxAge(c *gin.Context, maxAge int) error {
|
|
||||||
s := sessions.Default(c)
|
|
||||||
s.Options(sessions.Options{
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: maxAge,
|
|
||||||
})
|
|
||||||
return s.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLoginUser(c *gin.Context) *model.User {
|
func GetLoginUser(c *gin.Context) *model.User {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
obj := s.Get(loginUser)
|
obj := s.Get(loginUser)
|
||||||
@@ -53,8 +40,12 @@ func IsLogin(c *gin.Context) bool {
|
|||||||
func ClearSession(c *gin.Context) {
|
func ClearSession(c *gin.Context) {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
s.Clear()
|
s.Clear()
|
||||||
|
basePath := c.GetString("base_path")
|
||||||
|
if basePath == "" {
|
||||||
|
basePath = "/"
|
||||||
|
}
|
||||||
s.Options(sessions.Options{
|
s.Options(sessions.Options{
|
||||||
Path: "/",
|
Path: basePath,
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
})
|
})
|
||||||
s.Save()
|
s.Save()
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "Remark"
|
"remark" = "Remark"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"port" = "Port"
|
"port" = "Port"
|
||||||
|
"portMap" = "Port Mapping"
|
||||||
"traffic" = "Traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "Details"
|
"details" = "Details"
|
||||||
"transportConfig" = "Transport Config"
|
"transportConfig" = "Transport Config"
|
||||||
@@ -416,6 +417,12 @@
|
|||||||
"psk" = "PreShared Key"
|
"psk" = "PreShared Key"
|
||||||
"domainStrategy" = "Domain Strategy"
|
"domainStrategy" = "Domain Strategy"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "The name of the TUN interface. Default is 'xrayN', where N is some number"
|
||||||
|
"mtuDesc" = "Maximum Transmission Unit. The maximum size of data packets. Default is 1500"
|
||||||
|
"userLevel" = "User Level"
|
||||||
|
"userLevelDesc" = "All connections made through this inbound will use this user level. Default is 0"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Enable DNS"
|
"enable" = "Enable DNS"
|
||||||
"enableDesc" = "Enables built-in DNS server."
|
"enableDesc" = "Enables built-in DNS server."
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "نام"
|
"remark" = "نام"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"port" = "پورت"
|
"port" = "پورت"
|
||||||
|
"portMap" = "پورتهای نظیر"
|
||||||
"traffic" = "ترافیک"
|
"traffic" = "ترافیک"
|
||||||
"details" = "جزئیات"
|
"details" = "جزئیات"
|
||||||
"transportConfig" = "نحوه اتصال"
|
"transportConfig" = "نحوه اتصال"
|
||||||
@@ -414,6 +415,12 @@
|
|||||||
"psk" = "کلید مشترک"
|
"psk" = "کلید مشترک"
|
||||||
"domainStrategy" = "استراتژی حل دامنه"
|
"domainStrategy" = "استراتژی حل دامنه"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "نام رابط TUN. مقدار پیشفرض 'xrayN', N یک عدد است"
|
||||||
|
"mtuDesc" = "واحد انتقال حداکثر. بیشترین اندازه بستههای داده. مقدار پیشفرض 1500 است"
|
||||||
|
"userLevel" = "سطح کاربر"
|
||||||
|
"userLevelDesc" = "تمام اتصالات انجامشده از طریق این ورودی از این سطح کاربری استفاده خواهند کرد. مقدار پیشفرض 0 است"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "فعال کردن حل دامنه"
|
"enable" = "فعال کردن حل دامنه"
|
||||||
"enableDesc" = "سرور حل دامنه داخلی را فعال میکند"
|
"enableDesc" = "سرور حل دامنه داخلی را فعال میکند"
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "Примечание"
|
"remark" = "Примечание"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
|
"portMap" = "Порт-перенаправление"
|
||||||
"traffic" = "Трафик"
|
"traffic" = "Трафик"
|
||||||
"details" = "Подробнее"
|
"details" = "Подробнее"
|
||||||
"transportConfig" = "Перенести"
|
"transportConfig" = "Перенести"
|
||||||
@@ -416,6 +417,12 @@
|
|||||||
"psk" = "Общий ключ"
|
"psk" = "Общий ключ"
|
||||||
"domainStrategy" = "Стратегия домена"
|
"domainStrategy" = "Стратегия домена"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "Имя интерфейса TUN. Значение по умолчанию - 'xrayN', где N - номер интерфейса."
|
||||||
|
"mtuDesc" = "Максимальная единица передачи. Максимальный размер пакетов данных. Значение по умолчанию - 1500"
|
||||||
|
"userLevel" = "Уровень пользователя"
|
||||||
|
"userLevelDesc" = "Все соединения, установленные через этот входящий поток, будут использовать этот уровень пользователя. Значение по умолчанию - 0"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Включить DNS"
|
"enable" = "Включить DNS"
|
||||||
"enableDesc" = "Включить встроенный DNS-сервер"
|
"enableDesc" = "Включить встроенный DNS-сервер"
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "Nhận xét"
|
"remark" = "Nhận xét"
|
||||||
"protocol" = "Giao thức"
|
"protocol" = "Giao thức"
|
||||||
"port" = "Cổng"
|
"port" = "Cổng"
|
||||||
|
"portMap" = "Cổng điều chính"
|
||||||
"traffic" = "Lưu lượng"
|
"traffic" = "Lưu lượng"
|
||||||
"details" = "Chi tiết"
|
"details" = "Chi tiết"
|
||||||
"transportConfig" = "Cấu hình vận chuyển"
|
"transportConfig" = "Cấu hình vận chuyển"
|
||||||
@@ -416,6 +417,12 @@
|
|||||||
"psk" = "Khóa chia sẻ"
|
"psk" = "Khóa chia sẻ"
|
||||||
"domainStrategy" = "Chiến lược tên miền"
|
"domainStrategy" = "Chiến lược tên miền"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "Tên của giao diện TUN. Giá trị mặc định là 'xrayN', với N là số nguyen."
|
||||||
|
"mtuDesc" = "Đơn vị Truyền Tối đa. Kích thước tối đa của các gói dữ liệu. Giá trị mặc định là 1500"
|
||||||
|
"userLevel" = "Mức Người Dùng"
|
||||||
|
"userLevelDesc" = "Tất cả các kết nối được thực hiện thông qua inbound này sẽ sử dụng mức người dùng này. Giá trị mặc định là 0"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Kích hoạt DNS"
|
"enable" = "Kích hoạt DNS"
|
||||||
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "备注"
|
"remark" = "备注"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"port" = "端口"
|
"port" = "端口"
|
||||||
|
"portMap" = "端口映射"
|
||||||
"traffic" = "流量"
|
"traffic" = "流量"
|
||||||
"details" = "详细信息"
|
"details" = "详细信息"
|
||||||
"transportConfig" = "传输配置"
|
"transportConfig" = "传输配置"
|
||||||
@@ -416,6 +417,12 @@
|
|||||||
"psk" = "共享密钥"
|
"psk" = "共享密钥"
|
||||||
"domainStrategy" = "域策略"
|
"domainStrategy" = "域策略"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "TUN 接口的名称。默认值为 'xrayN', 其中 N 是接口的编号。"
|
||||||
|
"mtuDesc" = "最大传输单元。数据包的最大大小。默认值为 1500"
|
||||||
|
"userLevel" = "用户级别"
|
||||||
|
"userLevelDesc" = "通过此入站的所有连接都将使用此用户级别。默认值为 0"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "启用 DNS"
|
"enable" = "启用 DNS"
|
||||||
"enableDesc" = "启用内置 DNS 服务器"
|
"enableDesc" = "启用内置 DNS 服务器"
|
||||||
|
|||||||
41
web/web.go
41
web/web.go
@@ -14,15 +14,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/web/controller"
|
"github.com/alireza0/x-ui/web/controller"
|
||||||
"x-ui/web/job"
|
"github.com/alireza0/x-ui/web/job"
|
||||||
"x-ui/web/locale"
|
"github.com/alireza0/x-ui/web/locale"
|
||||||
"x-ui/web/middleware"
|
"github.com/alireza0/x-ui/web/middleware"
|
||||||
"x-ui/web/network"
|
"github.com/alireza0/x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
@@ -176,10 +176,23 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionMaxAge, err := s.settingService.GetSessionMaxAge()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "xui/API/"})))
|
engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "xui/API/"})))
|
||||||
assetsBasePath := basePath + "assets/"
|
assetsBasePath := basePath + "assets/"
|
||||||
|
|
||||||
store := cookie.NewStore(secret)
|
store := cookie.NewStore(secret)
|
||||||
|
sessionOptions := sessions.Options{
|
||||||
|
Path: basePath,
|
||||||
|
HttpOnly: true,
|
||||||
|
}
|
||||||
|
if sessionMaxAge > 0 {
|
||||||
|
sessionOptions.MaxAge = sessionMaxAge * 60
|
||||||
|
}
|
||||||
|
store.Options(sessionOptions)
|
||||||
engine.Use(sessions.Sessions("x-ui", store))
|
engine.Use(sessions.Sessions("x-ui", store))
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
c.Set("base_path", basePath)
|
c.Set("base_path", basePath)
|
||||||
@@ -228,7 +241,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
s.index = controller.NewIndexController(g)
|
s.index = controller.NewIndexController(g)
|
||||||
s.server = controller.NewServerController(g)
|
s.server = controller.NewServerController(g)
|
||||||
s.xui = controller.NewXUIController(g)
|
s.xui = controller.NewXUIController(g)
|
||||||
s.api = controller.NewAPIController(g)
|
s.api = controller.NewAPIController(g, s.server)
|
||||||
|
|
||||||
|
engine.NoRoute(func(c *gin.Context) {
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
@@ -388,3 +405,7 @@ func (s *Server) GetCtx() context.Context {
|
|||||||
func (s *Server) GetCron() *cron.Cron {
|
func (s *Server) GetCron() *cron.Cron {
|
||||||
return s.cron
|
return s.cron
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) RestartXray() error {
|
||||||
|
return s.xrayService.RestartXray(true)
|
||||||
|
}
|
||||||
|
|||||||
BIN
windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
Normal file
BIN
windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
Normal file
Binary file not shown.
12
windows_files/readme.txt
Normal file
12
windows_files/readme.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
we don't have bash menu for windows
|
||||||
|
if you forgot your password you need to check your database with https://sqlitebrowser.org/
|
||||||
|
the app need to be open all the time
|
||||||
|
|
||||||
|
default setting:
|
||||||
|
http://localhost:54321/
|
||||||
|
user: admin
|
||||||
|
pass: admin
|
||||||
|
port: 54321
|
||||||
|
|
||||||
|
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt
|
||||||
@@ -8,6 +8,7 @@ Environment="XRAY_VMESS_AEAD_FORCED=false"
|
|||||||
Type=simple
|
Type=simple
|
||||||
WorkingDirectory=/usr/local/x-ui/
|
WorkingDirectory=/usr/local/x-ui/
|
||||||
ExecStart=/usr/local/x-ui/x-ui
|
ExecStart=/usr/local/x-ui/x-ui
|
||||||
|
ExecReload=kill -USR1 $MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
141
x-ui.sh
141
x-ui.sh
@@ -34,75 +34,6 @@ fi
|
|||||||
|
|
||||||
echo "The OS release is: $release"
|
echo "The OS release is: $release"
|
||||||
|
|
||||||
os_version=""
|
|
||||||
os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.')
|
|
||||||
|
|
||||||
if [[ "${release}" == "arch" ]]; then
|
|
||||||
echo "Your OS is Arch Linux"
|
|
||||||
elif [[ "${release}" == "parch" ]]; then
|
|
||||||
echo "Your OS is Parch Linux"
|
|
||||||
elif [[ "${release}" == "manjaro" ]]; then
|
|
||||||
echo "Your OS is Manjaro"
|
|
||||||
elif [[ "${release}" == "armbian" ]]; then
|
|
||||||
echo "Your OS is Armbian"
|
|
||||||
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
|
|
||||||
echo "Your OS is OpenSUSE Tumbleweed"
|
|
||||||
elif [[ "${release}" == "openEuler" ]]; then
|
|
||||||
if [[ ${os_version} -lt 2203 ]]; then
|
|
||||||
echo -e "${red} Please use OpenEuler 22.03 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "centos" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "ubuntu" ]]; then
|
|
||||||
if [[ ${os_version} -lt 2204 ]]; then
|
|
||||||
echo -e "${red} Please use Ubuntu 22 or higher version!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "fedora" ]]; then
|
|
||||||
if [[ ${os_version} -lt 36 ]]; then
|
|
||||||
echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "amzn" ]]; then
|
|
||||||
if [[ ${os_version} != "2023" ]]; then
|
|
||||||
echo -e "${red} Please use Amazon Linux 2023!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "debian" ]]; then
|
|
||||||
if [[ ${os_version} -lt 12 ]]; then
|
|
||||||
echo -e "${red} Please use Debian 12 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "almalinux" ]]; then
|
|
||||||
if [[ ${os_version} -lt 95 ]]; then
|
|
||||||
echo -e "${red} Please use AlmaLinux 9.5 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "rocky" ]]; then
|
|
||||||
if [[ ${os_version} -lt 95 ]]; then
|
|
||||||
echo -e "${red} Please use Rocky Linux 9.5 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "ol" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
|
||||||
echo "Please ensure you are using one of the following supported operating systems:"
|
|
||||||
echo "- Ubuntu 22.04+"
|
|
||||||
echo "- Debian 12+"
|
|
||||||
echo "- CentOS 8+"
|
|
||||||
echo "- OpenEuler 22.03+"
|
|
||||||
echo "- Fedora 36+"
|
|
||||||
echo "- Arch Linux"
|
|
||||||
echo "- Parch Linux"
|
|
||||||
echo "- Manjaro"
|
|
||||||
echo "- Armbian"
|
|
||||||
echo "- AlmaLinux 9.5+"
|
|
||||||
echo "- Rocky Linux 9.5+"
|
|
||||||
echo "- Oracle Linux 8+"
|
|
||||||
echo "- OpenSUSE Tumbleweed"
|
|
||||||
echo "- Amazon Linux 2023"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
if [[ $# > 1 ]]; then
|
if [[ $# > 1 ]]; then
|
||||||
echo && read -p "$1 [Default $2]: " temp
|
echo && read -p "$1 [Default $2]: " temp
|
||||||
@@ -210,15 +141,23 @@ uninstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset_user() {
|
reset_user() {
|
||||||
confirm "Reset your username and password to admin?" "n"
|
confirm "Are you sure to reset the username and password of the panel?" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
show_menu
|
show_menu
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
/usr/local/x-ui/x-ui setting -username admin -password admin
|
|
||||||
echo -e "Username and password have been reset to ${green}admin${plain}, Please restart the panel now."
|
read -rp "Please set the login username [default is a random username]: " config_account
|
||||||
|
[[ -z $config_account ]] && config_account=$(gen_random_string 10)
|
||||||
|
read -rp "Please set the login password [default is a random password]: " config_password
|
||||||
|
[[ -z $config_password ]] && config_password=$(gen_random_string 18)
|
||||||
|
|
||||||
|
echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
|
||||||
|
echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
|
||||||
|
echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
|
||||||
|
|
||||||
confirm_restart
|
confirm_restart
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +286,16 @@ restart() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restart_xray() {
|
||||||
|
systemctl reload x-ui
|
||||||
|
LOGI "xray-core Restart signal sent successfully, Please check the log information to confirm whether xray restarted successfully"
|
||||||
|
sleep 2
|
||||||
|
show_xray_status
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
systemctl status x-ui -l
|
systemctl status x-ui -l
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
@@ -1113,6 +1062,7 @@ show_usage() {
|
|||||||
echo "x-ui start - Start"
|
echo "x-ui start - Start"
|
||||||
echo "x-ui stop - Stop"
|
echo "x-ui stop - Stop"
|
||||||
echo "x-ui restart - Restart"
|
echo "x-ui restart - Restart"
|
||||||
|
echo "x-ui restart-xray - Restart xray-core"
|
||||||
echo "x-ui status - Current Status"
|
echo "x-ui status - Current Status"
|
||||||
echo "x-ui settings - Current Settings"
|
echo "x-ui settings - Current Settings"
|
||||||
echo "x-ui enable - Enable Autostart on OS Startup"
|
echo "x-ui enable - Enable Autostart on OS Startup"
|
||||||
@@ -1145,19 +1095,20 @@ show_menu() {
|
|||||||
${green}10.${plain} Start
|
${green}10.${plain} Start
|
||||||
${green}11.${plain} Stop
|
${green}11.${plain} Stop
|
||||||
${green}12.${plain} Restart
|
${green}12.${plain} Restart
|
||||||
${green}13.${plain} Check State
|
${green}13.${plain} Restart Xray
|
||||||
${green}14.${plain} Check Logs
|
${green}14.${plain} Check State
|
||||||
|
${green}15.${plain} Check Logs
|
||||||
————————————————
|
————————————————
|
||||||
${green}15.${plain} Enable Autostart
|
${green}16.${plain} Enable Autostart
|
||||||
${green}16.${plain} Disable Autostart
|
${green}17.${plain} Disable Autostart
|
||||||
————————————————
|
————————————————
|
||||||
${green}17.${plain} SSL Certificate Management
|
${green}18.${plain} SSL Certificate Management
|
||||||
${green}18.${plain} Cloudflare SSL Certificate
|
${green}19.${plain} Cloudflare SSL Certificate
|
||||||
${green}19.${plain} Firewall Management
|
${green}20.${plain} Firewall Management
|
||||||
————————————————
|
————————————————
|
||||||
${green}20.${plain} Enable or Disable BBR
|
${green}21.${plain} Enable or Disable BBR
|
||||||
${green}21.${plain} Update Geo Files
|
${green}22.${plain} Update Geo Files
|
||||||
${green}22.${plain} Speedtest by Ookla
|
${green}23.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
echo && read -p "Please enter your selection [0-22]: " num
|
echo && read -p "Please enter your selection [0-22]: " num
|
||||||
@@ -1203,37 +1154,40 @@ show_menu() {
|
|||||||
check_install && restart
|
check_install && restart
|
||||||
;;
|
;;
|
||||||
13)
|
13)
|
||||||
check_install && status
|
check_install && restart_xray
|
||||||
;;
|
;;
|
||||||
14)
|
14)
|
||||||
check_install && show_log
|
check_install && status
|
||||||
;;
|
;;
|
||||||
15)
|
15)
|
||||||
check_install && enable
|
check_install && show_log
|
||||||
;;
|
;;
|
||||||
16)
|
16)
|
||||||
check_install && disable
|
check_install && enable
|
||||||
;;
|
;;
|
||||||
17)
|
17)
|
||||||
ssl_cert_issue_main
|
check_install && disable
|
||||||
;;
|
;;
|
||||||
18)
|
18)
|
||||||
ssl_cert_issue_CF
|
ssl_cert_issue_main
|
||||||
;;
|
;;
|
||||||
19)
|
19)
|
||||||
firewall_menu
|
ssl_cert_issue_CF
|
||||||
;;
|
;;
|
||||||
20)
|
20)
|
||||||
bbr_menu
|
firewall_menu
|
||||||
;;
|
;;
|
||||||
21)
|
21)
|
||||||
update_geo
|
bbr_menu
|
||||||
;;
|
;;
|
||||||
22)
|
22)
|
||||||
|
update_geo
|
||||||
|
;;
|
||||||
|
23)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
LOGE "Please enter the correct number [0-22]"
|
LOGE "Please enter the correct number [0-23]"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -1249,6 +1203,9 @@ if [[ $# > 0 ]]; then
|
|||||||
"restart")
|
"restart")
|
||||||
check_install 0 && restart 0
|
check_install 0 && restart 0
|
||||||
;;
|
;;
|
||||||
|
"restart-xray")
|
||||||
|
check_install 0 && restart_xray 0
|
||||||
|
;;
|
||||||
"status")
|
"status")
|
||||||
check_install 0 && status 0
|
check_install 0 && status 0
|
||||||
;;
|
;;
|
||||||
|
|||||||
31
xray/api.go
31
xray/api.go
@@ -7,8 +7,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/proxyman/command"
|
"github.com/xtls/xray-core/app/proxyman/command"
|
||||||
statsService "github.com/xtls/xray-core/app/stats/command"
|
statsService "github.com/xtls/xray-core/app/stats/command"
|
||||||
@@ -94,10 +94,33 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
|
|||||||
Id: user["id"].(string),
|
Id: user["id"].(string),
|
||||||
})
|
})
|
||||||
case "vless":
|
case "vless":
|
||||||
account = serial.ToTypedMessage(&vless.Account{
|
vlessAccount := &vless.Account{
|
||||||
Id: user["id"].(string),
|
Id: user["id"].(string),
|
||||||
Flow: user["flow"].(string),
|
Flow: user["flow"].(string),
|
||||||
})
|
}
|
||||||
|
// Add testseed if provided
|
||||||
|
if testseedVal, ok := user["testseed"]; ok {
|
||||||
|
if testseedArr, ok := testseedVal.([]interface{}); ok && len(testseedArr) >= 4 {
|
||||||
|
testseed := make([]uint32, len(testseedArr))
|
||||||
|
for i, v := range testseedArr {
|
||||||
|
if num, ok := v.(float64); ok {
|
||||||
|
testseed[i] = uint32(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vlessAccount.Testseed = testseed
|
||||||
|
} else if testseedArr, ok := testseedVal.([]uint32); ok && len(testseedArr) >= 4 {
|
||||||
|
vlessAccount.Testseed = testseedArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add testpre if provided (for outbound, but can be in user for compatibility)
|
||||||
|
if testpreVal, ok := user["testpre"]; ok {
|
||||||
|
if testpre, ok := testpreVal.(float64); ok && testpre > 0 {
|
||||||
|
vlessAccount.Testpre = uint32(testpre)
|
||||||
|
} else if testpre, ok := testpreVal.(uint32); ok && testpre > 0 {
|
||||||
|
vlessAccount.Testpre = testpre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
account = serial.ToTypedMessage(vlessAccount)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
account = serial.ToTypedMessage(&trojan.Account{
|
account = serial.ToTypedMessage(&trojan.Account{
|
||||||
Password: user["password"].(string),
|
Password: user["password"].(string),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package xray
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"github.com/alireza0/x-ui/util/json_util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package xray
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"github.com/alireza0/x-ui/util/json_util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InboundConfig struct {
|
type InboundConfig struct {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLogWriter() *LogWriter {
|
func NewLogWriter() *LogWriter {
|
||||||
@@ -32,32 +32,56 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
|||||||
return len(m), nil
|
return len(m), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
|
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
|
||||||
messages := strings.Split(message, "\n")
|
messages := strings.SplitSeq(message, "\n")
|
||||||
|
|
||||||
for _, msg := range messages {
|
for msg := range messages {
|
||||||
matches := regex.FindStringSubmatch(msg)
|
matches := regex.FindStringSubmatch(msg)
|
||||||
|
|
||||||
if len(matches) > 3 {
|
if len(matches) > 3 {
|
||||||
level := matches[2]
|
level := matches[2]
|
||||||
msgBody := matches[3]
|
msgBody := matches[3]
|
||||||
|
msgBodyLower := strings.ToLower(msgBody)
|
||||||
|
|
||||||
// Map the level to the appropriate logger function
|
if strings.Contains(msgBodyLower, "tls handshake error") ||
|
||||||
switch level {
|
strings.Contains(msgBodyLower, "connection ends") {
|
||||||
case "Debug":
|
|
||||||
logger.Debug("XRAY: " + msgBody)
|
logger.Debug("XRAY: " + msgBody)
|
||||||
case "Info":
|
lw.lastLine = ""
|
||||||
logger.Info("XRAY: " + msgBody)
|
continue
|
||||||
case "Warning":
|
}
|
||||||
logger.Warning("XRAY: " + msgBody)
|
|
||||||
case "Error":
|
if strings.Contains(msgBodyLower, "failed") {
|
||||||
logger.Error("XRAY: " + msgBody)
|
logger.Error("XRAY: " + msgBody)
|
||||||
default:
|
} else {
|
||||||
logger.Debug("XRAY: " + msg)
|
switch level {
|
||||||
|
case "Debug":
|
||||||
|
logger.Debug("XRAY: " + msgBody)
|
||||||
|
case "Info":
|
||||||
|
logger.Info("XRAY: " + msgBody)
|
||||||
|
case "Warning":
|
||||||
|
logger.Warning("XRAY: " + msgBody)
|
||||||
|
case "Error":
|
||||||
|
logger.Error("XRAY: " + msgBody)
|
||||||
|
default:
|
||||||
|
logger.Debug("XRAY: " + msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lw.lastLine = ""
|
lw.lastLine = ""
|
||||||
} else if msg != "" {
|
} else if msg != "" {
|
||||||
logger.Debug("XRAY: " + msg)
|
msgLower := strings.ToLower(msg)
|
||||||
|
|
||||||
|
if strings.Contains(msgLower, "tls handshake error") ||
|
||||||
|
strings.Contains(msgLower, "connection ends") {
|
||||||
|
logger.Debug("XRAY: " + msg)
|
||||||
|
lw.lastLine = msg
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(msgLower, "failed") {
|
||||||
|
logger.Error("XRAY: " + msg)
|
||||||
|
} else {
|
||||||
|
logger.Debug("XRAY: " + msg)
|
||||||
|
}
|
||||||
lw.lastLine = msg
|
lw.lastLine = msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetBinaryName() string {
|
func GetBinaryName() string {
|
||||||
@@ -189,7 +189,12 @@ func (p *process) Stop() error {
|
|||||||
if !p.IsRunning() {
|
if !p.IsRunning() {
|
||||||
return errors.New("xray is not running")
|
return errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
return p.cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return p.cmd.Process.Kill()
|
||||||
|
} else {
|
||||||
|
return p.cmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCrashReport(m []byte) error {
|
func writeCrashReport(m []byte) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user