diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 89d23d69..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,155 +0,0 @@ -# 3X-UI Development Guide - -## Project Overview -3X-UI is a web-based control panel for managing Xray-core servers. It's a Go application using Gin web framework with embedded static assets and SQLite database. The panel manages VPN/proxy inbounds, monitors traffic, and provides Telegram bot integration. - -## Architecture - -### Core Components -- **main.go**: Entry point that initializes database, web server, and subscription server. Handles graceful shutdown via SIGHUP/SIGTERM signals -- **web/**: Primary web server with Gin router, HTML templates, and static assets embedded via `//go:embed` -- **xray/**: Xray-core process management and API communication for traffic monitoring -- **database/**: GORM-based SQLite database with models in `database/model/` -- **sub/**: Subscription server running alongside main web server (separate port) -- **web/service/**: Business logic layer containing InboundService, SettingService, TgBot, etc. -- **web/controller/**: HTTP handlers using Gin context (`*gin.Context`) -- **web/job/**: Cron-based background jobs for traffic monitoring, CPU checks, LDAP sync - -### Key Architectural Patterns -1. **Embedded Resources**: All web assets (HTML, CSS, JS, translations) are embedded at compile time using `embed.FS`: - - `web/assets` → `assetsFS` - - `web/html` → `htmlFS` - - `web/translation` → `i18nFS` - -2. **Dual Server Design**: Main web panel + subscription server run concurrently, managed by `web/global` package - -3. **Xray Integration**: Panel generates `config.json` for Xray binary, communicates via gRPC API for real-time traffic stats - -4. **Signal-Based Restart**: SIGHUP triggers graceful restart. **Critical**: Always call `service.StopBot()` before restart to prevent Telegram bot 409 conflicts - -5. **Database Seeders**: Uses `HistoryOfSeeders` model to track one-time migrations (e.g., password bcrypt migration) - -## Development Workflows - -### Building & Running -```bash -# Build (creates bin/3x-ui.exe) -go run tasks.json → "go: build" task - -# Run with debug logging -XUI_DEBUG=true go run ./main.go -# Or use task: "go: run" - -# Test -go test ./... -``` - -### Command-Line Operations -The main.go accepts flags for admin tasks: -- `-reset` - Reset all panel settings to defaults -- `-show` - Display current settings (port, paths) -- Use these by running the binary directly, not via web interface - -### Database Management -- DB path: Configured via `config.GetDBPath()`, typically `/etc/x-ui/x-ui.db` -- Models: Located in `database/model/model.go` - Auto-migrated on startup -- Seeders: Use `HistoryOfSeeders` to prevent re-running migrations -- Default credentials: admin/admin (hashed with bcrypt) - -### Telegram Bot Development -- Bot instance in `web/service/tgbot.go` (3700+ lines) -- Uses `telego` library with long polling -- **Critical Pattern**: Must call `service.StopBot()` before any server restart to prevent 409 bot conflicts -- Bot handlers use `telegohandler.BotHandler` for routing -- i18n via embedded `i18nFS` passed to bot startup - -## Code Conventions - -### Service Layer Pattern -Services inject dependencies (like xray.XrayAPI) and operate on GORM models: -```go -type InboundService struct { - xrayApi xray.XrayAPI -} - -func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { - // Business logic here -} -``` - -### Controller Pattern -Controllers use Gin context and inherit from BaseController: -```go -func (a *InboundController) getInbounds(c *gin.Context) { - // Use I18nWeb(c, "key") for translations - // Check auth via checkLogin middleware -} -``` - -### Configuration Management -- Environment vars: `XUI_DEBUG`, `XUI_LOG_LEVEL`, `XUI_MAIN_FOLDER` -- Config embedded files: `config/version`, `config/name` -- Use `config.GetLogLevel()`, `config.GetDBPath()` helpers - -### Internationalization -- Translation files: `web/translation/translate.*.toml` -- Access via `I18nWeb(c, "pages.login.loginAgain")` in controllers -- Use `locale.I18nType` enum (Web, Api, etc.) - -## External Dependencies & Integration - -### Xray-core -- Binary management: Download platform-specific binary (`xray-{os}-{arch}`) to bin folder -- Config generation: Panel creates `config.json` dynamically from inbound/outbound settings -- Process control: Start/stop via `xray/process.go` -- gRPC API: Real-time stats via `xray/api.go` using `google.golang.org/grpc` - -### Critical External Paths -- Xray binary: `{bin_folder}/xray-{os}-{arch}` -- Xray config: `{bin_folder}/config.json` -- GeoIP/GeoSite: `{bin_folder}/geoip.dat`, `geosite.dat` -- Logs: `{log_folder}/3xipl.log`, `3xipl-banned.log` - -### Job Scheduling -Uses `robfig/cron/v3` for periodic tasks: -- Traffic monitoring: `xray_traffic_job.go` -- CPU alerts: `check_cpu_usage.go` -- IP tracking: `check_client_ip_job.go` -- LDAP sync: `ldap_sync_job.go` - -Jobs registered in `web/web.go` during server initialization - -## Deployment & Scripts - -### Installation Script Pattern -Both `install.sh` and `x-ui.sh` follow these patterns: -- Multi-distro support via `$release` variable (ubuntu, debian, centos, arch, etc.) -- Port detection with `is_port_in_use()` using ss/netstat/lsof -- Systemd service management with distro-specific unit files (`.service.debian`, `.service.arch`, `.service.rhel`) - -### Docker Build -Multi-stage Dockerfile: -1. **Builder**: CGO-enabled build, runs `DockerInit.sh` to download Xray binary -2. **Final**: Alpine-based with fail2ban pre-configured - -### Key File Locations (Production) -- Binary: `/usr/local/x-ui/` -- Database: `/etc/x-ui/x-ui.db` -- Logs: `/var/log/x-ui/` -- Service: `/etc/systemd/system/x-ui.service.*` - -## Testing & Debugging -- Set `XUI_DEBUG=true` for detailed logging -- Check Xray process: `x-ui.sh` script provides menu for status/logs -- Database inspection: Direct SQLite access to x-ui.db -- Traffic debugging: Check `3xipl.log` for IP limit tracking -- Telegram bot: Logs show bot initialization and command handling - -## Common Gotchas -1. **Bot Restart**: Always stop Telegram bot before server restart to avoid 409 conflict -2. **Embedded Assets**: Changes to HTML/CSS require recompilation (not hot-reload) -3. **Password Migration**: Seeder system tracks bcrypt migration - check `HistoryOfSeeders` table -4. **Port Binding**: Subscription server uses different port from main panel -5. **Xray Binary**: Must match OS/arch exactly - managed by installer scripts -6. **Session Management**: Uses `gin-contrib/sessions` with cookie store -7. **IP Limitation**: Implements "last IP wins" - when client exceeds LimitIP, oldest connections are automatically disconnected via Xray API to allow newest IPs diff --git a/.github/workflows/cleanup_caches.yml b/.github/workflows/cleanup_caches.yml deleted file mode 100644 index dcf50fce..00000000 --- a/.github/workflows/cleanup_caches.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Cleanup Caches -on: - schedule: - - cron: '0 3 * * 0' # every Sunday - workflow_dispatch: - -jobs: - cleanup: - runs-on: ubuntu-latest - permissions: - actions: write - steps: - - name: Delete caches older than 3 days - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - CUTOFF_DATE=$(date -d "3 days ago" -Ins --utc | sed 's/+0000/Z/') - echo "Deleting caches older than: $CUTOFF_DATE" - - CACHE_IDS=$(gh api --paginate repos/${{ github.repository }}/actions/caches \ - --jq ".actions_caches[] | select(.last_accessed_at < \"$CUTOFF_DATE\") | .id" 2>/dev/null) - - if [ -z "$CACHE_IDS" ]; then - echo "No old caches found to delete." - else - echo "$CACHE_IDS" | while read CACHE_ID; do - echo "Deleting cache: $CACHE_ID" - gh api -X DELETE repos/${{ github.repository }}/actions/caches/$CACHE_ID - done - echo "Old caches deleted successfully." - fi \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dda4d3d8..a744b9f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -89,7 +89,7 @@ jobs: cd x-ui/bin # Download dependencies - Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.2.2/" + Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.18/" if [ "${{ matrix.platform }}" == "amd64" ]; then wget -q ${Xray_URL}Xray-linux-64.zip unzip Xray-linux-64.zip @@ -173,42 +173,21 @@ jobs: go-version-file: go.mod check-latest: true - - name: Install MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: >- - mingw-w64-x86_64-gcc - mingw-w64-x86_64-sqlite3 - mingw-w64-x86_64-pkg-config - - - name: Build 3X-UI for Windows (CGO) - shell: msys2 {0} - run: | - export PATH="/c/hostedtoolcache/windows/go/$(ls /c/hostedtoolcache/windows/go | sort -V | tail -n1)/x64/bin:$PATH" - - export CGO_ENABLED=1 - export GOOS=windows - export GOARCH=amd64 - export CC=x86_64-w64-mingw32-gcc - - which go - go version - gcc --version - - go build -ldflags "-w -s" -o xui-release.exe -v main.go - - - name: Copy and download resources + - name: Build 3X-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\x-ui.exe + 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.2/" + $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.1.18/" 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" diff --git a/DockerInit.sh b/DockerInit.sh index 6f153efe..176e1261 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -27,7 +27,7 @@ case $1 in esac mkdir -p build/bin cd build/bin -curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.2.2/Xray-linux-${ARCH}.zip" +curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.1.18/Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip" rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat mv xray "xray-linux-${FNAME}" diff --git a/config/version b/config/version index 0d367c86..c3e2bd4c 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -2.8.9 \ No newline at end of file +2.8.8 \ No newline at end of file diff --git a/database/db.go b/database/db.go index 6b579dd9..f2ad4fec 100644 --- a/database/db.go +++ b/database/db.go @@ -38,6 +38,7 @@ func initModels() error { &model.InboundClientIps{}, &xray.ClientTraffic{}, &model.HistoryOfSeeders{}, + &model.ConnectionReport{}, } for _, model := range models { if err := db.AutoMigrate(model); err != nil { diff --git a/database/model/report.go b/database/model/report.go new file mode 100644 index 00000000..ae83d771 --- /dev/null +++ b/database/model/report.go @@ -0,0 +1,15 @@ +package model + +type ConnectionReport struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement"` + ClientIP string `json:"client_ip"` + Protocol string `json:"protocol"` + Remarks string `json:"remarks"` + Latency int `json:"latency"` + Success bool `json:"success"` + InterfaceName string `json:"interface_name"` + InterfaceDescription string `json:"interface_description"` + InterfaceType string `json:"interface_type"` + Message string `json:"message"` + CreatedAt int64 `json:"created_at" gorm:"autoCreateTime"` +} diff --git a/go.mod b/go.mod index 98f44d28..7d602b11 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,11 @@ require ( github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml/v2 v2.2.4 github.com/robfig/cron/v3 v3.0.1 - github.com/shirou/gopsutil/v4 v4.26.1 + github.com/shirou/gopsutil/v4 v4.25.12 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/valyala/fasthttp v1.69.0 github.com/xlzd/gotp v0.1.0 - github.com/xtls/xray-core v1.260202.0 + github.com/xtls/xray-core v1.260118.0 go.uber.org/atomic v1.11.0 golang.org/x/crypto v0.47.0 golang.org/x/sys v0.40.0 @@ -35,12 +35,13 @@ require ( github.com/andybalholm/brotli v1.2.0 // indirect github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.0 // indirect - github.com/cloudflare/circl v1.6.3 // indirect + github.com/bytedance/sonic v1.14.2 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/cloudflare/circl v1.6.2 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/ebitengine/purego v0.9.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -63,21 +64,24 @@ require ( github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.33 // indirect - github.com/miekg/dns v1.1.72 // indirect + github.com/miekg/dns v1.1.70 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pires/go-proxyproto v0.9.2 // indirect + github.com/pires/go-proxyproto v0.8.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.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/sagernet/sing v0.7.18 // indirect + github.com/sagernet/sing v0.7.14 // 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/numcpus v0.11.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect + github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fastjson v1.6.7 // indirect github.com/vishvananda/netlink v1.3.1 // indirect @@ -94,8 +98,8 @@ require ( golang.org/x/tools v0.41.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect google.golang.org/protobuf v1.36.11 // indirect - gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect + gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 // indirect lukechampine.com/blake3 v1.4.1 // indirect ) diff --git a/go.sum b/go.sum index 247ac90d..e711c95d 100644 --- a/go.sum +++ b/go.sum @@ -10,21 +10,24 @@ github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/ github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= -github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= -github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= -github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -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/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= +github.com/cloudflare/circl v1.6.2/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= +github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM= +github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= 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/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= @@ -121,8 +124,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= -github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA= +github.com/miekg/dns v1.1.70/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -138,8 +141,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v 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/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U= -github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= @@ -150,16 +153,20 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= 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/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/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E= -github.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.7.14 h1:5QQRDCUvYNOMyVp3LuK/hYEBAIv0VsbD3x/l9zH467s= +github.com/sagernet/sing v0.7.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM= github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8= -github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo= -github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc= +github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= +github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= +github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY= +github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -167,6 +174,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS 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/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.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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -181,6 +189,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= 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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= @@ -195,8 +205,8 @@ github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM= github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= -github.com/xtls/xray-core v1.260202.0 h1:dYduYxGlkn/krSQJbmksbTtCdRe8OFb3YwpuXXEJG5c= -github.com/xtls/xray-core v1.260202.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q= +github.com/xtls/xray-core v1.260118.0 h1:RJtgIbQ3ykFRcH1CKeoCgQ5WvhsMFu+lnvLF/fFHagE= +github.com/xtls/xray-core v1.260118.0/go.mod h1:A5k7TXE2KfAjT8dAq6Ir4mMP1q0OTh+8VMmUdqWMQpg= 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= @@ -253,8 +263,8 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= @@ -265,13 +275,14 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV 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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= -gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk= -gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q= +gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 h1:fr6L00yGG2RP5NMea6njWpdC+bm+cMdFClrSpaicp1c= +gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= diff --git a/install.sh b/install.sh index 852e128a..62c8ea37 100644 --- a/install.sh +++ b/install.sh @@ -147,7 +147,7 @@ setup_ssl_certificate() { echo -e "${green}Issuing SSL certificate for ${domain}...${plain}" echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}" - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1 + ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1 ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force if [ $? -ne 0 ]; then @@ -272,7 +272,7 @@ setup_ip_certificate() { # Issue certificate with shortlived profile echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1 + ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1 ~/.acme.sh/acme.sh --issue \ ${domain_args} \ @@ -414,7 +414,7 @@ ssl_cert_issue() { systemctl stop x-ui 2>/dev/null || rc-service x-ui stop 2>/dev/null # issue the certificate - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force + ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force if [ $? -ne 0 ]; then echo -e "${red}Issuing certificate failed, please check logs.${plain}" diff --git a/sub/sub.go b/sub/sub.go index 1dcd9601..0605c8b9 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -153,31 +153,6 @@ func (s *Server) initRouter() (*gin.Engine, error) { SubTitle = "" } - SubSupportUrl, err := s.settingService.GetSubSupportUrl() - if err != nil { - SubSupportUrl = "" - } - - SubProfileUrl, err := s.settingService.GetSubProfileUrl() - if err != nil { - SubProfileUrl = "" - } - - SubAnnounce, err := s.settingService.GetSubAnnounce() - if err != nil { - SubAnnounce = "" - } - - SubEnableRouting, err := s.settingService.GetSubEnableRouting() - if err != nil { - return nil, err - } - - SubRoutingRules, err := s.settingService.GetSubRoutingRules() - if err != nil { - SubRoutingRules = "" - } - // set per-request localizer from headers/cookies engine.Use(locale.LocalizerMiddleware()) @@ -256,8 +231,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { s.sub = NewSUBController( g, LinksPath, JsonPath, subJsonEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates, - SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle, SubSupportUrl, - SubProfileUrl, SubAnnounce, SubEnableRouting, SubRoutingRules) + SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle) return engine, nil } diff --git a/sub/subController.go b/sub/subController.go index 53b5580b..ec574d6e 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -3,7 +3,6 @@ package sub import ( "encoding/base64" "fmt" - "strconv" "strings" "github.com/mhsanaei/3x-ui/v2/config" @@ -13,17 +12,12 @@ import ( // SUBController handles HTTP requests for subscription links and JSON configurations. type SUBController struct { - subTitle string - subSupportUrl string - subProfileUrl string - subAnnounce string - subEnableRouting bool - subRoutingRules string - subPath string - subJsonPath string - jsonEnabled bool - subEncrypt bool - updateInterval string + subTitle string + subPath string + subJsonPath string + jsonEnabled bool + subEncrypt bool + updateInterval string subService *SubService subJsonService *SubJsonService @@ -44,25 +38,15 @@ func NewSUBController( jsonMux string, jsonRules string, subTitle string, - subSupportUrl string, - subProfileUrl string, - subAnnounce string, - subEnableRouting bool, - subRoutingRules string, ) *SUBController { sub := NewSubService(showInfo, rModel) a := &SUBController{ - subTitle: subTitle, - subSupportUrl: subSupportUrl, - subProfileUrl: subProfileUrl, - subAnnounce: subAnnounce, - subEnableRouting: subEnableRouting, - subRoutingRules: subRoutingRules, - subPath: subPath, - subJsonPath: jsonPath, - jsonEnabled: jsonEnabled, - subEncrypt: encrypt, - updateInterval: update, + subTitle: subTitle, + subPath: subPath, + subJsonPath: jsonPath, + jsonEnabled: jsonEnabled, + subEncrypt: encrypt, + updateInterval: update, subService: sub, subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub), @@ -143,7 +127,7 @@ func (a *SUBController) subs(c *gin.Context) { // Add headers header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) - a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules) + a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) if a.subEncrypt { c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) @@ -161,31 +145,17 @@ func (a *SUBController) subJsons(c *gin.Context) { if err != nil || len(jsonSub) == 0 { c.String(400, "Error!") } else { + // Add headers - a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules) + a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle) c.String(200, jsonSub) } } // ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title. -func (a *SUBController) ApplyCommonHeaders( - c *gin.Context, - header, - updateInterval, - profileTitle string, - profileSupportUrl string, - profileUrl string, - profileAnnounce string, - profileEnableRouting bool, - profileRoutingRules string, -) { +func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) { c.Writer.Header().Set("Subscription-Userinfo", header) c.Writer.Header().Set("Profile-Update-Interval", updateInterval) c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle))) - c.Writer.Header().Set("Support-Url", profileSupportUrl) - c.Writer.Header().Set("Profile-Web-Page-Url", profileUrl) - c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce))) - c.Writer.Header().Set("Routing-Enable", strconv.FormatBool(profileEnableRouting)) - c.Writer.Header().Set("Routing", profileRoutingRules) } diff --git a/sub/subService.go b/sub/subService.go index e046ebb4..d956587d 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -527,8 +527,33 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { // Set the new query values on the URL url.RawQuery = q.Encode() + // Set the new query values on the URL + url.RawQuery = q.Encode() + url.Fragment = s.genRemark(inbound, email, "") - return url.String() + + return s.appendReportUrl(url).String() +} + +func (s *SubService) appendReportUrl(u *url.URL) *url.URL { + // Construct report URL: https:////report + // Assuming s.address is the domain/IP. + // We need to know the protocol (http/https). For now, infer or use simple heuristic. + // Or better: pass the full "reportUrl" if we can derive it. + + // Since we don't have the full context here easily, let's construct it based on s.address + // Caveat: port might be missing if on 80/443. + + reportUrl := fmt.Sprintf("https://%s/report", s.address) + + // Append as query param 'reportUrl' to the fragment or the query string? + // Standard Xray/V2ray config doesn't use this. The client needs to parse it. + // Putting it in the query string is safer for link parsers. + + q := u.Query() + q.Set("reportUrl", reportUrl) + u.RawQuery = q.Encode() + return u } func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string { diff --git a/update.sh b/update.sh index 0c4bb725..3781c365 100755 --- a/update.sh +++ b/update.sh @@ -173,7 +173,7 @@ setup_ssl_certificate() { echo -e "${green}Issuing SSL certificate for ${domain}...${plain}" echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}" - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1 + ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1 ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force if [ $? -ne 0 ]; then @@ -297,7 +297,7 @@ setup_ip_certificate() { # Issue certificate with shortlived profile echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1 + ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1 ~/.acme.sh/acme.sh --issue \ ${domain_args} \ @@ -437,7 +437,7 @@ ssl_cert_issue() { systemctl stop x-ui 2>/dev/null || rc-service x-ui stop 2>/dev/null # issue the certificate - ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force + ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force if [ $? -ne 0 ]; then echo -e "${red}Issuing certificate failed, please check logs.${plain}" diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index 9bc93c30..e5fe368f 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -318,13 +318,15 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass { class KcpStreamSettings extends XrayCommonClass { constructor( - mtu = 1350, - tti = 20, + mtu = 1250, + tti = 50, uplinkCapacity = 5, downlinkCapacity = 20, congestion = false, - readBufferSize = 1, - writeBufferSize = 1, + readBufferSize = 2, + writeBufferSize = 2, + type = 'none', + seed = RandomUtil.randomSeq(10), ) { super(); this.mtu = mtu; @@ -334,6 +336,8 @@ class KcpStreamSettings extends XrayCommonClass { this.congestion = congestion; this.readBuffer = readBufferSize; this.writeBuffer = writeBufferSize; + this.type = type; + this.seed = seed; } static fromJson(json = {}) { @@ -345,6 +349,8 @@ class KcpStreamSettings extends XrayCommonClass { json.congestion, json.readBufferSize, json.writeBufferSize, + ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type, + json.seed, ); } @@ -357,6 +363,10 @@ class KcpStreamSettings extends XrayCommonClass { congestion: this.congestion, readBufferSize: this.readBuffer, writeBufferSize: this.writeBuffer, + header: { + type: this.type, + }, + seed: this.seed, }; } } @@ -487,19 +497,6 @@ class xHTTPStreamSettings extends XrayCommonClass { noSSEHeader = false, xPaddingBytes = "100-1000", mode = MODE_OPTION.AUTO, - xPaddingObfsMode = false, - xPaddingKey = '', - xPaddingHeader = '', - xPaddingPlacement = '', - xPaddingMethod = '', - uplinkHTTPMethod = '', - sessionPlacement = '', - sessionKey = '', - seqPlacement = '', - seqKey = '', - uplinkDataPlacement = '', - uplinkDataKey = '', - uplinkChunkSize = 0, ) { super(); this.path = path; @@ -511,19 +508,6 @@ class xHTTPStreamSettings extends XrayCommonClass { this.noSSEHeader = noSSEHeader; this.xPaddingBytes = xPaddingBytes; 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) { @@ -545,19 +529,6 @@ class xHTTPStreamSettings extends XrayCommonClass { json.noSSEHeader, json.xPaddingBytes, 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, ); } @@ -572,19 +543,6 @@ class xHTTPStreamSettings extends XrayCommonClass { noSSEHeader: this.noSSEHeader, xPaddingBytes: this.xPaddingBytes, 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, }; } } @@ -596,6 +554,7 @@ class TlsStreamSettings extends XrayCommonClass { maxVersion = TLS_VERSION_OPTION.TLS13, cipherSuites = '', rejectUnknownSni = false, + verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'], disableSystemRoot = false, enableSessionResumption = false, certificates = [new TlsStreamSettings.Cert()], @@ -610,6 +569,7 @@ class TlsStreamSettings extends XrayCommonClass { this.maxVersion = maxVersion; this.cipherSuites = cipherSuites; this.rejectUnknownSni = rejectUnknownSni; + this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames; this.disableSystemRoot = disableSystemRoot; this.enableSessionResumption = enableSessionResumption; this.certs = certificates; @@ -643,6 +603,7 @@ class TlsStreamSettings extends XrayCommonClass { json.maxVersion, json.cipherSuites, json.rejectUnknownSni, + json.verifyPeerCertInNames, json.disableSystemRoot, json.enableSessionResumption, certs, @@ -660,6 +621,7 @@ class TlsStreamSettings extends XrayCommonClass { maxVersion: this.maxVersion, cipherSuites: this.cipherSuites, rejectUnknownSni: this.rejectUnknownSni, + verifyPeerCertInNames: this.verifyPeerCertInNames.split(","), disableSystemRoot: this.disableSystemRoot, enableSessionResumption: this.enableSessionResumption, certificates: TlsStreamSettings.toJsonArray(this.certs), @@ -967,68 +929,6 @@ class SockoptStreamSettings extends XrayCommonClass { } } -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()) - }; - - } -} - class StreamSettings extends XrayCommonClass { constructor(network = 'tcp', security = 'none', @@ -1041,7 +941,6 @@ class StreamSettings extends XrayCommonClass { grpcSettings = new GrpcStreamSettings(), httpupgradeSettings = new HTTPUpgradeStreamSettings(), xhttpSettings = new xHTTPStreamSettings(), - finalmask = new FinalMaskStreamSettings(), sockopt = undefined, ) { super(); @@ -1056,24 +955,9 @@ class StreamSettings extends XrayCommonClass { this.grpc = grpcSettings; this.httpupgrade = httpupgradeSettings; this.xhttp = xhttpSettings; - this.finalmask = finalmask; 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() { return this.security === "tls"; } @@ -1120,7 +1004,6 @@ class StreamSettings extends XrayCommonClass { GrpcStreamSettings.fromJson(json.grpcSettings), HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings), xHTTPStreamSettings.fromJson(json.xhttpSettings), - FinalMaskStreamSettings.fromJson(json.finalmask), SockoptStreamSettings.fromJson(json.sockopt), ); } @@ -1139,7 +1022,6 @@ class StreamSettings extends XrayCommonClass { grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined, xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined, - finalmask: this.hasFinalMask ? this.finalmask.toJson() : undefined, sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined, }; } @@ -1310,6 +1192,14 @@ class Inbound extends XrayCommonClass { return null; } + get kcpType() { + return this.stream.kcp.type; + } + + get kcpSeed() { + return this.stream.kcp.seed; + } + get serviceName() { return this.stream.grpc.serviceName; } @@ -1386,6 +1276,8 @@ class Inbound extends XrayCommonClass { } } else if (network === 'kcp') { const kcp = this.stream.kcp; + obj.type = kcp.type; + obj.path = kcp.seed; } else if (network === 'ws') { const ws = this.stream.ws; obj.path = ws.path; @@ -1448,6 +1340,8 @@ class Inbound extends XrayCommonClass { break; case "kcp": const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); break; case "ws": const ws = this.stream.ws; @@ -1551,6 +1445,8 @@ class Inbound extends XrayCommonClass { break; case "kcp": const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); break; case "ws": const ws = this.stream.ws; @@ -1630,6 +1526,8 @@ class Inbound extends XrayCommonClass { break; case "kcp": const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); break; case "ws": const ws = this.stream.ws; @@ -2049,9 +1947,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { json.selectedAuth = this.selectedAuth; } - // Only include testseed if at least one client has a flow set - const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== ''); - if (hasFlow && this.testseed && this.testseed.length >= 4) { + if (this.testseed && this.testseed.length >= 4) { json.testseed = this.testseed; } @@ -2613,7 +2509,7 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass { Inbound.WireguardSettings = class extends XrayCommonClass { constructor( protocol, - mtu = 1420, + mtu = 1250, secretKey = Wireguard.generateKeypair().privateKey, peers = [new Inbound.WireguardSettings.Peer()], noKernelTun = false diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index fc110b4e..c6529560 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -165,13 +165,15 @@ class TcpStreamSettings extends CommonClass { class KcpStreamSettings extends CommonClass { constructor( - mtu = 1350, - tti = 20, + mtu = 1250, + tti = 50, uplinkCapacity = 5, downlinkCapacity = 20, congestion = false, - readBufferSize = 1, - writeBufferSize = 1, + readBufferSize = 2, + writeBufferSize = 2, + type = 'none', + seed = '', ) { super(); this.mtu = mtu; @@ -181,6 +183,8 @@ class KcpStreamSettings extends CommonClass { this.congestion = congestion; this.readBuffer = readBufferSize; this.writeBuffer = writeBufferSize; + this.type = type; + this.seed = seed; } static fromJson(json = {}) { @@ -192,6 +196,8 @@ class KcpStreamSettings extends CommonClass { json.congestion, json.readBufferSize, json.writeBufferSize, + ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type, + json.seed, ); } @@ -204,6 +210,10 @@ class KcpStreamSettings extends CommonClass { congestion: this.congestion, readBufferSize: this.readBuffer, writeBufferSize: this.writeBuffer, + header: { + type: this.type, + }, + seed: this.seed, }; } } @@ -347,8 +357,6 @@ class TlsStreamSettings extends CommonClass { fingerprint = '', allowInsecure = false, echConfigList = '', - verifyPeerCertByName = 'cloudflare-dns.com', - pinnedPeerCertSha256 = '', ) { super(); this.serverName = serverName; @@ -356,8 +364,6 @@ class TlsStreamSettings extends CommonClass { this.fingerprint = fingerprint; this.allowInsecure = allowInsecure; this.echConfigList = echConfigList; - this.verifyPeerCertByName = verifyPeerCertByName; - this.pinnedPeerCertSha256 = pinnedPeerCertSha256; } static fromJson(json = {}) { @@ -367,8 +373,6 @@ class TlsStreamSettings extends CommonClass { json.fingerprint, json.allowInsecure, json.echConfigList, - json.verifyPeerCertByName, - json.pinnedPeerCertSha256, ); } @@ -378,9 +382,7 @@ class TlsStreamSettings extends CommonClass { alpn: this.alpn, fingerprint: this.fingerprint, allowInsecure: this.allowInsecure, - echConfigList: this.echConfigList, - verifyPeerCertByName: this.verifyPeerCertByName, - pinnedPeerCertSha256: this.pinnedPeerCertSha256 + echConfigList: this.echConfigList }; } } @@ -432,8 +434,7 @@ class HysteriaStreamSettings extends CommonClass { up = '0', down = '0', udphopPort = '', - udphopIntervalMin = 30, - udphopIntervalMax = 30, + udphopInterval = 30, initStreamReceiveWindow = 8388608, maxStreamReceiveWindow = 8388608, initConnectionReceiveWindow = 20971520, @@ -449,8 +450,7 @@ class HysteriaStreamSettings extends CommonClass { this.up = up; this.down = down; this.udphopPort = udphopPort; - this.udphopIntervalMin = udphopIntervalMin; - this.udphopIntervalMax = udphopIntervalMax; + this.udphopInterval = udphopInterval; this.initStreamReceiveWindow = initStreamReceiveWindow; this.maxStreamReceiveWindow = maxStreamReceiveWindow; this.initConnectionReceiveWindow = initConnectionReceiveWindow; @@ -462,18 +462,10 @@ class HysteriaStreamSettings extends CommonClass { static fromJson(json = {}) { let udphopPort = ''; - let udphopIntervalMin = 30; - let udphopIntervalMax = 30; + let udphopInterval = 30; if (json.udphop) { udphopPort = json.udphop.port || ''; - // Backward compatibility: if old 'interval' exists, use it for both min/max - if (json.udphop.interval !== undefined) { - udphopIntervalMin = json.udphop.interval; - udphopIntervalMax = json.udphop.interval; - } else { - udphopIntervalMin = json.udphop.intervalMin || 30; - udphopIntervalMax = json.udphop.intervalMax || 30; - } + udphopInterval = json.udphop.interval || 30; } return new HysteriaStreamSettings( json.version, @@ -482,8 +474,7 @@ class HysteriaStreamSettings extends CommonClass { json.up, json.down, udphopPort, - udphopIntervalMin, - udphopIntervalMax, + udphopInterval, json.initStreamReceiveWindow, json.maxStreamReceiveWindow, json.initConnectionReceiveWindow, @@ -512,8 +503,7 @@ class HysteriaStreamSettings extends CommonClass { if (this.udphopPort) { result.udphop = { port: this.udphopPort, - intervalMin: this.udphopIntervalMin, - intervalMax: this.udphopIntervalMax + interval: this.udphopInterval }; } return result; @@ -569,65 +559,29 @@ class SockoptStreamSettings extends CommonClass { } class UdpMask extends CommonClass { - constructor(type = 'salamander', settings = {}) { + constructor(type = 'salamander', password = '') { 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 'mkcp-original': - case 'header-dtls': - case 'header-srtp': - case 'header-utp': - case 'header-wechat': - case 'header-wireguard': - return {}; // No settings needed - default: - return settings; - } + this.password = password; } static fromJson(json = {}) { return new UdpMask( - json.type || 'salamander', - json.settings || {} + json.type, + json.settings?.password || '' ); } toJson() { return { type: this.type, - settings: (this.settings && Object.keys(this.settings).length > 0) ? this.settings : undefined + settings: { + password: this.password + } }; } } -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()) - }; - - } -} - class StreamSettings extends CommonClass { constructor( network = 'tcp', @@ -641,7 +595,7 @@ class StreamSettings extends CommonClass { httpupgradeSettings = new HttpUpgradeStreamSettings(), xhttpSettings = new xHTTPStreamSettings(), hysteriaSettings = new HysteriaStreamSettings(), - finalmask = new FinalMaskStreamSettings(), + udpmasks = [], sockopt = undefined, ) { super(); @@ -656,22 +610,16 @@ class StreamSettings extends CommonClass { this.httpupgrade = httpupgradeSettings; this.xhttp = xhttpSettings; this.hysteria = hysteriaSettings; - this.finalmask = finalmask; + this.udpmasks = udpmasks; this.sockopt = sockopt; } - addUdpMask(type = 'salamander') { - this.finalmask.udp.push(new UdpMask(type)); + addUdpMask() { + this.udpmasks.push(new UdpMask()); } delUdpMask(index) { - if (this.finalmask.udp) { - this.finalmask.udp.splice(index, 1); - } - } - - get hasFinalMask() { - return this.finalmask.udp && this.finalmask.udp.length > 0; + this.udpmasks.splice(index, 1); } get isTls() { @@ -691,6 +639,7 @@ class StreamSettings extends CommonClass { } static fromJson(json = {}) { + const udpmasks = json.udpmasks ? json.udpmasks.map(mask => UdpMask.fromJson(mask)) : []; return new StreamSettings( json.network, json.security, @@ -703,7 +652,7 @@ class StreamSettings extends CommonClass { HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings), xHTTPStreamSettings.fromJson(json.xhttpSettings), HysteriaStreamSettings.fromJson(json.hysteriaSettings), - FinalMaskStreamSettings.fromJson(json.finalmask), + udpmasks, SockoptStreamSettings.fromJson(json.sockopt), ); } @@ -722,7 +671,7 @@ class StreamSettings extends CommonClass { httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined, xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined, hysteriaSettings: network === 'hysteria' ? this.hysteria.toJson() : undefined, - finalmask: this.hasFinalMask ? this.finalmask.toJson() : undefined, + udpmasks: this.udpmasks.length > 0 ? this.udpmasks.map(mask => mask.toJson()) : undefined, sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined, }; } @@ -1047,15 +996,7 @@ class Outbound extends CommonClass { stream.hysteria.up = urlParams.get('up') ?? '0'; stream.hysteria.down = urlParams.get('down') ?? '0'; stream.hysteria.udphopPort = urlParams.get('udphopPort') ?? ''; - // Support both old single interval and new min/max range - 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'); - } + stream.hysteria.udphopInterval = parseInt(urlParams.get('udphopInterval') ?? '30'); // Optional QUIC parameters if (urlParams.has('initStreamReceiveWindow')) { @@ -1344,14 +1285,11 @@ Outbound.VLESSSettings = class extends CommonClass { flow: this.flow, encryption: this.encryption, }; - // Only include Vision settings when flow is set - if (this.flow && this.flow !== '') { - if (this.testpre > 0) { - result.testpre = this.testpre; - } - if (this.testseed && this.testseed.length >= 4) { - result.testseed = this.testseed; - } + if (this.testpre > 0) { + result.testpre = this.testpre; + } + if (this.testseed && this.testseed.length >= 4) { + result.testseed = this.testseed; } return result; } @@ -1484,7 +1422,7 @@ Outbound.HttpSettings = class extends CommonClass { Outbound.WireguardSettings = class extends CommonClass { constructor( - mtu = 1420, + mtu = 1250, secretKey = '', address = [''], workers = 2, diff --git a/web/assets/js/model/reality_targets.js b/web/assets/js/model/reality_targets.js index d17bcfc1..cfe65afc 100644 --- a/web/assets/js/model/reality_targets.js +++ b/web/assets/js/model/reality_targets.js @@ -1,15 +1,18 @@ // List of popular services for VLESS Reality Target/SNI randomization const REALITY_TARGETS = [ - { target: 'www.apple.com:443', sni: 'www.apple.com' }, - { target: 'www.icloud.com:443', sni: 'www.icloud.com' }, - { target: 'www.amazon.com:443', sni: 'www.amazon.com' }, - { target: 'aws.amazon.com:443', sni: 'aws.amazon.com' }, - { target: 'www.oracle.com:443', sni: 'www.oracle.com' }, - { target: 'www.nvidia.com:443', sni: 'www.nvidia.com' }, - { target: 'www.amd.com:443', sni: 'www.amd.com' }, - { target: 'www.intel.com:443', sni: 'www.intel.com' }, - { target: 'www.tesla.com:443', sni: 'www.tesla.com' }, - { target: 'www.sony.com:443', sni: 'www.sony.com' } + { target: 'www.icloud.com:443', sni: 'www.icloud.com,icloud.com' }, + { target: 'www.apple.com:443', sni: 'www.apple.com,apple.com' }, + { target: 'www.tesla.com:443', sni: 'www.tesla.com,tesla.com' }, + { target: 'www.sony.com:443', sni: 'www.sony.com,sony.com' }, + { target: 'www.nvidia.com:443', sni: 'www.nvidia.com,nvidia.com' }, + { target: 'www.amd.com:443', sni: 'www.amd.com,amd.com' }, + { target: 'azure.microsoft.com:443', sni: 'azure.microsoft.com,www.azure.com' }, + { target: 'aws.amazon.com:443', sni: 'aws.amazon.com,amazon.com' }, + { target: 'www.bing.com:443', sni: 'www.bing.com,bing.com' }, + { target: 'www.oracle.com:443', sni: 'www.oracle.com,oracle.com' }, + { target: 'www.intel.com:443', sni: 'www.intel.com,intel.com' }, + { target: 'www.microsoft.com:443', sni: 'www.microsoft.com,microsoft.com' }, + { target: 'www.amazon.com:443', sni: 'www.amazon.com,amazon.com' } ]; /** @@ -25,3 +28,4 @@ function getRandomRealityTarget() { sni: selected.sni }; } + diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js index af80a63e..53ffae1a 100644 --- a/web/assets/js/model/setting.js +++ b/web/assets/js/model/setting.js @@ -29,11 +29,6 @@ class AllSetting { this.subEnable = true; this.subJsonEnable = false; this.subTitle = ""; - this.subSupportUrl = ""; - this.subProfileUrl = ""; - this.subAnnounce = ""; - this.subEnableRouting = true; - this.subRoutingRules = ""; this.subListen = ""; this.subPort = 2096; this.subPath = "/sub/"; diff --git a/web/controller/report.go b/web/controller/report.go new file mode 100644 index 00000000..cdbdec6e --- /dev/null +++ b/web/controller/report.go @@ -0,0 +1,78 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/logger" + "github.com/mhsanaei/3x-ui/v2/web/service" +) + +type ReportController struct { + reportService service.ReportService +} + +func NewReportController(g *gin.RouterGroup) *ReportController { + a := &ReportController{ + reportService: service.NewReportService(), + } + a.initRouter(g) + return a +} + +func (a *ReportController) initRouter(g *gin.RouterGroup) { + g.POST("/report", a.receiveReport) +} + +type ReportData struct { + SystemInfo struct { + InterfaceName string `json:"InterfaceName"` + InterfaceDescription string `json:"InterfaceDescription"` + InterfaceType string `json:"InterfaceType"` + Message string `json:"Message"` + } `json:"SystemInfo"` + ConnectionQuality struct { + Latency int `json:"Latency"` + Success bool `json:"Success"` + Message string `json:"Message"` + } `json:"ConnectionQuality"` + ProtocolInfo struct { + Protocol string `json:"Protocol"` + Remarks string `json:"Remarks"` + Address string `json:"Address"` + } `json:"ProtocolInfo"` +} + +func (a *ReportController) receiveReport(c *gin.Context) { + var req ReportData + err := c.ShouldBindJSON(&req) + if err != nil { + jsonMsg(c, "Invalid report format", err) + return + } + + report := &model.ConnectionReport{ + ClientIP: c.ClientIP(), + Protocol: req.ProtocolInfo.Protocol, + Remarks: req.ProtocolInfo.Remarks, + Latency: req.ConnectionQuality.Latency, + Success: req.ConnectionQuality.Success, + InterfaceName: req.SystemInfo.InterfaceName, + InterfaceDescription: req.SystemInfo.InterfaceDescription, + InterfaceType: req.SystemInfo.InterfaceType, + Message: req.SystemInfo.Message, + } + + err = a.reportService.SaveReport(report) + if err != nil { + logger.Error("Failed to save report: ", err) + jsonMsg(c, "Failed to save report", err) + return + } + + logger.Info("Received and Saved Connection Report: Protocol=%s, UserIP=%s, Latency=%dms", + report.Protocol, + report.ClientIP, + report.Latency) + + jsonMsg(c, "Report received and saved successfully", nil) +} diff --git a/web/entity/entity.go b/web/entity/entity.go index 40294925..42e2df85 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -57,11 +57,6 @@ type AllSetting struct { SubEnable bool `json:"subEnable" form:"subEnable"` // Enable subscription server SubJsonEnable bool `json:"subJsonEnable" form:"subJsonEnable"` // Enable JSON subscription endpoint SubTitle string `json:"subTitle" form:"subTitle"` // Subscription title - SubSupportUrl string `json:"subSupportUrl" form:"subSupportUrl"` // Subscription support URL - SubProfileUrl string `json:"subProfileUrl" form:"subProfileUrl"` // Subscription profile URL - SubAnnounce string `json:"subAnnounce" form:"subAnnounce"` // Subscription announce - SubEnableRouting bool `json:"subEnableRouting" form:"subEnableRouting"` // Enable routing for subscription - SubRoutingRules string `json:"subRoutingRules" form:"subRoutingRules"` // Subscription global routing rules (Only for Happ) SubListen string `json:"subListen" form:"subListen"` // Subscription server listen IP SubPort int `json:"subPort" form:"subPort"` // Subscription server port SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html index 4df095d4..688a3564 100644 --- a/web/html/form/outbound.html +++ b/web/html/form/outbound.html @@ -407,6 +407,21 @@ - + diff --git a/web/html/form/stream/stream_finalmask.html b/web/html/form/stream/stream_finalmask.html deleted file mode 100644 index 35962dfa..00000000 --- a/web/html/form/stream/stream_finalmask.html +++ /dev/null @@ -1,84 +0,0 @@ -{{define "form/streamFinalMask"}} - - - - - - - -{{end}} diff --git a/web/html/form/stream/stream_kcp.html b/web/html/form/stream/stream_kcp.html index 11f89ebd..50794574 100644 --- a/web/html/form/stream/stream_kcp.html +++ b/web/html/form/stream/stream_kcp.html @@ -1,32 +1,48 @@ {{define "form/streamKCP"}} - + + + + None + SRTP + uTP + WeChat + DTLS 1.2 + WireGuard + DNS + + + + + + - + - + - - + + - + - + - + {{end}} diff --git a/web/html/form/stream/stream_settings.html b/web/html/form/stream/stream_settings.html index 5b00ef25..f6b17cc6 100644 --- a/web/html/form/stream/stream_settings.html +++ b/web/html/form/stream/stream_settings.html @@ -1,10 +1,8 @@ {{define "form/streamSettings"}} - + - TCP (RAW) mKCP @@ -50,10 +48,4 @@ - - - {{end}} diff --git a/web/html/form/stream/stream_xhttp.html b/web/html/form/stream/stream_xhttp.html index 447612c9..4b3052b6 100644 --- a/web/html/form/stream/stream_xhttp.html +++ b/web/html/form/stream/stream_xhttp.html @@ -1,6 +1,5 @@ {{define "form/streamXHTTP"}} - + @@ -8,138 +7,38 @@ - + - + - + - + - [[ key - ]] + [[ key ]] - - + + - - + + - - + + - - - - - - - Default (POST) - POST - PUT - GET (packet-up only) - - - - - Default (path) - path - header - cookie - query - - - - - - - - Default (path) - path - header - cookie - query - - - - - - - - Default (body) - body - header - query - - - - - - - - diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html index b2368d4f..3723130e 100644 --- a/web/html/form/tls_settings.html +++ b/web/html/form/tls_settings.html @@ -1,13 +1,11 @@ {{define "form/tlsSettings"}} - + {{ i18n "none" }} - Reality + Reality TLS @@ -18,44 +16,33 @@ - - Auto - [[ - value ]] + + Auto + [[ value ]] - - [[ key - ]] + [[ key ]] - - [[ key - ]] + [[ key ]] - - None - [[ key - ]] + None + [[ key ]] - - [[ alpn - ]] + + [[ alpn ]] @@ -70,25 +57,21 @@ + + + @@ -117,10 +99,8 @@ - - [[ key - ]] + + [[ key ]] @@ -128,22 +108,20 @@ - + - + - - [[ - key ]] - + + [[ key ]] + - Get New - ECH Cert + Get New ECH Cert Clear diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html index 1ab187ee..72023e75 100644 --- a/web/html/modals/inbound_info_modal.html +++ b/web/html/modals/inbound_info_modal.html @@ -1,8 +1,5 @@ {{define "modals/inboundInfoModal"}} - + @@ -29,8 +26,7 @@
-