diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/api.go b/api.go new file mode 100644 index 0000000..549ef22 --- /dev/null +++ b/api.go @@ -0,0 +1,247 @@ +package main + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "golang.org/x/crypto/bcrypt" +) + +func SetupRouter() *gin.Engine { + r := gin.Default() + + // 静态文件(前端) + r.Static("/static", "./static") + + // API 路由 + api := r.Group("/api") + { + // 登录(无需鉴权) + api.POST("/login", loginHandler) + + // 需要鉴权的路由 + auth := api.Group("/") + auth.Use(AuthMiddleware()) + { + // 全局配置 + auth.GET("/config", getConfigHandler) + auth.PUT("/config", updateConfigHandler) + + // 隧道管理 + auth.GET("/proxies", getProxiesHandler) + auth.GET("/proxy/:id", getProxyHandler) + auth.POST("/proxy", createProxyHandler) + auth.PUT("/proxy/:id", updateProxyHandler) + auth.DELETE("/proxy/:id", deleteProxyHandler) + + // frpc 控制 + auth.POST("/frpc/reload", reloadFrpcHandler) + auth.POST("/frpc/start", startFrpcHandler) + auth.POST("/frpc/stop", stopFrpcHandler) + auth.GET("/frpc/status", getFrpcStatusHandler) + } + } + + // 前端入口 + r.GET("/", func(c *gin.Context) { + c.File("./static/index.html") + }) + + return r +} + +// ========== 登录 ========== +func loginHandler(c *gin.Context) { + var req struct { + Username string `json:"username"` + Password string `json:"password"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) + return + } + + user, err := GetUserByUsername(req.Username) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "用户名或密码错误"}) + return + } + + err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "用户名或密码错误"}) + return + } + + token, err := GenerateJWT(user.Username) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成Token失败"}) + return + } + + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "登录成功", "data": gin.H{"token": token}}) +} + +// ========== 全局配置 ========== +func getConfigHandler(c *gin.Context) { + cfg, err := GetGlobalConfig() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取配置失败"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "data": cfg}) +} + +func updateConfigHandler(c *gin.Context) { + var cfg GlobalConfig + if err := c.ShouldBindJSON(&cfg); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) + return + } + + if err := UpdateGlobalConfig(&cfg); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新配置失败"}) + return + } + + // 自动生成配置文件并热加载 + if err := GenerateFrpcConfig(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置文件失败: " + err.Error()}) + return + } + + if err := ReloadFrpc(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "热加载失败: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配置更新成功"}) +} + +// ========== 隧道管理 ========== +func getProxiesHandler(c *gin.Context) { + proxies, err := GetProxies() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取隧道列表失败"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "data": proxies}) +} + +func getProxyHandler(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + p, err := GetProxy(id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"code": 1, "msg": "隧道不存在"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "data": p}) +} + +func createProxyHandler(c *gin.Context) { + var p Proxy + if err := c.ShouldBindJSON(&p); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) + return + } + p.Enabled = true // 默认启用 + + if err := CreateProxy(&p); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "创建隧道失败"}) + return + } + + // 重新生成配置并热加载 + if err := generateAndReload(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "配置生效失败: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道创建成功", "data": gin.H{"id": p.ID}}) +} + +func updateProxyHandler(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var p Proxy + if err := c.ShouldBindJSON(&p); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) + return + } + p.ID = id + + if err := UpdateProxy(&p); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新隧道失败"}) + return + } + + // 重新生成配置并热加载 + if err := generateAndReload(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "配置生效失败: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道更新成功"}) +} + +func deleteProxyHandler(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + if err := DeleteProxy(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "删除隧道失败"}) + return + } + + // 重新生成配置并热加载 + if err := generateAndReload(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "配置生效失败: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道删除成功"}) +} + +// ========== frpc 控制 ========== +func reloadFrpcHandler(c *gin.Context) { + if err := GenerateFrpcConfig(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置失败: " + err.Error()}) + return + } + if err := ReloadFrpc(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "热加载失败: " + err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "热加载成功"}) +} + +func startFrpcHandler(c *gin.Context) { + if err := StartFrpc(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "启动失败: " + err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "frpc 启动成功"}) +} + +func stopFrpcHandler(c *gin.Context) { + if err := StopFrpc(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "停止失败: " + err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "frpc 已停止"}) +} + +func getFrpcStatusHandler(c *gin.Context) { + running, err := GetFrpcStatus() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "查询状态失败"}) + return + } + c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"running": running}}) +} + +// ========== 辅助函数 ========== +func generateAndReload() error { + if err := GenerateFrpcConfig(); err != nil { + return err + } + return ReloadFrpc() +} diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..b20ef50 --- /dev/null +++ b/auth.go @@ -0,0 +1,138 @@ +package main + +import ( + "errors" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "golang.org/x/crypto/bcrypt" +) + +var jwtSecret = []byte("frpc-console-secret-key-2024") + +type Claims struct { + Username string `json:"username"` + jwt.RegisteredClaims +} + +func GenerateJWT(username string) (string, error) { + claims := Claims{ + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(jwtSecret) +} + +func ParseJWT(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return jwtSecret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + return nil, errors.New("invalid token") +} + +// 密码复杂度验证:至少3个大写 + 8个数字 + 1个特殊字符 +func ValidatePassword(pwd string) bool { + var upper, digit, special int + for _, ch := range pwd { + if ch >= 'A' && ch <= 'Z' { + upper++ + } + if ch >= '0' && ch <= '9' { + digit++ + } + if strings.ContainsAny(string(ch), "!@#$%^&*()_+-=[]{}|;:,.<>?") { + special++ + } + } + return upper >= 3 && digit >= 8 && special >= 1 && len(pwd) >= 12 +} + +// 首次启动强制设置管理员账户 +func InitAdminUser() { + count, err := CountUsers() + if err != nil { + log.Fatal("❌ 检查用户表失败:", err) + } + + if count > 0 { + log.Println("✅ 已存在管理员账户,跳过初始化") + return + } + + log.Println("⚠️ 首次启动,请设置管理员账户") + log.Print("用户名: ") + var username string + fmt.Scanln(&username) + + if username == "" { + username = "admin" + } + + for { + log.Print("密码 (至少3个大写+8个数字+1个特殊字符): ") + var password string + fmt.Scanln(&password) + + if ValidatePassword(password) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + log.Println("❌ 密码加密失败:", err) + continue + } + err = CreateUser(username, string(hash)) + if err != nil { + log.Println("❌ 创建用户失败:", err) + continue + } + log.Println("✅ 管理员账户创建成功!") + break + } else { + log.Println("❌ 密码不符合复杂度要求,请重新输入") + log.Println(" 要求: 至少3个大写字母 + 8个数字 + 1个特殊字符") + } + } +} + +// JWT 认证中间件 +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "未提供认证令牌"}) + c.Abort() + return + } + // 格式 "Bearer " + parts := strings.SplitN(authHeader, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "认证令牌格式错误"}) + c.Abort() + return + } + tokenString := parts[1] + claims, err := ParseJWT(tokenString) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "无效的认证令牌"}) + c.Abort() + return + } + // 将用户名存入上下文,方便后续使用 + c.Set("username", claims.Username) + c.Next() + } +} diff --git a/db.go b/db.go new file mode 100644 index 0000000..836d630 --- /dev/null +++ b/db.go @@ -0,0 +1,231 @@ +package main + +import ( + "database/sql" + "log" + + _ "modernc.org/sqlite" +) + +var DB *sql.DB + +// 全局配置表(A表) +type GlobalConfig struct { + ID int `json:"id"` + ServerAddr string `json:"serverAddr"` + ServerPort int `json:"serverPort"` + Token string `json:"token"` + LogLevel string `json:"logLevel"` + LogMaxDays int `json:"logMaxDays"` + TcpMux bool `json:"tcpMux"` + TcpMuxKeepalive int `json:"tcpMuxKeepalive"` + HeartbeatInterval int `json:"heartbeatInterval"` + HeartbeatTimeout int `json:"heartbeatTimeout"` + PoolCount int `json:"poolCount"` +} + +// 隧道表(B表) +type Proxy struct { + ID int `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + LocalIP string `json:"localIP"` + LocalPort int `json:"localPort"` + RemotePort int `json:"remotePort"` + Enabled bool `json:"enabled"` +} + +// 用户表(C表) +type User struct { + ID int `json:"id"` + Username string `json:"username"` + PasswordHash string `json:"-"` + CreatedAt string `json:"createdAt"` +} + +func InitDB() error { + var err error + DB, err = sql.Open("sqlite3", "./frpc-console.db") + if err != nil { + return err + } + + // 创建用户表 + _, err = DB.Exec(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `) + if err != nil { + return err + } + + // 创建全局配置表(只有一行数据) + _, err = DB.Exec(` + CREATE TABLE IF NOT EXISTS global_config ( + id INTEGER PRIMARY KEY CHECK (id = 1), + server_addr TEXT NOT NULL DEFAULT 'frp.whitetop.xyz', + server_port INTEGER NOT NULL DEFAULT 9358, + token TEXT NOT NULL DEFAULT 'Lxh10020328', + log_level TEXT NOT NULL DEFAULT 'info', + log_max_days INTEGER NOT NULL DEFAULT 3, + tcp_mux INTEGER NOT NULL DEFAULT 1, + tcp_mux_keepalive INTEGER NOT NULL DEFAULT 30, + heartbeat_interval INTEGER NOT NULL DEFAULT 15, + heartbeat_timeout INTEGER NOT NULL DEFAULT 70, + pool_count INTEGER NOT NULL DEFAULT 8, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `) + if err != nil { + return err + } + + // 确保全局配置表有一行数据 + var count int + DB.QueryRow("SELECT COUNT(*) FROM global_config").Scan(&count) + if count == 0 { + _, err = DB.Exec(` + INSERT INTO global_config (id, server_addr, server_port, token, log_level, log_max_days, tcp_mux, tcp_mux_keepalive, heartbeat_interval, heartbeat_timeout, pool_count) + VALUES (1, 'frp.whitetop.xyz', 9358, 'Lxh10020328', 'info', 3, 1, 30, 15, 70, 8) + `) + if err != nil { + return err + } + log.Println("✅ 全局配置初始化完成") + } + + // 创建隧道表 + _, err = DB.Exec(` + CREATE TABLE IF NOT EXISTS proxies ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL DEFAULT 'tcp', + local_ip TEXT NOT NULL, + local_port INTEGER NOT NULL, + remote_port INTEGER NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `) + if err != nil { + return err + } + + log.Println("✅ 数据库初始化完成") + return nil +} + +// ========== 全局配置 CRUD ========== +func GetGlobalConfig() (*GlobalConfig, error) { + var cfg GlobalConfig + err := DB.QueryRow(` + SELECT id, server_addr, server_port, token, log_level, log_max_days, tcp_mux, tcp_mux_keepalive, heartbeat_interval, heartbeat_timeout, pool_count + FROM global_config WHERE id = 1 + `).Scan(&cfg.ID, &cfg.ServerAddr, &cfg.ServerPort, &cfg.Token, &cfg.LogLevel, &cfg.LogMaxDays, &cfg.TcpMux, &cfg.TcpMuxKeepalive, &cfg.HeartbeatInterval, &cfg.HeartbeatTimeout, &cfg.PoolCount) + if err != nil { + return nil, err + } + return &cfg, nil +} + +func UpdateGlobalConfig(cfg *GlobalConfig) error { + _, err := DB.Exec(` + UPDATE global_config SET + server_addr = ?, server_port = ?, token = ?, log_level = ?, log_max_days = ?, + tcp_mux = ?, tcp_mux_keepalive = ?, heartbeat_interval = ?, heartbeat_timeout = ?, pool_count = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = 1 + `, cfg.ServerAddr, cfg.ServerPort, cfg.Token, cfg.LogLevel, cfg.LogMaxDays, + cfg.TcpMux, cfg.TcpMuxKeepalive, cfg.HeartbeatInterval, cfg.HeartbeatTimeout, cfg.PoolCount) + return err +} + +// ========== 隧道 CRUD ========== +func GetProxies() ([]Proxy, error) { + rows, err := DB.Query(` + SELECT id, name, type, local_ip, local_port, remote_port, enabled + FROM proxies ORDER BY id + `) + if err != nil { + return nil, err + } + defer rows.Close() + + var proxies []Proxy + for rows.Next() { + var p Proxy + err := rows.Scan(&p.ID, &p.Name, &p.Type, &p.LocalIP, &p.LocalPort, &p.RemotePort, &p.Enabled) + if err != nil { + return nil, err + } + proxies = append(proxies, p) + } + return proxies, nil +} + +func GetProxy(id int) (*Proxy, error) { + var p Proxy + err := DB.QueryRow(` + SELECT id, name, type, local_ip, local_port, remote_port, enabled + FROM proxies WHERE id = ? + `, id).Scan(&p.ID, &p.Name, &p.Type, &p.LocalIP, &p.LocalPort, &p.RemotePort, &p.Enabled) + if err != nil { + return nil, err + } + return &p, nil +} + +func CreateProxy(p *Proxy) error { + _, err := DB.Exec(` + INSERT INTO proxies (name, type, local_ip, local_port, remote_port, enabled) + VALUES (?, ?, ?, ?, ?, ?) + `, p.Name, p.Type, p.LocalIP, p.LocalPort, p.RemotePort, p.Enabled) + return err +} + +func UpdateProxy(p *Proxy) error { + _, err := DB.Exec(` + UPDATE proxies SET + name = ?, type = ?, local_ip = ?, local_port = ?, remote_port = ?, enabled = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ? + `, p.Name, p.Type, p.LocalIP, p.LocalPort, p.RemotePort, p.Enabled, p.ID) + return err +} + +func DeleteProxy(id int) error { + _, err := DB.Exec("DELETE FROM proxies WHERE id = ?", id) + return err +} + +// ========== 用户 CRUD ========== +func GetUserByUsername(username string) (*User, error) { + var u User + err := DB.QueryRow(` + SELECT id, username, password_hash, created_at + FROM users WHERE username = ? + `, username).Scan(&u.ID, &u.Username, &u.PasswordHash, &u.CreatedAt) + if err != nil { + return nil, err + } + return &u, nil +} + +func CreateUser(username, passwordHash string) error { + _, err := DB.Exec(` + INSERT INTO users (username, password_hash) VALUES (?, ?) + `, username, passwordHash) + return err +} + +func CountUsers() (int, error) { + var count int + err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&count) + return count, err +} diff --git a/frp.go b/frp.go new file mode 100644 index 0000000..813165d --- /dev/null +++ b/frp.go @@ -0,0 +1,146 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + "text/template" +) + +// frpc.toml 模板 - 完全复制你的配置文件结构 +const frpcTemplate = `serverAddr = "{{.ServerAddr}}" +serverPort = {{.ServerPort}} + +[auth] +token = "{{.Token}}" + +[log] +to = "./frpc.log" +level = "{{.LogLevel}}" +maxDays = {{.LogMaxDays}} + +[transport] +tcpMux = {{.TcpMux}} +tcpMuxKeepaliveInterval = {{.TcpMuxKeepalive}} +heartbeatInterval = {{.HeartbeatInterval}} +heartbeatTimeout = {{.HeartbeatTimeout}} +poolCount = {{.PoolCount}} + +{{range .Proxies}} +[[proxies]] +name = "{{.Name}}" +type = "{{.Type}}" +localIP = "{{.LocalIP}}" +localPort = {{.LocalPort}} +remotePort = {{.RemotePort}} +{{end}}` + +// 生成 frpc.toml 文件 +func GenerateFrpcConfig() error { + // 1. 读取全局配置 + cfg, err := GetGlobalConfig() + if err != nil { + return fmt.Errorf("读取全局配置失败: %w", err) + } + + // 2. 读取所有启用的隧道 + proxies, err := GetProxies() + if err != nil { + return fmt.Errorf("读取隧道列表失败: %w", err) + } + + var activeProxies []Proxy + for _, p := range proxies { + if p.Enabled { + activeProxies = append(activeProxies, p) + } + } + + // 3. 组合数据 + data := struct { + *GlobalConfig + Proxies []Proxy + }{ + GlobalConfig: cfg, + Proxies: activeProxies, + } + + // 4. 渲染模板 + tmpl, err := template.New("frpc").Parse(frpcTemplate) + if err != nil { + return fmt.Errorf("解析模板失败: %w", err) + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + if err != nil { + return fmt.Errorf("渲染模板失败: %w", err) + } + + // 5. 写入文件 + err = os.WriteFile("./frpc.toml", buf.Bytes(), 0644) + if err != nil { + return fmt.Errorf("写入配置文件失败: %w", err) + } + + return nil +} + +// 热加载 frpc +func ReloadFrpc() error { + // 先检查 frpc 进程是否在运行 + cmd := exec.Command("pgrep", "-f", "frpc") + output, err := cmd.Output() + if err != nil || len(output) == 0 { + // frpc 没在运行,尝试启动 + return StartFrpc() + } + + // 执行 reload + reloadCmd := exec.Command("frpc", "reload", "-c", "./frpc.toml") + output, err = reloadCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("reload 失败: %s, %w", string(output), err) + } + return nil +} + +// 启动 frpc +func StartFrpc() error { + // 先确保配置文件存在 + if _, err := os.Stat("./frpc.toml"); os.IsNotExist(err) { + if err := GenerateFrpcConfig(); err != nil { + return fmt.Errorf("生成配置文件失败: %w", err) + } + } + + cmd := exec.Command("frpc", "-c", "./frpc.toml") + // 后台运行,不阻塞 + err := cmd.Start() + if err != nil { + return fmt.Errorf("启动 frpc 失败: %w", err) + } + return nil +} + +// StopFrpc 停止 frpc +func StopFrpc() error { + cmd := exec.Command("pkill", "-f", "frpc") + err := cmd.Run() + if err != nil && !strings.Contains(err.Error(), "no process") { + return fmt.Errorf("停止 frpc 失败: %w", err) + } + return nil +} + +// 检查 frpc 状态 +func GetFrpcStatus() (bool, error) { + cmd := exec.Command("pgrep", "-f", "frpc") + output, err := cmd.Output() + if err != nil { + return false, nil + } + return len(output) > 0, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4161340 --- /dev/null +++ b/go.mod @@ -0,0 +1,48 @@ +module frpc-console + +go 1.26.5 + +require ( + github.com/gin-gonic/gin v1.12.0 + github.com/golang-jwt/jwt/v5 v5.3.1 + golang.org/x/crypto v0.54.0 + modernc.org/sqlite v1.54.0 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + modernc.org/libc v1.74.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..935b577 --- /dev/null +++ b/go.sum @@ -0,0 +1,137 @@ +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc= +modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ= +modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog= +modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..742cd46 --- /dev/null +++ b/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "log" +) + +func main() { + // 1. 初始化数据库 + if err := InitDB(); err != nil { + log.Fatal("❌ 数据库初始化失败:", err) + } + + // 2. 首次启动设置管理员 + InitAdminUser() + + // 3. 启动时生成 frpc.toml + if err := GenerateFrpcConfig(); err != nil { + log.Println("⚠️ 生成配置文件失败:", err) + } + + // 4. 尝试启动 frpc + if err := StartFrpc(); err != nil { + log.Println("⚠️ 启动 frpc 失败:", err) + } + + // 5. 启动 Web 服务 + r := SetupRouter() + log.Println("🚀 frpc-console 启动成功!") + log.Println("📍 访问地址: http://localhost:8080") + log.Println("📍 API 地址: http://localhost:8080/api") + + if err := r.Run(":8080"); err != nil { + log.Fatal("❌ 服务启动失败:", err) + } +} diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..e69de29 diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..b49a20b --- /dev/null +++ b/static/index.html @@ -0,0 +1,22 @@ + + + + + + frpc-console + + + +
+

🚀 frpc-console

+

后端已启动,正在等待前端接入...

+
✅ frpc 运行中
+
+ + \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..e69de29 diff --git a/templates/frpc.toml.tmpl b/templates/frpc.toml.tmpl new file mode 100644 index 0000000..e69de29