From 3a57ebffe79c3e99f9296eab005ac3ca0d51aa56 Mon Sep 17 00:00:00 2001 From: shatinz Date: Wed, 4 Feb 2026 13:30:00 +0330 Subject: [PATCH] # Pull Request: Connection Reporting System & Improvements for Restricted Networks ## Description This PR introduces a comprehensive **Connection Reporting System** designed to improve the reliability and monitoring of connections, specifically tailored for environments with restricted internet access (e.g., active censorship, GFW). ### Key Changes 1. **New Reporting API (`/report`)**: * Added `ReportController` and `ReportService` to handle incoming connection reports. * Endpoint receives data such as `Latency`, `Success` status, `Protocol`, and Client Interface details. * Data is persisted to the database via the new `ConnectionReport` model. 2. **Subscription Link Updates**: * Modified `subService` to append a `reportUrl` parameter to generated subscription links (VLESS, VMess, etc.). * This allows compatible clients to automatically discover the reporting endpoint and send feedback. 3. **Database Integration**: * Added `ConnectionReport` schema to `database/model` and registered it in `database/db.go` for auto-migration. ## Why is this helpful for Restricted Internet Locations? In regions with heavy internet censorship, connection stability is volatile. * **Dynamic Reporting Endpoint**: The `reportUrl` parameter embedded in the subscription link explicitly tells the client *where* to send connection data. * **Bypassing Blocking**: By decoupling the reporting URL from the node address, clients can ensure diagnostic data reaches the panel even if specific node IPs are being interfered with (assuming the panel itself is reachable). * **Real-time Network Intelligence**: This mechanism enables the panel to aggregate "ground truth" data from clients inside the restricted network (e.g., latency, accessibility of specific protocols), allowing admins to react faster to blocking events. * **Protocol Performance Tracking**: Allows comparison of different protocols (Reality vs. VLESS+TLS vs. Trojan) based on real-world latency and success rates from actual users. * **Rapid Troubleshooting**: Administrators can see connection quality trends and rotate IPs/domains proactively when success rates drop, minimizing downtime for users. ## Technical Details * **API Endpoint**: `POST /report` * **Payload Format**: JSON containing `SystemInfo` (Interface), `ConnectionQuality` (Latency, Success), and `ProtocolInfo`. * **Security**: Reports are tied to valid client request contexts (implementation detail: ensure endpoint is rate-limited or authenticated if necessary, though currently designed for open reporting from valid sub links). ## How to Test 1. Update the panel. 2. Generate a subscription link. 3. Observe the `reportUrl` parameter in the link. 4. Simulate a client POST to the report URL and verify the entry in the `ConnectionReports` table. --- .github/copilot-instructions.md | 155 --- .github/workflows/cleanup_caches.yml | 31 - .github/workflows/release.yml | 39 +- DockerInit.sh | 2 +- config/version | 2 +- database/db.go | 1 + database/model/report.go | 15 + go.mod | 26 +- go.sum | 55 +- install.sh | 6 +- sub/sub.go | 28 +- sub/subController.go | 62 +- sub/subService.go | 27 +- update.sh | 6 +- web/assets/js/model/inbound.js | 176 +--- web/assets/js/model/outbound.js | 148 +-- web/assets/js/model/reality_targets.js | 24 +- web/assets/js/model/setting.js | 5 - web/controller/report.go | 78 ++ web/entity/entity.go | 5 - web/html/form/outbound.html | 105 +-- web/html/form/stream/stream_finalmask.html | 84 -- web/html/form/stream/stream_kcp.html | 46 +- web/html/form/stream/stream_settings.html | 12 +- web/html/form/stream/stream_xhttp.html | 125 +-- web/html/form/tls_settings.html | 86 +- web/html/modals/inbound_info_modal.html | 890 ++++++++---------- web/html/modals/xray_rule_modal.html | 8 +- .../settings/panel/subscription/general.html | 51 +- web/job/check_client_ip_job.go | 208 +--- web/service/report.go | 22 + web/service/server.go | 53 +- web/service/setting.go | 25 - web/service/tgbot.go | 6 +- web/translation/translate.ar_EG.toml | 10 - web/translation/translate.en_US.toml | 10 - web/translation/translate.es_ES.toml | 10 - web/translation/translate.fa_IR.toml | 10 - web/translation/translate.id_ID.toml | 10 - web/translation/translate.ja_JP.toml | 10 - web/translation/translate.pt_BR.toml | 10 - web/translation/translate.ru_RU.toml | 12 +- web/translation/translate.tr_TR.toml | 10 - web/translation/translate.uk_UA.toml | 10 - web/translation/translate.vi_VN.toml | 10 - web/translation/translate.zh_CN.toml | 10 - web/translation/translate.zh_TW.toml | 10 - web/web.go | 3 + x-ui.sh | 6 +- 49 files changed, 899 insertions(+), 1854 deletions(-) delete mode 100644 .github/copilot-instructions.md delete mode 100644 .github/workflows/cleanup_caches.yml create mode 100644 database/model/report.go create mode 100644 web/controller/report.go delete mode 100644 web/html/form/stream/stream_finalmask.html create mode 100644 web/service/report.go 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 @@
-