mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-20 09:35:48 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49430b3991 | ||
|
|
104526aab2 | ||
|
|
a0c07241c0 | ||
|
|
adf3242602 | ||
|
|
3f62592e4b | ||
|
|
02bff4db6c | ||
|
|
8ff4e1ff31 | ||
|
|
26c6438ec2 | ||
|
|
b3e96230c4 | ||
|
|
1016f3b4f9 |
80
.github/workflows/docker.yml
vendored
80
.github/workflows/docker.yml
vendored
@@ -1,7 +1,9 @@
|
|||||||
name: Release 3X-UI for Docker
|
name: Release 3X-UI for Docker
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -13,48 +15,48 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
hsanaeii/3x-ui
|
hsanaeii/3x-ui
|
||||||
ghcr.io/mhsanaei/3x-ui
|
ghcr.io/mhsanaei/3x-ui
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=ref,event=tag
|
type=ref,event=tag
|
||||||
type=pep440,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6,linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.8.3
|
2.8.4
|
||||||
2
go.mod
2
go.mod
@@ -95,7 +95,7 @@ require (
|
|||||||
golang.org/x/tools v0.37.0 // indirect
|
golang.org/x/tools v0.37.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -236,6 +236,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 h1:V1jCN2HBa8sySkR5vLcCSqJSTMv093Rw9EJefhQGP7M=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
||||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
|||||||
25
install.sh
25
install.sh
@@ -56,6 +56,9 @@ install_base() {
|
|||||||
opensuse-tumbleweed)
|
opensuse-tumbleweed)
|
||||||
zypper refresh && zypper -q install -y wget curl tar timezone
|
zypper refresh && zypper -q install -y wget curl tar timezone
|
||||||
;;
|
;;
|
||||||
|
alpine)
|
||||||
|
apk update && apk add wget curl tar tzdata
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
apt-get update && apt-get install -y -q wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
@@ -177,7 +180,11 @@ install_x-ui() {
|
|||||||
|
|
||||||
# Stop x-ui service and remove old resources
|
# Stop x-ui service and remove old resources
|
||||||
if [[ -e /usr/local/x-ui/ ]]; then
|
if [[ -e /usr/local/x-ui/ ]]; then
|
||||||
systemctl stop x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui stop
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
fi
|
||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -201,10 +208,18 @@ install_x-ui() {
|
|||||||
chmod +x /usr/bin/x-ui
|
chmod +x /usr/bin/x-ui
|
||||||
config_after_install
|
config_after_install
|
||||||
|
|
||||||
cp -f x-ui.service /etc/systemd/system/
|
if [[ $release == "alpine" ]]; then
|
||||||
systemctl daemon-reload
|
wget -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc
|
||||||
systemctl enable x-ui
|
chmod +x /etc/init.d/x-ui
|
||||||
systemctl start x-ui
|
rc-update add x-ui
|
||||||
|
rc-service x-ui start
|
||||||
|
else
|
||||||
|
cp -f x-ui.service /etc/systemd/system/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl start x-ui
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "┌───────────────────────────────────────────────────────┐
|
echo -e "┌───────────────────────────────────────────────────────┐
|
||||||
|
|||||||
@@ -142,7 +142,10 @@
|
|||||||
},
|
},
|
||||||
npvtunUrl() {
|
npvtunUrl() {
|
||||||
return this.app.subUrl;
|
return this.app.subUrl;
|
||||||
}
|
},
|
||||||
|
happUrl() {
|
||||||
|
return `happ://add/${encodeURIComponent(this.app.subUrl)}`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
renderLink,
|
renderLink,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v2/web/service"
|
"github.com/mhsanaei/3x-ui/v2/web/service"
|
||||||
|
"github.com/mhsanaei/3x-ui/v2/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -21,11 +24,21 @@ func NewAPIController(g *gin.RouterGroup) *APIController {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkAPIAuth is a middleware that returns 404 for unauthenticated API requests
|
||||||
|
// to hide the existence of API endpoints from unauthorized users
|
||||||
|
func (a *APIController) checkAPIAuth(c *gin.Context) {
|
||||||
|
if !session.IsLogin(c) {
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
// initRouter sets up the API routes for inbounds, server, and other endpoints.
|
// initRouter sets up the API routes for inbounds, server, and other endpoints.
|
||||||
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
// Main API group
|
// Main API group
|
||||||
api := g.Group("/panel/api")
|
api := g.Group("/panel/api")
|
||||||
api.Use(a.checkLogin)
|
api.Use(a.checkAPIAuth)
|
||||||
|
|
||||||
// Inbounds API
|
// Inbounds API
|
||||||
inbounds := api.Group("/inbounds")
|
inbounds := api.Group("/inbounds")
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ func NewIndexController(g *gin.RouterGroup) *IndexController {
|
|||||||
// initRouter sets up the routes for index, login, logout, and two-factor authentication.
|
// initRouter sets up the routes for index, login, logout, and two-factor authentication.
|
||||||
func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.POST("/login", a.login)
|
|
||||||
g.GET("/logout", a.logout)
|
g.GET("/logout", a.logout)
|
||||||
|
|
||||||
|
g.POST("/login", a.login)
|
||||||
g.POST("/getTwoFactorEnable", a.getTwoFactorEnable)
|
g.POST("/getTwoFactorEnable", a.getTwoFactorEnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
type XUIController struct {
|
type XUIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
inboundController *InboundController
|
|
||||||
serverController *ServerController
|
|
||||||
settingController *SettingController
|
settingController *SettingController
|
||||||
xraySettingController *XraySettingController
|
xraySettingController *XraySettingController
|
||||||
}
|
}
|
||||||
@@ -31,8 +29,6 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.GET("/settings", a.settings)
|
g.GET("/settings", a.settings)
|
||||||
g.GET("/xray", a.xraySettings)
|
g.GET("/xray", a.xraySettings)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
|
||||||
a.serverController = NewServerController(g)
|
|
||||||
a.settingController = NewSettingController(g)
|
a.settingController = NewSettingController(g)
|
||||||
a.xraySettingController = NewXraySettingController(g)
|
a.xraySettingController = NewXraySettingController(g)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model.number="inbound.port" :min="1" :max="65531"></a-input-number>
|
<a-input-number v-model.number="inbound.port" :min="1" :max="65535"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -52,7 +52,8 @@
|
|||||||
<br v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
|
<br v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
|
||||||
<span v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
|
<span v-if="dbInbound.lastTrafficResetTime && dbInbound.lastTrafficResetTime > 0">
|
||||||
<strong>{{ i18n "pages.inbounds.lastReset" }}:</strong>
|
<strong>{{ i18n "pages.inbounds.lastReset" }}:</strong>
|
||||||
<span v-if="datepicker == 'gregorian'">[[ moment(dbInbound.lastTrafficResetTime).format('YYYY-MM-DD HH:mm:ss') ]]</span>
|
<span v-if="datepicker == 'gregorian'">[[
|
||||||
|
moment(dbInbound.lastTrafficResetTime).format('YYYY-MM-DD HH:mm:ss') ]]</span>
|
||||||
<span v-else>[[ DateUtil.convertToJalalian(moment(dbInbound.lastTrafficResetTime)) ]]</span>
|
<span v-else>[[ DateUtil.convertToJalalian(moment(dbInbound.lastTrafficResetTime)) ]]</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
<a-form-item label="External Proxy">
|
<a-form-item label="External Proxy">
|
||||||
<a-switch v-model="externalProxy"></a-switch>
|
<a-switch v-model="externalProxy"></a-switch>
|
||||||
<a-button icon="plus" v-if="externalProxy" type="primary" :style="{ marginLeft: '10px' }" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"></a-button>
|
<a-button icon="plus" v-if="externalProxy" type="primary" :style="{ marginLeft: '10px' }" size="small"
|
||||||
|
@click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-input-group :style="{ margin: '8px 0' }" compact v-for="(row, index) in inbound.stream.externalProxy">
|
<a-input-group :style="{ margin: '8px 0' }" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||||
<template>
|
<template>
|
||||||
<a-tooltip title="Force TLS">
|
<a-tooltip title="Force TLS">
|
||||||
<a-select v-model="row.forceTls" :style="{ width: '20%', margin: '0px' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="row.forceTls" :style="{ width: '20%', margin: '0px' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||||
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option value="tls">TLS</a-select-option>
|
<a-select-option value="tls">TLS</a-select-option>
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-input :style="{ width: '30%' }" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
<a-input :style="{ width: '30%' }" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number :style="{ width: '15%' }" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
<a-input-number :style="{ width: '15%' }" v-model.number="row.port" min="1" max="65535"></a-input-number>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input :style="{ width: '30%', top: '0' }" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
|
<a-input :style="{ width: '30%', top: '0' }" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
|
|||||||
@@ -7,12 +7,13 @@
|
|||||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model.number="dnsModal.dnsServer.port" :min="1" :max="65531"></a-input-number>
|
<a-input-number v-model.number="dnsModal.dnsServer.port" :min="1" :max="65535"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}'>
|
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}'>
|
||||||
<a-select v-model="dnsModal.dnsServer.queryStrategy" :style="{ width: '100%' }"
|
<a-select v-model="dnsModal.dnsServer.queryStrategy" :style="{ width: '100%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l" :label="l" v-for="l in ['UseSystem', 'UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
|
<a-select-option :value="l" :label="l" v-for="l in ['UseSystem', 'UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]]
|
||||||
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<template #title>{{ i18n "pages.settings.panelPort"}}</template>
|
<template #title>{{ i18n "pages.settings.panelPort"}}</template>
|
||||||
<template #description>{{ i18n "pages.settings.panelPortDesc"}}</template>
|
<template #description>{{ i18n "pages.settings.panelPortDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number :min="1" :min="65531" v-model="allSetting.webPort" :style="{ width: '100%' }"></a-input>
|
<a-input-number :min="1" :min="65535" v-model="allSetting.webPort" :style="{ width: '100%' }"></a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
@@ -137,7 +137,8 @@
|
|||||||
<template #title>{{ i18n "pages.settings.datepicker"}}</template>
|
<template #title>{{ i18n "pages.settings.datepicker"}}</template>
|
||||||
<template #description>{{ i18n "pages.settings.datepickerDescription"}}</template>
|
<template #description>{{ i18n "pages.settings.datepickerDescription"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-select :style="{ width: '100%' }" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker">
|
<a-select :style="{ width: '100%' }" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
v-model="datepicker">
|
||||||
<a-select-option v-for="item in datepickerList" :value="item.value">
|
<a-select-option v-for="item in datepickerList" :value="item.value">
|
||||||
<span v-text="item.name"></span>
|
<span v-text="item.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
||||||
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input-number v-model="allSetting.subPort" :min="1" :min="65531"
|
<a-input-number v-model="allSetting.subPort" :min="1" :min="65535"
|
||||||
:style="{ width: '100%' }"></a-input-number>
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
@@ -48,13 +48,10 @@
|
|||||||
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
||||||
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
|
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
|
||||||
<template #control>
|
<template #control>
|
||||||
<a-input
|
<a-input type="text" v-model="allSetting.subPath"
|
||||||
type="text"
|
|
||||||
v-model="allSetting.subPath"
|
|
||||||
@input="allSetting.subPath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
|
@input="allSetting.subPath = ((typeof $event === 'string' ? $event : ($event && $event.target ? $event.target.value : '')) || '').replace(/[:*]/g, '')"
|
||||||
@blur="allSetting.subPath = (p => { p = p || '/'; if (!p.startsWith('/')) p='/' + p; if (!p.endsWith('/')) p += '/'; return p.replace(/\/+/g,'/'); })(allSetting.subPath)"
|
@blur="allSetting.subPath = (p => { p = p || '/'; if (!p.startsWith('/')) p='/' + p; if (!p.endsWith('/')) p += '/'; return p.replace(/\/+/g,'/'); })(allSetting.subPath)"
|
||||||
placeholder="/sub/"
|
placeholder="/sub/"></a-input>
|
||||||
></a-input>
|
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
<a-setting-list-item paddings="small">
|
<a-setting-list-item paddings="small">
|
||||||
|
|||||||
@@ -218,6 +218,8 @@
|
|||||||
<a-menu-item key="android-npvtunnel"
|
<a-menu-item key="android-npvtunnel"
|
||||||
@click="copy(app.subUrl)">NPV
|
@click="copy(app.subUrl)">NPV
|
||||||
Tunnel</a-menu-item>
|
Tunnel</a-menu-item>
|
||||||
|
<a-menu-item key="android-happ"
|
||||||
|
@click="open('happ://add/' + encodeURIComponent(app.subUrl))">Happ</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -244,6 +246,8 @@
|
|||||||
@click="copy(npvtunUrl)">NPV
|
@click="copy(npvtunUrl)">NPV
|
||||||
Tunnel
|
Tunnel
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="ios-happ"
|
||||||
|
@click="open(happUrl)">Happ</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|||||||
@@ -12,13 +12,14 @@
|
|||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }" message='{{ i18n "secAlertTitle" }}'
|
<a-alert type="error" v-if="showAlert && loadingStates.fetched" :style="{ marginBottom: '10px' }"
|
||||||
color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable>
|
message='{{ i18n "secAlertTitle" }}' color="red" description='{{ i18n "secAlertSsl" }}' show-icon closable>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row v-if="!loadingStates.fetched">
|
<a-row v-if="!loadingStates.fetched">
|
||||||
<a-card :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }">
|
<a-card
|
||||||
|
:style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent', border: 'none' }">
|
||||||
<a-spin tip='{{ i18n "loading" }}'></a-spin>
|
<a-spin tip='{{ i18n "loading" }}'></a-spin>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -37,7 +38,8 @@
|
|||||||
<a-popover v-if="restartResult" :overlay-class-name="themeSwitcher.currentTheme">
|
<a-popover v-if="restartResult" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
<span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line ]]</span>
|
<span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line
|
||||||
|
]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
@@ -537,6 +539,7 @@
|
|||||||
serverObj = o.settings.vnext;
|
serverObj = o.settings.vnext;
|
||||||
break;
|
break;
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
|
return [o.settings?.address + ':' + o.settings?.port];
|
||||||
case Protocols.HTTP:
|
case Protocols.HTTP:
|
||||||
case Protocols.Socks:
|
case Protocols.Socks:
|
||||||
case Protocols.Shadowsocks:
|
case Protocols.Shadowsocks:
|
||||||
|
|||||||
@@ -35,6 +35,25 @@ func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
|||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Enrich client stats with UUID/SubId from inbound settings
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
clients, _ := s.GetClients(inbound)
|
||||||
|
if len(clients) == 0 || len(inbound.ClientStats) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Build a map email -> client
|
||||||
|
cMap := make(map[string]model.Client, len(clients))
|
||||||
|
for _, c := range clients {
|
||||||
|
cMap[strings.ToLower(c.Email)] = c
|
||||||
|
}
|
||||||
|
for i := range inbound.ClientStats {
|
||||||
|
email := strings.ToLower(inbound.ClientStats[i].Email)
|
||||||
|
if c, ok := cMap[email]; ok {
|
||||||
|
inbound.ClientStats[i].UUID = c.ID
|
||||||
|
inbound.ClientStats[i].SubId = c.SubID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +66,24 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
|
|||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Enrich client stats with UUID/SubId from inbound settings
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
clients, _ := s.GetClients(inbound)
|
||||||
|
if len(clients) == 0 || len(inbound.ClientStats) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cMap := make(map[string]model.Client, len(clients))
|
||||||
|
for _, c := range clients {
|
||||||
|
cMap[strings.ToLower(c.Email)] = c
|
||||||
|
}
|
||||||
|
for i := range inbound.ClientStats {
|
||||||
|
email := strings.ToLower(inbound.ClientStats[i].Email)
|
||||||
|
if c, ok := cMap[email]; ok {
|
||||||
|
inbound.ClientStats[i].UUID = c.ID
|
||||||
|
inbound.ClientStats[i].SubId = c.SubID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ var (
|
|||||||
hashStorage *global.HashStorage
|
hashStorage *global.HashStorage
|
||||||
|
|
||||||
// Performance improvements
|
// Performance improvements
|
||||||
messageWorkerPool chan struct{} // Semaphore for limiting concurrent message processing
|
messageWorkerPool chan struct{} // Semaphore for limiting concurrent message processing
|
||||||
optimizedHTTPClient *http.Client // HTTP client with connection pooling and timeouts
|
optimizedHTTPClient *http.Client // HTTP client with connection pooling and timeouts
|
||||||
|
|
||||||
// Simple cache for frequently accessed data
|
// Simple cache for frequently accessed data
|
||||||
statusCache struct {
|
statusCache struct {
|
||||||
@@ -359,7 +359,7 @@ func (t *Tgbot) OnReceive() {
|
|||||||
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||||
// Use goroutine with worker pool for concurrent command processing
|
// Use goroutine with worker pool for concurrent command processing
|
||||||
go func() {
|
go func() {
|
||||||
messageWorkerPool <- struct{}{} // Acquire worker
|
messageWorkerPool <- struct{}{} // Acquire worker
|
||||||
defer func() { <-messageWorkerPool }() // Release worker
|
defer func() { <-messageWorkerPool }() // Release worker
|
||||||
|
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
@@ -371,7 +371,7 @@ func (t *Tgbot) OnReceive() {
|
|||||||
botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
|
botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
|
||||||
// Use goroutine with worker pool for concurrent callback processing
|
// Use goroutine with worker pool for concurrent callback processing
|
||||||
go func() {
|
go func() {
|
||||||
messageWorkerPool <- struct{}{} // Acquire worker
|
messageWorkerPool <- struct{}{} // Acquire worker
|
||||||
defer func() { <-messageWorkerPool }() // Release worker
|
defer func() { <-messageWorkerPool }() // Release worker
|
||||||
|
|
||||||
delete(userStates, query.Message.GetChat().ID)
|
delete(userStates, query.Message.GetChat().ID)
|
||||||
|
|||||||
@@ -95,10 +95,9 @@ type Server struct {
|
|||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
|
|
||||||
index *controller.IndexController
|
index *controller.IndexController
|
||||||
server *controller.ServerController
|
panel *controller.XUIController
|
||||||
panel *controller.XUIController
|
api *controller.APIController
|
||||||
api *controller.APIController
|
|
||||||
|
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
@@ -264,7 +263,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
g := engine.Group(basePath)
|
g := engine.Group(basePath)
|
||||||
|
|
||||||
s.index = controller.NewIndexController(g)
|
s.index = controller.NewIndexController(g)
|
||||||
s.server = controller.NewServerController(g)
|
|
||||||
s.panel = controller.NewXUIController(g)
|
s.panel = controller.NewXUIController(g)
|
||||||
s.api = controller.NewAPIController(g)
|
s.api = controller.NewAPIController(g)
|
||||||
|
|
||||||
|
|||||||
13
x-ui.rc
Normal file
13
x-ui.rc
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
|
command="/usr/local/x-ui/x-ui"
|
||||||
|
command_background=true
|
||||||
|
pidfile="/run/x-ui.pid"
|
||||||
|
description="x-ui Service"
|
||||||
|
procname="x-ui"
|
||||||
|
depend() {
|
||||||
|
need net
|
||||||
|
}
|
||||||
|
start_pre(){
|
||||||
|
cd /usr/local/x-ui
|
||||||
|
}
|
||||||
251
x-ui.sh
251
x-ui.sh
@@ -153,11 +153,19 @@ uninstall() {
|
|||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
systemctl stop x-ui
|
|
||||||
systemctl disable x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
rm /etc/systemd/system/x-ui.service -f
|
rc-service x-ui stop
|
||||||
systemctl daemon-reload
|
rc-update del x-ui
|
||||||
systemctl reset-failed
|
rm /etc/init.d/x-ui -f
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
systemctl disable x-ui
|
||||||
|
rm /etc/systemd/system/x-ui.service -f
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl reset-failed
|
||||||
|
fi
|
||||||
|
|
||||||
rm /etc/x-ui/ -rf
|
rm /etc/x-ui/ -rf
|
||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
|
|
||||||
@@ -286,7 +294,11 @@ start() {
|
|||||||
echo ""
|
echo ""
|
||||||
LOGI "Panel is running, No need to start again, If you need to restart, please select restart"
|
LOGI "Panel is running, No need to start again, If you need to restart, please select restart"
|
||||||
else
|
else
|
||||||
systemctl start x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui start
|
||||||
|
else
|
||||||
|
systemctl start x-ui
|
||||||
|
fi
|
||||||
sleep 2
|
sleep 2
|
||||||
check_status
|
check_status
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
@@ -307,7 +319,11 @@ stop() {
|
|||||||
echo ""
|
echo ""
|
||||||
LOGI "Panel stopped, No need to stop again!"
|
LOGI "Panel stopped, No need to stop again!"
|
||||||
else
|
else
|
||||||
systemctl stop x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui stop
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
fi
|
||||||
sleep 2
|
sleep 2
|
||||||
check_status
|
check_status
|
||||||
if [[ $? == 1 ]]; then
|
if [[ $? == 1 ]]; then
|
||||||
@@ -323,7 +339,11 @@ stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
systemctl restart x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui restart
|
||||||
|
else
|
||||||
|
systemctl restart x-ui
|
||||||
|
fi
|
||||||
sleep 2
|
sleep 2
|
||||||
check_status
|
check_status
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
@@ -337,14 +357,22 @@ restart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
systemctl status x-ui -l
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui status
|
||||||
|
else
|
||||||
|
systemctl status x-ui -l
|
||||||
|
fi
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
systemctl enable x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-update add x-ui
|
||||||
|
else
|
||||||
|
systemctl enable x-ui
|
||||||
|
fi
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "x-ui Set to boot automatically on startup successfully"
|
LOGI "x-ui Set to boot automatically on startup successfully"
|
||||||
else
|
else
|
||||||
@@ -357,7 +385,11 @@ enable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
systemctl disable x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-update del x-ui
|
||||||
|
else
|
||||||
|
systemctl disable x-ui
|
||||||
|
fi
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "x-ui Autostart Cancelled successfully"
|
LOGI "x-ui Autostart Cancelled successfully"
|
||||||
else
|
else
|
||||||
@@ -370,32 +402,54 @@ disable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show_log() {
|
show_log() {
|
||||||
echo -e "${green}\t1.${plain} Debug Log"
|
if [[ $release == "alpine" ]]; then
|
||||||
echo -e "${green}\t2.${plain} Clear All logs"
|
echo -e "${green}\t1.${plain} Debug Log"
|
||||||
echo -e "${green}\t0.${plain} Back to Main Menu"
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||||
read -rp "Choose an option: " choice
|
read -rp "Choose an option: " choice
|
||||||
|
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
0)
|
0)
|
||||||
show_menu
|
show_menu
|
||||||
;;
|
;;
|
||||||
1)
|
1)
|
||||||
journalctl -u x-ui -e --no-pager -f -p debug
|
grep -F 'x-ui[' /var/log/messages
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
2)
|
*)
|
||||||
sudo journalctl --rotate
|
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||||||
sudo journalctl --vacuum-time=1s
|
show_log
|
||||||
echo "All Logs cleared."
|
;;
|
||||||
restart
|
esac
|
||||||
;;
|
else
|
||||||
*)
|
echo -e "${green}\t1.${plain} Debug Log"
|
||||||
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
echo -e "${green}\t2.${plain} Clear All logs"
|
||||||
show_log
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||||
;;
|
read -rp "Choose an option: " choice
|
||||||
esac
|
|
||||||
|
case "$choice" in
|
||||||
|
0)
|
||||||
|
show_menu
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
journalctl -u x-ui -e --no-pager -f -p debug
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
|
before_show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
sudo journalctl --rotate
|
||||||
|
sudo journalctl --vacuum-time=1s
|
||||||
|
echo "All Logs cleared."
|
||||||
|
restart
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||||||
|
show_log
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
bbr_menu() {
|
bbr_menu() {
|
||||||
@@ -464,6 +518,9 @@ enable_bbr() {
|
|||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Sy --noconfirm ca-certificates
|
pacman -Sy --noconfirm ca-certificates
|
||||||
;;
|
;;
|
||||||
|
alpine)
|
||||||
|
apk add ca-certificates
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -500,23 +557,42 @@ update_shell() {
|
|||||||
|
|
||||||
# 0: running, 1: not running, 2: not installed
|
# 0: running, 1: not running, 2: not installed
|
||||||
check_status() {
|
check_status() {
|
||||||
if [[ ! -f /etc/systemd/system/x-ui.service ]]; then
|
if [[ $release == "alpine" ]]; then
|
||||||
return 2
|
if [[ ! -f /etc/init.d/x-ui ]]; then
|
||||||
fi
|
return 2
|
||||||
temp=$(systemctl status x-ui | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
|
fi
|
||||||
if [[ "${temp}" == "running" ]]; then
|
if [[ $(rc-service x-ui status | grep -F 'status: started' -c) == 1 ]]; then
|
||||||
return 0
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
return 1
|
if [[ ! -f /etc/systemd/system/x-ui.service ]]; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
temp=$(systemctl status x-ui | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
|
||||||
|
if [[ "${temp}" == "running" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
check_enabled() {
|
check_enabled() {
|
||||||
temp=$(systemctl is-enabled x-ui)
|
if [[ $release == "alpine" ]]; then
|
||||||
if [[ "${temp}" == "enabled" ]]; then
|
if [[ $(rc-update show | grep -F 'x-ui' | grep default -c) == 1 ]]; then
|
||||||
return 0
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
return 1
|
temp=$(systemctl is-enabled x-ui)
|
||||||
|
if [[ "${temp}" == "enabled" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,7 +874,11 @@ update_geo() {
|
|||||||
show_menu
|
show_menu
|
||||||
;;
|
;;
|
||||||
1)
|
1)
|
||||||
systemctl stop x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui stop
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
fi
|
||||||
rm -f geoip.dat geosite.dat
|
rm -f geoip.dat geosite.dat
|
||||||
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
@@ -806,7 +886,11 @@ update_geo() {
|
|||||||
restart
|
restart
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
systemctl stop x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui stop
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
fi
|
||||||
rm -f geoip_IR.dat geosite_IR.dat
|
rm -f geoip_IR.dat geosite_IR.dat
|
||||||
wget -O geoip_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
wget -O geoip_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||||
wget -O geosite_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
wget -O geosite_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
@@ -814,7 +898,11 @@ update_geo() {
|
|||||||
restart
|
restart
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
systemctl stop x-ui
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service x-ui stop
|
||||||
|
else
|
||||||
|
systemctl stop x-ui
|
||||||
|
fi
|
||||||
rm -f geoip_RU.dat geosite_RU.dat
|
rm -f geoip_RU.dat geosite_RU.dat
|
||||||
wget -O geoip_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -O geoip_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget -O geosite_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget -O geosite_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
@@ -985,6 +1073,9 @@ ssl_cert_issue() {
|
|||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Sy --noconfirm socat
|
pacman -Sy --noconfirm socat
|
||||||
;;
|
;;
|
||||||
|
alpine)
|
||||||
|
apk add socat
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -1335,7 +1426,11 @@ iplimit_main() {
|
|||||||
read -rp "Please enter new Ban Duration in Minutes [default 30]: " NUM
|
read -rp "Please enter new Ban Duration in Minutes [default 30]: " NUM
|
||||||
if [[ $NUM =~ ^[0-9]+$ ]]; then
|
if [[ $NUM =~ ^[0-9]+$ ]]; then
|
||||||
create_iplimit_jails ${NUM}
|
create_iplimit_jails ${NUM}
|
||||||
systemctl restart fail2ban
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service fail2ban restart
|
||||||
|
else
|
||||||
|
systemctl restart fail2ban
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
|
echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
|
||||||
fi
|
fi
|
||||||
@@ -1388,7 +1483,11 @@ iplimit_main() {
|
|||||||
iplimit_main
|
iplimit_main
|
||||||
;;
|
;;
|
||||||
9)
|
9)
|
||||||
systemctl restart fail2ban
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service fail2ban restart
|
||||||
|
else
|
||||||
|
systemctl restart fail2ban
|
||||||
|
fi
|
||||||
iplimit_main
|
iplimit_main
|
||||||
;;
|
;;
|
||||||
10)
|
10)
|
||||||
@@ -1436,6 +1535,9 @@ install_iplimit() {
|
|||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Syu --noconfirm fail2ban
|
pacman -Syu --noconfirm fail2ban
|
||||||
;;
|
;;
|
||||||
|
alpine)
|
||||||
|
apk add fail2ban
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -1472,12 +1574,21 @@ install_iplimit() {
|
|||||||
create_iplimit_jails
|
create_iplimit_jails
|
||||||
|
|
||||||
# Launching fail2ban
|
# Launching fail2ban
|
||||||
if ! systemctl is-active --quiet fail2ban; then
|
if [[ $release == "alpine" ]]; then
|
||||||
systemctl start fail2ban
|
if [[ $(rc-service fail2ban status | grep -F 'status: started' -c) == 0 ]]; then
|
||||||
|
rc-service fail2ban start
|
||||||
|
else
|
||||||
|
rc-service fail2ban restart
|
||||||
|
fi
|
||||||
|
rc-update add fail2ban
|
||||||
else
|
else
|
||||||
systemctl restart fail2ban
|
if ! systemctl is-active --quiet fail2ban; then
|
||||||
|
systemctl start fail2ban
|
||||||
|
else
|
||||||
|
systemctl restart fail2ban
|
||||||
|
fi
|
||||||
|
systemctl enable fail2ban
|
||||||
fi
|
fi
|
||||||
systemctl enable fail2ban
|
|
||||||
|
|
||||||
echo -e "${green}IP Limit installed and configured successfully!${plain}\n"
|
echo -e "${green}IP Limit installed and configured successfully!${plain}\n"
|
||||||
before_show_menu
|
before_show_menu
|
||||||
@@ -1493,13 +1604,21 @@ remove_iplimit() {
|
|||||||
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
|
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
|
||||||
rm -f /etc/fail2ban/action.d/3x-ipl.conf
|
rm -f /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
|
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
systemctl restart fail2ban
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service fail2ban restart
|
||||||
|
else
|
||||||
|
systemctl restart fail2ban
|
||||||
|
fi
|
||||||
echo -e "${green}IP Limit removed successfully!${plain}\n"
|
echo -e "${green}IP Limit removed successfully!${plain}\n"
|
||||||
before_show_menu
|
before_show_menu
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
rm -rf /etc/fail2ban
|
rm -rf /etc/fail2ban
|
||||||
systemctl stop fail2ban
|
if [[ $release == "alpine" ]]; then
|
||||||
|
rc-service fail2ban stop
|
||||||
|
else
|
||||||
|
systemctl stop fail2ban
|
||||||
|
fi
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
apt-get remove -y fail2ban
|
apt-get remove -y fail2ban
|
||||||
@@ -1517,6 +1636,9 @@ remove_iplimit() {
|
|||||||
arch | manjaro | parch)
|
arch | manjaro | parch)
|
||||||
pacman -Rns --noconfirm fail2ban
|
pacman -Rns --noconfirm fail2ban
|
||||||
;;
|
;;
|
||||||
|
alpine)
|
||||||
|
apk del fail2ban
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
|
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -1540,9 +1662,16 @@ show_banlog() {
|
|||||||
|
|
||||||
echo -e "${green}Checking ban logs...${plain}\n"
|
echo -e "${green}Checking ban logs...${plain}\n"
|
||||||
|
|
||||||
if ! systemctl is-active --quiet fail2ban; then
|
if [[ $release == "alpine" ]]; then
|
||||||
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
if [[ $(rc-service fail2ban status | grep -F 'status: started' -c) == 0 ]]; then
|
||||||
return 1
|
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! systemctl is-active --quiet fail2ban; then
|
||||||
|
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$system_log" ]]; then
|
if [[ -f "$system_log" ]]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user