This commit is contained in:
0xJacky 2022-02-27 00:27:17 +08:00
parent 1b4abab47f
commit 460480c64a
28 changed files with 527 additions and 357 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ app.ini
dist dist
*.exe *.exe
*.po~ *.po~
nginx-ui-server

14
Dockerfile Normal file
View file

@ -0,0 +1,14 @@
# CGO_ENABLED=1 GOOS=linux CC=x86_64-unknown-linux-gnu-gcc CXX=x86_64-unknown-linux-gnu-g++ GOARCH=amd64 go build -o nginx-ui-server -v main.go
FROM --platform=linux/amd64 debian:buster
WORKDIR /app
COPY ./resources/demo/sources.list /etc/apt/sources.list
RUN cd /app && apt-get update -y && apt install nginx curl -y
EXPOSE 80
COPY ./resources/demo/nginx.conf /etc/nginx/sites-available/default
COPY ./resources/demo/app.ini /app/app.ini
COPY ./resources/demo/demo.db /app/database.db
COPY ./resources/demo/install.sh /app/install.sh
COPY ./resources/demo/start.sh /app/start.sh
COPY ./nginx-ui-server /app/nginx-ui
RUN cd /app && chmod a+x start.sh
CMD ["./start.sh"]

View file

@ -1,6 +1,6 @@
{ {
"name": "nginx-ui-frontend", "name": "nginx-ui-frontend",
"version": "1.1.0", "version": "1.2.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",

View file

@ -4,6 +4,7 @@ import auth from './auth'
import user from './user' import user from './user'
import install from './install' import install from './install'
import analytic from './analytic' import analytic from './analytic'
import settings from './settings'
export default { export default {
domain, domain,
@ -11,5 +12,6 @@ export default {
auth, auth,
user, user,
install, install,
analytic analytic,
settings
} }

View file

@ -0,0 +1,9 @@
import http from '@/lib/http'
const settings = {
get() {
return http.get('/settings')
}
}
export default settings

View file

@ -2,27 +2,22 @@ export const settings = {
namespace: true, namespace: true,
state: { state: {
language: '', language: '',
translations: {}, env: {}
}, },
mutations: { mutations: {
set_language(state, payload) { set_language(state, payload) {
state.language = payload state.language = payload
}, },
update_translations(state, payload) { update_env(state, payload) {
state.translations = payload state.env = {...payload}
}
},
actions: {
set_language({commit}, data) {
commit('set_language', data)
},
update_translations({commit}, data) {
commit('update_translations', data)
} }
}, },
getters: { getters: {
current_language(state) { current_language(state) {
return state.language return state.language
},
env(state) {
return state.env
} }
} }
} }

View file

@ -44,7 +44,7 @@ msgstr ""
msgid "Build with" msgid "Build with"
msgstr "" msgstr ""
#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:24 #: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:23
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -52,11 +52,11 @@ msgstr ""
msgid "Certificate Auto-renewal" msgid "Certificate Auto-renewal"
msgstr "" msgstr ""
#: src/views/domain/CertInfo.vue:11 src/views/domain/CertInfo.vue:2 #: src/views/domain/CertInfo.vue:12 src/views/domain/CertInfo.vue:2
msgid "Certificate has expired" msgid "Certificate has expired"
msgstr "" msgstr ""
#: src/views/domain/CertInfo.vue:15 src/views/domain/CertInfo.vue:2 #: src/views/domain/CertInfo.vue:16 src/views/domain/CertInfo.vue:2
msgid "Certificate is valid" msgid "Certificate is valid"
msgstr "" msgstr ""
@ -125,7 +125,7 @@ msgstr ""
msgid "Do you want to change the template to support the TLS?" msgid "Do you want to change the template to support the TLS?"
msgstr "" msgstr ""
#: src/views/domain/DomainEdit.vue:38 #: src/views/domain/DomainEdit.vue:42
msgid "Edit %{n}" msgid "Edit %{n}"
msgstr "" msgstr ""
@ -133,7 +133,7 @@ msgstr ""
msgid "Edit Configuration" msgid "Edit Configuration"
msgstr "" msgstr ""
#: src/views/domain/DomainEdit.vue:87 #: src/views/domain/DomainEdit.vue:95
msgid "Edit Configuration File" msgid "Edit Configuration File"
msgstr "" msgstr ""
@ -182,7 +182,7 @@ msgstr ""
msgid "File Not Found" msgid "File Not Found"
msgstr "" msgstr ""
#: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:4 #: src/views/domain/DomainEdit.vue:9 src/views/domain/DomainEdit.vue:3
msgid "Getting Certificate from Let's Encrypt" msgid "Getting Certificate from Let's Encrypt"
msgstr "" msgstr ""
@ -243,7 +243,7 @@ msgstr ""
msgid "Logout successful" msgid "Logout successful"
msgstr "" msgstr ""
#: src/views/domain/DomainEdit.vue:13 src/views/domain/DomainEdit.vue:7 #: src/views/domain/DomainEdit.vue:12 src/views/domain/DomainEdit.vue:6
msgid "" msgid ""
"Make sure you have configured a reverse proxy for .well-known directory to " "Make sure you have configured a reverse proxy for .well-known directory to "
"HTTPChallengePort (default: 9180) before getting the certificate." "HTTPChallengePort (default: 9180) before getting the certificate."
@ -344,7 +344,7 @@ msgid "Root Directory (root)"
msgstr "" msgstr ""
#: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6 #: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6
#: src/views/domain/DomainEdit.vue:25 #: src/views/domain/DomainEdit.vue:24
msgid "Save" msgid "Save"
msgstr "" msgstr ""
@ -418,6 +418,10 @@ msgid ""
"changed after it has been created." "changed after it has been created."
msgstr "" msgstr ""
#: src/views/domain/DomainEdit.vue:11 src/views/domain/DomainEdit.vue:5
msgid "This feature is not available in demo."
msgstr ""
#: src/views/domain/DomainEdit.vue:134 #: src/views/domain/DomainEdit.vue:134
msgid "This operation will lose the custom configuration." msgid "This operation will lose the custom configuration."
msgstr "" msgstr ""

View file

@ -46,7 +46,7 @@ msgstr "成功启用 %{name} 自动续签"
msgid "Build with" msgid "Build with"
msgstr "构建基于" msgstr "构建基于"
#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:24 #: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:23
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
@ -54,11 +54,11 @@ msgstr "取消"
msgid "Certificate Auto-renewal" msgid "Certificate Auto-renewal"
msgstr "证书自动续签" msgstr "证书自动续签"
#: src/views/domain/CertInfo.vue:11 src/views/domain/CertInfo.vue:2 #: src/views/domain/CertInfo.vue:12 src/views/domain/CertInfo.vue:2
msgid "Certificate has expired" msgid "Certificate has expired"
msgstr "此证书已过期" msgstr "此证书已过期"
#: src/views/domain/CertInfo.vue:15 src/views/domain/CertInfo.vue:2 #: src/views/domain/CertInfo.vue:16 src/views/domain/CertInfo.vue:2
msgid "Certificate is valid" msgid "Certificate is valid"
msgstr "此证书有效" msgstr "此证书有效"
@ -127,7 +127,7 @@ msgstr "磁盘 IO"
msgid "Do you want to change the template to support the TLS?" msgid "Do you want to change the template to support the TLS?"
msgstr "你想要改变模板以支持 TLS 吗?" msgstr "你想要改变模板以支持 TLS 吗?"
#: src/views/domain/DomainEdit.vue:38 #: src/views/domain/DomainEdit.vue:42
msgid "Edit %{n}" msgid "Edit %{n}"
msgstr "编辑 %{n}" msgstr "编辑 %{n}"
@ -135,7 +135,7 @@ msgstr "编辑 %{n}"
msgid "Edit Configuration" msgid "Edit Configuration"
msgstr "编辑配置" msgstr "编辑配置"
#: src/views/domain/DomainEdit.vue:87 #: src/views/domain/DomainEdit.vue:95
msgid "Edit Configuration File" msgid "Edit Configuration File"
msgstr "编辑配置文件" msgstr "编辑配置文件"
@ -184,7 +184,7 @@ msgstr "启用失败 %{msg}"
msgid "File Not Found" msgid "File Not Found"
msgstr "未找到文件" msgstr "未找到文件"
#: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:4 #: src/views/domain/DomainEdit.vue:9 src/views/domain/DomainEdit.vue:3
msgid "Getting Certificate from Let's Encrypt" msgid "Getting Certificate from Let's Encrypt"
msgstr "从 Let's Encrypt 获取证书" msgstr "从 Let's Encrypt 获取证书"
@ -245,7 +245,7 @@ msgstr "登录成功"
msgid "Logout successful" msgid "Logout successful"
msgstr "登出成功" msgstr "登出成功"
#: src/views/domain/DomainEdit.vue:13 src/views/domain/DomainEdit.vue:7 #: src/views/domain/DomainEdit.vue:12 src/views/domain/DomainEdit.vue:6
msgid "" msgid ""
"Make sure you have configured a reverse proxy for .well-known directory to " "Make sure you have configured a reverse proxy for .well-known directory to "
"HTTPChallengePort (default: 9180) before getting the certificate." "HTTPChallengePort (default: 9180) before getting the certificate."
@ -275,7 +275,7 @@ msgstr "名称"
#: src/views/dashboard/DashBoard.vue:231 #: src/views/dashboard/DashBoard.vue:231
msgid "Network" msgid "Network"
msgstr "" msgstr "网络"
#: src/views/dashboard/DashBoard.vue:165 #: src/views/dashboard/DashBoard.vue:165
msgid "Network Total Receive" msgid "Network Total Receive"
@ -348,7 +348,7 @@ msgid "Root Directory (root)"
msgstr "网站根目录 (root)" msgstr "网站根目录 (root)"
#: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6 #: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6
#: src/views/domain/DomainEdit.vue:25 #: src/views/domain/DomainEdit.vue:24
msgid "Save" msgid "Save"
msgstr "保存" msgstr "保存"
@ -426,6 +426,10 @@ msgstr ""
"只有在您的配置文件中有相应字段时,下列的配置才能生效。配置文件名称创建后不" "只有在您的配置文件中有相应字段时,下列的配置才能生效。配置文件名称创建后不"
"可修改。" "可修改。"
#: src/views/domain/DomainEdit.vue:11 src/views/domain/DomainEdit.vue:5
msgid "This feature is not available in demo."
msgstr "该功能在 Demo 中不可用。"
#: src/views/domain/DomainEdit.vue:134 #: src/views/domain/DomainEdit.vue:134
msgid "This operation will lose the custom configuration." msgid "This operation will lose the custom configuration."
msgstr "该操作将会丢失自定义配置。" msgstr "该操作将会丢失自定义配置。"

View file

@ -47,7 +47,7 @@ msgstr "成功啟用 %{name} 自動續簽"
msgid "Build with" msgid "Build with"
msgstr "構建基於" msgstr "構建基於"
#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:24 #: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:23
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
@ -55,11 +55,11 @@ msgstr "取消"
msgid "Certificate Auto-renewal" msgid "Certificate Auto-renewal"
msgstr "證書自動續簽" msgstr "證書自動續簽"
#: src/views/domain/CertInfo.vue:11 src/views/domain/CertInfo.vue:2 #: src/views/domain/CertInfo.vue:12 src/views/domain/CertInfo.vue:2
msgid "Certificate has expired" msgid "Certificate has expired"
msgstr "此證書已過期" msgstr "此證書已過期"
#: src/views/domain/CertInfo.vue:15 src/views/domain/CertInfo.vue:2 #: src/views/domain/CertInfo.vue:16 src/views/domain/CertInfo.vue:2
msgid "Certificate is valid" msgid "Certificate is valid"
msgstr "此證書有效" msgstr "此證書有效"
@ -129,7 +129,7 @@ msgstr ""
msgid "Do you want to change the template to support the TLS?" msgid "Do you want to change the template to support the TLS?"
msgstr "你想要改變模板以支援 TLS 嗎?" msgstr "你想要改變模板以支援 TLS 嗎?"
#: src/views/domain/DomainEdit.vue:38 #: src/views/domain/DomainEdit.vue:42
msgid "Edit %{n}" msgid "Edit %{n}"
msgstr "編輯 %{n}" msgstr "編輯 %{n}"
@ -137,7 +137,7 @@ msgstr "編輯 %{n}"
msgid "Edit Configuration" msgid "Edit Configuration"
msgstr "編輯配置" msgstr "編輯配置"
#: src/views/domain/DomainEdit.vue:87 #: src/views/domain/DomainEdit.vue:95
msgid "Edit Configuration File" msgid "Edit Configuration File"
msgstr "編輯配置檔案" msgstr "編輯配置檔案"
@ -186,7 +186,7 @@ msgstr "啟用失敗 %{msg}"
msgid "File Not Found" msgid "File Not Found"
msgstr "未找到檔案" msgstr "未找到檔案"
#: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:4 #: src/views/domain/DomainEdit.vue:9 src/views/domain/DomainEdit.vue:3
msgid "Getting Certificate from Let's Encrypt" msgid "Getting Certificate from Let's Encrypt"
msgstr "從 Let's Encrypt 獲取證書" msgstr "從 Let's Encrypt 獲取證書"
@ -247,7 +247,7 @@ msgstr "登入成功"
msgid "Logout successful" msgid "Logout successful"
msgstr "登出成功" msgstr "登出成功"
#: src/views/domain/DomainEdit.vue:13 src/views/domain/DomainEdit.vue:7 #: src/views/domain/DomainEdit.vue:12 src/views/domain/DomainEdit.vue:6
#, fuzzy #, fuzzy
msgid "" msgid ""
"Make sure you have configured a reverse proxy for .well-known directory to " "Make sure you have configured a reverse proxy for .well-known directory to "
@ -351,7 +351,7 @@ msgid "Root Directory (root)"
msgstr "網站根目錄 (root)" msgstr "網站根目錄 (root)"
#: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6 #: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6
#: src/views/domain/DomainEdit.vue:25 #: src/views/domain/DomainEdit.vue:24
msgid "Save" msgid "Save"
msgstr "儲存" msgstr "儲存"
@ -430,6 +430,10 @@ msgstr ""
"只有在您的配置檔案中有相應欄位時,下列的配置才能生效。配置檔名稱建立後不可修" "只有在您的配置檔案中有相應欄位時,下列的配置才能生效。配置檔名稱建立後不可修"
"改。" "改。"
#: src/views/domain/DomainEdit.vue:11 src/views/domain/DomainEdit.vue:5
msgid "This feature is not available in demo."
msgstr ""
#: src/views/domain/DomainEdit.vue:134 #: src/views/domain/DomainEdit.vue:134
msgid "This operation will lose the custom configuration." msgid "This operation will lose the custom configuration."
msgstr "該操作將會丟失自定義配置。" msgstr "該操作將會丟失自定義配置。"

View file

@ -20,6 +20,10 @@ Vue.config.productionTip = false
Vue.prototype.$routeConfig = routes Vue.prototype.$routeConfig = routes
Vue.prototype.$api = api Vue.prototype.$api = api
api.settings.get().then(r => {
store.commit('update_env', r)
})
Vue.use(GetTextPlugin, { Vue.use(GetTextPlugin, {
availableLanguages, availableLanguages,
defaultLanguage: store.getters.current_language, defaultLanguage: store.getters.current_language,

File diff suppressed because one or more lines are too long

View file

@ -6,10 +6,16 @@
<std-data-entry :data-list="columns" v-model="config"/> <std-data-entry :data-list="columns" v-model="config"/>
<template v-if="config.support_ssl"> <template v-if="config.support_ssl">
<cert-info :domain="name" ref="cert-info" v-if="name"/> <cert-info :domain="name" ref="cert-info" v-if="name"/>
<a-button @click="issue_cert" type="primary" ghost style="margin: 10px 0"> <a-button
@click="issue_cert"
type="primary" ghost
style="margin: 10px 0"
:disabled="is_demo"
>
<translate>Getting Certificate from Let's Encrypt</translate> <translate>Getting Certificate from Let's Encrypt</translate>
</a-button> </a-button>
<p v-translate>Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort (default: 9180) before getting the certificate.</p> <p v-if="is_demo" v-translate>This feature is not available in demo.</p>
<p v-else v-translate>Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort (default: 9180) before getting the certificate.</p>
</template> </template>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
@ -238,6 +244,9 @@ export default {
return [...columns] return [...columns]
} }
} }
},
is_demo() {
return this.$store.getters.env.demo===true
} }
} }
} }

View file

@ -1 +1 @@
{"version":"1.1.0","build_id":23,"total_build":40} {"version":"1.2.0","build_id":2,"total_build":42}

7
go.mod
View file

@ -3,7 +3,6 @@ module github.com/0xJacky/Nginx-UI
go 1.17 go 1.17
require ( require (
github.com/0xJacky/pofile v0.0.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/gin-contrib/static v0.0.1 github.com/gin-contrib/static v0.0.1
@ -16,6 +15,7 @@ require (
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/shirou/gopsutil/v3 v3.21.7 github.com/shirou/gopsutil/v3 v3.21.7
github.com/spf13/cast v1.3.1 github.com/spf13/cast v1.3.1
github.com/stretchr/testify v1.7.0
github.com/unknwon/com v1.0.1 github.com/unknwon/com v1.0.1
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
gopkg.in/ini.v1 v1.62.0 gopkg.in/ini.v1 v1.62.0
@ -26,10 +26,10 @@ require (
require ( require (
github.com/StackExchange/wmi v1.2.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-ole/go-ole v1.2.5 // indirect
github.com/golang/protobuf v1.3.4 // indirect github.com/golang/protobuf v1.3.4 // indirect
github.com/itchyny/timefmt-go v0.1.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.2 // indirect github.com/jinzhu/now v1.1.2 // indirect
github.com/json-iterator/go v1.1.9 // indirect github.com/json-iterator/go v1.1.9 // indirect
@ -39,7 +39,7 @@ require (
github.com/miekg/dns v1.1.40 // indirect github.com/miekg/dns v1.1.40 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.7 // indirect github.com/tklauser/go-sysconf v0.3.7 // indirect
github.com/tklauser/numcpus v0.2.3 // indirect github.com/tklauser/numcpus v0.2.3 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect github.com/ugorji/go/codec v1.1.7 // indirect
@ -48,4 +48,5 @@ require (
golang.org/x/text v0.3.4 // indirect golang.org/x/text v0.3.4 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )

7
go.sum
View file

@ -23,10 +23,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/0xJacky/pofile v0.0.0-20220219101524-60ce48e4de23 h1:nqaxj4ZYzLZzhFQeX1ZrXEBz0haUu7PypGFhQbQfDX0=
github.com/0xJacky/pofile v0.0.0-20220219101524-60ce48e4de23/go.mod h1:gSDWobvodMtvwh7FE/F999AQoCwBoXgzyGffYFX9nKA=
github.com/0xJacky/pofile v0.0.1 h1:hVRaw6ZHkajSMAuP58WMDTvGF8+OF297jpAchFK/4rQ=
github.com/0xJacky/pofile v0.0.1/go.mod h1:gSDWobvodMtvwh7FE/F999AQoCwBoXgzyGffYFX9nKA=
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
@ -229,8 +225,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@ -341,7 +335,6 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=

BIN
resources/demo/demo.db Normal file

Binary file not shown.

78
resources/demo/install.sh Normal file
View file

@ -0,0 +1,78 @@
#!/usr/bin/env bash
PROXY=""
RPROXY="https://ghproxy.com/"
MACHINE="amd64"
# Font color
FontBlack="\033[30m";
FontRed="\033[31m";
FontGreen="\033[32m";
FontYellow="\033[33m";
FontBlue="\033[34m";
FontPurple="\033[35m";
FontSkyBlue="\033[36m";
FontWhite="\033[37m";
FontSuffix="\033[0m";
get_latest_version() {
# Get latest release version number
local latest_release
if ! latest_release=$(curl -x "${PROXY}" -sS -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/0xJacky/nginx-ui/releases/latest"); then
echo -e "${FontRed}error: Failed to get release list, please check your network.${FontSuffix}"
exit 1
fi
RELEASE_LATEST="$(echo "$latest_release" | sed 'y/,/\n/' | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')"
if [[ -z "$RELEASE_LATEST" ]]; then
if echo "$latest_release" | grep -q "API rate limit exceeded"; then
echo -e "${FontRed}error: github API rate limit exceeded${FontSuffix}"
else
echo -e "${FontRed}error: Failed to get the latest release version.${FontSuffix}"
echo "Welcome bug report: https://github.com/0xJacky/nginx-ui/issues"
fi
exit 1
fi
RELEASE_LATEST="v${RELEASE_LATEST#v}"
}
download_nginx_ui() {
local download_link
download_link="${RPROXY}https://github.com/0xJacky/nginx-ui/releases/download/$RELEASE_LATEST/nginx-ui-linux-$MACHINE.tar.gz"
echo "Downloading Nginx UI archive: $download_link"
if ! curl -x "${PROXY}" -R -H 'Cache-Control: no-cache' -L -o "$TAR_FILE" "$download_link"; then
echo 'error: Download failed! Please check your network or try again.'
return 1
fi
return 0
}
decompression() {
echo "$1"
if ! tar -zxf "$1" -C "$TMP_DIRECTORY"; then
echo -e "${FontRed}error: Nginx UI decompression failed.${FontSuffix}"
"rm" -r "$TMP_DIRECTORY"
echo "removed: $TMP_DIRECTORY"
exit 1
fi
echo "info: Extract the Nginx UI package to $TMP_DIRECTORY and prepare it for installation."
}
install_bin() {
NAME="nginx-ui"
install -m 755 "${TMP_DIRECTORY}/$NAME" "/app/$NAME"
}
main() {
# Important Variables
TMP_DIRECTORY="$(mktemp -d)"
TAR_FILE="${TMP_DIRECTORY}/nginx-ui-linux-$MACHINE.tar.gz"
get_latest_version
download_nginx_ui
decompression "$TAR_FILE"
install_bin
}
main "$@"

16
resources/demo/nginx.conf Normal file
View file

@ -0,0 +1,16 @@
server {
listen 80;
server_name localhost; # your domain here
client_max_body_size 128M; # maximum upload size
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_pass http://127.0.0.1:9000/;
}
}

View file

@ -0,0 +1,4 @@
deb http://mirrors.aliyun.com/debian/ buster main non-free contrib
deb http://mirrors.aliyun.com/debian-security buster/updates main
deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib
deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib

3
resources/demo/start.sh Normal file
View file

@ -0,0 +1,3 @@
#!/bin/bash
nginx
/app/nginx-ui --config app.ini

View file

@ -1,150 +1,163 @@
package api package api
import ( import (
"encoding/json" "encoding/json"
"github.com/0xJacky/Nginx-UI/server/tool" "github.com/0xJacky/Nginx-UI/server/settings"
"github.com/gin-gonic/gin" "github.com/0xJacky/Nginx-UI/server/tool"
"github.com/gorilla/websocket" "github.com/gin-gonic/gin"
"log" "github.com/gorilla/websocket"
"net/http" "log"
"os" "net/http"
"os"
) )
func CertInfo(c *gin.Context) { func CertInfo(c *gin.Context) {
domain := c.Param("domain") domain := c.Param("domain")
key := tool.GetCertInfo(domain) key := tool.GetCertInfo(domain)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"subject_name": key.Subject.CommonName, "subject_name": key.Subject.CommonName,
"issuer_name": key.Issuer.CommonName, "issuer_name": key.Issuer.CommonName,
"not_after": key.NotAfter, "not_after": key.NotAfter,
"not_before": key.NotBefore, "not_before": key.NotBefore,
}) })
} }
func IssueCert(c *gin.Context) { func IssueCert(c *gin.Context) {
domain := c.Param("domain") domain := c.Param("domain")
var upGrader = websocket.Upgrader{ var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
return true return true
}, },
} }
// upgrade http to websocket // upgrade http to websocket
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
defer func(ws *websocket.Conn) { defer func(ws *websocket.Conn) {
err := ws.Close() err := ws.Close()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
}(ws) }(ws)
for { for {
// read // read
mt, message, err := ws.ReadMessage() mt, message, err := ws.ReadMessage()
if err != nil { if err != nil {
break break
} }
if string(message) == "go" { if string(message) == "go" {
var m []byte var m []byte
err = tool.IssueCert(domain) if settings.ServerSettings.Demo {
if err != nil { m, _ = json.Marshal(gin.H{
m, err = json.Marshal(gin.H{ "status": "error",
"status": "error", "message": "this feature is not available in demo",
"message": err.Error(), })
}) _ = ws.WriteMessage(mt, m)
return
}
if err != nil { err = tool.IssueCert(domain)
log.Println(err)
return
}
err = ws.WriteMessage(mt, m) if err != nil {
if err != nil { log.Println(err)
log.Println(err)
return
}
log.Println(err) m, err = json.Marshal(gin.H{
return "status": "error",
} "message": err.Error(),
})
sslCertificatePath := tool.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") if err != nil {
_, err = os.Stat(sslCertificatePath) log.Println(err)
return
}
if err != nil { err = ws.WriteMessage(mt, m)
log.Println(err)
return
}
log.Println("[found]", "fullchain.cer") if err != nil {
m, err = json.Marshal(gin.H{ log.Println(err)
"status": "success", return
"message": "[found] fullchain.cer", }
})
if err != nil { return
log.Println(err) }
return
}
err = ws.WriteMessage(mt, m) sslCertificatePath := tool.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
_, err = os.Stat(sslCertificatePath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
sslCertificateKeyPath := tool.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") log.Println("[found]", "fullchain.cer")
_, err = os.Stat(sslCertificateKeyPath) m, err = json.Marshal(gin.H{
"status": "success",
"message": "[found] fullchain.cer",
})
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
log.Println("[found]", "cert key") err = ws.WriteMessage(mt, m)
m, err = json.Marshal(gin.H{
"status": "success",
"message": "[found] cert key",
})
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} return
}
err = ws.WriteMessage(mt, m) sslCertificateKeyPath := tool.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
_, err = os.Stat(sslCertificateKeyPath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} return
}
log.Println("申请成功") log.Println("[found]", "cert key")
m, err = json.Marshal(gin.H{ m, err = json.Marshal(gin.H{
"status": "success", "status": "success",
"message": "申请成功", "message": "[found] cert key",
"ssl_certificate": sslCertificatePath, })
"ssl_certificate_key": sslCertificateKeyPath,
})
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
err = ws.WriteMessage(mt, m) err = ws.WriteMessage(mt, m)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
}
} log.Println("申请成功")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "申请成功",
"ssl_certificate": sslCertificatePath,
"ssl_certificate_key": sslCertificateKeyPath,
})
if err != nil {
log.Println(err)
}
err = ws.WriteMessage(mt, m)
if err != nil {
log.Println(err)
}
}
}
} }

View file

@ -2,18 +2,16 @@ package api
import ( import (
"github.com/0xJacky/Nginx-UI/server/settings" "github.com/0xJacky/Nginx-UI/server/settings"
"github.com/0xJacky/Nginx-UI/server/template"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strings" "strings"
) )
func GetTemplate(c *gin.Context) { func GetTemplate(c *gin.Context) {
name := c.Param("name") name := c.Param("name")
path := filepath.Join("template", name) content, err := template.DistFS.ReadFile(name)
content, err := ioutil.ReadFile(path)
_content := string(content) _content := string(content)
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}", _content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",

View file

@ -1,77 +1,85 @@
package router package router
import ( import (
"bufio" "bufio"
"github.com/0xJacky/Nginx-UI/server/api" "github.com/0xJacky/Nginx-UI/server/api"
"github.com/gin-contrib/static" "github.com/0xJacky/Nginx-UI/server/settings"
"github.com/gin-gonic/gin" "github.com/gin-contrib/static"
"net/http" "github.com/gin-gonic/gin"
"strings" "net/http"
"strings"
) )
func InitRouter() *gin.Engine { func InitRouter() *gin.Engine {
r := gin.New() r := gin.New()
r.Use(gin.Logger()) r.Use(gin.Logger())
r.Use(gin.Recovery()) r.Use(gin.Recovery())
r.Use(static.Serve("/", mustFS(""))) r.Use(static.Serve("/", mustFS("")))
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
accept := c.Request.Header.Get("Accept") accept := c.Request.Header.Get("Accept")
if strings.Contains(accept, "text/html") { if strings.Contains(accept, "text/html") {
file, _ := mustFS("").Open("index.html") file, _ := mustFS("").Open("index.html")
stat, _ := file.Stat() stat, _ := file.Stat()
c.DataFromReader(http.StatusOK, stat.Size(), "text/html", c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
bufio.NewReader(file), nil) bufio.NewReader(file), nil)
} }
}) })
g := r.Group("/api") g := r.Group("/api")
{ {
g.GET("install", api.InstallLockCheck)
g.POST("install", api.InstallNginxUI)
g.POST("/login", api.Login) g.GET("settings", func(c *gin.Context) {
g.DELETE("/logout", api.Logout) c.JSON(http.StatusOK, gin.H{
"demo": settings.ServerSettings.Demo,
})
})
g := g.Group("/", authRequired()) g.GET("install", api.InstallLockCheck)
{ g.POST("install", api.InstallNginxUI)
g.GET("/analytic", api.Analytic)
g.GET("/analytic/init", api.GetAnalyticInit)
g.GET("/users", api.GetUsers) g.POST("/login", api.Login)
g.GET("/user/:id", api.GetUser) g.DELETE("/logout", api.Logout)
g.POST("/user", api.AddUser)
g.POST("/user/:id", api.EditUser)
g.DELETE("/user/:id", api.DeleteUser)
g.GET("domains", api.GetDomains) g := g.Group("/", authRequired())
g.GET("domain/:name", api.GetDomain) {
g.POST("domain/:name", api.EditDomain) g.GET("/analytic", api.Analytic)
g.POST("domain/:name/enable", api.EnableDomain) g.GET("/analytic/init", api.GetAnalyticInit)
g.POST("domain/:name/disable", api.DisableDomain)
g.DELETE("domain/:name", api.DeleteDomain)
g.GET("configs", api.GetConfigs) g.GET("/users", api.GetUsers)
g.GET("config/:name", api.GetConfig) g.GET("/user/:id", api.GetUser)
g.POST("config", api.AddConfig) g.POST("/user", api.AddUser)
g.POST("config/:name", api.EditConfig) g.POST("/user/:id", api.EditUser)
g.DELETE("/user/:id", api.DeleteUser)
g.GET("backups", api.GetFileBackupList) g.GET("domains", api.GetDomains)
g.GET("backup/:id", api.GetFileBackup) g.GET("domain/:name", api.GetDomain)
g.POST("domain/:name", api.EditDomain)
g.POST("domain/:name/enable", api.EnableDomain)
g.POST("domain/:name/disable", api.DisableDomain)
g.DELETE("domain/:name", api.DeleteDomain)
g.GET("template/:name", api.GetTemplate) g.GET("configs", api.GetConfigs)
g.GET("config/:name", api.GetConfig)
g.POST("config", api.AddConfig)
g.POST("config/:name", api.EditConfig)
g.GET("cert/issue/:domain", api.IssueCert) g.GET("backups", api.GetFileBackupList)
g.GET("cert/:domain/info", api.CertInfo) g.GET("backup/:id", api.GetFileBackup)
// 添加域名到自动续期列表 g.GET("template/:name", api.GetTemplate)
g.POST("cert/:domain", api.AddDomainToAutoCert)
// 从自动续期列表中删除域名
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
}
}
return r g.GET("cert/issue/:domain", api.IssueCert)
g.GET("cert/:domain/info", api.CertInfo)
// 添加域名到自动续期列表
g.POST("cert/:domain", api.AddDomainToAutoCert)
// 从自动续期列表中删除域名
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
}
}
return r
} }

View file

@ -15,6 +15,7 @@ type Server struct {
HTTPChallengePort string HTTPChallengePort string
Email string Email string
Database string Database string
Demo bool
} }
var ServerSettings = &Server{ var ServerSettings = &Server{
@ -22,6 +23,7 @@ var ServerSettings = &Server{
RunMode: "debug", RunMode: "debug",
HTTPChallengePort: "9180", HTTPChallengePort: "9180",
Database: "database", Database: "database",
Demo: false,
} }
var ConfPath string var ConfPath string

View file

@ -0,0 +1,6 @@
package template
import "embed"
//go:embed http-conf https-conf
var DistFS embed.FS

View file

@ -48,8 +48,8 @@ func TestLego(t *testing.T) {
config := lego.NewConfig(&myUser) config := lego.NewConfig(&myUser)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM. // This CA URL is configured for a local dev instance of Boulder running in Dockerfile in a VM.
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
config.Certificate.KeyType = certcrypto.RSA2048 config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server. // A client facilitates communication with the CA server.

View file

@ -1,169 +1,171 @@
package tool package tool
import ( import (
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"github.com/0xJacky/Nginx-UI/server/model" "github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/settings" "github.com/0xJacky/Nginx-UI/server/settings"
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/v4/registration"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
) )
// MyUser You'll need a user or account type that implements acme.User // MyUser You'll need a user or account type that implements acme.User
type MyUser struct { type MyUser struct {
Email string Email string
Registration *registration.Resource Registration *registration.Resource
key crypto.PrivateKey key crypto.PrivateKey
} }
func (u *MyUser) GetEmail() string { func (u *MyUser) GetEmail() string {
return u.Email return u.Email
} }
func (u MyUser) GetRegistration() *registration.Resource { func (u MyUser) GetRegistration() *registration.Resource {
return u.Registration return u.Registration
} }
func (u *MyUser) GetPrivateKey() crypto.PrivateKey { func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
return u.key return u.key
} }
func AutoCert() { func AutoCert() {
for { for {
log.Println("[AutoCert] Start") log.Println("[AutoCert] Start")
autoCertList := model.GetAutoCertList() autoCertList := model.GetAutoCertList()
for i := range autoCertList { for i := range autoCertList {
domain := autoCertList[i].Domain domain := autoCertList[i].Domain
key := GetCertInfo(domain) key := GetCertInfo(domain)
// 未到一个月 // 未到一个月
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) { if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
continue continue
} }
// 过一个月了 // 过一个月了
err := IssueCert(domain) err := IssueCert(domain)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
} }
time.Sleep(1 * time.Hour) time.Sleep(1 * time.Hour)
} }
} }
func GetCertInfo(domain string) (key *x509.Certificate) { func GetCertInfo(domain string) (key *x509.Certificate) {
ts := &http.Transport{ ts := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
} }
client := &http.Client{Transport: ts} client := &http.Client{Transport: ts}
response, err := client.Get("https://" + domain) response, err := client.Get("https://" + domain)
if err != nil { if err != nil {
return return
} }
defer func(Body io.ReadCloser) { defer func(Body io.ReadCloser) {
err = Body.Close() err = Body.Close()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
}(response.Body) }(response.Body)
key = response.TLS.PeerCertificates[0] key = response.TLS.PeerCertificates[0]
return return
} }
func IssueCert(domain string) error { func IssueCert(domain string) error {
// Create a user. New accounts need an email and private key to start. // Create a user. New accounts need an email and private key to start.
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
myUser := MyUser{ myUser := MyUser{
Email: settings.ServerSettings.Email, Email: settings.ServerSettings.Email,
key: privateKey, key: privateKey,
} }
config := lego.NewConfig(&myUser) config := lego.NewConfig(&myUser)
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" if settings.ServerSettings.Demo {
config.Certificate.KeyType = certcrypto.RSA2048 config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
}
config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server. // A client facilitates communication with the CA server.
client, err := lego.NewClient(config) client, err := lego.NewClient(config)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
err = client.Challenge.SetHTTP01Provider( err = client.Challenge.SetHTTP01Provider(
http01.NewProviderServer("", http01.NewProviderServer("",
settings.ServerSettings.HTTPChallengePort, settings.ServerSettings.HTTPChallengePort,
), ),
) )
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
// New users will need to register // New users will need to register
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
myUser.Registration = reg myUser.Registration = reg
request := certificate.ObtainRequest{ request := certificate.ObtainRequest{
Domains: []string{domain}, Domains: []string{domain},
Bundle: true, Bundle: true,
} }
certificates, err := client.Certificate.Obtain(request) certificates, err := client.Certificate.Obtain(request)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
saveDir := GetNginxConfPath("ssl/" + domain) saveDir := GetNginxConfPath("ssl/" + domain)
if _, err := os.Stat(saveDir); os.IsNotExist(err) { if _, err := os.Stat(saveDir); os.IsNotExist(err) {
err = os.Mkdir(saveDir, 0755) err = os.Mkdir(saveDir, 0755)
if err != nil { if err != nil {
log.Println("fail to create", saveDir) log.Println("fail to create", saveDir)
return err return err
} }
} }
// Each certificate comes back with the cert bytes, the bytes of the client's // Each certificate comes back with the cert bytes, the bytes of the client's
// private key, and a certificate URL. SAVE THESE TO DISK. // private key, and a certificate URL. SAVE THESE TO DISK.
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"), err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
certificates.Certificate, 0644) certificates.Certificate, 0644)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"), err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
certificates.PrivateKey, 0644) certificates.PrivateKey, 0644)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return err return err
} }
ReloadNginx() ReloadNginx()
return nil return nil
} }