Compare commits

...

30 Commits

Author SHA1 Message Date
mhsanaei
96e43fa195 v2.3.8 2024-07-08 23:48:01 +02:00
mhsanaei
f1500a5d31 improved - message logs 2024-07-08 23:47:49 +02:00
mhsanaei
c9a218d060 axios v1.7.2 2024-07-08 21:03:20 +02:00
mhsanaei
6d18a15c4e Expand arch support in downloadXRay 2024-07-08 18:24:07 +02:00
dependabot[bot]
c0f86d2f38 Bump github.com/mymmrac/telego from 0.30.2 to 0.31.0 (#2432)
Bumps [github.com/mymmrac/telego](https://github.com/mymmrac/telego) from 0.30.2 to 0.31.0.
- [Release notes](https://github.com/mymmrac/telego/releases)
- [Commits](https://github.com/mymmrac/telego/compare/v0.30.2...v0.31.0)

---
updated-dependencies:
- dependency-name: github.com/mymmrac/telego
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 15:48:14 +02:00
mhsanaei
5227fefaeb update dependencies 2024-07-07 12:10:47 +02:00
mhsanaei
7a51d2f2cc Typo fixed 2024-07-07 12:10:24 +02:00
mhsanaei
02ae61fe6b change session name 2024-07-05 14:33:04 +02:00
mhsanaei
24b9e5bfa3 some changes 2024-07-04 15:04:04 +02:00
mhsanaei
9ff7f14b6e unnecessary log 2024-07-04 00:28:37 +02:00
mhsanaei
c3b42b8ea4 typo 2024-07-04 00:17:44 +02:00
mhsanaei
5afb8d85fc Optimize XrayAPI functionality and structure 2024-07-04 00:17:28 +02:00
dependabot[bot]
767ee4ec2b Bump google.golang.org/grpc from 1.64.0 to 1.65.0 (#2431)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 23:55:09 +02:00
mhsanaei
21b64beb96 tgbot - login notify (show password for failed login) 2024-07-03 21:53:45 +02:00
mhsanaei
b84e3ef338 improve bash menu 2024-07-02 00:34:25 +02:00
mhsanaei
86586b7e8f v2.3.7 2024-07-01 23:54:12 +02:00
mhsanaei
f9792632d4 pageSize default to 50 2024-07-01 21:11:42 +02:00
mhsanaei
a9ec24f811 update dependencies 2024-07-01 19:45:51 +02:00
mhsanaei
2da7dda794 grpc.Dial is deprecated: use NewClient instead 2024-07-01 19:22:35 +02:00
mhsanaei
39aae6fd16 sub - add hour for time left
1D,10H
22M
2024-07-01 19:11:40 +02:00
dependabot[bot]
f355ab5758 Bump github.com/shirou/gopsutil/v4 from 4.24.5 to 4.24.6 (#2429)
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.5 to 4.24.6.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.5...v4.24.6)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:13:28 +02:00
mhsanaei
3847bc0a78 Update README.md 2024-06-27 12:45:58 +02:00
mhsanaei
546d676472 Xray Core v1.8.16 2024-06-25 08:56:52 +02:00
mhsanaei
6fb6241c3c bash - update menu 2024-06-24 15:45:50 +02:00
mhsanaei
f481ab993e bash - Reset Web Base Path 2024-06-24 15:06:52 +02:00
mhsanaei
3ef4ab423f Update README.md 2024-06-24 14:15:32 +02:00
mhsanaei
b5a32ef57e update commands 2024-06-24 13:47:13 +02:00
mhsanaei
4033001798 update readme 2024-06-24 12:36:38 +02:00
mhsanaei
2486b5ff43 ensure file exists 2024-06-24 09:57:46 +02:00
dependabot[bot]
58647c6496 Bump github.com/xtls/xray-core from 1.8.15 to 1.8.16 (#2426)
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.15 to 1.8.16.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.15...v1.8.16)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 00:57:32 +02:00
46 changed files with 712 additions and 477 deletions

View File

@@ -83,7 +83,7 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.15/"
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.16/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip

View File

@@ -27,7 +27,7 @@ case $1 in
esac
mkdir -p build/bin
cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.15/Xray-linux-${ARCH}.zip"
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.16/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}"

View File

@@ -1,5 +1,3 @@
# 3X-UI
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>

195
README.md
View File

@@ -1,5 +1,3 @@
# 3X-UI
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
@@ -28,48 +26,59 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Install Custom Version
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.6`:
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.7`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.6
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.7
```
## SSL Certificate
<details>
<summary>Click for SSL Certificate</summary>
<summary>Click for SSL Certificate details</summary>
### Cloudflare
### ACME
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
To manage SSL certificates using ACME:
- Cloudflare registered email
- Cloudflare Global API Key
- The domain name has been resolved to the current server through cloudflare
1. Ensure your domain is correctly resolved to the server.
2. Run the `x-ui` command in the terminal, then choose `SSL Certificate Management`.
3. You will be presented with the following options:
How to get the Cloudflare Global API Key:
1. Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
2. Visit the link https://dash.cloudflare.com/profile/api-tokens
3. Click on View Global API Key (See the screenshot below)
![](media/APIKey1.PNG)
4. You may have to re-authenticate your account. After that, the API Key will be shown (See the screenshot below)\
![](media/APIKey2.png)
When using, just enter `domain name`, `email`, `API KEY`, the diagram is as follows:
![](media/DetailEnter.png)
- **Get SSL:** Obtain SSL certificates.
- **Revoke:** Revoke existing SSL certificates.
- **Force Renew:** Force renewal of SSL certificates.
### Certbot
```
To install and use Certbot:
```sh
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
### Cloudflare
The management script includes a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
- Cloudflare registered email
- Cloudflare Global API Key
- The domain name must be resolved to the current server through Cloudflare
**How to get the Cloudflare Global API Key:**
1. Run the `x-ui` command in the terminal, then choose `Cloudflare SSL Certificate`.
2. Visit the link: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens).
3. Click on "View Global API Key" (see the screenshot below):
![](media/APIKey1.PNG)
4. You may need to re-authenticate your account. After that, the API Key will be shown (see the screenshot below):
![](media/APIKey2.png)
When using, just enter your `domain name`, `email`, and `API KEY`. The diagram is as follows:
![](media/DetailEnter.png)
</details>
@@ -135,26 +144,26 @@ systemctl restart x-ui
#### Usage
1. Install Docker:
1. **Install Docker:**
```sh
bash <(curl -sSL https://get.docker.com)
```
2. Clone the Project Repository:
2. **Clone the Project Repository:**
```sh
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
```
3. Start the Service
3. **Start the Service:**
```sh
docker compose up -d
```
OR
**OR**
```sh
docker run -itd \
@@ -167,22 +176,22 @@ systemctl restart x-ui
ghcr.io/mhsanaei/3x-ui:latest
```
update to latest version
4. **Update to the Latest Version:**
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
remove 3x-ui from docker
5. **Remove 3x-ui from Docker:**
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
@@ -255,25 +264,45 @@ Our platform offers compatibility with a diverse range of architectures and devi
- Supports export/import database from the panel
## Default Settings
## Default Panel Settings
<details>
<summary>Click for default settings details</summary>
### Information
### Username & Password & webbasepath:
These will be generated randomly if you skip modifying them.
- **Port:** the default port for panel is `2053`
### Database Management:
You can conveniently perform database Backups and Restores directly from the panel.
- **Port:** 2053
- **Username & Password:** It will be generated randomly if you skip modifying.
- **Database Path:**
- /etc/x-ui/x-ui.db
- **Xray Config Path:**
- /usr/local/x-ui/bin/config.json
- **Web Panel Path w/o Deploying SSL:**
- http://ip:2053/panel
- http://domain:2053/panel
- **Web Panel Path w/ Deploying SSL:**
- https://domain:2053/panel
- `/etc/x-ui/x-ui.db`
### Web Base Path
1. **Reset Web Base Path:**
- Open your terminal.
- Run the `x-ui` command.
- Select the option to `Reset Web Base Path`.
2. **Generate or Customize Path:**
- The path will be randomly generated, or you can enter a custom path.
3. **View Current Settings:**
- To view your current settings, use the `x-ui settings` command in the terminal or `View Current Settings` in `x-ui`
### Security Recommendation:
- For enhanced security, use a long, random word in your URL structure.
**Examples:**
- `http://ip:port/*webbasepath*/panel`
- `http://domain:port/*webbasepath*/panel`
</details>
## WARP Configuration
@@ -283,27 +312,20 @@ Our platform offers compatibility with a diverse range of architectures and devi
#### Usage
If you want to use routing to WARP before v2.1.0 follow steps as below:
**For versions `v2.1.0` and later:**
**1.** Install WARP on **SOCKS Proxy Mode**:
WARP is built-in, and no additional installation is required. Simply turn on the necessary configuration in the panel.
```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
```
**For versions before `v2.1.0`:**
**2.** If you already installed warp, you can uninstall using below command:
1. Run the `x-ui` command in the terminal, then choose `WARP Management`.
2. You will see the following options:
```sh
warp u
```
- **Account Type (free, plus, team):** Choose the appropriate account type.
- **Enable/Disable WireProxy:** Toggle WireProxy on or off.
- **Uninstall WARP:** Remove the WARP application.
**3.** Turn on the config you need in panel
Config Features:
- Block Ads
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
- Fix Google 403 error
3. Configure the settings as needed in the panel.
</details>
@@ -314,29 +336,40 @@ If you want to use routing to WARP before v2.1.0 follow steps as below:
#### Usage
**Note:** IP Limit won't work correctly when using IP Tunnel
**Note:** IP Limit won't work correctly when using IP Tunnel.
- For versions up to `v1.6.1`:
- **For versions up to `v1.6.1`:**
- The IP limit is built-in to the panel
- IP limit is built-in into the panel.
**For versions `v1.7.0` and newer:**
- For versions `v1.7.0` and newer:
To enable the IP Limit functionality, you need to install `fail2ban` and its required files by following these steps:
- To make IP Limit work properly, you need to install fail2ban and its required files by following these steps:
1. Run the `x-ui` command in the terminal, then choose `IP Limit Management`.
2. You will see the following options:
1. Use the `x-ui` command inside the shell.
2. Select `IP Limit Management`.
3. Choose the appropriate options based on your needs.
- **Change Ban Duration:** Adjust the duration of bans.
- **Unban Everyone:** Lift all current bans.
- **Check Logs:** Review the logs.
- **Fail2ban Status:** Check the status of `fail2ban`.
- **Restart Fail2ban:** Restart the `fail2ban` service.
- **Uninstall Fail2ban:** Uninstall Fail2ban with configuration.
3. Add a path for the access log on the panel by setting `Xray Configs/log/Access log` to `./access.log` then save and restart xray.
- make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
```sh
- **For versions before `v2.1.3`:**
- You need to set the access log path manually in your Xray configuration:
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
```
- **For versions `v2.1.3` and newer:**
- There is an option for configuring `access.log` directly from the panel.
</details>

View File

@@ -1,5 +1,3 @@
# 3X-UI
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>

View File

@@ -1 +1 @@
2.3.6
2.3.8

26
go.mod
View File

@@ -7,37 +7,37 @@ require (
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.3
github.com/mymmrac/telego v0.30.2
github.com/mymmrac/telego v0.31.0
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.2
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.24.5
github.com/shirou/gopsutil/v4 v4.24.6
github.com/valyala/fasthttp v1.55.0
github.com/xtls/xray-core v1.8.15
github.com/xtls/xray-core v1.8.16
go.uber.org/atomic v1.11.0
golang.org/x/text v0.16.0
google.golang.org/grpc v1.64.0
google.golang.org/grpc v1.65.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.10
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.8 // indirect
github.com/bytedance/sonic v1.11.9 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/fasthttp/router v1.5.1 // indirect
github.com/fasthttp/router v1.5.2 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.21.0 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
@@ -50,7 +50,7 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -66,7 +66,7 @@ require (
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.4.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
@@ -83,11 +83,11 @@ require (
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect

52
go.sum
View File

@@ -18,8 +18,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA=
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -37,14 +37,14 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8=
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs=
github.com/fasthttp/router v1.5.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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=
@@ -69,8 +69,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0=
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
@@ -127,8 +127,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -157,8 +157,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.30.2 h1:CqGlqX0hkgz9qMwdA3q+aZtSonqMOKQQrFLn/oUOTaw=
github.com/mymmrac/telego v0.30.2/go.mod h1:U6cWJBgRCzGt+s0q77x/Dh2+i+u56VTAAYKlMenhuFc=
github.com/mymmrac/telego v0.31.0 h1:vsN+JCNkh7Z9vfL/2/AHZ2xBsRk2GCMj3zydjCxkgIc=
github.com/mymmrac/telego v0.31.0/go.mod h1:MuqgVf2xXnIOWZs0prvsp3f4Yss80kCSjVEj4CRl7Ig=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
@@ -200,13 +200,13 @@ github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v4 v4.24.5 h1:gGsArG5K6vmsh5hcFOHaPm87UD003CaDMkAOweSQjhM=
github.com/shirou/gopsutil/v4 v4.24.5/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA=
github.com/shirou/gopsutil/v4 v4.24.6 h1:9qqCSYF2pgOU+t+NgJtp7Co5+5mHF/HyKBUckySQL64=
github.com/shirou/gopsutil/v4 v4.24.6/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -275,8 +275,8 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.15 h1:8lmDV0TaqSz0Agdh4dqQstg2QJa183j2D59PKOTVc+Y=
github.com/xtls/xray-core v1.8.15/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M=
github.com/xtls/xray-core v1.8.16 h1:PhbpdREAIvDS7xmxR6Sdpkx0h5ugmf6wIoWECWtJ0kE=
github.com/xtls/xray-core v1.8.16/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -294,8 +294,8 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@@ -312,8 +312,8 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -339,8 +339,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
@@ -377,8 +377,8 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -121,10 +121,16 @@ install_base() {
esac
}
gen_random_string() {
local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w "$length" | head -n 1)
echo "$random_string"
}
# This function will be called when user installed x-ui out of security
config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
read -p "Do you want to continue with the modification [y/n]?": config_confirm
read -p "Would you like to customize the panel settings? (If not, random settings will be applied) [y/n]: " config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up your username: " config_account
echo -e "${yellow}Your username will be: ${config_account}${plain}"
@@ -146,7 +152,7 @@ config_after_install() {
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
local usernameTemp=$(head -c 6 /dev/urandom | base64)
local passwordTemp=$(head -c 6 /dev/urandom | base64)
local webBasePathTemp=$(head -c 6 /dev/urandom | base64)
local webBasePathTemp=$(gen_random_string 10)
/usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp} -webBasePath ${webBasePathTemp}
echo -e "This is a fresh installation, will generate random login info for security concerns:"
echo -e "###############################################"
@@ -154,9 +160,9 @@ config_after_install() {
echo -e "${green}Password: ${passwordTemp}${plain}"
echo -e "${green}WebBasePath: ${webBasePathTemp}${plain}"
echo -e "###############################################"
echo -e "${red}If you forgot your login info, you can type x-ui and then type 8 to check after installation${plain}"
echo -e "${yellow}If you forgot your login info, you can type "x-ui settings" to check after installation${plain}"
else
echo -e "${red}This is your upgrade, will keep old settings. If you forgot your login info, you can type x-ui and then type 8 to check${plain}"
echo -e "${yellow}This is your upgrade, will keep old settings. If you forgot your login info, you can type "x-ui settings" to check${plain}"
fi
fi
/usr/local/x-ui/x-ui migrate
@@ -218,18 +224,21 @@ install_x-ui() {
echo -e ""
echo -e "x-ui control menu usages: "
echo -e "----------------------------------------------"
echo -e "x-ui - Enter Admin menu"
echo -e "x-ui start - Start x-ui"
echo -e "x-ui stop - Stop x-ui"
echo -e "x-ui restart - Restart x-ui"
echo -e "x-ui status - Show x-ui status"
echo -e "x-ui enable - Enable x-ui on system startup"
echo -e "x-ui disable - Disable x-ui on system startup"
echo -e "x-ui log - Check x-ui logs"
echo -e "SUBCOMMANDS:"
echo -e "x-ui - Admin Management Script"
echo -e "x-ui start - Start"
echo -e "x-ui stop - Stop"
echo -e "x-ui restart - Restart"
echo -e "x-ui status - Current Status"
echo -e "x-ui settings - Current Settings"
echo -e "x-ui enable - Enable Autostart on OS Startup"
echo -e "x-ui disable - Disable Autostart on OS Startup"
echo -e "x-ui log - Check logs"
echo -e "x-ui banlog - Check Fail2ban ban logs"
echo -e "x-ui update - Update x-ui"
echo -e "x-ui install - Install x-ui"
echo -e "x-ui uninstall - Uninstall x-ui"
echo -e "x-ui update - Update"
echo -e "x-ui custom - custom version"
echo -e "x-ui install - Install"
echo -e "x-ui uninstall - Uninstall"
echo -e "----------------------------------------------"
}

98
main.go
View File

@@ -21,7 +21,7 @@ import (
)
func runWebServer() {
log.Printf("%v %v", config.GetName(), config.GetVersion())
log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
switch config.GetLogLevel() {
case config.Debug:
@@ -35,31 +35,29 @@ func runWebServer() {
case config.Error:
logger.InitLogger(logging.ERROR)
default:
log.Fatal("unknown log level:", config.GetLogLevel())
log.Fatalf("Unknown log level: %v", config.GetLogLevel())
}
err := database.InitDB(config.GetDBPath())
if err != nil {
log.Fatal(err)
log.Fatalf("Error initializing database: %v", err)
}
var server *web.Server
server = web.NewServer()
global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error starting web server: %v", err)
return
}
var subServer *sub.Server
subServer = sub.NewServer()
global.SetSubServer(subServer)
err = subServer.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error starting sub server: %v", err)
return
}
@@ -71,34 +69,39 @@ func runWebServer() {
switch sig {
case syscall.SIGHUP:
logger.Info("Received SIGHUP signal. Restarting servers...")
err := server.Stop()
if err != nil {
logger.Warning("stop server err:", err)
logger.Warning("Error stopping web server:", err)
}
err = subServer.Stop()
if err != nil {
logger.Warning("stop server err:", err)
logger.Warning("Error stopping sub server:", err)
}
server = web.NewServer()
global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error restarting web server: %v", err)
return
}
log.Println("Web server restarted successfully.")
subServer = sub.NewServer()
global.SetSubServer(subServer)
err = subServer.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error restarting sub server: %v", err)
return
}
log.Println("Sub server restarted successfully.")
default:
server.Stop()
subServer.Stop()
log.Println("Shutting down servers.")
return
}
}
@@ -107,16 +110,16 @@ func runWebServer() {
func resetSetting() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Failed to initialize database:", err)
return
}
settingService := service.SettingService{}
err = settingService.ResetSettings()
if err != nil {
fmt.Println("reset setting failed:", err)
fmt.Println("Failed to reset settings:", err)
} else {
fmt.Println("reset setting success")
fmt.Println("Settings successfully reset.")
}
}
@@ -159,19 +162,19 @@ func showSetting(show bool) {
func updateTgbotEnableSts(status bool) {
settingService := service.SettingService{}
currentTgSts, err := settingService.GetTgbotenabled()
currentTgSts, err := settingService.GetTgbotEnabled()
if err != nil {
fmt.Println(err)
return
}
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
if currentTgSts != status {
err := settingService.SetTgbotenabled(status)
err := settingService.SetTgbotEnabled(status)
if err != nil {
fmt.Println(err)
return
} else {
logger.Infof("SetTgbotenabled[%v] success", status)
logger.Infof("SetTgbotEnabled[%v] success", status)
}
}
}
@@ -179,7 +182,7 @@ func updateTgbotEnableSts(status bool) {
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Error initializing database:", err)
return
}
@@ -188,68 +191,65 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
if tgBotToken != "" {
err := settingService.SetTgBotToken(tgBotToken)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot token: %v\n", err)
return
} else {
logger.Info("updateTgbotSetting tgBotToken success")
}
logger.Info("Successfully updated Telegram bot token.")
}
if tgBotRuntime != "" {
err := settingService.SetTgbotRuntime(tgBotRuntime)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
return
} else {
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
}
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
}
if tgBotChatid != "" {
err := settingService.SetTgBotChatId(tgBotChatid)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
return
} else {
logger.Info("updateTgbotSetting tgBotChatid success")
}
logger.Info("Successfully updated Telegram bot chat ID.")
}
}
func updateSetting(port int, username string, password string, webBasePath string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Database initialization failed:", err)
return
}
settingService := service.SettingService{}
userService := service.UserService{}
if port > 0 {
err := settingService.SetPort(port)
if err != nil {
fmt.Println("set port failed:", err)
fmt.Println("Failed to set port:", err)
} else {
fmt.Printf("set port %v success", port)
fmt.Printf("Port set successfully: %v\n", port)
}
}
if username != "" || password != "" {
userService := service.UserService{}
err := userService.UpdateFirstUser(username, password)
if err != nil {
fmt.Println("set username and password failed:", err)
fmt.Println("Failed to update username and password:", err)
} else {
fmt.Println("set username and password success")
fmt.Println("Username and password updated successfully")
}
}
if webBasePath != "" {
err := settingService.SetBasePath(webBasePath)
if err != nil {
fmt.Println("set base URI path failed:", err)
fmt.Println("Failed to set base URI path:", err)
} else {
fmt.Println("set base URI path success")
fmt.Println("Base URI path set successfully")
}
}
}
@@ -348,19 +348,19 @@ func main() {
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.BoolVar(&remove_secret, "remove_secret", false, "remove secret")
settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "set web base path")
settingCmd.StringVar(&webCertFile, "webCert", "", "set web public key path")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "set web private key path")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Display current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
oldUsage := flag.Usage
flag.Usage = func() {

View File

@@ -163,13 +163,13 @@ func (s *Server) Start() (err error) {
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("sub server run https on", listener.Addr())
logger.Info("Sub server running HTTPS on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("sub server run http on", listener.Addr())
logger.Error("Error loading certificates:", err)
logger.Info("Sub server running HTTP on", listener.Addr())
}
} else {
logger.Info("sub server run http on", listener.Addr())
logger.Info("Sub server running HTTP on", listener.Addr())
}
s.listener = listener

View File

@@ -1007,9 +1007,37 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
now := time.Now().Unix()
switch exp := stats.ExpiryTime / 1000; {
case exp > 0:
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
remainingSeconds := exp - now
days := remainingSeconds / 86400
hours := (remainingSeconds % 86400) / 3600
minutes := (remainingSeconds % 3600) / 60
if days > 0 {
if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
} else {
remark = append(remark, fmt.Sprintf("%dD⏳", days))
}
} else if hours > 0 {
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
} else {
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
}
case exp < 0:
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
passedSeconds := now - exp
days := passedSeconds / 86400
hours := (passedSeconds % 86400) / 3600
minutes := (passedSeconds % 3600) / 60
if days > 0 {
if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
} else {
remark = append(remark, fmt.Sprintf("%dD⏳", days))
}
} else if hours > 0 {
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
} else {
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ class AllSetting {
this.webKeyFile = "";
this.webBasePath = "/";
this.sessionMaxAge = "";
this.pageSize = 0;
this.pageSize = 50;
this.expireDiff = "";
this.trafficDiff = "";
this.remarkModel = "-ieo";

View File

@@ -232,14 +232,12 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
}
email := c.Param("email")
needRestart := true
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}
jsonMsg(c, "traffic reseted", nil)
jsonMsg(c, "Traffic has been reset", nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
@@ -253,7 +251,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
} else {
a.xrayService.SetToNeedRestart()
}
jsonMsg(c, "All traffics reseted", nil)
jsonMsg(c, "all traffic has been reset", nil)
}
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
@@ -270,7 +268,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
} else {
a.xrayService.SetToNeedRestart()
}
jsonMsg(c, "All traffics of client reseted", nil)
jsonMsg(c, "All traffic from the client has been reset.", nil)
}
func (a *InboundController) importInbound(c *gin.Context) {
@@ -313,9 +311,9 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
jsonMsg(c, "Something went wrong!", err)
return
}
jsonMsg(c, "All delpeted clients are deleted", nil)
jsonMsg(c, "All depleted clients are deleted", nil)
}
func (a *InboundController) onlines(c *gin.Context) {
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
jsonObj(c, a.inboundService.GetOnlineClients(), nil)
}

View File

@@ -65,36 +65,36 @@ func (a *IndexController) login(c *gin.Context) {
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
timeStr := time.Now().Format("2006-01-02 15:04:05")
if user == nil {
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
logger.Warningf("wrong username or password or secret: \"%s\" \"%s\" \"%s\"", form.Username, form.Password, form.LoginSecret)
a.tgbot.UserLoginNotify(form.Username, form.Password, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return
} else {
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
logger.Infof("%s logged in successfully, Ip Address: %s\n", form.Username, getRemoteIp(c))
a.tgbot.UserLoginNotify(form.Username, ``, getRemoteIp(c), timeStr, 1)
}
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
if err != nil {
logger.Warningf("Unable to get session's max age from DB")
logger.Warning("Unable to get session's max age from DB")
}
if sessionMaxAge > 0 {
err = session.SetMaxAge(c, sessionMaxAge*60)
if err != nil {
logger.Warningf("Unable to set session's max age")
logger.Warning("Unable to set session's max age")
}
}
err = session.SetLoginUser(c, user)
logger.Info("user", user.Id, "login success")
logger.Infof("%s logged in successfully", user.Username)
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
}
func (a *IndexController) logout(c *gin.Context) {
user := session.GetLoginUser(c)
if user != nil {
logger.Info("user", user.Id, "logout")
logger.Infof("%s logged out successfully", user.Username)
}
session.ClearSession(c)
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))

View File

@@ -105,7 +105,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
jsonMsg(c, "", err)
return
}
jsonMsg(c, "Xray stoped", err)
jsonMsg(c, "Xray stopped", err)
}
func (a *ServerController) restartXrayService(c *gin.Context) {

View File

@@ -81,7 +81,7 @@ func (a *XraySettingController) warp(c *gin.Context) {
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license)
resp, err = a.XraySettingService.SetWarpLicense(license)
}
jsonObj(c, resp, err)

View File

@@ -79,8 +79,8 @@
qrModal: qrModal,
},
methods: {
copyToClipboard(elmentId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
copyToClipboard(elementId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.qrModal.clipboard.on('success', () => {
@@ -88,9 +88,9 @@
this.qrModal.clipboard.destroy();
});
},
setQrCode(elmentId, content) {
setQrCode(elementId, content) {
new QRious({
element: document.querySelector('#' + elmentId),
element: document.querySelector('#' + elementId),
size: 400,
value: content,
background: 'white',

View File

@@ -494,8 +494,8 @@
},
},
methods: {
copyToClipboard(elmentId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
copyToClipboard(elementId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.infoModal.clipboard.on('success', () => {

View File

@@ -664,7 +664,7 @@
tgBotEnable: false,
showAlert: false,
ipLimitEnable: false,
pageSize: 0,
pageSize: 50,
isMobile: window.innerWidth <= 768,
},
methods: {

View File

@@ -503,6 +503,7 @@
this.loading(false);
if (msg.success) {
this.user = {};
window.location.replace(basePath + "logout");
}
},
async restartPanel() {

View File

@@ -252,46 +252,55 @@ func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string)
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
jsonIps, err := json.Marshal(ips)
j.checkError(err)
if err != nil {
logger.Error("failed to marshal IPs to JSON:", err)
return false
}
inboundClientIps.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps)
// check inbound limitation
// Fetch inbound settings by client email
inbound, err := j.getInboundByEmail(clientEmail)
j.checkError(err)
if inbound.Settings == "" {
logger.Debug("wrong data ", inbound)
if err != nil {
logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err)
return false
}
if inbound.Settings == "" {
logger.Debug("wrong data:", inbound)
return false
}
// Unmarshal settings to get client limits
settings := map[string][]model.Client{}
json.Unmarshal([]byte(inbound.Settings), &settings)
clients := settings["clients"]
shouldCleanLog := false
j.disAllowedIps = []string{}
// create iplimit log file channel
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
// Open log file for IP limits
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
logger.Errorf("failed to create or open ip limit log file: %s", err)
logger.Errorf("failed to open IP limit log file: %s", err)
return false
}
defer logIpFile.Close()
log.SetOutput(logIpFile)
log.SetFlags(log.LstdFlags)
// Check client IP limits
for _, client := range clients {
if client.Email == clientEmail {
limitIp := client.LimitIP
if limitIp != 0 {
if limitIp > 0 && inbound.Enable {
shouldCleanLog = true
if limitIp < len(ips) && inbound.Enable {
if limitIp < len(ips) {
j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
for i := limitIp; i < len(ips); i++ {
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
logger.Debugf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
}
}
}
@@ -301,12 +310,15 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
sort.Strings(j.disAllowedIps)
if len(j.disAllowedIps) > 0 {
logger.Debug("disAllowedIps ", j.disAllowedIps)
logger.Debug("disAllowedIps:", j.disAllowedIps)
}
db := database.GetDB()
err = db.Save(inboundClientIps).Error
j.checkError(err)
if err != nil {
logger.Error("failed to save inboundClientIps:", err)
return false
}
return shouldCleanLog
}

View File

@@ -3,6 +3,7 @@ package job
import (
"io"
"os"
"path/filepath"
"x-ui/logger"
"x-ui/xray"
@@ -14,28 +15,53 @@ func NewClearLogsJob() *ClearLogsJob {
return new(ClearLogsJob)
}
// ensureFileExists creates the necessary directories and file if they don't exist
func ensureFileExists(path string) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return err
}
file.Close()
return nil
}
// Here Run is an interface method of the Job interface
func (j *ClearLogsJob) Run() {
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
// clear log files and copy to previous logs
// Ensure all log files and their paths exist
for _, path := range append(logFiles, logFilesPrev...) {
if err := ensureFileExists(path); err != nil {
logger.Warning("Failed to ensure log file exists:", path, "-", err)
}
}
// Clear log files and copy to previous logs
for i := 0; i < len(logFiles); i++ {
if i > 0 {
// copy to previous logs
// Copy to previous logs
logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
logger.Warning("clear logs job err:", err)
logger.Warning("Failed to open previous log file for writing:", logFilesPrev[i-1], "-", err)
continue
}
logFile, err := os.OpenFile(logFiles[i], os.O_CREATE|os.O_RDONLY, 0644)
if err == nil {
_, err = io.Copy(logFilePrev, logFile)
if err != nil {
logger.Warning("clear logs job err:", err)
}
} else {
logger.Warning("clear logs job err:", err)
logFile, err := os.OpenFile(logFiles[i], os.O_RDONLY, 0644)
if err != nil {
logger.Warning("Failed to open current log file for reading:", logFiles[i], "-", err)
logFilePrev.Close()
continue
}
_, err = io.Copy(logFilePrev, logFile)
if err != nil {
logger.Warning("Failed to copy log file:", logFiles[i], "to", logFilesPrev[i-1], "-", err)
}
logFile.Close()
@@ -44,7 +70,7 @@ func (j *ClearLogsJob) Run() {
err := os.Truncate(logFiles[i], 0)
if err != nil {
logger.Warning("clear logs job err:", err)
logger.Warning("Failed to truncate log file:", logFiles[i], "-", err)
}
}
}

View File

@@ -19,10 +19,8 @@ func (j *XrayTrafficJob) Run() {
if !j.xrayService.IsXrayRunning() {
return
}
traffics, clientTraffics, err := j.xrayService.GetXrayTraffic()
if err != nil {
logger.Warning("get xray traffic failed:", err)
return
}
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)

View File

@@ -595,7 +595,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
inerfaceClients := settings["clients"].([]interface{})
interfaceClients := settings["clients"].([]interface{})
oldInbound, err := s.GetInbound(data.Id)
if err != nil {
@@ -650,7 +650,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
settingsClients := oldSettings["clients"].([]interface{})
settingsClients[clientIndex] = inerfaceClients[0]
settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
@@ -1134,7 +1134,6 @@ func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
}
func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error {
logger.Warning(email)
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
}
@@ -1143,7 +1142,7 @@ func (s *InboundService) GetClientInboundByTrafficID(trafficId int) (traffic *xr
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warningf("Error retrieving ClientTraffic with trafficId %d: %v", trafficId, err)
return nil, nil, err
}
if len(traffics) > 0 {
@@ -1158,7 +1157,7 @@ func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.Cl
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, nil, err
}
if len(traffics) > 0 {
@@ -1699,15 +1698,20 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
db := database.GetDB()
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
// Retrieve inbounds where settings contain the given tgId
err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound {
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
return nil, err
}
var emails []string
for _, inbound := range inbounds {
clients, err := s.GetClients(inbound)
if err != nil {
logger.Error("Unable to get clients from inbound")
logger.Errorf("Error retrieving clients for inbound %d: %v", inbound.Id, err)
continue
}
for _, client := range clients {
if client.TgID == tgId {
@@ -1715,15 +1719,19 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
}
}
}
var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
logger.Warning(err)
return nil, err
logger.Warning("No ClientTraffic records found for emails:", emails)
return nil, nil
}
logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", emails, err)
return nil, err
}
return traffics, err
return traffics, nil
}
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
@@ -1732,7 +1740,7 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, err
}
if len(traffics) > 0 {
@@ -1747,38 +1755,51 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
inbound := &model.Inbound{}
traffic = &xray.ClientTraffic{}
err = db.Model(model.Inbound{}).Where("settings like ?", "%\""+query+"\"%").First(inbound).Error
// Search for inbound settings that contain the query
err = db.Model(model.Inbound{}).Where("settings LIKE ?", "%\""+query+"\"%").First(inbound).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
logger.Warning(err)
logger.Warningf("Inbound settings containing query %s not found: %v", query, err)
return nil, err
}
logger.Errorf("Error searching for inbound settings with query %s: %v", query, err)
return nil, err
}
traffic.InboundId = inbound.Id
// get settings clients
// Unmarshal settings to get clients
settings := map[string][]model.Client{}
json.Unmarshal([]byte(inbound.Settings), &settings)
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
logger.Errorf("Error unmarshalling inbound settings for inbound ID %d: %v", inbound.Id, err)
return nil, err
}
clients := settings["clients"]
for _, client := range clients {
if client.ID == query && client.Email != "" {
traffic.Email = client.Email
break
}
if client.Password == query && client.Email != "" {
if (client.ID == query || client.Password == query) && client.Email != "" {
traffic.Email = client.Email
break
}
}
if traffic.Email == "" {
return nil, err
logger.Warningf("No client found with query %s in inbound ID %d", query, inbound.Id)
return nil, gorm.ErrRecordNotFound
}
// Retrieve ClientTraffic based on the found email
err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
if err != nil {
logger.Warning(err)
if err == gorm.ErrRecordNotFound {
logger.Warningf("ClientTraffic for email %s not found: %v", traffic.Email, err)
return nil, err
}
logger.Errorf("Error retrieving ClientTraffic for email %s: %v", traffic.Email, err)
return nil, err
}
return traffic, err
return traffic, nil
}
func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
@@ -1948,6 +1969,6 @@ func (s *InboundService) MigrateDB() {
s.MigrationRemoveOrphanedTraffics()
}
func (s *InboundService) GetOnlineClinets() []string {
func (s *InboundService) GetOnlineClients() []string {
return p.GetOnlineClients()
}

View File

@@ -70,7 +70,7 @@ func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, erro
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
if err != nil {
logger.Warning(err)
logger.Warning("Error retrieving OutboundTraffics: ", err)
return nil, err
}

View File

@@ -19,7 +19,7 @@ func (s *PanelService) RestartPanel(delay time.Duration) error {
time.Sleep(delay)
err := p.Signal(syscall.SIGHUP)
if err != nil {
logger.Error("send signal SIGHUP failed:", err)
logger.Error("failed to send SIGHUP signal:", err)
}
}()
return nil

View File

@@ -312,6 +312,16 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
arch = "64"
case "arm64":
arch = "arm64-v8a"
case "armv7":
arch = "arm32-v7a"
case "armv6":
arch = "arm32-v6"
case "armv5":
arch = "arm32-v5"
case "386":
arch = "32"
case "s390x":
arch = "s390x"
}
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)

View File

@@ -33,7 +33,7 @@ var defaultValueMap = map[string]string{
"secret": random.Seq(32),
"webBasePath": "/",
"sessionMaxAge": "0",
"pageSize": "0",
"pageSize": "50",
"expireDiff": "0",
"trafficDiff": "0",
"remarkModel": "-ieo",
@@ -269,11 +269,11 @@ func (s *SettingService) SetTgBotChatId(chatIds string) error {
return s.setString("tgBotChatId", chatIds)
}
func (s *SettingService) GetTgbotenabled() (bool, error) {
func (s *SettingService) GetTgbotEnabled() (bool, error) {
return s.getBool("tgBotEnable")
}
func (s *SettingService) SetTgbotenabled(value bool) error {
func (s *SettingService) SetTgbotEnabled(value bool) error {
return s.setBool("tgBotEnable", value)
}
@@ -524,7 +524,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },

View File

@@ -64,52 +64,59 @@ func (t *Tgbot) GetHashStorage() *global.HashStorage {
}
func (t *Tgbot) Start(i18nFS embed.FS) error {
// Initialize localizer
err := locale.InitLocalizer(i18nFS, &t.settingService)
if err != nil {
return err
}
// init hash storage => store callback queries
// Initialize hash storage to store callback queries
hashStorage = global.NewHashStorage(20 * time.Minute)
t.SetHostname()
tgBottoken, err := t.settingService.GetTgBotToken()
if err != nil || tgBottoken == "" {
logger.Warning("Get TgBotToken failed:", err)
// Get Telegram bot token
tgBotToken, err := t.settingService.GetTgBotToken()
if err != nil || tgBotToken == "" {
logger.Warning("Failed to get Telegram bot token:", err)
return err
}
tgBotid, err := t.settingService.GetTgBotChatId()
// Get Telegram bot chat ID(s)
tgBotID, err := t.settingService.GetTgBotChatId()
if err != nil {
logger.Warning("Get GetTgBotChatId failed:", err)
logger.Warning("Failed to get Telegram bot chat ID:", err)
return err
}
if tgBotid != "" {
for _, adminId := range strings.Split(tgBotid, ",") {
id, err := strconv.Atoi(adminId)
// Parse admin IDs from comma-separated string
if tgBotID != "" {
for _, adminID := range strings.Split(tgBotID, ",") {
id, err := strconv.Atoi(adminID)
if err != nil {
logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
return err
}
adminIds = append(adminIds, int64(id))
}
}
// Get Telegram bot proxy URL
tgBotProxy, err := t.settingService.GetTgBotProxy()
if err != nil {
logger.Warning("Failed to get ProxyUrl:", err)
logger.Warning("Failed to get Telegram bot proxy URL:", err)
}
bot, err = t.NewBot(tgBottoken, tgBotProxy)
// Create new Telegram bot instance
bot, err = t.NewBot(tgBotToken, tgBotProxy)
if err != nil {
fmt.Println("Get tgbot's api error:", err)
logger.Error("Failed to initialize Telegram bot API:", err)
return err
}
// listen for TG bot income messages
// Start receiving Telegram bot messages
if !isRunning {
logger.Info("Starting Telegram receiver ...")
logger.Info("Telegram bot receiver started")
go t.OnReceive()
isRunning = true
}
@@ -201,7 +208,7 @@ func (t *Tgbot) OnReceive() {
}, th.AnyCommand())
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) {
t.asnwerCallback(&query, checkAdmin(query.From.ID))
t.answerCallback(&query, checkAdmin(query.From.ID))
}, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
@@ -286,7 +293,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
}
}
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
chatId := callbackQuery.Message.GetChat().ID
if isAdmin {
@@ -964,7 +971,7 @@ func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
return info
}
// Send server usage without an inline keyborad
// Send server usage without an inline keyboard
func (t *Tgbot) sendServerUsage() string {
info := t.prepareServerUsageInfo()
return info
@@ -1019,7 +1026,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
return info
}
func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) {
func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) {
if !t.IsRunning() {
return
}
@@ -1037,11 +1044,12 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
msg := ""
if status == LoginSuccess {
msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
} else if status == LoginFail {
msg += t.I18nBot("tgbot.messages.loginFailed")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
}
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.username", "Username=="+username)
msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip)
msg += t.I18nBot("tgbot.messages.time", "Time=="+time)
@@ -1051,14 +1059,14 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
func (t *Tgbot) getInboundUsages() string {
info := ""
// get traffic
inbouds, err := t.inboundService.GetAllInbounds()
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
logger.Warning("GetAllInbounds run failed:", err)
info += t.I18nBot("tgbot.answers.getInboundsFailed")
} else {
// NOTE:If there no any sessions here,need to notify here
// TODO:Sub-node push, automatic conversion format
for _, inbound := range inbouds {
for _, inbound := range inbounds {
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
@@ -1331,20 +1339,20 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
}
func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbouds, err := t.inboundService.SearchInbounds(remark)
inbounds, err := t.inboundService.SearchInbounds(remark)
if err != nil {
logger.Warning(err)
msg := t.I18nBot("tgbot.wentWrong")
t.SendMsgToTgbot(chatId, msg)
return
}
if len(inbouds) == 0 {
if len(inbounds) == 0 {
msg := t.I18nBot("tgbot.noInbounds")
t.SendMsgToTgbot(chatId, msg)
return
}
for _, inbound := range inbouds {
for _, inbound := range inbounds {
info := ""
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))

View File

@@ -97,7 +97,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
if !clientTraffic.Enable {
clients = RemoveIndex(clients, index-indexDecrease)
indexDecrease++
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
logger.Infof("Remove Inbound User %s due to expiration or traffic limit", c["email"])
}
}
}
@@ -165,11 +165,20 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
if !s.IsXrayRunning() {
return nil, nil, errors.New("xray is not running")
err := errors.New("xray is not running")
logger.Debug("Attempted to fetch Xray traffic, but Xray is not running:", err)
return nil, nil, err
}
s.xrayAPI.Init(p.GetAPIPort())
apiPort := p.GetAPIPort()
s.xrayAPI.Init(apiPort)
defer s.xrayAPI.Close()
return s.xrayAPI.GetTraffic(true)
traffic, clientTraffic, err := s.xrayAPI.GetTraffic(true)
if err != nil {
logger.Debug("Failed to fetch Xray traffic:", err)
return nil, nil, err
}
return traffic, clientTraffic, nil
}
func (s *XrayService) RestartXray(isForce bool) error {
@@ -202,7 +211,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
func (s *XrayService) StopXray() error {
lock.Lock()
defer lock.Unlock()
logger.Debug("stop xray")
logger.Debug("Attempting to stop Xray...")
if s.IsXrayRunning() {
return p.Stop()
}

View File

@@ -128,7 +128,7 @@ func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string
return result, nil
}
func (s *XraySettingService) SetWarpLicence(license string) (string, error) {
func (s *XraySettingService) SetWarpLicense(license string) (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {

View File

@@ -9,9 +9,7 @@ import (
"github.com/gin-gonic/gin"
)
const (
loginUser = "LOGIN_USER"
)
const loginUser = "LOGIN_USER"
func init() {
gob.Register(model.User{})
@@ -34,24 +32,28 @@ func SetMaxAge(c *gin.Context, maxAge int) error {
func GetLoginUser(c *gin.Context) *model.User {
s := sessions.Default(c)
obj := s.Get(loginUser)
if obj == nil {
return nil
if obj := s.Get(loginUser); obj != nil {
if user, ok := obj.(model.User); ok {
return &user
}
}
user := obj.(model.User)
return &user
return nil
}
func IsLogin(c *gin.Context) bool {
return GetLoginUser(c) != nil
}
func ClearSession(c *gin.Context) {
func ClearSession(c *gin.Context) error {
s := sessions.Default(c)
s.Clear()
s.Options(sessions.Options{
Path: "/",
MaxAge: -1,
})
s.Save()
if err := s.Save(); err != nil {
return err
}
c.SetCookie("3x-ui", "", -1, "/", "", false, true)
return nil
}

View File

@@ -44,7 +44,7 @@
"monitor" = "Listen IP"
"certificate" = "Digital Certificate"
"fail" = " Failed"
"success" = " Successful"
"success" = " Successfully"
"getVersion" = "Get Version"
"install" = "Install"
"clients" = "Clients"
@@ -78,7 +78,7 @@
"invalidFormData" = "The Input data format is invalid."
"emptyUsername" = "Username is required"
"emptyPassword" = "Password is required"
"wrongUsernameOrPassword" = "Invalid username or password."
"wrongUsernameOrPassword" = "Invalid username or password or secret."
"successLogin" = "Login"
[pages.index]
@@ -544,7 +544,7 @@
"selectUserFailed" = "❌ Error in user selection!"
"userSaved" = "✅ Telegram User saved."
"loginSuccess" = "✅ Logged in to the panel successfully.\r\n"
"loginFailed" = "❗️ Log in to the panel failed.\r\n"
"loginFailed" = "❗Login attempt to the panel failed.\r\n"
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
@@ -562,6 +562,7 @@
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Username: {{ .Username }}\r\n"
"password" = "👤 Password: {{ .Password }}\r\n"
"time" = "⏰ Time: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"

View File

@@ -560,6 +560,7 @@
"traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Estado de Xray: {{ .State }}\r\n"
"username" = "👤 Nombre de usuario: {{ .Username }}\r\n"
"password" = "👤 Contraseña: {{ .Password }}\r\n"
"time" = "⏰ Hora: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Puerto: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " وضعیت‌ایکس‌ری: {{ .State }}\r\n"
"username" = "👤 نام‌کاربری: {{ .Username }}\r\n"
"password" = "👤 رمز عبور: {{ .Password }}\r\n"
"time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
"password" = "👤 Kata Sandi: {{ .Password }}\r\n"
"time" = "⏰ Waktu: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Состояние Xray: {{ .State }}\r\n"
"username" = "👤 Имя пользователя: {{ .Username }}\r\n"
"password" = "👤 Пароль: {{ .Password }}\r\n"
"time" = "⏰ Время: {{ .Time }}\r\n"
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
"port" = "🔌 Порт: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Статус: {{ .State }}\r\n"
"username" = "👤 Ім'я користувача: {{ .Username }}\r\n"
"password" = "👤 Пароль: {{ .Password }}\r\n"
"time" = "⏰ Час: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Порт: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Trạng thái Xray: {{ .State }}\r\n"
"username" = "👤 Tên người dùng: {{ .Username }}\r\n"
"password" = "👤 Mật khẩu: {{ .Password }}\r\n"
"time" = "⏰ Thời gian: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Cổng: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Xray 状态:{{ .State }}\r\n"
"username" = "👤 用户名:{{ .Username }}\r\n"
"password" = "👤 密码: {{ .Password }}\r\n"
"time" = "⏰ 时间:{{ .Time }}\r\n"
"inbound" = "📍 入站:{{ .Remark }}\r\n"
"port" = "🔌 端口:{{ .Port }}\r\n"

View File

@@ -180,7 +180,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
assetsBasePath := basePath + "assets/"
store := cookie.NewStore(secret)
engine.Use(sessions.Sessions("session", store))
engine.Use(sessions.Sessions("3x-ui", store))
engine.Use(func(c *gin.Context) {
c.Set("base_path", basePath)
})
@@ -268,7 +268,7 @@ func (s *Server) startTask() {
// Make a traffic condition every day, 8:30
var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotenabled()
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) {
runtime, err := s.settingService.GetTgbotRuntime()
if err != nil || runtime == "" {
@@ -344,13 +344,13 @@ func (s *Server) Start() (err error) {
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("web server run https on", listener.Addr())
logger.Info("Web server running HTTPS on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("web server run http on", listener.Addr())
logger.Error("Error loading certificates:", err)
logger.Info("Web server running HTTP on", listener.Addr())
}
} else {
logger.Info("web server run http on", listener.Addr())
logger.Info("Web server running HTTP on", listener.Addr())
}
s.listener = listener
@@ -364,7 +364,7 @@ func (s *Server) Start() (err error) {
s.startTask()
isTgbotenabled, err := s.settingService.GetTgbotenabled()
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) {
tgBot := s.tgbotService.NewTgbot()
tgBot.Start(i18nFS)

178
x-ui.sh
View File

@@ -157,6 +157,30 @@ update() {
fi
}
update_menu() {
echo -e "${yellow}Updating Menu${plain}"
confirm "This function will update the menu to the latest changes." "y"
if [[ $? != 0 ]]; then
LOGE "Cancelled"
if [[ $# == 0 ]]; then
before_show_menu
fi
return 0
fi
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
chmod +x /usr/bin/x-ui
if [[ $? == 0 ]]; then
echo -e "${green}Update successful. The panel has automatically restarted.${plain}"
exit 0
else
echo -e "${red}Failed to update the menu.${plain}"
return 1
fi
}
custom_version() {
echo "Enter the panel version (like 2.0.0):"
read panel_version
@@ -228,6 +252,31 @@ reset_user() {
confirm_restart
}
gen_random_string() {
local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w "$length" | head -n 1)
echo "$random_string"
}
reset_webbasepath() {
echo -e "${yellow}Resetting Web Base Path${plain}"
# Prompt user to set a new web base path
read -rp "Please set the new web base path [press 'y' for a random path]: " config_webBasePath
if [[ $config_webBasePath == "y" ]]; then
config_webBasePath=$(gen_random_string 10)
fi
# Apply the new web base path setting
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1
systemctl restart x-ui
# Display confirmation message
echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}"
echo -e "${green}Please use the new web base path to access the panel.${plain}"
}
reset_config() {
confirm "Are you sure you want to reset all panel settings, Account data will not be lost, Username and password will not change" "n"
if [[ $? != 0 ]]; then
@@ -1070,7 +1119,7 @@ iplimit_main() {
echo -e "${green}\t4.${plain} Check Logs"
echo -e "${green}\t5.${plain} Fail2ban Status"
echo -e "${green}\t6.${plain} Restart Fail2ban"
echo -e "${green}\t7.${plain} Uninstall IP Limit"
echo -e "${green}\t7.${plain} Uninstall Fail2ban"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
@@ -1253,57 +1302,62 @@ remove_iplimit() {
show_usage() {
echo "x-ui control menu usages: "
echo "------------------------------------------"
echo -e "x-ui - Enter control menu"
echo -e "x-ui start - Start x-ui "
echo -e "x-ui stop - Stop x-ui "
echo -e "x-ui restart - Restart x-ui "
echo -e "x-ui status - Show x-ui status"
echo -e "x-ui enable - Enable x-ui on system startup"
echo -e "x-ui disable - Disable x-ui on system startup"
echo -e "x-ui log - Check x-ui logs"
echo -e "SUBCOMMANDS:"
echo -e "x-ui - Admin Management Script"
echo -e "x-ui start - Start"
echo -e "x-ui stop - Stop"
echo -e "x-ui restart - Restart"
echo -e "x-ui status - Current Status"
echo -e "x-ui settings - Current Settings"
echo -e "x-ui enable - Enable Autostart on OS Startup"
echo -e "x-ui disable - Disable Autostart on OS Startup"
echo -e "x-ui log - Check logs"
echo -e "x-ui banlog - Check Fail2ban ban logs"
echo -e "x-ui update - Update x-ui "
echo -e "x-ui install - Install x-ui "
echo -e "x-ui uninstall - Uninstall x-ui "
echo -e "x-ui update - Update"
echo -e "x-ui custom - custom version"
echo -e "x-ui install - Install"
echo -e "x-ui uninstall - Uninstall"
echo "------------------------------------------"
}
show_menu() {
echo -e "
${green}3X-ui Panel Management Script${plain}
${green}3X-UI Panel Management Script${plain}
${green}0.${plain} Exit Script
————————————————
${green}1.${plain} Install
${green}2.${plain} Update
${green}3.${plain} Custom Version
${green}4.${plain} Uninstall
${green}3.${plain} Update Menu
${green}4.${plain} Custom Version
${green}5.${plain} Uninstall
————————————————
${green}5.${plain} Reset Username & Password & Secret Token
${green}6.${plain} Reset Settings
${green}7.${plain} Change Port
${green}8.${plain} View Current Settings
${green}6.${plain} Reset Username & Password & Secret Token
${green}7.${plain} Reset Web Base Path
${green}8.${plain} Reset Settings
${green}9.${plain} Change Port
${green}10.${plain} View Current Settings
————————————————
${green}9.${plain} Start
${green}10.${plain} Stop
${green}11.${plain} Restart
${green}12.${plain} Check Status
${green}13.${plain} Check Logs
${green}11.${plain} Start
${green}12.${plain} Stop
${green}13.${plain} Restart
${green}14.${plain} Check Status
${green}15.${plain} Check Logs
————————————————
${green}14.${plain} Enable Autostart
${green}15.${plain} Disable Autostart
${green}16.${plain} Enable Autostart
${green}17.${plain} Disable Autostart
————————————————
${green}16.${plain} SSL Certificate Management
${green}17.${plain} Cloudflare SSL Certificate
${green}18.${plain} IP Limit Management
${green}19.${plain} WARP Management
${green}20.${plain} Firewall Management
${green}18.${plain} SSL Certificate Management
${green}19.${plain} Cloudflare SSL Certificate
${green}20.${plain} IP Limit Management
${green}21.${plain} WARP Management
${green}22.${plain} Firewall Management
————————————————
${green}21.${plain} Enable BBR
${green}22.${plain} Update Geo Files
${green}23.${plain} Speedtest by Ookla
${green}23.${plain} Enable BBR
${green}24.${plain} Update Geo Files
${green}25.${plain} Speedtest by Ookla
"
show_status
echo && read -p "Please enter your selection [0-23]: " num
echo && read -p "Please enter your selection [0-25]: " num
case "${num}" in
0)
@@ -1316,70 +1370,76 @@ show_menu() {
check_install && update
;;
3)
check_install && custom_version
check_install && update_menu
;;
4)
check_install && uninstall
check_install && custom_version
;;
5)
check_install && reset_user
check_install && uninstall
;;
6)
check_install && reset_config
check_install && reset_user
;;
7)
check_install && set_port
check_install && reset_webbasepath
;;
8)
check_install && check_config
check_install && reset_config
;;
9)
check_install && start
check_install && set_port
;;
10)
check_install && stop
check_install && check_config
;;
11)
check_install && restart
check_install && start
;;
12)
check_install && status
check_install && stop
;;
13)
check_install && show_log
check_install && restart
;;
14)
check_install && enable
check_install && status
;;
15)
check_install && disable
check_install && show_log
;;
16)
ssl_cert_issue_main
check_install && enable
;;
17)
ssl_cert_issue_CF
check_install && disable
;;
18)
iplimit_main
ssl_cert_issue_main
;;
19)
warp_cloudflare
ssl_cert_issue_CF
;;
20)
firewall_menu
iplimit_main
;;
21)
bbr_menu
warp_cloudflare
;;
22)
update_geo
firewall_menu
;;
23)
bbr_menu
;;
24)
update_geo
;;
25)
run_speedtest
;;
*)
LOGE "Please enter the correct number [0-23]"
LOGE "Please enter the correct number [0-25]"
;;
esac
}
@@ -1398,6 +1458,9 @@ if [[ $# > 0 ]]; then
"status")
check_install 0 && status 0
;;
"settings")
check_install 0 && check_config 0
;;
"enable")
check_install 0 && enable 0
;;
@@ -1413,6 +1476,9 @@ if [[ $# > 0 ]]; then
"update")
check_install 0 && update 0
;;
"custom")
check_install 0 && custom_version 0
;;
"install")
check_uninstall 0 && install 0
;;

View File

@@ -31,23 +31,27 @@ type XrayAPI struct {
isConnected bool
}
func (x *XrayAPI) Init(apiPort int) (err error) {
if apiPort == 0 {
return common.NewError("xray api port wrong:", apiPort)
func (x *XrayAPI) Init(apiPort int) error {
if apiPort <= 0 {
return fmt.Errorf("invalid Xray API port: %d", apiPort)
}
x.grpcClient, err = grpc.Dial(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
addr := fmt.Sprintf("127.0.0.1:%d", apiPort)
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return err
return fmt.Errorf("failed to connect to Xray API: %w", err)
}
x.grpcClient = conn
x.isConnected = true
hsClient := command.NewHandlerServiceClient(x.grpcClient)
ssClient := statsService.NewStatsServiceClient(x.grpcClient)
hsClient := command.NewHandlerServiceClient(conn)
ssClient := statsService.NewStatsServiceClient(conn)
x.HandlerServiceClient = &hsClient
x.StatsServiceClient = &ssClient
return
return nil
}
func (x *XrayAPI) Close() {
@@ -148,94 +152,101 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
return err
}
func (x *XrayAPI) RemoveUser(inboundTag string, email string) error {
client := *x.HandlerServiceClient
_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
Tag: inboundTag,
Operation: serial.ToTypedMessage(&command.RemoveUserOperation{
Email: email,
}),
})
return err
func (x *XrayAPI) RemoveUser(inboundTag, email string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
op := &command.RemoveUserOperation{Email: email}
req := &command.AlterInboundRequest{
Tag: inboundTag,
Operation: serial.ToTypedMessage(op),
}
_, err := (*x.HandlerServiceClient).AlterInbound(ctx, req)
if err != nil {
return fmt.Errorf("failed to remove user: %w", err)
}
return nil
}
func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
if x.grpcClient == nil {
return nil, nil, common.NewError("xray api is not initialized")
}
trafficRegex := regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
ClientTrafficRegex := regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
client := *x.StatsServiceClient
trafficRegex := regexp.MustCompile(`(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)`)
clientTrafficRegex := regexp.MustCompile(`user>>>([^>]+)>>>traffic>>>(downlink|uplink)`)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
request := &statsService.QueryStatsRequest{
Reset_: reset,
}
resp, err := client.QueryStats(ctx, request)
resp, err := (*x.StatsServiceClient).QueryStats(ctx, &statsService.QueryStatsRequest{Reset_: reset})
if err != nil {
logger.Debug("Failed to query Xray stats:", err)
return nil, nil, err
}
tagTrafficMap := map[string]*Traffic{}
emailTrafficMap := map[string]*ClientTraffic{}
clientTraffics := make([]*ClientTraffic, 0)
traffics := make([]*Traffic, 0)
tagTrafficMap := make(map[string]*Traffic)
emailTrafficMap := make(map[string]*ClientTraffic)
for _, stat := range resp.GetStat() {
matchs := trafficRegex.FindStringSubmatch(stat.Name)
if len(matchs) < 3 {
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
if len(matchs) < 3 {
continue
} else {
isUser := matchs[1] == "user"
email := matchs[2]
isDown := matchs[3] == "downlink"
if !isUser {
continue
}
traffic, ok := emailTrafficMap[email]
if !ok {
traffic = &ClientTraffic{
Email: email,
}
emailTrafficMap[email] = traffic
clientTraffics = append(clientTraffics, traffic)
}
if isDown {
traffic.Down = stat.Value
} else {
traffic.Up = stat.Value
}
}
continue
}
isInbound := matchs[1] == "inbound"
isOutbound := matchs[1] == "outbound"
tag := matchs[2]
isDown := matchs[3] == "downlink"
if tag == "api" {
continue
}
traffic, ok := tagTrafficMap[tag]
if !ok {
traffic = &Traffic{
IsInbound: isInbound,
IsOutbound: isOutbound,
Tag: tag,
}
tagTrafficMap[tag] = traffic
traffics = append(traffics, traffic)
}
if isDown {
traffic.Down = stat.Value
} else {
traffic.Up = stat.Value
if matches := trafficRegex.FindStringSubmatch(stat.Name); len(matches) == 4 {
processTraffic(matches, stat.Value, tagTrafficMap)
} else if matches := clientTrafficRegex.FindStringSubmatch(stat.Name); len(matches) == 3 {
processClientTraffic(matches, stat.Value, emailTrafficMap)
}
}
return traffics, clientTraffics, nil
return mapToSlice(tagTrafficMap), mapToSlice(emailTrafficMap), nil
}
func processTraffic(matches []string, value int64, trafficMap map[string]*Traffic) {
isInbound := matches[1] == "inbound"
tag := matches[2]
isDown := matches[3] == "downlink"
if tag == "api" {
return
}
traffic, ok := trafficMap[tag]
if !ok {
traffic = &Traffic{
IsInbound: isInbound,
IsOutbound: !isInbound,
Tag: tag,
}
trafficMap[tag] = traffic
}
if isDown {
traffic.Down = value
} else {
traffic.Up = value
}
}
func processClientTraffic(matches []string, value int64, clientTrafficMap map[string]*ClientTraffic) {
email := matches[1]
isDown := matches[2] == "downlink"
traffic, ok := clientTrafficMap[email]
if !ok {
traffic = &ClientTraffic{Email: email}
clientTrafficMap[email] = traffic
}
if isDown {
traffic.Down = value
} else {
traffic.Up = value
}
}
func mapToSlice[T any](m map[string]*T) []*T {
result := make([]*T, 0, len(m))
for _, v := range m {
result = append(result, v)
}
return result
}

View File

@@ -60,14 +60,14 @@ func GetAccessPersistentPrevLogPath() string {
func GetAccessLogPath() (string, error) {
config, err := os.ReadFile(GetConfigPath())
if err != nil {
logger.Warningf("Something went wrong: %s", err)
logger.Warningf("Failed to read configuration file: %s", err)
return "", err
}
jsonConfig := map[string]interface{}{}
err = json.Unmarshal([]byte(config), &jsonConfig)
if err != nil {
logger.Warningf("Something went wrong: %s", err)
logger.Warningf("Failed to parse JSON configuration: %s", err)
return "", err
}
@@ -206,7 +206,7 @@ func (p *process) Start() (err error) {
err = os.MkdirAll(config.GetLogFolder(), 0o770)
if err != nil {
logger.Warningf("Something went wrong: %s", err)
logger.Warningf("Failed to create log folder: %s", err)
}
configPath := GetConfigPath()
@@ -224,7 +224,7 @@ func (p *process) Start() (err error) {
go func() {
err := cmd.Run()
if err != nil {
logger.Error("Failure in running xray-core: ", err)
logger.Error("Failure in running xray-core:", err)
p.exitErr = err
}
}()