From 6baa62dc063098439a37ea14f00b4a8536e56faa Mon Sep 17 00:00:00 2001 From: micah Date: Sat, 28 Mar 2026 19:25:12 +0800 Subject: [PATCH] feat: init --- .gitignore | 28 +++++++++++ README.md | 0 cmd/acme_sh/acme_sh.go | 41 ++++++++++++++++ cmd/acme_sh/check.go | 47 ++++++++++++++++++ cmd/acme_sh/command.go | 44 +++++++++++++++++ cmd/acme_sh/read.go | 38 +++++++++++++++ cmd/cmd.go | 19 ++++++++ cmd/initialize/check.go | 25 ++++++++++ cmd/initialize/command.go | 18 +++++++ cmd/initialize/install.go | 18 +++++++ cmd/initialize/read.go | 16 ++++++ cmd/nginx/check.go | 53 ++++++++++++++++++++ cmd/nginx/command.go | 49 +++++++++++++++++++ cmd/nginx/nginx.go | 100 ++++++++++++++++++++++++++++++++++++++ cmd/nginx/read.go | 46 ++++++++++++++++++ cmd/nginx/template.go | 97 ++++++++++++++++++++++++++++++++++++ cmd/test/test.go | 32 ++++++++++++ config/acme_sh.go | 8 +++ config/config.go | 94 +++++++++++++++++++++++++++++++++++ config/nginx.go | 12 +++++ go.mod | 21 ++++++++ go.sum | 30 ++++++++++++ main.go | 38 +++++++++++++++ pkg/env/shell.go | 17 +++++++ pkg/env/shell_test.go | 22 +++++++++ pkg/loggerx/logger.go | 48 ++++++++++++++++++ 26 files changed, 961 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cmd/acme_sh/acme_sh.go create mode 100644 cmd/acme_sh/check.go create mode 100644 cmd/acme_sh/command.go create mode 100644 cmd/acme_sh/read.go create mode 100644 cmd/cmd.go create mode 100644 cmd/initialize/check.go create mode 100644 cmd/initialize/command.go create mode 100644 cmd/initialize/install.go create mode 100644 cmd/initialize/read.go create mode 100644 cmd/nginx/check.go create mode 100644 cmd/nginx/command.go create mode 100644 cmd/nginx/nginx.go create mode 100644 cmd/nginx/read.go create mode 100644 cmd/nginx/template.go create mode 100644 cmd/test/test.go create mode 100644 config/acme_sh.go create mode 100644 config/config.go create mode 100644 config/nginx.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/env/shell.go create mode 100644 pkg/env/shell_test.go create mode 100644 pkg/loggerx/logger.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c4e876 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# ide +.idea +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +magic + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cmd/acme_sh/acme_sh.go b/cmd/acme_sh/acme_sh.go new file mode 100644 index 0000000..776bdbd --- /dev/null +++ b/cmd/acme_sh/acme_sh.go @@ -0,0 +1,41 @@ +package acme_sh + +import ( + "context" + "fmt" + "os/exec" + "strings" + + "gitea.micah.wiki/pandora/magic/config" + "gitea.micah.wiki/pandora/magic/pkg/env" +) + +func Run(_ context.Context, cmdPath string, conf *config.Config, current *config.AcmeSh, isNew bool) error { + var currentCmd string + domains := strings.Split(current.Domain, ",") + domainValue := "" + for _, domain := range domains { + if strings.Contains(domain, "*") { + domainValue = fmt.Sprintf("%s -d \"%s\"", domainValue, domain) + } else { + domainValue = fmt.Sprintf("%s -d %s", domainValue, domain) + } + } + if isNew { + currentCmd = fmt.Sprintf("%s --issue %s --dns %s --key-file %s/key.pem --fullchain-file %s/cert.pem --yes-I-know-dns-manual-mode-enough-go-ahead-please --server letsencrypt", + cmdPath, domainValue, current.DNSType, current.KeyPath, current.KeyPath) + } else { + currentCmd = fmt.Sprintf("%s --renew %s --dns %s --key-file %s/key.pem --fullchain-file %s/cert.pem --yes-I-know-dns-manual-mode-enough-go-ahead-please --force --server letsencrypt", + cmdPath, domainValue, current.DNSType, current.KeyPath, current.KeyPath) + } + + cmd := exec.Command(env.GetShellName(), "-c", fmt.Sprintf("source %s; %s", conf.ShellConfig, currentCmd)) + output, err := cmd.Output() + // 如果返回是没有错误,则直接成功。 + if err != nil { + return err + } + fmt.Println(fmt.Sprintf("acme.sh 创建成功\n%s", string(output))) + + return nil +} diff --git a/cmd/acme_sh/check.go b/cmd/acme_sh/check.go new file mode 100644 index 0000000..711f830 --- /dev/null +++ b/cmd/acme_sh/check.go @@ -0,0 +1,47 @@ +package acme_sh + +import ( + "context" + "fmt" + "os/exec" + "strings" + + "gitea.micah.wiki/pandora/magic/config" + "gitea.micah.wiki/pandora/magic/pkg/env" + "gitea.micah.wiki/pandora/magic/pkg/loggerx" +) + +// CheckAcmeSh 安装ache.sh 成功后返回安装路径,否则返回异常 +func CheckAcmeSh(ctx context.Context, conf *config.Config, current *config.AcmeSh) (string, error) { + shellConf := conf.ShellConfig + shellName := env.GetShellName() + if len(shellConf) == 0 { + tempConf, err := env.GetShellConfig() + if err != nil { + return "", err + } + if len(tempConf) == 0 { + return "", fmt.Errorf("未找到sh配置文件地址") + } + shellConf = tempConf + conf.ShellConfig = tempConf + } + cmd := exec.Command(shellName, "-c", fmt.Sprintf("source %s; ls ~/.acme.sh/acme.sh", shellConf)) + output, err := cmd.Output() + // 如果返回是没有错误,则直接成功。 + if err == nil { + dir := strings.TrimSpace(string(output)) + fmt.Println("acme.sh 路径为: " + dir) + loggerx.AcmeSh().Infof("acme.sh already installed. dir: %s", dir) + return dir, nil + } + + cmd = exec.Command(shellName, "-c", fmt.Sprintf("source %s; curl https://get.acme.sh | sh -s email=%s", shellConf, current.DNSEmail)) + output, err = cmd.Output() + // 如果返回是没有错误,则直接成功。 + if err != nil { + return "", err + } + fmt.Println(fmt.Sprintf("acme.sh 安装成功\n%s", string(output))) + return CheckAcmeSh(ctx, conf, current) +} diff --git a/cmd/acme_sh/command.go b/cmd/acme_sh/command.go new file mode 100644 index 0000000..24ef4ff --- /dev/null +++ b/cmd/acme_sh/command.go @@ -0,0 +1,44 @@ +package acme_sh + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "gitea.micah.wiki/pandora/magic/config" +) + +func AcmeSH() *cli.Command { + return &cli.Command{ + Name: "acme.sh", + Action: func(c *cli.Context) error { + conf := config.Get() + if len(conf.AcmeShs) == 0 { + return fmt.Errorf("未找到acme.sh的配置") + } + + current, err := ReadCurrentAcmeSh(c.Context, conf) + if err != nil { + return err + } + + cmdPath, err := CheckAcmeSh(c.Context, conf, current) + if err != nil { + return err + } + + newFlag, err := ReadIsNew(c.Context) + if err != nil { + return err + } + + err = Run(c.Context, cmdPath, conf, current, newFlag) + if err != nil { + return err + } + + _ = config.Save(conf) + return nil + }, + } +} diff --git a/cmd/acme_sh/read.go b/cmd/acme_sh/read.go new file mode 100644 index 0000000..331152d --- /dev/null +++ b/cmd/acme_sh/read.go @@ -0,0 +1,38 @@ +package acme_sh + +import ( + "context" + "fmt" + "strings" + + "gitea.micah.wiki/pandora/naive/pkg/stdinx" + + "gitea.micah.wiki/pandora/magic/config" +) + +func ReadIsNew(ctx context.Context) (bool, error) { + isNewFlag, err := stdinx.ReadStr(ctx, fmt.Sprintf("*** 新增 or 更新:(新增: Y; 更新: N;)")) + if err != nil { + return false, err + } + + if strings.EqualFold(isNewFlag, "y") { + return true, nil + } + + return false, nil +} + +func ReadCurrentAcmeSh(ctx context.Context, conf *config.Config) (*config.AcmeSh, error) { + total := len(conf.AcmeShs) + for i, acmeSh := range conf.AcmeShs { + check, err := stdinx.ReadStr(ctx, fmt.Sprintf("*** 请确认是否需要生成当前域名`%s`(%d/%d):(是: Y; 否: N;)", acmeSh.Domain, i+1, total)) + if err != nil { + return nil, err + } + if strings.EqualFold(check, "y") { + return acmeSh, nil + } + } + return nil, fmt.Errorf("未选择任何需要生成的域名,请编辑配置后,再运行") +} diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..e9931aa --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/urfave/cli/v2" + + acmesh "gitea.micah.wiki/pandora/magic/cmd/acme_sh" + "gitea.micah.wiki/pandora/magic/cmd/initialize" + "gitea.micah.wiki/pandora/magic/cmd/nginx" + "gitea.micah.wiki/pandora/magic/cmd/test" +) + +func GetList() []*cli.Command { + return []*cli.Command{ + test.Test(), + nginx.Nginx(), + acmesh.AcmeSH(), + initialize.Init(), + } +} diff --git a/cmd/initialize/check.go b/cmd/initialize/check.go new file mode 100644 index 0000000..4d3f143 --- /dev/null +++ b/cmd/initialize/check.go @@ -0,0 +1,25 @@ +package initialize + +import ( + "context" + "fmt" + "os/exec" +) + +func CheckHomebrew(ctx context.Context) error { + dir, err := exec.LookPath("brew") + if len(dir) > 0 { + fmt.Println("Homebrew 路径为: " + dir) + return nil + } + + install := ReadInstallHomebrew(ctx) + if install { + err = InstallHomebrew(ctx) + if err != nil { + return err + } + } + + return CheckHomebrew(ctx) +} diff --git a/cmd/initialize/command.go b/cmd/initialize/command.go new file mode 100644 index 0000000..24bb993 --- /dev/null +++ b/cmd/initialize/command.go @@ -0,0 +1,18 @@ +package initialize + +import ( + "github.com/urfave/cli/v2" +) + +func Init() *cli.Command { + return &cli.Command{ + Name: "init", + Action: func(c *cli.Context) error { + err := CheckHomebrew(c.Context) + if err != nil { + return err + } + return nil + }, + } +} diff --git a/cmd/initialize/install.go b/cmd/initialize/install.go new file mode 100644 index 0000000..fc56397 --- /dev/null +++ b/cmd/initialize/install.go @@ -0,0 +1,18 @@ +package initialize + +import ( + "context" + "fmt" + "os/exec" +) + +func InstallHomebrew(_ context.Context) error { + cmd := exec.Command("/bin/bash", "-c", "\"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"") + output, err := cmd.Output() + // 如果返回是没有错误,则直接成功。 + fmt.Println(string(output)) + if err != nil { + return err + } + return nil +} diff --git a/cmd/initialize/read.go b/cmd/initialize/read.go new file mode 100644 index 0000000..cf7a8ae --- /dev/null +++ b/cmd/initialize/read.go @@ -0,0 +1,16 @@ +package initialize + +import ( + "context" + "strings" + + "gitea.micah.wiki/pandora/naive/pkg/stdinx" +) + +func ReadInstallHomebrew(ctx context.Context) bool { + restartFlag, err := stdinx.ReadStr(ctx, "*** 是否需要安装Homebrew(确认:Y or Yes,按其他任意键不重启)") + if err != nil { + return false + } + return strings.EqualFold(restartFlag, "yes") || strings.EqualFold(restartFlag, "y") +} diff --git a/cmd/nginx/check.go b/cmd/nginx/check.go new file mode 100644 index 0000000..0035881 --- /dev/null +++ b/cmd/nginx/check.go @@ -0,0 +1,53 @@ +package nginx + +import ( + "context" + "fmt" + "os/exec" + "strings" + + "gitea.micah.wiki/pandora/naive/pkg/filex" + + "gitea.micah.wiki/pandora/magic/config" +) + +func Check(_ context.Context, nginx *config.Nginx) (bool, error) { + _, err := exec.LookPath("nginx") + if err != nil { + return false, err + } + + if b, err := filex.Exists(nginx.RootPath); err != nil { + return false, err + } else if !b { + return false, fmt.Errorf("nginx.RootPath: %s, 不存在", nginx.RootPath) + } + + if b, err := filex.Exists(nginx.LogPath); err != nil { + return false, err + } else if !b { + return false, fmt.Errorf("nginx.LogPath: %s, 不存在", nginx.LogPath) + } + + if b, err := filex.Exists(nginx.SSLPath); err != nil { + return false, err + } else if !b { + return false, fmt.Errorf("nginx.SSLPath: %s, 不存在", nginx.SSLPath) + } + + if b, err := filex.Exists(nginx.ConfigPath); err != nil { + return false, err + } else if !b { + return false, fmt.Errorf("nginx.ConfigPath: %s, 不存在", nginx.ConfigPath) + } + + if len(nginx.Domain) == 0 { + return false, fmt.Errorf("nginx.Domain 为空") + } + + if len(nginx.Proxy) == 0 || !strings.HasPrefix(nginx.Proxy, "http") { + return false, fmt.Errorf("nginx.Proxy 为空 或者 不是以http开头") + } + + return true, nil +} diff --git a/cmd/nginx/command.go b/cmd/nginx/command.go new file mode 100644 index 0000000..a898c1d --- /dev/null +++ b/cmd/nginx/command.go @@ -0,0 +1,49 @@ +package nginx + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "gitea.micah.wiki/pandora/magic/config" +) + +func Nginx() *cli.Command { + return &cli.Command{ + Name: "nginx", + Action: func(c *cli.Context) error { + conf := config.Get() + if len(conf.Nginx) == 0 { + return fmt.Errorf("未找到nginx的配置") + } + + current, err := ReadCurrent(c.Context, conf) + if err != nil { + return err + } + + exist, err := Check(c.Context, current) + if err != nil { + return err + } + if !exist { + return fmt.Errorf("在 $PATH 中未找到nginx,请自行安装,并配置在 $PATH 中") + } + + err = Create(c.Context, current) + if err != nil { + return err + } + + restart := ReadRestart(c.Context) + if restart { + err = ReStart(c.Context) + if err != nil { + return err + } + } + + return nil + }, + } +} diff --git a/cmd/nginx/nginx.go b/cmd/nginx/nginx.go new file mode 100644 index 0000000..6d4801d --- /dev/null +++ b/cmd/nginx/nginx.go @@ -0,0 +1,100 @@ +package nginx + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" + + "gitea.micah.wiki/pandora/naive/pkg/filex" + + "gitea.micah.wiki/pandora/magic/config" + "gitea.micah.wiki/pandora/magic/pkg/loggerx" +) + +func Create(ctx context.Context, current *config.Nginx) error { + file := fmt.Sprintf("%s/%s", current.ConfigPath, current.Domain) + isExist, err := filex.Exists(file) + if err != nil { + loggerx.Nginx().Errorf("config file Exists error, err: %+v", err) + return err + } + if isExist { + err = os.Remove(file) + if err != nil { + loggerx.Nginx().Errorf("config file Remove error, err: %+v", err) + return err + } + } + f, err := filex.CreateIfNotExists(file) + if err != nil { + loggerx.Nginx().Errorf("CreateIfNotExists error, err: %+v", err) + return err + } + defer func() { _ = f.Close() }() + _, err = f.Write([]byte(GetInfo(ctx, current))) + if err != nil { + loggerx.Nginx().Errorf("Write config error, err: %+v", err) + return err + } + + if strings.Contains(current.ConfigPath, "sites-available") { + lnFile := fmt.Sprintf("%s/../sites-enabled/%s", current.ConfigPath, current.Domain) + isExist, err = filex.Exists(lnFile) + if err != nil { + loggerx.Nginx().Errorf("ln config file Exists error, err: %+v", err) + return err + } + + if isExist { + err = os.Remove(lnFile) + if err != nil { + loggerx.Nginx().Errorf("ln config file Remove error, err: %+v", err) + return err + } + } + cmd := exec.Command("ln", "-s", file, lnFile) + // 执行命令并捕获输出 + _, err = cmd.CombinedOutput() + if err != nil { + loggerx.Nginx().Errorf("ln config error, err: %+v", err) + // 若执行命令出错,打印错误信息和命令输出 + return err + } + } + return nil +} + +func ReStart(_ context.Context) error { + _, err := exec.LookPath("brew") + if err == nil { + // 如果存在brew 则按照 brew运行 + cmd := exec.Command("brew", "services", "restart", "nginx") + // 执行命令并捕获输出 + output, err := cmd.CombinedOutput() + if err != nil { + // 若执行命令出错,打印错误信息和命令输出 + return err + } + fmt.Println(string(output)) + return nil + } + cmd := exec.Command("systemctl", "restart", "nginx") + // 执行命令并捕获输出 + output, err := cmd.CombinedOutput() + if err == nil { + fmt.Println(string(output)) + // 若执行命令出错,打印错误信息和命令输出 + return nil + } + cmd = exec.Command("nginx", "-s", "reload") + // 执行命令并捕获输出 + output, err = cmd.CombinedOutput() + if err != nil { + // 若执行命令出错,打印错误信息和命令输出 + return err + } + fmt.Println(string(output)) + return nil +} diff --git a/cmd/nginx/read.go b/cmd/nginx/read.go new file mode 100644 index 0000000..02600fe --- /dev/null +++ b/cmd/nginx/read.go @@ -0,0 +1,46 @@ +package nginx + +import ( + "context" + "fmt" + "strings" + + "gitea.micah.wiki/pandora/naive/pkg/stdinx" + + "gitea.micah.wiki/pandora/magic/config" +) + +//func ReadIsNew(ctx context.Context) (bool, error) { +// isNewFlag, err := stdinx.ReadStr(ctx, fmt.Sprintf("*** 新增 or 更新:(新增: Y; 更新: N;)")) +// if err != nil { +// return false, err +// } +// +// if strings.EqualFold(isNewFlag, "y") { +// return true, nil +// } +// +// return false, nil +//} + +func ReadCurrent(ctx context.Context, conf *config.Config) (*config.Nginx, error) { + total := len(conf.Nginx) + for i, nginx := range conf.Nginx { + check, err := stdinx.ReadStr(ctx, fmt.Sprintf("*** 请确认是否需要生成当前域名`%s`(%d/%d):(是: Y; 否: N;)", nginx.Domain, i+1, total)) + if err != nil { + return nil, err + } + if strings.EqualFold(check, "y") { + return nginx, nil + } + } + return nil, fmt.Errorf("未选择任何需要生成的域名,请编辑配置后,再运行") +} + +func ReadRestart(ctx context.Context) bool { + restartFlag, err := stdinx.ReadStr(ctx, "*** 是否需要重启nginx(确认:Y or Yes,按其他任意键不重启)") + if err != nil { + return false + } + return strings.EqualFold(restartFlag, "yes") || strings.EqualFold(restartFlag, "y") +} diff --git a/cmd/nginx/template.go b/cmd/nginx/template.go new file mode 100644 index 0000000..cad8d65 --- /dev/null +++ b/cmd/nginx/template.go @@ -0,0 +1,97 @@ +package nginx + +import ( + "context" + "fmt" + "strings" + + "gitea.micah.wiki/pandora/magic/config" +) + +var ( + domainKey = "${Domain}" + logPathKey = "${LogPath}" + sslPathKey = "${SSLPath}" + certPathKey = "${CertPath}" + rootPathKey = "${RootPath}" + proxyKey = "${Proxy}" + + httpTemplate = ` +server { + listen 80; + server_name ${Domain}; + + rewrite ^(.*)$ https://${server_name}$1 permanent; +} +` + httpsTemplate = ` +server { + listen 443 ssl; + server_name ${Domain}; + error_log ${LogPath}/${Domain}.error.log; + access_log ${LogPath}/${Domain}.access.log main; + + + ssl_certificate ${SSLPath}/${CertPath}cert.pem; + ssl_certificate_key ${SSLPath}/${CertPath}key.pem; + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + root ${RootPath}; + location / { + proxy_pass ${Proxy}; + + #开启websocket + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Host $server_name; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + # proxy_set_header X-Forwarded-Ssl on; + proxy_set_header Accept-Encoding ""; + + proxy_connect_timeout 86400; + proxy_send_timeout 86400; + proxy_read_timeout 86400; + send_timeout 86400; + + client_max_body_size 100g; + client_body_buffer_size 128k; + proxy_buffer_size 4k; + proxy_buffers 4 32k; + proxy_busy_buffers_size 64k; + proxy_temp_file_write_size 64k; + } + location = /robots.txt {} +} +` +) + +func GetInfo(ctx context.Context, nginx *config.Nginx) string { + nginxConf := GetHTTPsInfo(ctx, nginx.Domain, nginx.RootPath, nginx.LogPath, nginx.SSLPath, nginx.CertPrefix, nginx.Proxy) + if nginx.HTTPDirect { + nginxConf = fmt.Sprintf("%s\n%s", nginxConf, GetHTTPInfo(ctx, nginx.Domain)) + } + return nginxConf +} + +func GetHTTPInfo(_ context.Context, domain string) string { + return strings.ReplaceAll(httpTemplate, domainKey, domain) +} + +func GetHTTPsInfo(_ context.Context, domain, rootPath, logPath, sslPath, certPrefix, proxy string) string { + info := strings.ReplaceAll(httpsTemplate, domainKey, domain) + info = strings.ReplaceAll(info, rootPathKey, rootPath) + info = strings.ReplaceAll(info, logPathKey, logPath) + info = strings.ReplaceAll(info, sslPathKey, sslPath) + info = strings.ReplaceAll(info, certPathKey, certPrefix) + info = strings.ReplaceAll(info, proxyKey, proxy) + return info +} diff --git a/cmd/test/test.go b/cmd/test/test.go new file mode 100644 index 0000000..89f51ed --- /dev/null +++ b/cmd/test/test.go @@ -0,0 +1,32 @@ +package test + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "gitea.micah.wiki/pandora/naive/pkg/pointx" + "gitea.micah.wiki/pandora/naive/pkg/stdinx" +) + +func Test() *cli.Command { + return &cli.Command{ + Name: "test", + Action: func(c *cli.Context) error { + // 假设这是你的原始数据 + data := []string{"apple", "banana", "cherry", "date", "elderberry", "fig"} + + // 打印原始数据 + tips := "原始数据:\n" + for _, item := range data { + tips = fmt.Sprintf("%s%s\n", tips, item) + } + value, err := stdinx.ReadStr(c.Context, tips) + if err != nil { + return err + } + fmt.Println(pointx.Prettify(value)) + return nil + }, + } +} diff --git a/config/acme_sh.go b/config/acme_sh.go new file mode 100644 index 0000000..12f0a31 --- /dev/null +++ b/config/acme_sh.go @@ -0,0 +1,8 @@ +package config + +type AcmeSh struct { + Domain string `yaml:"domain" toml:"domain" json:"domain"` + KeyPath string `yaml:"key_path" toml:"key_path" json:"key_path"` + DNSType string `yaml:"dns_type" toml:"dns_type" json:"dns_type"` + DNSEmail string `yaml:"dns_email" toml:"dns_email" json:"dns_email"` +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..6f4648e --- /dev/null +++ b/config/config.go @@ -0,0 +1,94 @@ +package config + +import ( + "fmt" + "os" + + "gitea.micah.wiki/pandora/naive/pkg/filex" +) + +type Config struct { + ShellConfig string `yaml:"shell_config" toml:"shell_config" json:"shell_config"` + Nginx []*Nginx `yaml:"nginx" toml:"nginx" json:"nginx"` + AcmeShs []*AcmeSh `yaml:"acme_sh" toml:"acme_sh" json:"acme_sh"` +} + +var ( + config *Config + configTypes = []string{"yaml", "toml", "json"} + fileName = "" + configType = "" +) + +func init() { + homeDir, _ := os.UserHomeDir() + for _, cType := range configTypes { + name := fmt.Sprintf("%s/.magic/config.%s", homeDir, cType) + exist, err := filex.Exists(name) + if err != nil { + panic(err) + } + if exist { + fileName = name + configType = cType + break + } + } + conf, err := parseByType() + if err != nil { + panic(err) + } + config = conf +} + +func parseByType() (*Config, error) { + conf := &Config{} + switch configType { + case "yaml": + err := filex.ParseYAML(fileName, conf) + if err != nil { + return nil, err + } + case "toml": + err := filex.ParseTOML(fileName, conf) + if err != nil { + return nil, err + } + case "json": + err := filex.ParseJSON(fileName, conf) + if err != nil { + return nil, err + } + } + return conf, nil +} + +func Save(conf *Config) error { + if configType == "" { + configType = "yaml" + homeDir, _ := os.UserHomeDir() + fileName = fmt.Sprintf("%s/.magic/config.%s", homeDir, configType) + } + switch configType { + case "yaml": + err := filex.GenerateYAML(fileName, conf) + if err != nil { + return err + } + case "toml": + err := filex.ParseTOML(fileName, conf) + if err != nil { + return err + } + case "json": + err := filex.ParseJSON(fileName, conf) + if err != nil { + return err + } + } + return nil +} + +func Get() *Config { + return config +} diff --git a/config/nginx.go b/config/nginx.go new file mode 100644 index 0000000..0be18ef --- /dev/null +++ b/config/nginx.go @@ -0,0 +1,12 @@ +package config + +type Nginx struct { + Domain string `yaml:"domain" toml:"domain" json:"domain"` + RootPath string `yaml:"root_path" toml:"root_path" json:"root_path"` + LogPath string `yaml:"log_path" toml:"log_path" json:"log_path"` + SSLPath string `yaml:"ssl_path" toml:"ssl_path" json:"ssl_path"` + CertPrefix string `yaml:"cert_prefix" toml:"cert_prefix" json:"cert_prefix"` + Proxy string `yaml:"proxy" toml:"proxy" json:"proxy"` + HTTPDirect bool `yaml:"http_direct" toml:"http_direct" json:"http_direct"` + ConfigPath string `yaml:"config_path" toml:"config_path" json:"config_path"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..74ac500 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module gitea.micah.wiki/pandora/magic + +go 1.22 + +toolchain go1.24.12 + +require ( + gitea.micah.wiki/pandora/naive v1.0.2-0.20250310144841-5ab2bac644a3 + github.com/urfave/cli/v2 v2.27.7 + github.com/urfave/cli/v3 v3.6.2 + go.uber.org/zap v1.27.1 +) + +require ( + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + go.uber.org/multierr v1.11.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..95be4bf --- /dev/null +++ b/go.sum @@ -0,0 +1,30 @@ +gitea.micah.wiki/pandora/naive v1.0.2-0.20250310144841-5ab2bac644a3 h1:9ea1+oQeE3R4krLsgqdJ2TfPM8ZK0+3kQ7CIIBdudDc= +gitea.micah.wiki/pandora/naive v1.0.2-0.20250310144841-5ab2bac644a3/go.mod h1:ebLisq9JH4+IP/qqSsozxr7vkIM82pyHyGcszNX51yg= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= +github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8= +github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5686c24 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v3" + + "gitea.micah.wiki/pandora/magic/cmd" + "gitea.micah.wiki/pandora/magic/config" + _ "gitea.micah.wiki/pandora/magic/config" + "gitea.micah.wiki/pandora/magic/pkg/loggerx" +) + +var VERSION = "v1.0.0" + +func main() { + // 刷新日志,当程序退出时,把在缓存中的内容刷到日志中 + defer func() { + loggerx.Sync() + }() + + app := cli.NewApp() + app.Name = "magic" + app.Version = VERSION + app.Usage = "A command line tool for tools" + app.Authors = append(app.Authors, &cli.Author{ + Name: "micah", + Email: "micah.shi@gmail.com", + }) + app.Commands = cmd.GetList() + + if err := app.Run(os.Args); err != nil { + fmt.Println(err) + return + } + _ = config.Save(config.Get()) +} diff --git a/pkg/env/shell.go b/pkg/env/shell.go new file mode 100644 index 0000000..5b355af --- /dev/null +++ b/pkg/env/shell.go @@ -0,0 +1,17 @@ +package env + +import ( + "os" + + "gitea.micah.wiki/pandora/naive/pkg/env" +) + +func GetShellName() string { + // 获取 SHELL 环境变量 + shell := os.Getenv("SHELL") + return shell +} + +func GetShellConfig() (string, error) { + return env.ShellConfig() +} diff --git a/pkg/env/shell_test.go b/pkg/env/shell_test.go new file mode 100644 index 0000000..5013221 --- /dev/null +++ b/pkg/env/shell_test.go @@ -0,0 +1,22 @@ +package env + +import "testing" + +func TestGetShellName(t *testing.T) { + tests := []struct { + name string + want string + }{ + { + name: "", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetShellName(); got != tt.want { + t.Errorf("GetShellName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/loggerx/logger.go b/pkg/loggerx/logger.go new file mode 100644 index 0000000..e0ccb2d --- /dev/null +++ b/pkg/loggerx/logger.go @@ -0,0 +1,48 @@ +package loggerx + +import ( + "fmt" + + "gitea.micah.wiki/pandora/naive/pkg/filex" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + nameNginx = "nginx" + nameAcmeSh = "acme.sh" + logger *zap.SugaredLogger +) + +func Sync() { + if logger != nil { + _ = logger.Sync() + } +} + +func Nginx() *zap.SugaredLogger { + return getLogger(nameNginx) +} + +func AcmeSh() *zap.SugaredLogger { + return getLogger(nameAcmeSh) +} + +func getLogger(name string) *zap.SugaredLogger { + if logger != nil { + return logger.Named(name) + } + homeDir, err := filex.HomeDir() + if err != nil { + panic(err) + } + loggerFile := fmt.Sprintf("%s/.magic/logs.log", homeDir) + file, err := filex.CreateIfNotExists(loggerFile) + if err != nil { + panic(err) + } + writeSyncer := zapcore.AddSync(file) + core := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writeSyncer, zapcore.DebugLevel) + logger = zap.New(core).Sugar() + return logger.Named(name) +}