This commit is contained in:
chermack
2026-07-26 00:09:57 +08:00
commit 059be96536
134 changed files with 19678 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
package trojan
import (
"encoding/base64"
"fmt"
"trojan/core"
"trojan/util"
)
var clientPath = "/root/config.json"
// GenClientJson 生成客户端json
func GenClientJson() {
fmt.Println()
var user core.User
domain, port := GetDomainAndPort()
mysql := core.GetMysql()
userList, err := mysql.GetData()
if err != nil {
fmt.Println(err.Error())
return
}
if len(userList) == 1 {
user = *userList[0]
} else {
UserList()
choice := util.LoopInput("请选择要生成配置文件的用户序号: ", userList, true)
if choice < 0 {
return
}
user = *userList[choice-1]
}
pass, err := base64.StdEncoding.DecodeString(user.Password)
if err != nil {
fmt.Println(util.Red("Base64解码失败: " + err.Error()))
return
}
if !core.WriteClient(port, string(pass), domain, clientPath) {
fmt.Println(util.Red("生成配置文件失败!"))
} else {
fmt.Println("成功生成配置文件: " + util.Green(clientPath))
}
}
+12
View File
@@ -0,0 +1,12 @@
package trojan
var (
// MVersion 程序版本号
MVersion string
// BuildDate 编译时间
BuildDate string
// GoVersion go版本
GoVersion string
// GitVersion git版本
GitVersion string
)
+224
View File
@@ -0,0 +1,224 @@
package trojan
import (
"fmt"
"net"
"runtime"
"strconv"
"strings"
"time"
"trojan/asset"
"trojan/core"
"trojan/util"
)
var (
dockerInstallUrl = "https://docker-install.netlify.app/install.sh"
dbDockerRun = "docker run --name trojan-mariadb --restart=always -p %d:3306 -v /home/mariadb:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=%s -e MYSQL_ROOT_HOST=%% -e MYSQL_DATABASE=trojan -d mariadb:10.2"
)
// InstallMenu 安装目录
func InstallMenu() {
fmt.Println()
menu := []string{"更新trojan", "证书申请", "安装mysql"}
switch util.LoopInput("请选择: ", menu, true) {
case 1:
InstallTrojan("")
case 2:
InstallTls()
case 3:
InstallMysql()
default:
return
}
}
// InstallDocker 安装docker
func InstallDocker() {
if !util.CheckCommandExists("docker") {
util.RunWebShell(dockerInstallUrl)
fmt.Println()
}
}
// InstallTrojan 安装trojan
func InstallTrojan(version string) {
fmt.Println()
data := string(asset.GetAsset("trojan-install.sh"))
checkTrojan := util.ExecCommandWithResult("systemctl list-unit-files|grep trojan.service")
if (checkTrojan == "" && runtime.GOARCH != "amd64") || Type() == "trojan-go" {
data = strings.ReplaceAll(data, "TYPE=0", "TYPE=1")
}
if version != "" {
data = strings.ReplaceAll(data, "INSTALL_VERSION=\"\"", "INSTALL_VERSION=\""+version+"\"")
}
util.ExecCommand(data)
util.OpenPort(443)
util.SystemctlRestart("trojan")
util.SystemctlEnable("trojan")
}
// InstallTls 安装证书
func InstallTls() {
domain := ""
server := "letsencrypt"
fmt.Println()
choice := util.LoopInput("请选择使用证书方式: ", []string{"Let's Encrypt 证书", "ZeroSSL 证书", "BuyPass 证书", "自定义证书路径"}, true)
if choice < 0 {
return
} else if choice == 4 {
crtFile := util.Input("请输入证书的cert文件路径: ", "")
keyFile := util.Input("请输入证书的key文件路径: ", "")
if !util.IsExists(crtFile) || !util.IsExists(keyFile) {
fmt.Println("输入的cert或者key文件不存在!")
} else {
domain = util.Input("请输入此证书对应的域名: ", "")
if domain == "" {
fmt.Println("输入域名为空!")
return
}
core.WriteTls(crtFile, keyFile, domain)
}
} else {
if choice == 2 {
server = "zerossl"
} else if choice == 3 {
server = "buypass"
}
localIP := util.GetLocalIP()
fmt.Printf("本机ip: %s\n", localIP)
for {
domain = util.Input("请输入申请证书的域名: ", "")
ipList, err := net.LookupIP(domain)
fmt.Printf("%s 解析到的ip: %v\n", domain, ipList)
if err != nil {
fmt.Println(err)
fmt.Println("域名有误,请重新输入")
continue
}
checkIp := false
for _, ip := range ipList {
if localIP == ip.String() {
checkIp = true
}
}
if checkIp {
break
} else {
fmt.Println("输入的域名和本机ip不一致, 请重新输入!")
}
}
util.InstallPack("socat")
if !util.IsExists("/root/.acme.sh/acme.sh") {
util.RunWebShell("https://get.acme.sh")
}
util.SystemctlStop("trojan-web")
util.OpenPort(80)
checkResult := util.ExecCommandWithResult("/root/.acme.sh/acme.sh -v|tr -cd '[0-9]'")
acmeVersion, _ := strconv.Atoi(checkResult)
if acmeVersion < 300 {
util.ExecCommand("/root/.acme.sh/acme.sh --upgrade")
}
if server != "letsencrypt" {
var email string
for {
email = util.Input(fmt.Sprintf("请输入申请%s域名所需的邮箱: ", server), "")
if email == "" {
fmt.Println("申请域名的邮箱地址为空!")
return
} else if util.VerifyEmailFormat(email) {
break
} else {
fmt.Println("邮箱格式不正确, 请重新输入!")
}
}
util.ExecCommand(fmt.Sprintf("bash /root/.acme.sh/acme.sh --server %s --register-account -m %s", server, email))
}
issueCommand := fmt.Sprintf("bash /root/.acme.sh/acme.sh --issue -d %s --debug --standalone --keylength ec-256 --force --server %s", domain, server)
if server == "buypass" {
issueCommand = issueCommand + " --days 170"
}
util.ExecCommand(issueCommand)
crtFile := "/root/.acme.sh/" + domain + "_ecc" + "/fullchain.cer"
keyFile := "/root/.acme.sh/" + domain + "_ecc" + "/" + domain + ".key"
core.WriteTls(crtFile, keyFile, domain)
}
Restart()
util.SystemctlRestart("trojan-web")
fmt.Println()
}
// InstallMysql 安装mysql
func InstallMysql() {
var (
mysql core.Mysql
choice int
)
fmt.Println()
if util.IsExists("/.dockerenv") {
choice = 2
} else {
choice = util.LoopInput("请选择: ", []string{"安装docker版mysql(mariadb)", "输入自定义mysql连接"}, true)
}
if choice < 0 {
return
} else if choice == 1 {
mysql = core.Mysql{ServerAddr: "127.0.0.1", ServerPort: util.RandomPort(), Password: util.RandString(8, util.LETTER+util.DIGITS), Username: "root", Database: "trojan"}
InstallDocker()
fmt.Println(fmt.Sprintf(dbDockerRun, mysql.ServerPort, mysql.Password))
if util.CheckCommandExists("setenforce") {
util.ExecCommand("setenforce 0")
}
util.OpenPort(mysql.ServerPort)
util.ExecCommand(fmt.Sprintf(dbDockerRun, mysql.ServerPort, mysql.Password))
db := mysql.GetDB()
for {
fmt.Printf("%s mariadb启动中,请稍等...\n", time.Now().Format("2006-01-02 15:04:05"))
err := db.Ping()
if err == nil {
db.Close()
break
} else {
time.Sleep(2 * time.Second)
}
}
fmt.Println("mariadb启动成功!")
} else if choice == 2 {
mysql = core.Mysql{}
for {
for {
mysqlUrl := util.Input("请输入mysql连接地址(格式: host:port), 默认连接地址为127.0.0.1:3306, 使用直接回车, 否则输入自定义连接地址: ",
"127.0.0.1:3306")
urlInfo := strings.Split(mysqlUrl, ":")
if len(urlInfo) != 2 {
fmt.Printf("输入的%s不符合匹配格式(host:port)\n", mysqlUrl)
continue
}
port, err := strconv.Atoi(urlInfo[1])
if err != nil {
fmt.Printf("%s不是数字\n", urlInfo[1])
continue
}
mysql.ServerAddr, mysql.ServerPort = urlInfo[0], port
break
}
mysql.Username = util.Input("请输入mysql的用户名(回车使用root): ", "root")
mysql.Password = util.Input(fmt.Sprintf("请输入mysql %s用户的密码: ", mysql.Username), "")
db := mysql.GetDB()
if db != nil && db.Ping() == nil {
mysql.Database = util.Input("请输入使用的数据库名(不存在可自动创建, 回车使用trojan): ", "trojan")
db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", mysql.Database))
break
} else {
fmt.Println("连接mysql失败, 请重新输入")
}
}
}
mysql.CreateTable()
core.WriteMysql(&mysql)
if userList, _ := mysql.GetData(); len(userList) == 0 {
AddUser()
}
Restart()
fmt.Println()
}
+150
View File
@@ -0,0 +1,150 @@
package trojan
import (
"errors"
"fmt"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"trojan/core"
"trojan/util"
)
// ControlMenu Trojan控制菜单
func ControlMenu() {
fmt.Println()
tType := Type()
if tType == "trojan" {
tType = "trojan-go"
} else {
tType = "trojan"
}
menu := []string{"启动trojan", "停止trojan", "重启trojan", "查看trojan状态", "查看trojan日志", "修改trojan端口"}
menu = append(menu, "切换为"+tType)
switch util.LoopInput("请选择: ", menu, true) {
case 1:
Start()
case 2:
Stop()
case 3:
Restart()
case 4:
Status(true)
case 5:
go util.Log("trojan", 300)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
//阻塞
<-c
case 6:
ChangePort()
case 7:
if err := SwitchType(tType); err != nil {
fmt.Println(err)
}
}
}
// Restart 重启trojan
func Restart() {
util.OpenPort(core.GetConfig().LocalPort)
util.SystemctlRestart("trojan")
}
// Start 启动trojan
func Start() {
util.OpenPort(core.GetConfig().LocalPort)
util.SystemctlStart("trojan")
}
// Stop 停止trojan
func Stop() {
util.SystemctlStop("trojan")
}
// Status 获取trojan状态
func Status(isPrint bool) string {
result := util.SystemctlStatus("trojan")
if isPrint {
fmt.Println(result)
}
return result
}
// UpTime Trojan运行时间
func UpTime() string {
result := strings.TrimSpace(util.ExecCommandWithResult("ps -Ao etime,args|grep -v grep|grep /usr/local/etc/trojan/config.json"))
resultSlice := strings.Split(result, " ")
if len(resultSlice) > 0 {
return resultSlice[0]
}
return ""
}
// ChangePort 修改trojan端口
func ChangePort() {
config := core.GetConfig()
oldPort := config.LocalPort
randomPort := util.RandomPort()
fmt.Println("当前trojan端口: " + util.Green(strconv.Itoa(oldPort)))
newPortStr := util.Input(fmt.Sprintf("请输入新的trojan端口(若要使用随机端口%s直接回车即可): ", util.Blue(strconv.Itoa(randomPort))), strconv.Itoa(randomPort))
newPort, err := strconv.Atoi(newPortStr)
if err != nil {
fmt.Println("修改端口失败: " + err.Error())
return
}
if core.WritePort(newPort) {
util.OpenPort(newPort)
fmt.Println(util.Green("端口修改成功!"))
Restart()
} else {
fmt.Println(util.Red("端口修改成功!"))
}
}
// Version Trojan版本
func Version() string {
flag := "-v"
if Type() == "trojan-go" {
flag = "-version"
}
result := strings.TrimSpace(util.ExecCommandWithResult("/usr/bin/trojan/trojan " + flag))
if len(result) == 0 {
return ""
}
firstLine := strings.Split(result, "\n")[0]
tempSlice := strings.Split(firstLine, " ")
return tempSlice[len(tempSlice)-1]
}
// SwitchType 切换Trojan类型
func SwitchType(tType string) error {
ARCH := runtime.GOARCH
if ARCH != "amd64" && ARCH != "arm64" {
return errors.New("not support " + ARCH + " machine")
}
if tType == "trojan" && ARCH != "amd64" {
return errors.New("trojan not support " + ARCH + " machine")
}
if err := core.SetValue("trojanType", tType); err != nil {
return err
}
InstallTrojan("")
return nil
}
// Type Trojan类型
func Type() string {
tType, _ := core.GetValue("trojanType")
if tType == "" {
if strings.Contains(Status(false), "trojan-go") {
tType = "trojan-go"
} else {
tType = "trojan"
}
_ = core.SetValue("trojanType", tType)
}
return tType
}
+196
View File
@@ -0,0 +1,196 @@
package trojan
import (
"encoding/base64"
"fmt"
"net/url"
"strconv"
"strings"
"trojan/core"
"trojan/util"
)
// UserMenu 用户管理菜单
func UserMenu() {
fmt.Println()
menu := []string{"新增用户", "删除用户", "限制流量", "清空流量", "设置限期", "取消限期"}
switch util.LoopInput("请选择: ", menu, false) {
case 1:
AddUser()
case 2:
DelUser()
case 3:
SetUserQuota()
case 4:
CleanData()
case 5:
SetupExpire()
case 6:
CancelExpire()
}
}
// AddUser 添加用户
func AddUser() {
randomUser := util.RandString(4, util.LETTER)
randomPass := util.RandString(8, util.LETTER+util.DIGITS)
inputUser := util.Input(fmt.Sprintf("生成随机用户名: %s, 使用直接回车, 否则输入自定义用户名: ", randomUser), randomUser)
if inputUser == "admin" {
fmt.Println(util.Yellow("不能新建用户名为'admin'的用户!"))
return
}
mysql := core.GetMysql()
if user := mysql.GetUserByName(inputUser); user != nil {
fmt.Println(util.Yellow("已存在用户名为: " + inputUser + " 的用户!"))
return
}
inputPass := util.Input(fmt.Sprintf("生成随机密码: %s, 使用直接回车, 否则输入自定义密码: ", randomPass), randomPass)
base64Pass := base64.StdEncoding.EncodeToString([]byte(inputPass))
if user := mysql.GetUserByPass(base64Pass); user != nil {
fmt.Println(util.Yellow("已存在密码为: " + inputPass + " 的用户!"))
return
}
if mysql.CreateUser(inputUser, base64Pass, inputPass) == nil {
fmt.Println("新增用户成功!")
}
}
// DelUser 删除用户
func DelUser() {
userList := UserList()
mysql := core.GetMysql()
choice := util.LoopInput("请选择要删除的用户序号: ", userList, true)
if choice == -1 {
return
}
if mysql.DeleteUser(userList[choice-1].ID) == nil {
fmt.Println("删除用户成功!")
Restart()
}
}
// SetUserQuota 限制用户流量
func SetUserQuota() {
var (
limit int
err error
)
userList := UserList()
mysql := core.GetMysql()
choice := util.LoopInput("请选择要限制流量的用户序号: ", userList, true)
if choice == -1 {
return
}
for {
quota := util.Input("请输入用户"+userList[choice-1].Username+"限制的流量大小(单位byte)", "")
limit, err = strconv.Atoi(quota)
if err != nil {
fmt.Printf("%s 不是数字, 请重新输入!\n", quota)
} else {
break
}
}
if mysql.SetQuota(userList[choice-1].ID, limit) == nil {
fmt.Println("成功设置用户" + userList[choice-1].Username + "限制流量" + util.Bytefmt(uint64(limit)))
}
}
// CleanData 清空用户流量
func CleanData() {
userList := UserList()
mysql := core.GetMysql()
choice := util.LoopInput("请选择要清空流量的用户序号: ", userList, true)
if choice == -1 {
return
}
if mysql.CleanData(userList[choice-1].ID) == nil {
fmt.Println("清空流量成功!")
}
}
// CancelExpire 取消限期
func CancelExpire() {
userList := UserList()
mysql := core.GetMysql()
choice := util.LoopInput("请选择要取消限期的用户序号: ", userList, true)
if choice == -1 {
return
}
if userList[choice-1].UseDays == 0 {
fmt.Println(util.Yellow("选择的用户未设置限期!"))
return
}
if mysql.CancelExpire(userList[choice-1].ID) == nil {
fmt.Println("取消限期成功!")
}
}
// SetupExpire 设置限期
func SetupExpire() {
userList := UserList()
mysql := core.GetMysql()
choice := util.LoopInput("请选择要设置限期的用户序号: ", userList, true)
if choice == -1 {
return
}
useDayStr := util.Input("请输入要限制使用的天数: ", "")
if useDayStr == "" {
return
} else if strings.Contains(useDayStr, "-") {
fmt.Println(util.Yellow("天数不能为负数"))
return
} else if !util.IsInteger(useDayStr) {
fmt.Println(util.Yellow("输入为非整数!"))
return
}
useDays, _ := strconv.Atoi(useDayStr)
if mysql.SetExpire(userList[choice-1].ID, uint(useDays)) == nil {
fmt.Println("设置限期成功!")
}
}
// CleanDataByName 清空指定用户流量
func CleanDataByName(usernames []string) {
mysql := core.GetMysql()
if err := mysql.CleanDataByName(usernames); err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("清空流量成功!")
}
}
// UserList 获取用户列表并打印显示
func UserList(ids ...string) []*core.User {
mysql := core.GetMysql()
userList, err := mysql.GetData(ids...)
if err != nil {
fmt.Println(err.Error())
return nil
}
domain, port := GetDomainAndPort()
for i, k := range userList {
pass, err := base64.StdEncoding.DecodeString(k.Password)
if err != nil {
pass = []byte("")
}
fmt.Printf("%d.\n", i+1)
fmt.Println("用户名: " + k.Username)
fmt.Println("密码: " + string(pass))
fmt.Println("上传流量: " + util.Cyan(util.Bytefmt(k.Upload)))
fmt.Println("下载流量: " + util.Cyan(util.Bytefmt(k.Download)))
if k.Quota < 0 {
fmt.Println("流量限额: " + util.Cyan("无限制"))
} else {
fmt.Println("流量限额: " + util.Cyan(util.Bytefmt(uint64(k.Quota))))
}
if k.UseDays == 0 {
fmt.Println("到期日期: " + util.Cyan("无限制"))
} else {
fmt.Println("到期日期: " + util.Cyan(k.ExpiryDate))
}
remark := url.QueryEscape(fmt.Sprintf("%s:%d", domain, port))
fmt.Println("分享链接: " + util.Green(fmt.Sprintf("trojan://%s@%s:%d#%s", string(pass), domain, port, remark)))
fmt.Println()
}
return userList
}
+56
View File
@@ -0,0 +1,56 @@
package trojan
import (
"crypto/sha256"
"fmt"
"trojan/core"
"trojan/util"
)
// WebMenu web管理菜单
func WebMenu() {
fmt.Println()
menu := []string{"重置web管理员密码", "修改显示的域名(非申请证书)"}
switch util.LoopInput("请选择: ", menu, true) {
case 1:
ResetAdminPass()
case 2:
SetDomain("")
}
}
// ResetAdminPass 重置管理员密码
func ResetAdminPass() {
inputPass := util.Input("请输入admin用户密码: ", "")
if inputPass == "" {
fmt.Println("撤销更改!")
} else {
encryPass := sha256.Sum224([]byte(inputPass))
err := core.SetValue("admin_pass", fmt.Sprintf("%x", encryPass))
if err == nil {
fmt.Println(util.Green("重置admin密码成功!"))
} else {
fmt.Println(err)
}
}
}
// SetDomain 设置显示的域名
func SetDomain(domain string) {
if domain == "" {
domain = util.Input("请输入要显示的域名地址: ", "")
}
if domain == "" {
fmt.Println("撤销更改!")
} else {
core.WriteDomain(domain)
Restart()
fmt.Println("修改domain成功!")
}
}
// GetDomainAndPort 获取域名和端口
func GetDomainAndPort() (string, int) {
config := core.GetConfig()
return config.SSl.Sni, config.LocalPort
}