From 866fa62f62c8b21ccf82951b0bd57a2fa7a3cca6 Mon Sep 17 00:00:00 2001 From: godo Date: Sat, 28 Dec 2024 16:06:52 +0800 Subject: [PATCH] add nas and frpc --- frontend/components.d.ts | 4 + frontend/src/components/setting/LocalNas.vue | 157 ++++++++++ .../src/components/setting/LocalProxy.vue | 34 ++- frontend/src/components/setting/NasClient.vue | 154 ++++++++++ frontend/src/components/setting/NetProxy.vue | 4 + .../src/components/setting/SetAccount.vue | 45 ++- frontend/src/components/setting/SetCustom.vue | 3 +- frontend/src/components/setting/SetNas.vue | 33 ++ frontend/src/components/setting/SetSystem.vue | 92 +++--- frontend/src/components/setting/Setting.vue | 22 +- .../src/components/setting/WebDavClient.vue | 154 ++++++++++ godo/build.sh | 2 + godo/files/pwd.go | 3 +- godo/libs/dir.go | 8 + godo/model/client_user.go | 15 + godo/model/init.go | 27 ++ godo/model/local_proxy.go | 14 + godo/model/server_user.go | 16 + godo/model/sys_disk.go | 59 ++++ godo/model/sys_user.go | 13 + godo/sys/frpc.go | 285 ++++++++++++++++++ 21 files changed, 1078 insertions(+), 66 deletions(-) create mode 100644 frontend/src/components/setting/LocalNas.vue create mode 100644 frontend/src/components/setting/NasClient.vue create mode 100644 frontend/src/components/setting/SetNas.vue create mode 100644 frontend/src/components/setting/WebDavClient.vue create mode 100644 godo/model/client_user.go create mode 100644 godo/model/init.go create mode 100644 godo/model/local_proxy.go create mode 100644 godo/model/server_user.go create mode 100644 godo/model/sys_disk.go create mode 100644 godo/model/sys_user.go create mode 100644 godo/sys/frpc.go diff --git a/frontend/components.d.ts b/frontend/components.d.ts index ada20b6..b9b837b 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -129,6 +129,7 @@ declare module 'vue' { InstallMember: typeof import('./src/components/install/InstallMember.vue')['default'] InstallPerson: typeof import('./src/components/install/InstallPerson.vue')['default'] LocalChat: typeof import('./src/components/localchat/LocalChat.vue')['default'] + LocalNas: typeof import('./src/components/setting/LocalNas.vue')['default'] LocalProxy: typeof import('./src/components/setting/LocalProxy.vue')['default'] LockDesktop: typeof import('./src/components/desktop/LockDesktop.vue')['default'] Magnet: typeof import('./src/components/taskbar/Magnet.vue')['default'] @@ -141,6 +142,7 @@ declare module 'vue' { MobileApp: typeof import('./src/components/desktop/mobile/MobileApp.vue')['default'] MusicStore: typeof import('./src/components/builtin/MusicStore.vue')['default'] MusicViewer: typeof import('./src/components/builtin/MusicViewer.vue')['default'] + NasClient: typeof import('./src/components/setting/NasClient.vue')['default'] NetProxy: typeof import('./src/components/setting/NetProxy.vue')['default'] NetWork: typeof import('./src/components/taskbar/NetWork.vue')['default'] NetworkPop: typeof import('./src/components/taskbar/NetworkPop.vue')['default'] @@ -167,6 +169,7 @@ declare module 'vue' { SetCustom: typeof import('./src/components/setting/SetCustom.vue')['default'] SetFilePwd: typeof import('./src/components/setting/SetFilePwd.vue')['default'] SetLang: typeof import('./src/components/setting/SetLang.vue')['default'] + SetNas: typeof import('./src/components/setting/SetNas.vue')['default'] SetSystem: typeof import('./src/components/setting/SetSystem.vue')['default'] Setting: typeof import('./src/components/setting/Setting.vue')['default'] SetUpdate: typeof import('./src/components/setting/SetUpdate.vue')['default'] @@ -182,6 +185,7 @@ declare module 'vue' { UrlBrowser: typeof import('./src/components/builtin/UrlBrowser.vue')['default'] Version: typeof import('./src/components/builtin/Version.vue')['default'] VideoViewer: typeof import('./src/components/builtin/VideoViewer.vue')['default'] + WebDavClient: typeof import('./src/components/setting/WebDavClient.vue')['default'] WinButton: typeof import('./src/components/ui/WinButton.vue')['default'] WinCheckBox: typeof import('./src/components/ui/WinCheckBox.vue')['default'] WindowGroup: typeof import('./src/components/window/WindowGroup.vue')['default'] diff --git a/frontend/src/components/setting/LocalNas.vue b/frontend/src/components/setting/LocalNas.vue new file mode 100644 index 0000000..84b6a41 --- /dev/null +++ b/frontend/src/components/setting/LocalNas.vue @@ -0,0 +1,157 @@ + + \ No newline at end of file diff --git a/frontend/src/components/setting/LocalProxy.vue b/frontend/src/components/setting/LocalProxy.vue index 099cf28..e53631e 100644 --- a/frontend/src/components/setting/LocalProxy.vue +++ b/frontend/src/components/setting/LocalProxy.vue @@ -1,10 +1,11 @@ + \ No newline at end of file diff --git a/frontend/src/components/setting/NetProxy.vue b/frontend/src/components/setting/NetProxy.vue index b2a7a05..95cf01f 100644 --- a/frontend/src/components/setting/NetProxy.vue +++ b/frontend/src/components/setting/NetProxy.vue @@ -37,7 +37,11 @@ const isEditing = ref(false); const pwdRef = ref(null); const types = ref([ { label: 'HTTP', value: 'http' }, + { label: '静态文件访问', value: 'file' }, + { label: '点对点穿透', value: 'p2p' }, + { label: 'SSH', value: 'ssh' }, { label: 'SOCKS5', value: 'socks5' }, + ]) const addProxy = () => { diff --git a/frontend/src/components/setting/SetAccount.vue b/frontend/src/components/setting/SetAccount.vue index 417ef63..39b6838 100644 --- a/frontend/src/components/setting/SetAccount.vue +++ b/frontend/src/components/setting/SetAccount.vue @@ -68,6 +68,24 @@ +
+
+

{{ t("language") }}

+
+
+ + + + +
+ +
+ + + {{ t("confirm") }} + +
+
@@ -75,15 +93,30 @@ \ No newline at end of file diff --git a/frontend/src/components/setting/SetSystem.vue b/frontend/src/components/setting/SetSystem.vue index 86d2495..da33df0 100644 --- a/frontend/src/components/setting/SetSystem.vue +++ b/frontend/src/components/setting/SetSystem.vue @@ -152,8 +152,8 @@ + \ No newline at end of file diff --git a/godo/build.sh b/godo/build.sh index 3751c6c..7ecf0f0 100644 --- a/godo/build.sh +++ b/godo/build.sh @@ -28,6 +28,8 @@ for PLATFORM in "${PLATFORMS[@]}"; do export GOOS=$OS export GOARCH=$ARCH export GODOTOPTYPE="web" + # 设置CGO_ENABLED=0以进行静态链接 + export CGO_ENABLED=0 # 执行编译命令,并处理可能的错误 go build -ldflags="-s -w" -o "$OUTPUT_FILE" ./main.go || { echo "编译 $OS/$ARCH 失败,请检查错误并尝试解决。"; continue; } diff --git a/godo/files/pwd.go b/godo/files/pwd.go index 38a4a93..ce02a97 100644 --- a/godo/files/pwd.go +++ b/godo/files/pwd.go @@ -5,7 +5,6 @@ import ( "fmt" "godo/libs" "io" - "log" "net/http" "os" "path/filepath" @@ -175,7 +174,7 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) { if haslink { needPwd = false } - log.Printf("needPwd:%v", needPwd) + //log.Printf("needPwd:%v", needPwd) // 即不是加密用户又不是加密文件 if !needPwd { // 直接写入新内容 diff --git a/godo/libs/dir.go b/godo/libs/dir.go index 7619462..3bf52ff 100644 --- a/godo/libs/dir.go +++ b/godo/libs/dir.go @@ -163,6 +163,14 @@ func GetVectorDb() string { } return filepath.Join(dbPath, "vector.db") } +func GetSystemDb() string { + homeDir := GetDataDir() + dbPath := filepath.Join(homeDir, "db") + if !PathExists(dbPath) { + os.MkdirAll(dbPath, 0755) + } + return filepath.Join(dbPath, "system.db") +} func GetVectorDbName(name string) string { hasher := md5.New() hasher.Write([]byte(name)) diff --git a/godo/model/client_user.go b/godo/model/client_user.go new file mode 100644 index 0000000..183e842 --- /dev/null +++ b/godo/model/client_user.go @@ -0,0 +1,15 @@ +package model + +import "gorm.io/gorm" + +type ClientUser struct { + gorm.Model + ServerUrl string `json:"server_url"` + DiskId string `json:"disk_id"` + Username string `json:"username"` + Password string `json:"password"` +} + +func (*ClientUser) TableName() string { + return "client_user" +} diff --git a/godo/model/init.go b/godo/model/init.go new file mode 100644 index 0000000..de10475 --- /dev/null +++ b/godo/model/init.go @@ -0,0 +1,27 @@ +package model + +import ( + "godo/libs" + + _ "github.com/ncruces/go-sqlite3/embed" + "github.com/ncruces/go-sqlite3/gormlite" + "gorm.io/gorm" +) + +var Db *gorm.DB + +func InitDB() { + dbPath := libs.GetSystemDb() + db, err := gorm.Open(gormlite.Open(dbPath), &gorm.Config{}) + if err != nil { + return + } + Db = db + // 自动迁移模式 + db.AutoMigrate(&SysDisk{}) + // 初始化 SysDisk 记录 + initSysDisk(db) + db.AutoMigrate(&SysUser{}) + db.AutoMigrate(&ClientUser{}) + db.AutoMigrate(&ServerUser{}) +} diff --git a/godo/model/local_proxy.go b/godo/model/local_proxy.go new file mode 100644 index 0000000..48658aa --- /dev/null +++ b/godo/model/local_proxy.go @@ -0,0 +1,14 @@ +package model + +import "gorm.io/gorm" + +type LocalProxy struct { + gorm.Model + Port uint `json:"port"` + ProxyType string `json:"proxy_type"` + Domain string `json:"domain"` +} + +func (*LocalProxy) TableName() string { + return "local_proxy" +} diff --git a/godo/model/server_user.go b/godo/model/server_user.go new file mode 100644 index 0000000..22f016d --- /dev/null +++ b/godo/model/server_user.go @@ -0,0 +1,16 @@ +package model + +import "gorm.io/gorm" + +type ServerUser struct { + gorm.Model + DiskId string `json:"disk_id"` + AuthType string `json:"auth_type"` + Username string `json:"username"` + Password string `json:"password"` + Nickname string `json:"nickname"` +} + +func (*ServerUser) TableName() string { + return "server_user" +} diff --git a/godo/model/sys_disk.go b/godo/model/sys_disk.go new file mode 100644 index 0000000..d8ff240 --- /dev/null +++ b/godo/model/sys_disk.go @@ -0,0 +1,59 @@ +package model + +import ( + "fmt" + "godo/libs" + "os" + "path/filepath" + + "gorm.io/gorm" +) + +type SysDisk struct { + gorm.Model + Name string `json:"name"` + Disk string `json:"disk" gorm:"unique"` + Size int64 `json:"size"` + Type uint `json:"type"` //0C-E本地 1nasserver 2nasclient 3webdavserver 4webdavclient 5F分享 6B回收站 + Path string `json:"path"` + Status uint `json:"status"` +} + +func (*SysDisk) TableName() string { + return "sys_disk" +} +func initSysDisk(db *gorm.DB) { + var count int64 + db.Model(&SysDisk{}).Count(&count) + basePath, err := libs.GetOsDir() + if err != nil { + basePath, _ = os.Getwd() + } + if count == 0 { + disks := []SysDisk{ + {Disk: "B", Name: "回收站", Size: 0, Path: filepath.Join(basePath, "B"), Type: 6, Status: 1}, + {Disk: "C", Name: "系统", Size: 0, Path: filepath.Join(basePath, "C"), Type: 0, Status: 1}, + {Disk: "D", Name: "文档", Size: 0, Path: filepath.Join(basePath, "D"), Type: 0, Status: 1}, + {Disk: "E", Name: "办公", Size: 0, Path: filepath.Join(basePath, "E"), Type: 0, Status: 1}, + } + db.Create(&disks) + fmt.Println("Initialized A-Z disks") + } +} + +// BeforeDelete 钩子 +func (sd *SysDisk) BeforeDelete(tx *gorm.DB) (err error) { + // 不允许删除的磁盘列表 + nonDeletableDisks := map[string]struct{}{ + "B": {}, + "C": {}, + "D": {}, + "E": {}, + "F": {}, + } + + if _, exists := nonDeletableDisks[sd.Disk]; exists { + return fmt.Errorf("disk %s cannot be deleted", sd.Disk) + } + return nil +} diff --git a/godo/model/sys_user.go b/godo/model/sys_user.go new file mode 100644 index 0000000..1cdc793 --- /dev/null +++ b/godo/model/sys_user.go @@ -0,0 +1,13 @@ +package model + +import "gorm.io/gorm" + +type SysUser struct { + gorm.Model + Username string `json:"username"` + Password string `json:"password"` +} + +func (*SysUser) TableName() string { + return "sys_user" +} diff --git a/godo/sys/frpc.go b/godo/sys/frpc.go new file mode 100644 index 0000000..b1add0e --- /dev/null +++ b/godo/sys/frpc.go @@ -0,0 +1,285 @@ +package sys + +import ( + "fmt" + "strings" +) + +// FrpConfig 结构体用于存储 FRP 配置 +type FrpConfig struct { + ServerAddr string + ServerPort int + AuthMethod string + AuthToken string + User string + MetaToken string + TransportHeartbeatInterval int + TransportHeartbeatTimeout int + LogLevel string + LogMaxDays int + WebPort int + TlsConfigEnable bool + TlsConfigCertFile string + TlsConfigKeyFile string + TlsConfigTrustedCaFile string + TlsConfigServerName string + ProxyConfigEnable bool + ProxyConfigProxyUrl string +} + +// Proxy 结构体用于存储代理配置 +type Proxy struct { + Name string + Type string + LocalIp string + LocalPort int + RemotePort int + CustomDomains []string + Subdomain string + BasicAuth bool + HttpUser string + HttpPassword string + StcpModel string + ServerName string + BindAddr string + BindPort int + FallbackTo string + FallbackTimeoutMs int + SecretKey string +} + +// isRangePort 检查端口是否为范围端口 +func isRangePort(proxy Proxy) bool { + // 这里假设范围端口的判断逻辑 + // 你可以根据实际情况调整 + return strings.Contains(proxy.Name, ":") +} + +// GenFrpcIniConfig 生成 FRP 配置文件内容 +func GenFrpcIniConfig(config FrpConfig, proxys []Proxy) string { + var proxyIni []string + + for _, m := range proxys { + rangePort := isRangePort(m) + ini := fmt.Sprintf("[%s%s]\ntype = \"%s\"\n", + func() string { + if rangePort { + return "range:" + } + return "" + }(), + m.Name, + m.Type, + ) + + switch m.Type { + case "tcp", "udp": + ini += fmt.Sprintf(` +localIP = "%s" +localPort = %d +remotePort = %d +`, + m.LocalIp, + m.LocalPort, + m.RemotePort, + ) + case "http", "https": + ini += fmt.Sprintf(` +localIP = "%s" +localPort = %d +`, + m.LocalIp, + m.LocalPort, + ) + + if len(m.CustomDomains) > 0 { + ini += fmt.Sprintf(`custom_domains = [%s] +`, + strings.Join(m.CustomDomains, ","), + ) + } + + if m.Subdomain != "" { + ini += fmt.Sprintf(`subdomain="%s" +`, + m.Subdomain, + ) + } + if m.BasicAuth { + ini += fmt.Sprintf(` +httpUser = "%s" +httpPassword = "%s" +`, + m.HttpUser, + m.HttpPassword, + ) + } + case "stcp", "xtcp", "sudp": + if m.StcpModel == "visitors" { + // 访问者 + ini += fmt.Sprintf(` +role = visitor +serverName = "%s" +bindAddr = "%s" +bindPort = %d +`, + m.ServerName, + m.BindAddr, + m.BindPort, + ) + if m.FallbackTo != "" { + ini += fmt.Sprintf(` +fallbackTo = %s +fallbackTimeoutMs = %d +`, + m.FallbackTo, + m.FallbackTimeoutMs, + ) + } + } else if m.StcpModel == "visited" { + // 被访问者 + ini += fmt.Sprintf(` +localIP = "%s" +localPort = %d +`, + m.LocalIp, + m.LocalPort, + ) + } + ini += fmt.Sprintf(` +sk="%s" +`, + m.SecretKey, + ) + default: + // 默认情况不做处理 + } + + proxyIni = append(proxyIni, ini) + } + + ini := fmt.Sprintf(`[common] +serverAddr = %s +serverPort = %d +%s +%s +%s +%s +logFile = "frpc.log" +logLevel = %s +logMaxDays = %d +adminAddr = 127.0.0.1 +adminPort = %d +tlsEnable = %t +%s +%s +%s +%s +%s +`, + config.ServerAddr, + config.ServerPort, + func() string { + if config.AuthMethod == "token" { + return fmt.Sprintf(` +authenticationMethod = %s +token = %s +`, + config.AuthMethod, + config.AuthToken, + ) + } + return "" + }(), + func() string { + if config.AuthMethod == "multiuser" { + return fmt.Sprintf(` +user = %s +metaToken = %s +`, + config.User, + config.MetaToken, + ) + } + return "" + }(), + func() string { + if config.TransportHeartbeatInterval > 0 { + return fmt.Sprintf(` +heartbeatInterval = %d +`, + config.TransportHeartbeatInterval, + ) + } + return "" + }(), + func() string { + if config.TransportHeartbeatTimeout > 0 { + return fmt.Sprintf(` +heartbeatTimeout = %d +`, + config.TransportHeartbeatTimeout, + ) + } + return "" + }(), + config.LogLevel, + config.LogMaxDays, + config.WebPort, + config.TlsConfigEnable, + func() string { + if config.TlsConfigEnable && config.TlsConfigCertFile != "" { + return fmt.Sprintf(` +tlsCertFile = %s +`, + config.TlsConfigCertFile, + ) + } + return "" + }(), + func() string { + if config.TlsConfigEnable && config.TlsConfigKeyFile != "" { + return fmt.Sprintf(` +tlsKeyFile = %s +`, + config.TlsConfigKeyFile, + ) + } + return "" + }(), + func() string { + if config.TlsConfigEnable && config.TlsConfigTrustedCaFile != "" { + return fmt.Sprintf(` +tlsTrustedCaFile = %s +`, + config.TlsConfigTrustedCaFile, + ) + } + return "" + }(), + func() string { + if config.TlsConfigEnable && config.TlsConfigServerName != "" { + return fmt.Sprintf(` +tlsServerName = %s +`, + config.TlsConfigServerName, + ) + } + return "" + }(), + func() string { + if config.ProxyConfigEnable { + return fmt.Sprintf(` +httpProxy = "%s" +`, + config.ProxyConfigProxyUrl, + ) + } + return "" + }(), + ) + + ini += strings.Join(proxyIni, "") + + return ini +}