feat: 初始提交 - Code Server Bridge完整实现
- OAuth认证系统(Gitea + Lua扩展) - Git自动化操作(本地/SSH远程) - 实时进度WebSocket推送 - 现代化Tab界面UI - Cobra CLI命令行(init/version/serve) - 完整构建系统(Makefile + Taskfile) - UPX压缩支持(体积减少70%)
This commit is contained in:
129
internal/config/config.go
Normal file
129
internal/config/config.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"cs-bridge/internal/consts"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
globalViper *viper.Viper
|
||||
globalViperOnce sync.Once
|
||||
configMu sync.RWMutex
|
||||
cachedConfig *Config
|
||||
)
|
||||
|
||||
// NewConfig 初始化配置
|
||||
func NewConfig() *Config {
|
||||
globalViperOnce.Do(func() {
|
||||
globalViper = viper.New()
|
||||
})
|
||||
|
||||
configPath := loadConfigFile()
|
||||
if configPath == "" {
|
||||
log.Println("⚠️ 未找到配置文件,使用默认配置并导出为 config.yaml")
|
||||
cachedConfig = DefaultConfig()
|
||||
exportConfig(cachedConfig, "yaml")
|
||||
return cachedConfig
|
||||
}
|
||||
|
||||
conf, err := parseConfig()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 解析配置失败,使用默认配置并导出为 config.yaml: %v", err)
|
||||
conf = DefaultConfig()
|
||||
exportConfig(conf, "yaml")
|
||||
}
|
||||
|
||||
cachedConfig = conf
|
||||
return conf
|
||||
}
|
||||
|
||||
// loadConfigFile 加载配置文件
|
||||
func loadConfigFile() string {
|
||||
files := []string{
|
||||
fmt.Sprintf("%s.yaml", consts.ConfigName),
|
||||
fmt.Sprintf("%s.yml", consts.ConfigName),
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
ext := filepath.Ext(file)[1:]
|
||||
globalViper.SetConfigFile(file)
|
||||
globalViper.SetConfigType(ext)
|
||||
globalViper.AddConfigPath(".")
|
||||
|
||||
if err := globalViper.ReadInConfig(); err != nil {
|
||||
log.Printf("⚠️ 读取配置文件 %s 失败: %v", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("✅ 成功加载配置文件: %s", file)
|
||||
return file
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// parseConfig 将配置解析为结构体
|
||||
func parseConfig() (*Config, error) {
|
||||
configMu.Lock()
|
||||
defer configMu.Unlock()
|
||||
|
||||
var conf Config
|
||||
if err := globalViper.Unmarshal(&conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
// exportConfig 导出配置文件(默认保存为 config.yaml)
|
||||
func exportConfig(conf *Config, format string) {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
|
||||
switch format {
|
||||
case "json":
|
||||
data, err = json.MarshalIndent(conf, "", " ")
|
||||
case "yaml", "yml", "":
|
||||
format = "yaml"
|
||||
data, err = yaml.Marshal(conf)
|
||||
default:
|
||||
log.Printf("⚠️ 不支持的导出格式: %s", format)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 导出 %s 格式配置失败: %v", format, err)
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s.%s", consts.ConfigName, format)
|
||||
if err := os.WriteFile(filename, data, 0644); err != nil {
|
||||
log.Printf("⚠️ 写入配置文件 %s 失败: %v", filename, err)
|
||||
} else {
|
||||
log.Printf("✅ 已导出配置文件: %s", filename)
|
||||
}
|
||||
|
||||
log.Printf("📋 当前 %s 配置:\n%s", format, data)
|
||||
}
|
||||
|
||||
// GetConfig 获取当前配置(线程安全)
|
||||
func GetConfig() *Config {
|
||||
configMu.RLock()
|
||||
defer configMu.RUnlock()
|
||||
|
||||
if cachedConfig == nil {
|
||||
return DefaultConfig()
|
||||
}
|
||||
conf := *cachedConfig
|
||||
return &conf
|
||||
}
|
||||
116
internal/config/type.go
Normal file
116
internal/config/type.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"cs-bridge/internal/consts"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Debug bool `mapstructure:"debug"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Level string `mapstructure:"level" yaml:"level"`
|
||||
Filepath string `mapstructure:"filepath" yaml:"filepath"`
|
||||
MaxSizeMB int `mapstructure:"max_size_mb" yaml:"max_size_mb"` // 单个日志文件最大(MB)
|
||||
MaxAgeDay int `mapstructure:"max_age_day" yaml:"max_age_day"` // 日志文件最大保存天数
|
||||
Backups int `mapstructure:"backups" yaml:"backups"` // 保留的旧文件个数
|
||||
Compress bool `mapstructure:"compress" yaml:"compress"` // 是否压缩
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
SqliteDbPath string `mapstructure:"sqlite_db_path" yaml:"sqlite_db_path"`
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
TokenTTL time.Duration `mapstructure:"token_ttl" yaml:"token_ttl"`
|
||||
WSTTL time.Duration `mapstructure:"ws_ttl" yaml:"ws_ttl"`
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
Name string `mapstructure:"name" yaml:"name"`
|
||||
Type string `mapstructure:"type" yaml:"type"`
|
||||
ClientID string `mapstructure:"client_id" yaml:"client_id"`
|
||||
ClientSecret string `mapstructure:"client_secret" yaml:"client_secret"`
|
||||
BaseURL string `mapstructure:"base_url" yaml:"base_url"`
|
||||
AuthorizeURL string `mapstructure:"authorize_url" yaml:"authorize_url"`
|
||||
TokenURL string `mapstructure:"token_url" yaml:"token_url"`
|
||||
UserURL string `mapstructure:"user_url" yaml:"user_url"`
|
||||
}
|
||||
|
||||
type OAuth struct {
|
||||
BaseURL string `mapstructure:"base_url" yaml:"base_url"`
|
||||
Providers []Provider `mapstructure:"providers" yaml:"providers"`
|
||||
}
|
||||
|
||||
type CodeServer struct {
|
||||
BaseURL string `mapstructure:"base_url" yaml:"base_url"`
|
||||
WorkspaceRoot string `mapstructure:"workspace_root" yaml:"workspace_root"` // 容器内路径,传递给code-server
|
||||
// SSH配置 - 用于远程执行git操作
|
||||
SSHHost string `mapstructure:"ssh_host" yaml:"ssh_host"` // SSH服务器地址
|
||||
SSHPort int `mapstructure:"ssh_port" yaml:"ssh_port"` // SSH端口,默认22
|
||||
SSHUser string `mapstructure:"ssh_user" yaml:"ssh_user"` // SSH用户名(专用账号)
|
||||
SSHKeyPath string `mapstructure:"ssh_key_path" yaml:"ssh_key_path"` // SSH私钥路径
|
||||
SSHWorkspaceRoot string `mapstructure:"ssh_workspace_root" yaml:"ssh_workspace_root"` // SSH服务器上的workspace路径(容器映射到宿主机的路径)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Server Server `mapstructure:"server" yaml:"server"`
|
||||
Log Log `mapstructure:"log" yaml:"log"`
|
||||
Database Database `mapstructure:"database" yaml:"database"`
|
||||
Secret Secret `mapstructure:"secret" yaml:"secret"`
|
||||
OAuth OAuth `mapstructure:"oauth" yaml:"oauth"`
|
||||
CodeServer CodeServer `mapstructure:"code_server" yaml:"code_server"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
logPath := filepath.Join(consts.DataDir, "log.log")
|
||||
dbPath := filepath.Join(consts.DataDir, "sqlite.db")
|
||||
return &Config{
|
||||
Server: Server{
|
||||
Debug: true,
|
||||
Port: 8080,
|
||||
},
|
||||
Log: Log{
|
||||
Level: "debug",
|
||||
Filepath: logPath,
|
||||
MaxSizeMB: 10,
|
||||
MaxAgeDay: 7,
|
||||
Backups: 3,
|
||||
Compress: true,
|
||||
},
|
||||
Database: Database{
|
||||
SqliteDbPath: dbPath,
|
||||
},
|
||||
Secret: Secret{
|
||||
TokenTTL: time.Millisecond * 600,
|
||||
WSTTL: time.Millisecond * 3600,
|
||||
},
|
||||
OAuth: OAuth{
|
||||
BaseURL: "http://localhost:8080",
|
||||
Providers: []Provider{
|
||||
{
|
||||
Name: "gitea",
|
||||
Type: "gitea",
|
||||
ClientID: "xxx",
|
||||
ClientSecret: "xxx",
|
||||
BaseURL: "https://xxx",
|
||||
AuthorizeURL: "/login/oauth/authorize",
|
||||
TokenURL: "/login/oauth/access_token",
|
||||
UserURL: "/api/v1/user",
|
||||
},
|
||||
},
|
||||
},
|
||||
CodeServer: CodeServer{
|
||||
BaseURL: "xxx",
|
||||
WorkspaceRoot: "/config/workspace", // 容器内路径
|
||||
SSHHost: "",
|
||||
SSHPort: 22,
|
||||
SSHUser: "git",
|
||||
SSHKeyPath: "",
|
||||
SSHWorkspaceRoot: "/root/code-server/data/workspace", // SSH服务器实际路径
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user