Update Tg bot related function
40
README.md
@@ -11,8 +11,10 @@
|
|||||||
- 流量统计,限制流量,限制到期时间
|
- 流量统计,限制流量,限制到期时间
|
||||||
- 可自定义 xray 配置模板
|
- 可自定义 xray 配置模板
|
||||||
- 支持 https 访问面板(自备域名 + ssl 证书)
|
- 支持 https 访问面板(自备域名 + ssl 证书)
|
||||||
|
- 支持一键SSL证书申请且自动续签
|
||||||
- 更多高级配置项,详见面板
|
- 更多高级配置项,详见面板
|
||||||
|
|
||||||
|
|
||||||
# 安装&升级
|
# 安装&升级
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/vaxilu/x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/vaxilu/x-ui/master/install.sh)
|
||||||
@@ -58,6 +60,44 @@ docker run -itd --network=host \
|
|||||||
```shell
|
```shell
|
||||||
docker build -t x-ui .
|
docker build -t x-ui .
|
||||||
```
|
```
|
||||||
|
## SSL证书申请
|
||||||
|
>此功能与教程由[FranzKafkaYu](https://github.com/FranzKafkaYu)提供
|
||||||
|
|
||||||
|
脚本内置SSL证书申请功能,使用该脚本申请证书,需满足以下条件:
|
||||||
|
- 知晓Cloudflare 注册邮箱
|
||||||
|
- 知晓Cloudflare Global API Key
|
||||||
|
- 域名已通过cloudflare进行解析到当前服务器
|
||||||
|
|
||||||
|
获取Cloudflare Global API Key的方法:
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
使用时只需输入`域名`, `邮箱`, `API KEY`即可,示意图如下:
|
||||||
|

|
||||||
|
|
||||||
|
注意事项:
|
||||||
|
- 该脚本使用DNS API进行证书申请
|
||||||
|
- 默认使用Let'sEncrypt作为CA方
|
||||||
|
- 证书安装目录为/root/cert目录
|
||||||
|
- 本脚本申请证书均为泛域名证书
|
||||||
|
|
||||||
|
## Tg机器人使用
|
||||||
|
>此功能与教程由[FranzKafkaYu](https://github.com/FranzKafkaYu)提供
|
||||||
|
|
||||||
|
X-UI支持通过Tg机器人实现每日流量通知,面板登录提醒等功能,使用Tg机器人,需要自行申请
|
||||||
|
具体申请教程可以参考[博客链接](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
|
使用说明:在面板后台或通过脚本设置机器人相关参数,具体包括
|
||||||
|
- Tg机器人Token
|
||||||
|
- Tg机器人ChatId
|
||||||
|
- Tg机器人周期运行时间,采用crontab语法
|
||||||
|
|
||||||
|
参考示例:
|
||||||
|
每小时定时通知
|
||||||
|

|
||||||
|
每分钟的第30s通知
|
||||||
|

|
||||||
|
效果示意图:
|
||||||
|

|
||||||
|
|
||||||
## 建议系统
|
## 建议系统
|
||||||
- CentOS 7+
|
- CentOS 7+
|
||||||
|
|||||||
1
go.mod
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/gin-contrib/sessions v0.0.3
|
github.com/gin-contrib/sessions v0.0.3
|
||||||
github.com/gin-gonic/gin v1.7.1
|
github.com/gin-gonic/gin v1.7.1
|
||||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2
|
github.com/nicksnyder/go-i18n/v2 v2.1.2
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
|||||||
2
go.sum
@@ -70,6 +70,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
|
|||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
|||||||
@@ -83,15 +83,15 @@ install_base() {
|
|||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
echo -e "${yellow}出于安全考虑,安装完成后需要强制修改端口与账户密码${plain}"
|
echo -e "${yellow}出于安全考虑,安装/更新完成后需要强制修改端口与账户密码${plain}"
|
||||||
|
read -p "确认是否继续?[y/n]": config_confirm
|
||||||
|
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
||||||
read -p "请设置您的账户名:" config_account
|
read -p "请设置您的账户名:" config_account
|
||||||
echo -e "${yellow}您的账户名将设定为:${config_account}${plain}"
|
echo -e "${yellow}您的账户名将设定为:${config_account}${plain}"
|
||||||
read -p "请设置您的账户密码:" config_password
|
read -p "请设置您的账户密码:" config_password
|
||||||
echo -e "${yellow}您的账户密码将设定为:${config_password}${plain}"
|
echo -e "${yellow}您的账户密码将设定为:${config_password}${plain}"
|
||||||
read -p "请设置面板访问端口:" config_port
|
read -p "请设置面板访问端口:" config_port
|
||||||
echo -e "${yellow}您的面板访问端口将设定为:${config_port}${plain}"
|
echo -e "${yellow}您的面板访问端口将设定为:${config_port}${plain}"
|
||||||
read -p "确认设定完成?[y/n]": config_confirm
|
|
||||||
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
|
||||||
echo -e "${yellow}确认设定,设定中${plain}"
|
echo -e "${yellow}确认设定,设定中${plain}"
|
||||||
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
||||||
echo -e "${yellow}账户密码设定完成${plain}"
|
echo -e "${yellow}账户密码设定完成${plain}"
|
||||||
|
|||||||
107
main.go
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/op/go-logging"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -16,6 +15,8 @@ import (
|
|||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runWebServer() {
|
func runWebServer() {
|
||||||
@@ -50,6 +51,7 @@ func runWebServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
//信号量捕获处理
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL)
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
@@ -90,6 +92,90 @@ func resetSetting() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showSetting(show bool) {
|
||||||
|
if show {
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
port, err := settingService.GetPort()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get current port fialed,error info:", err)
|
||||||
|
}
|
||||||
|
userService := service.UserService{}
|
||||||
|
userModel, err := userService.GetFirstUser()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get current user info failed,error info:", err)
|
||||||
|
}
|
||||||
|
username := userModel.Username
|
||||||
|
userpasswd := userModel.Password
|
||||||
|
if (username == "") || (userpasswd == "") {
|
||||||
|
fmt.Println("current username or password is empty")
|
||||||
|
}
|
||||||
|
fmt.Println("current pannel settings as follows:")
|
||||||
|
fmt.Println("username:", username)
|
||||||
|
fmt.Println("userpasswd:", userpasswd)
|
||||||
|
fmt.Println("port:", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTgbotEnableSts(status bool) {
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.Infof("SetTgbotenabled[%v] success", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTgbotSetting(tgBotToken string, tgBotChatid int, tgBotRuntime string) {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
|
||||||
|
if tgBotToken != "" {
|
||||||
|
err := settingService.SetTgBotToken(tgBotToken)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.Info("updateTgbotSetting tgBotToken success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tgBotRuntime != "" {
|
||||||
|
err := settingService.SetTgbotRuntime(tgBotRuntime)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tgBotChatid != 0 {
|
||||||
|
err := settingService.SetTgBotChatId(tgBotChatid)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.Info("updateTgbotSetting tgBotChatid success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateSetting(port int, username string, password string) {
|
func updateSetting(port int, username string, password string) {
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -137,11 +223,21 @@ func main() {
|
|||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
var password string
|
var password string
|
||||||
|
var tgbottoken string
|
||||||
|
var tgbotchatid int
|
||||||
|
var enabletgbot bool
|
||||||
|
var tgbotRuntime string
|
||||||
var reset bool
|
var reset bool
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "reset all setting")
|
var show bool
|
||||||
|
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||||
|
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||||
|
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token")
|
||||||
|
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time")
|
||||||
|
settingCmd.IntVar(&tgbotchatid, "tgbotchatid", 0, "set telegrame bot chat id")
|
||||||
|
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
||||||
|
|
||||||
oldUsage := flag.Usage
|
oldUsage := flag.Usage
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
@@ -188,6 +284,13 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
updateSetting(port, username, password)
|
updateSetting(port, username, password)
|
||||||
}
|
}
|
||||||
|
if show {
|
||||||
|
showSetting(show)
|
||||||
|
}
|
||||||
|
updateTgbotEnableSts(enabletgbot)
|
||||||
|
if (tgbottoken != "") || (tgbotchatid != 0) || (tgbotRuntime != "") {
|
||||||
|
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|||||||
BIN
media/2022-04-04_141259.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
media/2022-04-17_110907.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
media/2022-04-17_111321.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
media/2022-04-17_111705.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
media/2022-04-17_111910.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
media/bda84fbc2ede834deaba1c173a932223.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
media/d13ffd6a73f938d1037d0708e31433bf.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
21
util/common/format.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FormatTraffic(trafficBytes int64) (size string) {
|
||||||
|
if trafficBytes < 1024 {
|
||||||
|
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
||||||
|
} else if trafficBytes < (1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024))
|
||||||
|
} else if trafficBytes < (1024 * 1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024))
|
||||||
|
} else if trafficBytes < (1024 * 1024 * 1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024))
|
||||||
|
} else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024))
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024))
|
||||||
|
}
|
||||||
|
}
|
||||||
9
util/common/stringUtil.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
func IsSubString(target string, str_array []string) bool {
|
||||||
|
sort.Strings(str_array)
|
||||||
|
index := sort.SearchStrings(str_array, target)
|
||||||
|
return index < len(str_array) && str_array[index] == target
|
||||||
|
}
|
||||||
@@ -163,7 +163,9 @@ class AllSetting {
|
|||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
this.webBasePath = "/";
|
this.webBasePath = "/";
|
||||||
|
this.tgBotToken = "";
|
||||||
|
this.tgBotChatId = 0;
|
||||||
|
this.tgRunTime = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
|
|
||||||
this.timeLocation = "Asia/Shanghai";
|
this.timeLocation = "Asia/Shanghai";
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/web/job"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginForm struct {
|
type LoginForm struct {
|
||||||
@@ -59,6 +62,10 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
pureJsonMsg(c, false, "用户名或密码错误")
|
pureJsonMsg(c, false, "用户名或密码错误")
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c))
|
||||||
|
job.NewStatsNotifyJob().UserLoginNotify(form.Username, getRemoteIp(c), timeStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ type AllSetting struct {
|
|||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||||
|
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||||
|
TgBotChatId int `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
|
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
|
|||||||
@@ -71,7 +71,14 @@
|
|||||||
<setting-list-item type="textarea" title="xray 配置模版" desc="以该模版为基础生成最终的 xray 配置文件,重启面板生效" v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<setting-list-item type="textarea" title="xray 配置模版" desc="以该模版为基础生成最终的 xray 配置文件,重启面板生效" v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="4" tab="其他设置">
|
<a-tab-pane key="4" tab="TG提醒相关设置">
|
||||||
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
|
<setting-list-item type="text" title="电报机器人TOKEN" desc="重启面板生效" v-model="allSetting.tgBotToken"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title="电报机器人ChatId" desc="重启面板生效" v-model.number="allSetting.tgBotChatId"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title="电报机器人通知时间" desc="采用Crontab定时格式" v-model="allSetting.tgRunTime"></setting-list-item>
|
||||||
|
</a-list>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="5" tab="其他设置">
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
<setting-list-item type="text" title="时区" desc="定时任务按照该时区的时间运行,重启面板生效" v-model="allSetting.timeLocation"></setting-list-item>
|
<setting-list-item type="text" title="时区" desc="定时任务按照该时区的时间运行,重启面板生效" v-model="allSetting.timeLocation"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
|
|||||||
121
web/job/stats_notify_job.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatsNotifyJob struct {
|
||||||
|
enable bool
|
||||||
|
xrayService service.XrayService
|
||||||
|
inboundService service.InboundService
|
||||||
|
settingService service.SettingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatsNotifyJob() *StatsNotifyJob {
|
||||||
|
return new(StatsNotifyJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *StatsNotifyJob) SendMsgToTgbot(msg string) {
|
||||||
|
//Telegram bot basic info
|
||||||
|
tgBottoken, err := j.settingService.GetTgBotToken()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sendMsgToTgbot failed,GetTgBotToken fail:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tgBotid, err := j.settingService.GetTgBotChatId()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sendMsgToTgbot failed,GetTgBotChatId fail:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bot, err := tgbotapi.NewBotAPI(tgBottoken)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get tgbot error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bot.Debug = true
|
||||||
|
fmt.Printf("Authorized on account %s", bot.Self.UserName)
|
||||||
|
info := tgbotapi.NewMessage(int64(tgBotid), msg)
|
||||||
|
//msg.ReplyToMessageID = int(tgBotid)
|
||||||
|
bot.Send(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Here run is a interface method of Job interface
|
||||||
|
func (j *StatsNotifyJob) Run() {
|
||||||
|
if !j.xrayService.IsXrayRunning() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var info string
|
||||||
|
//get hostname
|
||||||
|
name, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get hostname error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info = fmt.Sprintf("主机名称:%s\r\n", name)
|
||||||
|
//get ip address
|
||||||
|
var ip string
|
||||||
|
netInterfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("net.Interfaces failed, err:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(netInterfaces); i++ {
|
||||||
|
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||||
|
addrs, _ := netInterfaces[i].Addrs()
|
||||||
|
|
||||||
|
for _, address := range addrs {
|
||||||
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||||
|
if ipnet.IP.To4() != nil {
|
||||||
|
ip = ipnet.IP.String()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
ip = ipnet.IP.String()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info += fmt.Sprintf("IP地址:%s\r\n \r\n", ip)
|
||||||
|
|
||||||
|
//get traffic
|
||||||
|
inbouds, err := j.inboundService.GetAllInbounds()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("StatsNotifyJob run failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//NOTE:If there no any sessions here,need to notify here
|
||||||
|
//TODO:分节点推送,自动转化格式
|
||||||
|
for _, inbound := range inbouds {
|
||||||
|
info += fmt.Sprintf("节点名称:%s\r\n端口:%d\r\n上行流量↑:%s\r\n下行流量↓:%s\r\n总流量:%s\r\n \r\n", inbound.Remark, inbound.Port, common.FormatTraffic(inbound.Up), common.FormatTraffic(inbound.Down), common.FormatTraffic((inbound.Up + inbound.Down)))
|
||||||
|
}
|
||||||
|
j.SendMsgToTgbot(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *StatsNotifyJob) UserLoginNotify(username string, ip string, time string) {
|
||||||
|
if username == "" || ip == "" || time == "" {
|
||||||
|
logger.Warning("UserLoginNotify failed,invalid info")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var msg string
|
||||||
|
//get hostname
|
||||||
|
name, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get hostname error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = fmt.Sprintf("面板登录提醒\r\n主机名称:%s\r\n", name)
|
||||||
|
msg += fmt.Sprintf("时间:%s\r\n", time)
|
||||||
|
msg += fmt.Sprintf("用户:%s\r\n", username)
|
||||||
|
msg += fmt.Sprintf("IP:%s\r\n", ip)
|
||||||
|
j.SendMsgToTgbot(msg)
|
||||||
|
}
|
||||||
@@ -2,12 +2,13 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"gorm.io/gorm"
|
|
||||||
"time"
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InboundService struct {
|
type InboundService struct {
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ var defaultValueMap = map[string]string{
|
|||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
"timeLocation": "Asia/Shanghai",
|
"timeLocation": "Asia/Shanghai",
|
||||||
|
"tgBotEnable": "false",
|
||||||
|
"tgBotToken": "",
|
||||||
|
"tgBotChatId": "0",
|
||||||
|
"tgRunTime": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -156,6 +160,18 @@ func (s *SettingService) setString(key string, value string) error {
|
|||||||
return s.saveSetting(key, value)
|
return s.saveSetting(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) getBool(key string) (bool, error) {
|
||||||
|
str, err := s.getString(key)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return strconv.ParseBool(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) setBool(key string, value bool) error {
|
||||||
|
return s.setString(key, strconv.FormatBool(value))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) getInt(key string) (int, error) {
|
func (s *SettingService) getInt(key string) (int, error) {
|
||||||
str, err := s.getString(key)
|
str, err := s.getString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -176,6 +192,38 @@ func (s *SettingService) GetListen() (string, error) {
|
|||||||
return s.getString("webListen")
|
return s.getString("webListen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgBotToken() (string, error) {
|
||||||
|
return s.getString("tgBotToken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgBotToken(token string) error {
|
||||||
|
return s.setString("tgBotToken", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgBotChatId() (int, error) {
|
||||||
|
return s.getInt("tgBotChatId")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgBotChatId(chatId int) error {
|
||||||
|
return s.setInt("tgBotChatId", chatId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgbotenabled(value bool) error {
|
||||||
|
return s.setBool("tgBotEnable", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgbotenabled() (bool, error) {
|
||||||
|
return s.getBool("tgBotEnable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetTgbotRuntime(time string) error {
|
||||||
|
return s.setString("tgRunTime", time)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgbotRuntime() (string, error) {
|
||||||
|
return s.getString("tgRunTime")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetPort() (int, error) {
|
func (s *SettingService) GetPort() (int, error) {
|
||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"gorm.io/gorm"
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"go.uber.org/atomic"
|
|
||||||
"sync"
|
"sync"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var p *xray.Process
|
var p *xray.Process
|
||||||
|
|||||||
35
web/web.go
@@ -4,13 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
"github.com/gin-contrib/sessions"
|
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -27,6 +20,14 @@ import (
|
|||||||
"x-ui/web/job"
|
"x-ui/web/job"
|
||||||
"x-ui/web/network"
|
"x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed assets/*
|
//go:embed assets/*
|
||||||
@@ -294,9 +295,28 @@ func (s *Server) startTask() {
|
|||||||
|
|
||||||
// 每 30 秒检查一次 inbound 流量超出和到期的情况
|
// 每 30 秒检查一次 inbound 流量超出和到期的情况
|
||||||
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
||||||
|
// 每一天提示一次流量情况,上海时间8点30
|
||||||
|
var entry cron.EntryID
|
||||||
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
|
if (err == nil) && (isTgbotenabled) {
|
||||||
|
runtime, err := s.settingService.GetTgbotRuntime()
|
||||||
|
if err != nil || runtime == "" {
|
||||||
|
logger.Errorf("Add NewStatsNotifyJob error[%s],Runtime[%s] invalid,wil run default", err, runtime)
|
||||||
|
runtime = "@daily"
|
||||||
|
}
|
||||||
|
logger.Infof("Tg notify enabled,run at %s", runtime)
|
||||||
|
entry, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob())
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Add NewStatsNotifyJob error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.cron.Remove(entry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() (err error) {
|
func (s *Server) Start() (err error) {
|
||||||
|
//这是一个匿名函数,没没有函数名
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Stop()
|
s.Stop()
|
||||||
@@ -348,6 +368,7 @@ func (s *Server) Start() (err error) {
|
|||||||
listener = network.NewAutoHttpsListener(listener)
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
listener = tls.NewListener(listener, c)
|
listener = tls.NewListener(listener, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
if certFile != "" || keyFile != "" {
|
if certFile != "" || keyFile != "" {
|
||||||
logger.Info("web server run https on", listener.Addr())
|
logger.Info("web server run https on", listener.Addr())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
131
x-ui.sh
@@ -18,7 +18,7 @@ function LOGI() {
|
|||||||
echo -e "${green}[INF] $* ${plain}"
|
echo -e "${green}[INF] $* ${plain}"
|
||||||
}
|
}
|
||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && LOGE "错误: 必须使用root用户运行此脚本!\n" && exit 1
|
[[ $EUID -ne 0 ]] && LOGE "错误: 必须使用root用户运行此脚本!\n" && exit 1
|
||||||
|
|
||||||
# check os
|
# check os
|
||||||
if [[ -f /etc/redhat-release ]]; then
|
if [[ -f /etc/redhat-release ]]; then
|
||||||
@@ -121,7 +121,7 @@ update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uninstall() {
|
uninstall() {
|
||||||
confirm "确定要卸载面板吗,xray 也会卸载?" "n"
|
confirm "确定要卸载面板吗,xray 也会卸载?" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
show_menu
|
show_menu
|
||||||
@@ -171,6 +171,15 @@ reset_config() {
|
|||||||
confirm_restart
|
confirm_restart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_config() {
|
||||||
|
info=$(/usr/local/x-ui/x-ui setting -show true)
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
LOGE "get current settings error,please check logs"
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
LOGI "${info}"
|
||||||
|
}
|
||||||
|
|
||||||
set_port() {
|
set_port() {
|
||||||
echo && echo -n -e "输入端口号[1-65535]: " && read port
|
echo && echo -n -e "输入端口号[1-65535]: " && read port
|
||||||
if [[ -z "${port}" ]]; then
|
if [[ -z "${port}" ]]; then
|
||||||
@@ -399,6 +408,70 @@ show_xray_status() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_telegram_bot() {
|
||||||
|
echo -E ""
|
||||||
|
LOGI "设置Telegram Bot需要知晓Bot的Token与ChatId"
|
||||||
|
LOGI "使用方法请参考博客https://coderfan.net"
|
||||||
|
confirm "我已确认以上内容[y/n]" "y"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
read -p "please input your tg bot token here:" TG_BOT_TOKEN
|
||||||
|
LOGI "你设置的电报机器人Token:$TG_BOT_TOKEN"
|
||||||
|
read -p "please input your tg chat id here:" TG_BOT_CHATID
|
||||||
|
LOGI "你设置的电报机器人ChatId:$TG_BOT_CHATID"
|
||||||
|
read -p "please input your tg bot runtime here:" TG_BOT_RUNTIME
|
||||||
|
LOGI "你设置的电报机器人运行周期:$TG_BOT_RUNTIME"
|
||||||
|
info=$(/usr/local/x-ui/x-ui setting -tgbottoken ${TG_BOT_TOKEN} -tgbotchatid ${TG_BOT_CHATID} -tgbotRuntime "$TG_BOT_RUNTIME")
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
LOGE "$info"
|
||||||
|
LOGE "设置TelegramBot失败"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
LOGI "设置TelegramBot成功"
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_telegram_bot() {
|
||||||
|
echo -E ""
|
||||||
|
LOGI "该功能会开启Telegram Bot通知"
|
||||||
|
LOGI "通知内容包括:"
|
||||||
|
LOGI "1.流量使用情况"
|
||||||
|
LOGI "2.节点到期提醒,待实现(规划中)"
|
||||||
|
LOGI "3.面板登录提醒,待完善(规划中)"
|
||||||
|
confirm "我已确认以上内容[y/n]" "y"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
info=$(/usr/local/x-ui/x-ui setting -enabletgbot=true)
|
||||||
|
if [ $? == 0 ]; then
|
||||||
|
LOGI "开启成功,重启X-UI生效,重启中...."
|
||||||
|
restart
|
||||||
|
else
|
||||||
|
LOGE "开启失败,即将退出..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_telegram_bot() {
|
||||||
|
confirm "确认是否关闭Tgbot[y/n]" "n"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
info=$(/usr/local/x-ui/x-ui setting -enabletgbot=false)
|
||||||
|
if [ $? == 0 ]; then
|
||||||
|
LOGI "关闭成功,重启X-UI生效,重启中...."
|
||||||
|
restart
|
||||||
|
else
|
||||||
|
LOGE "关闭失败,请检查日志..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
ssl_cert_issue() {
|
ssl_cert_issue() {
|
||||||
echo -E ""
|
echo -E ""
|
||||||
LOGD "******使用说明******"
|
LOGD "******使用说明******"
|
||||||
@@ -504,21 +577,25 @@ show_menu() {
|
|||||||
${green}4.${plain} 重置用户名密码
|
${green}4.${plain} 重置用户名密码
|
||||||
${green}5.${plain} 重置面板设置
|
${green}5.${plain} 重置面板设置
|
||||||
${green}6.${plain} 设置面板端口
|
${green}6.${plain} 设置面板端口
|
||||||
|
${green}7.${plain} 当前面板设置
|
||||||
————————————————
|
————————————————
|
||||||
${green}7.${plain} 启动 x-ui
|
${green}8.${plain} 启动 x-ui
|
||||||
${green}8.${plain} 停止 x-ui
|
${green}9.${plain} 停止 x-ui
|
||||||
${green}9.${plain} 重启 x-ui
|
${green}10.${plain} 重启 x-ui
|
||||||
${green}10.${plain} 查看 x-ui 状态
|
${green}11.${plain} 查看 x-ui 状态
|
||||||
${green}11.${plain} 查看 x-ui 日志
|
${green}12.${plain} 查看 x-ui 日志
|
||||||
————————————————
|
————————————————
|
||||||
${green}12.${plain} 设置 x-ui 开机自启
|
${green}13.${plain} 设置 x-ui 开机自启
|
||||||
${green}13.${plain} 取消 x-ui 开机自启
|
${green}14.${plain} 取消 x-ui 开机自启
|
||||||
————————————————
|
————————————————
|
||||||
${green}14.${plain} 一键安装 bbr (最新内核)
|
${green}15.${plain} 一键安装 bbr (最新内核)
|
||||||
${green}15.${plain} 一键申请SSL证书(acme申请)
|
${green}16.${plain} 一键申请SSL证书(acme申请)
|
||||||
|
${green}17.${plain} 开启Telegram通知(TgBot)
|
||||||
|
${green}18.${plain} 关闭Telegram通知(TgBot)
|
||||||
|
${green}19.${plain} 设置TelegramBot
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
echo && read -p "请输入选择 [0-14]: " num
|
echo && read -p "请输入选择 [0-19]: " num
|
||||||
|
|
||||||
case "${num}" in
|
case "${num}" in
|
||||||
0)
|
0)
|
||||||
@@ -543,34 +620,46 @@ show_menu() {
|
|||||||
check_install && set_port
|
check_install && set_port
|
||||||
;;
|
;;
|
||||||
7)
|
7)
|
||||||
check_install && start
|
check_install && check_config
|
||||||
;;
|
;;
|
||||||
8)
|
8)
|
||||||
check_install && stop
|
check_install && start
|
||||||
;;
|
;;
|
||||||
9)
|
9)
|
||||||
check_install && restart
|
check_install && stop
|
||||||
;;
|
;;
|
||||||
10)
|
10)
|
||||||
check_install && status
|
check_install && restart
|
||||||
;;
|
;;
|
||||||
11)
|
11)
|
||||||
check_install && show_log
|
check_install && status
|
||||||
;;
|
;;
|
||||||
12)
|
12)
|
||||||
check_install && enable
|
check_install && show_log
|
||||||
;;
|
;;
|
||||||
13)
|
13)
|
||||||
check_install && disable
|
check_install && enable
|
||||||
;;
|
;;
|
||||||
14)
|
14)
|
||||||
install_bbr
|
check_install && disable
|
||||||
;;
|
;;
|
||||||
15)
|
15)
|
||||||
|
install_bbr
|
||||||
|
;;
|
||||||
|
16)
|
||||||
ssl_cert_issue
|
ssl_cert_issue
|
||||||
;;
|
;;
|
||||||
|
17)
|
||||||
|
enable_telegram_bot
|
||||||
|
;;
|
||||||
|
18)
|
||||||
|
disable_telegram_bot
|
||||||
|
;;
|
||||||
|
19)
|
||||||
|
set_telegram_bot
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
LOGE "请输入正确的数字 [0-14]"
|
LOGE "请输入正确的数字 [0-19]"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Workiva/go-datastructures/queue"
|
|
||||||
statsservice "github.com/xtls/xray-core/app/stats/command"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -18,6 +15,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/Workiva/go-datastructures/queue"
|
||||||
|
statsservice "github.com/xtls/xray-core/app/stats/command"
|
||||||
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||||
|
|||||||