Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
8a09c75e47 | |||
ad99da70fb | |||
4b43bc3533 | |||
b41a60a932 | |||
ca2a3a8c24 | |||
77f7aec1c8 | |||
8d063e1330 | |||
a742f096f0 | |||
d505213d2c | |||
510af680c4 | |||
4f9086650e | |||
e6d412c114 | |||
55d241b873 | |||
30d77aac56 | |||
6ed343cf06 |
12
.run/block.run.xml
Normal file
12
.run/block.run.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="block" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||
<module name="acme-mana" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="block" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="acme-mana" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$/main.go" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
17
.run/build-darwin-amd64.run.xml
Normal file
17
.run/build-darwin-amd64.run.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="build-darwin-amd64" type="GoApplicationRunConfiguration" factoryName="Go Application" folderName="build">
|
||||
<module name="acme-mana" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<go_parameters value="-o bin/acme-mana-darwin-amd64" />
|
||||
<envs>
|
||||
<env name="GOARCH" value="amd64" />
|
||||
<env name="GOOS" value="darwin" />
|
||||
</envs>
|
||||
<kind value="PACKAGE" />
|
||||
<package value="acme-mana" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<option name="run" value="false" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
17
.run/build-linux-amd64.run.xml
Normal file
17
.run/build-linux-amd64.run.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="build-linux-amd64" type="GoApplicationRunConfiguration" factoryName="Go Application" folderName="build">
|
||||
<module name="acme-mana" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<go_parameters value="-o bin/acme-mana-linux-amd64" />
|
||||
<envs>
|
||||
<env name="GOARCH" value="amd64" />
|
||||
<env name="GOOS" value="linux" />
|
||||
</envs>
|
||||
<kind value="PACKAGE" />
|
||||
<package value="acme-mana" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<option name="run" value="false" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
17
.run/build-windows-amd64.run.xml
Normal file
17
.run/build-windows-amd64.run.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="build-windows-amd64" type="GoApplicationRunConfiguration" factoryName="Go Application" folderName="build">
|
||||
<module name="acme-mana" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<go_parameters value="-o bin/acme-mana-windows-amd64.exe" />
|
||||
<envs>
|
||||
<env name="GOARCH" value="amd64" />
|
||||
<env name="GOOS" value="windows" />
|
||||
</envs>
|
||||
<kind value="PACKAGE" />
|
||||
<package value="acme-mana" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$" />
|
||||
<option name="run" value="false" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
12
.run/conf show.run.xml
Normal file
12
.run/conf show.run.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="conf show" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||
<module name="acme-mana" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="conf show" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="acme-mana" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$/main.go" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
12
.run/help.run.xml
Normal file
12
.run/help.run.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="help" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||
<module name="acme-mana" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="help" />
|
||||
<kind value="PACKAGE" />
|
||||
<package value="acme-mana" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<filePath value="$PROJECT_DIR$/main.go" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
24
go.mod
24
go.mod
|
@ -5,18 +5,29 @@ go 1.22.0
|
|||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbletea v1.1.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-acme/lego/v4 v4.19.1
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/urfave/cli/v2 v2.27.4
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.13.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.4.0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
|
||||
|
@ -24,24 +35,35 @@ require (
|
|||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
|
|
55
go.sum
55
go.sum
|
@ -4,21 +4,41 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY
|
|||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 h1:r2uwBUQhLhcPzaWz9tRJqc8MjYwHb+oF2+Q6467BF14=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
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/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc=
|
||||
github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E=
|
||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||
github.com/charmbracelet/x/ansi v0.4.0 h1:NqwHA4B23VwsDn4H3VcNX1W1tOmgnvY1NDx5tOXdnOU=
|
||||
github.com/charmbracelet/x/ansi v0.4.0/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
||||
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
|
@ -47,6 +67,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
|
@ -64,8 +86,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
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/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -73,6 +103,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||
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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
|
@ -84,6 +120,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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=
|
||||
|
@ -104,6 +149,10 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
|
|||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
||||
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
||||
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/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
|
@ -132,13 +181,15 @@ golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
|
|
18
main.go
18
main.go
|
@ -1,7 +1,21 @@
|
|||
package main
|
||||
|
||||
import "acme-mana/src"
|
||||
import (
|
||||
"acme-mana/src"
|
||||
"acme-mana/src/cmd/cmd_handle"
|
||||
"acme-mana/src/conf"
|
||||
)
|
||||
|
||||
func main() {
|
||||
src.Start()
|
||||
//src.Start()
|
||||
src.StartProgram()
|
||||
select {}
|
||||
//runServer()
|
||||
|
||||
}
|
||||
|
||||
func runServer() {
|
||||
conf.LoadAppConfig()
|
||||
cmd_handle.RunStart(nil, nil)
|
||||
select {}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package src
|
||||
package acme
|
||||
|
||||
import (
|
||||
"acme-mana/src/common"
|
||||
"acme-mana/src/conf"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
|
@ -9,6 +11,7 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/log"
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
|
@ -18,10 +21,11 @@ import (
|
|||
"path/filepath"
|
||||
)
|
||||
|
||||
// Apply 申请证书
|
||||
// domain: 申请的域名
|
||||
func Apply(domain Domain) {
|
||||
email, hosts, name := domain.Email, domain.Host, domain.Name
|
||||
func Apply(cert *conf.CertConf) {
|
||||
email := cert.Email
|
||||
host := cert.Host
|
||||
name := cert.Name
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -31,26 +35,19 @@ func Apply(domain Domain) {
|
|||
Email: email,
|
||||
key: privateKey,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(acmeUser)
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ali := appConfig.Provider.Ali
|
||||
conf := alidns.NewDefaultConfig()
|
||||
conf.RegionID = ali.RegionID
|
||||
conf.APIKey = ali.APIKey
|
||||
conf.SecretKey = ali.SecretKey
|
||||
provider, err := alidns.NewDNSProviderConfig(conf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
providerName := cert.Provider
|
||||
p, _ := conf.FindProvider(providerName)
|
||||
|
||||
challenge := client.Challenge
|
||||
err = challenge.SetDNS01Provider(provider)
|
||||
provider := getProvider(p)
|
||||
|
||||
chall := client.Challenge
|
||||
err = chall.SetDNS01Provider(provider)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -63,21 +60,45 @@ func Apply(domain Domain) {
|
|||
acmeUser.Registration = reg
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: hosts,
|
||||
Domains: host,
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
cert, err := client.Certificate.Obtain(request)
|
||||
res, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
saveCertFile(cert, name)
|
||||
saveCertFile(res, cert, name)
|
||||
|
||||
}
|
||||
|
||||
func getProvider(p *conf.ProviderConf) challenge.Provider {
|
||||
|
||||
switch p.Type {
|
||||
case "ali":
|
||||
return aliProvider(p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func aliProvider(p *conf.ProviderConf) challenge.Provider {
|
||||
aliConf := alidns.NewDefaultConfig()
|
||||
aliConf.RegionID = p.Conf["RegionID"]
|
||||
aliConf.APIKey = p.Conf["APIKey"]
|
||||
aliConf.SecretKey = p.Conf["SecretKey"]
|
||||
provider, err := alidns.NewDNSProviderConfig(aliConf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
func tencentProvider() challenge.Provider {
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveCertFile 保存证书文件
|
||||
func saveCertFile(cert *certificate.Resource, name string) {
|
||||
dir := GetAppConfig().CertDir
|
||||
func saveCertFile(cert *certificate.Resource, cerfConf *conf.CertConf, name string) {
|
||||
dir := cerfConf.Dir
|
||||
dir = filepath.Join(dir, name)
|
||||
_, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
|
@ -90,12 +111,12 @@ func saveCertFile(cert *certificate.Resource, name string) {
|
|||
}
|
||||
|
||||
certBytes := cert.Certificate
|
||||
err = os.WriteFile(path.Join(dir, CertFileName), certBytes, 0755)
|
||||
err = os.WriteFile(path.Join(dir, common.CertFileName), certBytes, 0755)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to save certificate: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(dir, KeyFileName), cert.PrivateKey, 0755)
|
||||
err = os.WriteFile(path.Join(dir, common.KeyFileName), cert.PrivateKey, 0755)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to save private key: %v", err)
|
||||
}
|
||||
|
@ -119,7 +140,7 @@ func saveCertFile(cert *certificate.Resource, name string) {
|
|||
if err != nil {
|
||||
log.Fatalf("Failed to marshal certificate: %v", err)
|
||||
}
|
||||
err = os.WriteFile(path.Join(dir, CertInfoFileName), certJson, 0644)
|
||||
err = os.WriteFile(path.Join(dir, common.CertInfoFileName), certJson, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to save certificate info: %v", err)
|
||||
}
|
75
src/acme/provider.go
Normal file
75
src/acme/provider.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
//go:embed providers.json
|
||||
var providerFile embed.FS
|
||||
|
||||
type ProviderInfo struct {
|
||||
Type string `json:"type"`
|
||||
Remarks string `json:"remarks"`
|
||||
Doc *ProviderDoc `json:"doc"`
|
||||
Variables *ConfVariables `json:"variables"`
|
||||
}
|
||||
|
||||
type ProviderDoc struct {
|
||||
Show string `json:"show"`
|
||||
Home string `json:"home"`
|
||||
Api string `json:"api"`
|
||||
Sdk string `json:"sdk"`
|
||||
}
|
||||
|
||||
type ConfVariables struct {
|
||||
Info string `json:"info"`
|
||||
Docs *[]string `json:"docs"`
|
||||
Items *[]ConfItem `json:"items"`
|
||||
}
|
||||
|
||||
type ConfItem struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Require bool `json:"require"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
var ProviderInfoMap map[string]ProviderInfo = nil
|
||||
|
||||
// ProviderInfos 数组
|
||||
var ProviderInfos = make([]ProviderInfo, 0)
|
||||
|
||||
func ListProvider() *[]ProviderInfo {
|
||||
if ProviderInfoMap == nil {
|
||||
initProvider()
|
||||
}
|
||||
return &ProviderInfos
|
||||
}
|
||||
func MapProvider() *map[string]ProviderInfo {
|
||||
if ProviderInfoMap == nil {
|
||||
initProvider()
|
||||
}
|
||||
return &ProviderInfoMap
|
||||
}
|
||||
func GetProvider(name string) *ProviderInfo {
|
||||
info := ProviderInfoMap[name]
|
||||
return &info
|
||||
}
|
||||
|
||||
func initProvider() {
|
||||
data, err := providerFile.ReadFile("providers.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read config file: %v", err)
|
||||
}
|
||||
var providers []ProviderInfo
|
||||
if err := json.Unmarshal(data, &providers); err != nil {
|
||||
log.Fatalf("Failed to parse config file: %v", err)
|
||||
}
|
||||
ProviderInfoMap = make(map[string]ProviderInfo)
|
||||
for _, provider := range providers {
|
||||
ProviderInfos = append(ProviderInfos, provider)
|
||||
ProviderInfoMap[provider.Type] = provider
|
||||
}
|
||||
}
|
66
src/acme/providers.json
Normal file
66
src/acme/providers.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
[
|
||||
{
|
||||
"type": "alidns",
|
||||
"remarks": "ALIBABA CLOUD DNS",
|
||||
"doc": {
|
||||
"show": "https://go-acme.github.io/lego/dns/alidns/index.html",
|
||||
"home": "https://www.alibabacloud.com/product/dns",
|
||||
"api": "https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records",
|
||||
"sdk": "https://github.com/aliyun/alibaba-cloud-sdk-go"
|
||||
},
|
||||
"variables": {
|
||||
"info": "",
|
||||
"docs": [
|
||||
""
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"name": "RegionID",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"info": "RegionID"
|
||||
},{
|
||||
"name": "APIKey",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"info": "APIKey"
|
||||
},{
|
||||
"name": "SecretKey",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"info": "SecretKey"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tencentcloud",
|
||||
"remarks": "TENCENT CLOUD DNS",
|
||||
"doc": {
|
||||
"show": "https://go-acme.github.io/lego/dns/tencentcloud/index.html",
|
||||
"home": "https://cloud.tencent.com/product/cns",
|
||||
"api": "https://cloud.tencent.com/document/product/1427/56153",
|
||||
"sdk": "https://github.com/tencentcloud/tencentcloud-sdk-go"
|
||||
},
|
||||
"variables": {
|
||||
"info": "",
|
||||
"docs": [
|
||||
""
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"name": "SecretID",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"info": "SecretID"
|
||||
},
|
||||
{
|
||||
"name": "SecretKey",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"info": "SecretKey"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
297
src/cmd/cmd.go
Normal file
297
src/cmd/cmd.go
Normal file
|
@ -0,0 +1,297 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"acme-mana/src/cmd/cmd_handle"
|
||||
"acme-mana/src/common"
|
||||
"acme-mana/src/conf"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
func InitCmd() (*cobra.Command, error) {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "acme-mana",
|
||||
Short: "ACME协议客户端",
|
||||
Long: "基于 ACME 协议实现自动申请证书和续签证书",
|
||||
}
|
||||
common.RootCmd = rootCmd
|
||||
|
||||
rootCmd.AddCommand(initConfCmd())
|
||||
rootCmd.AddCommand(initServerCmd())
|
||||
rootCmd.AddCommand(initTaskCmd())
|
||||
rootCmd.AddCommand(initProviderCmd())
|
||||
rootCmd.AddCommand(certCmd())
|
||||
rootCmd.AddCommand(acmeCmd())
|
||||
|
||||
flags := rootCmd.PersistentFlags()
|
||||
flags.BoolP("force", "f", false, "强制执行")
|
||||
flags.StringP("conf", "c", "config.yml", "指定配置文件")
|
||||
_ = rootCmd.ParseFlags(os.Args)
|
||||
|
||||
//rootCmd.GenBashCompletion(os.Stdout)
|
||||
//rootCmd.GenPowerShellCompletion(os.Stdout)
|
||||
//rootCmd.GenZshCompletion(os.Stdout)
|
||||
|
||||
conf.LoadAppConfig()
|
||||
|
||||
err := rootCmd.Execute()
|
||||
|
||||
return rootCmd, err
|
||||
}
|
||||
|
||||
func initConfCmd() *cobra.Command {
|
||||
confCmd := &cobra.Command{
|
||||
Use: "conf",
|
||||
Short: "应用配置命令",
|
||||
Long: "对当前应用配置进行操作",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
confShow := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "查看配置列表",
|
||||
Long: "查看系统配置列表",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ConfShow(cmd, args)
|
||||
},
|
||||
}
|
||||
confCmd.AddCommand(confShow)
|
||||
return confCmd
|
||||
}
|
||||
|
||||
func initServerCmd() *cobra.Command {
|
||||
serverCmd := &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "服务端命令",
|
||||
Long: "acme-mana服务端相关命令, 配置服务相关参数, 如监听端口,监听地址等",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := cmd.Help()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
editCmd := &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "编辑服务配置",
|
||||
Long: "编辑服务配置",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ServerEdit(cmd, args)
|
||||
},
|
||||
}
|
||||
stateCmd := &cobra.Command{
|
||||
Use: "state",
|
||||
Short: "查看服务状态",
|
||||
Long: "查看服务状态",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ServerState(cmd, args)
|
||||
},
|
||||
}
|
||||
startCmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "启动服务",
|
||||
Long: "启动服务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ServerStart(cmd, args)
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "前台运行服务",
|
||||
Long: "前台运行服务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.RunStart(cmd, args)
|
||||
},
|
||||
}
|
||||
stopCmd := &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "停止服务",
|
||||
Long: "停止服务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ServerStop(cmd, args)
|
||||
},
|
||||
}
|
||||
|
||||
serverCmd.AddCommand(editCmd)
|
||||
serverCmd.AddCommand(stateCmd)
|
||||
serverCmd.AddCommand(startCmd)
|
||||
serverCmd.AddCommand(runCmd)
|
||||
serverCmd.AddCommand(stopCmd)
|
||||
return serverCmd
|
||||
}
|
||||
|
||||
func initTaskCmd() *cobra.Command {
|
||||
taskCmd := &cobra.Command{
|
||||
Use: "task",
|
||||
Short: "定时任务相关命令",
|
||||
Long: "自动刷新证书定时任务的相关命令",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
editTask := &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "编辑定时任务配置",
|
||||
Long: "编辑定时任务配置",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.TaskEdit(cmd, args)
|
||||
},
|
||||
}
|
||||
startTask := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "启动定时任务",
|
||||
Long: "启动定时任务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.TaskStart(cmd, args)
|
||||
},
|
||||
}
|
||||
stopTask := &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "停止定时任务",
|
||||
Long: "停止定时任务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.TaskStop(cmd, args)
|
||||
},
|
||||
}
|
||||
statusTask := &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "查看定时任务状态",
|
||||
Long: "查看定时任务状态",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.TaskStatus(cmd, args)
|
||||
},
|
||||
}
|
||||
runTask := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "手动执行一次任务",
|
||||
Long: "手动执行一次任务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.TaskRun(cmd, args)
|
||||
},
|
||||
}
|
||||
taskCmd.AddCommand(editTask)
|
||||
taskCmd.AddCommand(startTask)
|
||||
taskCmd.AddCommand(stopTask)
|
||||
taskCmd.AddCommand(statusTask)
|
||||
taskCmd.AddCommand(runTask)
|
||||
return taskCmd
|
||||
}
|
||||
|
||||
func initProviderCmd() *cobra.Command {
|
||||
providerCmd := &cobra.Command{
|
||||
Use: "provider",
|
||||
Short: "DNS服务商相关命令",
|
||||
Long: "DNS服务商相关命令",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
providerSupport := &cobra.Command{
|
||||
Use: "support",
|
||||
Short: "列出支持的DNS服务商",
|
||||
Long: "列出支持的DNS服务商",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ProviderSupport(cmd, args)
|
||||
},
|
||||
}
|
||||
providerList := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "列出已配置的DNS服务商",
|
||||
Long: "列出已配置的DNS服务商",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ProviderList(cmd, args)
|
||||
},
|
||||
}
|
||||
providerAdd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "添加DNS服务商",
|
||||
Long: "添加DNS服务商",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ProviderAdd(cmd, args)
|
||||
},
|
||||
}
|
||||
providerEdit := &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "编辑DNS服务商",
|
||||
Long: "编辑DNS服务商",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ProviderEdit(cmd, args)
|
||||
},
|
||||
}
|
||||
providerDelete := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "删除DNS服务商",
|
||||
Long: "删除DNS服务商",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.ProviderDelete(cmd, args)
|
||||
},
|
||||
}
|
||||
|
||||
providerCmd.AddCommand(providerSupport)
|
||||
providerCmd.AddCommand(providerList)
|
||||
providerCmd.AddCommand(providerAdd)
|
||||
providerCmd.AddCommand(providerEdit)
|
||||
providerCmd.AddCommand(providerDelete)
|
||||
return providerCmd
|
||||
}
|
||||
|
||||
func certCmd() *cobra.Command {
|
||||
certCmd := &cobra.Command{
|
||||
Use: "cert",
|
||||
Short: "证书配置相关命令",
|
||||
Long: "证书配置相关命令",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
certList := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "列出已配置的证书",
|
||||
Long: "列出已配置的证书",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.CertList(cmd, args)
|
||||
},
|
||||
}
|
||||
certAdd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "添加证书",
|
||||
Long: "添加证书",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.CertAdd(cmd, args)
|
||||
},
|
||||
}
|
||||
certDelete := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "删除证书",
|
||||
Long: "删除证书",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.CertDelete(cmd, args)
|
||||
},
|
||||
}
|
||||
editCert := &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "编辑证书",
|
||||
Long: "编辑证书",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd_handle.CertEdit(cmd, args)
|
||||
},
|
||||
}
|
||||
certCmd.AddCommand(certList)
|
||||
certCmd.AddCommand(certAdd)
|
||||
certCmd.AddCommand(certDelete)
|
||||
certCmd.AddCommand(editCert)
|
||||
return certCmd
|
||||
}
|
||||
|
||||
func acmeCmd() *cobra.Command {
|
||||
acmeCmd := &cobra.Command{
|
||||
Use: "acme",
|
||||
Short: "ACME相关命令",
|
||||
Long: "acme.sh原生命令",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
return acmeCmd
|
||||
}
|
22
src/cmd/cmd_handle/cert.go
Normal file
22
src/cmd/cmd_handle/cert.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package cmd_handle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CertEdit(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("edit cert")
|
||||
}
|
||||
|
||||
func CertDelete(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("delete cert")
|
||||
}
|
||||
|
||||
func CertAdd(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("add cert")
|
||||
}
|
||||
|
||||
func CertList(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("list cert")
|
||||
}
|
19
src/cmd/cmd_handle/conf.go
Normal file
19
src/cmd/cmd_handle/conf.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package cmd_handle
|
||||
|
||||
import (
|
||||
"acme-mana/src/conf"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ConfShow 打印 配置信息
|
||||
func ConfShow(cmd *cobra.Command, args []string) {
|
||||
tea.Println()
|
||||
confJson, err := json.MarshalIndent(conf.Config(), "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("序列化配置信息失败:", err)
|
||||
}
|
||||
fmt.Println(string(confJson))
|
||||
}
|
26
src/cmd/cmd_handle/provider.go
Normal file
26
src/cmd/cmd_handle/provider.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package cmd_handle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func ProviderDelete(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("delete provider")
|
||||
}
|
||||
|
||||
func ProviderEdit(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("edit provider")
|
||||
}
|
||||
|
||||
func ProviderAdd(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("add provider")
|
||||
}
|
||||
|
||||
func ProviderList(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("list provider")
|
||||
}
|
||||
|
||||
func ProviderSupport(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("support provider")
|
||||
}
|
156
src/cmd/cmd_handle/server.go
Normal file
156
src/cmd/cmd_handle/server.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package cmd_handle
|
||||
|
||||
import (
|
||||
"acme-mana/src/common"
|
||||
"acme-mana/src/conf"
|
||||
"acme-mana/src/server"
|
||||
"acme-mana/src/util"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var stdout *os.File
|
||||
var stderr *os.File
|
||||
|
||||
func ServerStart(command *cobra.Command, args []string) {
|
||||
initLog()
|
||||
|
||||
isDaemon := os.Getenv("GO_DAEMON")
|
||||
log.Println("守护进程启动: " + isDaemon)
|
||||
|
||||
if isRunning() {
|
||||
log.Println("守护进程已启动, 跳过执行")
|
||||
return
|
||||
}
|
||||
|
||||
if isDaemon == "1" {
|
||||
daemonStart()
|
||||
return
|
||||
}
|
||||
|
||||
workPath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatalf("无法获取当前工作路径: %v", err)
|
||||
}
|
||||
cmd := exec.Cmd{
|
||||
Path: workPath,
|
||||
Args: os.Args,
|
||||
Dir: filepath.Dir(workPath),
|
||||
Env: append(os.Environ(), "GO_DAEMON=1"),
|
||||
//Stdin: os.Stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
SysProcAttr: &syscall.SysProcAttr{},
|
||||
}
|
||||
log.Println("启动守护进程中...")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("无法启动守护进程: %v", err)
|
||||
}
|
||||
err = os.WriteFile(common.PidFile, []byte(strconv.Itoa(cmd.Process.Pid)), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("无法写出PID文件: %v", err)
|
||||
}
|
||||
log.Printf("启动进程启动成功, PID: %d", cmd.Process.Pid)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func RunStart(command *cobra.Command, args []string) {
|
||||
daemonStart()
|
||||
}
|
||||
|
||||
func ServerEdit(cmd *cobra.Command, args []string) {
|
||||
s := conf.Config().Server
|
||||
host := util.ReadLine("请输入服务监听地址;", s.Host)
|
||||
port := util.ReadInt("请输入服务监听端口;", strconv.Itoa(s.Port))
|
||||
key := util.ReadLine("请输入服务通信认证秘钥;", s.Key)
|
||||
web := util.ReadLine("是否启用WEB服务; Y(es)/N(o);", s.Key)
|
||||
openWeb := strings.ToUpper(web) == "Y" || strings.ToUpper(web) == "YES"
|
||||
conf.EditServer(host, port, key, openWeb)
|
||||
fmt.Println("服务监听配置已完成")
|
||||
}
|
||||
|
||||
func ServerState(cmd *cobra.Command, args []string) {
|
||||
server.HttpInstance.Status()
|
||||
}
|
||||
func ServerStop(cmd *cobra.Command, args []string) {
|
||||
server.HttpInstance.Stop()
|
||||
}
|
||||
|
||||
func daemonStart() {
|
||||
// 启动 HttpServer
|
||||
server.HttpInstance = &server.HttpServer{}
|
||||
server.HttpInstance.Init()
|
||||
server.HttpInstance.Start()
|
||||
|
||||
// 启动 SocketServer
|
||||
server.SocketInstance.Start()
|
||||
|
||||
// TODO 启动 AutoRefreshCert
|
||||
}
|
||||
|
||||
func isRunning() bool {
|
||||
log.Println("Checking if daemon is running...")
|
||||
pid, err := readPID()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
log.Println("Found process:", process)
|
||||
if err != nil {
|
||||
log.Println("Failed to find process:", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 读取PID文件
|
||||
func readPID() (int, error) {
|
||||
log.Println("Reading PID file...")
|
||||
data, err := os.ReadFile(common.PidFile)
|
||||
if err != nil {
|
||||
log.Println("Failed to read PID file:", err)
|
||||
return 0, err
|
||||
}
|
||||
log.Println("PID file content:", string(data))
|
||||
|
||||
pid, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
log.Println("Failed to parse PID:", err)
|
||||
return 0, err
|
||||
}
|
||||
log.Println("PID:", pid)
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func initLog() {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("无法获取当前工作路径: %v", err)
|
||||
}
|
||||
dir := path.Join(pwd, "log")
|
||||
_, err = os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
err := os.Mkdir(dir, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("无法创建日志目录: %v", err)
|
||||
}
|
||||
}
|
||||
outFile, err := os.OpenFile(path.Join(dir, "out.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
defer outFile.Close()
|
||||
stdout = outFile
|
||||
|
||||
errFile, err := os.OpenFile(path.Join(dir, "err.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
defer stderr.Close()
|
||||
stderr = errFile
|
||||
}
|
26
src/cmd/cmd_handle/task.go
Normal file
26
src/cmd/cmd_handle/task.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package cmd_handle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TaskEdit(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("edit task")
|
||||
}
|
||||
|
||||
func TaskStatus(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("status task")
|
||||
}
|
||||
|
||||
func TaskStop(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("stop task")
|
||||
}
|
||||
|
||||
func TaskStart(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("start task")
|
||||
}
|
||||
|
||||
func TaskRun(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("run task")
|
||||
}
|
27
src/cmd/tea/readline.go
Normal file
27
src/cmd/tea/readline.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package tea_programe
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
type Readline struct {
|
||||
Line string
|
||||
}
|
||||
|
||||
func (m Readline) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Readline) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
if msg.String() == "enter" {
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.Line += msg.String()
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Readline) View() string {
|
||||
return m.Line
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package src
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// InitSocket 初始化 socket 文件
|
||||
func InitSocket() {
|
||||
log.Println("Start listen command")
|
||||
// 删除旧的 socket 文件
|
||||
if _, err := os.Stat(SocketFile); err == nil {
|
||||
os.Remove(SocketFile)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", SocketFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen on socket: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
for {
|
||||
log.Println("Waiting for connections...")
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Printf("Failed to accept connection: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go handleConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
处理连接
|
||||
*/
|
||||
func handleConnection(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read command: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
command := string(buf[:n])
|
||||
log.Printf("Received command: %s", command)
|
||||
|
||||
// 在这里处理接收到的命令
|
||||
switch command {
|
||||
case "stop":
|
||||
onStop()
|
||||
default:
|
||||
onCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
收到停止命令
|
||||
*/
|
||||
func onStop() {
|
||||
log.Println("Stopping daemon...")
|
||||
os.Remove(PidFile)
|
||||
log.Println("Remove PID File...")
|
||||
os.Remove(SocketFile)
|
||||
log.Println("Remove Socket File...")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
/*
|
||||
收到命令
|
||||
*/
|
||||
func onCommand(command string) {
|
||||
|
||||
}
|
||||
|
||||
func onConfig() {
|
||||
|
||||
}
|
14
src/common/variable.go
Normal file
14
src/common/variable.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const PidFile = "acme-mana.pid"
|
||||
const SocketFile = "acme-mana.sock"
|
||||
|
||||
const CertFileName = "fullchain.pem"
|
||||
const KeyFileName = "privkey.pem"
|
||||
const CertInfoFileName = "info.json"
|
||||
|
||||
var RootCmd *cobra.Command
|
60
src/conf/conf.go
Normal file
60
src/conf/conf.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package conf
|
||||
|
||||
// AppConfig
|
||||
// 配置文件
|
||||
type AppConfig struct {
|
||||
// 服务端配置
|
||||
Server *ServerConf `json:"server" yaml:"server"`
|
||||
// 网页配置
|
||||
Web *WebConf `json:"web" yaml:"web"`
|
||||
// 任务配置
|
||||
Task *TaskConf `json:"task" yaml:"task"`
|
||||
// 提供商配置
|
||||
Providers *[]ProviderConf `json:"provider" yaml:"provider"`
|
||||
// 证书配置
|
||||
Certs *[]CertConf `json:"cert" yaml:"cert"`
|
||||
}
|
||||
|
||||
// ServerConf 服务端配置
|
||||
type ServerConf struct {
|
||||
// 是否启用WEB控制台支持
|
||||
Web bool `json:"enable" yaml:"enable"`
|
||||
// 监听地址
|
||||
Host string `json:"host" yaml:"host"`
|
||||
// 监听端口
|
||||
Port int `json:"port" yaml:"port"`
|
||||
// 通信密钥, DES 加密
|
||||
Key string `json:"key" yaml:"key"`
|
||||
}
|
||||
|
||||
// WebConf 网页服务配置
|
||||
type WebConf struct {
|
||||
Info string `json:"info" yaml:"info"`
|
||||
}
|
||||
|
||||
// TaskConf 定时任务配置
|
||||
type TaskConf struct {
|
||||
// 启动延迟时间, 单位: 毫秒, 默认: 0
|
||||
Delay int `json:"delay" yaml:"delay"`
|
||||
// 间隔时间
|
||||
Interval int `json:"interval" yaml:"interval"`
|
||||
}
|
||||
|
||||
// ProviderConf 提供商配置
|
||||
type ProviderConf struct {
|
||||
// 提供商名称
|
||||
Name string `json:"name" yaml:"name"`
|
||||
// 提供商类型
|
||||
Type string `json:"type" yaml:"type"`
|
||||
// 提供商配置
|
||||
Conf map[string]string `json:"conf" yaml:"conf"`
|
||||
}
|
||||
|
||||
// CertConf 证书配置
|
||||
type CertConf struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Provider string `json:"use" yaml:"provider"`
|
||||
Dir string `json:"dir" yaml:"dir"`
|
||||
Email string `json:"email" yaml:"email"`
|
||||
Host []string `json:"host" yaml:"host"`
|
||||
}
|
129
src/conf/func.go
Normal file
129
src/conf/func.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
package conf
|
||||
|
||||
import "log"
|
||||
|
||||
func Config() *AppConfig {
|
||||
return appConf
|
||||
}
|
||||
|
||||
func EditServer(host string, port int, key string, web bool) *AppConfig {
|
||||
appConf.Server.Host = host
|
||||
appConf.Server.Port = port
|
||||
appConf.Server.Key = key
|
||||
appConf.Server.Web = web
|
||||
WriteConfig()
|
||||
return appConf
|
||||
}
|
||||
|
||||
func EditWeb(info string) *AppConfig {
|
||||
appConf.Web.Info = info
|
||||
WriteConfig()
|
||||
return appConf
|
||||
}
|
||||
|
||||
func ExecWeb(run bool) *AppConfig {
|
||||
appConf.Server.Web = run
|
||||
WriteConfig()
|
||||
return appConf
|
||||
}
|
||||
|
||||
func EditTask(delay int, interval int) *AppConfig {
|
||||
appConf.Task.Delay = delay
|
||||
appConf.Task.Interval = interval
|
||||
WriteConfig()
|
||||
return appConf
|
||||
}
|
||||
|
||||
func FindProvider(name string) (*ProviderConf, int) {
|
||||
for index, provider := range *appConf.Providers {
|
||||
if provider.Name == name {
|
||||
return &provider, index
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func AddProvider(name string, typeName string, conf map[string]string) *AppConfig {
|
||||
provider, _ := FindProvider(name)
|
||||
if provider != nil {
|
||||
log.Fatal("已存在相同名称的配置")
|
||||
return nil
|
||||
}
|
||||
provider = &ProviderConf{
|
||||
Name: name,
|
||||
Type: typeName,
|
||||
Conf: conf,
|
||||
}
|
||||
providers := *appConf.Providers
|
||||
newProviders := append(providers, *provider)
|
||||
appConf.Providers = &newProviders
|
||||
return appConf
|
||||
}
|
||||
|
||||
func EditProvider(name string, typeName string, conf map[string]string) *AppConfig {
|
||||
provider, _ := FindProvider(name)
|
||||
if provider == nil {
|
||||
log.Fatal("不存在该配置")
|
||||
return nil
|
||||
}
|
||||
provider.Type = typeName
|
||||
provider.Conf = conf
|
||||
WriteConfig()
|
||||
return appConf
|
||||
}
|
||||
|
||||
func RmProvider(name string) *AppConfig {
|
||||
_, index := FindProvider(name)
|
||||
if index == -1 {
|
||||
log.Fatal("不存在该配置")
|
||||
}
|
||||
providers := *appConf.Providers
|
||||
newProviders := append(providers[:index], providers[index+1:]...)
|
||||
appConf.Providers = &newProviders
|
||||
return appConf
|
||||
}
|
||||
|
||||
func FindCert(name string) (*CertConf, int) {
|
||||
for index, cert := range *appConf.Certs {
|
||||
if cert.Name == name {
|
||||
return &cert, index
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
func FindCertByProvider(provider string) *[]CertConf {
|
||||
var result []CertConf
|
||||
for _, cert := range *appConf.Certs {
|
||||
if cert.Provider == provider {
|
||||
result = append(result, cert)
|
||||
}
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
func AddCert(cert *CertConf) *AppConfig {
|
||||
certs := *appConf.Certs
|
||||
newCerts := append(certs, *cert)
|
||||
appConf.Certs = &newCerts
|
||||
return appConf
|
||||
}
|
||||
func EditCert(name string, cert *CertConf) *AppConfig {
|
||||
_, index := FindCert(name)
|
||||
if index == -1 {
|
||||
log.Fatal("不存在该配置")
|
||||
}
|
||||
certs := *appConf.Certs
|
||||
certs[index] = *cert
|
||||
appConf.Certs = &certs
|
||||
return appConf
|
||||
}
|
||||
func RmCert(name string) *AppConfig {
|
||||
_, index := FindCert(name)
|
||||
if index == -1 {
|
||||
log.Fatal("不存在该配置")
|
||||
}
|
||||
certs := *appConf.Certs
|
||||
newCerts := append(certs[:index], certs[index+1:]...)
|
||||
appConf.Certs = &newCerts
|
||||
return appConf
|
||||
}
|
98
src/conf/mana.go
Normal file
98
src/conf/mana.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package conf
|
||||
|
||||
import (
|
||||
"acme-mana/src/common"
|
||||
"acme-mana/src/util"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func LoadAppConfig() {
|
||||
// 读取配置文件位置
|
||||
confFile := getConfFile()
|
||||
// 判断配资文件是否存在
|
||||
if _, err := os.Stat(confFile); os.IsNotExist(err) {
|
||||
log.Println("配置文件不存在, 自动创建")
|
||||
config := defaultAppConfig()
|
||||
log.Println("默认配置文件创建成功")
|
||||
log.Println("服务器通信密钥: " + config.Server.Key)
|
||||
appConf = config
|
||||
writeConf(appConf, confFile)
|
||||
} else {
|
||||
appConf = readAppConfig(appConf)
|
||||
}
|
||||
}
|
||||
|
||||
// RefreshConfig 刷新配置
|
||||
func RefreshConfig() {
|
||||
readAppConfig(appConf)
|
||||
}
|
||||
|
||||
// WriteConfig 写入配置文件
|
||||
func WriteConfig() {
|
||||
confFile, err := common.RootCmd.PersistentFlags().GetString("conf")
|
||||
if err != nil {
|
||||
log.Fatalln("读取配置文件参数失败")
|
||||
}
|
||||
writeConf(appConf, confFile)
|
||||
}
|
||||
|
||||
func writeConf(c *AppConfig, file string) {
|
||||
out, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
log.Fatalln("序列化配置文件失败")
|
||||
}
|
||||
util.MkFileDir(file)
|
||||
err = os.WriteFile(file, out, 0644)
|
||||
if err != nil {
|
||||
log.Fatalln("写入配置文件失败")
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultAppConfig 默认配置
|
||||
func defaultAppConfig() *AppConfig {
|
||||
serverKey := util.RandomStr(32)
|
||||
|
||||
return &AppConfig{
|
||||
Server: &ServerConf{
|
||||
Web: false,
|
||||
Host: "0.0.0.0",
|
||||
Port: 36851,
|
||||
Key: serverKey,
|
||||
},
|
||||
Web: &WebConf{
|
||||
Info: "网页管理端",
|
||||
},
|
||||
Task: &TaskConf{
|
||||
Delay: 0,
|
||||
Interval: 0,
|
||||
},
|
||||
Certs: nil,
|
||||
Providers: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// getConfFile 获取配置文件位置
|
||||
func getConfFile() string {
|
||||
confFile, err := common.RootCmd.PersistentFlags().GetString("conf")
|
||||
if err != nil {
|
||||
log.Fatalln("读取配置文件参数失败")
|
||||
}
|
||||
return confFile
|
||||
}
|
||||
|
||||
// readAppConfig 读取配置文件
|
||||
func readAppConfig(c *AppConfig) *AppConfig {
|
||||
confFile := getConfFile()
|
||||
file, err := os.ReadFile(confFile)
|
||||
if err != nil {
|
||||
log.Fatalln("读取配置文件失败")
|
||||
}
|
||||
//var conf = &model.AppConfig{}
|
||||
err = yaml.Unmarshal(file, &c)
|
||||
if err != nil {
|
||||
log.Fatalln("解析配置文件失败")
|
||||
}
|
||||
return c
|
||||
}
|
3
src/conf/variable.go
Normal file
3
src/conf/variable.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package conf
|
||||
|
||||
var appConf *AppConfig
|
265
src/config.go
265
src/config.go
|
@ -1,265 +0,0 @@
|
|||
package src
|
||||
|
||||
import (
|
||||
"acme-mana/src/crypto"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/go-acme/lego/v4/log"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ReadConfig() *AppConfig {
|
||||
InitConfig()
|
||||
file, err := os.ReadFile(GetEnvConf().ConfFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var conf *AppConfig
|
||||
err = yaml.Unmarshal(file, &conf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func InitConfig() {
|
||||
// 判断当前目录下是否存在配置文件 config.yml
|
||||
_, err := os.Stat(GetEnvConf().ConfFile)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
// 配置文件不存在,则创建一个
|
||||
log.Infof("配置文件不存在,自动创建默认配置文件")
|
||||
// 生成默认配置
|
||||
conf := readNewConf()
|
||||
// 创建一个默认的配置文件
|
||||
file, err := os.Create(GetEnvConf().ConfFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
data, err := yaml.Marshal(conf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// 写入配置文件
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func readNewConf() *AppConfig {
|
||||
conf := defaultConf()
|
||||
log.Println("无配置文件, 生成配置文件")
|
||||
|
||||
conf.CertDir = scanConfDefault("请输入证书保存目录; 默认为 cert", "cert")
|
||||
isGenMsg := "是否需要自动生成用于数据传输加密的RSA密钥对? \n请输入yes(Y)/no(N) 默认为 yes"
|
||||
isGenErrMsg := "请输入yes(Y)/no(N)"
|
||||
isGenValues := []string{"yes", "no", "Y", "N"}
|
||||
isGenEncrypt := scanConfDefaultCheck(isGenMsg, "yes", isGenValues, isGenErrMsg)
|
||||
if isGenEncrypt == "no" || isGenEncrypt == "N" {
|
||||
conf.Encrypt.PriKey = scanConf("请输入RSA私钥", "请输入RSA私钥")
|
||||
conf.Encrypt.PubKey = scanConf("请输入RSA公钥", "请输入RSA公钥")
|
||||
}
|
||||
|
||||
codes := []string{"alidns", "tencentcloud", "cloudflare"}
|
||||
msg := fmt.Sprintf("请输入DNS提供商; 当前支持的: %s", strings.Join(codes, ","))
|
||||
errMsg := fmt.Sprintf("不支持的DNS提供商; 当前支持的: %s", strings.Join(codes, ","))
|
||||
conf.Use = scanConfDefaultCheck(msg, "", codes, errMsg)
|
||||
switch conf.Use {
|
||||
case "alidns":
|
||||
fmt.Printf("阿里云DNS配置帮助页: \n%s\n", "https://go-acme.github.io/lego/dns/alidns/index.html")
|
||||
fmt.Printf("阿里云令牌获取方式:\n%s\n", "https://usercenter.console.aliyun.com/#/manage/ak")
|
||||
fmt.Printf("阿里云SDK客户端项目地址:\n%s\n", "https://github.com/aliyun/alibaba-cloud-sdk-go?tab=readme-ov-file")
|
||||
conf.Provider.Ali.RegionID = scanConf("请输入阿里云Region ID", "请输入阿里云Region ID")
|
||||
conf.Provider.Ali.APIKey = scanConf("请输入阿里云API Key", "请输入阿里云API Key")
|
||||
conf.Provider.Ali.SecretKey = scanConf("请输入阿里云Secret Key", "请输入阿里云Secret Key")
|
||||
case "tencentcloud":
|
||||
fmt.Printf("腾讯云DNS配置帮助页: \n%s\n", "https://go-acme.github.io/lego/dns/tencentcloud/index.html")
|
||||
fmt.Printf("腾讯云令牌获取方式: \n%s\n", "https://console.cloud.tencent.com/cam/capi")
|
||||
fmt.Printf("腾讯云SDK客户端项目地址: \n%s\n", "https://github.com/tencentcloud/tencentcloud-sdk-go?tab=readme-ov-file")
|
||||
conf.Provider.Tencent.SecretId = scanConf("请输入腾讯云Secret Id", "请输入腾讯云Secret Id")
|
||||
conf.Provider.Tencent.SecretKey = scanConf("请输入腾讯云Secret Key", "请输入腾讯云Secret Key")
|
||||
case "cloudflare":
|
||||
fmt.Printf("Cloudflare DNS配置帮助页: \n%s\n", "https://go-acme.github.io/lego/dns/tencentcloud/index.html")
|
||||
fmt.Printf("Cloudflare 令牌获取方式: \n%s\n", "https://blog.cloudflare.com/zh-cn/api-tokens-general-availability/")
|
||||
fmt.Printf("Cloudflare SDK客户端项目地址: \n%s\n", "https://github.com/cloudflare/cloudflare-go")
|
||||
conf.Provider.CloudFlare.Token = scanConf("请输入CloudFlare DNS API Token", "请输入CloudFlare Token")
|
||||
}
|
||||
isAddDomainMsg := "是否需要添加证书获取配置?\n您可以在此处通过控制台交互添加;也可以在配置文件创建后,直接修改配置文件.\n请输入yes(Y)/no(N) 默认为 yes"
|
||||
isAddDomain := scanConfDefaultCheck(isAddDomainMsg, "yes", isGenValues, isGenErrMsg)
|
||||
if isAddDomain == "no" || isAddDomain == "N" {
|
||||
fmt.Printf("您可以通过手动修改%s文件, 调整证书获取配置.", GetEnvConf().ConfFile)
|
||||
conf.Domains = []Domain{}
|
||||
return conf
|
||||
}
|
||||
conf.Domains = []Domain{}
|
||||
for {
|
||||
name := scanConf("请输入配置名称", "配置名称不能为空")
|
||||
email := scanConf("请输入邮箱", "邮箱不能为空")
|
||||
host := scanConf("请输入主机名;支持泛解析;\n支持多个域名,多个域名用,(英文逗号)分割;\n如: example.com,*.example.com\n", "主机名不能为空")
|
||||
// 将host通过,分割为数组
|
||||
hosts := strings.Split(host, ",")
|
||||
conf.Domains = append(conf.Domains, Domain{
|
||||
Name: name,
|
||||
Email: email,
|
||||
Host: hosts,
|
||||
})
|
||||
isAddNextDomainMsg := "是否需要继续添加证书获取配置?\n请输入yes(Y)/no(N) 默认为 yes"
|
||||
isAddNextDomain := scanConfDefaultCheck(isAddNextDomainMsg, "yes", isGenValues, isGenErrMsg)
|
||||
if isAddNextDomain == "no" || isAddNextDomain == "N" {
|
||||
fmt.Printf("您后续可以通过手动修改%s文件, 调整证书获取配置.", GetEnvConf().ConfFile)
|
||||
return conf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读取用户输入
|
||||
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 scanConfDefault(msg string, defaultContent 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 == "" {
|
||||
return defaultContent
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func scanConfDefaultCheck(msg string, defaultContent string, values []string, errMsg string) string {
|
||||
for {
|
||||
content := scanConfDefault(msg, defaultContent)
|
||||
// 判断内容是否在values中
|
||||
for _, value := range values {
|
||||
if value == content {
|
||||
return content
|
||||
}
|
||||
}
|
||||
log.Println(errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultConf() *AppConfig {
|
||||
//priKey, pubKey, err := GenRsa()
|
||||
priKey, pubKey, err := crypto.GenRSA()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return &AppConfig{
|
||||
Use: "Ali",
|
||||
CertDir: "cert",
|
||||
Provider: AppProvider{
|
||||
Ali: AliProvider{
|
||||
RegionID: "cn-hangzhou",
|
||||
APIKey: "api_key",
|
||||
SecretKey: "secret_key",
|
||||
},
|
||||
Tencent: TencentProvider{
|
||||
SecretId: "secret_id",
|
||||
SecretKey: "secret_key",
|
||||
},
|
||||
CloudFlare: CloudFlareProvider{
|
||||
Token: "token",
|
||||
},
|
||||
},
|
||||
Domains: []Domain{
|
||||
{
|
||||
Name: "example.com",
|
||||
Email: "email@example.com",
|
||||
Host: []string{"www.example.com"},
|
||||
},
|
||||
},
|
||||
Encrypt: Encrypt{
|
||||
PriKey: priKey,
|
||||
PubKey: pubKey,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Use string
|
||||
|
||||
CertDir string
|
||||
|
||||
Provider AppProvider
|
||||
|
||||
Domains []Domain
|
||||
|
||||
Encrypt Encrypt
|
||||
}
|
||||
|
||||
type AppProvider struct {
|
||||
Ali AliProvider
|
||||
Tencent TencentProvider
|
||||
CloudFlare CloudFlareProvider
|
||||
}
|
||||
type AliProvider struct {
|
||||
RegionID string
|
||||
APIKey string
|
||||
SecretKey string
|
||||
}
|
||||
type TencentProvider struct {
|
||||
SecretId string
|
||||
SecretKey string
|
||||
}
|
||||
type CloudFlareProvider struct {
|
||||
Token string
|
||||
}
|
||||
type Domain struct {
|
||||
Name string
|
||||
Email string
|
||||
Host []string
|
||||
}
|
||||
type Encrypt struct {
|
||||
PriKey string
|
||||
PubKey string
|
||||
}
|
||||
|
||||
func (conf AppConfig) FindDomain(name string) *Domain {
|
||||
for _, domain := range conf.Domains {
|
||||
if domain.Name == name {
|
||||
return &domain
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const ENV_CONF_FILE = "ACME_MANA_CONF_FILE"
|
||||
|
||||
func InitRuntimeConf() *EnvConf {
|
||||
return &EnvConf{
|
||||
ConfFile: env.GetOrDefaultString(ENV_CONF_FILE, "config.yml"),
|
||||
}
|
||||
}
|
||||
|
||||
type EnvConf struct {
|
||||
ConfFile string
|
||||
}
|
292
src/daemon.go
292
src/daemon.go
|
@ -1,292 +0,0 @@
|
|||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var stdout *os.File
|
||||
var stderr *os.File
|
||||
|
||||
// Start 启动/*
|
||||
func Start() {
|
||||
initLog()
|
||||
args := os.Args
|
||||
if len(args) <= 1 {
|
||||
//daemonStart()
|
||||
doTask()
|
||||
return
|
||||
}
|
||||
command := args[1]
|
||||
switch command {
|
||||
case "block":
|
||||
doTask()
|
||||
case "start":
|
||||
daemonStart()
|
||||
case "stop":
|
||||
daemonStop()
|
||||
case "status":
|
||||
daemonStatus()
|
||||
case "dump":
|
||||
dumpConfig()
|
||||
case "domains":
|
||||
showDomains()
|
||||
case "pubkey":
|
||||
showPubkey()
|
||||
case "apply":
|
||||
applyOnce()
|
||||
case "-s":
|
||||
daemonCommand()
|
||||
default:
|
||||
log.Fatalf("Unknown command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
初始化日志文件
|
||||
*/
|
||||
func initLog() {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get current working directory: %v", err)
|
||||
}
|
||||
dir := path.Join(pwd, "log")
|
||||
_, err = os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
err := os.Mkdir(dir, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create directory: %v", err)
|
||||
}
|
||||
}
|
||||
outFile, err := os.OpenFile(path.Join(dir, "out.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
defer outFile.Close()
|
||||
stdout = outFile
|
||||
|
||||
errFile, err := os.OpenFile(path.Join(dir, "err.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
defer stderr.Close()
|
||||
stderr = errFile
|
||||
}
|
||||
|
||||
/*
|
||||
守护进程启动
|
||||
*/
|
||||
func daemonStart() {
|
||||
GetAppConfig()
|
||||
isDaemon := os.Getenv("GO_DAEMON")
|
||||
log.Println("Run Daemon, DAEMON Is " + isDaemon)
|
||||
if isDaemon != "1" {
|
||||
// 直接启动
|
||||
if isRunning() {
|
||||
log.Println("Daemon is already running.")
|
||||
return
|
||||
}
|
||||
|
||||
workPath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get executable path: %v", err)
|
||||
}
|
||||
cmd := exec.Cmd{
|
||||
Path: workPath,
|
||||
Args: os.Args,
|
||||
Dir: filepath.Dir(workPath),
|
||||
Env: append(os.Environ(), "GO_DAEMON=1"),
|
||||
//Stdin: os.Stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
//Stdin: os.Stdin,
|
||||
//Stdout: os.Stdout,
|
||||
//Stderr: os.Stderr,
|
||||
SysProcAttr: &syscall.SysProcAttr{},
|
||||
}
|
||||
|
||||
log.Println("Starting daemon...")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start daemon: %v", err)
|
||||
}
|
||||
err = os.WriteFile(PidFile, []byte(strconv.Itoa(cmd.Process.Pid)), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write PID file: %v", err)
|
||||
}
|
||||
log.Printf("Daemon started with PID: %d", cmd.Process.Pid)
|
||||
os.Exit(0)
|
||||
|
||||
} else {
|
||||
// 子进程
|
||||
doTask()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
守护进程停止
|
||||
*/
|
||||
func daemonStop() {
|
||||
//pid, err := readPID()
|
||||
//if err != nil {
|
||||
// log.Fatalf("Failed to stop daemon: %v", err)
|
||||
//}
|
||||
//
|
||||
//process, err := os.FindProcess(pid)
|
||||
//if err != nil {
|
||||
// log.Fatalf("Failed to find process: %v", err)
|
||||
//}
|
||||
//
|
||||
//err = process.Signal(syscall.SIGTERM)
|
||||
//if err != nil {
|
||||
// log.Fatalf("Failed to stop process: %v", err)
|
||||
//}
|
||||
//
|
||||
//os.Remove(PidFile)
|
||||
//os.Remove(SocketFile)
|
||||
//log.Println("Daemon stopped.")
|
||||
sendCommand("stop")
|
||||
}
|
||||
|
||||
/*
|
||||
守护进程状态
|
||||
*/
|
||||
func daemonStatus() {
|
||||
if isRunning() {
|
||||
log.Println("Daemon is running.")
|
||||
} else {
|
||||
log.Println("Daemon is not running.")
|
||||
}
|
||||
}
|
||||
|
||||
func dumpConfig() {
|
||||
//config, err := json.Marshal(GetAppConfig())
|
||||
config, err := json.MarshalIndent(GetAppConfig(), "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal config: %v", err)
|
||||
}
|
||||
log.Println(string(config))
|
||||
}
|
||||
|
||||
func applyOnce() {
|
||||
if len(os.Args) < 3 {
|
||||
log.Fatalf("Please enter domain name!")
|
||||
}
|
||||
name := os.Args[2]
|
||||
if name == "" {
|
||||
log.Fatalf("No domain specified!")
|
||||
}
|
||||
domain := GetAppConfig().FindDomain(name)
|
||||
if domain == nil {
|
||||
log.Fatalf("Domain not found: %s", name)
|
||||
}
|
||||
Apply(*domain)
|
||||
}
|
||||
|
||||
func showDomains() {
|
||||
domains := GetAppConfig().Domains
|
||||
// 格式化为json并打印
|
||||
config, err := json.MarshalIndent(domains, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal config: %v", err)
|
||||
}
|
||||
log.Println(string(config))
|
||||
}
|
||||
|
||||
func showPubkey() {
|
||||
key := GetAppConfig().Encrypt.PubKey
|
||||
log.Println(key)
|
||||
}
|
||||
|
||||
// 守护进程接收命令
|
||||
func daemonCommand() {
|
||||
log.Println("Sending command...")
|
||||
command := os.Args[2]
|
||||
sendCommand(command)
|
||||
|
||||
}
|
||||
|
||||
// 发送命令
|
||||
func sendCommand(command string) {
|
||||
conn, err := net.Dial("unix", SocketFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to daemon: %v", err)
|
||||
}
|
||||
defer func(conn net.Conn) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to close connection: %v", err)
|
||||
}
|
||||
}(conn)
|
||||
|
||||
_, err = conn.Write([]byte(command))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to send command: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Sending command '%s' to daemon with PID: %d", command, 0)
|
||||
}
|
||||
|
||||
// 业务进程执行任务
|
||||
func doTask() {
|
||||
|
||||
// 监听主进程下发的指令
|
||||
go InitSocket()
|
||||
|
||||
// 监听HTTP请求
|
||||
go InitHttpServer("0.0.0.0", 10000)
|
||||
|
||||
// 自动执行域名证书更新
|
||||
go AutoRefreshCert()
|
||||
|
||||
// 阻止退出
|
||||
select {}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
是否已启动
|
||||
*/
|
||||
func isRunning() bool {
|
||||
log.Println("Checking if daemon is running...")
|
||||
pid, err := readPID()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
log.Println("Found process:", process)
|
||||
if err != nil {
|
||||
log.Println("Failed to find process:", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
//err = process.Signal(syscall.Signal(0))
|
||||
//log.Println("Signal result:", err)
|
||||
//return err == nil
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
读取PID文件
|
||||
*/
|
||||
func readPID() (int, error) {
|
||||
log.Println("Reading PID file...")
|
||||
data, err := os.ReadFile(PidFile)
|
||||
if err != nil {
|
||||
log.Println("Failed to read PID file:", err)
|
||||
return 0, err
|
||||
}
|
||||
log.Println("PID file content:", string(data))
|
||||
|
||||
pid, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
log.Println("Failed to parse PID:", err)
|
||||
return 0, err
|
||||
}
|
||||
log.Println("PID:", pid)
|
||||
|
||||
return pid, nil
|
||||
}
|
145
src/http.go
145
src/http.go
|
@ -1,145 +0,0 @@
|
|||
package src
|
||||
|
||||
import (
|
||||
"acme-mana/src/crypto"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func InitHttpServer(host string, port int) {
|
||||
|
||||
log.Println("Start http server, Listen " + strconv.Itoa(port))
|
||||
h := gin.Default()
|
||||
h.GET("/api/v1/refresh", refreshCert)
|
||||
h.GET("/api/v1/cert", getCert)
|
||||
h.GET("/api/v1/domain/list", domainList)
|
||||
err := h.Run(host + ":" + strconv.Itoa(port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func domainList(c *gin.Context) {
|
||||
token := getToken(c)
|
||||
domains := GetAppConfig().Domains
|
||||
data, err := json.Marshal(domains)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
encryptData := encryptResult(string(data), token)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": encryptData,
|
||||
})
|
||||
}
|
||||
|
||||
func getCert(c *gin.Context) {
|
||||
name := c.Query("name")
|
||||
token := getToken(c)
|
||||
|
||||
dir := GetAppConfig().CertDir
|
||||
dir = filepath.Join(dir, name)
|
||||
_, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Name does not exist.",
|
||||
})
|
||||
return
|
||||
}
|
||||
crtFilePath := path.Join(dir, CertFileName)
|
||||
crtContent, err := os.ReadFile(crtFilePath)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Failed to read crt file.",
|
||||
})
|
||||
return
|
||||
}
|
||||
crt := string(crtContent)
|
||||
|
||||
keyFilePath := path.Join(dir, KeyFileName)
|
||||
keyContent, err := os.ReadFile(keyFilePath)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Failed to read key file.",
|
||||
})
|
||||
return
|
||||
}
|
||||
key := string(keyContent)
|
||||
|
||||
certInfoFilePath := path.Join(dir, CertInfoFileName)
|
||||
certInfoContent, err := os.ReadFile(certInfoFilePath)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Failed to read cert info file.",
|
||||
})
|
||||
return
|
||||
}
|
||||
certInfo := string(certInfoContent)
|
||||
|
||||
data, err := json.Marshal(&DomainData{
|
||||
Fullchain: crt,
|
||||
Key: key,
|
||||
Info: certInfo,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
encryptData := encryptResult(string(data), token)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "Success",
|
||||
"data": encryptData,
|
||||
})
|
||||
}
|
||||
|
||||
func refreshCert(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
domain := GetAppConfig().FindDomain(name)
|
||||
if domain == nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Name does not exist.",
|
||||
})
|
||||
return
|
||||
}
|
||||
Apply(*domain)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "Success",
|
||||
})
|
||||
}
|
||||
|
||||
func getToken(c *gin.Context) (token string) {
|
||||
token = c.Query("token")
|
||||
token = decryptParam(token)
|
||||
return
|
||||
}
|
||||
|
||||
func decryptParam(param string) string {
|
||||
priKey := GetAppConfig().Encrypt.PriKey
|
||||
tokenBytes, err := hex.DecodeString(param)
|
||||
tokenPlain, err := crypto.DecryptRSABase64(priKey, tokenBytes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return string(tokenPlain)
|
||||
}
|
||||
|
||||
func encryptResult(content string, token string) string {
|
||||
result := crypto.EncryptAES([]byte(token), []byte(content))
|
||||
return base64.StdEncoding.EncodeToString(result)
|
||||
}
|
14
src/program.go
Normal file
14
src/program.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package src
|
||||
|
||||
import (
|
||||
"acme-mana/src/cmd"
|
||||
)
|
||||
|
||||
func StartProgram() {
|
||||
_, err := cmd.InitCmd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
93
src/server/http-server.go
Normal file
93
src/server/http-server.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"acme-mana/src/conf"
|
||||
"acme-mana/src/server/http_handler"
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var staticFiles embed.FS
|
||||
|
||||
type HttpServer struct {
|
||||
server *http.Server
|
||||
status bool
|
||||
engine *gin.Engine
|
||||
}
|
||||
|
||||
func (s *HttpServer) Init() {
|
||||
config := conf.Config()
|
||||
var serverConf = config.Server
|
||||
s.initServer(serverConf.Host, serverConf.Port)
|
||||
}
|
||||
|
||||
// initServer 初始化
|
||||
func (s *HttpServer) initServer(host string, port int) {
|
||||
//gin.SetMode(gin.ReleaseMode)
|
||||
s.engine = gin.Default()
|
||||
s.register()
|
||||
s.status = false
|
||||
|
||||
s.server = &http.Server{
|
||||
Addr: host + ":" + strconv.Itoa(port),
|
||||
Handler: s.engine,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HttpServer) Start() {
|
||||
if s.status {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := s.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("listen: %s\n\n", err)
|
||||
s.status = true
|
||||
} else {
|
||||
s.status = false
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *HttpServer) Stop() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
err := HttpInstance.server.Shutdown(ctx)
|
||||
s.status = false
|
||||
if err != nil {
|
||||
log.Fatalln("HttpInstance Shutdown:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HttpServer) Status() bool {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *HttpServer) register() {
|
||||
service := s.engine
|
||||
service.Use(gin.Logger())
|
||||
service.Use(http_handler.GlobalErrorHandler())
|
||||
|
||||
fs := http.FS(staticFiles)
|
||||
service.StaticFS("/s", fs)
|
||||
|
||||
certHandler := http_handler.CertHandlerInstance
|
||||
certGroup := service.Group("/api/v1/cert", http_handler.AuthMiddleware())
|
||||
certGroup.GET("/", certHandler.Get)
|
||||
|
||||
providerHandler := http_handler.ProviderHandlerInstance
|
||||
providerGroup := service.Group("/api/v1/provider", http_handler.AuthMiddleware())
|
||||
providerGroup.GET("/list", providerHandler.List)
|
||||
providerGroup.GET("/map", providerHandler.Map)
|
||||
|
||||
confHandler := http_handler.ConfHandlerInstance
|
||||
confGroup := service.Group("/api/v1", http_handler.AuthMiddleware())
|
||||
confGroup.GET("/conf", confHandler.Get)
|
||||
|
||||
}
|
149
src/server/http-server_test.go
Normal file
149
src/server/http-server_test.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpServer_InitServer(t *testing.T) {
|
||||
type fields struct {
|
||||
server *http.Server
|
||||
status bool
|
||||
engine *gin.Engine
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &HttpServer{
|
||||
server: tt.fields.server,
|
||||
status: tt.fields.status,
|
||||
engine: tt.fields.engine,
|
||||
}
|
||||
s.Init()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpServer_Start(t *testing.T) {
|
||||
type fields struct {
|
||||
server *http.Server
|
||||
status bool
|
||||
engine *gin.Engine
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
{
|
||||
name: "0.0.0.0",
|
||||
fields: fields{
|
||||
server: nil,
|
||||
status: false,
|
||||
engine: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &HttpServer{
|
||||
server: tt.fields.server,
|
||||
status: tt.fields.status,
|
||||
engine: tt.fields.engine,
|
||||
}
|
||||
s.initServer("0.0.0.0", 35541)
|
||||
s.Start()
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
fmt.Println("Shutting down server...")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpServer_Stop(t *testing.T) {
|
||||
type fields struct {
|
||||
server *http.Server
|
||||
status bool
|
||||
engine *gin.Engine
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &HttpServer{
|
||||
server: tt.fields.server,
|
||||
status: tt.fields.status,
|
||||
engine: tt.fields.engine,
|
||||
}
|
||||
s.Stop()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpServer_register(t *testing.T) {
|
||||
type fields struct {
|
||||
server *http.Server
|
||||
status bool
|
||||
engine *gin.Engine
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &HttpServer{
|
||||
server: tt.fields.server,
|
||||
status: tt.fields.status,
|
||||
engine: tt.fields.engine,
|
||||
}
|
||||
s.register()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpServer_initServer(t *testing.T) {
|
||||
type fields struct {
|
||||
server *http.Server
|
||||
status bool
|
||||
engine *gin.Engine
|
||||
}
|
||||
type args struct {
|
||||
host string
|
||||
port int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &HttpServer{
|
||||
server: tt.fields.server,
|
||||
status: tt.fields.status,
|
||||
engine: tt.fields.engine,
|
||||
}
|
||||
s.initServer(tt.args.host, tt.args.port)
|
||||
})
|
||||
}
|
||||
}
|
13
src/server/http_handler/auth.go
Normal file
13
src/server/http_handler/auth.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package http_handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
// 进行身份认证
|
||||
c.Next()
|
||||
}
|
||||
}
|
43
src/server/http_handler/cert.go
Normal file
43
src/server/http_handler/cert.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package http_handler
|
||||
|
||||
import (
|
||||
"acme-mana/src/acme"
|
||||
"acme-mana/src/conf"
|
||||
"acme-mana/src/server/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
)
|
||||
|
||||
var CertHandlerInstance = &CertHandler{}
|
||||
|
||||
type CertHandler struct {
|
||||
}
|
||||
|
||||
func (h *CertHandler) List(c *gin.Context) {
|
||||
log.Println("list cert")
|
||||
certs := conf.Config().Certs
|
||||
c.JSON(200, model.SuccessD(certs))
|
||||
}
|
||||
|
||||
func (h *CertHandler) Get(c *gin.Context) {
|
||||
log.Println("get cert")
|
||||
name := c.Query("name")
|
||||
if name == "" {
|
||||
c.Error(model.NewAppError(500, "请输入名称"))
|
||||
return
|
||||
}
|
||||
cert, _ := conf.FindCert(name)
|
||||
c.JSON(200, model.SuccessD(cert))
|
||||
}
|
||||
|
||||
func (h *CertHandler) Refresh(c *gin.Context) {
|
||||
log.Println("refresh cert")
|
||||
name := c.Query("name")
|
||||
if name == "" {
|
||||
c.Error(model.NewAppError(500, "请输入名称"))
|
||||
return
|
||||
}
|
||||
cert, _ := conf.FindCert(name)
|
||||
acme.Apply(cert)
|
||||
c.JSON(200, model.Success())
|
||||
}
|
17
src/server/http_handler/conf.go
Normal file
17
src/server/http_handler/conf.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package http_handler
|
||||
|
||||
import (
|
||||
"acme-mana/src/conf"
|
||||
"acme-mana/src/server/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var ConfHandlerInstance = &ConfHandler{}
|
||||
|
||||
type ConfHandler struct {
|
||||
}
|
||||
|
||||
func (h *ConfHandler) Get(c *gin.Context) {
|
||||
config := conf.Config()
|
||||
c.JSON(200, model.SuccessD(config))
|
||||
}
|
34
src/server/http_handler/error.go
Normal file
34
src/server/http_handler/error.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package http_handler
|
||||
|
||||
import (
|
||||
"acme-mana/src/server/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GlobalErrorHandler 是一个全局错误处理器中间件
|
||||
func GlobalErrorHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 继续执行后续中间件和处理函数
|
||||
c.Next()
|
||||
|
||||
// 获取上下文中的错误
|
||||
if err, ok := c.Get("error"); ok {
|
||||
switch typedErr := err.(type) {
|
||||
case *model.AppError:
|
||||
c.JSON(200, &model.Result{
|
||||
Code: typedErr.Code,
|
||||
Message: typedErr.Message,
|
||||
Data: nil,
|
||||
})
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Internal Server Error",
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
20
src/server/http_handler/provider.go
Normal file
20
src/server/http_handler/provider.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package http_handler
|
||||
|
||||
import (
|
||||
"acme-mana/src/acme"
|
||||
"acme-mana/src/server/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ProviderHandler struct {
|
||||
}
|
||||
|
||||
var ProviderHandlerInstance = &ProviderHandler{}
|
||||
|
||||
func (h *ProviderHandler) List(c *gin.Context) {
|
||||
c.JSON(200, model.SuccessD(acme.ListProvider()))
|
||||
}
|
||||
|
||||
func (h *ProviderHandler) Map(c *gin.Context) {
|
||||
c.JSON(200, model.SuccessD(acme.MapProvider()))
|
||||
}
|
19
src/server/model/app-error.go
Normal file
19
src/server/model/app-error.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package model
|
||||
|
||||
type AppError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Error 实现 error 接口
|
||||
func (e *AppError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewAppError 创建一个新的 AppError
|
||||
func NewAppError(code int, message string) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
27
src/server/model/result.go
Normal file
27
src/server/model/result.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package model
|
||||
|
||||
type Result struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func (r Result) SetData(data any) Result {
|
||||
r.Data = data
|
||||
return r
|
||||
}
|
||||
|
||||
func Success() *Result {
|
||||
return &Result{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
}
|
||||
}
|
||||
|
||||
func SuccessD(data any) *Result {
|
||||
return &Result{
|
||||
Code: 200,
|
||||
Message: "success",
|
||||
Data: data,
|
||||
}
|
||||
}
|
53
src/server/socker-server.go
Normal file
53
src/server/socker-server.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"acme-mana/src/common"
|
||||
socker_handler "acme-mana/src/server/socket_handler"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SocketServer struct {
|
||||
}
|
||||
|
||||
func (s *SocketServer) InitSocketServer() {
|
||||
|
||||
}
|
||||
|
||||
func (s *SocketServer) Start() {
|
||||
go start()
|
||||
}
|
||||
|
||||
func start() {
|
||||
log.Println("启动指令监听服务...")
|
||||
// 删除旧的 socket 文件
|
||||
if _, err := os.Stat(common.SocketFile); err == nil {
|
||||
err := os.Remove(common.SocketFile)
|
||||
if err != nil {
|
||||
log.Fatalf("无法删除Socket文件: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", common.SocketFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen on socket: %v", err)
|
||||
}
|
||||
defer func(listener net.Listener) {
|
||||
err := listener.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("无法关闭Socket文件监听: %v", err)
|
||||
}
|
||||
}(listener)
|
||||
|
||||
for {
|
||||
log.Println("等待连接...")
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Printf("无法建立连接: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go socker_handler.HandleConnection(conn)
|
||||
}
|
||||
}
|
1
src/server/socket_handler/cert.go
Normal file
1
src/server/socket_handler/cert.go
Normal file
|
@ -0,0 +1 @@
|
|||
package socket_handler
|
47
src/server/socket_handler/handler.go
Normal file
47
src/server/socket_handler/handler.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package socket_handler
|
||||
|
||||
import (
|
||||
"acme-mana/src/common"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func HandleConnection(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read command: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
command := string(buf[:n])
|
||||
log.Printf("Received command: %s", command)
|
||||
|
||||
// 在这里处理接收到的命令
|
||||
switch command {
|
||||
case "stop":
|
||||
onStop()
|
||||
default:
|
||||
onCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
收到命令
|
||||
*/
|
||||
func onCommand(command string) {
|
||||
|
||||
}
|
||||
|
||||
// 收到停止命令
|
||||
func onStop() {
|
||||
log.Println("停止守护进程...")
|
||||
os.Remove(common.PidFile)
|
||||
log.Println("删除PID文件...")
|
||||
os.Remove(common.SocketFile)
|
||||
log.Println("删除Socket文件...")
|
||||
os.Exit(0)
|
||||
}
|
10
src/server/static/index.html
Normal file
10
src/server/static/index.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
123123
|
||||
</body>
|
||||
</html>
|
4
src/server/variable.go
Normal file
4
src/server/variable.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package server
|
||||
|
||||
var HttpInstance *HttpServer
|
||||
var SocketInstance *SocketServer
|
86
src/task.go
86
src/task.go
|
@ -1,86 +0,0 @@
|
|||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AutoRefreshCertTicker = time.NewTicker(time.Hour)
|
||||
|
||||
func AutoRefreshCert() {
|
||||
log.Println("Start auto refresh cert")
|
||||
defer AutoRefreshCertTicker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-AutoRefreshCertTicker.C:
|
||||
doRefreshCert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doRefreshCert() {
|
||||
domains := GetAppConfig().Domains
|
||||
for _, domain := range domains {
|
||||
doRefreshCertOnce(domain)
|
||||
}
|
||||
}
|
||||
|
||||
func doRefreshCertOnce(domain Domain) {
|
||||
name := domain.Name
|
||||
dir := GetAppConfig().CertDir
|
||||
certDir := path.Join(dir, name)
|
||||
if !ValidExist(certDir, domain) {
|
||||
Apply(domain)
|
||||
}
|
||||
infoFile := path.Join(certDir, CertInfoFileName)
|
||||
certInfo := ParseCertInfo(infoFile, domain)
|
||||
log.Println("Checking if the certificate is expired, Domain: {}", name)
|
||||
if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour {
|
||||
log.Println("Apply for a certificate that is about to expire, domain name:", name)
|
||||
Apply(domain)
|
||||
}
|
||||
}
|
||||
|
||||
func ValidExist(certDir string, domain Domain) bool {
|
||||
_, err := os.Stat(certDir)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Applying for a certificate, Domain: %s certificate directory does not exist!", domain.Name)
|
||||
return false
|
||||
}
|
||||
if !ExistFile(certDir, CertFileName) {
|
||||
log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, CertFileName)
|
||||
return false
|
||||
}
|
||||
if !ExistFile(certDir, KeyFileName) {
|
||||
log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, KeyFileName)
|
||||
return false
|
||||
}
|
||||
if !ExistFile(certDir, CertInfoFileName) {
|
||||
log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, CertInfoFileName)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ParseCertInfo(infoFile string, domain Domain) CertInfo {
|
||||
infoBytes, err := os.ReadFile(infoFile)
|
||||
if err != nil {
|
||||
log.Println("Failed to read cert info file, Domain: {}", domain.Name)
|
||||
}
|
||||
var certInfo CertInfo
|
||||
err = json.Unmarshal(infoBytes, &certInfo)
|
||||
//if err != nil {
|
||||
// log.Println("Failed to parse cert info file, Domain: {}", domain.Name)
|
||||
//}
|
||||
return certInfo
|
||||
}
|
||||
|
||||
func ExistFile(dir string, fileName string) bool {
|
||||
f := path.Join(dir, fileName)
|
||||
_, err := os.Stat(f)
|
||||
return !os.IsNotExist(err)
|
||||
|
||||
}
|
114
src/task/task.go
Normal file
114
src/task/task.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package task
|
||||
|
||||
import (
|
||||
"acme-mana/src/acme"
|
||||
"acme-mana/src/common"
|
||||
"acme-mana/src/conf"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AutoRefreshCertTicker = time.NewTicker(time.Hour)
|
||||
|
||||
func AutoRefreshCert() {
|
||||
log.Println("Start auto refresh cert")
|
||||
defer AutoRefreshCertTicker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-AutoRefreshCertTicker.C:
|
||||
doRefreshCert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doRefreshCert() {
|
||||
config := conf.Config()
|
||||
certs := config.Certs
|
||||
|
||||
for _, cert := range *certs {
|
||||
doRefreshCertOne(&cert)
|
||||
}
|
||||
|
||||
//domains := GetAppConfig().Domains
|
||||
//for _, domain := range domains {
|
||||
// doRefreshCertOnce(domain)
|
||||
//}
|
||||
}
|
||||
|
||||
func doRefreshCertOne(cert *conf.CertConf) {
|
||||
name := cert.Name
|
||||
dir := cert.Dir
|
||||
certDir := path.Join(dir, name)
|
||||
if !ValidExist(certDir, cert) {
|
||||
acme.Apply(cert)
|
||||
return
|
||||
}
|
||||
infoFile := path.Join(certDir, common.CertInfoFileName)
|
||||
certInfo := ParseCertInfo(infoFile, cert)
|
||||
log.Println("校验当前证书有效时间, 证书: {}", name)
|
||||
if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour {
|
||||
log.Println("申请即将过期的证书: {}", name)
|
||||
acme.Apply(cert)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//func doRefreshCertOnce(domain Domain) {
|
||||
// name := domain.Name
|
||||
// dir := GetAppConfig().CertDir
|
||||
// certDir := path.Join(dir, name)
|
||||
// if !ValidExist(certDir, domain) {
|
||||
// Apply(domain)
|
||||
// }
|
||||
// infoFile := path.Join(certDir, CertInfoFileName)
|
||||
// certInfo := ParseCertInfo(infoFile, domain)
|
||||
// log.Println("Checking if the certificate is expired, Domain: {}", name)
|
||||
// if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour {
|
||||
// log.Println("Apply for a certificate that is about to expire, domain name:", name)
|
||||
// Apply(domain)
|
||||
// }
|
||||
//}
|
||||
|
||||
func ValidExist(certDir string, cert *conf.CertConf) bool {
|
||||
_, err := os.Stat(certDir)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Applying for a certificate, Domain: %s certificate directory does not exist!", cert.Name)
|
||||
return false
|
||||
}
|
||||
if !ExistFile(certDir, common.CertFileName) {
|
||||
log.Printf("Applying for a certificate, Domain: %s %s does not exist!", cert.Name, common.CertFileName)
|
||||
return false
|
||||
}
|
||||
if !ExistFile(certDir, common.KeyFileName) {
|
||||
log.Printf("Applying for a certificate, Domain: %s %s does not exist!", cert.Name, common.KeyFileName)
|
||||
return false
|
||||
}
|
||||
if !ExistFile(certDir, common.CertInfoFileName) {
|
||||
log.Printf("Applying for a certificate, Domain: %s %s does not exist!", cert.Name, common.CertInfoFileName)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ParseCertInfo(infoFile string, cert *conf.CertConf) *acme.CertInfo {
|
||||
infoBytes, err := os.ReadFile(infoFile)
|
||||
if err != nil {
|
||||
log.Println("Failed to read cert info file, Domain: {}", cert.Name)
|
||||
}
|
||||
var certInfo acme.CertInfo
|
||||
err = json.Unmarshal(infoBytes, &certInfo)
|
||||
//if err != nil {
|
||||
// log.Println("Failed to parse cert info file, Domain: {}", domain.Name)
|
||||
//}
|
||||
return &certInfo
|
||||
}
|
||||
|
||||
func ExistFile(dir string, fileName string) bool {
|
||||
f := path.Join(dir, fileName)
|
||||
_, err := os.Stat(f)
|
||||
return !os.IsNotExist(err)
|
||||
|
||||
}
|
37
src/util/bash.go
Normal file
37
src/util/bash.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/manifoldco/promptui"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ReadLine(label string, defaultContent string) string {
|
||||
for {
|
||||
prompt := &promptui.Prompt{
|
||||
Label: label,
|
||||
Default: defaultContent,
|
||||
}
|
||||
line, err := prompt.Run()
|
||||
if err.Error() == "^D" || err.Error() == "^C" {
|
||||
os.Exit(0)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("输入错误:", err)
|
||||
continue
|
||||
}
|
||||
return line
|
||||
}
|
||||
}
|
||||
func ReadInt(label string, defaultContent string) int {
|
||||
for {
|
||||
line := ReadLine(label, defaultContent)
|
||||
intValue, err := strconv.Atoi(line)
|
||||
if err != nil {
|
||||
fmt.Println("输入错误, 请输入整数:", err)
|
||||
continue
|
||||
}
|
||||
return intValue
|
||||
}
|
||||
}
|
18
src/util/file-util.go
Normal file
18
src/util/file-util.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// MkFileDir 创建文件所在的目录
|
||||
func MkFileDir(file string) {
|
||||
dir := filepath.Dir(file)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
log.Fatal("创建目录失败!", err)
|
||||
}
|
||||
}
|
||||
}
|
17
src/util/random.go
Normal file
17
src/util/random.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
|
||||
func RandomStr(size int) string {
|
||||
rand.NewSource(time.Now().UnixNano())
|
||||
b := make([]byte, size)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package src
|
||||
|
||||
const PidFile = "acme-mana.pid"
|
||||
const SocketFile = "acme-mana.sock"
|
||||
|
||||
const CertFileName = "fullchain.pem"
|
||||
const KeyFileName = "privkey.pem"
|
||||
const CertInfoFileName = "info.json"
|
||||
|
||||
var appConfig *AppConfig = ReadConfig()
|
||||
|
||||
func GetAppConfig() *AppConfig {
|
||||
return appConfig
|
||||
}
|
||||
|
||||
var envConf *EnvConf = InitRuntimeConf()
|
||||
|
||||
func GetEnvConf() *EnvConf {
|
||||
return envConf
|
||||
}
|
||||
|
||||
type DomainData struct {
|
||||
Fullchain string `json:"fullchain"`
|
||||
Key string `json:"key"`
|
||||
Info string `json:"info"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user