0xJacky 3 éve
szülő
commit
460480c64a

+ 1 - 0
.gitignore

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

+ 14 - 0
Dockerfile

@@ -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"]

+ 1 - 1
frontend/package.json

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

+ 3 - 1
frontend/src/api/index.js

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

+ 9 - 0
frontend/src/api/settings.js

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

+ 6 - 11
frontend/src/lib/store/settings.js

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

+ 12 - 8
frontend/src/locale/en/LC_MESSAGES/app.po

@@ -44,7 +44,7 @@ msgstr ""
 msgid "Build with"
 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"
 msgstr ""
 
@@ -52,11 +52,11 @@ msgstr ""
 msgid "Certificate Auto-renewal"
 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"
 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"
 msgstr ""
 
@@ -125,7 +125,7 @@ msgstr ""
 msgid "Do you want to change the template to support the TLS?"
 msgstr ""
 
-#: src/views/domain/DomainEdit.vue:38
+#: src/views/domain/DomainEdit.vue:42
 msgid "Edit %{n}"
 msgstr ""
 
@@ -133,7 +133,7 @@ msgstr ""
 msgid "Edit Configuration"
 msgstr ""
 
-#: src/views/domain/DomainEdit.vue:87
+#: src/views/domain/DomainEdit.vue:95
 msgid "Edit Configuration File"
 msgstr ""
 
@@ -182,7 +182,7 @@ msgstr ""
 msgid "File Not Found"
 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"
 msgstr ""
 
@@ -243,7 +243,7 @@ msgstr ""
 msgid "Logout successful"
 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 ""
 "Make sure you have configured a reverse proxy for .well-known directory to "
 "HTTPChallengePort (default: 9180) before getting the certificate."
@@ -344,7 +344,7 @@ msgid "Root Directory (root)"
 msgstr ""
 
 #: 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"
 msgstr ""
 
@@ -418,6 +418,10 @@ msgid ""
 "changed after it has been created."
 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
 msgid "This operation will lose the custom configuration."
 msgstr ""

BIN
frontend/src/locale/zh_CN/LC_MESSAGES/app.mo


+ 13 - 9
frontend/src/locale/zh_CN/LC_MESSAGES/app.po

@@ -46,7 +46,7 @@ msgstr "成功启用 %{name} 自动续签"
 msgid "Build with"
 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"
 msgstr "取消"
 
@@ -54,11 +54,11 @@ msgstr "取消"
 msgid "Certificate Auto-renewal"
 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"
 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"
 msgstr "此证书有效"
 
@@ -127,7 +127,7 @@ msgstr "磁盘 IO"
 msgid "Do you want to change the template to support the TLS?"
 msgstr "你想要改变模板以支持 TLS 吗?"
 
-#: src/views/domain/DomainEdit.vue:38
+#: src/views/domain/DomainEdit.vue:42
 msgid "Edit %{n}"
 msgstr "编辑 %{n}"
 
@@ -135,7 +135,7 @@ msgstr "编辑 %{n}"
 msgid "Edit Configuration"
 msgstr "编辑配置"
 
-#: src/views/domain/DomainEdit.vue:87
+#: src/views/domain/DomainEdit.vue:95
 msgid "Edit Configuration File"
 msgstr "编辑配置文件"
 
@@ -184,7 +184,7 @@ msgstr "启用失败 %{msg}"
 msgid "File Not Found"
 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"
 msgstr "从 Let's Encrypt 获取证书"
 
@@ -245,7 +245,7 @@ msgstr "登录成功"
 msgid "Logout successful"
 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 ""
 "Make sure you have configured a reverse proxy for .well-known directory to "
 "HTTPChallengePort (default: 9180) before getting the certificate."
@@ -275,7 +275,7 @@ msgstr "名称"
 
 #: src/views/dashboard/DashBoard.vue:231
 msgid "Network"
-msgstr ""
+msgstr "网络"
 
 #: src/views/dashboard/DashBoard.vue:165
 msgid "Network Total Receive"
@@ -348,7 +348,7 @@ msgid "Root Directory (root)"
 msgstr "网站根目录 (root)"
 
 #: 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"
 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
 msgid "This operation will lose the custom configuration."
 msgstr "该操作将会丢失自定义配置。"

+ 12 - 8
frontend/src/locale/zh_TW/LC_MESSAGES/app.po

@@ -47,7 +47,7 @@ msgstr "成功啟用 %{name} 自動續簽"
 msgid "Build with"
 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"
 msgstr "取消"
 
@@ -55,11 +55,11 @@ msgstr "取消"
 msgid "Certificate Auto-renewal"
 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"
 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"
 msgstr "此證書有效"
 
@@ -129,7 +129,7 @@ msgstr ""
 msgid "Do you want to change the template to support the TLS?"
 msgstr "你想要改變模板以支援 TLS 嗎?"
 
-#: src/views/domain/DomainEdit.vue:38
+#: src/views/domain/DomainEdit.vue:42
 msgid "Edit %{n}"
 msgstr "編輯 %{n}"
 
@@ -137,7 +137,7 @@ msgstr "編輯 %{n}"
 msgid "Edit Configuration"
 msgstr "編輯配置"
 
-#: src/views/domain/DomainEdit.vue:87
+#: src/views/domain/DomainEdit.vue:95
 msgid "Edit Configuration File"
 msgstr "編輯配置檔案"
 
@@ -186,7 +186,7 @@ msgstr "啟用失敗 %{msg}"
 msgid "File Not Found"
 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"
 msgstr "從 Let's Encrypt 獲取證書"
 
@@ -247,7 +247,7 @@ msgstr "登入成功"
 msgid "Logout successful"
 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
 msgid ""
 "Make sure you have configured a reverse proxy for .well-known directory to "
@@ -351,7 +351,7 @@ msgid "Root Directory (root)"
 msgstr "網站根目錄 (root)"
 
 #: 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"
 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
 msgid "This operation will lose the custom configuration."
 msgstr "該操作將會丟失自定義配置。"

+ 4 - 0
frontend/src/main.js

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

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
frontend/src/translations.json


+ 11 - 2
frontend/src/views/domain/DomainEdit.vue

@@ -6,10 +6,16 @@
                 <std-data-entry :data-list="columns" v-model="config"/>
                 <template v-if="config.support_ssl">
                     <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>
                     </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>
             </a-collapse-panel>
         </a-collapse>
@@ -238,6 +244,9 @@ export default {
                     return [...columns]
                 }
             }
+        },
+        is_demo() {
+            return this.$store.getters.env.demo===true
         }
     }
 }

+ 1 - 1
frontend/version.json

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

+ 4 - 3
go.mod

@@ -3,7 +3,6 @@ module github.com/0xJacky/Nginx-UI
 go 1.17
 
 require (
-	github.com/0xJacky/pofile v0.0.1
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/dustin/go-humanize v1.0.0
 	github.com/gin-contrib/static v0.0.1
@@ -16,6 +15,7 @@ require (
 	github.com/gorilla/websocket v1.4.2
 	github.com/shirou/gopsutil/v3 v3.21.7
 	github.com/spf13/cast v1.3.1
+	github.com/stretchr/testify v1.7.0
 	github.com/unknwon/com v1.0.1
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 	gopkg.in/ini.v1 v1.62.0
@@ -26,10 +26,10 @@ require (
 require (
 	github.com/StackExchange/wmi v1.2.1 // 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/go-ole/go-ole v1.2.5 // 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/now v1.1.2 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/numcpus v0.2.3 // indirect
 	github.com/ugorji/go/codec v1.1.7 // indirect
@@ -48,4 +48,5 @@ require (
 	golang.org/x/text v0.3.4 // indirect
 	gopkg.in/square/go-jose.v2 v2.5.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )

+ 0 - 7
go.sum

@@ -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=
 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=
-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/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=
@@ -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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 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/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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 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/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=

BIN
resources/demo/demo.db


+ 78 - 0
resources/demo/install.sh

@@ -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 - 0
resources/demo/nginx.conf

@@ -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/;
+    }
+}

+ 4 - 0
resources/demo/sources.list

@@ -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 - 0
resources/demo/start.sh

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

+ 151 - 138
server/api/cert.go

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

+ 2 - 4
server/api/template.go

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

+ 78 - 70
server/router/routers.go

@@ -1,77 +1,85 @@
 package router
 
 import (
-	"bufio"
-	"github.com/0xJacky/Nginx-UI/server/api"
-	"github.com/gin-contrib/static"
-	"github.com/gin-gonic/gin"
-	"net/http"
-	"strings"
+    "bufio"
+    "github.com/0xJacky/Nginx-UI/server/api"
+    "github.com/0xJacky/Nginx-UI/server/settings"
+    "github.com/gin-contrib/static"
+    "github.com/gin-gonic/gin"
+    "net/http"
+    "strings"
 )
 
 func InitRouter() *gin.Engine {
-	r := gin.New()
-	r.Use(gin.Logger())
-
-	r.Use(gin.Recovery())
-
-	r.Use(static.Serve("/", mustFS("")))
-
-	r.NoRoute(func(c *gin.Context) {
-		accept := c.Request.Header.Get("Accept")
-		if strings.Contains(accept, "text/html") {
-			file, _ := mustFS("").Open("index.html")
-			stat, _ := file.Stat()
-			c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
-				bufio.NewReader(file), nil)
-		}
-	})
-
-	g := r.Group("/api")
-	{
-		g.GET("install", api.InstallLockCheck)
-		g.POST("install", api.InstallNginxUI)
-
-		g.POST("/login", api.Login)
-		g.DELETE("/logout", api.Logout)
-
-		g := g.Group("/", authRequired())
-		{
-			g.GET("/analytic", api.Analytic)
-			g.GET("/analytic/init", api.GetAnalyticInit)
-
-			g.GET("/users", api.GetUsers)
-			g.GET("/user/:id", api.GetUser)
-			g.POST("/user", api.AddUser)
-			g.POST("/user/:id", api.EditUser)
-			g.DELETE("/user/:id", api.DeleteUser)
-
-			g.GET("domains", api.GetDomains)
-			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("configs", api.GetConfigs)
-			g.GET("config/:name", api.GetConfig)
-			g.POST("config", api.AddConfig)
-			g.POST("config/:name", api.EditConfig)
-
-			g.GET("backups", api.GetFileBackupList)
-			g.GET("backup/:id", api.GetFileBackup)
-
-			g.GET("template/:name", api.GetTemplate)
-
-			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
+    r := gin.New()
+    r.Use(gin.Logger())
+
+    r.Use(gin.Recovery())
+
+    r.Use(static.Serve("/", mustFS("")))
+
+    r.NoRoute(func(c *gin.Context) {
+        accept := c.Request.Header.Get("Accept")
+        if strings.Contains(accept, "text/html") {
+            file, _ := mustFS("").Open("index.html")
+            stat, _ := file.Stat()
+            c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
+                bufio.NewReader(file), nil)
+        }
+    })
+
+    g := r.Group("/api")
+    {
+
+        g.GET("settings", func(c *gin.Context) {
+            c.JSON(http.StatusOK, gin.H{
+                "demo": settings.ServerSettings.Demo,
+            })
+        })
+
+        g.GET("install", api.InstallLockCheck)
+        g.POST("install", api.InstallNginxUI)
+
+        g.POST("/login", api.Login)
+        g.DELETE("/logout", api.Logout)
+
+        g := g.Group("/", authRequired())
+        {
+            g.GET("/analytic", api.Analytic)
+            g.GET("/analytic/init", api.GetAnalyticInit)
+
+            g.GET("/users", api.GetUsers)
+            g.GET("/user/:id", api.GetUser)
+            g.POST("/user", api.AddUser)
+            g.POST("/user/:id", api.EditUser)
+            g.DELETE("/user/:id", api.DeleteUser)
+
+            g.GET("domains", api.GetDomains)
+            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("configs", api.GetConfigs)
+            g.GET("config/:name", api.GetConfig)
+            g.POST("config", api.AddConfig)
+            g.POST("config/:name", api.EditConfig)
+
+            g.GET("backups", api.GetFileBackupList)
+            g.GET("backup/:id", api.GetFileBackup)
+
+            g.GET("template/:name", api.GetTemplate)
+
+            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
 }

+ 2 - 0
server/settings/settings.go

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

+ 6 - 0
server/template/template.go

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

+ 2 - 2
server/test/lego_test.go

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

+ 141 - 139
server/tool/cert.go

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

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott