470 lines
11 KiB
Go
470 lines
11 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"crypto/rand"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
"os"
|
||
"os/exec"
|
||
"regexp"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
// 颜色常量(ANSI转义码)
|
||
ColorReset = "\033[0m"
|
||
ColorRed = "\033[31m"
|
||
ColorGreen = "\033[32m"
|
||
ColorYellow = "\033[33m"
|
||
ColorCyan = "\033[36m"
|
||
|
||
// 构建:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o sk5 main.go
|
||
// 脚本过期时间以及其他变量
|
||
EXPIRE_DATE = "2025-06-08 02:01:01"
|
||
CONFIG_FILE = "/usr/local/etc/xray/config.json"
|
||
SOCKS_FILE = "/home/socks.txt"
|
||
XRAY_INSTALL_URL = "https://github.com/XTLS/Xray-install/raw/main/install-release.sh"
|
||
XRAY_VERSION = "v1.8.4"
|
||
START_PORT = 10001
|
||
)
|
||
|
||
// 彩色打印函数
|
||
func colorPrint(colorCode, format string, a ...interface{}) {
|
||
fmt.Printf(colorCode+format+ColorReset+"\n", a...)
|
||
}
|
||
|
||
// XrayConfig represents the Xray configuration structure
|
||
type XrayConfig struct {
|
||
Inbounds []Inbound `json:"inbounds"`
|
||
Outbounds []Outbound `json:"outbounds"`
|
||
Routing Routing `json:"routing"`
|
||
}
|
||
|
||
type Inbound struct {
|
||
Port int `json:"port"`
|
||
Protocol string `json:"protocol"`
|
||
Settings InboundSettings `json:"settings"`
|
||
StreamSettings StreamSettings `json:"streamSettings"`
|
||
Tag string `json:"tag"`
|
||
}
|
||
|
||
type InboundSettings struct {
|
||
Auth string `json:"auth"`
|
||
Accounts []Account `json:"accounts"`
|
||
UDP bool `json:"udp"`
|
||
IP string `json:"ip"`
|
||
}
|
||
|
||
type Account struct {
|
||
User string `json:"user"`
|
||
Pass string `json:"pass"`
|
||
}
|
||
|
||
type StreamSettings struct {
|
||
Network string `json:"network"`
|
||
}
|
||
|
||
type Outbound struct {
|
||
Protocol string `json:"protocol"`
|
||
Settings interface{} `json:"settings"`
|
||
SendThrough string `json:"sendThrough"`
|
||
Tag string `json:"tag"`
|
||
}
|
||
|
||
type Routing struct {
|
||
Rules []Rule `json:"rules"`
|
||
}
|
||
|
||
type Rule struct {
|
||
Type string `json:"type"`
|
||
InboundTag []string `json:"inboundTag"`
|
||
OutboundTag string `json:"outboundTag"`
|
||
}
|
||
|
||
type NodeInfo struct {
|
||
IP string
|
||
Port int
|
||
Username string
|
||
Password string
|
||
}
|
||
|
||
// generateRandomString generates a random string of specified length
|
||
func generateRandomString(length int) string {
|
||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||
b := make([]byte, length)
|
||
if _, err := rand.Read(b); err != nil {
|
||
panic(err)
|
||
}
|
||
for i := range b {
|
||
b[i] = charset[b[i]%byte(len(charset))]
|
||
}
|
||
return string(b)
|
||
}
|
||
|
||
// checkExpiration checks if the script has expired
|
||
func checkExpiration() error {
|
||
colorPrint(ColorCyan, "开始运行...")
|
||
|
||
// Get timestamp from cloudflare
|
||
resp, err := http.Get("https://www.cloudflare.com/cdn-cgi/trace")
|
||
if err != nil {
|
||
return fmt.Errorf("网络错误")
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return fmt.Errorf("读取响应失败")
|
||
}
|
||
|
||
// Extract timestamp
|
||
re := regexp.MustCompile(`ts=(\d+)`)
|
||
matches := re.FindStringSubmatch(string(body))
|
||
if len(matches) < 2 {
|
||
return fmt.Errorf("无法解析时间")
|
||
}
|
||
|
||
timestamp, err := strconv.ParseInt(matches[1], 10, 64)
|
||
if err != nil {
|
||
return fmt.Errorf("时间转换失败")
|
||
}
|
||
|
||
// Convert to Beijing time
|
||
currentTime := time.Unix(timestamp, 0).In(time.FixedZone("CST", 8*3600))
|
||
expireTime, _ := time.ParseInLocation("2006-01-02 15:04:05", EXPIRE_DATE, time.FixedZone("CST", 8*3600))
|
||
|
||
if currentTime.After(expireTime) {
|
||
return fmt.Errorf("当前脚本已过期,请联系作者")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// commandExists checks if a command exists in PATH
|
||
func commandExists(cmd string) bool {
|
||
_, err := exec.LookPath(cmd)
|
||
return err == nil
|
||
}
|
||
|
||
// installJQ installs jq if not present
|
||
func installJQ() error {
|
||
if commandExists("jq") {
|
||
colorPrint(ColorGreen, "jq 已安装")
|
||
return nil
|
||
}
|
||
|
||
colorPrint(ColorYellow, "jq 未安装,正在安装 jq...")
|
||
|
||
// Detect OS
|
||
if _, err := os.Stat("/etc/debian_version"); err == nil {
|
||
// Debian/Ubuntu
|
||
cmd := exec.Command("bash", "-c", "apt update && apt install -yq jq")
|
||
return cmd.Run()
|
||
} else if _, err := os.Stat("/etc/redhat-release"); err == nil {
|
||
// RHEL/CentOS
|
||
cmd := exec.Command("yum", "install", "-y", "epel-release", "jq")
|
||
return cmd.Run()
|
||
}
|
||
|
||
return fmt.Errorf("无法确定系统发行版,请手动安装 jq")
|
||
}
|
||
|
||
// installXray installs Xray if not present
|
||
func installXray() error {
|
||
if commandExists("xray") {
|
||
colorPrint(ColorGreen, "Xray 已安装")
|
||
return nil
|
||
}
|
||
|
||
colorPrint(ColorYellow, "Xray 未安装,正在安装 Xray...")
|
||
|
||
cmd := exec.Command("bash", "-c", fmt.Sprintf("curl -L %s | bash -s install --version %s", XRAY_INSTALL_URL, XRAY_VERSION))
|
||
if err := cmd.Run(); err != nil {
|
||
return fmt.Errorf("Xray 安装失败: %v", err)
|
||
}
|
||
|
||
colorPrint(ColorGreen, "Xray 安装完成")
|
||
return nil
|
||
}
|
||
|
||
// getPublicIPv4 gets all public IPv4 addresses
|
||
func getPublicIPv4() ([]string, error) {
|
||
var publicIPs []string
|
||
|
||
// Get all network interfaces
|
||
interfaces, err := net.Interfaces()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, iface := range interfaces {
|
||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||
continue
|
||
}
|
||
|
||
addrs, err := iface.Addrs()
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
for _, addr := range addrs {
|
||
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
||
if ipNet.IP.To4() != nil {
|
||
ip := ipNet.IP.String()
|
||
// Check if it's a public IP
|
||
if isPublicIP(ip) {
|
||
publicIPs = append(publicIPs, ip)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return publicIPs, nil
|
||
}
|
||
|
||
// isPublicIP checks if an IP is public
|
||
func isPublicIP(ip string) bool {
|
||
parsedIP := net.ParseIP(ip)
|
||
if parsedIP == nil {
|
||
return false
|
||
}
|
||
|
||
// Check for private IP ranges
|
||
privateRanges := []string{
|
||
"127.0.0.0/8", // loopback
|
||
"10.0.0.0/8", // private
|
||
"172.16.0.0/12", // private
|
||
"192.168.0.0/16", // private
|
||
"169.254.0.0/16", // link-local
|
||
}
|
||
|
||
for _, cidr := range privateRanges {
|
||
_, network, _ := net.ParseCIDR(cidr)
|
||
if network.Contains(parsedIP) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// ensureSocksFileExists creates socks.txt if it doesn't exist
|
||
func ensureSocksFileExists() error {
|
||
if _, err := os.Stat(SOCKS_FILE); os.IsNotExist(err) {
|
||
colorPrint(ColorYellow, "socks.txt 文件不存在,正在创建...")
|
||
file, err := os.Create(SOCKS_FILE)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
file.Close()
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// saveNodeInfo saves node information to file and prints it
|
||
func saveNodeInfo(node NodeInfo) error {
|
||
// Print node info with colors
|
||
fmt.Printf(" IP: %s%s%s 端口: %s%d%s 用户名: %s%s%s 密码: %s%s%s\n",
|
||
ColorGreen, node.IP, ColorReset,
|
||
ColorGreen, node.Port, ColorReset,
|
||
ColorGreen, node.Username, ColorReset,
|
||
ColorGreen, node.Password, ColorReset)
|
||
|
||
// Save to file
|
||
file, err := os.OpenFile(SOCKS_FILE, os.O_APPEND|os.O_WRONLY, 0644)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer file.Close()
|
||
|
||
_, err = fmt.Fprintf(file, "%s %d %s %s\n", node.IP, node.Port, node.Username, node.Password)
|
||
return err
|
||
}
|
||
|
||
// configureXray configures Xray with multiple IPs
|
||
func configureXray() error {
|
||
publicIPs, err := getPublicIPv4()
|
||
if err != nil {
|
||
return fmt.Errorf("获取公网IP失败: %v", err)
|
||
}
|
||
|
||
if len(publicIPs) == 0 {
|
||
return fmt.Errorf("未找到额外IP地址")
|
||
}
|
||
|
||
colorPrint(ColorCyan, "找到的公网 IPv4 地址: %v", publicIPs)
|
||
|
||
// Create initial config
|
||
config := XrayConfig{
|
||
Inbounds: []Inbound{},
|
||
Outbounds: []Outbound{},
|
||
Routing: Routing{
|
||
Rules: []Rule{},
|
||
},
|
||
}
|
||
|
||
// Configure each IP
|
||
port := START_PORT
|
||
for _, ip := range publicIPs {
|
||
colorPrint(ColorCyan, "正在配置 IP: %s 端口: %d", ip, port)
|
||
|
||
username := generateRandomString(8)
|
||
password := generateRandomString(8)
|
||
|
||
// Create inbound
|
||
inbound := Inbound{
|
||
Port: port,
|
||
Protocol: "socks",
|
||
Settings: InboundSettings{
|
||
Auth: "password",
|
||
Accounts: []Account{
|
||
{User: username, Pass: password},
|
||
},
|
||
UDP: true,
|
||
IP: "0.0.0.0",
|
||
},
|
||
StreamSettings: StreamSettings{
|
||
Network: "tcp",
|
||
},
|
||
Tag: fmt.Sprintf("in-%d", port),
|
||
}
|
||
|
||
// Create outbound
|
||
outbound := Outbound{
|
||
Protocol: "freedom",
|
||
Settings: map[string]interface{}{},
|
||
SendThrough: ip,
|
||
Tag: fmt.Sprintf("out-%d", port),
|
||
}
|
||
|
||
// Create routing rule
|
||
rule := Rule{
|
||
Type: "field",
|
||
InboundTag: []string{fmt.Sprintf("in-%d", port)},
|
||
OutboundTag: fmt.Sprintf("out-%d", port),
|
||
}
|
||
|
||
config.Inbounds = append(config.Inbounds, inbound)
|
||
config.Outbounds = append(config.Outbounds, outbound)
|
||
config.Routing.Rules = append(config.Routing.Rules, rule)
|
||
|
||
// Save node info
|
||
node := NodeInfo{
|
||
IP: ip,
|
||
Port: port,
|
||
Username: username,
|
||
Password: password,
|
||
}
|
||
if err := saveNodeInfo(node); err != nil {
|
||
return fmt.Errorf("保存节点信息失败: %v", err)
|
||
}
|
||
|
||
port++
|
||
}
|
||
|
||
// Write config file
|
||
configData, err := json.MarshalIndent(config, "", " ")
|
||
if err != nil {
|
||
return fmt.Errorf("序列化配置失败: %v", err)
|
||
}
|
||
|
||
if err := os.WriteFile(CONFIG_FILE, configData, 0644); err != nil {
|
||
return fmt.Errorf("写入配置文件失败: %v", err)
|
||
}
|
||
|
||
colorPrint(ColorGreen, "Xray 配置完成")
|
||
return nil
|
||
}
|
||
|
||
// restartXray restarts the Xray service
|
||
func restartXray() error {
|
||
colorPrint(ColorCyan, "正在重启 Xray 服务...")
|
||
|
||
// Restart service
|
||
cmd := exec.Command("systemctl", "restart", "xray")
|
||
if err := cmd.Run(); err != nil {
|
||
return fmt.Errorf("Xray 服务重启失败: %v", err)
|
||
}
|
||
|
||
// Enable service
|
||
cmd = exec.Command("systemctl", "enable", "xray")
|
||
if err := cmd.Run(); err != nil {
|
||
return fmt.Errorf("启用 Xray 服务失败: %v", err)
|
||
}
|
||
|
||
colorPrint(ColorGreen, "Xray 服务已重启")
|
||
return nil
|
||
}
|
||
|
||
// readUserInput reads user input for confirmation
|
||
func readUserInput(prompt string) string {
|
||
fmt.Print(prompt)
|
||
reader := bufio.NewReader(os.Stdin)
|
||
input, _ := reader.ReadString('\n')
|
||
return strings.TrimSpace(input)
|
||
}
|
||
|
||
func main() {
|
||
colorPrint(ColorCyan, "站群多IP源进源出sk5协议一键脚本")
|
||
colorPrint(ColorCyan, "当前为测试版,可以联系作者获取源码")
|
||
expireTime, err := time.ParseInLocation("2006-01-02 15:04:05", EXPIRE_DATE, time.FixedZone("CST", 8*3600))
|
||
if err == nil {
|
||
expireStr := fmt.Sprintf("%d年%d月%d日%d点%d分%d秒",
|
||
expireTime.Year(),
|
||
expireTime.Month(),
|
||
expireTime.Day(),
|
||
expireTime.Hour(),
|
||
expireTime.Minute(),
|
||
expireTime.Second())
|
||
colorPrint(ColorCyan, "脚本过期时间: %s", expireStr)
|
||
} else {
|
||
colorPrint(ColorYellow, "脚本过期时间解析失败")
|
||
}
|
||
fmt.Println()
|
||
|
||
// Check expiration
|
||
if err := checkExpiration(); err != nil {
|
||
colorPrint(ColorRed, "错误: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Ensure socks file exists
|
||
if err := ensureSocksFileExists(); err != nil {
|
||
colorPrint(ColorRed, "创建socks文件失败: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Install jq
|
||
if err := installJQ(); err != nil {
|
||
colorPrint(ColorRed, "安装jq失败: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Install Xray
|
||
if err := installXray(); err != nil {
|
||
colorPrint(ColorRed, "安装Xray失败: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Configure Xray
|
||
if err := configureXray(); err != nil {
|
||
colorPrint(ColorRed, "配置Xray失败: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Restart Xray
|
||
if err := restartXray(); err != nil {
|
||
colorPrint(ColorRed, "重启Xray失败: %v", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
colorPrint(ColorGreen, "部署完成,所有节点信息已保存到 %s", SOCKS_FILE)
|
||
}
|