diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5938007f..39bd9aaa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,11 +10,11 @@ jobs: name: build x-ui amd64 version runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3.5.1 + - uses: actions/checkout@v3.5.2 - name: Set up Go uses: actions/setup-go@v4.0.0 with: - go-version: 'stable' + go-version: "stable" - name: build linux amd64 version run: | CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go @@ -28,7 +28,7 @@ jobs: cd bin wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip unzip Xray-linux-64.zip - rm -f Xray-linux-64.zip geoip.dat geosite.dat + rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat @@ -50,11 +50,11 @@ jobs: name: build x-ui arm64 version runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3.5.1 + - uses: actions/checkout@v3.5.2 - name: Set up Go uses: actions/setup-go@v4.0.0 with: - go-version: 'stable' + go-version: "stable" - name: build linux arm64 version run: | sudo apt-get update @@ -70,7 +70,7 @@ jobs: cd bin wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip unzip Xray-linux-arm64-v8a.zip - rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat + rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat diff --git a/.gitignore b/.gitignore index 7136428c..7ba03558 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea tmp +backup/ bin/ dist/ x-ui-*.tar.gz @@ -9,4 +10,5 @@ x-ui-*.tar.gz main release/ access.log +error.log .cache diff --git a/README.md b/README.md index 8c3bb5c8..f410713a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # 3x-ui + [![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases) [![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#) [![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#) [![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) - > **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment** +**If you think this project is helpful to you, you may wish to give a** :star2: + xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)** # Install & Upgrade @@ -15,20 +17,23 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)** ``` bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) ``` + ## Install custom version -To install your desired version you can add the version to the end of install command. Example for ver `v1.0.9`: + +To install your desired version you can add the version to the end of install command. Example for ver `v1.2.6`: + ``` -bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.0.9 +bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.2.6 ``` + # SSL + ``` apt-get install certbot -y certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com certbot renew --dry-run ``` -**If you think this project is helpful to you, you may wish to give a** :star2: - # Default settings - Port: 2053 @@ -36,18 +41,63 @@ certbot renew --dry-run - database path: /etc/x-ui/x-ui.db - xray config path: /usr/local/x-ui/bin/config.json -before you set ssl on settings -- http:// ip or domain:2053/xui +Before you set ssl on settings + +- http://ip:2053/xui +- http://domain:2053/xui + +After you set ssl on settings -After you set ssl on settings - https://yourdomain:2053/xui -# Enable Traffic For Users: +# Environment Variables + +| Variable | Type | Default | +| -------------- | :--------------------------------------------: | :------------ | +| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | +| XUI_DEBUG | `boolean` | `false` | +| XUI_BIN_FOLDER | `string` | `"bin"` | +| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` | + +Example: + +```sh +XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go +``` + +# Xray Configurations: **copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install) -- [enable traffic](./media/enable-traffic.txt) -- [enable traffic+block all IR IP address](./media/enable-traffic+block-IR-IP.txt) -- [enable traffic+block all IR domain](./media/enable-traffic+block-IR-domain.txt) + +- [traffic](./media/configs/traffic.json) +- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json) +- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json) +- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json) +- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json) + +# [WARP Configuration](https://github.com/fscarmen/warp) (Optional) + +If you want to use routing to WARP follow steps as below: + +1. If you already installed warp, you can uninstall using below command: + + ```sh + warp u + ``` + +2. Install WARP on **socks proxy mode**: + + ```sh + curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash + ``` + +3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json) + + Config Features: + + - Block Ads + - Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP + - Fix Google 403 error # Features @@ -62,7 +112,8 @@ After you set ssl on settings - Support https access panel (self-provided domain name + ssl certificate) - Support one-click SSL certificate application and automatic renewal - For more advanced configuration items, please refer to the panel -- fix api routes (user setting will create with api) +- Fix api routes (user setting will create with api) +- Support to change configs by different items provided in panel # Tg robot use @@ -79,8 +130,8 @@ Set the robot-related parameters in the panel background, including: Reference syntax: -- 30 * * * * * //Notify at the 30s of each point -- 0 */10 * * * * //Notify at the first second of each 10 minutes +- 30 \* \* \* \* \* //Notify at the 30s of each point +- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes - @hourly // hourly notification - @daily // Daily notification (00:00 in the morning) - @every 8h // notify every 8 hours @@ -100,38 +151,46 @@ Reference syntax: - Check depleted users - Receive backup by request and in periodic reports - ## API routes - `/login` with `PUSH` user data: `{username: '', password: ''}` for login - `/xui/API/inbounds` base for following actions: -| Method | Path | Action | -| ------------- | ------------- | ------------- | -| GET | "/list" | Get all inbounds | -| GET | "/get/:id" | Get inbound with inbound.id | -| POST | "/add" | Add inbound | -| POST | "/del/:id" | Delete Inbound | -| POST | "/update/:id" | Update Inbound | -| POST | "/clientIps/:email" | Client Ip address | -| POST | "/clearClientIps/:email" | Clear Client Ip address | -| POST | "/addClient/" | Add Client to inbound | -| POST | "/delClient/:email" | Delete Client | -| POST | "/updateClient/:index" | Update Client | -| POST | "/:id/resetClientTraffic/:email" | Reset Client's Traffic | -| POST | "/resetAllTraffics" | Reset traffics of all inbounds | -| POST | "/resetAllClientTraffics/:id" | Reset traffics of all clients in an inbound | +| Method | Path | Action | +| :----: | ---------------------------------- | ------------------------------------------- | +| `GET` | `"/list"` | Get all inbounds | +| `GET` | `"/get/:id"` | Get inbound with inbound.id | +| `POST` | `"/add"` | Add inbound | +| `POST` | `"/del/:id"` | Delete Inbound | +| `POST` | `"/update/:id"` | Update Inbound | +| `POST` | `"/clientIps/:email"` | Client Ip address | +| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address | +| `POST` | `"/addClient/"` | Add Client to inbound | +| `POST` | `"/delClient/:email"` | Delete Client | +| `POST` | `"/updateClient/:index"` | Update Client | +| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic | +| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | +| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound | # A Special Thanks To + - [alireza0](https://github.com/alireza0/) -- [FranzKafkaYu](https://github.com/FranzKafkaYu) # Suggestion System + - Ubuntu 20.04+ - Debian 10+ - CentOS 8+ - Fedora 36+ +# Buy Me a Coffee + +[![](https://img.shields.io/badge/Wallet-USDT__TRC20-green.svg)](#) + +``` +TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC +``` + # Pictures ![1](./media/1.png) diff --git a/config/version b/config/version index 0495c4a8..c04c650a 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -1.2.3 +1.2.7 diff --git a/database/db.go b/database/db.go index b9c16be8..ae42a6de 100644 --- a/database/db.go +++ b/database/db.go @@ -27,8 +27,9 @@ func initUser() error { } if count == 0 { user := &model.User{ - Username: "admin", - Password: "admin", + Username: "admin", + Password: "admin", + LoginSecret: "", } return db.Create(user).Error } diff --git a/database/model/model.go b/database/model/model.go index 778ad9b6..d1498b06 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -18,9 +18,10 @@ const ( ) type User struct { - Id int `json:"id" gorm:"primaryKey;autoIncrement"` - Username string `json:"username"` - Password string `json:"password"` + Id int `json:"id" gorm:"primaryKey;autoIncrement"` + Username string `json:"username"` + Password string `json:"password"` + LoginSecret string `json:"loginSecret"` } type Inbound struct { diff --git a/go.mod b/go.mod index b3f9ac32..160020a0 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.7 github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v3 v3.23.3 - github.com/xtls/xray-core v1.8.0 + github.com/xtls/xray-core v1.8.1 go.uber.org/atomic v1.10.0 golang.org/x/text v0.9.0 google.golang.org/grpc v1.54.0 diff --git a/go.sum b/go.sum index 1dcdc37d..1a3c1f94 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,7 @@ 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-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= +github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo= github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo= @@ -43,7 +44,7 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= @@ -61,7 +62,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso= +github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -79,7 +80,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -97,7 +98,7 @@ github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= -github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= +github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw= 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= @@ -106,7 +107,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA= github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0= -github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -122,15 +123,15 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= -github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= -github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:ULRv/GPW5KYDafE0FACN2no+HTCyQLUtfyOIeyp3GNc= +github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= 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/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08= -github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q= +github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk= +github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE= @@ -164,9 +165,9 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= -github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50= -github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k= -github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI= +github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE= +github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4= +github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= @@ -183,10 +184,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -227,7 +228,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/install.sh b/install.sh index 15226a08..df74a0cf 100644 --- a/install.sh +++ b/install.sh @@ -23,23 +23,14 @@ else fi echo "The OS release is: $release" -arch=$(arch) - -if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then - arch="amd64" -elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then - arch="arm64" -else - arch="amd64" - echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}" -fi - -echo "arch: ${arch}" - -if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then - echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!" - exit -1 -fi +arch3xui() { + case "$(uname -m)" in + x86_64 | x64 | amd64 ) echo 'amd64' ;; + armv8 | arm64 | aarch64 ) echo 'arm64' ;; + * ) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; + esac +} +echo "arch: $(arch3xui)" os_version="" os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) @@ -122,18 +113,18 @@ install_x-ui() { exit 1 fi echo -e "Got x-ui latest version: ${last_version}, beginning the installation..." - wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz + wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz if [[ $? -ne 0 ]]; then echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}" exit 1 fi else last_version=$1 - url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz" + url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz" echo -e "Begining to install x-ui $1" - wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url} + wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz ${url} if [[ $? -ne 0 ]]; then - echo -e "${red}Download x-ui $1 failed,please check the version exists${plain}" + echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}" exit 1 fi fi @@ -142,10 +133,10 @@ install_x-ui() { rm /usr/local/x-ui/ -rf fi - tar zxvf x-ui-linux-${arch}.tar.gz - rm x-ui-linux-${arch}.tar.gz -f + tar zxvf x-ui-linux-$(arch3xui).tar.gz + rm x-ui-linux-$(arch3xui).tar.gz -f cd x-ui - chmod +x x-ui bin/xray-linux-${arch} + chmod +x x-ui bin/xray-linux-$(arch3xui) cp -f x-ui.service /etc/systemd/system/ wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh diff --git a/main.go b/main.go index 2ea3fb33..710b80d4 100644 --- a/main.go +++ b/main.go @@ -51,8 +51,8 @@ func runWebServer() { } sigCh := make(chan os.Signal, 1) - //信号量捕获处理 - signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL) + // Trap shutdown signals + signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM) for { sig := <-sigCh @@ -204,6 +204,24 @@ func updateSetting(port int, username string, password string) { } } +func removeSecret() { + err := database.InitDB(config.GetDBPath()) + if err != nil { + fmt.Println(err) + return + } + userService := service.UserService{} + err = userService.RemoveUserSecret() + if err != nil { + fmt.Println(err) + } + settingService := service.SettingService{} + err = settingService.SetSecretStatus(false) + if err != nil { + fmt.Println(err) + } +} + func main() { if len(os.Args) < 2 { runWebServer() @@ -229,6 +247,7 @@ func main() { var tgbotRuntime string var reset bool var show bool + var remove_secret bool settingCmd.BoolVar(&reset, "reset", false, "reset all settings") settingCmd.BoolVar(&show, "show", false, "show current settings") settingCmd.IntVar(&port, "port", 0, "set panel port") @@ -290,6 +309,12 @@ func main() { if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") { updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime) } + if remove_secret { + removeSecret() + } + if enabletgbot { + updateTgbotEnableSts(enabletgbot) + } default: fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands") fmt.Println() diff --git a/media/configs/traffic+block-ads+ipv4-google.json b/media/configs/traffic+block-ads+ipv4-google.json new file mode 100644 index 00000000..d2ca2d45 --- /dev/null +++ b/media/configs/traffic+block-ads+ipv4-google.json @@ -0,0 +1,88 @@ +{ + "log": { + "loglevel": "warning", + "access": "./access.log", + "error": "./error.log" + }, + "api": { + "tag": "api", + "services": ["HandlerService", "LoggerService", "StatsService"] + }, + "inbounds": [ + { + "tag": "api", + "listen": "127.0.0.1", + "port": 62789, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "settings": {} + }, + { + "tag": "blocked", + "protocol": "blackhole", + "settings": {} + }, + { + "tag": "IPv4", + "protocol": "freedom", + "settings": { + "domainStrategy": "UseIPv4" + } + } + ], + "policy": { + "levels": { + "0": { + "statsUserDownlink": true, + "statsUserUplink": true + } + }, + "system": { + "statsInboundDownlink": true, + "statsInboundUplink": true + } + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "inboundTag": ["api"], + "outboundTag": "api" + }, + { + "type": "field", + "outboundTag": "blocked", + "ip": ["geoip:private"] + }, + { + "type": "field", + "outboundTag": "blocked", + "protocol": ["bittorrent"] + }, + { + "type": "field", + "outboundTag": "blocked", + "domain": [ + "geosite:category-ads-all", + "geosite:category-ads", + "geosite:google-ads", + "geosite:spotify-ads" + ] + }, + { + "type": "field", + "outboundTag": "IPv4", + "domain": ["geosite:google"] + } + ] + }, + "stats": {} +} diff --git a/media/configs/traffic+block-ads+warp.json b/media/configs/traffic+block-ads+warp.json new file mode 100644 index 00000000..504a8481 --- /dev/null +++ b/media/configs/traffic+block-ads+warp.json @@ -0,0 +1,98 @@ +{ + "log": { + "loglevel": "warning", + "access": "./access.log", + "error": "./error.log" + }, + "api": { + "tag": "api", + "services": ["HandlerService", "LoggerService", "StatsService"] + }, + "inbounds": [ + { + "tag": "api", + "listen": "127.0.0.1", + "port": 62789, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "settings": {} + }, + { + "tag": "blocked", + "protocol": "blackhole", + "settings": {} + }, + { + "tag": "WARP", + "protocol": "socks", + "settings": { + "servers": [ + { + "address": "127.0.0.1", + "port": 40000 + } + ] + } + } + ], + "policy": { + "levels": { + "0": { + "statsUserDownlink": true, + "statsUserUplink": true + } + }, + "system": { + "statsInboundDownlink": true, + "statsInboundUplink": true + } + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "inboundTag": ["api"], + "outboundTag": "api" + }, + { + "type": "field", + "outboundTag": "blocked", + "ip": ["geoip:private"] + }, + { + "type": "field", + "outboundTag": "blocked", + "protocol": ["bittorrent"] + }, + { + "type": "field", + "outboundTag": "blocked", + "domain": [ + "geosite:category-ads-all", + "geosite:category-ads", + "geosite:google-ads", + "geosite:spotify-ads" + ] + }, + { + "type": "field", + "outboundTag": "WARP", + "domain": [ + "geosite:google", + "geosite:netflix", + "geosite:spotify", + "geosite:openai" + ] + } + ] + }, + "stats": {} +} diff --git a/media/enable-traffic+block-IR-domain.txt b/media/configs/traffic+block-iran-domains.json similarity index 56% rename from media/enable-traffic+block-IR-domain.txt rename to media/configs/traffic+block-iran-domains.json index 4fcf3ee7..b6f05704 100644 --- a/media/enable-traffic+block-IR-domain.txt +++ b/media/configs/traffic+block-iran-domains.json @@ -1,25 +1,22 @@ { "log": { "loglevel": "warning", - "access": "./access.log" + "access": "./access.log", + "error": "./error.log" }, "api": { - "services": [ - "HandlerService", - "LoggerService", - "StatsService" - ], - "tag": "api" + "tag": "api", + "services": ["HandlerService", "LoggerService", "StatsService"] }, "inbounds": [ { + "tag": "api", "listen": "127.0.0.1", "port": 62789, "protocol": "dokodemo-door", "settings": { "address": "127.0.0.1" - }, - "tag": "api" + } } ], "outbounds": [ @@ -28,16 +25,16 @@ "settings": {} }, { + "tag": "blocked", "protocol": "blackhole", - "settings": {}, - "tag": "blocked" + "settings": {} } ], "policy": { "levels": { "0": { - "statsUserUplink": true, - "statsUserDownlink": true + "statsUserDownlink": true, + "statsUserUplink": true } }, "system": { @@ -49,36 +46,31 @@ "domainStrategy": "IPIfNonMatch", "rules": [ { - "inboundTag": [ - "api" - ], - "outboundTag": "api", - "type": "field" + "type": "field", + "inboundTag": ["api"], + "outboundTag": "api" }, { - "ip": [ - "geoip:private" - ], + "type": "field", "outboundTag": "blocked", - "type": "field" + "ip": ["geoip:private"] }, { + "type": "field", "outboundTag": "blocked", - "protocol": [ - "bittorrent" - ], - "type": "field" + "protocol": ["bittorrent"] }, { + "type": "field", "outboundTag": "blocked", "domain": [ - "regexp:.+.ir$", + "regexp:.*\\.ir$", "ext:iran.dat:ir", - "ext:iran.dat:other" - ], - "type": "field" + "ext:iran.dat:other", + "geosite:category-ir" + ] } ] }, "stats": {} -} \ No newline at end of file +} diff --git a/media/enable-traffic+block-IR-IP.txt b/media/configs/traffic+block-iran-ip.json similarity index 55% rename from media/enable-traffic+block-IR-IP.txt rename to media/configs/traffic+block-iran-ip.json index 919bef03..103ca700 100644 --- a/media/enable-traffic+block-IR-IP.txt +++ b/media/configs/traffic+block-iran-ip.json @@ -1,25 +1,22 @@ { "log": { "loglevel": "warning", - "access": "./access.log" + "access": "./access.log", + "error": "./error.log" }, "api": { - "services": [ - "HandlerService", - "LoggerService", - "StatsService" - ], - "tag": "api" + "tag": "api", + "services": ["HandlerService", "LoggerService", "StatsService"] }, "inbounds": [ { + "tag": "api", "listen": "127.0.0.1", "port": 62789, "protocol": "dokodemo-door", "settings": { "address": "127.0.0.1" - }, - "tag": "api" + } } ], "outbounds": [ @@ -28,16 +25,16 @@ "settings": {} }, { + "tag": "blocked", "protocol": "blackhole", - "settings": {}, - "tag": "blocked" + "settings": {} } ], "policy": { "levels": { "0": { - "statsUserUplink": true, - "statsUserDownlink": true + "statsUserDownlink": true, + "statsUserUplink": true } }, "system": { @@ -49,34 +46,31 @@ "domainStrategy": "IPIfNonMatch", "rules": [ { - "inboundTag": [ - "api" - ], - "outboundTag": "api", - "type": "field" + "type": "field", + "inboundTag": ["api"], + "outboundTag": "api" }, { + "type": "field", "outboundTag": "blocked", - "protocol": [ - "bittorrent" - ], - "type": "field" + "ip": ["geoip:private"] }, { + "type": "field", "outboundTag": "blocked", - "ip": [ - "geoip:private" - ], - "type": "field" + "protocol": ["bittorrent"] }, { + "type": "field", "outboundTag": "blocked", - "ip": [ - "geoip:ir" - ], - "type": "field" + "ip": ["geoip:private"] + }, + { + "type": "field", + "outboundTag": "blocked", + "ip": ["geoip:ir"] } ] }, "stats": {} -} \ No newline at end of file +} diff --git a/media/enable-traffic.txt b/media/configs/traffic.json similarity index 57% rename from media/enable-traffic.txt rename to media/configs/traffic.json index 34e2038f..9d8d2d9f 100644 --- a/media/enable-traffic.txt +++ b/media/configs/traffic.json @@ -1,25 +1,22 @@ { "log": { "loglevel": "warning", - "access": "./access.log" + "access": "./access.log", + "error": "./error.log" }, "api": { - "services": [ - "HandlerService", - "LoggerService", - "StatsService" - ], - "tag": "api" + "tag": "api", + "services": ["HandlerService", "LoggerService", "StatsService"] }, "inbounds": [ { + "tag": "api", "listen": "127.0.0.1", "port": 62789, "protocol": "dokodemo-door", "settings": { "address": "127.0.0.1" - }, - "tag": "api" + } } ], "outbounds": [ @@ -28,16 +25,16 @@ "settings": {} }, { + "tag": "blocked", "protocol": "blackhole", - "settings": {}, - "tag": "blocked" + "settings": {} } ], "policy": { "levels": { "0": { - "statsUserUplink": true, - "statsUserDownlink": true + "statsUserDownlink": true, + "statsUserUplink": true } }, "system": { @@ -49,27 +46,21 @@ "domainStrategy": "IPIfNonMatch", "rules": [ { - "inboundTag": [ - "api" - ], - "outboundTag": "api", - "type": "field" + "type": "field", + "inboundTag": ["api"], + "outboundTag": "api" }, { + "type": "field", "outboundTag": "blocked", - "ip": [ - "geoip:private" - ], - "type": "field" + "ip": ["geoip:private"] }, { + "type": "field", "outboundTag": "blocked", - "protocol": [ - "bittorrent" - ], - "type": "field" + "protocol": ["bittorrent"] } ] }, "stats": {} -} \ No newline at end of file +} diff --git a/web/assets/css/custom.css b/web/assets/css/custom.css index 229d8500..741b01ae 100644 --- a/web/assets/css/custom.css +++ b/web/assets/css/custom.css @@ -246,6 +246,11 @@ background-color: #2e3b52; } +.ant-card-dark .ant-select-disabled .ant-select-selection { + border: 1px solid rgba(255, 255, 255, 0.2); + background-color: #242c3a; +} + .ant-card-dark .ant-collapse-item { color: hsla(0,0%,100%,.65); background-color: #161b22; diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js index 1de76850..ace99f48 100644 --- a/web/assets/js/model/models.js +++ b/web/assets/js/model/models.js @@ -3,6 +3,7 @@ class User { constructor() { this.username = ""; this.password = ""; + this.LoginSecret = ""; } } @@ -180,6 +181,7 @@ class AllSetting { this.tgBotBackup = false; this.tgCpu = ""; this.xrayTemplateConfig = ""; + this.secretEnable = false; this.timeLocation = "Asia/Tehran"; diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index f5c7da3f..8614e588 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -49,6 +49,7 @@ const XTLS_FLOW_CONTROL = { const TLS_FLOW_CONTROL = { VISION: "xtls-rprx-vision", + VISION_UDP443: "xtls-rprx-vision-udp443", }; const TLS_VERSION_OPTION = { @@ -91,9 +92,6 @@ const UTLS_FINGERPRINT = { UTLS_RANDOMIZED: "randomized", }; -const bytesToHex = e => Array.from(e).map(e => e.toString(16).padStart(2, 0)).join(''); -const hexToBytes = e => new Uint8Array(e.match(/[0-9a-f]{2}/gi).map(e => parseInt(e, 16))); - const ALPN_OPTION = { H3: "h3", H2: "h2", @@ -481,7 +479,7 @@ class TlsStreamSettings extends XrayCommonClass { cipherSuites = '', certificates=[new TlsStreamSettings.Cert()], alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1], - settings=[new TlsStreamSettings.Settings()]) { + settings=new TlsStreamSettings.Settings()) { super(); this.server = serverName; this.minVersion = minVersion; @@ -508,8 +506,7 @@ class TlsStreamSettings extends XrayCommonClass { } if (!ObjectUtil.isEmpty(json.settings)) { - let values = json.settings[0]; - settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)]; + settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName); } return new TlsStreamSettings( json.serverName, @@ -530,7 +527,7 @@ class TlsStreamSettings extends XrayCommonClass { cipherSuites: this.cipherSuites, certificates: TlsStreamSettings.toJsonArray(this.certs), alpn: this.alpn, - settings: TlsStreamSettings.toJsonArray(this.settings), + settings: this.settings, }; } } @@ -598,71 +595,204 @@ TlsStreamSettings.Settings = class extends XrayCommonClass { }; } }; +class XtlsStreamSettings extends XrayCommonClass { + constructor(serverName='', + certificates=[new XtlsStreamSettings.Cert()], + alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1], + settings=new XtlsStreamSettings.Settings()) { + super(); + this.server = serverName; + this.certs = certificates; + this.alpn = alpn; + this.settings = settings; + } + + addCert(cert) { + this.certs.push(cert); + } + + removeCert(index) { + this.certs.splice(index, 1); + } + + static fromJson(json={}) { + let certs; + let settings; + if (!ObjectUtil.isEmpty(json.certificates)) { + certs = json.certificates.map(cert => XtlsStreamSettings.Cert.fromJson(cert)); + } + + if (!ObjectUtil.isEmpty(json.settings)) { + settings = new XtlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.serverName); + } + return new XtlsStreamSettings( + json.serverName, + certs, + json.alpn, + settings, + ); + } + + toJson() { + return { + serverName: this.server, + certificates: XtlsStreamSettings.toJsonArray(this.certs), + alpn: this.alpn, + settings: this.settings, + }; + } +} + +XtlsStreamSettings.Cert = class extends XrayCommonClass { + constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') { + super(); + this.useFile = useFile; + this.certFile = certificateFile; + this.keyFile = keyFile; + this.cert = certificate instanceof Array ? certificate.join('\n') : certificate; + this.key = key instanceof Array ? key.join('\n') : key; + } + + static fromJson(json={}) { + if ('certificateFile' in json && 'keyFile' in json) { + return new XtlsStreamSettings.Cert( + true, + json.certificateFile, + json.keyFile, + ); + } else { + return new XtlsStreamSettings.Cert( + false, '', '', + json.certificate.join('\n'), + json.key.join('\n'), + ); + } + } + + toJson() { + if (this.useFile) { + return { + certificateFile: this.certFile, + keyFile: this.keyFile, + }; + } else { + return { + certificate: this.cert.split('\n'), + key: this.key.split('\n'), + }; + } + } +}; + +XtlsStreamSettings.Settings = class extends XrayCommonClass { + constructor(allowInsecure = false, serverName = '') { + super(); + this.allowInsecure = allowInsecure; + this.serverName = serverName; + } + static fromJson(json = {}) { + return new XtlsStreamSettings.Settings( + json.allowInsecure, + json.servername, + ); + } + toJson() { + return { + allowInsecure: this.allowInsecure, + serverName: this.serverName, + }; + } +}; class RealityStreamSettings extends XrayCommonClass { constructor( show = false,xver = 0, - fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, dest = 'yahoo.com:443', serverNames = 'yahoo.com,www.yahoo.com', - privateKey = RandomUtil.randomX25519PrivateKey(), - publicKey = '', + privateKey = '', minClient = '', maxClient = '', maxTimediff = 0, - shortIds = RandomUtil.randowShortId() - ) - { + shortIds = RandomUtil.randowShortId(), + settings= new RealityStreamSettings.Settings() + ){ super(); this.show = show; this.xver = xver; - this.fingerprint = fingerprint; this.dest = dest; this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames; this.privateKey = privateKey; - this.publicKey = RandomUtil.randomX25519PublicKey(this.privateKey); this.minClient = minClient; this.maxClient = maxClient; this.maxTimediff = maxTimediff; this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds; + this.settings = settings; + } + + static fromJson(json = {}) { + let settings; + if (!ObjectUtil.isEmpty(json.settings)) { + settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName); } - static fromJson(json = {}) { return new RealityStreamSettings( json.show, json.xver, - json.fingerprint, json.dest, json.serverNames, json.privateKey, - json.publicKey, json.minClient, json.maxClient, json.maxTimediff, - json.shortIds + json.shortIds, + json.settings, ); - } - toJson() { + + } + toJson() { return { show: this.show, xver: this.xver, - fingerprint: this.fingerprint, dest: this.dest, - serverNames: this.serverNames.split(/,|,|\s+/), + serverNames: this.serverNames.split(","), privateKey: this.privateKey, - publicKey: this.publicKey, minClient: this.minClient, maxClient: this.maxClient, maxTimediff: this.maxTimediff, - shortIds: this.shortIds.split(/,|,|\s+/) - }; - } + shortIds: this.shortIds.split(","), + settings: this.settings, + }; } +} + +RealityStreamSettings.Settings = class extends XrayCommonClass { + constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '') { + super(); + this.publicKey = publicKey; + this.fingerprint = fingerprint; + this.serverName = serverName; + } + static fromJson(json = {}) { + return new RealityStreamSettings.Settings( + json.publicKey, + json.fingerprint, + json.serverName, + ); + } + toJson() { + return { + publicKey: this.publicKey, + fingerprint: this.fingerprint, + serverName: this.serverName, + }; + } +}; class StreamSettings extends XrayCommonClass { constructor(network='tcp', security='none', tlsSettings=new TlsStreamSettings(), + xtlsSettings=new XtlsStreamSettings(), realitySettings = new RealityStreamSettings(), tcpSettings=new TcpStreamSettings(), kcpSettings=new KcpStreamSettings(), @@ -675,6 +805,7 @@ class StreamSettings extends XrayCommonClass { this.network = network; this.security = security; this.tls = tlsSettings; + this.xtls = xtlsSettings; this.reality = realitySettings; this.tcp = tcpSettings; this.kcp = kcpSettings; @@ -685,7 +816,7 @@ class StreamSettings extends XrayCommonClass { } get isTls() { - return this.security === 'tls'; + return this.security === "tls"; } set isTls(isTls) { @@ -696,12 +827,12 @@ class StreamSettings extends XrayCommonClass { } } - get isXTLS() { + get isXtls() { return this.security === "xtls"; } - set isXTLS(isXTLS) { - if (isXTLS) { + set isXtls(isXtls) { + if (isXtls) { this.security = 'xtls'; } else { this.security = 'none'; @@ -715,27 +846,20 @@ class StreamSettings extends XrayCommonClass { set isReality(isReality) { if (isReality) { - this.security = "reality"; + this.security = 'reality'; } else { - this.security = "none"; + this.security = 'none'; } } - - static fromJson(json = {}) { - let tls, reality; - if (json.security === "xtls") { - tls = TlsStreamSettings.fromJson(json.XTLSSettings); - } else if (json.security === "tls") { - tls = TlsStreamSettings.fromJson(json.tlsSettings); - } - if (json.security === "reality") { - reality = RealityStreamSettings.fromJson(json.realitySettings) - } + + static fromJson(json={}) { + return new StreamSettings( json.network, json.security, - tls, - reality, + TlsStreamSettings.fromJson(json.tlsSettings), + XtlsStreamSettings.fromJson(json.xtlsSettings), + RealityStreamSettings.fromJson(json.realitySettings), TcpStreamSettings.fromJson(json.tcpSettings), KcpStreamSettings.fromJson(json.kcpSettings), WsStreamSettings.fromJson(json.wsSettings), @@ -751,9 +875,9 @@ class StreamSettings extends XrayCommonClass { network: network, security: this.security, tlsSettings: this.isTls ? this.tls.toJson() : undefined, - XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined, - tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, + xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined, realitySettings: this.isReality ? this.reality.toJson() : undefined, + tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined, wsSettings: network === 'ws' ? this.ws.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined, @@ -826,22 +950,18 @@ class Inbound extends XrayCommonClass { set tls(isTls) { if (isTls) { - this.xtls = false; - this.reality = false; this.stream.security = 'tls'; } else { this.stream.security = 'none'; } } - get XTLS() { + get xtls() { return this.stream.security === 'xtls'; } - set XTLS(isXTLS) { - if (isXTLS) { - this.xtls = false; - this.reality = false; + set xtls(isXtls) { + if (isXtls) { this.stream.security = 'xtls'; } else { this.stream.security = 'none'; @@ -850,19 +970,14 @@ class Inbound extends XrayCommonClass { //for Reality get reality() { - if (this.stream.security === "reality") { - return this.network === "tcp" || this.network === "grpc" || this.network === "http"; - } - return false; + return this.stream.security === 'reality'; } set reality(isReality) { if (isReality) { - this.tls = false; - this.xtls = false; - this.stream.security = "reality"; + this.stream.security = 'reality'; } else { - this.stream.security = "none"; + this.stream.security = 'none'; } } @@ -969,7 +1084,7 @@ class Inbound extends XrayCommonClass { } get serverName() { - if (this.stream.isTls || this.stream.isXTLS) { + if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) { return this.stream.tls.server; } return ""; @@ -1070,7 +1185,14 @@ class Inbound extends XrayCommonClass { default: return false; } - return this.network === "tcp" || this.network === "grpc" || this.network === "http"; + switch (this.network) { + case "tcp": + case "http": + case "grpc": + return true; + default: + return false; + } } //this is used for xtls-rprx-vision @@ -1090,7 +1212,7 @@ class Inbound extends XrayCommonClass { return this.canEnableTls(); } - canEnableXTLS() { + canEnableXtls() { switch (this.protocol) { case Protocols.VLESS: case Protocols.TROJAN: @@ -1195,10 +1317,10 @@ class Inbound extends XrayCommonClass { host: host, path: path, tls: this.stream.security, - sni: this.stream.tls.settings[0]['serverName'], - fp: this.stream.tls.settings[0]['fingerprint'], + sni: this.stream.tls.settings.serverName, + fp: this.stream.tls.settings.fingerprint, alpn: this.stream.tls.alpn.join(','), - allowInsecure: this.stream.tls.settings[0].allowInsecure, + allowInsecure: this.stream.tls.settings.allowInsecure, }; return 'vmess://' + base64(JSON.stringify(obj, null, 2)); } @@ -1257,54 +1379,54 @@ class Inbound extends XrayCommonClass { if (this.tls) { params.set("security", "tls"); - params.set("fp" , this.stream.tls.settings[0]['fingerprint']); + params.set("fp" , this.stream.tls.settings.fingerprint); params.set("alpn", this.stream.tls.alpn); - if(this.stream.tls.settings[0].allowInsecure){ + if(this.stream.tls.settings.allowInsecure){ params.set("allowInsecure", "1"); } if (!ObjectUtil.isEmpty(this.stream.tls.server)) { address = this.stream.tls.server; } - if (this.stream.tls.settings[0]['serverName'] !== ''){ - params.set("sni", this.stream.tls.settings[0]['serverName']); + if (this.stream.tls.settings.serverName !== ''){ + params.set("sni", this.stream.tls.settings.serverName); } if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) { params.set("flow", this.settings.vlesses[clientIndex].flow); } } - if (this.XTLS) { + if (this.xtls) { params.set("security", "xtls"); - params.set("alpn", this.stream.tls.alpn); - if(this.stream.tls.settings[0].allowInsecure){ + params.set("alpn", this.stream.xtls.alpn); + if(this.stream.xtls.settings.allowInsecure){ params.set("allowInsecure", "1"); } - if (!ObjectUtil.isEmpty(this.stream.tls.server)) { - address = this.stream.tls.server; - } + if (!ObjectUtil.isEmpty(this.stream.xtls.server)) { + address = this.stream.xtls.server; + } params.set("flow", this.settings.vlesses[clientIndex].flow); } if (this.reality) { params.set("security", "reality"); + params.set("pbk", this.stream.reality.settings.publicKey); if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { - params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]); + params.set("sni", this.stream.reality.serverNames.split(",")[0]); } - if (this.stream.reality.publicKey != "") { - //params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey)); - params.set("pbk", this.stream.reality.publicKey); - } - if (this.stream.network === 'tcp') { + if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) { params.set("flow", this.settings.vlesses[clientIndex].flow); } - if (this.stream.reality.shortIds != "") { - params.set("sid", this.stream.reality.shortIds); + if (this.stream.reality.shortIds.length > 0) { + params.set("sid", this.stream.reality.shortIds.split(",")[0]); } - if (this.stream.reality.fingerprint != "") { - params.set("fp", this.stream.reality.fingerprint); + if (!ObjectUtil.isEmpty(this.stream.reality.settings.fingerprint)) { + params.set("fp", this.stream.reality.settings.fingerprint); + } + if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) { + address = this.stream.reality.settings.serverName; } } - + const link = `vless://${uuid}@${address}:${port}`; const url = new URL(link); for (const [key, value] of params) { @@ -1376,47 +1498,47 @@ class Inbound extends XrayCommonClass { if (this.tls) { params.set("security", "tls"); - params.set("fp" , this.stream.tls.settings[0]['fingerprint']); + params.set("fp" , this.stream.tls.settings.fingerprint); params.set("alpn", this.stream.tls.alpn); - if(this.stream.tls.settings[0].allowInsecure){ + if(this.stream.tls.settings.allowInsecure){ params.set("allowInsecure", "1"); } if (!ObjectUtil.isEmpty(this.stream.tls.server)) { address = this.stream.tls.server; } - if (this.stream.tls.settings[0]['serverName'] !== ''){ - params.set("sni", this.stream.tls.settings[0]['serverName']); + if (this.stream.tls.settings.serverName !== ''){ + params.set("sni", this.stream.tls.settings.serverName); } } if (this.reality) { params.set("security", "reality"); + params.set("pbk", this.stream.reality.settings.publicKey); if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) { - params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]); + params.set("sni", this.stream.reality.serverNames.split(",")[0]); } - if (this.stream.reality.publicKey != "") { - //params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey)); - params.set("pbk", this.stream.reality.publicKey); + if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) { + address = this.stream.reality.settings.serverName; } - if (this.stream.network === 'tcp') { - params.set("flow", this.settings.trojans[clientIndex].flow); + if (this.stream.reality.shortIds.length > 0) { + params.set("sid", this.stream.reality.shortIds.split(",")[0]); } - if (this.stream.reality.shortIds != "") { - params.set("sid", this.stream.reality.shortIds); + if (!ObjectUtil.isEmpty(this.stream.reality.settings.fingerprint)) { + params.set("fp", this.stream.reality.settings.fingerprint); } - if (this.stream.reality.fingerprint != "") { - params.set("fp", this.stream.reality.fingerprint); + if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) { + address = this.stream.reality.settings.serverName; } } - if (this.XTLS) { + if (this.xtls) { params.set("security", "xtls"); - params.set("alpn", this.stream.tls.alpn); - if(this.stream.tls.settings[0].allowInsecure){ + params.set("alpn", this.stream.xtls.alpn); + if(this.stream.xtls.settings.allowInsecure){ params.set("allowInsecure", "1"); } if (!ObjectUtil.isEmpty(this.stream.tls.server)) { - address = this.stream.tls.server; + address = this.stream.xtls.server; } params.set("flow", this.settings.trojans[clientIndex].flow); } diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 405985da..451f63e9 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -94,26 +94,6 @@ const shortIdSeq = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ]; -const x25519Map = new Map( - [ - ['EH2FWe-Ij_FFAa2u9__-aiErLvVIneP601GOCdlyPWw', "goY3OtfaA4UYbiz7Hn0NysI5QJrK0VT_Chg6RLgUPQU"], - ['cKI_6DoMSP1IepeWWXrG3G9nkehl94KYBhagU50g2U0', "VigpKFbSLnHLzBWobZaS1IBmw--giJ51w92y723ajnU"], - ['qM2SNyK3NyHB6deWpEP3ITyCGKQFRTna_mlKP0w1QH0', "HYyIGuyNFslmcnNT7mrDdmuXwn4cm7smE_FZbYguKHQ"], - ['qCWg5GMEDFd3n1nxDswlIpOHoPUXMLuMOIiLUVzubkI', "rJFC3dUjJxMnVZiUGzmf_LFsJUwFWY-CU5RQgFOHCWM"], - ['4NOBxDrEsOhNI3Y3EnVIy_TN-uyBoAjQw6QM0YsOi0s', "CbcY9qc4YuMDJDyyL0OITlU824TBg1O84ClPy27e2RM"], - ['eBvFb0M4HpSOwWjtXV8zliiEs_hg56zX4a2LpuuqpEI', "CjulQ2qVIky7ImIfysgQhNX7s_drGLheCGSkVHcLZhc"], - ['yEpOzQV04NNcycWVeWtRNTzv5TS-ynTuKRacZCH-6U8', "O9RSr5gSdok2K_tobQnf_scyKVqnCx6C4Jrl7_rCZEQ"], - ['CNt6TAUVCwqM6xIBHyni0K3Zqbn2htKQLvLb6XDgh0s', "d9cGLVBrDFS02L2OvkqyqwFZ1Ux3AHs28ehl4Rwiyl0"], - ['EInKw-6Wr0rAHXlxxDuZU5mByIzcD3Z-_iWPzXlUL1k', "LlYD2nNVAvyjNvjZGZh4R8PkMIwkc6EycPTvR2LE0nQ"], - ['GKIKo7rcXVyle-EUHtGIDtYnDsI6osQmOUl3DTJRAGc', "VcqHivYGGoBkcxOI6cSSjQmneltstkb2OhvO53dyhEM"], - ['-FVDzv68IC17fJVlNDlhrrgX44WeBfbhwjWpCQVXGHE', "PGG2EYOvsFt2lAQTD7lqHeRxz2KxvllEDKcUrtizPBU"], - ['0H3OJEYEu6XW7woqy7cKh2vzg6YHkbF_xSDTHKyrsn4', "mzevpYbS8kXengBY5p7tt56QE4tS3lwlwRemmkcQeyc"], - ['8F8XywN6ci44ES6em2Z0fYYxyptB9uaXY9Hc1WSSPE4', "qCZUdWQZ2H33vWXnOkG8NpxBeq3qn5QWXlfCOWBNkkc"], - ['IN0dqfkC10dj-ifRHrg2PmmOrzYs697ajGMwcLbu-1g', "2UW_EO3r7uczPGUUlpJBnMDpDmWUHE2yDzCmXS4sckE"], - ['uIcmks5rAhvBe4dRaJOdeSqgxLGGMZhsGk4J4PEKL2s', "F9WJV_74IZp0Ide4hWjiJXk9FRtBUBkUr3mzU-q1lzk"], - ] -); - class RandomUtil { static randomIntRange(min, max) { @@ -170,26 +150,6 @@ class RandomUtil { }); } - static randowShortId() { - let str = ''; - str += this.randomShortIdSeq(8) - return str; - } - - static randomX25519PrivateKey() { - let num = x25519Map.size; - let index = this.randomInt(num); - let cntr = 0; - for (let key of x25519Map.keys()) { - if (cntr++ === index) { - return key; - } - } - } - - static randomX25519PublicKey(key) { - return x25519Map.get(key) - } static randomText() { var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var string = ''; @@ -199,6 +159,12 @@ class RandomUtil { } return string; } + + static randowShortId() { + let str = ''; + str += this.randomShortIdSeq(8) + return str; + } } class ObjectUtil { diff --git a/web/controller/api.go b/web/controller/api.go index c64b27bf..c8ad2a67 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -19,6 +19,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { g.GET("/list", a.getAllInbounds) g.GET("/get/:id", a.getSingleInbound) + g.GET("/getClientTraffics/:email", a.getClientTraffics) g.POST("/add", a.addInbound) g.POST("/del/:id", a.delInbound) g.POST("/update/:id", a.updateInbound) @@ -39,6 +40,9 @@ func (a *APIController) getAllInbounds(c *gin.Context) { func (a *APIController) getSingleInbound(c *gin.Context) { a.inboundController.getInbound(c) } +func (a *APIController) getClientTraffics(c *gin.Context) { + a.inboundController.getClientTraffics(c) +} func (a *APIController) addInbound(c *gin.Context) { a.inboundController.addInbound(c) } diff --git a/web/controller/inbound.go b/web/controller/inbound.go index f7ea35eb..f32cb766 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -33,7 +33,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/update/:id", a.updateInbound) g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) - g.POST("/addClient/", a.addInboundClient) + g.POST("/addClient", a.addInboundClient) g.POST("/delClient/:email", a.delInboundClient) g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) @@ -78,6 +78,16 @@ func (a *InboundController) getInbound(c *gin.Context) { jsonObj(c, inbound, nil) } +func (a *InboundController) getClientTraffics(c *gin.Context) { + email := c.Param("email") + clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email) + if err != nil { + jsonMsg(c, "Error getting traffics", err) + return + } + jsonObj(c, clientTraffics, nil) +} + func (a *InboundController) addInbound(c *gin.Context) { inbound := &model.Inbound{} err := c.ShouldBind(inbound) @@ -151,19 +161,19 @@ func (a *InboundController) clearClientIps(c *gin.Context) { jsonMsg(c, "Log Cleared", nil) } func (a *InboundController) addInboundClient(c *gin.Context) { - inbound := &model.Inbound{} - err := c.ShouldBind(inbound) + data := &model.Inbound{} + err := c.ShouldBind(data) if err != nil { jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) return } - err = a.inboundService.AddInboundClient(inbound) + err = a.inboundService.AddInboundClient(data) if err != nil { jsonMsg(c, "something worng!", err) return } - jsonMsg(c, "Client added", nil) + jsonMsg(c, "Client(s) added", nil) if err == nil { a.xrayService.SetToNeedRestart() } diff --git a/web/controller/index.go b/web/controller/index.go index b4f981e8..c19ee799 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -11,15 +11,17 @@ import ( ) type LoginForm struct { - Username string `json:"username" form:"username"` - Password string `json:"password" form:"password"` + Username string `json:"username" form:"username"` + Password string `json:"password" form:"password"` + LoginSecret string `json:"loginSecret" form:"loginSecret"` } type IndexController struct { BaseController - userService service.UserService - tgbot service.Tgbot + settingService service.SettingService + userService service.UserService + tgbot service.Tgbot } func NewIndexController(g *gin.RouterGroup) *IndexController { @@ -32,6 +34,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) { g.GET("/", a.index) g.POST("/login", a.login) g.GET("/logout", a.logout) + g.POST("/getSecretStatus", a.getSecretStatus) } func (a *IndexController) index(c *gin.Context) { @@ -57,7 +60,7 @@ func (a *IndexController) login(c *gin.Context) { pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword")) return } - user := a.userService.CheckUser(form.Username, form.Password) + user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret) timeStr := time.Now().Format("2006-01-02 15:04:05") if user == nil { a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) @@ -82,3 +85,11 @@ func (a *IndexController) logout(c *gin.Context) { session.ClearSession(c) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) } + +func (a *IndexController) getSecretStatus(c *gin.Context) { + status, err := a.settingService.GetSecretStatus() + if err == nil { + jsonObj(c, status, nil) + } + +} diff --git a/web/controller/server.go b/web/controller/server.go index 24c3d623..c365ae4b 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/logs/:count", a.getLogs) g.POST("/getConfigJson", a.getConfigJson) g.GET("/getDb", a.getDb) + g.POST("/getNewX25519Cert", a.getNewX25519Cert) } func (a *ServerController) refreshStatus() { @@ -114,7 +115,7 @@ func (a *ServerController) getLogs(c *gin.Context) { count := c.Param("count") logs, err := a.serverService.GetLogs(count) if err != nil { - jsonMsg(c, I18n(c, "getLogs"), err) + jsonMsg(c, "getLogs", err) return } jsonObj(c, logs, nil) @@ -123,7 +124,7 @@ func (a *ServerController) getLogs(c *gin.Context) { func (a *ServerController) getConfigJson(c *gin.Context) { configJson, err := a.serverService.GetConfigJson() if err != nil { - jsonMsg(c, I18n(c, "getLogs"), err) + jsonMsg(c, "get config.json", err) return } jsonObj(c, configJson, nil) @@ -132,7 +133,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) { func (a *ServerController) getDb(c *gin.Context) { db, err := a.serverService.GetDb() if err != nil { - jsonMsg(c, I18n(c, "getLogs"), err) + jsonMsg(c, "get Database", err) return } // Set the headers for the response @@ -142,3 +143,12 @@ func (a *ServerController) getDb(c *gin.Context) { // Write the file contents to the response c.Writer.Write(db) } + +func (a *ServerController) getNewX25519Cert(c *gin.Context) { + cert, err := a.serverService.GetNewX25519Cert() + if err != nil { + jsonMsg(c, "get x25519 certificate", err) + return + } + jsonObj(c, cert, nil) +} diff --git a/web/controller/setting.go b/web/controller/setting.go index 261eeec8..2726c228 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -17,6 +17,10 @@ type updateUserForm struct { NewPassword string `json:"newPassword" form:"newPassword"` } +type updateSecretForm struct { + LoginSecret string `json:"loginSecret" form:"loginSecret"` +} + type SettingController struct { settingService service.SettingService userService service.UserService @@ -37,6 +41,9 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) { g.POST("/update", a.updateSetting) g.POST("/updateUser", a.updateUser) g.POST("/restartPanel", a.restartPanel) + g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig) + g.POST("/updateUserSecret", a.updateSecret) + g.POST("/getUserSecret", a.getUserSecret) } func (a *SettingController) getAllSetting(c *gin.Context) { @@ -48,6 +55,15 @@ func (a *SettingController) getAllSetting(c *gin.Context) { jsonObj(c, allSetting, nil) } +func (a *SettingController) getDefaultJsonConfig(c *gin.Context) { + defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig() + if err != nil { + jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err) + return + } + jsonObj(c, defaultJsonConfig, nil) +} + func (a *SettingController) getDefaultSettings(c *gin.Context) { expireDiff, err := a.settingService.GetExpireDiff() if err != nil { @@ -118,3 +134,25 @@ func (a *SettingController) restartPanel(c *gin.Context) { err := a.panelService.RestartPanel(time.Second * 3) jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err) } + +func (a *SettingController) updateSecret(c *gin.Context) { + form := &updateSecretForm{} + err := c.ShouldBind(form) + if err != nil { + jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err) + } + user := session.GetLoginUser(c) + err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret) + if err == nil { + user.LoginSecret = form.LoginSecret + session.SetLoginUser(c, user) + } + jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), err) +} +func (a *SettingController) getUserSecret(c *gin.Context) { + loginUser := session.GetLoginUser(c) + user := a.userService.GetUserSecret(loginUser.Id) + if user != nil { + jsonObj(c, user, nil) + } +} diff --git a/web/controller/sub.go b/web/controller/sub.go index 5695f032..5ab1fe46 100644 --- a/web/controller/sub.go +++ b/web/controller/sub.go @@ -29,14 +29,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) { func (a *SUBController) subs(c *gin.Context) { subId := c.Param("subid") host := strings.Split(c.Request.Host, ":")[0] - subs, err := a.subService.GetSubs(subId, host) - if err != nil { + subs, header, err := a.subService.GetSubs(subId, host) + if err != nil || len(subs) == 0 { c.String(400, "Error!") } else { result := "" for _, sub := range subs { result += sub + "\n" } + + // Add subscription-userinfo + c.Writer.Header().Set("Subscription-Userinfo", header) + c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) } } diff --git a/web/entity/entity.go b/web/entity/entity.go index b464de00..f1b24520 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -42,6 +42,7 @@ type AllSetting struct { TgCpu int `json:"tgCpu" form:"tgCpu"` XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"` TimeLocation string `json:"timeLocation" form:"timeLocation"` + SecretEnable bool `json:"secretEnable" form:"secretEnable"` } func (s *AllSetting) CheckValid() error { diff --git a/web/html/login.html b/web/html/login.html index 4218793c..2f4cb3e6 100644 --- a/web/html/login.html +++ b/web/html/login.html @@ -57,6 +57,11 @@ + + + + + {{ i18n "login" }} @@ -98,10 +103,12 @@ data: { loading: false, user: new User(), + secretEnable: false, lang : "" }, created(){ this.lang = getLang(); + this.secretEnable = this.getSecretStatus(); }, methods: { async login() { @@ -111,6 +118,15 @@ if (msg.success) { location.href = basePath + 'xui/'; } + }, + async getSecretStatus() { + this.loading= true; + const msg = await HttpUtil.post('/getSecretStatus'); + this.loading = false; + if (msg.success){ + this.secretEnable = msg.obj; + return msg.obj; + } } } }); diff --git a/web/html/xui/client_bulk_modal.html b/web/html/xui/client_bulk_modal.html index 4e282ccd..46bc6657 100644 --- a/web/html/xui/client_bulk_modal.html +++ b/web/html/xui/client_bulk_modal.html @@ -33,6 +33,30 @@ {{ i18n "pages.client.clientCount" }} + + + {{ i18n "pages.inbounds.IPLimit" }} + + + + + + + + + + {{ i18n "none" }} + [[ key ]] + + + + + {{ i18n "none" }} + [[ key ]] + + @@ -51,10 +75,10 @@ - + - + @@ -83,9 +107,9 @@ confirm: null, dbInbound: new DBInbound(), inbound: new Inbound(), - clients: [], quantity: 1, totalGB: 0, + limitIp: 0, expiryTime: '', emailMethod: 0, firstNum: 1, @@ -94,8 +118,10 @@ emailPostfix: "", subId: "", tgId: "", + flow: "", delayedStart: false, ok() { + clients = []; method=clientsBulkModal.emailMethod; if(method>1){ start=clientsBulkModal.firstNum; @@ -113,11 +139,18 @@ newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; newClient.subId = clientsBulkModal.subId; newClient.tgId = clientsBulkModal.tgId; + newClient.limitIp = clientsBulkModal.limitIp; newClient._totalGB = clientsBulkModal.totalGB; newClient._expiryTime = clientsBulkModal.expiryTime; - clientsBulkModal.clients.push(newClient); + if(clientsBulkModal.inbound.canEnableTlsFlow()){ + newClient.flow = clientsBulkModal.flow; + } + if(clientsBulkModal.inbound.xtls){ + newClient.flow = clientsBulkModal.flow; + } + clients.push(newClient); } - ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound); + ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id); }, show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) { this.visible = true; @@ -128,15 +161,16 @@ this.totalGB = 0; this.expiryTime = 0; this.emailMethod= 0; + this.limitIp= 0; this.firstNum= 1; this.lastNum= 1; this.emailPrefix= ""; this.emailPostfix= ""; this.subId= ""; this.tgId= ""; + this.flow= ""; this.dbInbound = new DBInbound(dbInbound); this.inbound = dbInbound.toInbound(); - this.clients = this.getClients(this.inbound.protocol, this.inbound.settings); this.delayedStart = false; }, getClients(protocol, clientSettings) { diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index d1078f23..c01bd10c 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -12,6 +12,7 @@ confirmLoading: false, title: '', okText: '', + isEdit: false, dbInbound: new DBInbound(), inbound: new Inbound(), clients: [], @@ -21,9 +22,13 @@ isExpired: false, delayedStart: false, ok() { - ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index); + if(clientModal.isEdit){ + ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index); + } else { + ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id); + } }, - show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) { + show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) { this.visible = true; this.title = title; this.okText = okText; @@ -139,6 +144,24 @@ } document.getElementById("clientIPs").value = "" }, + resetClientTraffic(email,dbInboundId,iconElement) { + this.$confirm({ + title: '{{ i18n "pages.inbounds.resetTraffic"}}', + content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', + class: siderDrawer.isDarkTheme ? darkClass : '', + okText: '{{ i18n "reset"}}', + cancelText: '{{ i18n "cancel"}}', + onOk: async () => { + iconElement.disabled = true; + const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email); + if (msg.success) { + this.clientModal.clientStats.up = 0; + this.clientModal.clientStats.down = 0; + } + iconElement.disabled = false; + }, + }) + }, }, }); diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index 1de05ac9..330a20c1 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -68,7 +68,7 @@ - + {{ i18n "none" }} [[ key ]] @@ -98,12 +98,16 @@ [[ sizeFormat(clientStats.down) ]] ([[ sizeFormat(clientStats.up + clientStats.down) ]]) + + + + - + - + diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html index 91fd7afd..9f5c120a 100644 --- a/web/html/xui/form/protocol/trojan.html +++ b/web/html/xui/form/protocol/trojan.html @@ -1,7 +1,7 @@ {{define "form/trojan"}} - + @@ -31,7 +31,7 @@ - + {{ i18n "none" }} [[ key ]] diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html index 029f2c9e..68c719dd 100644 --- a/web/html/xui/form/protocol/vless.html +++ b/web/html/xui/form/protocol/vless.html @@ -1,7 +1,7 @@ {{define "form/vless"}} - + @@ -31,7 +31,7 @@ - + {{ i18n "none" }} [[ key ]] diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html index ad2b3960..d19e5cb7 100644 --- a/web/html/xui/form/protocol/vmess.html +++ b/web/html/xui/form/protocol/vmess.html @@ -1,7 +1,7 @@ {{define "form/vmess"}} - + diff --git a/web/html/xui/form/tls_settings.html b/web/html/xui/form/tls_settings.html index f954b76b..dcef3990 100644 --- a/web/html/xui/form/tls_settings.html +++ b/web/html/xui/form/tls_settings.html @@ -17,7 +17,7 @@ - + XTLS @@ -27,14 +27,14 @@ - + - - - + + + @@ -52,22 +52,22 @@ [[ key ]] - - + + + + + None [[ key ]] - - - [[ key ]] - + @@ -93,33 +93,79 @@ + + + + + + + + + [[ key ]] + + + + + + + + {{ i18n "pages.inbounds.certificatePath" }} + {{ i18n "pages.inbounds.certificateContent" }} + + + + + + + - + - + - + [[ key ]] + + + - + - - + + - - - - - - - + + + + + + + + + Get New Key + {{end}} \ No newline at end of file diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html index 049d529e..4e8c7dae 100644 --- a/web/html/xui/inbound_info_modal.html +++ b/web/html/xui/inbound_info_modal.html @@ -49,10 +49,14 @@ tls: {{ i18n "enabled" }}
tls {{ i18n "domainName" }}: [[ inbound.serverName ? inbound.serverName : '' ]] - + xtls: {{ i18n "enabled" }}
xtls {{ i18n "domainName" }}: [[ inbound.serverName ? inbound.serverName : '' ]] + + reality: {{ i18n "enabled" }}
+ reality {{ i18n "domainName" }}: [[ inbound.serverName ? inbound.serverName : '' ]] + tls: {{ i18n "disabled" }} diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index 6b140abc..98cb188f 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -43,6 +43,14 @@ loading(loading) { inModal.confirmLoading = loading; }, + getClients(protocol, clientSettings) { + switch(protocol){ + case Protocols.VMESS: return clientSettings.vmesses; + case Protocols.VLESS: return clientSettings.vlesses; + case Protocols.TROJAN: return clientSettings.trojans; + default: return null; + } + }, }; const protocols = { @@ -62,6 +70,7 @@ inModal: inModal, Protocols: protocols, SSMethods: SSMethods, + delayedStart: false, get inbound() { return inModal.inbound; }, @@ -70,36 +79,40 @@ }, get isEdit() { return inModal.isEdit; - } + }, + get client() { + return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0]; + }, + get delayedExpireDays() { + return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; + }, + set delayedExpireDays(days){ + this.client.expiryTime = -86400000 * days; + }, }, methods: { - streamNetworkChange(oldValue) { - if (oldValue === 'kcp') { - this.inModal.inbound.tls = false; + streamNetworkChange() { + if (!inModal.inbound.canSetTls()) { + this.inModal.inbound.stream.security = 'none'; } - }, - addClient(protocol, clients) { - switch (protocol) { - case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess()); - case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS()); - case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan()); - default: return null; + if (!inModal.inbound.canEnableReality()) { + this.inModal.inbound.reality = false; } }, - removeClient(index, clients) { - clients.splice(index, 1); - }, - isExpiry(index) { - return this.inbound.isExpiry(index) - }, - isClientEnable(email) { - clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null - return clientStats ? clientStats['enable'] : true - }, setDefaultCertData(){ inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert; inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey; }, + async getNewX25519Cert(){ + inModal.loading(true); + const msg = await HttpUtil.post('/server/getNewX25519Cert'); + inModal.loading(false); + if (!msg.success) { + return; + } + inModal.inbound.stream.reality.privateKey = msg.obj.privateKey; + inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey; + }, getNewEmail(client) { var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var string = ''; diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index b962efcb..5bfcaccd 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -133,26 +133,26 @@