Compare commits
3 Commits
63f3ac828f
...
a1defc9aff
Author | SHA1 | Date | |
---|---|---|---|
a1defc9aff | |||
7e15185359 | |||
7150dbf22a |
8
go.mod
8
go.mod
|
@ -6,3 +6,11 @@ require (
|
||||||
github.com/go-acme/lego/v4 v4.19.2
|
github.com/go-acme/lego/v4 v4.19.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||||
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
|
golang.org/x/net v0.29.0 // indirect
|
||||||
|
golang.org/x/text v0.18.0 // indirect
|
||||||
|
)
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -1,11 +1,23 @@
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y=
|
github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y=
|
||||||
github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ=
|
github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||||
|
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||||
|
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||||
|
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||||
|
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||||
|
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
229
main.go
229
main.go
|
@ -3,17 +3,24 @@ package main
|
||||||
import (
|
import (
|
||||||
"acme-client/src"
|
"acme-client/src"
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"log"
|
||||||
"fmt"
|
|
||||||
"github.com/go-acme/lego/v4/log"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
validConf()
|
||||||
|
// 获取指令, 如果没有则提示添加
|
||||||
|
args := os.Args
|
||||||
|
if len(args) < 2 {
|
||||||
|
log.Println("请输入命令; 如: add")
|
||||||
|
log.Println("或者输入 help 查看帮助")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src.OnCommand()
|
||||||
|
}
|
||||||
|
|
||||||
|
func validConf() {
|
||||||
// 读取server参数,如果没有提示添加
|
// 读取server参数,如果没有提示添加
|
||||||
config := src.GetClientConfig()
|
config := src.GetClientConfig()
|
||||||
server := config.Server
|
server := config.Server
|
||||||
|
@ -42,214 +49,4 @@ func main() {
|
||||||
config.RsaPublicKey = rsaPublicKeyContent
|
config.RsaPublicKey = rsaPublicKeyContent
|
||||||
src.WriteConfig()
|
src.WriteConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取指令, 如果没有则提示添加
|
|
||||||
args := os.Args
|
|
||||||
if len(args) < 2 {
|
|
||||||
log.Println("请输入命令; 如: add")
|
|
||||||
log.Println("或者输入 help 查看帮助")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
onCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
func onCommand() {
|
|
||||||
command := os.Args[1]
|
|
||||||
switch command {
|
|
||||||
case "help":
|
|
||||||
showHelp()
|
|
||||||
case "list":
|
|
||||||
showList()
|
|
||||||
case "add":
|
|
||||||
addConf()
|
|
||||||
case "del":
|
|
||||||
delConf()
|
|
||||||
case "edit":
|
|
||||||
editConf()
|
|
||||||
case "get":
|
|
||||||
getCert()
|
|
||||||
case "-s":
|
|
||||||
onServerCommand()
|
|
||||||
default:
|
|
||||||
log.Fatalf("不支持的指令: %s\n您可以使用 help 查看帮助", command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 帮助
|
|
||||||
func showHelp() {
|
|
||||||
fmt.Printf("help\t查看帮助\n")
|
|
||||||
fmt.Printf("list\t查看配置列表\n")
|
|
||||||
fmt.Printf("add\t添加配置\n")
|
|
||||||
fmt.Printf("del\t删除配置\n")
|
|
||||||
fmt.Printf("edit\t修改配置\n")
|
|
||||||
fmt.Printf("get\t从获取端获取证书,会自动判断有效期\n")
|
|
||||||
fmt.Printf("\t[-f]\t从获取端获取证书,不判断有效期\n")
|
|
||||||
fmt.Printf("-s list\t查看服务端已配置的Domain名称\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示配置列表
|
|
||||||
func showList() {
|
|
||||||
domains := src.GetClientConfig().Domains
|
|
||||||
if len(domains) == 0 {
|
|
||||||
fmt.Println("暂无配置; 您可以使用 add 命令添加配置")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := range domains {
|
|
||||||
domain := domains[i]
|
|
||||||
fmt.Printf("%d. %s\n", i, domain.Name)
|
|
||||||
fmt.Printf(" - 证书文件: %s\n", domain.CertFile)
|
|
||||||
fmt.Printf(" - 密钥文件: %s\n", domain.KeyFile)
|
|
||||||
fmt.Printf(" - 详情文件: %s\n", domain.InfoFile)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加配置
|
|
||||||
func addConf() {
|
|
||||||
domain := &src.Domain{}
|
|
||||||
name := scanConf("请输入服务端已配置名称;可以通过 -s list 查看", "名称不能为空")
|
|
||||||
domain.Name = name
|
|
||||||
domain.CertFile = scanConf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem", "证书文件路径不能为空")
|
|
||||||
domain.KeyFile = scanConf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem", "私钥文件路径不能为空")
|
|
||||||
domain.InfoFile = scanConf("请输入详情文件存放全路径;如: /data/cert/info.json", "详情文件路径不能为空")
|
|
||||||
config := src.GetClientConfig()
|
|
||||||
config.Domains = append(config.Domains, *domain)
|
|
||||||
src.WriteConfig()
|
|
||||||
fmt.Println("添加成功")
|
|
||||||
showList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除配置
|
|
||||||
func delConf() {
|
|
||||||
fmt.Println("当前配置: ")
|
|
||||||
showList()
|
|
||||||
indexInt, _ := selectDomain("请输入要删除的序号")
|
|
||||||
config := src.GetClientConfig()
|
|
||||||
config.Domains = append(config.Domains[:indexInt], config.Domains[indexInt+1:]...)
|
|
||||||
src.WriteConfig()
|
|
||||||
fmt.Println("删除成功")
|
|
||||||
showList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改配置
|
|
||||||
func editConf() {
|
|
||||||
fmt.Println("当前配置")
|
|
||||||
showList()
|
|
||||||
_, domain := selectDomain("请输入要修改的序号")
|
|
||||||
msg := fmt.Sprintf("请输入服务端已配置名称;可以通过 -s list 查看\n当前配置: %s\n", domain.Name)
|
|
||||||
domain.Name = scanConf(msg, "名称不能为空")
|
|
||||||
certFile := fmt.Sprintf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem\n当前配置: %s\n", domain.CertFile)
|
|
||||||
domain.CertFile = scanConf(certFile, "证书文件路径不能为空")
|
|
||||||
msg = fmt.Sprintf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem\n当前配置: %s\n", domain.KeyFile)
|
|
||||||
domain.KeyFile = scanConf(msg, "私钥文件路径不能为空")
|
|
||||||
msg = fmt.Sprintf("请输入详情文件存放全路径;如: /data/cert/info.json\n当前配置: %s\n", domain.InfoFile)
|
|
||||||
domain.InfoFile = scanConf(msg, "详情文件路径不能为空")
|
|
||||||
src.WriteConfig()
|
|
||||||
fmt.Println("修改成功")
|
|
||||||
showList()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCert() {
|
|
||||||
_, domain := selectDomain("请选择要获取证书的配置编号")
|
|
||||||
args := os.Args
|
|
||||||
isDoGet := len(args) >= 3 && args[3] == "-f"
|
|
||||||
if !isDoGet {
|
|
||||||
infoFile := domain.InfoFile
|
|
||||||
os.Stat(infoFile)
|
|
||||||
//info, err := os.ReadFile(infoFile)
|
|
||||||
//keyFile := domain.KeyFile
|
|
||||||
}
|
|
||||||
|
|
||||||
//config := src.GetClientConfig()
|
|
||||||
//domains := config.Domains
|
|
||||||
//config.FindDomain(args[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
// 服务端命令
|
|
||||||
func onServerCommand() {
|
|
||||||
args := os.Args
|
|
||||||
if len(args) < 3 {
|
|
||||||
log.Fatal("参数错误, 请检查")
|
|
||||||
}
|
|
||||||
command := args[2]
|
|
||||||
switch command {
|
|
||||||
case "list":
|
|
||||||
showServerList()
|
|
||||||
default:
|
|
||||||
log.Fatal("参数错误, 请检查")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从服务端获取域名列表
|
|
||||||
func showServerList() {
|
|
||||||
server := src.GetClientConfig().Server
|
|
||||||
token, encryptToken := src.GenToken()
|
|
||||||
url := server + "/api/v1/domain/list?token=" + encryptToken
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("获取服务端数据失败, 请检查 server 地址是否正确")
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
result := src.Result{}
|
|
||||||
err = json.Unmarshal(body, &result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if result.Code != 200 {
|
|
||||||
log.Fatal("读取数据出错; ", result.Msg)
|
|
||||||
}
|
|
||||||
data := result.Data
|
|
||||||
text := src.DecryptByToken(token, data)
|
|
||||||
d := &[]src.SDomain{}
|
|
||||||
err = json.Unmarshal([]byte(text), d)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for i := range *d {
|
|
||||||
domain := (*d)[i]
|
|
||||||
fmt.Printf("- %s\n", domain.Name)
|
|
||||||
fmt.Printf("\t认证域名: [ ")
|
|
||||||
for j := range domain.Host {
|
|
||||||
host := domain.Host[j]
|
|
||||||
fmt.Printf("%s ", host)
|
|
||||||
}
|
|
||||||
fmt.Printf("]\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取用户输入
|
|
||||||
func scanConf(msg string, errMsg string) string {
|
|
||||||
for {
|
|
||||||
log.Println(msg)
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
name, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("读取失败;", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name = strings.Trim(name, "\r\n")
|
|
||||||
if name == "" {
|
|
||||||
fmt.Println(errMsg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择域名
|
|
||||||
func selectDomain(msg string) (int, *src.Domain) {
|
|
||||||
index := scanConf(msg, "序号不能为空")
|
|
||||||
indexInt, err := strconv.Atoi(index)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("序号错误")
|
|
||||||
}
|
|
||||||
config := src.GetClientConfig()
|
|
||||||
if indexInt >= len(config.Domains) || indexInt < 0 {
|
|
||||||
log.Fatal("序号超出范围")
|
|
||||||
}
|
|
||||||
return indexInt, &config.Domains[indexInt]
|
|
||||||
}
|
}
|
||||||
|
|
252
src/command.go
Normal file
252
src/command.go
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OnCommand() {
|
||||||
|
command := os.Args[1]
|
||||||
|
switch command {
|
||||||
|
case "help":
|
||||||
|
showHelp()
|
||||||
|
case "list":
|
||||||
|
showList()
|
||||||
|
case "add":
|
||||||
|
addConf()
|
||||||
|
case "del":
|
||||||
|
delConf()
|
||||||
|
case "edit":
|
||||||
|
editConf()
|
||||||
|
case "get":
|
||||||
|
getCert()
|
||||||
|
case "-s":
|
||||||
|
onServerCommand()
|
||||||
|
default:
|
||||||
|
log.Fatalf("不支持的指令: %s\n您可以使用 help 查看帮助", command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帮助
|
||||||
|
func showHelp() {
|
||||||
|
fmt.Printf("help\t查看帮助\n")
|
||||||
|
fmt.Printf("list\t查看配置列表\n")
|
||||||
|
fmt.Printf("add\t添加配置\n")
|
||||||
|
fmt.Printf("del\t删除配置\n")
|
||||||
|
fmt.Printf("edit\t修改配置\n")
|
||||||
|
fmt.Printf("get\t从获取端获取证书,会自动判断有效期\n")
|
||||||
|
fmt.Printf("\t[-f]\t从获取端获取证书,不判断有效期\n")
|
||||||
|
fmt.Printf("-s list\t查看服务端已配置的Domain名称\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示配置列表
|
||||||
|
func showList() {
|
||||||
|
domains := GetClientConfig().Domains
|
||||||
|
if len(domains) == 0 {
|
||||||
|
fmt.Println("暂无配置; 您可以使用 add 命令添加配置")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range domains {
|
||||||
|
domain := domains[i]
|
||||||
|
fmt.Printf("%d. %s\n", i, domain.Name)
|
||||||
|
fmt.Printf(" - 证书文件: %s\n", domain.CertFile)
|
||||||
|
fmt.Printf(" - 密钥文件: %s\n", domain.KeyFile)
|
||||||
|
fmt.Printf(" - 详情文件: %s\n", domain.InfoFile)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加配置
|
||||||
|
func addConf() {
|
||||||
|
domain := &Domain{}
|
||||||
|
name := scanConf("请输入服务端已配置名称;可以通过 -s list 查看", "名称不能为空")
|
||||||
|
domain.Name = name
|
||||||
|
domain.CertFile = scanConf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem", "证书文件路径不能为空")
|
||||||
|
domain.KeyFile = scanConf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem", "私钥文件路径不能为空")
|
||||||
|
domain.InfoFile = scanConf("请输入详情文件存放全路径;如: /data/cert/info.json", "详情文件路径不能为空")
|
||||||
|
config := GetClientConfig()
|
||||||
|
config.Domains = append(config.Domains, *domain)
|
||||||
|
WriteConfig()
|
||||||
|
fmt.Println("添加成功")
|
||||||
|
showList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除配置
|
||||||
|
func delConf() {
|
||||||
|
fmt.Println("当前配置: ")
|
||||||
|
showList()
|
||||||
|
indexInt, _ := selectDomain("请输入要删除的序号")
|
||||||
|
config := GetClientConfig()
|
||||||
|
config.Domains = append(config.Domains[:indexInt], config.Domains[indexInt+1:]...)
|
||||||
|
WriteConfig()
|
||||||
|
fmt.Println("删除成功")
|
||||||
|
showList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改配置
|
||||||
|
func editConf() {
|
||||||
|
fmt.Println("当前配置")
|
||||||
|
showList()
|
||||||
|
_, domain := selectDomain("请输入要修改的序号")
|
||||||
|
msg := fmt.Sprintf("请输入服务端已配置名称;可以通过 -s list 查看\n当前配置: %s\n", domain.Name)
|
||||||
|
domain.Name = scanConf(msg, "名称不能为空")
|
||||||
|
certFile := fmt.Sprintf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem\n当前配置: %s\n", domain.CertFile)
|
||||||
|
domain.CertFile = scanConf(certFile, "证书文件路径不能为空")
|
||||||
|
msg = fmt.Sprintf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem\n当前配置: %s\n", domain.KeyFile)
|
||||||
|
domain.KeyFile = scanConf(msg, "私钥文件路径不能为空")
|
||||||
|
msg = fmt.Sprintf("请输入详情文件存放全路径;如: /data/cert/info.json\n当前配置: %s\n", domain.InfoFile)
|
||||||
|
domain.InfoFile = scanConf(msg, "详情文件路径不能为空")
|
||||||
|
WriteConfig()
|
||||||
|
fmt.Println("修改成功")
|
||||||
|
showList()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCert() {
|
||||||
|
fmt.Println("当前配置")
|
||||||
|
showList()
|
||||||
|
_, domain := selectDomain("请选择要获取证书的配置编号")
|
||||||
|
args := os.Args
|
||||||
|
isDoGet := len(args) >= 3 && args[3] == "-f"
|
||||||
|
|
||||||
|
certFile := domain.CertFile
|
||||||
|
keyFile := domain.KeyFile
|
||||||
|
infoFile := domain.InfoFile
|
||||||
|
|
||||||
|
isDoGet = isDoGet || !isExist(certFile)
|
||||||
|
isDoGet = isDoGet || !isExist(keyFile)
|
||||||
|
isDoGet = isDoGet || !isExist(infoFile)
|
||||||
|
|
||||||
|
if !isDoGet {
|
||||||
|
info, _ := readInfoFile(infoFile)
|
||||||
|
if info == nil {
|
||||||
|
isDoGet = true
|
||||||
|
}
|
||||||
|
if !isDoGet {
|
||||||
|
if info.Info.NotAfter.Sub(time.Now()) < 7*24*time.Hour {
|
||||||
|
isDoGet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isDoGet {
|
||||||
|
fmt.Println("日志文件已存在, 无需更新.\n如果需要强制更新, 请使用命令: get -f")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domainData, err := doGetCert(domain.Name)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("从服务端获取配置失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeCert(domain, domainData)
|
||||||
|
fmt.Printf("证书相关文件写出成功,配置名称: %s\n", domain.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doGetCert(name string) (*DomainData, error) {
|
||||||
|
config := GetClientConfig()
|
||||||
|
server := config.Server
|
||||||
|
token, encryptToken := GenToken()
|
||||||
|
url := server + "/api/v1/cert?name=" + name + "&token=" + encryptToken
|
||||||
|
data, err := httpGet(url, token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := DomainData{}
|
||||||
|
err = json.Unmarshal([]byte(data), &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取用户输入
|
||||||
|
func scanConf(msg string, errMsg string) string {
|
||||||
|
for {
|
||||||
|
log.Println(msg)
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
name, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("读取失败;", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name = strings.Trim(name, "\r\n")
|
||||||
|
if name == "" {
|
||||||
|
fmt.Println(errMsg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择域名
|
||||||
|
func selectDomain(msg string) (int, *Domain) {
|
||||||
|
index := scanConf(msg, "序号不能为空")
|
||||||
|
indexInt, err := strconv.Atoi(index)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("序号错误")
|
||||||
|
}
|
||||||
|
config := GetClientConfig()
|
||||||
|
if indexInt >= len(config.Domains) || indexInt < 0 {
|
||||||
|
log.Fatal("序号超出范围")
|
||||||
|
}
|
||||||
|
return indexInt, &config.Domains[indexInt]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断文件是否存在
|
||||||
|
func isExist(file string) bool {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取证书详情文件
|
||||||
|
func readInfoFile(file string) (*CertInfo, error) {
|
||||||
|
readFile, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var certInfo *CertInfo
|
||||||
|
err = json.Unmarshal(readFile, &certInfo)
|
||||||
|
return certInfo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写出证书
|
||||||
|
func writeCert(domain *Domain, data *DomainData) {
|
||||||
|
certFile := domain.CertFile
|
||||||
|
keyFile := domain.KeyFile
|
||||||
|
infoFile := domain.InfoFile
|
||||||
|
|
||||||
|
mkFileDir(certFile)
|
||||||
|
mkFileDir(keyFile)
|
||||||
|
mkFileDir(infoFile)
|
||||||
|
|
||||||
|
fullchain := data.Fullchain
|
||||||
|
key := data.Key
|
||||||
|
info := data.Info
|
||||||
|
|
||||||
|
if err := os.WriteFile(certFile, []byte(fullchain), 0755); err != nil {
|
||||||
|
log.Fatal("写出证书文件失败!", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(keyFile, []byte(key), 0755); err != nil {
|
||||||
|
log.Fatal("写出秘钥文件失败!", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(infoFile, []byte(info), 0755); err != nil {
|
||||||
|
log.Fatal("写出证书详情文件失败!", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建文件所在的目录
|
||||||
|
func mkFileDir(file string) {
|
||||||
|
dir := filepath.Dir(file)
|
||||||
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
|
fmt.Printf("证书文件夹 %s 不存在,自动创建...\n", dir)
|
||||||
|
err := os.MkdirAll(dir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("创建证书文件夹失败!", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
package src
|
package src
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-acme/lego/v4/log"
|
|
||||||
"github.com/go-acme/lego/v4/platform/config/env"
|
"github.com/go-acme/lego/v4/platform/config/env"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ func InitConfig() {
|
||||||
_, err := os.Stat(confFile)
|
_, err := os.Stat(confFile)
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Infof("客户端配置文件不存在,自动创建默认配置文件")
|
log.Println("客户端配置文件不存在,自动创建默认配置文件")
|
||||||
conf := defaultConfig()
|
conf := defaultConfig()
|
||||||
file, err := os.Create(confFile)
|
file, err := os.Create(confFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -72,25 +72,3 @@ func getConfigFile() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENV_CLIENT_CONF_FILE = "ACME_MANA_CONF_FILE"
|
const ENV_CLIENT_CONF_FILE = "ACME_MANA_CONF_FILE"
|
||||||
|
|
||||||
type ClientConfig struct {
|
|
||||||
Server string
|
|
||||||
RsaPublicKey string
|
|
||||||
Domains []Domain
|
|
||||||
}
|
|
||||||
|
|
||||||
type Domain struct {
|
|
||||||
Name string
|
|
||||||
CertFile string
|
|
||||||
KeyFile string
|
|
||||||
InfoFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ClientConfig) FindDomain(name string) *Domain {
|
|
||||||
for _, domain := range conf.Domains {
|
|
||||||
if domain.Name == name {
|
|
||||||
return &domain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
31
src/http.go
Normal file
31
src/http.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// http get
|
||||||
|
func httpGet(url string, token string) (string, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result := Result{}
|
||||||
|
err = json.Unmarshal(body, &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if result.Code != 200 {
|
||||||
|
return "", errors.New(result.Msg)
|
||||||
|
}
|
||||||
|
data := result.Data
|
||||||
|
return DecryptByToken(token, data), nil
|
||||||
|
}
|
58
src/models.go
Normal file
58
src/models.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConfig 客户端配置
|
||||||
|
type ClientConfig struct {
|
||||||
|
Server string
|
||||||
|
RsaPublicKey string
|
||||||
|
Domains []Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain 域名
|
||||||
|
type Domain struct {
|
||||||
|
Name string
|
||||||
|
CertFile string
|
||||||
|
KeyFile string
|
||||||
|
InfoFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindDomain 查找域名
|
||||||
|
func (conf *ClientConfig) FindDomain(name string) *Domain {
|
||||||
|
for _, domain := range conf.Domains {
|
||||||
|
if domain.Name == name {
|
||||||
|
return &domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result 服务端统一响应格式
|
||||||
|
type Result struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SDomain 服务端域名配置结构
|
||||||
|
type SDomain struct {
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
Host []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainData 证书相关信息
|
||||||
|
type DomainData struct {
|
||||||
|
Fullchain string `json:"fullchain"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertInfo 证书详情
|
||||||
|
type CertInfo struct {
|
||||||
|
Cert certificate.Resource
|
||||||
|
Info x509.Certificate
|
||||||
|
}
|
66
src/scommand.go
Normal file
66
src/scommand.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 服务端命令
|
||||||
|
func onServerCommand() {
|
||||||
|
args := os.Args
|
||||||
|
if len(args) < 3 {
|
||||||
|
log.Fatal("参数错误, 请检查")
|
||||||
|
}
|
||||||
|
command := args[2]
|
||||||
|
switch command {
|
||||||
|
case "list":
|
||||||
|
showServerList()
|
||||||
|
default:
|
||||||
|
log.Fatal("参数错误, 请检查")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从服务端获取域名列表
|
||||||
|
func showServerList() {
|
||||||
|
server := GetClientConfig().Server
|
||||||
|
token, encryptToken := GenToken()
|
||||||
|
url := server + "/api/v1/domain/list?token=" + encryptToken
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("获取服务端数据失败, 请检查 server 地址是否正确")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
result := Result{}
|
||||||
|
err = json.Unmarshal(body, &result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if result.Code != 200 {
|
||||||
|
log.Fatal("读取数据出错; ", result.Msg)
|
||||||
|
}
|
||||||
|
data := result.Data
|
||||||
|
text := DecryptByToken(token, data)
|
||||||
|
d := &[]SDomain{}
|
||||||
|
err = json.Unmarshal([]byte(text), d)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for i := range *d {
|
||||||
|
domain := (*d)[i]
|
||||||
|
fmt.Printf("- %s\n", domain.Name)
|
||||||
|
fmt.Printf("\t认证域名: [ ")
|
||||||
|
for j := range domain.Host {
|
||||||
|
host := domain.Host[j]
|
||||||
|
fmt.Printf("%s ", host)
|
||||||
|
}
|
||||||
|
fmt.Printf("]\n")
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/go-acme/lego/v4/log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,3 @@ func GetClientConfig() *ClientConfig {
|
||||||
func ReloadClientConfig() {
|
func ReloadClientConfig() {
|
||||||
clientConfig = ReadConfig()
|
clientConfig = ReadConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
Code int
|
|
||||||
Msg string
|
|
||||||
Data string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SDomain struct {
|
|
||||||
Name string
|
|
||||||
Email string
|
|
||||||
Host []string
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user