Quellcode durchsuchen

Merge pull request #97 from 0xJacky/v1.8-dev

v1.8
Jacky vor 2 Jahren
Ursprung
Commit
ed142f3061
100 geänderte Dateien mit 5060 neuen und 2185 gelöschten Zeilen
  1. 7 0
      app.example.ini
  2. 5 2
      frontend/components.d.ts
  3. 12 11
      frontend/package.json
  4. 13 0
      frontend/src/api/auto_cert.ts
  5. 5 0
      frontend/src/api/dns_credential.ts
  6. 4 0
      frontend/src/api/domain.ts
  7. 4 0
      frontend/src/api/template.ts
  8. 5 5
      frontend/src/components/Chart/AreaChart.vue
  9. 7 3
      frontend/src/components/ChatGPT/ChatGPT.vue
  10. 1 1
      frontend/src/components/FooterToolbar/FooterToolBar.vue
  11. 3 3
      frontend/src/components/NginxControl/NginxControl.vue
  12. 17 18
      frontend/src/components/StdDataDisplay/StdBatchEdit.vue
  13. 22 21
      frontend/src/components/StdDataDisplay/StdCurd.vue
  14. 5 5
      frontend/src/components/StdDataDisplay/StdPagination.vue
  15. 1 1
      frontend/src/components/StdDataDisplay/StdTable.vue
  16. 4 4
      frontend/src/components/StdDataEntry/components/StdPassword.vue
  17. 1 1
      frontend/src/components/StdDataEntry/index.tsx
  18. 4 1
      frontend/src/language/constants.ts
  19. 297 177
      frontend/src/language/en/app.po
  20. 289 189
      frontend/src/language/messages.pot
  21. 0 0
      frontend/src/language/translations.json
  22. BIN
      frontend/src/language/zh_CN/app.mo
  23. 294 177
      frontend/src/language/zh_CN/app.po
  24. BIN
      frontend/src/language/zh_TW/app.mo
  25. 294 177
      frontend/src/language/zh_TW/app.po
  26. 19 1
      frontend/src/layouts/BaseLayout.vue
  27. 2 1
      frontend/src/main.ts
  28. 18 6
      frontend/src/routes/index.ts
  29. 1 1
      frontend/src/version.json
  30. 1 1
      frontend/src/views/cert/Cert.vue
  31. 92 0
      frontend/src/views/cert/DNSChallenge.vue
  32. 61 0
      frontend/src/views/cert/DNSCredential.vue
  33. 6 9
      frontend/src/views/config/Config.vue
  34. 1 1
      frontend/src/views/config/ConfigEdit.vue
  35. 3 3
      frontend/src/views/config/InspectConfig.vue
  36. 1 2
      frontend/src/views/config/config.ts
  37. 1 1
      frontend/src/views/dashboard/DashBoard.vue
  38. 1 2
      frontend/src/views/domain/DomainAdd.vue
  39. 48 30
      frontend/src/views/domain/DomainEdit.vue
  40. 3 4
      frontend/src/views/domain/DomainList.vue
  41. 6 4
      frontend/src/views/domain/cert/Cert.vue
  42. 1 1
      frontend/src/views/domain/cert/CertInfo.vue
  43. 1 1
      frontend/src/views/domain/cert/ChangeCert.vue
  44. 31 182
      frontend/src/views/domain/cert/IssueCert.vue
  45. 66 0
      frontend/src/views/domain/cert/components/AutoCertStepOne.vue
  46. 89 0
      frontend/src/views/domain/cert/components/DNSChallenge.vue
  47. 244 0
      frontend/src/views/domain/cert/components/ObtainCert.vue
  48. 1 1
      frontend/src/views/domain/ngx_conf/LogEntry.vue
  49. 73 10
      frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue
  50. 23 4
      frontend/src/views/domain/ngx_conf/config_template/ConfigTemplate.vue
  51. 15 0
      frontend/src/views/domain/ngx_conf/config_template/TemplateForm.vue
  52. 33 0
      frontend/src/views/domain/ngx_conf/config_template/TemplateFormItem.vue
  53. 0 1
      frontend/src/views/domain/ngx_conf/directive/DirectiveAdd.vue
  54. 3 2
      frontend/src/views/domain/ngx_conf/directive/DirectiveEditor.vue
  55. 15 31
      frontend/src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue
  56. 6 6
      frontend/src/views/nginx_log/NginxLog.vue
  57. 6 6
      frontend/src/views/other/Install.vue
  58. 2 3
      frontend/src/views/other/Login.vue
  59. 50 0
      frontend/src/views/preference/BasicSettings.vue
  60. 24 0
      frontend/src/views/preference/GitSettings.vue
  61. 24 0
      frontend/src/views/preference/NginxLogSettings.vue
  62. 41 0
      frontend/src/views/preference/OpenAISettings.vue
  63. 45 73
      frontend/src/views/preference/Preference.vue
  64. 28 0
      frontend/src/views/preference/typedef.ts
  65. 79 47
      frontend/src/views/system/Upgrade.vue
  66. 0 11
      frontend/src/views/template/Template.vue
  67. 1 1
      frontend/version.json
  68. 312 330
      frontend/yarn.lock
  69. 119 14
      go.mod
  70. 777 43
      go.sum
  71. 43 0
      lego-config.sh
  72. 25 81
      resources/development/nginx/sites-available/homework.jackyu.cn
  73. 13 0
      resources/development/nginx/sites-available/ojbk.me
  74. 30 23
      resources/development/nginx/sites-available/qi.jackyu.cn
  75. 5 0
      resources/development/nginx/sites-available/test
  76. 1 0
      resources/development/nginx/sites-enabled/homework.jackyu.cn
  77. 1 0
      resources/development/nginx/sites-enabled/qi.jackyu.cn
  78. 1 0
      resources/development/nginx/sites-enabled/test
  79. 24 24
      resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/fullchain.cer
  80. 25 25
      resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/private.key
  81. 93 0
      resources/development/nginx/ssl/home.jackyu.cn/fullchain.cer
  82. 27 0
      resources/development/nginx/ssl/home.jackyu.cn/private.key
  83. 64 0
      resources/development/nginx/ssl/homework.jackyu.cn/fullchain.cer
  84. 27 0
      resources/development/nginx/ssl/homework.jackyu.cn/private.key
  85. 64 0
      resources/development/nginx/ssl/ojbk.me/fullchain.cer
  86. 27 0
      resources/development/nginx/ssl/ojbk.me/private.key
  87. 93 0
      resources/development/nginx/ssl/qi.jackyu.cn/fullchain.cer
  88. 27 0
      resources/development/nginx/ssl/qi.jackyu.cn/private.key
  89. 64 0
      resources/development/nginx/ssl/qi.jackyu.cn_amstourship.jackyu.cn/fullchain.cer
  90. 27 0
      resources/development/nginx/ssl/qi.jackyu.cn_amstourship.jackyu.cn/private.key
  91. 64 0
      resources/development/nginx/ssl/test.jackyu.cn/fullchain.cer
  92. 27 0
      resources/development/nginx/ssl/test.jackyu.cn/private.key
  93. 322 304
      server/api/cert.go
  94. 130 0
      server/api/dns_credential.go
  95. 70 8
      server/api/domain.go
  96. 55 54
      server/api/install.go
  97. 3 0
      server/api/settings.go
  98. 11 18
      server/api/template.go
  99. 12 4
      server/api/upgrade.go
  100. 18 15
      server/api/user.go

+ 7 - 0
app.example.ini

@@ -20,3 +20,10 @@ Model =
 BaseUrl =
 Proxy =
 Token =
+
+[git]
+Url =
+AuthMethod =
+Username =
+Password =
+PrivateKeyFile =

+ 5 - 2
frontend/components.d.ts

@@ -1,5 +1,7 @@
-// generated by unplugin-vue-components
-// We suggest you to commit this file into source control
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
 // Read more: https://github.com/vuejs/core/pull/3399
 import '@vue/runtime-core'
 
@@ -20,6 +22,7 @@ declare module '@vue/runtime-core' {
     AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
     ADivider: typeof import('ant-design-vue/es')['Divider']
     ADrawer: typeof import('ant-design-vue/es')['Drawer']
+    ADropdown: typeof import('ant-design-vue/es')['Dropdown']
     AEmpty: typeof import('ant-design-vue/es')['Empty']
     AForm: typeof import('ant-design-vue/es')['Form']
     AFormItem: typeof import('ant-design-vue/es')['FormItem']

+ 12 - 11
frontend/package.json

@@ -1,7 +1,7 @@
 {
     "name": "nginx-ui-frontend-next",
     "private": true,
-    "version": "1.7.9",
+    "version": "1.8.0",
     "type": "commonjs",
     "scripts": {
         "dev": "vite",
@@ -12,13 +12,14 @@
     },
     "dependencies": {
         "@ant-design/icons-vue": "^6.1.0",
+        "@formkit/auto-animate": "^1.0.0-beta.6",
         "@types/lodash": "^4.14.188",
         "@types/marked": "^4.0.8",
         "@types/nprogress": "^0.2.0",
         "@types/sortablejs": "^1.15.0",
-        "ant-design-vue": "^3.2.15",
+        "ant-design-vue": "^3.2.17",
         "apexcharts": "^3.36.3",
-        "axios": "^1.2.2",
+        "axios": "^1.3.5",
         "dayjs": "^1.11.7",
         "highlight.js": "^11.7.0",
         "marked": "^4.2.5",
@@ -27,26 +28,26 @@
         "pinia-plugin-persistedstate": "^3.0.2",
         "reconnecting-websocket": "^4.4.0",
         "vite-plugin-build-id": "^0.2.3",
-        "vue": "^3.2.45",
+        "vue": "^3.2.47",
         "vue-router": "4",
         "vue3-ace-editor": "^2.2.2",
         "vue3-apexcharts": "^1.4.1",
-        "vue3-gettext": "^2.3.4",
+        "vue3-gettext": "^2.5.0-alpha.1",
         "vuedraggable": "^4.1.0",
         "xterm": "^5.1.0",
         "xterm-addon-attach": "^0.8.0",
         "xterm-addon-fit": "^0.7.0"
     },
     "devDependencies": {
-        "@vitejs/plugin-vue": "^4.0.0",
-        "@vitejs/plugin-vue-jsx": "^3.0.0",
+        "@vitejs/plugin-vue": "^4.1.0",
+        "@vitejs/plugin-vue-jsx": "^3.0.1",
         "@zougt/vite-plugin-theme-preprocessor": "^1.4.8",
         "less": "^4.1.3",
-        "typescript": "^4.9.4",
-        "unplugin-vue-components": "^0.22.12",
-        "vite": "^4.1.4",
+        "typescript": "^5.0.4",
+        "unplugin-vue-components": "^0.24.1",
+        "vite": "^4.2.1",
         "vite-plugin-html": "^3.2.0",
         "vite-svg-loader": "^4.0.0",
-        "vue-tsc": "^1.0.24"
+        "vue-tsc": "^1.2.0"
     }
 }

+ 13 - 0
frontend/src/api/auto_cert.ts

@@ -0,0 +1,13 @@
+import http from '@/lib/http'
+
+const auto_cert = {
+    get_dns_providers() {
+        return http.get('/auto_cert/dns/providers')
+    },
+
+    get_dns_provider(code: string) {
+        return http.get('/auto_cert/dns/provider/' + code)
+    }
+}
+
+export default auto_cert

+ 5 - 0
frontend/src/api/dns_credential.ts

@@ -0,0 +1,5 @@
+import Curd from '@/api/curd'
+
+const dns_credential = new Curd('/dns_credential')
+
+export default dns_credential

+ 4 - 0
frontend/src/api/domain.ts

@@ -25,6 +25,10 @@ class Domain extends Curd {
     duplicate(name: string, data: any) {
         return http.post(this.baseUrl + '/' + name + '/duplicate', data)
     }
+
+    advance_mode(name: string, data: any) {
+        return http.post(this.baseUrl + '/' + name + '/advance', data)
+    }
 }
 
 const domain = new Domain('/domain')

+ 4 - 0
frontend/src/api/template.ts

@@ -18,6 +18,10 @@ class Template extends Curd {
         return http.get('template/block/' + name)
     }
 
+    build_block(name: string, data: any) {
+        return http.post('template/block/' + name, data)
+    }
+
 }
 
 const template = new Template('/template')

+ 5 - 5
frontend/src/components/Chart/AreaChart.vue

@@ -22,11 +22,11 @@ let chartOptions = {
             enabled: false
         },
         animations: {
-            enabled: false,
+            enabled: false
         },
         toolbar: {
             show: false
-        },
+        }
     },
     colors: ['#ff6385', '#36a3eb'],
     fill: {
@@ -41,7 +41,7 @@ let chartOptions = {
     },
     stroke: {
         curve: 'smooth',
-        width: 0,
+        width: 0
     },
     xaxis: {
         type: 'datetime',
@@ -75,7 +75,7 @@ let chartOptions = {
         },
         onItemHover: {
             highlightDataSeries: false
-        },
+        }
     }
 }
 
@@ -114,7 +114,7 @@ const callback = () => {
                 },
                 onItemHover: {
                     highlightDataSeries: false
-                },
+                }
             }
         }
     }

+ 7 - 3
frontend/src/components/ChatGPT/ChatGPT.vue

@@ -7,11 +7,10 @@ import {urlJoin} from '@/lib/helper'
 import {marked} from 'marked'
 import hljs from 'highlight.js'
 import 'highlight.js/styles/vs2015.css'
-import {SendOutlined} from '@ant-design/icons-vue'
-import Template from '@/views/template/Template.vue'
+import Icon, {SendOutlined} from '@ant-design/icons-vue'
+
 import openai from '@/api/openai'
 import ChatGPT_logo from '@/assets/svg/ChatGPT_logo.svg'
-import Icon from '@ant-design/icons-vue'
 
 const {$gettext} = useGettext()
 
@@ -260,6 +259,11 @@ const show = computed(() => messages?.value?.length > 1)
 .chatgpt {
     position: sticky;
     top: 78px;
+
+    :deep(.ant-card-body) {
+        max-height: 100vh;
+        overflow-y: scroll;
+    }
 }
 
 .chat-start {

+ 1 - 1
frontend/src/components/FooterToolbar/FooterToolBar.vue

@@ -28,7 +28,7 @@ export default {
 <style lang="less" scoped>
 .dark {
     .ant-pro-footer-toolbar {
-        background: rgba(24, 24, 24, 0.62);
+        background: rgba(24, 24, 24, 1);
         border-top: unset;
     }
 }

+ 3 - 3
frontend/src/components/NginxControl/NginxControl.vue

@@ -1,14 +1,14 @@
 <script setup lang="ts">
 import gettext from '@/gettext'
-
-const {$gettext} = gettext
 import ngx from '@/api/ngx'
 import logLevel from '@/views/config/constants'
 import {message} from 'ant-design-vue'
 import {ReloadOutlined} from '@ant-design/icons-vue'
-import Template from '@/views/template/Template.vue'
+
 import {ref, watch} from 'vue'
 
+const {$gettext} = gettext
+
 function get_status() {
     ngx.status().then(r => {
         if (r?.running === true) {

+ 17 - 18
frontend/src/components/StdDataDisplay/StdBatchEdit.vue

@@ -1,12 +1,11 @@
 <script setup lang="ts">
 import {reactive, ref} from 'vue'
 import gettext from '@/gettext'
-
-const {$gettext} = gettext
-
 import StdDataEntry from '@/components/StdDataEntry'
 import {message} from 'ant-design-vue'
 
+const {$gettext} = gettext
+
 const emit = defineEmits(['onSave'])
 
 const props = defineProps(['api', 'beforeSave'])
@@ -49,23 +48,23 @@ async function ok() {
 
 <template>
     <a-modal
-            class="std-curd-edit-modal"
-            :mask="false"
-            :title="$gettext('Batch Modify')"
-            v-model:visible="visible"
-            :cancel-text="$gettext('Cancel')"
-            :ok-text="$gettext('OK')"
-            @ok="ok"
-            :confirm-loading="loading"
-            :width="600"
-            destroyOnClose
+        class="std-curd-edit-modal"
+        :mask="false"
+        :title="$gettext('Batch Modify')"
+        v-model:visible="visible"
+        :cancel-text="$gettext('Cancel')"
+        :ok-text="$gettext('OK')"
+        @ok="ok"
+        :confirm-loading="loading"
+        :width="600"
+        destroyOnClose
     >
 
         <std-data-entry
-                ref="std_data_entry"
-                :data-list="batchColumns"
-                v-model:data-source="data"
-                :error="error"
+            ref="std_data_entry"
+            :data-list="batchColumns"
+            :data-source="data"
+            :error="error"
         />
 
         <slot name="extra"/>
@@ -74,4 +73,4 @@ async function ok() {
 
 <style scoped>
 
-</style>
+</style>

+ 22 - 21
frontend/src/components/StdDataDisplay/StdCurd.vue

@@ -4,7 +4,7 @@ import StdTable from './StdTable.vue'
 
 import StdDataEntry from '@/components/StdDataEntry'
 
-import {reactive, ref} from 'vue'
+import {provide, reactive, ref} from 'vue'
 import {message} from 'ant-design-vue'
 
 const {$gettext} = gettext
@@ -62,6 +62,7 @@ const props = defineProps({
 const visible = ref(false)
 const update = ref(0)
 const data: any = reactive({id: null})
+provide('data', data)
 const error: any = reactive({})
 const selected = ref([])
 
@@ -146,12 +147,12 @@ const selectedRowKeys = ref([])
             </template>
 
             <std-table
-                    ref="table"
-                    v-model:selected-row-keys="selectedRowKeys"
-                    v-bind="props"
-                    @clickEdit="edit"
-                    @selected="onSelect"
-                    :key="update"
+                ref="table"
+                v-model:selected-row-keys="selectedRowKeys"
+                v-bind="props"
+                @clickEdit="edit"
+                @selected="onSelect"
+                :key="update"
             >
                 <template v-slot:actions="slotProps">
                     <slot name="actions" :actions="slotProps.record"/>
@@ -160,26 +161,26 @@ const selectedRowKeys = ref([])
         </a-card>
 
         <a-modal
-                class="std-curd-edit-modal"
-                :mask="false"
-                :title="edit_text?edit_text:(data.id ? $gettext('Modify') : $gettext('Add'))"
-                :visible="visible"
-                :cancel-text="$gettext('Cancel')"
-                :ok-text="$gettext('OK')"
-                @cancel="cancel"
-                @ok="ok"
-                :width="modalWidth"
-                destroyOnClose
+            class="std-curd-edit-modal"
+            :mask="false"
+            :title="edit_text?edit_text:(data.id ? $gettext('Modify') : $gettext('Add'))"
+            :visible="visible"
+            :cancel-text="$gettext('Cancel')"
+            :ok-text="$gettext('OK')"
+            @cancel="cancel"
+            @ok="ok"
+            :width="modalWidth"
+            destroyOnClose
         >
             <div class="before-edit" v-if="$slots.beforeEdit">
                 <slot name="beforeEdit" :data="data"/>
             </div>
 
             <std-data-entry
-                    ref="std_data_entry"
-                    :data-list="editableColumns()"
-                    v-model:data-source="data"
-                    :error="error"
+                ref="std_data_entry"
+                :data-list="editableColumns()"
+                :data-source="data"
+                :error="error"
             />
 
             <slot name="edit" :data="data"/>

+ 5 - 5
frontend/src/components/StdDataDisplay/StdPagination.vue

@@ -24,11 +24,11 @@ const pageSize = computed({
 <template>
     <div class="pagination-container" v-if="pagination.total>pagination.per_page">
         <a-pagination
-                :current="pagination.current_page"
-                v-model:pageSize="pageSize"
-                :size="size"
-                :total="pagination.total"
-                @change="change"
+            :current="pagination.current_page"
+            v-model:pageSize="pageSize"
+            :size="size"
+            :total="pagination.total"
+            @change="change"
         />
     </div>
 </template>

+ 1 - 1
frontend/src/components/StdDataDisplay/StdTable.vue

@@ -474,7 +474,7 @@ function initSortable() {
         <std-data-entry
             v-if="!disable_search && searchColumns.length"
             :data-list="searchColumns"
-            v-model:data-source="params"
+            :data-source="params"
             layout="inline"
         >
             <template #action>

+ 4 - 4
frontend/src/components/StdDataEntry/components/StdPassword.vue

@@ -34,9 +34,9 @@ function handle_generate() {
 <template>
     <a-input-group compact>
         <a-input-password
-                v-if="!visibility"
-                :class="{compact: generate}"
-                v-model:value="M_value" :placeholoder="placeholder"/>
+            v-if="!visibility"
+            :class="{compact: generate}"
+            v-model:value="M_value" :placeholoder="placeholder"/>
         <a-input v-else :class="{compact: generate}" v-model:value="M_value" :placeholoder="placeholder"/>
         <a-button @click="handle_generate" v-if="generate" type="primary">
             <translate>Generate</translate>
@@ -48,4 +48,4 @@ function handle_generate() {
 .compact {
     width: calc(100% - 91px)
 }
-</style>
+</style>

+ 1 - 1
frontend/src/components/StdDataEntry/index.tsx

@@ -1,6 +1,6 @@
 import StdDataEntry from './StdDataEntry.js'
 import {h} from 'vue'
-import {Input, Textarea, InputPassword, InputNumber} from 'ant-design-vue'
+import {Input, InputNumber, Textarea} from 'ant-design-vue'
 import StdSelector from './components/StdSelector.vue'
 import StdSelect from './components/StdSelect.vue'
 import StdPassword from './components/StdPassword.vue'

+ 4 - 1
frontend/src/language/constants.ts

@@ -12,6 +12,9 @@ export const msg = [
     $gettext('Preparing lego configurations'),
     $gettext('Creating client facilitates communication with the CA server'),
     $gettext('Using HTTP01 challenge provider'),
+    $gettext('Using DNS01 challenge provider'),
+    $gettext('Setting environment variables'),
+    $gettext('Cleaning environment variables'),
     $gettext('Registering user'),
     $gettext('Obtaining certificate'),
     $gettext('Writing certificate to disk'),
@@ -27,6 +30,6 @@ export const msg = [
     $gettext('Performing core upgrade'),
     $gettext('Perform core upgrade error'),
     $gettext('Upgraded successfully'),
-    
+
     $gettext('File exists')
 ]

Datei-Diff unterdrückt, da er zu groß ist
+ 297 - 177
frontend/src/language/en/app.po


Datei-Diff unterdrückt, da er zu groß ist
+ 289 - 189
frontend/src/language/messages.pot


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
frontend/src/language/translations.json


BIN
frontend/src/language/zh_CN/app.mo


Datei-Diff unterdrückt, da er zu groß ist
+ 294 - 177
frontend/src/language/zh_CN/app.po


BIN
frontend/src/language/zh_TW/app.mo


Datei-Diff unterdrückt, da er zu groß ist
+ 294 - 177
frontend/src/language/zh_TW/app.po


+ 19 - 1
frontend/src/layouts/BaseLayout.vue

@@ -71,7 +71,11 @@ const lang = computed(() => {
                 <a-layout-content>
                     <page-header/>
                     <div class="router-view">
-                        <router-view/>
+                        <router-view v-slot="{ Component, route }">
+                            <transition name="slide-fade">
+                                <component :is="Component" :key="route.path"/>
+                            </transition>
+                        </router-view>
                     </div>
                 </a-layout-content>
 
@@ -122,6 +126,20 @@ const lang = computed(() => {
 </style>
 
 <style lang="less">
+.slide-fade-enter-active {
+    transition: all .3s ease-in-out;
+}
+
+.slide-fade-leave-active {
+    transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
+}
+
+.slide-fade-enter-from, .slide-fade-enter-to, .slide-fade-leave-to
+    /* .slide-fade-leave-active for below version 2.1.8 */ {
+    transform: translateX(10px);
+    opacity: 0;
+}
+
 body {
     overflow: unset !important;
 }

+ 2 - 1
frontend/src/main.ts

@@ -6,6 +6,7 @@ import router from './routes'
 //import 'ant-design-vue/dist/antd.less'
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 import {useSettingsStore} from '@/pinia'
+import {autoAnimatePlugin} from '@formkit/auto-animate/vue'
 
 
 const pinia = createPinia()
@@ -19,6 +20,6 @@ app.use(gettext)
 const settings = useSettingsStore()
 gettext.current = settings.language || 'en'
 
-app.use(router).mount('#app')
+app.use(router).use(autoAnimatePlugin).mount('#app')
 
 export default app

+ 18 - 6
frontend/src/routes/index.ts

@@ -1,4 +1,4 @@
-import {createRouter, createWebHashHistory, createWebHistory} from 'vue-router'
+import {createRouter, createWebHashHistory} from 'vue-router'
 import gettext from '../gettext'
 import {useUserStore} from '@/pinia'
 
@@ -6,12 +6,12 @@ import {
     CloudOutlined,
     CodeOutlined,
     FileOutlined,
+    FileTextOutlined,
     HomeOutlined,
     InfoCircleOutlined,
-    UserOutlined,
-    FileTextOutlined,
+    SafetyCertificateOutlined,
     SettingOutlined,
-    SafetyCertificateOutlined
+    UserOutlined
 } from '@ant-design/icons-vue'
 import NProgress from 'nprogress'
 import 'nprogress/nprogress.css'
@@ -87,10 +87,22 @@ export const routes = [
             {
                 path: 'cert',
                 name: () => $gettext('Certification'),
-                component: () => import('@/views/cert/Cert.vue'),
+                component: () => import('@/layouts/BaseRouterView.vue'),
                 meta: {
                     icon: SafetyCertificateOutlined
-                }
+                },
+                children: [
+                    {
+                        path: 'list',
+                        name: () => $gettext('Certification List'),
+                        component: () => import('@/views/cert/Cert.vue')
+                    },
+                    {
+                        path: 'dns_credential',
+                        name: () => $gettext('DNS Credentials'),
+                        component: () => import('@/views/cert/DNSCredential.vue')
+                    }
+                ]
             },
             {
                 path: 'terminal',

+ 1 - 1
frontend/src/version.json

@@ -1 +1 @@
-{"version":"1.7.9","build_id":87,"total_build":157}
+{"version":"1.8.0","build_id":98,"total_build":168}

+ 1 - 1
frontend/src/views/cert/Cert.vue

@@ -5,7 +5,7 @@ import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransf
 import {Badge} from 'ant-design-vue'
 import cert from '@/api/cert'
 import StdCurd from '@/components/StdDataDisplay/StdCurd.vue'
-import Template from '@/views/template/Template.vue'
+
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import CertInfo from '@/views/domain/cert/CertInfo.vue'
 import {h} from 'vue'

+ 92 - 0
frontend/src/views/cert/DNSChallenge.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import {computed, inject, ref, watch} from 'vue'
+import auto_cert from '@/api/auto_cert'
+import {useGettext} from 'vue3-gettext'
+import {SelectProps} from 'ant-design-vue'
+
+const {$gettext} = useGettext()
+const providers: any = ref([])
+
+const data: any = inject('data')!
+
+const code = computed(() => {
+    return data.code
+})
+
+function init() {
+    data.configuration = {
+        credentials: {},
+        additional: {}
+    }
+    providers.value?.forEach((v: any, k: number) => {
+        if (v.code === code.value) {
+            provider_idx.value = k
+        }
+    })
+}
+
+auto_cert.get_dns_providers().then(r => {
+    providers.value = r
+}).then(() => {
+    init()
+})
+
+const provider_idx = ref()
+
+const current: any = computed(() => {
+    return providers.value?.[provider_idx.value]
+})
+
+
+watch(code, init)
+
+watch(current, () => {
+    data.code = current.value.code
+    data.provider = current.value.name
+    auto_cert.get_dns_provider(current.value.code).then(r => {
+        Object.assign(current.value, r)
+    })
+})
+
+const options = computed<SelectProps['options']>(() => {
+    let list: SelectProps['options'] = []
+
+    providers.value.forEach((v: any, k: number) => {
+        list!.push({
+            value: k,
+            label: v.name
+        })
+    })
+
+    return list
+})
+
+const filterOption = (input: string, option: any) => {
+    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+}
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('DNS Provider')">
+            <a-select v-model:value="provider_idx" show-search :options="options" :filter-option="filterOption"/>
+        </a-form-item>
+        <template v-if="current?.configuration?.credentials">
+            <h4>{{ $gettext('Credentials') }}</h4>
+            <a-form-item :label="k" v-for="(v,k) in current?.configuration?.credentials"
+                         :extra="v" :rules="[{ required: true }]">
+                <a-input v-model:value="data.configuration.credentials[k]"/>
+            </a-form-item>
+        </template>
+        <template v-if="current?.configuration?.additional">
+            <h4>{{ $gettext('Additional') }}</h4>
+            <a-form-item :label="k" v-for="(v,k) in current?.configuration?.additional" :extra="v">
+                <a-input v-model:value="data.configuration.additional[k]"/>
+            </a-form-item>
+        </template>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 61 - 0
frontend/src/views/cert/DNSCredential.vue

@@ -0,0 +1,61 @@
+<script setup lang="tsx">
+import {useGettext} from 'vue3-gettext'
+import {datetime} from '@/components/StdDataDisplay/StdTableTransformer'
+import dns_credential from '@/api/dns_credential'
+import StdCurd from '@/components/StdDataDisplay/StdCurd.vue'
+import DNSChallenge from './DNSChallenge.vue'
+import {input} from '@/components/StdDataEntry'
+
+const {$gettext, interpolate} = useGettext()
+
+const columns = [{
+    title: () => $gettext('Name'),
+    dataIndex: 'name',
+    sorter: true,
+    pithy: true,
+    edit: {
+        type: input
+    }
+}, {
+    title: () => $gettext('Provider'),
+    dataIndex: ['config', 'name'],
+    sorter: true,
+    pithy: true
+}, {
+    title: () => $gettext('Updated at'),
+    dataIndex: 'updated_at',
+    customRender: datetime,
+    sorter: true,
+    pithy: true
+}, {
+    title: () => $gettext('Action'),
+    dataIndex: 'action'
+}]
+</script>
+
+<template>
+    <std-curd :title="$gettext('DNS Credentials')" :api="dns_credential" :columns="columns"
+              row-key="name"
+    >
+        <template #beforeEdit>
+            <a-alert type="info" show-icon :message="$gettext('Note')">
+                <template #description>
+                    <p v-translate>
+                        Please fill in the API authentication credentials provided by your DNS provider.
+                        We will add one or more TXT records to the DNS records of your domain for ownership
+                        verification.
+                        Once the verification is complete, the records will be removed.
+                        Please note that the time configurations below are all in seconds.
+                    </p>
+                </template>
+            </a-alert>
+        </template>
+        <template #edit="{data}">
+            <d-n-s-challenge/>
+        </template>
+    </std-curd>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 6 - 9
frontend/src/views/config/Config.vue

@@ -2,19 +2,17 @@
 import StdTable from '@/components/StdDataDisplay/StdTable.vue'
 import gettext from '@/gettext'
 import config from '@/api/config'
-import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
-import {computed, h, nextTick, ref, watch} from 'vue'
-
-const {$gettext} = gettext
-
-const api = config
-
+import {computed, ref, watch} from 'vue'
 import configColumns from '@/views/config/config'
 import {useRoute} from 'vue-router'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import router from '@/routes'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 
+const {$gettext} = gettext
+
+const api = config
+
 const table = ref(null)
 const route = useRoute()
 
@@ -44,9 +42,8 @@ watch(route, () => {
 </script>
 
 <template>
-    <inspect-config ref="inspect_config"/>
-
     <a-card :title="$gettext('Configurations')">
+        <inspect-config ref="inspect_config"/>
         <std-table
             :key="update"
             ref="table"

+ 1 - 1
frontend/src/views/config/ConfigEdit.vue

@@ -72,10 +72,10 @@ const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 2
 
 
 <template>
-    <inspect-config ref="inspect_config"/>
     <a-row :gutter="16">
         <a-col :xs="24" :sm="24" :md="editor_md">
             <a-card :title="$gettext('Edit Configuration')">
+                <inspect-config ref="inspect_config"/>
                 <code-editor v-model:content="configText"/>
                 <footer-tool-bar>
                     <a-space>

+ 3 - 3
frontend/src/views/config/InspectConfig.vue

@@ -3,7 +3,7 @@ import ngx from '@/api/ngx'
 import {useGettext} from 'vue3-gettext'
 import {ref} from 'vue'
 import logLevel from '@/views/config/constants'
-import Template from '@/views/template/Template.vue'
+
 
 const {$gettext} = useGettext()
 
@@ -26,7 +26,7 @@ defineExpose({
 </script>
 
 <template>
-    <a-card class="inspect-container" :title="$gettext('Inspect Configurations')">
+    <div class="inspect-container">
         <a-alert :message="$gettext('Configuration file is test successful')" type="success"
                  show-icon v-if="data?.level<logLevel.Debug"/>
         <a-alert
@@ -50,7 +50,7 @@ defineExpose({
                 {{ data.message }}
             </template>
         </a-alert>
-    </a-card>
+    </div>
 </template>
 
 <style lang="less" scoped>

+ 1 - 2
frontend/src/views/config/config.ts

@@ -1,10 +1,9 @@
 import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
 import gettext from '@/gettext'
+import {h} from 'vue'
 
 const {$gettext} = gettext
 
-import {h} from 'vue'
-
 const configColumns = [{
     title: () => $gettext('Name'),
     dataIndex: 'name',

+ 1 - 1
frontend/src/views/dashboard/DashBoard.vue

@@ -20,7 +20,7 @@ const cpu_analytic_series = reactive([{name: 'User', data: <any>[]}, {name: 'Tot
 const net_analytic = reactive([{name: $gettext('Receive'), data: <any>[]},
     {name: $gettext('Send'), data: <any>[]}])
 const disk_io_analytic = reactive([{name: $gettext('Writes'), data: <any>[]},
-    {name: $gettext('Writes'), data: <any>[]}])
+    {name: $gettext('Reads'), data: <any>[]}])
 const memory = reactive({})
 const disk = reactive({})
 const disk_io = reactive({writes: 0, reads: 0})

+ 1 - 2
frontend/src/views/domain/DomainAdd.vue

@@ -8,7 +8,6 @@ import ngx from '@/api/ngx'
 import {computed, reactive, ref} from 'vue'
 import {message} from 'ant-design-vue'
 import {useRouter} from 'vue-router'
-import template from '@/api/template'
 
 const {$gettext, interpolate} = useGettext()
 
@@ -60,7 +59,7 @@ function save() {
 const router = useRouter()
 
 function goto_modify() {
-    router.push('/domain/' + config.name)
+    router.push('/domain/' + ngx_config.name)
 }
 
 function create_another() {

+ 48 - 30
frontend/src/views/domain/DomainEdit.vue

@@ -4,15 +4,14 @@ import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 
 import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor'
 import {useGettext} from 'vue3-gettext'
-import {computed, reactive, ref, watch} from 'vue'
+import {computed, provide, reactive, ref, watch} from 'vue'
 import {useRoute, useRouter} from 'vue-router'
 import domain from '@/api/domain'
 import ngx from '@/api/ngx'
-import {message} from 'ant-design-vue'
+import {message, Modal} from 'ant-design-vue'
 import config from '@/api/config'
 import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
 
-
 const {$gettext, interpolate} = useGettext()
 
 const route = useRoute()
@@ -25,7 +24,7 @@ watch(route, () => {
 
 const update = ref(0)
 
-const ngx_config = reactive({
+const ngx_config: any = reactive({
     name: '',
     upstreams: [],
     servers: []
@@ -57,6 +56,10 @@ const history_chatgpt_record = ref([])
 
 function handle_response(r: any) {
 
+    if (r.advanced) {
+        advance_mode.value = true
+    }
+
     Object.keys(cert_info_map).forEach(v => {
         delete cert_info_map[v]
     })
@@ -95,14 +98,17 @@ function handle_parse_error(r: any) {
     throw r
 }
 
-function on_mode_change(advance_mode: boolean) {
-    if (advance_mode) {
-        build_config()
-    } else {
-        return ngx.tokenize_config(configText.value).then((r: any) => {
-            Object.assign(ngx_config, r)
-        }).catch(handle_parse_error)
-    }
+function on_mode_change(advanced: boolean) {
+    domain.advance_mode(name.value, {advanced}).then(() => {
+        advance_mode.value = advanced
+        if (advanced) {
+            build_config()
+        } else {
+            return ngx.tokenize_config(configText.value).then((r: any) => {
+                Object.assign(ngx_config, r)
+            }).catch(handle_parse_error)
+        }
+    })
 }
 
 function build_config() {
@@ -124,17 +130,19 @@ const save = async () => {
         }
     }
 
-    domain.save(name.value, {
+    await domain.save(name.value, {
         name: filename.value || name.value,
         content: configText.value, overwrite: true
     }).then(r => {
         handle_response(r)
-        router.push('/domain/' + filename.value)
+        router.push({
+            path: '/domain/' + filename.value,
+            query: route.query
+        })
         message.success($gettext('Saved successfully'))
     }).catch(handle_parse_error).finally(() => {
         saving.value = false
     })
-
 }
 
 function enable() {
@@ -156,15 +164,24 @@ function disable() {
 }
 
 function on_change_enabled(checked: boolean) {
-    if (checked) {
-        enable()
-    } else {
-        disable()
-    }
+    Modal.confirm({
+        title: checked ? $gettext('Do you want to enable this site?') : $gettext('Do you want to disable this site?'),
+        mask: false,
+        centered: true,
+        async onOk() {
+            if (checked) {
+                enable()
+            } else {
+                disable()
+            }
+        }
+    })
 }
 
 const editor_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 16 : 24)
 const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 24)
+
+provide('save_site_config', save)
 </script>
 <template>
     <a-row :gutter="16">
@@ -183,7 +200,7 @@ const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 2
                     <div class="mode-switch">
                         <div class="switch">
                             <a-switch size="small" :disabled="parse_error_status"
-                                      v-model:checked="advance_mode" @change="on_mode_change"/>
+                                      :checked="advance_mode" @change="on_mode_change"/>
                         </div>
                         <template v-if="advance_mode">
                             <div>{{ $gettext('Advance Mode') }}</div>
@@ -194,6 +211,13 @@ const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 2
                     </div>
                 </template>
 
+                <a-form-item :label="$gettext('Enabled')">
+                    <a-switch :checked="enabled" @change="on_change_enabled"/>
+                </a-form-item>
+                <a-form-item :label="$gettext('Name')">
+                    <a-input v-model:value="filename"/>
+                </a-form-item>
+
                 <transition name="slide-fade">
                     <div v-if="advance_mode" key="advance">
                         <div class="parse-error-alert-wrapper" v-if="parse_error_status">
@@ -209,12 +233,6 @@ const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 2
                     </div>
 
                     <div class="domain-edit-container" key="basic" v-else>
-                        <a-form-item :label="$gettext('Enabled')">
-                            <a-switch v-model:checked="enabled" @change="on_change_enabled"/>
-                        </a-form-item>
-                        <a-form-item :label="$gettext('Name')">
-                            <a-input v-model:value="filename"/>
-                        </a-form-item>
                         <ngx-config-editor
                             ref="ngx_config_editor"
                             :ngx_config="ngx_config"
@@ -280,14 +298,14 @@ const chat_md = computed(() => history_chatgpt_record?.value?.length > 1 ? 8 : 2
 }
 
 .slide-fade-enter-active {
-    transition: all .5s ease-in-out;
+    transition: all .3s ease-in-out;
 }
 
 .slide-fade-leave-active {
-    transition: all .5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
+    transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
 }
 
-.slide-fade-enter, .slide-fade-leave-to
+.slide-fade-enter-from, .slide-fade-enter-to, .slide-fade-leave-to
     /* .slide-fade-leave-active for below version 2.1.8 */ {
     transform: translateX(10px);
     opacity: 0;

+ 3 - 4
frontend/src/views/domain/DomainList.vue

@@ -3,15 +3,14 @@ import StdTable from '@/components/StdDataDisplay/StdTable.vue'
 
 import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
 import {useGettext} from 'vue3-gettext'
-
-const {$gettext, interpolate} = useGettext()
-
 import domain from '@/api/domain'
 import {Badge, message} from 'ant-design-vue'
 import {h, ref} from 'vue'
 import {input} from '@/components/StdDataEntry'
 import SiteDuplicate from '@/views/domain/SiteDuplicate.vue'
 
+const {$gettext, interpolate} = useGettext()
+
 const columns = [{
     title: () => $gettext('Name'),
     dataIndex: 'name',
@@ -131,8 +130,8 @@ function handle_click_duplicate(name: string) {
                 </a-popconfirm>
             </template>
         </std-table>
+        <site-duplicate v-model:visible="show_duplicator" :name="target" @duplicated="table.get_list()"/>
     </a-card>
-    <site-duplicate v-model:visible="show_duplicator" :name="target" @duplicated="table.get_list()"/>
 </template>
 
 <style scoped>

+ 6 - 4
frontend/src/views/domain/cert/Cert.vue

@@ -1,13 +1,14 @@
 <script setup lang="ts">
 import CertInfo from '@/views/domain/cert/CertInfo.vue'
 import IssueCert from '@/views/domain/cert/IssueCert.vue'
-import {computed, ref} from 'vue'
+import {computed} from 'vue'
 import {useGettext} from 'vue3-gettext'
 import ChangeCert from '@/views/domain/cert/ChangeCert.vue'
 
 const {$gettext} = useGettext()
 
-const props = defineProps(['config_name', 'directivesMap', 'current_server_directives', 'enabled', 'cert_info'])
+const props = defineProps(['config_name', 'directivesMap', 'current_server_directives',
+    'enabled', 'ngx_config', 'current_server_index', 'cert_info'])
 
 const emit = defineEmits(['callback', 'update:enabled'])
 
@@ -33,14 +34,15 @@ const enabled = computed({
 <template>
     <div>
         <h2 v-translate>Certificate Status</h2>
-        <cert-info ref="info" :cert="props.cert_info"/>
+        <cert-info ref="info" :cert="cert_info"/>
 
-        <change-cert :directives-map="props.directivesMap"/>
+        <change-cert :directives-map="directivesMap"/>
 
         <issue-cert
             :config_name="config_name"
             :current_server_directives="props.current_server_directives"
             :directives-map="props.directivesMap"
+            :ngx_config="ngx_config"
             v-model:enabled="enabled"
             @callback="callback"
         />

+ 1 - 1
frontend/src/views/domain/cert/CertInfo.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {CloseCircleOutlined, CheckCircleOutlined} from '@ant-design/icons-vue'
+import {CheckCircleOutlined, CloseCircleOutlined} from '@ant-design/icons-vue'
 import dayjs from 'dayjs'
 
 const props = defineProps(['cert'])

+ 1 - 1
frontend/src/views/domain/cert/ChangeCert.vue

@@ -3,7 +3,7 @@ import {useGettext} from 'vue3-gettext'
 import {h, ref} from 'vue'
 import StdTable from '@/components/StdDataDisplay/StdTable.vue'
 import cert from '@/api/cert'
-import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
+import {customRender} from '@/components/StdDataDisplay/StdTableTransformer'
 import {input} from '@/components/StdDataEntry'
 import {Badge} from 'ant-design-vue'
 

+ 31 - 182
frontend/src/views/domain/cert/IssueCert.vue

@@ -1,139 +1,19 @@
 <script setup lang="ts">
 import {useGettext} from 'vue3-gettext'
-import {computed, nextTick, ref, watch} from 'vue'
-import {message} from 'ant-design-vue'
-import domain from '@/api/domain'
-import websocket from '@/lib/websocket'
-import Template from '@/views/template/Template.vue'
+import {computed, nextTick, provide, ref, watch} from 'vue'
+
+import ObtainCert from '@/views/domain/cert/components/ObtainCert.vue'
 
 const {$gettext, interpolate} = useGettext()
 
-const props = defineProps(['config_name', 'directivesMap', 'current_server_directives', 'enabled'])
+const props = defineProps(['config_name', 'directivesMap', 'current_server_directives',
+    'enabled', 'ngx_config'])
 
-const emit = defineEmits(['changeEnabled', 'callback', 'update:enabled'])
+const emit = defineEmits(['callback', 'update:enabled'])
 
 const issuing_cert = ref(false)
-const modalVisible = ref(false)
-
-function onchange(r: boolean) {
-    emit('changeEnabled', r)
-    change_auto_cert(r)
-    if (r) {
-        job()
-    }
-}
-
-function job() {
-    issuing_cert.value = true
-
-    if (no_server_name.value) {
-        message.error($gettext('server_name not found in directives'))
-        issuing_cert.value = false
-        return
-    }
-
-    const server_name = props.directivesMap['server_name'][0]
-
-    if (!props.directivesMap['ssl_certificate']) {
-        props.current_server_directives.splice(server_name.idx + 1, 0, {
-            directive: 'ssl_certificate',
-            params: ''
-        })
-    }
-
-    nextTick(() => {
-        if (!props.directivesMap['ssl_certificate_key']) {
-            const ssl_certificate = props.directivesMap['ssl_certificate'][0]
-            props.current_server_directives.splice(ssl_certificate.idx + 1, 0, {
-                directive: 'ssl_certificate_key',
-                params: ''
-            })
-        }
-    }).then(() => {
-        issue_cert(props.config_name, name.value, callback)
-    })
-}
-
-function callback(ssl_certificate: string, ssl_certificate_key: string) {
-    props.directivesMap['ssl_certificate'][0]['params'] = ssl_certificate
-    props.directivesMap['ssl_certificate_key'][0]['params'] = ssl_certificate_key
-}
-
-function change_auto_cert(r: boolean) {
-    if (r) {
-        domain.add_auto_cert(props.config_name, {domains: name.value.trim().split(' ')}).then(() => {
-            message.success(interpolate($gettext('Auto-renewal enabled for %{name}'), {name: name.value}))
-        }).catch(e => {
-            message.error(e.message ?? interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: name.value}))
-        })
-    } else {
-        domain.remove_auto_cert(props.config_name).then(() => {
-            message.success(interpolate($gettext('Auto-renewal disabled for %{name}'), {name: name.value}))
-        }).catch(e => {
-            message.error(e.message ?? interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: name.value}))
-        })
-    }
-}
-
-const logContainer = ref(null)
-
-function log(msg: string) {
-    const para = document.createElement('p')
-    para.appendChild(document.createTextNode($gettext(msg)));
-
-    (logContainer.value as any as Node).appendChild(para);
-
-    (logContainer.value as any as Element).scroll({top: 320, left: 0, behavior: 'smooth'})
-}
 
-const issue_cert = async (config_name: string, server_name: string, callback: Function) => {
-    progressStatus.value = 'active'
-    modalClosable.value = false
-    modalVisible.value = true
-    progressPercent.value = 0;
-    (logContainer.value as any as Element).innerHTML = ''
-
-    log($gettext('Getting the certificate, please wait...'))
-
-    const ws = websocket(`/api/domain/${config_name}/cert`, false)
-
-    ws.onopen = () => {
-        ws.send(JSON.stringify({
-            server_name: server_name.trim().split(' ')
-        }))
-    }
-
-    ws.onmessage = m => {
-        const r = JSON.parse(m.data)
-        log(r.message)
-
-        switch (r.status) {
-            case 'info':
-                progressPercent.value += 5
-                break
-            default:
-                modalClosable.value = true
-                issuing_cert.value = false
-
-                if (r.status === 'success' && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
-                    progressStatus.value = 'success'
-                    progressPercent.value = 100
-                    callback(r.ssl_certificate, r.ssl_certificate_key)
-                } else {
-                    progressStatus.value = 'exception'
-                }
-                break
-        }
-    }
-}
-
-const no_server_name = computed(() => {
-    return props.directivesMap['server_name']?.length === 0
-})
-
-const name = computed(() => {
-    return props.directivesMap['server_name'][0].params.trim()
-})
+const obtain_cert: any = ref()
 
 const enabled = computed({
     get() {
@@ -144,75 +24,40 @@ const enabled = computed({
     }
 })
 
-watch(no_server_name, () => {
-    emit('update:enabled', false)
-    onchange(false)
-})
+const no_server_name = computed(() => {
+    if (props.directivesMap['server_name'] === undefined) {
+        return true
+    }
 
-const progressStrokeColor = {
-    from: '#108ee9',
-    to: '#87d068'
-}
+    return props.directivesMap['server_name'].length === 0
+})
 
-const progressPercent = ref(0)
+provide('no_server_name', no_server_name)
+provide('props', props)
+provide('issuing_cert', issuing_cert)
 
-const progressStatus = ref('active')
+watch(no_server_name, () => emit('update:enabled', false))
+const update = ref(0)
 
-const modalClosable = ref(false)
+async function onchange() {
+    update.value++
+    await nextTick(() => {
+        obtain_cert.value.toggle(enabled.value)
+    })
+}
 </script>
 
 <template>
-    <a-modal
-        :title="$gettext('Obtaining certificate')"
-        v-model:visible="modalVisible"
-        :mask-closable="modalClosable"
-        :footer="null" :closable="modalClosable" force-render>
-        <a-progress
-            :stroke-color="progressStrokeColor"
-            :percent="progressPercent"
-            :status="progressStatus"
-        />
-
-        <div class="issue-cert-log-container" ref="logContainer"/>
-
-    </a-modal>
+    <obtain-cert ref="obtain_cert" :key="update"/>
     <div class="issue-cert">
         <a-form-item :label="$gettext('Encrypt website with Let\'s Encrypt')">
             <a-switch
                 :loading="issuing_cert"
-                v-model:checked="enabled"
-                @change="onchange"
+                :checked="enabled"
                 :disabled="no_server_name"
+                @change="onchange"
             />
-            <a-alert
-                v-if="no_server_name"
-                :message="$gettext('Warning')"
-                type="warning"
-                show-icon
-            >
-                <template slot="description">
-                    <span v-if="no_server_name" v-translate>
-                        server_name parameter is required
-                    </span>
-                </template>
-            </a-alert>
         </a-form-item>
-        <a-alert type="info" closable :message="$gettext('Note')">
-            <template #description>
-                <p v-translate>
-                    The server_name in the current configuration must be the domain name
-                    you need to get the certificate.
-                </p>
-                <p v-translate>
-                    The certificate for the domain will be checked every hour,
-                    and will be renewed if it has been more than 1 month since it was last issued.
-                </p>
-                <p 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-alert>
     </div>
 </template>
 
@@ -233,6 +78,10 @@ const modalClosable = ref(false)
 </style>
 
 <style lang="less" scoped>
+.ant-tag {
+    margin: 0;
+}
+
 .issue-cert {
     margin: 15px 0;
 }

+ 66 - 0
frontend/src/views/domain/cert/components/AutoCertStepOne.vue

@@ -0,0 +1,66 @@
+<script setup lang="ts">
+import {inject, Ref} from 'vue'
+import {useGettext} from 'vue3-gettext'
+import DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
+
+const {$gettext} = useGettext()
+const no_server_name: Ref = inject('no_server_name')!
+const data: Ref = inject('data')!
+</script>
+
+<template>
+    <template v-if="no_server_name">
+        <a-alert
+            :message="$gettext('Warning')"
+            type="warning"
+            show-icon
+        >
+            <template #description>
+                    <span v-if="no_server_name">
+                        {{ $gettext('server_name parameter is required') }}
+                    </span>
+            </template>
+        </a-alert>
+        <br/>
+    </template>
+
+    <a-alert type="info" show-icon :message="$gettext('Note')">
+        <template #description>
+            <p v-translate>
+                The server_name
+                in the current configuration must be the domain name you need to get the certificate, support
+                multiple domains.
+            </p>
+            <p v-translate>
+                The certificate for the domain will be checked every hour,
+                and will be renewed if it has been more than 1 week since it was last issued.
+            </p>
+            <p v-if="data.challenge_method==='http01'" v-translate>
+                Make sure you have configured a reverse proxy for .well-known
+                directory to HTTPChallengePort before obtaining the certificate.
+            </p>
+            <p v-else-if="data.challenge_method==='dns01'" v-translate>
+                Please first add credentials in Certification > DNS Credentials, and then select one of the credentials
+                below to request the API of the DNS provider.
+            </p>
+        </template>
+    </a-alert>
+    <br/>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('Challenge Method')">
+            <a-select v-model:value="data.challenge_method">
+                <a-select-option value="http01">
+                    {{ $gettext('HTTP01') }}
+                </a-select-option>
+                <a-select-option value="dns01">
+                    {{ $gettext('DNS01') }}
+                </a-select-option>
+            </a-select>
+        </a-form-item>
+    </a-form>
+    <d-n-s-challenge v-if="data.challenge_method==='dns01'"/>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 89 - 0
frontend/src/views/domain/cert/components/DNSChallenge.vue

@@ -0,0 +1,89 @@
+<script setup lang="ts">
+import {computed, inject, ref, watch} from 'vue'
+import auto_cert from '@/api/auto_cert'
+import {useGettext} from 'vue3-gettext'
+import {SelectProps} from 'ant-design-vue'
+import dns_credential from '@/api/dns_credential'
+
+const {$gettext} = useGettext()
+const providers: any = ref([])
+const credentials: any = ref([])
+
+const data: any = inject('data')!
+
+const code = computed(() => {
+    return data.code
+})
+
+function init() {
+    providers.value?.forEach((v: any, k: number) => {
+        if (v.code === code.value) {
+            provider_idx.value = k
+        }
+    })
+}
+
+auto_cert.get_dns_providers().then(r => {
+    providers.value = r
+}).then(() => {
+    init()
+})
+
+const provider_idx = ref()
+
+const current: any = computed(() => {
+    return providers.value?.[provider_idx.value]
+})
+
+
+watch(code, init)
+
+watch(current, () => {
+    credentials.value = []
+    data.code = current.value.code
+    data.provider = current.value.name
+    data.dns_credential_id = null
+
+    dns_credential.get_list({provider: data.provider}).then(r => {
+        r.data.forEach((v: any) => {
+            credentials.value.push({
+                value: v.id,
+                label: v.name
+            })
+        })
+
+    })
+})
+
+const options = computed<SelectProps['options']>(() => {
+    let list: SelectProps['options'] = []
+
+    providers.value.forEach((v: any, k: number) => {
+        list!.push({
+            value: k,
+            label: v.name
+        })
+    })
+
+    return list
+})
+
+const filterOption = (input: string, option: any) => {
+    return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+}
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('DNS Provider')">
+            <a-select v-model:value="provider_idx" show-search :options="options" :filter-option="filterOption"/>
+        </a-form-item>
+        <a-form-item v-if="provider_idx>-1" :label="$gettext('Credential')" :rules="[{ required: true }]">
+            <a-select :options="credentials" v-model:value="data.dns_credential_id"/>
+        </a-form-item>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 244 - 0
frontend/src/views/domain/cert/components/ObtainCert.vue

@@ -0,0 +1,244 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+import {computed, inject, nextTick, provide, reactive, Ref, ref} from 'vue'
+import websocket from '@/lib/websocket'
+import {message, Modal} from 'ant-design-vue'
+import template from '@/api/template'
+import domain from '@/api/domain'
+import AutoCertStepOne from '@/views/domain/cert/components/AutoCertStepOne.vue'
+
+const {$gettext, interpolate} = useGettext()
+
+const modalVisible = ref(false)
+
+const step = ref(1)
+
+const progressStrokeColor = {
+    from: '#108ee9',
+    to: '#87d068'
+}
+
+const data: any = reactive({
+    challenge_method: 'http01',
+    code: '',
+    configuration: {
+        credentials: {},
+        additional: {}
+    }
+})
+const progressPercent = ref(0)
+const progressStatus = ref('active')
+const modalClosable = ref(true)
+provide('data', data)
+
+const logContainer = ref(null)
+
+const save_site_config: Function = inject('save_site_config')!
+const no_server_name: Ref = inject('no_server_name')!
+const props: any = inject('props')!
+const issuing_cert: Ref<boolean> = inject('issuing_cert')!
+
+async function callback(ssl_certificate: string, ssl_certificate_key: string) {
+    props.directivesMap['ssl_certificate'][0]['params'] = ssl_certificate
+    props.directivesMap['ssl_certificate_key'][0]['params'] = ssl_certificate_key
+    save_site_config()
+}
+
+function change_auto_cert(status: boolean) {
+    if (status) {
+        domain.add_auto_cert(props.config_name, {domains: name.value.trim().split(' ')}).then(() => {
+            message.success(interpolate($gettext('Auto-renewal enabled for %{name}'), {name: name.value}))
+        }).catch(e => {
+            message.error(e.message ?? interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: name.value}))
+        })
+    } else {
+        domain.remove_auto_cert(props.config_name).then(() => {
+            message.success(interpolate($gettext('Auto-renewal disabled for %{name}'), {name: name.value}))
+        }).catch(e => {
+            message.error(e.message ?? interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: name.value}))
+        })
+    }
+}
+
+async function onchange(r: boolean) {
+    if (r) {
+        await template.get_block('letsencrypt.conf').then(r => {
+            props.ngx_config.servers.forEach(async (v: any) => {
+                v.locations = v.locations.filter((l: any) => l.path !== '/.well-known/acme-challenge')
+
+                v.locations.push(...r.locations)
+            })
+        })
+        // if ssl_certificate is empty, do not save, just use the config from last step.
+        if (!props.directivesMap['ssl_certificate']?.[0]) {
+            await save_site_config()
+        }
+        job()
+    } else {
+        await props.ngx_config.servers.forEach((v: any) => {
+            v.locations = v.locations.filter((l: any) => l.path !== '/.well-known/acme-challenge')
+        })
+        save_site_config()
+        change_auto_cert(r)
+    }
+}
+
+function job() {
+    modalClosable.value = false
+    issuing_cert.value = true
+
+    if (no_server_name.value) {
+        message.error($gettext('server_name not found in directives'))
+        issuing_cert.value = false
+        return
+    }
+
+    const server_name = props.directivesMap['server_name'][0]
+
+    if (!props.directivesMap['ssl_certificate']) {
+        props.current_server_directives.splice(server_name.idx + 1, 0, {
+            directive: 'ssl_certificate',
+            params: ''
+        })
+    }
+
+    nextTick(() => {
+        if (!props.directivesMap['ssl_certificate_key']) {
+            const ssl_certificate = props.directivesMap['ssl_certificate'][0]
+            props.current_server_directives.splice(ssl_certificate.idx + 1, 0, {
+                directive: 'ssl_certificate_key',
+                params: ''
+            })
+        }
+    }).then(() => {
+        issue_cert(props.config_name, name.value, callback)
+    })
+}
+
+function log(msg: string) {
+    const para = document.createElement('p')
+    para.appendChild(document.createTextNode($gettext(msg)));
+
+    (logContainer.value as any as Node).appendChild(para);
+
+    (logContainer.value as any as Element).scroll({top: 320, left: 0, behavior: 'smooth'})
+}
+
+const issue_cert = async (config_name: string, server_name: string, callback: Function) => {
+    progressStatus.value = 'active'
+    modalClosable.value = false
+    modalVisible.value = true
+    progressPercent.value = 0;
+    (logContainer.value as any as Element).innerHTML = ''
+
+    log($gettext('Getting the certificate, please wait...'))
+
+    const ws = websocket(`/api/domain/${config_name}/cert`, false)
+
+    ws.onopen = () => {
+        ws.send(JSON.stringify({
+            server_name: server_name.trim().split(' '),
+            ...data
+        }))
+    }
+
+    ws.onmessage = m => {
+        const r = JSON.parse(m.data)
+        log(r.message)
+
+        switch (r.status) {
+            case 'info':
+                progressPercent.value += 5
+                break
+            default:
+                modalClosable.value = true
+                issuing_cert.value = false
+
+                if (r.status === 'success' && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
+                    progressStatus.value = 'success'
+                    progressPercent.value = 100
+                    callback(r.ssl_certificate, r.ssl_certificate_key)
+                    change_auto_cert(true)
+                } else {
+                    progressStatus.value = 'exception'
+                }
+                break
+        }
+    }
+}
+
+const name = computed(() => {
+    return props.directivesMap['server_name'][0].params.trim()
+})
+
+
+function toggle(status: boolean) {
+    if (status) {
+        Modal.confirm({
+            title: $gettext('Do you want to disable auto-cert renewal?'),
+            content: $gettext('We will remove the HTTPChallenge configuration from ' +
+                'this file and reload the Nginx. Are you sure you want to continue?'),
+            mask: false,
+            centered: true,
+            onOk: () => onchange(false)
+        })
+    } else {
+        modalVisible.value = true
+        modalClosable.value = true
+    }
+}
+
+defineExpose({
+    toggle
+})
+
+const can_next = computed(() => {
+    if (step.value === 2) {
+        return false
+    } else {
+        if (data.challenge_method === 'http01') {
+            return true
+        } else if (data.challenge_method === 'dns01') {
+            return data?.code ?? false
+        }
+    }
+})
+
+function next() {
+    step.value++
+    onchange(true)
+}
+</script>
+
+<template>
+    <a-modal
+        :title="$gettext('Obtain certificate')"
+        v-model:visible="modalVisible"
+        :mask-closable="modalClosable"
+        :footer="null" :closable="modalClosable" force-render>
+        <template v-if="step===1">
+            <auto-cert-step-one/>
+        </template>
+        <template v-else-if="step===2">
+            <a-progress
+                :stroke-color="progressStrokeColor"
+                :percent="progressPercent"
+                :status="progressStatus"
+            />
+
+            <div class="issue-cert-log-container" ref="logContainer"/>
+        </template>
+        <div class="control-btn" v-if="can_next">
+            <a-button type="primary" @click="next">
+                {{ $gettext('Next') }}
+            </a-button>
+        </div>
+    </a-modal>
+</template>
+
+<style lang="less" scoped>
+.control-btn {
+    display: flex;
+    justify-content: flex-end;
+}
+</style>

+ 1 - 1
frontend/src/views/domain/ngx_conf/LogEntry.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {FileTextOutlined, FileExclamationOutlined} from '@ant-design/icons-vue'
+import {FileExclamationOutlined, FileTextOutlined} from '@ant-design/icons-vue'
 import {computed, ref} from 'vue'
 import {useRouter} from 'vue-router'
 

+ 73 - 10
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue

@@ -1,14 +1,16 @@
 <script setup lang="ts">
 import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor.vue'
 import LocationEditor from '@/views/domain/ngx_conf/LocationEditor.vue'
-import {computed, onMounted, ref, watch} from 'vue'
+import {computed, inject, onMounted, ref, watch} from 'vue'
 import {useRoute, useRouter} from 'vue-router'
 import {useGettext} from 'vue3-gettext'
 import Cert from '@/views/domain/cert/Cert.vue'
 import LogEntry from '@/views/domain/ngx_conf/LogEntry.vue'
-import ConfigTemplate from '@/views/domain/ngx_conf/ConfigTemplate.vue'
+import ConfigTemplate from '@/views/domain/ngx_conf/config_template/ConfigTemplate.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
-import {PlusOutlined} from '@ant-design/icons-vue'
+import {MoreOutlined, PlusOutlined} from '@ant-design/icons-vue'
+import {Modal} from 'ant-design-vue'
+import template from '@/api/template'
 
 const {$gettext} = useGettext()
 
@@ -16,11 +18,34 @@ const props = defineProps(['ngx_config', 'auto_cert', 'enabled', 'cert_info'])
 
 const emit = defineEmits(['callback', 'update:auto_cert'])
 
+const save_site_config: Function = inject('save_site_config')!
+
 const route = useRoute()
 
 const current_server_index = ref(0)
 const name = ref(route.params.name)
 
+function confirm_change_tls(r: boolean) {
+    Modal.confirm({
+        title: $gettext('Do you want to enable TLS?'),
+        content: $gettext('To make sure the certification auto-renewal can work normally, ' +
+            'we need to add a location which can proxy the request from authority to backend, ' +
+            'and we need to save this file and reload the Nginx. Are you sure you want to continue?'),
+        mask: false,
+        centered: true,
+        async onOk() {
+            await template.get_block('letsencrypt.conf').then(r => {
+                const first = props.ngx_config.servers[0]
+                first.locations = first.locations.filter((l: any) => l.path !== '/.well-known/acme-challenge')
+                first.locations.push(...r.locations)
+            })
+            await save_site_config()
+
+            change_tls(r)
+        }
+    })
+}
+
 function change_tls(r: any) {
     if (r) {
         // deep copy servers[0] to servers[1]
@@ -78,7 +103,7 @@ function change_tls(r: any) {
 }
 
 const current_server_directives = computed(() => {
-    return props.ngx_config.servers[current_server_index.value].directives
+    return props.ngx_config.servers?.[current_server_index.value]?.directives
 })
 
 const directivesMap = computed(() => {
@@ -138,6 +163,18 @@ onMounted(() => {
 
 const router = useRouter()
 
+const servers_length = computed(() => {
+    return props.ngx_config.servers.length
+})
+
+watch(servers_length, () => {
+    if (current_server_index.value >= servers_length.value) {
+        current_server_index.value = servers_length.value - 1
+    } else if (current_server_index.value < 0) {
+        current_server_index.value = 0
+    }
+})
+
 watch(current_server_index, () => {
     router.push({
         query: {
@@ -153,19 +190,41 @@ function add_server() {
         directives: []
     })
 }
+
+function remove_server(index: number) {
+    Modal.confirm({
+        title: $gettext('Do you want to remove this server?'),
+        mask: false,
+        centered: true,
+        onOk: () => props.ngx_config?.servers?.splice(index, 1)
+    })
+}
 </script>
 
 <template>
     <div>
         <a-form-item :label="$gettext('Enable TLS')" v-if="!support_ssl">
-            <a-switch @change="change_tls"/>
+            <a-switch @change="confirm_change_tls"/>
         </a-form-item>
 
         <h2>{{ $gettext('Custom') }}</h2>
         <code-editor v-model:content="ngx_config.custom" default-height="150px"/>
 
         <a-tabs v-model:activeKey="current_server_index">
-            <a-tab-pane :tab="'Server '+(k+1)" v-for="(v,k) in props.ngx_config.servers" :key="k">
+            <a-tab-pane v-for="(v,k) in props.ngx_config.servers" :key="k">
+                <template #tab>
+                    Server {{ (k + 1) }}
+                    <a-dropdown>
+                        <MoreOutlined/>
+                        <template #overlay>
+                            <a-menu>
+                                <a-menu-item>
+                                    <a href="javascript:;" @click="remove_server(k)">{{ $gettext('Delete') }}</a>
+                                </a-menu-item>
+                            </a-menu>
+                        </template>
+                    </a-dropdown>
+                </template>
                 <log-entry
                     :ngx_config="props.ngx_config"
                     :current_server_idx="current_server_index"
@@ -177,9 +236,11 @@ function add_server() {
                         <cert
                             v-if="current_support_ssl"
                             :config_name="ngx_config.name"
-                            :cert_info="props.cert_info?.[k]"
+                            :cert_info="cert_info?.[k]"
                             :current_server_directives="current_server_directives"
                             :directives-map="directivesMap"
+                            :current_server_index="current_server_index"
+                            :ngx_config="ngx_config"
                             v-model:enabled="autoCertRef"
                             @callback="$emit('callback')"
                         />
@@ -198,7 +259,7 @@ function add_server() {
                 </div>
 
             </a-tab-pane>
-            
+
             <template #rightExtra>
                 <a-button @click="add_server" type="link" size="small">
                     <PlusOutlined/>
@@ -210,6 +271,8 @@ function add_server() {
 
 </template>
 
-<style scoped>
-
+<style lang="less" scoped>
+:deep(.ant-tabs-tab-btn) {
+    margin-left: 16px;
+}
 </style>

+ 23 - 4
frontend/src/views/domain/ngx_conf/ConfigTemplate.vue → frontend/src/views/domain/ngx_conf/config_template/ConfigTemplate.vue

@@ -1,13 +1,14 @@
 <script setup lang="ts">
 import {useGettext} from 'vue3-gettext'
 import template from '@/api/template'
-import {computed, ref} from 'vue'
+import {computed, provide, ref} from 'vue'
 import {storeToRefs} from 'pinia'
 import {useSettingsStore} from '@/pinia'
-import Template from '@/views/template/Template.vue'
+
 import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor.vue'
 import LocationEditor from '@/views/domain/ngx_conf/LocationEditor.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
+import TemplateForm from '@/views/domain/ngx_conf/config_template/TemplateForm.vue'
 
 const {$gettext} = useGettext()
 const {language} = storeToRefs(useSettingsStore())
@@ -16,6 +17,7 @@ const props = defineProps(['ngx_config', 'current_server_index'])
 const blocks = ref([])
 const data: any = ref({})
 const visible = ref(false)
+const name = ref('')
 
 function get_block_list() {
     template.get_block_list().then(r => {
@@ -25,9 +27,11 @@ function get_block_list() {
 
 get_block_list()
 
-function view(name: string) {
+
+function view(n: string) {
     visible.value = true
-    template.get_block(name).then(r => {
+    name.value = n
+    template.get_block(n).then(r => {
         data.value = r
     })
 }
@@ -54,6 +58,20 @@ async function add() {
 
     visible.value = false
 }
+
+const variables = computed(() => {
+    return data.value.variables
+})
+
+function build_template() {
+    template.build_block(name.value, variables.value).then(r => {
+        data.value.directives = r.directives
+        data.value.locations = r.locations
+        data.value.custom = r.custom
+    })
+}
+
+provide('build_template', build_template)
 </script>
 
 <template>
@@ -88,6 +106,7 @@ async function add() {
         >
             <p>{{ $gettext('Author') }}: {{ data.author }}</p>
             <p>{{ $gettext('Description') }}: {{ trans_description(data) }}</p>
+            <template-form :data="data.variables"/>
             <template v-if="data.custom">
                 <h2>{{ $gettext('Custom') }}</h2>
                 <code-editor v-model:content="data.custom" default-height="150px"/>

+ 15 - 0
frontend/src/views/domain/ngx_conf/config_template/TemplateForm.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import TemplateFormItem from '@/views/domain/ngx_conf/config_template/TemplateFormItem.vue'
+
+const props = defineProps(['data'])
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <template-form-item v-for="(v,k) in data" :name="k" :data="v"/>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 33 - 0
frontend/src/views/domain/ngx_conf/config_template/TemplateFormItem.vue

@@ -0,0 +1,33 @@
+<script setup lang="ts">
+import {computed, inject, watch} from 'vue'
+import {storeToRefs} from 'pinia'
+import {useSettingsStore} from '@/pinia'
+import {useGettext} from 'vue3-gettext'
+import _ from 'lodash'
+
+const {$gettext} = useGettext()
+
+const {language} = storeToRefs(useSettingsStore())
+const props = defineProps(['data', 'name'])
+
+const trans_name = computed(() => {
+    return props.data?.name?.[language.value] ?? props.data?.name?.en ?? ''
+})
+
+const build_template: any = inject('build_template')!
+
+const value = computed(() => props.data.value)
+
+watch(value, _.throttle(build_template, 500))
+</script>
+
+<template>
+    <a-form-item :label="trans_name">
+        <a-input v-if="data.type === 'string'" v-model:value="data.value"/>
+        <a-switch v-else-if="data.type === 'boolean'" v-model:checked="data.value"/>
+    </a-form-item>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 0 - 1
frontend/src/views/domain/ngx_conf/directive/DirectiveAdd.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import {If} from '@/views/domain/ngx_conf'
 import CodeEditor from '@/components/CodeEditor'
 import {reactive, ref} from 'vue'
 import {useGettext} from 'vue3-gettext'

+ 3 - 2
frontend/src/views/domain/ngx_conf/directive/DirectiveEditor.vue

@@ -2,8 +2,8 @@
 import DirectiveAdd from '@/views/domain/ngx_conf/directive/DirectiveAdd'
 import {useGettext} from 'vue3-gettext'
 import {reactive, ref} from 'vue'
-import draggable from 'vuedraggable'
 import DirectiveEditorItem from '@/views/domain/ngx_conf/directive/DirectiveEditorItem.vue'
+import draggable from 'vuedraggable'
 
 const {$gettext} = useGettext()
 
@@ -38,11 +38,12 @@ function onSave(idx: number) {
                                    :current_idx="current_idx" :index="index"
                                    :ngx_directives="ngx_directives"
                                    :readonly="readonly"
+                                   v-auto-animate
             />
         </template>
     </draggable>
 
-    <directive-add v-if="!readonly" :ngx_directives="ngx_directives"/>
+    <directive-add v-if="!readonly" :ngx_directives="ngx_directives" v-auto-animate/>
 </template>
 
 <style lang="less" scoped>

+ 15 - 31
frontend/src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 import CodeEditor from '@/components/CodeEditor'
 import {DeleteOutlined, HolderOutlined} from '@ant-design/icons-vue'
-import {If} from '@/views/domain/ngx_conf'
 
 import {useGettext} from 'vue3-gettext'
 import {onMounted, ref, watch} from 'vue'
@@ -68,24 +67,22 @@ function save() {
                 </a-button>
             </a-popconfirm>
         </div>
-        <transition name="slide">
-            <div v-if="current_idx===index" class="directive-editor-extra">
-                <div class="extra-content">
-                    <a-form layout="vertical">
-                        <a-form-item :label="$gettext('Comments')">
-                            <a-textarea v-model:value="directive.comments"/>
-                        </a-form-item>
-                        <a-form-item :label="$gettext('Content')" v-if="directive.directive==='include'">
-                            <code-editor v-model:content="content"
-                                         defaultHeight="200px" style="width: 100%;"/>
-                            <div class="save-btn">
-                                <a-button @click="save">{{ $gettext('Save') }}</a-button>
-                            </div>
-                        </a-form-item>
-                    </a-form>
-                </div>
+        <div v-if="current_idx===index" class="directive-editor-extra">
+            <div class="extra-content">
+                <a-form layout="vertical">
+                    <a-form-item :label="$gettext('Comments')">
+                        <a-textarea v-model:value="directive.comments"/>
+                    </a-form-item>
+                    <a-form-item :label="$gettext('Content')" v-if="directive.directive==='include'">
+                        <code-editor v-model:content="content"
+                                     defaultHeight="200px" style="width: 100%;"/>
+                        <div class="save-btn">
+                            <a-button @click="save">{{ $gettext('Save') }}</a-button>
+                        </div>
+                    </a-form-item>
+                </a-form>
             </div>
-        </transition>
+        </div>
     </div>
 
 </template>
@@ -117,19 +114,6 @@ function save() {
     }
 }
 
-.slide-enter-active, .slide-leave-active {
-    transition: max-height .2s ease;
-    overflow: hidden;
-}
-
-.slide-enter-from, .slide-leave-to {
-    max-height: 0;
-}
-
-.slide-enter-to, .slide-leave-from {
-    max-height: 600px;
-}
-
 .input-wrapper {
     display: flex;
     gap: 10px;

+ 6 - 6
frontend/src/views/nginx_log/NginxLog.vue

@@ -124,7 +124,7 @@ function on_scroll_log() {
     if (!loading.value && page.value > 0) {
         loading.value = true
         const elem = (logContainer.value as any as Element)
-        if (elem.scrollTop / elem.scrollHeight < 0.333) {
+        if (elem?.scrollTop / elem?.scrollHeight < 0.333) {
             nginx_log.page(page.value, {
                 conf_name: (route.query.conf_name as string),
                 type: logType(),
@@ -167,12 +167,12 @@ const computedBuffer = computed(() => {
             <pre class="nginx-log-container" ref="logContainer"
                  @scroll="debounce(on_scroll_log,100, null)()" v-html="computedBuffer"/>
         </a-card>
+        <footer-tool-bar v-if="control.type==='site'">
+            <a-button @click="router.go(-1)">
+                <translate>Back</translate>
+            </a-button>
+        </footer-tool-bar>
     </a-card>
-    <footer-tool-bar v-if="control.type==='site'">
-        <a-button @click="router.go(-1)">
-            <translate>Back</translate>
-        </a-button>
-    </footer-tool-bar>
 </template>
 
 <style lang="less">

+ 6 - 6
frontend/src/views/other/Install.vue

@@ -5,7 +5,7 @@ import {reactive, ref} from 'vue'
 import gettext from '@/gettext'
 import install from '@/api/install'
 import {useRoute, useRouter} from 'vue-router'
-import {MailOutlined, UserOutlined, LockOutlined, DatabaseOutlined} from '@ant-design/icons-vue'
+import {DatabaseOutlined, LockOutlined, MailOutlined, UserOutlined} from '@ant-design/icons-vue'
 
 const {$gettext, interpolate} = gettext
 
@@ -33,19 +33,19 @@ const rulesRef = reactive({
         {
             required: true,
             type: 'email',
-            message: () => $gettext('Please input your E-mail!'),
+            message: () => $gettext('Please input your E-mail!')
         }
     ],
     username: [
         {
             required: true,
-            message: () => $gettext('Please input your username!'),
+            message: () => $gettext('Please input your username!')
         }
     ],
     password: [
         {
             required: true,
-            message: () => $gettext('Please input your password!'),
+            message: () => $gettext('Please input your password!')
         }
     ],
     database: [
@@ -53,9 +53,9 @@ const rulesRef = reactive({
             message: () => interpolate(
                 $gettext('The filename cannot contain the following characters: %{c}'),
                 {c: '& &quot; ? < > # {} % ~ / \\'}
-            ),
+            )
         }
-    ],
+    ]
 })
 
 const {validate, validateInfos} = Form.useForm(modelRef, rulesRef)

+ 2 - 3
frontend/src/views/other/Login.vue

@@ -1,8 +1,5 @@
 <script setup lang="ts">
 import {useUserStore} from '@/pinia'
-
-const thisYear = new Date().getFullYear()
-
 import {LockOutlined, UserOutlined} from '@ant-design/icons-vue'
 import {reactive, ref, watch} from 'vue'
 import {useRoute, useRouter} from 'vue-router'
@@ -12,6 +9,8 @@ import auth from '@/api/auth'
 import install from '@/api/install'
 import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
 
+const thisYear = new Date().getFullYear()
+
 const route = useRoute()
 const router = useRouter()
 

+ 50 - 0
frontend/src/views/preference/BasicSettings.vue

@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+import {inject} from 'vue'
+import {IData} from '@/views/preference/typedef'
+
+const {$gettext} = useGettext()
+const data: IData = inject('data')!
+const theme = inject('theme')
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('Theme')">
+            <a-select v-model:value="theme">
+                <a-select-option value="auto">
+                    {{ $gettext('Auto') }}
+                </a-select-option>
+                <a-select-option value="light">
+                    {{ $gettext('Light') }}
+                </a-select-option>
+                <a-select-option value="dark">
+                    {{ $gettext('Dark') }}
+                </a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item :label="$gettext('HTTP Port')">
+            <p>{{ data.server.http_port }}</p>
+        </a-form-item>
+        <a-form-item :label="$gettext('Run Mode')">
+            <p>{{ data.server.run_mode }}</p>
+        </a-form-item>
+        <a-form-item :label="$gettext('Jwt Secret')">
+            <p>{{ data.server.jwt_secret }}</p>
+        </a-form-item>
+        <a-form-item :label="$gettext('Terminal Start Command')">
+            <p>{{ data.server.start_cmd }}</p>
+        </a-form-item>
+        <a-form-item :label="$gettext('HTTP Challenge Port')">
+            <a-input-number v-model:value="data.server.http_challenge_port"/>
+        </a-form-item>
+        <a-form-item :label="$gettext('Github Proxy')">
+            <a-input v-model:value="data.server.github_proxy"
+                     :placeholder="$gettext('Chinese user: https://ghproxy.com/')"/>
+        </a-form-item>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 24 - 0
frontend/src/views/preference/GitSettings.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+import {inject} from 'vue'
+import {IData} from '@/views/preference/typedef'
+
+const {$gettext} = useGettext()
+
+const data: IData = inject('data')!
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('Repo url')">
+            <a-input v-model:value="data.git.url"/>
+        </a-form-item>
+        <a-form-item :label="$gettext('Username')">
+            <a-input v-model:value="data.git.username"/>
+        </a-form-item>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 24 - 0
frontend/src/views/preference/NginxLogSettings.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+import {inject} from 'vue'
+import {IData} from '@/views/preference/typedef'
+
+const {$gettext} = useGettext()
+
+const data: IData = inject('data')!
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('Nginx Access Log Path')">
+            <a-input v-model:value="data.nginx_log.access_log_path"/>
+        </a-form-item>
+        <a-form-item :label="$gettext('Nginx Error Log Path')">
+            <a-input v-model:value="data.nginx_log.error_log_path"/>
+        </a-form-item>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 41 - 0
frontend/src/views/preference/OpenAISettings.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+import {useGettext} from 'vue3-gettext'
+import {IData} from '@/views/preference/typedef'
+import {inject} from 'vue'
+
+const {$gettext} = useGettext()
+
+const data: IData = inject('data')!
+</script>
+
+<template>
+    <a-form layout="vertical">
+        <a-form-item :label="$gettext('ChatGPT Model')">
+            <a-select v-model:value="data.openai.model">
+                <a-select-option value="gpt-4">
+                    {{ $gettext('GPT-4') }}
+                </a-select-option>
+                <a-select-option value="gpt-4-32k">
+                    {{ $gettext('GPT-4-32K') }}
+                </a-select-option>
+                <a-select-option value="gpt-3.5-turbo">
+                    {{ $gettext('GPT-3.5-Turbo') }}
+                </a-select-option>
+            </a-select>
+        </a-form-item>
+        <a-form-item :label="$gettext('API Base Url')">
+            <a-input v-model:value="data.openai.base_url"
+                     :placeholder="$gettext('Leave blank for the default: https://api.openai.com/')"/>
+        </a-form-item>
+        <a-form-item :label="$gettext('API Proxy')">
+            <a-input v-model:value="data.openai.proxy" placeholder="http://127.0.0.1:1087"/>
+        </a-form-item>
+        <a-form-item :label="$gettext('API Token')">
+            <a-input-password v-model:value="data.openai.token"/>
+        </a-form-item>
+    </a-form>
+</template>
+
+<style lang="less" scoped>
+
+</style>

+ 45 - 73
frontend/src/views/preference/Preference.vue

@@ -1,24 +1,30 @@
 <script setup lang="ts">
 import {useGettext} from 'vue3-gettext'
-import {reactive, ref} from 'vue'
+import {provide, ref} from 'vue'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import {useSettingsStore} from '@/pinia'
 import {dark_mode} from '@/lib/theme'
 import settings from '@/api/settings'
 import {message} from 'ant-design-vue'
+import BasicSettings from '@/views/preference/BasicSettings.vue'
+import OpenAISettings from '@/views/preference/OpenAISettings.vue'
+import NginxLogSettings from '@/views/preference/NginxLogSettings.vue'
+import GitSettings from '@/views/preference/GitSettings.vue'
+import {IData} from '@/views/preference/typedef'
 
 const {$gettext} = useGettext()
 
 const settingsStore = useSettingsStore()
 const theme = ref(settingsStore.theme)
-const data = ref({
+const data = ref<IData>({
     server: {
-        http_port: 9000,
+        http_port: '9000',
         run_mode: 'debug',
         jwt_secret: '',
         start_cmd: '',
         email: '',
-        http_challenge_port: 9180
+        http_challenge_port: '9180',
+        github_proxy: ''
     },
     nginx_log: {
         access_log_path: '',
@@ -29,6 +35,13 @@ const data = ref({
         base_url: '',
         proxy: '',
         token: ''
+    },
+    git: {
+        url: '',
+        auth_method: '',
+        username: '',
+        password: '',
+        private_key_file_path: ''
     }
 })
 
@@ -36,10 +49,12 @@ settings.get().then(r => {
     data.value = r
 })
 
-function save() {
+async function save() {
     settingsStore.set_theme(theme.value)
     settingsStore.set_preference_theme(theme.value)
-    dark_mode(theme.value === 'dark')
+    await dark_mode(theme.value === 'dark')
+    // fix type
+    data.value.server.http_challenge_port = data.value.server.http_challenge_port.toString()
     settings.save(data.value).then(r => {
         data.value = r
         message.success($gettext('Save successfully'))
@@ -47,80 +62,37 @@ function save() {
         message.error(e?.message ?? $gettext('Server error'))
     })
 }
+
+provide('data', data)
+provide('theme', theme)
+
+const activeKey = ref('1')
 </script>
 
 <template>
     <a-card :title="$gettext('Preference')">
         <div class="preference-container">
-            <a-form layout="vertical">
-                <h4>{{ $gettext('Basic') }}</h4>
-                <a-form-item :label="$gettext('HTTP Port')">
-                    <p>{{ data.server.http_port }}</p>
-                </a-form-item>
-                <a-form-item :label="$gettext('Run Mode')">
-                    <p>{{ data.server.run_mode }}</p>
-                </a-form-item>
-                <a-form-item :label="$gettext('Jwt Secret')">
-                    <p>{{ data.server.jwt_secret }}</p>
-                </a-form-item>
-                <a-form-item :label="$gettext('Terminal Start Command')">
-                    <p>{{ data.server.start_cmd }}</p>
-                </a-form-item>
-                <a-form-item :label="$gettext('HTTP Challenge Port')">
-                    <a-input-number v-model:value="data.server.http_challenge_port"/>
-                </a-form-item>
-                <a-form-item :label="$gettext('Theme')">
-                    <a-select v-model:value="theme">
-                        <a-select-option value="auto">
-                            {{ $gettext('Auto') }}
-                        </a-select-option>
-                        <a-select-option value="light">
-                            {{ $gettext('Light') }}
-                        </a-select-option>
-                        <a-select-option value="dark">
-                            {{ $gettext('Dark') }}
-                        </a-select-option>
-                    </a-select>
-                </a-form-item>
-                <h4>{{ $gettext('Nginx Log') }}</h4>
-                <a-form-item :label="$gettext('Nginx Access Log Path')">
-                    <a-input v-model:value="data.nginx_log.access_log_path"/>
-                </a-form-item>
-                <a-form-item :label="$gettext('Nginx Error Log Path')">
-                    <a-input v-model:value="data.nginx_log.error_log_path"/>
-                </a-form-item>
-                <h4>{{ $gettext('OpenAI') }}</h4>
-                <a-form-item :label="$gettext('ChatGPT Model')">
-                    <a-select v-model:value="data.openai.model">
-                        <a-select-option value="gpt-4">
-                            {{ $gettext('GPT-4') }}
-                        </a-select-option>
-                        <a-select-option value="gpt-4-32k">
-                            {{ $gettext('GPT-4-32K') }}
-                        </a-select-option>
-                        <a-select-option value="gpt-3.5-turbo">
-                            {{ $gettext('GPT-3.5-Turbo') }}
-                        </a-select-option>
-                    </a-select>
-                </a-form-item>
-                <a-form-item :label="$gettext('API Base Url')">
-                    <a-input v-model:value="data.openai.base_url"
-                             :placeholder="$gettext('Leave blank for the default: https://api.openai.com/')"/>
-                </a-form-item>
-                <a-form-item :label="$gettext('API Proxy')">
-                    <a-input v-model:value="data.openai.proxy" placeholder="http://127.0.0.1:1087"/>
-                </a-form-item>
-                <a-form-item :label="$gettext('API Token')">
-                    <a-input-password v-model:value="data.openai.token"/>
-                </a-form-item>
-            </a-form>
+            <a-tabs v-model:activeKey="activeKey">
+                <a-tab-pane :tab="$gettext('Basic')" key="1">
+                    <basic-settings/>
+                </a-tab-pane>
+                <a-tab-pane :tab="$gettext('Nginx Log')" key="2">
+                    <nginx-log-settings/>
+                </a-tab-pane>
+                <a-tab-pane :tab="$gettext('OpenAI')" key="3">
+                    <open-a-i-settings/>
+                </a-tab-pane>
+                <a-tab-pane :tab="$gettext('Git')" key="4">
+                    <git-settings/>
+                </a-tab-pane>
+            </a-tabs>
         </div>
+        <footer-tool-bar>
+            <a-button type="primary" @click="save">
+                {{ $gettext('Save') }}
+            </a-button>
+        </footer-tool-bar>
     </a-card>
-    <footer-tool-bar>
-        <a-button type="primary" @click="save">
-            {{ $gettext('Save') }}
-        </a-button>
-    </footer-tool-bar>
 </template>
 
 <style lang="less" scoped>

+ 28 - 0
frontend/src/views/preference/typedef.ts

@@ -0,0 +1,28 @@
+export interface IData {
+    server: {
+        http_port: string
+        run_mode: string
+        jwt_secret: string
+        start_cmd: string
+        http_challenge_port: string
+        github_proxy: string,
+        email: string
+    },
+    nginx_log: {
+        access_log_path: string
+        error_log_path: string
+    },
+    openai: {
+        model: string
+        base_url: string
+        proxy: string
+        token: string
+    },
+    git: {
+        url: string
+        auth_method: string
+        username: string
+        password: string
+        private_key_file_path: string
+    }
+}

+ 79 - 47
frontend/src/views/system/Upgrade.vue

@@ -5,8 +5,9 @@ import {computed, ref} from 'vue'
 import version from '@/version.json'
 import dayjs from 'dayjs'
 import {marked} from 'marked'
-import Template from '@/views/template/Template.vue'
+
 import websocket from '@/lib/websocket'
+import {message} from 'ant-design-vue'
 
 const {$gettext} = useGettext()
 
@@ -20,15 +21,23 @@ const progressStrokeColor = {
 }
 
 const modalVisible = ref(false)
-const progressPercent = ref(0)
+const progressPercent: any = ref(0)
 const progressStatus = ref('active')
 const modalClosable = ref(false)
+const get_release_error = ref(false)
+
+const progressPercentComputed = computed(() => {
+    return parseFloat(progressPercent.value.toFixed(1))
+})
 
-function get_release_list() {
+function get_latest_release() {
     loading.value = true
     upgrade.get_latest_release().then(r => {
         data.value = r
         last_check.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
+    }).catch(e => {
+        get_release_error.value = e?.message
+        message.error(e?.message ?? $gettext('Server error'))
     }).finally(() => {
         setTimeout(() => {
             loading.value = false
@@ -36,7 +45,7 @@ function get_release_list() {
     })
 }
 
-get_release_list()
+get_latest_release()
 
 const is_latest_ver = computed(() => {
     return data.value.name === `v${version.version}`
@@ -64,14 +73,20 @@ async function perform_upgrade() {
 
     const ws = websocket('/api/upgrade/perform', false)
 
-    ws.onmessage = m => {
-        const r = JSON.parse(m.data)
-        log(r.message)
+    let last = 0
 
+    ws.onmessage = async m => {
+        const r = JSON.parse(m.data)
+        if (r.message) log(r.message)
+        console.log(r.status)
         switch (r.status) {
             case 'info':
                 progressPercent.value += 10
                 break
+            case 'progress':
+                progressPercent.value += (r.progress - last) / 2
+                last = r.progress
+                break
             case 'error':
                 progressStatus.value = 'exception'
                 modalClosable.value = true
@@ -90,6 +105,10 @@ async function perform_upgrade() {
                 progressPercent.value = 100
                 modalClosable.value = true
                 log('Upgraded successfully')
+
+                setInterval(() => {
+                    location.reload()
+                }, 1000)
             })
         }, 2000)
     }
@@ -97,51 +116,58 @@ async function perform_upgrade() {
 </script>
 
 <template>
-    <a-modal
-        :title="$gettext('Core Upgrade')"
-        v-model:visible="modalVisible"
-        :mask-closable="modalClosable"
-        :footer="null" :closable="modalClosable" force-render>
-        <a-progress
-            :stroke-color="progressStrokeColor"
-            :percent="progressPercent"
-            :status="progressStatus"
-        />
-
-        <div class="core-upgrade-log-container" ref="logContainer"/>
-
-    </a-modal>
     <a-card :title="$gettext('Upgrade')">
+        <a-modal
+            :title="$gettext('Core Upgrade')"
+            v-model:visible="modalVisible"
+            :mask-closable="false"
+            :footer="null" :closable="modalClosable" force-render>
+            <a-progress
+                :stroke-color="progressStrokeColor"
+                :percent="progressPercentComputed"
+                :status="progressStatus"
+            />
+
+            <div class="core-upgrade-log-container" ref="logContainer"/>
+        </a-modal>
         <div class="upgrade-container">
             <p>{{ $gettext('You can check Nginx UI upgrade at this page.') }}</p>
             <h3>{{ $gettext('Current Version') }}: v{{ version.version }}</h3>
-            <p>{{ $gettext('OS') }}: {{ data.os }}</p>
-            <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
-            <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
-            <p>{{ $gettext('Last checked at') }}: {{ last_check }}
-                <a-button type="link" @click="get_release_list" :loading="loading">{{
-                        $gettext('Check again')
-                    }}
-                </a-button>
-            </p>
-            <a-alert type="success" v-if="is_latest_ver"
-                     :message="$gettext('You are using the latest version')"
-                     banner
-            />
-            <a-alert type="info" v-else
-                     :message="$gettext('New version released')"
-                     banner
-            />
-            <div class="control-btn">
-                <a-space>
-                    <a-button type="primary" @click="perform_upgrade"
-                              ghost v-if="is_latest_ver">{{ $gettext('Reinstall') }}
-                    </a-button>
-                    <a-button type="primary" @click="perform_upgrade"
-                              ghost v-else>{{ $gettext('Upgrade') }}
+            <template v-if="get_release_error">
+                <a-alert type="error"
+                         :title="$gettext('Get release information error')"
+                         :message="get_release_error"
+                         banner
+                />
+            </template>
+            <template v-else>
+                <p>{{ $gettext('OS') }}: {{ data.os }}</p>
+                <p>{{ $gettext('Arch') }}: {{ data.arch }}</p>
+                <p>{{ $gettext('Executable Path') }}: {{ data.ex_path }}</p>
+                <p>{{ $gettext('Last checked at') }}: {{ last_check }}
+                    <a-button type="link" @click="get_latest_release" :loading="loading">
+                        {{ $gettext('Check again') }}
                     </a-button>
-                </a-space>
-            </div>
+                </p>
+                <a-alert type="success" v-if="is_latest_ver"
+                         :message="$gettext('You are using the latest version')"
+                         banner
+                />
+                <a-alert type="info" v-else
+                         :message="$gettext('New version released')"
+                         banner
+                />
+                <div class="control-btn">
+                    <a-space>
+                        <a-button type="primary" @click="perform_upgrade"
+                                  ghost v-if="is_latest_ver">{{ $gettext('Reinstall') }}
+                        </a-button>
+                        <a-button type="primary" @click="perform_upgrade"
+                                  ghost v-else>{{ $gettext('Upgrade') }}
+                        </a-button>
+                    </a-space>
+                </div>
+            </template>
             <template v-if="data.body">
                 <h2>{{ $gettext('Release Note') }}</h2>
                 <div v-html="marked.parse(data.body)"></div>
@@ -151,6 +177,12 @@ async function perform_upgrade() {
 </template>
 
 <style lang="less">
+.dark {
+    .core-upgrade-log-container {
+        background-color: rgba(0, 0, 0, 0.84);
+    }
+}
+
 .core-upgrade-log-container {
     height: 320px;
     overflow: scroll;

+ 0 - 11
frontend/src/views/template/Template.vue

@@ -1,11 +0,0 @@
-<script setup lang="ts">
-
-</script>
-
-<template>
-
-</template>
-
-<style lang="less" scoped>
-
-</style>

+ 1 - 1
frontend/version.json

@@ -1 +1 @@
-{"version":"1.7.9","build_id":87,"total_build":157}
+{"version":"1.8.0","build_id":98,"total_build":168}

Datei-Diff unterdrückt, da er zu groß ist
+ 312 - 330
frontend/yarn.lock


+ 119 - 14
go.mod

@@ -3,12 +3,13 @@ module github.com/0xJacky/Nginx-UI
 go 1.19
 
 require (
+	github.com/BurntSushi/toml v1.2.1
 	github.com/creack/pty v1.1.18
 	github.com/dustin/go-humanize v1.0.1
 	github.com/gin-contrib/static v0.0.1
 	github.com/gin-gonic/gin v1.9.0
 	github.com/go-acme/lego/v4 v4.10.2
-	github.com/go-co-op/gocron v1.19.0
+	github.com/go-co-op/gocron v1.20.1
 	github.com/go-playground/locales v0.14.1
 	github.com/go-playground/universal-translator v0.18.1
 	github.com/go-playground/validator/v10 v10.12.0
@@ -19,61 +20,165 @@ require (
 	github.com/jpillora/overseer v1.1.6
 	github.com/lib/pq v1.10.7
 	github.com/pkg/errors v0.9.1
-	github.com/sashabaranov/go-openai v1.5.8
-	github.com/shirou/gopsutil/v3 v3.23.1
+	github.com/sashabaranov/go-openai v1.7.0
+	github.com/shirou/gopsutil/v3 v3.23.3
 	github.com/spf13/cast v1.5.0
 	github.com/tufanbarisyildirim/gonginx v0.0.0-20230325082000-26dcb15a9df4
 	github.com/unknwon/com v1.0.1
-	golang.org/x/crypto v0.7.0
+	golang.org/x/crypto v0.8.0
 	gopkg.in/ini.v1 v1.67.0
-	gorm.io/driver/sqlite v1.4.4
+	gorm.io/driver/sqlite v1.5.0
 	gorm.io/gen v0.3.21
-	gorm.io/gorm v1.24.6
+	gorm.io/gorm v1.25.0
 	gorm.io/plugin/dbresolver v1.4.1
 )
 
 require (
+	cloud.google.com/go/compute v1.19.1 // indirect
+	cloud.google.com/go/compute/metadata v0.2.3 // indirect
+	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
+	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
+	github.com/Azure/go-autorest/autorest v0.11.28 // indirect
+	github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
+	github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
+	github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
+	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
+	github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
+	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
+	github.com/Azure/go-autorest/logger v0.2.1 // indirect
+	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
+	github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
 	github.com/StackExchange/wmi v1.2.1 // indirect
+	github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
+	github.com/aliyun/alibaba-cloud-sdk-go v1.62.281 // indirect
+	github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
+	github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
+	github.com/aws/aws-sdk-go v1.44.242 // indirect
+	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/bytedance/sonic v1.8.7 // indirect
 	github.com/cenkalti/backoff/v4 v4.2.0 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/civo/civogo v0.3.29 // indirect
+	github.com/cloudflare/cloudflare-go v0.49.0 // indirect
+	github.com/cpu/goacmedns v0.1.1 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/deepmap/oapi-codegen v1.12.4 // indirect
+	github.com/dimchansky/utfbom v1.1.1 // indirect
+	github.com/dnsimple/dnsimple-go v1.2.0 // indirect
+	github.com/exoscale/egoscale v0.90.0 // indirect
+	github.com/fatih/structs v1.1.0 // indirect
+	github.com/fsnotify/fsnotify v1.6.0 // indirect
+	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/go-ole/go-ole v1.2.6 // indirect
+	github.com/go-resty/resty/v2 v2.7.0 // indirect
 	github.com/go-sql-driver/mysql v1.7.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
+	github.com/google/s2a-go v0.1.1 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
+	github.com/googleapis/gax-go/v2 v2.8.0 // indirect
+	github.com/gophercloud/gophercloud v1.3.0 // indirect
+	github.com/gophercloud/utils v0.0.0-20230330070308-5bd5e1d608f8 // indirect
+	github.com/hashicorp/errwrap v1.1.0 // indirect
+	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+	github.com/hashicorp/go-multierror v1.1.1 // indirect
+	github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
+	github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
+	github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jpillora/s3 v1.1.4 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
-	github.com/leodido/go-urn v1.2.2 // indirect
+	github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
+	github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
+	github.com/labbsr0x/goh v1.0.1 // indirect
+	github.com/leodido/go-urn v1.2.3 // indirect
+	github.com/linode/linodego v1.16.1 // indirect
+	github.com/liquidweb/go-lwApi v0.0.5 // indirect
+	github.com/liquidweb/liquidweb-cli v0.6.10 // indirect
+	github.com/liquidweb/liquidweb-go v1.6.3 // indirect
 	github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
 	github.com/mattn/go-isatty v0.0.18 // indirect
 	github.com/mattn/go-sqlite3 v1.14.16 // indirect
 	github.com/miekg/dns v1.1.53 // indirect
+	github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
+	github.com/nrdcg/auroradns v1.1.0 // indirect
+	github.com/nrdcg/desec v0.7.0 // indirect
+	github.com/nrdcg/dnspod-go v0.4.0 // indirect
+	github.com/nrdcg/freemyip v0.2.0 // indirect
+	github.com/nrdcg/goinwx v0.8.2 // indirect
+	github.com/nrdcg/namesilo v0.2.1 // indirect
+	github.com/nrdcg/porkbun v0.2.0 // indirect
+	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
+	github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
+	github.com/ovh/go-ovh v1.4.1 // indirect
+	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pelletier/go-toml/v2 v2.0.7 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
+	github.com/pquerna/otp v1.4.0 // indirect
 	github.com/robfig/cron/v3 v3.0.1 // indirect
+	github.com/sacloud/api-client-go v0.2.7 // indirect
+	github.com/sacloud/go-http v0.1.5 // indirect
+	github.com/sacloud/iaas-api-go v1.9.2 // indirect
+	github.com/sacloud/packages-go v0.0.8 // indirect
+	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.15 // indirect
+	github.com/shoenig/go-m1cpu v0.1.5 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
+	github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
+	github.com/softlayer/softlayer-go v1.1.2 // indirect
+	github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
+	github.com/stretchr/objx v0.5.0 // indirect
+	github.com/stretchr/testify v1.8.2 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.637 // indirect
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.637 // indirect
 	github.com/tklauser/go-sysconf v0.3.11 // indirect
 	github.com/tklauser/numcpus v0.6.0 // indirect
+	github.com/transip/gotransip/v6 v6.20.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.11 // indirect
+	github.com/ultradns/ultradns-go-sdk v1.4.1-20230224143201-0d8b0f6 // indirect
+	github.com/vinyldns/go-vinyldns v0.9.16 // indirect
+	github.com/vultr/govultr/v2 v2.17.2 // indirect
+	github.com/yandex-cloud/go-genproto v0.0.0-20230410092700-15216dc82345 // indirect
+	github.com/yandex-cloud/go-sdk v0.0.0-20230403093608-cc5174142a48 // indirect
 	github.com/yusufpapurcu/wmi v1.2.2 // indirect
+	go.opencensus.io v0.24.0 // indirect
+	go.uber.org/ratelimit v0.2.0 // indirect
 	golang.org/x/arch v0.3.0 // indirect
-	golang.org/x/mod v0.9.0 // indirect
-	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/mod v0.10.0 // indirect
+	golang.org/x/net v0.9.0 // indirect
+	golang.org/x/oauth2 v0.7.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect
-	golang.org/x/sys v0.6.0 // indirect
-	golang.org/x/text v0.8.0 // indirect
-	golang.org/x/tools v0.7.0 // indirect
+	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/time v0.3.0 // indirect
+	golang.org/x/tools v0.8.0 // indirect
+	google.golang.org/api v0.118.0 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
+	google.golang.org/grpc v1.54.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/fsnotify.v1 v1.4.7 // indirect
+	gopkg.in/ns1/ns1-go.v2 v2.7.5 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	gorm.io/datatypes v1.1.1 // indirect
-	gorm.io/driver/mysql v1.4.7 // indirect
+	gorm.io/datatypes v1.2.0 // indirect
+	gorm.io/driver/mysql v1.5.0 // indirect
 	gorm.io/hints v1.1.1 // indirect
 )

+ 777 - 43
go.sum

@@ -1,23 +1,159 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
+cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
+github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
+github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
+github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
+github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8=
+github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
+github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
+github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
+github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
+github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
+github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
+github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
+github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
+github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
 github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/aliyun/alibaba-cloud-sdk-go v1.62.281 h1:sN94THxWQA+nPMDZD0esg1PGy6pmkTh7SCVc1MQaKzA=
+github.com/aliyun/alibaba-cloud-sdk-go v1.62.281/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
+github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
+github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
+github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aws/aws-sdk-go v1.44.242 h1:bb6Rqd7dxh1gTUoVXLJTNC2c+zNaHpLRlNKk0kGN3fc=
+github.com/aws/aws-sdk-go v1.44.242/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
+github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
+github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
 github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
+github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
 github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
 github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/civo/civogo v0.3.29 h1:N87pFQxEpiqaDiFkeDMxvd6YLHHTmSC2/+9v5l93bO0=
+github.com/civo/civogo v0.3.29/go.mod h1:SbS06e0JPgIF27r1sLC97gjU1xWmONQeHgzF1hfLpak=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/cloudflare-go v0.49.0 h1:KqJYk/YQ5ZhmyYz1oa4kGDskfF1gVuZfqesaJ/XDLto=
+github.com/cloudflare/cloudflare-go v0.49.0/go.mod h1:h0QgcIZ3qEXwFiwfBO8sQxjVdYsLX+PfD7NFEnANaKg=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
+github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s=
+github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
+github.com/dnsimple/dnsimple-go v1.2.0 h1:ddTGyLVKly5HKb5L65AkLqFqwZlWo3WnR0BlFZlIddM=
+github.com/dnsimple/dnsimple-go v1.2.0/go.mod h1:z/cs26v/eiRvUyXsHQBLd8lWF8+cD6GbmkPH84plM4U=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/exoscale/egoscale v0.90.0 h1:DZBXVU3iHqu5Ju5lQ5jWVlPo0IpI98SUo8Aa1UQVrmo=
+github.com/exoscale/egoscale v0.90.0/go.mod h1:wyXE5zrnFynMXA0jMhwQqSe24CfUhmBk2WI5wFZcq6Y=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
-github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
@@ -27,10 +163,18 @@ github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
 github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
 github.com/go-acme/lego/v4 v4.10.2 h1:5eW3qmda5v/LP21v1Hj70edKY1jeFZQwO617tdkwp6Q=
 github.com/go-acme/lego/v4 v4.10.2/go.mod h1:EMbf0Jmqwv94nJ5WL9qWnSXIBZnvsS9gNypansHGc6U=
-github.com/go-co-op/gocron v1.19.0 h1:XlPLqNnxnKblmCRLdfcWV1UgbukQaU54QdNeR1jtgak=
-github.com/go-co-op/gocron v1.19.0/go.mod h1:UqVyvM90I1q/R1qGEX6cBORI6WArLuEgYlbncLMvzRM=
+github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
+github.com/go-co-op/gocron v1.20.1 h1:wCGabII3xf/NrrYeOzJ4voLBBtA5k7Rb99+7l/iiu+g=
+github.com/go-co-op/gocron v1.20.1/go.mod h1:UqVyvM90I1q/R1qGEX6cBORI6WArLuEgYlbncLMvzRM=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
+github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
 github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@@ -46,112 +190,416 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
 github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
+github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
+github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
+github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/s2a-go v0.1.1 h1:XJQvZvUdPzOGdf4ZMQc78wYt9XrdIZOl//n03i8P68Q=
+github.com/google/s2a-go v0.1.1/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
+github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
+github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
+github.com/gophercloud/gophercloud v1.3.0 h1:RUKyCMiZoQR3VlVR5E3K7PK1AC3/qppsWYo6dtBiqs8=
+github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
+github.com/gophercloud/utils v0.0.0-20230330070308-5bd5e1d608f8 h1:K9r5WEeAiaEgFZsuOP0OYjE4TtyFcCLG1nI08t9AP6A=
+github.com/gophercloud/utils v0.0.0-20230330070308-5bd5e1d608f8/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
+github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
-github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
-github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
+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 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
+github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
-github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
-github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
-github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
+github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
+github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
 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/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+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=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/overseer v1.1.6 h1:3ygYfNcR3FfOr22miu3vR1iQcXKMHbmULBh98rbkIyo=
 github.com/jpillora/overseer v1.1.6/go.mod h1:aPXQtxuVb9PVWRWTXpo+LdnC/YXQ0IBLNXqKMJmgk88=
 github.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc=
 github.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
+github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
 github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
+github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk=
+github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
+github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk=
+github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
-github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
+github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
+github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/linode/linodego v1.16.1 h1:5otq57M4PdHycPERRfSFZ0s1yz1ETVWGjCp3hh7+F9w=
+github.com/linode/linodego v1.16.1/go.mod h1:aESRAbpLY9R6IA1WGAWHikRI9DU9Lhesapv1MhKmPHM=
+github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
+github.com/liquidweb/go-lwApi v0.0.5 h1:CT4cdXzJXmo0bon298kS7NeSk+Gt8/UHpWBBol1NGCA=
+github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
+github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
+github.com/liquidweb/liquidweb-cli v0.6.10 h1:97sBhsvXZEC8e9npmyigGhWnLEoK1+LaxqXhvq1bsnM=
+github.com/liquidweb/liquidweb-cli v0.6.10/go.mod h1:BwZ8l6DiFabXyhLFE77jYfmlFuMa6dXeBg3jzBOW7z4=
+github.com/liquidweb/liquidweb-go v1.6.3 h1:NVHvcnX3eb3BltiIoA+gLYn15nOpkYkdizOEYGSKrk4=
+github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
 github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
 github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
 github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
 github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
 github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
+github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34=
+github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
+github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
+github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
+github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
+github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs=
+github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY=
+github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
+github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
+github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8=
+github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4=
+github.com/nrdcg/goinwx v0.8.2 h1:RmjiHlEA+lzi3toXyPSaE6hWnBQ0+G+1u7w8C6Fpp4g=
+github.com/nrdcg/goinwx v0.8.2/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
+github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
+github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
+github.com/nrdcg/porkbun v0.2.0 h1:ghaqPtIKcffba99epWFkK3VWf6TKJT9WMXMgaTqv95Y=
+github.com/nrdcg/porkbun v0.2.0/go.mod h1:i0uLMn9ItFsLsSQIAeEu1wQ9/+6EvX1eQw15hulMMRw=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
+github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
+github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
+github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM=
+github.com/ovh/go-ovh v1.4.1/go.mod h1:6bL6pPyUT7tBfI0pqOegJgRjgjuO+mOo+MyXd1EEC0M=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
 github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
+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=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
 github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
 github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
+github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
-github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
-github.com/sashabaranov/go-openai v1.5.8 h1:EfNEmc+Ue+CuRy7iSpNdxfHyiOv2vQsQ2Y0kZRA/z5w=
-github.com/sashabaranov/go-openai v1.5.8/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
-github.com/shirou/gopsutil/v3 v3.23.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4=
-github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sacloud/api-client-go v0.2.7 h1:u8e8UdvYtpLiqTsmbJ6fLXceTievQ104ZKZb7VQ5wq8=
+github.com/sacloud/api-client-go v0.2.7/go.mod h1:PwvwOLcCdGLWK7MS/yawllp8XI60kRzzA4tg14u1zQY=
+github.com/sacloud/go-http v0.1.5 h1:Ov1Vr4Olf0P+FG2okmpSaftCQnyHoCKZtbCC6RlNZUI=
+github.com/sacloud/go-http v0.1.5/go.mod h1:jlBMvkz4PuAelewTMOVzNQVuI2EyBYJGHS7nub79Yh0=
+github.com/sacloud/iaas-api-go v1.9.2 h1:j+8E3RESsCp5hMzeDbldOv2/lmDKMvUoZOz3L90/0iY=
+github.com/sacloud/iaas-api-go v1.9.2/go.mod h1:A3shH+pHq9V1ZXw15KScArLs8BstYsdyrBQkxFM5Bcs=
+github.com/sacloud/packages-go v0.0.8 h1:9l1XrukLdLPd6l/y2et9foQK2Z00ZQEGCInZMRGivAA=
+github.com/sacloud/packages-go v0.0.8/go.mod h1:btPji+wtZ+Pk7MeCy+zo61o5IziBoLdHIrdGiYq9Kb8=
+github.com/sashabaranov/go-openai v1.7.0 h1:D1dBXoZhtf/aKNu6WFf0c7Ah2NM30PZ/3Mqly6cZ7fk=
+github.com/sashabaranov/go-openai v1.7.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.15 h1:Y7xOFbD+3jaPw+VN7lkakNJ/pa+ZSQVFp1ONtJaBxns=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.15/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
+github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
+github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
+github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
+github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
+github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
+github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
 github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
+github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
+github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/smartystreets/gunit v1.1.3 h1:32x+htJCu3aMswhPw3teoJ+PnWPONqdNgaGs6Qt8ZaU=
 github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
+github.com/softlayer/softlayer-go v1.1.2 h1:rUSSGCyaxymvTOsaFjwr+cGxA8muw3xg2LSrIMNcN/c=
+github.com/softlayer/softlayer-go v1.1.2/go.mod h1:hvAbzGH4LRXA6yXY8BNx99yoqZ7urfDdtl9mvBf0G+g=
+github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
+github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -159,91 +607,377 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.637 h1:qFqiFFxUQUixUn5op2w8CnBCWC7gAsvyx3/m8LsHx60=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.637/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.637 h1:9r85LEYF4CcKDbQQhJ5b3hYh5vj1WNvjsHrWHAV3c60=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.637/go.mod h1:5z3RG36i3UQvMr3aHVjPfrEzLdmk+sTiLgip3aFvKBo=
 github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
 github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
 github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
 github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/transip/gotransip/v6 v6.20.0 h1:AuvwyOZ51f2brzMbTqlRy/wmaM3kF7Vx5Wds8xcDflY=
+github.com/transip/gotransip/v6 v6.20.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c=
 github.com/tufanbarisyildirim/gonginx v0.0.0-20230325082000-26dcb15a9df4 h1:nay4Y7wTRlBikD1igtx55ALUs5rUKd/DGSpXSaYcf0g=
 github.com/tufanbarisyildirim/gonginx v0.0.0-20230325082000-26dcb15a9df4/go.mod h1:4fTjBxMoWGOIVnGFSTS9GAZ0yMyiGzTdATQS0krQv18=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
 github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/ultradns/ultradns-go-sdk v1.4.1-20230224143201-0d8b0f6 h1:QLAl5WjtTCho+BEY7jfYjF6I9eK2jSfvMxjeRjP34PQ=
+github.com/ultradns/ultradns-go-sdk v1.4.1-20230224143201-0d8b0f6/go.mod h1:F4UyVEmq4/m5lAmx+GccrxyRCXmnBjzUL09JLTQFp94=
 github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
 github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
+github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
+github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
+github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
+github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/yandex-cloud/go-genproto v0.0.0-20230403093326-123923969dc6/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
+github.com/yandex-cloud/go-genproto v0.0.0-20230410092700-15216dc82345 h1:GpSlltaiDKRYF1JgqQO+cSLMNuADuSfKqRNaxgmXV+Q=
+github.com/yandex-cloud/go-genproto v0.0.0-20230410092700-15216dc82345/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
+github.com/yandex-cloud/go-sdk v0.0.0-20230403093608-cc5174142a48 h1:C3yjOqP3gGxwiW3bXDAGI8tS+eKjxySJ9Ix7lpdtKZw=
+github.com/yandex-cloud/go-sdk v0.0.0-20230403093608-cc5174142a48/go.mod h1:+bvtdW+7bn1Yc7xUCbITnEalQ+hwkAAbUFHpeIY2wUQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
 github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
+go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
 golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
-golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
-golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
-golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
+golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.118.0 h1:FNfHq9Z2GKULxu7cEhCaB0wWQHg43UpomrrN+24ZRdE=
+google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
+google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
+gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ns1/ns1-go.v2 v2.7.5 h1:tE4SLOAFx2YXawh6MPv57lmlaBFUTyxSYOWKOGDgkM4=
+gopkg.in/ns1/ns1-go.v2 v2.7.5/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/datatypes v1.1.1 h1:XAjO7NNfUKVUvnS3+BkqMrPXxCAcxDlpOYbjnizxNCw=
-gorm.io/datatypes v1.1.1/go.mod h1:u8GEgFjJ+GpsGfgHmBUcQqHm/937t3sj/SO9dvbndTg=
+gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
+gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
 gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
-gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
-gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
-gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
+gorm.io/driver/mysql v1.5.0 h1:6hSAT5QcyIaty0jfnff0z0CLDjyRgZ8mlMHLqSt7uXM=
+gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo=
+gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
 gorm.io/driver/sqlite v1.4.2/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
-gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
-gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
+gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
+gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
 gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
 gorm.io/gen v0.3.21 h1:t8329wT4tW1ZZWOm7vn4LV6OIrz8a5zCg+p78ezt+rA=
 gorm.io/gen v0.3.21/go.mod h1:aWgvoKdG9f8Des4TegSa0N5a+gwhGsFo0JJMaLwokvk=
 gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
 gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
-gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
-gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
+gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
 gorm.io/hints v1.1.1 h1:NPampLxQujY+277452rt4yqtg6JmzNZ1jA2olk0eFXw=
 gorm.io/hints v1.1.1/go.mod h1:zdwzfFqvBWGbpuKiAhLFOSGSpeD3/VsRgkXR9Y7Z3cs=
 gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk=
 gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 43 - 0
lego-config.sh

@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Download go-acme/lego repository
+download_and_extract() {
+    local repo_url="https://github.com/go-acme/lego/archive/refs/heads/master.zip"
+    local target_dir="$1"
+
+    # Check if wget and unzip are installed
+    if ! command -v wget >/dev/null || ! command -v unzip >/dev/null; then
+        echo "Please ensure wget and unzip are installed."
+        exit 1
+    fi
+
+    # Download and extract the source code
+    wget -q -O lego-master.zip "$repo_url"
+    unzip -q lego-master.zip -d "$target_dir"
+    rm lego-master.zip
+}
+
+# Copy .toml files from providers to the specified directory
+copy_toml_files() {
+    local source_dir="$1/lego-master/providers"
+    local target_dir="server/pkg/cert/config"
+
+    # Remove the lego-master folder
+    if [ ! -d "$target_dir" ]; then
+        mkdir -p "$target_dir"
+    fi
+
+    # Copy .toml files
+    find "$source_dir" -type f -name "*.toml" -exec cp {} "$target_dir" \;
+}
+
+# Remove the lego-master folder
+remove_lego_master_folder() {
+  local folder="$1/lego-master"
+  rm -rf "$folder"
+}
+
+destination="./tmp"
+download_and_extract "$destination"
+copy_toml_files "$destination"
+remove_lego_master_folder "$destination"

+ 25 - 81
resources/development/nginx/sites-available/homework.jackyu.cn

@@ -1,84 +1,28 @@
 server {
-	listen	80;
-	listen	[::]:80;
-
-	server_name	homework.jackyu.cn;
-  	# rewrite ^(.*)$  https://$host$1 permanent;
-  	return 307 https://$server_name$request_uri;
+    listen 80;
+    listen [::]:80;
+    server_name homework.jackyu.cn;
+    # rewrite ^(.*)$  https://$host$1 permanent;
+    return 307 https://$server_name$request_uri;
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
 }
-
 server {
-	listen	443 ssl http2;
-	listen	[::]:443 ssl http2;
-
-	server_name	homework.jackyu.cn;
-
-	ssl_certificate	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_server_cert.pem;
-  	ssl_certificate_key	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_key.pem;
-
-	root	/var/www/homework/frontend;
-
-	# Add index.php to the list if you are using PHP
-	index	index.html;
-
-	location / {
-		# First attempt to serve request as file, then
-		# as directory, then fall back to displaying a 404.
-                index index.html;
-		try_files $uri $uri/ /index.html;
-	}
-    
-    location /student {
-      index manage.html;
-      try_files $uri $uri/ /student.html;
-	}
-    
-    location /teacher {
-      index manage.html;
-      try_files $uri $uri/ /teacher.html;
-	}
-    
-    location /admin {
-      index admin.html;
-      try_files $uri $uri/ /admin.html;
-	}
-            
-    location ^~/upload/ {
-		alias /var/www/homework/api/upload/;
-	}
-	include			error_json;
-	location /api/	 {
-    	proxy_http_version 1.1;
-        proxy_set_header Upgrade $http_upgrade;
-        proxy_set_header Connection upgrade;
-    
-		proxy_pass         http://127.0.0.1:9008/;
-		proxy_redirect     off;
-
-		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;
-        client_max_body_size 1000m;
-	}
-    
-    location /zigbee-pi	 {
-    	alias /var/www/zigbee-pi/frontend/;
-    	index index.html;
-	}
-    
-    location /zigbee-pi/api/	 {
-    	proxy_http_version 1.1;
-        proxy_set_header Upgrade $http_upgrade;
-        proxy_set_header Connection upgrade;
-    
-		proxy_pass         http://127.0.0.1:9200/;
-		proxy_redirect     off;
-
-		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;
-        client_max_body_size 1000m;
-	}
-}
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+    ssl_certificate /etc/nginx/ssl/home.jackyu.cn/fullchain.cer;
+    ssl_certificate_key /etc/nginx/ssl/home.jackyu.cn/private.key;
+    # rewrite ^(.*)$  https://$host$1 permanent;
+    return 307 https://$server_name$request_uri;
+    server_name home.jackyu.cn;
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
+}

+ 13 - 0
resources/development/nginx/sites-available/ojbk.me

@@ -8,4 +8,17 @@ server {
         proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
         proxy_pass http://127.0.0.1:5002;
     }
+}
+server {
+    listen 443 ssl http2;
+    listen [::]:443 ssl http2;
+    server_name ojbk.me;
+    ssl_certificate /etc/nginx/ssl/ojbk.me/fullchain.cer;
+    ssl_certificate_key /etc/nginx/ssl/ojbk.me/private.key;
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
 }

+ 30 - 23
resources/development/nginx/sites-available/qi.jackyu.cn

@@ -1,26 +1,33 @@
 server {
-	listen	80;
-	listen	[::]:80;
-	server_name	qi.jackyu.cn;
-	rewrite ^(.*)$  https://$host$1 permanent;
-
+    listen 80;
+    listen [::]:80;
+    server_name qi.jackyu.cn;
+    rewrite ^(.*)$ https://$host$1 permanent;
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
 }
-
 server {
-	server_name	qi.jackyu.cn;
-	ssl_certificate	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_server_cert.pem;
-	ssl_certificate_key	/etc/nginx/ssl/jackyu.cn/alpha/jackyu.cn_key.pem;
-	listen	443 ssl;
-	listen	[::]:443 ssl;
-
-	location / {
-		proxy_pass         http://127.0.0.1:5001/;
-		proxy_redirect     off;
-		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;
-	}
-
-}
-
+    server_name qi.jackyu.cn;
+    ssl_certificate /etc/nginx/ssl/qi.jackyu.cn/fullchain.cer;
+    ssl_certificate_key /etc/nginx/ssl/qi.jackyu.cn/private.key;
+    listen 443 ssl;
+    listen [::]:443 ssl;
+    location / {
+        proxy_pass http://127.0.0.1:5001/;
+        proxy_redirect off;
+        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;
+    }
+    location /.well-known/acme-challenge {
+        proxy_set_header Host $host;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+        proxy_pass http://127.0.0.1:5002;
+    }
+}

+ 5 - 0
resources/development/nginx/sites-available/test

@@ -0,0 +1,5 @@
+server {
+    listen 80;
+    listen [::]:80;
+    server_name test;
+}

+ 1 - 0
resources/development/nginx/sites-enabled/homework.jackyu.cn

@@ -0,0 +1 @@
+/etc/nginx/sites-available/homework.jackyu.cn

+ 1 - 0
resources/development/nginx/sites-enabled/qi.jackyu.cn

@@ -0,0 +1 @@
+/etc/nginx/sites-available/qi.jackyu.cn

+ 1 - 0
resources/development/nginx/sites-enabled/test

@@ -0,0 +1 @@
+/etc/nginx/sites-available/test

+ 24 - 24
resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/fullchain.cer

@@ -1,34 +1,34 @@
 -----BEGIN CERTIFICATE-----
-MIIFkzCCBHugAwIBAgITAP9BpIHmYRZFpLiNFNwEvnS6lTANBgkqhkiG9w0BAQsF
+MIIFlDCCBHygAwIBAgITAP8P9apD6m2icO0oW6VolSZRGTANBgkqhkiG9w0BAQsF
 ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
-QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzAyMTUwMzM5NDhaFw0yMzA1MTYw
-MzM5NDdaMCAxHjAcBgNVBAMTFWFtc3RvdXJzaGlwLmphY2t5dS5jbjCCASIwDQYJ
-KoZIhvcNAQEBBQADggEPADCCAQoCggEBAM31HTeIJ6CcbK1Y1MKb1Pylt2/mXvt8
-M4quWNJjJWWelaRCpe/BD/mBCM9RsQUnf5F8m5OW6QAesoM/QiewTAjf06o5WHfX
-SinH9yMKqXMny4nUBI7U5jJEXbiV82HBsuieU5YldeBCMilefjIG3UNfwNqcA40E
-Miq9xZAGQx9lmabno0iyQlltFyYb1l+4CX0SBm5ygBOyyhb4tReZEB1Sn54n5gtZ
-bK4ZvYgPvKg4wHT6f9A+D4GqE1kZPuGqAKkvAmt8whlEUfXL2zlfNOFfuReQNujs
-FrwnJgSVHa0UPhQEB3zowMjmBSqGZnJap9zXS0W8Eu+D5EWFKUVuRy0CAwEAAaOC
-AqEwggKdMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
-BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxKOLPUnFetKJF7xySU+hY/32
-3FwwKwYDVR0jBCQwIoAgYjWGqKXC1CgUyRtbS1bZxpMqaNdKnoY33nyaZdVEQ/Iw
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzA0MTIxNDUxNDBaFw0yMzA3MTEx
+NDUxMzlaMCAxHjAcBgNVBAMTFWFtc3RvdXJzaGlwLmphY2t5dS5jbjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAN1iuK3CxglZ8hbu82KkZTykkDWVcidg
+b+7Z+9Qae6Bj/PEsuk7WfDewImQR9b7i6V5/SERZKLEo7siK994oQ4zWQuFz/y/i
+7L+zz8oVIiL+STqPikwgqk1WwUS/+tVSQY9MTtu/p95HvmCd72w+DXLwNP0fOczS
+zxRip9x2kE1WQx6OcEcfqTL+lJxuW5xeWRyPoWLerkGCYoxUOk0psnLDRxlJALmt
+OpA35iGaIri1ygxXGmhnwlqVZujxTASR+S5Pq/YNu27UvpgDy+8yLPI6AQq/XpK4
+3hNm7cCmZPok+OEj96T0YmSr8egQx1Z+7WHt/aVuPU6mE4w0CPUxEvMCAwEAAaOC
+AqIwggKeMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
+BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUvHIVg1nXlpKrVWDlWsrGWnHt
+HiMwKwYDVR0jBCQwIoAgYjWGqKXC1CgUyRtbS1bZxpMqaNdKnoY33nyaZdVEQ/Iw
 cQYIKwYBBQUHAQEEZTBjMCIGCCsGAQUFBzABhhZodHRwOi8vMTI3LjAuMC4xOjQw
 MDIvMD0GCCsGAQUFBzAChjFodHRwOi8vMTI3LjAuMC4xOjQwMDEvYWlhL2lzc3Vl
 ci82NjA1NDQwNDk4MzY5NzQxMC0GA1UdEQQmMCSCFWFtc3RvdXJzaGlwLmphY2t5
 dS5jboILdC5qYWNreXUuY24wJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL2V4YW1w
 bGUuY29tL2NybDBABgNVHSAEOTA3MAgGBmeBDAECATArBgMqAwQwJDAiBggrBgEF
-BQcCARYWaHR0cDovL2V4YW1wbGUuY29tL2NwczCCAQMGCisGAQQB1nkCBAIEgfQE
-gfEA7wB2AFLU6MpxhMjJJFwzEHovC5eeKDMFhyNCIngxCrNdv03eAAABhlNfHncA
-AAQDAEcwRQIhAL84cecGwG4bYGHcCxGVwaLPgISazBGaIcOP/11sY78gAiAA+1/3
-XSk0hPTv5zWYwqJIcI0ajGOeiIfaLwpFxnW+DwB1ADiYjJTQNZjDky3f6SO6uvJ6
-Qg65bEHhWqgMGrD8BL0DAAABhlNfHncAAAQDAEYwRAIgS3rH0/r0zBshQN9LwmWv
-JadxbPEJtQuWjhyH/5gln4cCICZlS/B2qYkOZJzQQkjRgnfHrmUc1vRHFNBEGuRR
-HGX2MA0GCSqGSIb3DQEBCwUAA4IBAQBLP7i7PPn3mUtmsYoguW07lQa8abjsHirs
-r5TgfOpWLVFQ8ASWuIu/OTLdKrbfTXseZibLKlPU+Zoz+HF8V3lnCmgXbnlQo/ex
-+uEDPkLYyXuWe96nssiVgtUAmkSWQOEwhIz0xtWNgskgRVt2c4CihYbqBB3uXLL1
-TubIFHAizRKcQ/JUDfSnieN4R5tX1MIw/TnUmNxj3KMtF1OHsqGo9Pt2z8oRWGf5
-kx2HtgFyIigkTIlUB5TFmlv5HLAtE4H3cc2NSYZ397WXhil2mTqPTBLRXQJwzQ7C
-5tenpmzazqPOOu23QJaA94a7UeQtowcEDkSMoCe/G31leeEUbRjt
+BQcCARYWaHR0cDovL2V4YW1wbGUuY29tL2NwczCCAQQGCisGAQQB1nkCBAIEgfUE
+gfIA8AB1AB0a00HGvIsteEG68Ov4rYFc1Vk3GhDRd7VpyT++N6avAAABh3YqXRsA
+AAQDAEYwRAIgW+VQTyXsRKQf5JIpZxrfHen7vbulm9Gwp4+d//WV68gCID1nEPCy
+uQnxGqQQAX8uyJVBUuIu9fbNsk8rpa0aspYhAHcAOqk/Tv0cUSnEJ4bZa0eprm3I
+QQ4XgNcv20/bXixlxnQAAAGHdipdGwAABAMASDBGAiEAuSjdempyKM4JBhohDz/1
+HqiAceFWa6jlgR94tS1eNWUCIQCsRkkxJQSD98bN9TmdTqQbCnuXclLAu9wgRvJc
+j0ilODANBgkqhkiG9w0BAQsFAAOCAQEAH+OtS4DEYY4QEtWEPabpzFZGXmTrhJf4
+f/2VoNm99ViN8i66IkXDybuSRyopWkliLGgbGk1749KAkFW/GRprdmRmMk47nW0z
+3MLeqPysBHIwx63SW+N+4YuCbh7DZcL8zs+6M3RHmMLGF6PS3Htx2oW4EFleHUt/
+dpAC/ltnDnsDGs/hphgZAkgcQDvgC9od+qEvZ2pkINAgKwtQ0sbzD31J/HCHji9E
+F5Wwdlnj+XCfsWyhPDaCoj3mEbahaRMB4mmskA/3iDiOutterb5QQPQ03kriX1yX
+6+zwhyWq0gHO1I7u0RqxaIFrSdnxTA4UFHq4TlIunHOsR7t/jhCeJA==
 -----END CERTIFICATE-----
 
 -----BEGIN CERTIFICATE-----

+ 25 - 25
resources/development/nginx/ssl/amstourship.jackyu.cn_t.jackyu.cn/private.key

@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAzfUdN4gnoJxsrVjUwpvU/KW3b+Ze+3wziq5Y0mMlZZ6VpEKl
-78EP+YEIz1GxBSd/kXybk5bpAB6ygz9CJ7BMCN/TqjlYd9dKKcf3IwqpcyfLidQE
-jtTmMkRduJXzYcGy6J5TliV14EIyKV5+MgbdQ1/A2pwDjQQyKr3FkAZDH2WZpuej
-SLJCWW0XJhvWX7gJfRIGbnKAE7LKFvi1F5kQHVKfnifmC1lsrhm9iA+8qDjAdPp/
-0D4PgaoTWRk+4aoAqS8Ca3zCGURR9cvbOV804V+5F5A26OwWvCcmBJUdrRQ+FAQH
-fOjAyOYFKoZmclqn3NdLRbwS74PkRYUpRW5HLQIDAQABAoIBAGCuuRlxfBDEfave
-cHouxwwXCwanoVzzEAsBD0csLckHaf3jH5xuB/67WRRhp/Tgdt0oHqxpAlYBExHT
-p02UUA02bVmSc/pGAVWdvmEfxy38t2qrMbyPKsTcHRbipY71a/QRJvHsAerViFCt
-QBZh7IqNL64v4ObY2mgAD/ctSWM56gqkFR70X7HpMKQ9Z8Q9iT5IKeFdVtfOcdSq
-1HuAiz4p8v8IOzLHTKruY5OTYM9uyr02FtPoWsoZSyzSjaC8BpO+wnFlVsDXDBNv
-/Kgqyetj6Iqo6cInj8dExpdnCZXqO5j4FXgHABLgjgiIJV+L3F0KY1PbO9NejMEh
-TvKgZiECgYEA6aIGaUtdoPXWY1o4sEjHy8rwQnBs25JPx+4+HOC5wHrsn03yTmXC
-cjKapIySQhasekQZGQk++H4cRyToqR0izIfkqEmOLfec2kKy7FgcSXzs2PKH66om
-4mvYSWVQj0r3SwYyG6UHJJCeY8i+RCLMkEXSkNPvhEQXg7zCI2oMpSUCgYEA4azO
-yfHvUKnJbGW+r3ujbszn7IlsobVTL0Rf4nHLSuKrW0v/OFg9lsjsm6IJyWV1H5KO
-NNNVzpubAESvPujAwiYfSeFLjK67hbEVSaVLp+5ubsGKf+0BDPKh3+yVJ6abHmFo
-lqzqUlZhZteMp8BN+n3fwR/W5RJFAju9o3F972kCgYBq3IlCMA6rSMa+us2jFCcO
-t8wdF38zD0EemYIfg0pzF8aTNvvVkAXYZf0FtqZPRD+vzOYN5YS/9C7K+77PW1xH
-YQDdWIeHzvIXgtqD7+lAU7uhn4075Z/TgLB1IbovUIK79iGFM36I4v0PdwpP7/rR
-Ip6lT8sGHH8E6pmByUfvYQKBgQDhG1r6HZY1w+bjdWoL6SxQ7Zu6Wio9830SfQWX
-/yJlhEyhOOFP9tUYfztk0vEoL0fxQmMPVm8VNCoczmZwPgNoplY3f7+4iOMMrGMr
-nvIkhLUrTWs1x9dwbuYBUyBE+O9qEogdJEZn8zodN41aF3yxDLYREg1tWhfz7ltv
-mVmhYQKBgBFtHgvmlw/fA5P4dfn/xnOFYwz2v+f5dxcD9DXP9Hu53m7mVPTEUC1t
-2SiBaaUMqTiOflW8Aq9qHHvOPNoO1GrGt4oUbhrAzKwxmkiCUyspYTRBOWHbLjW0
-07Mu40Y8I0WqEVenAGIsHfrsXzvdzat0bijsAt+P1LMliRjK7qhh
+MIIEowIBAAKCAQEA3WK4rcLGCVnyFu7zYqRlPKSQNZVyJ2Bv7tn71Bp7oGP88Sy6
+TtZ8N7AiZBH1vuLpXn9IRFkosSjuyIr33ihDjNZC4XP/L+Lsv7PPyhUiIv5JOo+K
+TCCqTVbBRL/61VJBj0xO27+n3ke+YJ3vbD4NcvA0/R85zNLPFGKn3HaQTVZDHo5w
+Rx+pMv6UnG5bnF5ZHI+hYt6uQYJijFQ6TSmycsNHGUkAua06kDfmIZoiuLXKDFca
+aGfCWpVm6PFMBJH5Lk+r9g27btS+mAPL7zIs8joBCr9ekrjeE2btwKZk+iT44SP3
+pPRiZKvx6BDHVn7tYe39pW49TqYTjDQI9TES8wIDAQABAoIBACngm8iauHVaY1X9
+O2LkcBYJlxHbxC04q9PFvkJABDSx1U4VxORW5EZwdgDx+Md0CusKLrRnHTBt6sVa
+rEqE2UpR2XLmS1ZWmawlql+yobN24IlIT8PYxf49M+/6VMt3dNlTePNOjMWaUqPa
+HEkgb9DXKV7cQkXBdtSHWPN2JhPKrHQdOalmZrxkbYyVzn6ENL5POprUvHW3SmYS
+6ioW79nvnvoBQd7DaQ0uW/PlFFJLKtPrx81xssCTXnK/MTsRFHihpiV0tttOn+vG
+AT9Je6UIZADR7VLxwT3urE+T8reXCJWssQCjf/ikdNaTL+ercepqib3OyuQ67+KL
+nDvizWECgYEA78j1E1Y3vtf3UiA+jVr1G/2a5GN9I/hAAkzLWRvdHOOuHRkdGpdS
+dX7/XLLo0yONDIpZgt0E13Pvx8PB/GlhDkolgrgl5JW6VNsKBzG7ChJoyx4S0gTN
+u46NQmYwptuQJ93T5ouCf3lRFx/KgnnRtb5DAESeor9zj4IWuOLWw6MCgYEA7Fs+
+QlnztDHJX1VGRXucAxqeu21LdYtIcdrw+ODSynJT8T25in0GC8gm8K2NELyMrzyX
+79xN1GhD93FkLQMIyH57F/njCr3QexN7DwiIxdr2tejqyR+6RdEHy/m1ZqephmOp
+IzkxxpWbSzjGqWkccpkwXamnV5cms5Y8enoT6HECgYAthb9RTnsC1klz4/88ItqJ
+19nCJkkpvQWZ6i5o2mteArOZfp0R4UycVeHxHV4qR4ONr+5Sc9dpd+87w6ReYTs1
+1UnNyVwYS3y2g2ch+oUB4oDWnvz/tj3Ar82Nn4z5AJIbn3eNO0spHEUS2Mw/fRUd
+JePedyiOyT4NLqtVqmsPdQKBgQC9rZPPR8TuDS5f1V1pkYNockqEe3edQ0PGK0BL
+WpGPaiS3ayOlYiQh/GRTrdORZN86GlSMZ64GEg7JWzRbcx3vWKGMYju0eRQAUWfm
+bqDkt45Z+ivBcDbapJ+vMBxF37OKaBxqahHqHlHcEhpMBlCQHyPTZr0m6+RhJuYu
+HB4x4QKBgB8Z/akRPJm501yhVl+up/Vb5zubtc+YeQdbwrNlIYgFlF7HcAIw7hio
+7JaAf+R+N2ucqEb0XHI0nIAgOvuWxb6VGrT3Y4a7ANRjl9Bm8BZ6TN0Xy6kAeED/
+XnLNl12+5vb1x5wgZw0BB8uIZAds1dRqhZWgsu6A7TTxtrVKXBj7
 -----END RSA PRIVATE KEY-----

+ 93 - 0
resources/development/nginx/ssl/home.jackyu.cn/fullchain.cer

@@ -0,0 +1,93 @@
+-----BEGIN CERTIFICATE-----
+MIIFIjCCBAqgAwIBAgISA9cwhRyeHm9sWP3BzA7VdcKvMA0GCSqGSIb3DQEBCwUA
+MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
+EwJSMzAeFw0yMzA0MTMwNTQzMTJaFw0yMzA3MTIwNTQzMTFaMBkxFzAVBgNVBAMT
+DmhvbWUuamFja3l1LmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+wmvGGH/EeYiuR/XlorpccaPBS4mMRQHgwoudzv2h+V5N8ygnxfKPlmCG5xsgNrVu
+S9/CPg2Bj4jqZSkOH1MCCdPm7oWbkJZ125GW6vnRMvYFOIljkl5b0r6h9rYOFFvY
+Kg93RUgCG2qFHJxlTyzeBhcTfJKJdajB2BKxpgELqZfucxY0WB5VIIle3wlV1U7e
+QCjIk0FdvrJ3QxjRahtrvMstnuSgaaQGBEdGbQNM1kd+Iew3HW7sE9E07g38gkEN
+0tgD3057aW75heEuU0MAD+T9MvrHUiq08Hedr9erryje0MDkuBpYYqKIMzABX1n6
+tTq4N3AqA2H7NwnfIEMA8QIDAQABo4ICSTCCAkUwDgYDVR0PAQH/BAQDAgWgMB0G
+A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1Ud
+DgQWBBTR4VfaZbqf3ESlwy3wIkJvg5gDVzAfBgNVHSMEGDAWgBQULrMXt1hWy65Q
+CUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9y
+My5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iub3Jn
+LzAZBgNVHREEEjAQgg5ob21lLmphY2t5dS5jbjBMBgNVHSAERTBDMAgGBmeBDAEC
+ATA3BgsrBgEEAYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNl
+bmNyeXB0Lm9yZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB3AHoyjFTYty22IOo4
+4FIe6YQWcDIThU070ivBOlejUutSAAABh3lalJMAAAQDAEgwRgIhAMrMa1TWmJ6v
+u/zRN9YqWZGJcU+xSQyHDKgkM9V0s46iAiEA2qr2L39vvWJBu1Dnx+/0idNFj97a
+pjBXEZq35QTUbKcAdQC3Pvsk35xNunXyOcW6WPRsXfxCz3qfNcSeHQmBJe20mQAA
+AYd5WpTMAAAEAwBGMEQCIHKy+XtWSeGFiCj0M/Z8JvIsMAo0CETk9hmFrnfE6E1n
+AiBiWUOdFD3I77nl5RvY94bJDvMSl9HADmTj7NHhom5YYDANBgkqhkiG9w0BAQsF
+AAOCAQEAYLelbVG+VNZUOWQ6fWWLUFwIvw05ey3vVYVqazjsX0s6eMf/8aLREGV9
+lmUbftDooUqLW3wzRjk4aEnO8A4pdxQu7oPbdhLxjuCRreNMroH1LSgO1YWQ7BXP
+3ugihvxhHPAPbe0pfH/rqR3KOe/8tGXWYVeFoBmGHptaQVZt0d2ZceBHq6nMnetw
+K74et3/hAH0Ay3rWUy3uknfEDLymkKS+rMniGA8pybtcTyX0MAs6FbmiSgFvVct0
+0tNIEsjiCsl9y1Kp89r9t08MmJ3ZTxy3p+ce0gkbq8sIJRgo3o3cK9bUxag8UnhZ
+Tq/8hVY0nOsOZ3/SDXLW6rwHQAd5Uw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
+WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
+RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
+R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
+sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
+NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
+Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
+/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
+FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
+AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
+Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
+gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
+PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
+ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
+CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
+lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
+avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
+yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
+yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
+hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
+HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
+MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
+nLRbwHOoq7hHwg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
+ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
+wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
+LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
+4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
+bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
+sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
+Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
+FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
+SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
+PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
+TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
+c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
+ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
+b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
+U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
+MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
+5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
+9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
+WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
+he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
+Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/home.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAwmvGGH/EeYiuR/XlorpccaPBS4mMRQHgwoudzv2h+V5N8ygn
+xfKPlmCG5xsgNrVuS9/CPg2Bj4jqZSkOH1MCCdPm7oWbkJZ125GW6vnRMvYFOIlj
+kl5b0r6h9rYOFFvYKg93RUgCG2qFHJxlTyzeBhcTfJKJdajB2BKxpgELqZfucxY0
+WB5VIIle3wlV1U7eQCjIk0FdvrJ3QxjRahtrvMstnuSgaaQGBEdGbQNM1kd+Iew3
+HW7sE9E07g38gkEN0tgD3057aW75heEuU0MAD+T9MvrHUiq08Hedr9erryje0MDk
+uBpYYqKIMzABX1n6tTq4N3AqA2H7NwnfIEMA8QIDAQABAoIBAQCEDTGKeFWZepVt
+OP4UzeF2KhRi2vTT9heT68Ju0eSO/FeTfUWudDbEm6FlHQ5/OjHFBSDohsHmHMef
+mAgIjJfI1w12Gndz+E3qqXNI/A70PxeCtAZWZxKVDHfzmunrOAqVXtXSz7rmpi3t
+JejFoyLWHhxVMy58JPgsa14P84vZTrHF3HLbL5iXA9qdVNy9mYcYAqbhKD8PTLc7
+Cnk7nzVBRMQwSJZCuEoNDKvhU1nJpaIh/11dzebePUsXUf6VdQz+urJ2EwonIukr
+JHJKEdP0E4ZngfI4o4iFQY8PnQFkk/mBj4ByII2vgRJPVh0I2HSil1tcnQoEb966
+rw+1BBd1AoGBANJALAfjMKjQkXk/+ChDH8KSsRlY3fiOlqwSOnozQq/vGPKbaGaZ
+D3LHNyFZ3dgGXHmkHhErPNHOqv3hz19bLuzKDt7mMKXb+LvrMp7S0BUK+UFK+4P0
+vN3v8Vw5LaqgOuJ7ybkjZGNPC014G3FvrIw62RRQoC8Rz04cvkxmRiEDAoGBAOy5
+0s19jum8ll2eGpqL6HIdLczkvGj1IFQL0NF1rlBhlKNp37ec+mOCCbcow3NeYHfm
+xaJsK3wl0+Q3b2DVCe/UNHWa8dY1Cy0F/pFZxCEtm3ln3xfHj9KF6+HQxbr0Vw10
+p3iXNO6MdTZqQ1WEOe9DR6ayY3nPkTJ6zlzyf+H7AoGADqEIb0QPjq90b8tTqPmC
+rDcou2rDhxfkw/RAvV/zs+ofAkJt8TWVLZdO7rMiDHXk2VHiBa/Me4y1uRSNKUVe
+7nrgrgG2QNQdanXi/8oLUGuDDFf7SCMvQQIA+TnBQ64Cat/SGV+tDHvjfXBt+Gac
+yuUNVayGeL/0fKrjUs2K86ECgYBmLrwIyXbj++yoEnQHW/cFMwUvGVkesRi4TrVf
+hL2uosBnXW8dMdx/EYWiEy1y4j0f5HRQ7QJEP4vcSfWAxdTZOi8+yJg1T4Y4dArz
+sNzROX+QAz8wY4r4Y6hzPAvt7ESbYUxc6GAHzIdX6ryP8FiVp+QI153K2cciFBJ5
+2o9K+wKBgGFY5ipGubbXldPbJ5uKCZD6W4Y9L9GJZRmZtRK71+NdYiAHxhKehSlj
+cmOyh0LKhZkdyKWKFd+NBAYFhABOswWqppkzk/OOI+Bov8jaBuN+7/Va2RrrLXKz
+7kds4TarXHsrQ2gRpC9WNKpy89RLPUSa4TIcylerWaJcW9ZJyNRa
+-----END RSA PRIVATE KEY-----

+ 64 - 0
resources/development/nginx/ssl/homework.jackyu.cn/fullchain.cer

@@ -0,0 +1,64 @@
+-----BEGIN CERTIFICATE-----
+MIIFgDCCBGigAwIBAgITAP+S8fcCshdBkwAQnMp96YTVgjANBgkqhkiG9w0BAQsF
+ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzA0MTEwMjM0NDdaFw0yMzA3MTAw
+MjM0NDZaMB0xGzAZBgNVBAMTEmhvbWV3b3JrLmphY2t5dS5jbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANBS8mLu4uB9MtEXBhXe0kqYJ8KOgjVA4YxI
+4yFaF7OCj+BgQaKPPIinBv30nb9om2aTH9gZogG4pUoLTnzQenkWwcbrneZxzJjA
+6zvjRWzc+5dC/Pnc9aiJTIxTOcb+IEw6Tk6siId9LYg6bzWwivnOpzotSq1Alcaq
+64jhE2MRlyabIPzEU90fIbaH6T9GyAw3pJNhTLaWQFA7OmH0pxGD/jkIYVjqBQ9B
+3l36Boje6ZkqzBjFFLZMIrB8pD17siPj0evi9wYwHFEe1EzYVjbxEd+RC/fu6uwv
+zVu1XjDzI1cWwH9CjsfCH+GRY2IXZ+qsmvxDtafjO2jWX+RmKTkCAwEAAaOCApEw
+ggKNMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
+AwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUKNg5JLU2qrEGibB6e7Izs9KkGhww
+KwYDVR0jBCQwIoAgYjWGqKXC1CgUyRtbS1bZxpMqaNdKnoY33nyaZdVEQ/IwcQYI
+KwYBBQUHAQEEZTBjMCIGCCsGAQUFBzABhhZodHRwOi8vMTI3LjAuMC4xOjQwMDIv
+MD0GCCsGAQUFBzAChjFodHRwOi8vMTI3LjAuMC4xOjQwMDEvYWlhL2lzc3Vlci82
+NjA1NDQwNDk4MzY5NzQxMB0GA1UdEQQWMBSCEmhvbWV3b3JrLmphY2t5dS5jbjAn
+BgNVHR8EIDAeMBygGqAYhhZodHRwOi8vZXhhbXBsZS5jb20vY3JsMEAGA1UdIAQ5
+MDcwCAYGZ4EMAQIBMCsGAyoDBDAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBs
+ZS5jb20vY3BzMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAOqk/Tv0cUSnEJ4bZ
+a0eprm3IQQ4XgNcv20/bXixlxnQAAAGHbmFdrQAABAMARjBEAiBe95hlRBC+3fHj
+vTttV2T84ZrdD4FEH+RDiR5ilgHDjgIgR1yxRvPPoQCI9RiRyTMXtxZspChz6dw+
+wMa3ybElJxsAdgB73SBPJzgqRmke/amB5I5Lbbgx3r/bVSZYjFEX2Ze94gAAAYdu
+YV2tAAAEAwBHMEUCIQCogKHQIFrGwxctsf+Sy6AnY/mPhokU1J+NQxdCR2t4HAIg
+N9YJzdLCJ7/QLPr2zZqmqwLY5ubeTyLhh91ggY4INXQwDQYJKoZIhvcNAQELBQAD
+ggEBABhY4I9o0u8Sv+y9MWZ2whVBAV0/Bz2dMFDr/wNv1Xgv0lbiEvgWFOzF74Cp
+fxPQCharuR35qDX6pUkF7e6b52yR4D0K/9Mk7WtGVtTdCFyMJq9JOk08iWiJD2wf
+fFgCt2RezPImA2+of7BrIxkwPIRLpQTnXLY+MA+nHnqMQp0NFRZP3nT7xTDNf8Rt
+VyvMM+MxzwqOi2V067oW9NdUitrqMhGiyfNRXPc0BbSt4+utO7qRQKH+DBhr2HNi
+55yj3nQJQXPRbXvJubZBrrHd+XitVknSpeg8AQlUKNUVfakQrhEgvwE3yCljs+Cr
+wLfk+hSzwwA73NSZilfBNIMR9fg=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZzCCA0+gAwIBAgIQHPTBy0utaJ82mHJs9V3u8zANBgkqhkiG9w0BAQsFADA5
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMRYwFAYDVQQDEw1DQSBy
+b290IChSU0EpMB4XDTIwMDEwMTEyMDAwMFoXDTQwMDEwMTEyMDAwMFowQzELMAkG
+A1UEBhMCVVMxEjAQBgNVBAoTCWdvb2QgZ3V5czEgMB4GA1UEAxMXQ0EgaW50ZXJt
+ZWRpYXRlIChSU0EpIEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk
+f1rlGJeno27J8UAltWo8PopRsTP93Il6+L+SScaOUQsM+TCbTO5EJ4xC+d3Unp8v
+iZRLAB1/lGhHh/Uifzov2ux2sa9J4kxlfCxVaaCx6maOs9KnfGUug2hcUCh1oUVv
+zD9X9VkWdBdR+kKJvYqWJlU/EJxEa5ERjFp591LQBpR7ksZrsbLvXeywqVS3ek8s
+d7w+ZqpYOfo6DNLl5aEJlk6F6CiSjmT352n8dnsOEIEL+bOusLhP5F8pED85geU5
+rijc38fZ+gfZAVVenz7kqBh7ld6qT5inIM4uQa7oCuFX2dZ0jqm5TFBBtQp9dkFv
+WFz9kEb/CVJr1IsTdp1PAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMCAYYwHQYD
+VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+KQYDVR0OBCIEIGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMCsGA1Ud
+IwQkMCKAINmvCHaWHo5MD5lKL52otAsr/TsCX5Hwfy5euQ6zIAWEMFgGCCsGAQUF
+BwEBBEwwSjAjBggrBgEFBQcwAYYXaHR0cDovL2V4YW1wbGUuY29tL29jc3AwIwYI
+KwYBBQUHMAKGF2h0dHA6Ly9leGFtcGxlLmNvbS9yb290MCcGA1UdHwQgMB4wHKAa
+oBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwwOwYDVR0gBDQwMjAEBgIqAzAqBgIt
+BjAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMA0GCSqGSIb3
+DQEBCwUAA4ICAQCTLNQlCzHynESAvtPRV1FPaOQhx01RofwS/0Zg3IH5oXxSC98C
+n2L0xHN1gCaJai9XutrFtMCjeBmese48QoPa8MxrB1UpmZ1AuFOQAfHWJZbYPp0V
+PxgY34W9Onb+JPnKTbL9ofKUV0aX67eJ5KKFD1G2z+y9Lz1oA3yJpGzqOY/JCWYz
+q46ik0bmgcGfol6F/T5hoE8pZk8Wr+nNUpSuOSNp7c/g2/pKDRWK8trTrG3owtaJ
+LbQc+W4e97AtTg6DGvR5gftar/+4g2o0xhKSnep+s/bf5NFXVDCTvCmemrbR8Hr7
+NLDKXWuGMoMKIxhyPX6ttpU2Um3rQ1rCQbJ5yWIREZvbdaeK8HSRE3GYE71Z3n/0
+0Kmtg2BKGkrJzcqUSG4o+9mdSjhJ65J76ri5tVQby7Ai7W2KlNjpdI6GYtejUAlf
+vZz5N0e2X36XLPZ8tz04Ix9KLHXMEuA7w/aOglH1Lei+PPp7kBjvXAL66soqCTqu
+49yNbPPGIjGO453+jNzxhbeimh6a5/Fwd4SjsdSBe8AwIGGZTzLiIzCNM5OmcoUf
+Tl5RrVXau4DvX5KvfwOLusl/uJH+7oETJlbi8+fNn2ioYfHg5/Tu3zKZw/Y+6wSA
+LsOIJrFmJEgIBUnWp/B1ZC6TeIokmw5FeJsY1UnFDWsPVcax2T/tg6BZ5Q==
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/homework.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA0FLyYu7i4H0y0RcGFd7SSpgnwo6CNUDhjEjjIVoXs4KP4GBB
+oo88iKcG/fSdv2ibZpMf2BmiAbilSgtOfNB6eRbBxuud5nHMmMDrO+NFbNz7l0L8
++dz1qIlMjFM5xv4gTDpOTqyIh30tiDpvNbCK+c6nOi1KrUCVxqrriOETYxGXJpsg
+/MRT3R8htofpP0bIDDekk2FMtpZAUDs6YfSnEYP+OQhhWOoFD0HeXfoGiN7pmSrM
+GMUUtkwisHykPXuyI+PR6+L3BjAcUR7UTNhWNvER35EL9+7q7C/NW7VeMPMjVxbA
+f0KOx8If4ZFjYhdn6qya/EO1p+M7aNZf5GYpOQIDAQABAoIBAAQU2/LcQ02qpkg6
+b0AWcYBMotrI9/wbHVAmTWzl2vkFYdQ/jomkzMbQnu5y0HRjlEfaMhGFzwrBl1tw
+BUWkwAGHVJyVGS7eo+eKQBGYTk/ntHYuNc6faaC3r+w/5S3k5LUZn1cjQjmFVlMI
+8gzXPKHQTq5xHLlwPn758ktoPXz/XihnaKq1agepiV50Y94XEBN0V8WOTgV6Z1AB
+puyebcojoCqMG0TjaIVbowpC2suEV3LTwEdBuAIxdAKEHyTqgedFcULsXfLfkWrH
+Y5sU/gos+3pn9ssNLzfZmoXE9EZnJn6AcQVKfG81YpDnnrcvsa03H8P0NnQNX7iv
+sSuV2kECgYEA99TiQZ0X6C9Dd63BXWhnPg5cuSX0w27COlejtSghLWw4JdBQWBfg
+8Qu+Iv4WcLnnO+aTgYU/WUMdsHX7aW4/OHNibVK4XagK36Ovfd+x6hSUaVXoHHIZ
+O8CcQDwsjlf1RpEpHiWXePifcKT7U43A55gd5jkIryMdiJzQFRdoXMUCgYEA1zC2
+RDwrE70Y3d/AgC4DyuKoCI6BWxGDUUD5B5B8EQ8VJ7oCo8WvefEj856DrHadxDGN
+5knA5PAZRZMAf/xdaJZRb7DEWB+jL3MOXRx7iDZ+nxbuFGeIq8YhCNyl6X784tLm
+jUUIDLLcJmgDZWaZUeK/KJXG6CkX4wjAifKrSeUCgYBE8vQznvn8vdF+ZVfrndVD
+XYmMdTPgf1sIEkPYbZ61XWtNkeQYbiRFy6eNYrVpjT742lBlc2XJdrO22Uwl6+co
+1Gko0x+55ruHtqlnUqnT3v/fQmg+Kbw9dHvEHzmiLHRvuoj++yRmIn1xQGUvvLtT
+c222FF4HI7TewgneJFIDpQKBgDviZHasv9goYYktQWbqZ0fIt7ZZ4gmz3/V2vVar
+kLs2lU6O5zkoRGMNBlI/pKkUAWed2r1hfPq5Dm9cL2Fum5gFbPhGGv3jIAiOXkrv
+KDmJ4MbpHKw0+8XBx2Ot88X8/ucohdx+f3T5OWa+v2MyoCw3i7cBp27oMKuPvfUl
+wSE5AoGBALd3efbe2//0GP8Cxjf3FYjb8xDYNF3RKMbQVfbGDcLQX/surxAdpqUR
+NqWTiqUKcu7sUQO1nOwC33m2464MQbsWxtNp2zMmLaAGNKOtD6FpltA2Nup3K5HW
+FsP0kSm0NgWVyyEX/jScECLtSBNT3kC4q+DKyxC1wqQrq0cSe6gd
+-----END RSA PRIVATE KEY-----

+ 64 - 0
resources/development/nginx/ssl/ojbk.me/fullchain.cer

@@ -0,0 +1,64 @@
+-----BEGIN CERTIFICATE-----
+MIIFbTCCBFWgAwIBAgITAP/1MpNXKZIVpnbIa9/M+0pUCzANBgkqhkiG9w0BAQsF
+ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzA0MTAwMTU3MDhaFw0yMzA3MDkw
+MTU3MDdaMBIxEDAOBgNVBAMTB29qYmsubWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDH2mgsoGSFeXM3h3W0n0KofOPUxo+6Qx5hPcvtDN+HkTss+Udb
+UYUOxK5wsqFErOwbGS6gKCVwNeLuVDCkC/r2eAf3MSSAUpAMu10wZlPT/UYMCMs8
+mwLz2EN4MBvd3bxZmfmvjzRjYqK7kcvQhxIEjPwzGeQKoHjtbgTRcbJen17cR5i8
+jmHelcE+xt+R88MGyNP3LquudKujh7X+URiBwq4YedUl7GiCUIVxl1pGEW/8QK/5
+OvrcgCKpkNJU8vhWMsuhqvXLhTYQ3JXe2H7QNBPknGI8uNxGdCSasejyQsyXtamg
+w9bQ3D8gH3ooFE4CNFHRtr9Wgmg+ZyQb21/HAgMBAAGjggKJMIIChTAOBgNVHQ8B
+Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB
+/wQCMAAwHQYDVR0OBBYEFNfN19b9UyI7RL60PL/AicpNxp0/MCsGA1UdIwQkMCKA
+IGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMHEGCCsGAQUFBwEBBGUw
+YzAiBggrBgEFBQcwAYYWaHR0cDovLzEyNy4wLjAuMTo0MDAyLzA9BggrBgEFBQcw
+AoYxaHR0cDovLzEyNy4wLjAuMTo0MDAxL2FpYS9pc3N1ZXIvNjYwNTQ0MDQ5ODM2
+OTc0MTASBgNVHREECzAJggdvamJrLm1lMCcGA1UdHwQgMB4wHKAaoBiGFmh0dHA6
+Ly9leGFtcGxlLmNvbS9jcmwwQAYDVR0gBDkwNzAIBgZngQwBAgEwKwYDKgMEMCQw
+IgYIKwYBBQUHAgEWFmh0dHA6Ly9leGFtcGxlLmNvbS9jcHMwggEGBgorBgEEAdZ5
+AgQCBIH3BIH0APIAdwA6qT9O/RxRKcQnhtlrR6mubchBDheA1y/bT9teLGXGdAAA
+AYdpGIqzAAAEAwBIMEYCIQD8GVPhm9jPxwla9uVtuYk5ateCOqv9f6T0A5IVGWpb
+iAIhAOgnep1Go8Zvl/3ig5IV+J1E3rc5udUigq2Lbxt1q5u8AHcAUtToynGEyMkk
+XDMQei8Ll54oMwWHI0IieDEKs12/Td4AAAGHaRiKswAABAMASDBGAiEAlOUvnuUF
+BUWIzMgsH3TTbaQN1gga8OTYxDySjQdj/mQCIQDgGQPuqp1cgjQHPik/aVCWYtbq
+Pf79O3zv0Pz7p9ROvzANBgkqhkiG9w0BAQsFAAOCAQEAWodvuzVbZNalTo03JO0s
+bCugOhOWyi9dqfr1Zk2X/WvKFd/qMjNj0pVRU5as6ihS8zzhSL4fllO+RoNGOnJc
+Mw8QgfdHkrWc2XwmSmrKvGNpS68ZVjxv5TsZVvK+O2ZzHMQgKDXdgyF0P02MDkQP
+/FF8FvJc4HaQRaI3CMg2bXXwMQ8efvkaWZGs538acldJpgxloeUTM+5Y6RDcXkqR
+ihDD9R13GlXZgJ5N+z83ipeBorJLIIOMlqxGB//4gS9qxEqmnU4yISEw9efXICak
+Ny/wlpRqqK1jTrp3SuuAe4XCorQ4J9SZS+aa5BRU6rH5w5wKhXLd55vphPkFdtPO
+DA==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZzCCA0+gAwIBAgIQHPTBy0utaJ82mHJs9V3u8zANBgkqhkiG9w0BAQsFADA5
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMRYwFAYDVQQDEw1DQSBy
+b290IChSU0EpMB4XDTIwMDEwMTEyMDAwMFoXDTQwMDEwMTEyMDAwMFowQzELMAkG
+A1UEBhMCVVMxEjAQBgNVBAoTCWdvb2QgZ3V5czEgMB4GA1UEAxMXQ0EgaW50ZXJt
+ZWRpYXRlIChSU0EpIEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk
+f1rlGJeno27J8UAltWo8PopRsTP93Il6+L+SScaOUQsM+TCbTO5EJ4xC+d3Unp8v
+iZRLAB1/lGhHh/Uifzov2ux2sa9J4kxlfCxVaaCx6maOs9KnfGUug2hcUCh1oUVv
+zD9X9VkWdBdR+kKJvYqWJlU/EJxEa5ERjFp591LQBpR7ksZrsbLvXeywqVS3ek8s
+d7w+ZqpYOfo6DNLl5aEJlk6F6CiSjmT352n8dnsOEIEL+bOusLhP5F8pED85geU5
+rijc38fZ+gfZAVVenz7kqBh7ld6qT5inIM4uQa7oCuFX2dZ0jqm5TFBBtQp9dkFv
+WFz9kEb/CVJr1IsTdp1PAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMCAYYwHQYD
+VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+KQYDVR0OBCIEIGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMCsGA1Ud
+IwQkMCKAINmvCHaWHo5MD5lKL52otAsr/TsCX5Hwfy5euQ6zIAWEMFgGCCsGAQUF
+BwEBBEwwSjAjBggrBgEFBQcwAYYXaHR0cDovL2V4YW1wbGUuY29tL29jc3AwIwYI
+KwYBBQUHMAKGF2h0dHA6Ly9leGFtcGxlLmNvbS9yb290MCcGA1UdHwQgMB4wHKAa
+oBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwwOwYDVR0gBDQwMjAEBgIqAzAqBgIt
+BjAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMA0GCSqGSIb3
+DQEBCwUAA4ICAQCTLNQlCzHynESAvtPRV1FPaOQhx01RofwS/0Zg3IH5oXxSC98C
+n2L0xHN1gCaJai9XutrFtMCjeBmese48QoPa8MxrB1UpmZ1AuFOQAfHWJZbYPp0V
+PxgY34W9Onb+JPnKTbL9ofKUV0aX67eJ5KKFD1G2z+y9Lz1oA3yJpGzqOY/JCWYz
+q46ik0bmgcGfol6F/T5hoE8pZk8Wr+nNUpSuOSNp7c/g2/pKDRWK8trTrG3owtaJ
+LbQc+W4e97AtTg6DGvR5gftar/+4g2o0xhKSnep+s/bf5NFXVDCTvCmemrbR8Hr7
+NLDKXWuGMoMKIxhyPX6ttpU2Um3rQ1rCQbJ5yWIREZvbdaeK8HSRE3GYE71Z3n/0
+0Kmtg2BKGkrJzcqUSG4o+9mdSjhJ65J76ri5tVQby7Ai7W2KlNjpdI6GYtejUAlf
+vZz5N0e2X36XLPZ8tz04Ix9KLHXMEuA7w/aOglH1Lei+PPp7kBjvXAL66soqCTqu
+49yNbPPGIjGO453+jNzxhbeimh6a5/Fwd4SjsdSBe8AwIGGZTzLiIzCNM5OmcoUf
+Tl5RrVXau4DvX5KvfwOLusl/uJH+7oETJlbi8+fNn2ioYfHg5/Tu3zKZw/Y+6wSA
+LsOIJrFmJEgIBUnWp/B1ZC6TeIokmw5FeJsY1UnFDWsPVcax2T/tg6BZ5Q==
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/ojbk.me/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAx9poLKBkhXlzN4d1tJ9CqHzj1MaPukMeYT3L7Qzfh5E7LPlH
+W1GFDsSucLKhRKzsGxkuoCglcDXi7lQwpAv69ngH9zEkgFKQDLtdMGZT0/1GDAjL
+PJsC89hDeDAb3d28WZn5r480Y2Kiu5HL0IcSBIz8MxnkCqB47W4E0XGyXp9e3EeY
+vI5h3pXBPsbfkfPDBsjT9y6rrnSro4e1/lEYgcKuGHnVJexoglCFcZdaRhFv/ECv
++Tr63IAiqZDSVPL4VjLLoar1y4U2ENyV3th+0DQT5JxiPLjcRnQkmrHo8kLMl7Wp
+oMPW0Nw/IB96KBROAjRR0ba/VoJoPmckG9tfxwIDAQABAoIBAQCVNSGrl8AUyrzT
+i2W8iV657iwm8mviEzQ+Q2XZwE+upiY5CI9WxUEC6qKV7zdpz3SjMqTV28M/yGt5
+pg9+mpcupiTET8NBruJvLv9/IBab4a8HUxVxVQXaSe7tb3WOqnpxpvx8Lv1c6go2
+4b36HK2+CghMmprfuDkwgoBEYo9aBm5C9kH2EThZjvgqYb+3C2OPWKu4so6kn+pr
+BVgoslTlYEhmbdv3CVmEKpjfYJ2PFW0XQvHuNX1S6OSKgm7oPK8efgixdJ9DArH2
+B5ST8UasjCyGzdjXBiBMZl9hMwTQHM5dhEA0I8Ay1UOIpkJsBARrWYXXpacfMKYx
+TbN9yFqhAoGBAOuro28hNN7zazeI2rUIPQ03ALQUYS/fCHPF+Zj9pnX0G4JfSiAm
+lJjFZLosgByUTT1BfsRCvi8U1XXigR+cR1b7e/wQU0nY7dX5kTvdYtgy1EVIatjP
+ZE0gAkfPGfpBcvkuRpEn1d76NY+Tc0Skyczw1PenIsgAMX3lBEuIero3AoGBANkX
+zqBc2Ky5WZYOdceTDBQoYWMjFnFtyhN8uKDPCFVBmUPe8zjjVYcDxJhbUxSdHufZ
+iZ9m++VbghkMRsom/aQhtGSDA/G+0KFLZHkKI+2Kp5nlql+U5pBMaRAz3y7DDYUY
+tdmw0vKr3wyGG4a1OpX/3GyCUo0AWraNSe/rLX7xAoGAEKvHl+jlcJAPmZIYxo2a
+pACsEBEQ/u3sDySGD4iB6f4mpAZRnAyenl/4TCMxvNPQ0vB7Iy8FfRcgMoKfV7ka
+YtDPaHlPKv4pdOV+nJmAkIKn+W6yADxLgBkvkBzaXZWbqHzZiRMkUe2pNErd8JEn
+pVOz/QDZuQQqfBljQGSbKekCgYA+ktgh7OEfWAV0dRKJxd6zaLg5ZQGxDB4047wr
+zcDvIgtVPswIV65tTi66Y4hc5rTjyxYZ9gshHvEu2xUEa5ysVHK0jfbzFTb5Zsu8
+/6k/bLnGaCZlinuyl/OokesAhYQ+GtM/vfHvtI6EA5unhlEuryMPu7ijn7iF6c+n
+8beZYQKBgQDgXUhEV6jKvq8tDxsqSz+GuKt6tr18pHdFUrr3rbLOsRrvsEawnqkk
+R2MsC50rfIcpji+KMEG04olYiE9bHtWnwrm7ADP7rfqwB6M0FHbxbzidVqSlLTdH
+VvlDbBXGVwS7pFSXi7GVnAod+7EAt/zAvda3zVojBz7W3SOgaWobgQ==
+-----END RSA PRIVATE KEY-----

+ 93 - 0
resources/development/nginx/ssl/qi.jackyu.cn/fullchain.cer

@@ -0,0 +1,93 @@
+-----BEGIN CERTIFICATE-----
+MIIFHTCCBAWgAwIBAgISBOq798VWOHfbJedrr1a9p6XmMA0GCSqGSIb3DQEBCwUA
+MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
+EwJSMzAeFw0yMzA0MTIxMjI4MDZaFw0yMzA3MTExMjI4MDVaMBcxFTATBgNVBAMT
+DHFpLmphY2t5dS5jbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgt
+SYgwXhNM1XIDWd+wH5N9wQPVd1dSDAVOK4NakfxPyP1ztbCA26ELPaF7+Bt2r2pA
+4HrTXqAmtjCw3BQHaUIHt1ioAQ2jfY8+khjsZByZcMzI2DGQxLm8qyukUjh9IRxI
+k/0cYkcEE5Rc94Zi7YjVyJIepDtBJQYHRJdOHDFj8oqjEBn318M8SwWIz05AfC5Z
+AoIgT+HrAHmWTw6lFbVOzoEddPIK1aqmEtmn4XeGgwMXfbo6TA8WJedqVrTn02/9
+ZEaaZ+CDx85LXv2ZV7PhSCh+0BJSj/NHjJE3pi3ZRPwA64tbeOCJmYofXnH4CaaE
+PJjgfeqAc7AbBLy0VMUCAwEAAaOCAkYwggJCMA4GA1UdDwEB/wQEAwIFoDAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E
+FgQU+DT3aItAosiiAoe4SFpHJs/rWUcwHwYDVR0jBBgwFoAUFC6zF7dYVsuuUAlA
+5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vcjMu
+by5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNyLm9yZy8w
+FwYDVR0RBBAwDoIMcWkuamFja3l1LmNuMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
+CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
+cHQub3JnMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAtz77JN+cTbp18jnFulj0
+bF38Qs96nzXEnh0JgSXttJkAAAGHdabr6AAABAMARjBEAiBzsclX1T8pV+HhHFd4
+1IdJ2l0gMrhkIpOvjwc/u7SoswIgAK/8W7m5jG3V/7ifXcpq0FUHMjDB7EgA3u19
+wWC7S6oAdgB6MoxU2LcttiDqOOBSHumEFnAyE4VNO9IrwTpXo1LrUgAAAYd1puvw
+AAAEAwBHMEUCICOOkrLw2k+IMKS5Y1Yyry3JOO/0H0GFBjZkq0bma+CnAiEAw4n8
+huXWs9r/d5Wkh98dDScGV3TcbPo3kPMM+n+pnlgwDQYJKoZIhvcNAQELBQADggEB
+AB9/AXndrva++VWh3J9pnV4ozDD58nc50X8PhNEWWvx5Gc7ut05xG/RBSAgoGtmg
+A0iCIRdVegwMnJXKQo7r3fJwInY+cah3VbS1ItbDBBEx0m3cpOm8FnyH+87FaZSd
+RvnmARYRhlPomzeSDHRL/zGC6fg1KygEY8CALwSsRdQIGQmMLFXWz2HUQXxNRmAQ
+/UatYLTaUf7AVWGeD2z+Of1wDNs+GcP3hMDfyrxCf/oFfp7iDzp/VZboX40NoXBq
+vvdS9kJ0d9Tm//clGTXlI+FZbbAUwGkL5zwY4TraWJFqcmX1LKlSjQwyr0rT40cg
+61jNhVHyxUKVg97lFHH/MXs=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
+WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
+RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
+R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
+sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
+NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
+Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
+/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
+FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
+AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
+Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
+gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
+PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
+ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
+CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
+lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
+avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
+yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
+yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
+hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
+HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
+MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
+nLRbwHOoq7hHwg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
+ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
+wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
+LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
+4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
+bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
+sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
+Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
+FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
+SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
+PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
+TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
+c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
+ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
+b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
+U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
+MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
+5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
+9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
+WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
+he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
+Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/qi.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2C1JiDBeE0zVcgNZ37Afk33BA9V3V1IMBU4rg1qR/E/I/XO1
+sIDboQs9oXv4G3avakDgetNeoCa2MLDcFAdpQge3WKgBDaN9jz6SGOxkHJlwzMjY
+MZDEubyrK6RSOH0hHEiT/RxiRwQTlFz3hmLtiNXIkh6kO0ElBgdEl04cMWPyiqMQ
+GffXwzxLBYjPTkB8LlkCgiBP4esAeZZPDqUVtU7OgR108grVqqYS2afhd4aDAxd9
+ujpMDxYl52pWtOfTb/1kRppn4IPHzkte/ZlXs+FIKH7QElKP80eMkTemLdlE/ADr
+i1t44ImZih9ecfgJpoQ8mOB96oBzsBsEvLRUxQIDAQABAoIBAFt950YdemNevGOR
+qYLzjpmkuoD94pFxZycDq4TfWzPXLLCgPUBjeRDSqhXjWuF8vMcSiC9TsBPF7ovy
+/mH3tQO+MknyaOe1zxlGGR01RzWXd1ckleN8atZVVHiXBjlkNBQ9X2zbi3iU8Bh1
+tEkLK48jM80r7MQrURkZEF2dMG9yHsnu9GddhGgryEbn5NsoFs/gnH8kJ+Q5bMOY
+sR+o9RYV+5u6ux955FOunicrYMMc1w9UsUu+ynWBhIuh0Y8JiXJYrds2RTV+bYsz
+uWauuo7nw54oUfaxNYClFKXZZEzUx00UV6uKIsiTqGEXYJVaLhW+KMWy+K64bcbX
+Pot2cCECgYEA/+kVQyMPqgD7I/X2s0q7vxgt09x3HgFIot1RAKmX3ekq3dwCfXLB
+XIlCOQbN0Dm92sQZUWb7yuJOQ8vHSKrrQSSR2IWniYuSY3ZhxwGEXGQ1wSv24OMp
+aHlA6Y14L6EanjIQB5GMzB3Li6BLgKK7Y0VBWlJnDTK0i+S0l/HKhhkCgYEA2ECl
+YQf7etZwEkwWWkQ+75KR4ptttchd6QNqfEWF0qQjjiGi8nS6b3iVBKjx3JwkI498
+bXaaDPQmA9J2Mo1seavXDMGQ3924DsLfCety+SLsFUnR3wE0iqsAc0VVLJm/mddR
+YOh3f6+t5y3l1ISScZsn7cktJ5FAvNsus99BYY0CgYEA215/hnygqdeLcQkggBL5
+G9drOWiMh6EMFehnzoySjXyZ7XLyg30CegODTAUkGnHU6JofJeOExib2djFR1F4H
+qmDh0NzJgCOvyikpqgEH6HdSiRPZ3m98CH2gglRuCU4t1hwOF57SNgr4d+lhr5RP
+08oDOpzWj76+fAkCMhnnxMkCgYEA0YmiNWjUpevOYAxVxFVIXj65GMfeADwLstJa
+hduflcDxqrCxARlV5NkEG6XP5SFuav6HZFF9Z3vSsfVmDgm2yBZXo8aTKDfgNn1g
+PG5l0z2hX+dNcjXqwUp8fzT5GORJITnfYnUeBR0m9lAk2E000Nu0TtWV6Tb3cYc3
+s5Zp9akCgYBuhq9BUaUHR0xU1q+25Rk2cdyAoH35yF6XBbNntkjc/FvSRJifsyZc
+y0qwxy162zwA6zjkh+pDB2lvwYlcAz4c/nIAY8s9GmNUAHzEYdlyEw5QqBoboWya
+dhVj6WZzHa2+732C5KTT2M5ioc/Rq8QQ8pBSwh9KD/h0sqO4hir3kw==
+-----END RSA PRIVATE KEY-----

+ 64 - 0
resources/development/nginx/ssl/qi.jackyu.cn_amstourship.jackyu.cn/fullchain.cer

@@ -0,0 +1,64 @@
+-----BEGIN CERTIFICATE-----
+MIIFjTCCBHWgAwIBAgITAP/P3ZE1JmwV81LHZ6oFO0s8XDANBgkqhkiG9w0BAQsF
+ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzA0MTIxMTMyNDdaFw0yMzA3MTEx
+MTMyNDZaMBcxFTATBgNVBAMTDHFpLmphY2t5dS5jbjCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAN/eCplZNNzyLxxjwPQE+XE7bIZJ0MsmrFHarDH9Y5LM
+0V7GUZqSuCB8X6Rwbl6J8eXZ3eAAOJvGwn5YKvDpC1yPyp7Ej8dcLZpjV30foWKR
+0snjDS8vLu8b53FBxx6k77cnaDpz4p9odPSO8UJ5NgWcsIbhb1YtUkCkcSyfTq3Y
+2KV5N8b7u4BQsmelGL6IdAbJZ4Qvy5Pu570+LJjelZEFvUoLDN5az53W/jYcmdZF
+dl48CcA9qPax8qBumoSKzcHMgEWWKXHL1vOPCwsWkmoOdgGr/00hgEt5KbVvA3Gh
+fZpHxF0KiIAMbl2WBG4o4TqzZExpldNabHHp+cj3DhkCAwEAAaOCAqQwggKgMA4G
+A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYD
+VR0TAQH/BAIwADAdBgNVHQ4EFgQUnrbx1bK3bVtn6XeZWsXeb+pxljAwKwYDVR0j
+BCQwIoAgYjWGqKXC1CgUyRtbS1bZxpMqaNdKnoY33nyaZdVEQ/IwcQYIKwYBBQUH
+AQEEZTBjMCIGCCsGAQUFBzABhhZodHRwOi8vMTI3LjAuMC4xOjQwMDIvMD0GCCsG
+AQUFBzAChjFodHRwOi8vMTI3LjAuMC4xOjQwMDEvYWlhL2lzc3Vlci82NjA1NDQw
+NDk4MzY5NzQxMC4GA1UdEQQnMCWCFWFtc3RvdXJzaGlwLmphY2t5dS5jboIMcWku
+amFja3l1LmNuMCcGA1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9j
+cmwwQAYDVR0gBDkwNzAIBgZngQwBAgEwKwYDKgMEMCQwIgYIKwYBBQUHAgEWFmh0
+dHA6Ly9leGFtcGxlLmNvbS9jcHMwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgB7
+3SBPJzgqRmke/amB5I5Lbbgx3r/bVSZYjFEX2Ze94gAAAYd1dEYyAAAEAwBHMEUC
+IA3BeDykcaVd+TfY+5g9MhQqNF8HxMMORvyhn83lH5b0AiEAtzkqbBNT0XVsaqER
+e3rW3Dh6J+QUYKM+f03dblTllPoAdwA6qT9O/RxRKcQnhtlrR6mubchBDheA1y/b
+T9teLGXGdAAAAYd1dEYyAAAEAwBIMEYCIQDS7msBD7eaNH7EB9ib2jS1eJP7HRl5
+ARflt+tbuc92yQIhAN7ZWBvZ/aOq4UPHEPJAi5IuSEWkjh7fPcrVHcH7SrZcMA0G
+CSqGSIb3DQEBCwUAA4IBAQAOOPLPolx/46rSdH95EG14tb6p9BVk4STQrSF8uN/+
+l+7PlhS075iX6H83RnH/XK52BHFh5Tv63HfXarvALUUUs+Si1OXraIB2ukq75eE6
+SZvI3SNuVKI41DcKtgBAe/UPXNq1FWhaIeesPkDoWjGnjuCJSFozPuO9xaJ49JgZ
+u6bnOSDnrkp1gw3bOhLiIghF0nI8hotu/fV2ALUDBemm/svclXqS70kTnAfYIyR5
+dEXcqFC9dumH/KR7x9JEVpxDk/T8mRfoTi507mQJ3BBgW8S2GCTQvMctEHybPOZ7
+W9KfwNryC3zvgmxjuLIRs02XrI5aeN4W4Eqi7C2ot4aw
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZzCCA0+gAwIBAgIQHPTBy0utaJ82mHJs9V3u8zANBgkqhkiG9w0BAQsFADA5
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMRYwFAYDVQQDEw1DQSBy
+b290IChSU0EpMB4XDTIwMDEwMTEyMDAwMFoXDTQwMDEwMTEyMDAwMFowQzELMAkG
+A1UEBhMCVVMxEjAQBgNVBAoTCWdvb2QgZ3V5czEgMB4GA1UEAxMXQ0EgaW50ZXJt
+ZWRpYXRlIChSU0EpIEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk
+f1rlGJeno27J8UAltWo8PopRsTP93Il6+L+SScaOUQsM+TCbTO5EJ4xC+d3Unp8v
+iZRLAB1/lGhHh/Uifzov2ux2sa9J4kxlfCxVaaCx6maOs9KnfGUug2hcUCh1oUVv
+zD9X9VkWdBdR+kKJvYqWJlU/EJxEa5ERjFp591LQBpR7ksZrsbLvXeywqVS3ek8s
+d7w+ZqpYOfo6DNLl5aEJlk6F6CiSjmT352n8dnsOEIEL+bOusLhP5F8pED85geU5
+rijc38fZ+gfZAVVenz7kqBh7ld6qT5inIM4uQa7oCuFX2dZ0jqm5TFBBtQp9dkFv
+WFz9kEb/CVJr1IsTdp1PAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMCAYYwHQYD
+VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+KQYDVR0OBCIEIGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMCsGA1Ud
+IwQkMCKAINmvCHaWHo5MD5lKL52otAsr/TsCX5Hwfy5euQ6zIAWEMFgGCCsGAQUF
+BwEBBEwwSjAjBggrBgEFBQcwAYYXaHR0cDovL2V4YW1wbGUuY29tL29jc3AwIwYI
+KwYBBQUHMAKGF2h0dHA6Ly9leGFtcGxlLmNvbS9yb290MCcGA1UdHwQgMB4wHKAa
+oBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwwOwYDVR0gBDQwMjAEBgIqAzAqBgIt
+BjAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMA0GCSqGSIb3
+DQEBCwUAA4ICAQCTLNQlCzHynESAvtPRV1FPaOQhx01RofwS/0Zg3IH5oXxSC98C
+n2L0xHN1gCaJai9XutrFtMCjeBmese48QoPa8MxrB1UpmZ1AuFOQAfHWJZbYPp0V
+PxgY34W9Onb+JPnKTbL9ofKUV0aX67eJ5KKFD1G2z+y9Lz1oA3yJpGzqOY/JCWYz
+q46ik0bmgcGfol6F/T5hoE8pZk8Wr+nNUpSuOSNp7c/g2/pKDRWK8trTrG3owtaJ
+LbQc+W4e97AtTg6DGvR5gftar/+4g2o0xhKSnep+s/bf5NFXVDCTvCmemrbR8Hr7
+NLDKXWuGMoMKIxhyPX6ttpU2Um3rQ1rCQbJ5yWIREZvbdaeK8HSRE3GYE71Z3n/0
+0Kmtg2BKGkrJzcqUSG4o+9mdSjhJ65J76ri5tVQby7Ai7W2KlNjpdI6GYtejUAlf
+vZz5N0e2X36XLPZ8tz04Ix9KLHXMEuA7w/aOglH1Lei+PPp7kBjvXAL66soqCTqu
+49yNbPPGIjGO453+jNzxhbeimh6a5/Fwd4SjsdSBe8AwIGGZTzLiIzCNM5OmcoUf
+Tl5RrVXau4DvX5KvfwOLusl/uJH+7oETJlbi8+fNn2ioYfHg5/Tu3zKZw/Y+6wSA
+LsOIJrFmJEgIBUnWp/B1ZC6TeIokmw5FeJsY1UnFDWsPVcax2T/tg6BZ5Q==
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/qi.jackyu.cn_amstourship.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA394KmVk03PIvHGPA9AT5cTtshknQyyasUdqsMf1jkszRXsZR
+mpK4IHxfpHBuXonx5dnd4AA4m8bCflgq8OkLXI/KnsSPx1wtmmNXfR+hYpHSyeMN
+Ly8u7xvncUHHHqTvtydoOnPin2h09I7xQnk2BZywhuFvVi1SQKRxLJ9OrdjYpXk3
+xvu7gFCyZ6UYvoh0BslnhC/Lk+7nvT4smN6VkQW9SgsM3lrPndb+NhyZ1kV2XjwJ
+wD2o9rHyoG6ahIrNwcyARZYpccvW848LCxaSag52Aav/TSGAS3kptW8DcaF9mkfE
+XQqIgAxuXZYEbijhOrNkTGmV01pscen5yPcOGQIDAQABAoIBAQDAas9dY0mGpztL
+AYq1sxjb9KGhAw1Nr93pNTVQemT9psJG3dsCKx3L/lsOsfyLkbGzSIHmqQn/CuXd
+RtcR3pz/YDBeKsESL+6ahsyKJYfHe2NcV6XbaojCyI8zz7/gXRAVsu9pXnXpYhU0
+pzBDXH/MbxNju5zAy4+pNC56litATEiyzuQofYBQuOm6zlhNVQfqj025EgWufFmf
+StMoIj/IScsGOw8/M8HRmMICJToXai+EdgAcfJtavvzTRjBHjvZsPAmKju1plpIh
+KaYHIlNmAvt8yPbhrmOCPmINfU4NRPQMaSGq5za1snefETu2ecNiAsRql3HCBplp
+mmH/O/tpAoGBAPVGFEzOUHz4ZNaKodYpKtC20taN94KSFgdSn4vltj7LJjcFAHut
+7R5a9XAq1kEm5M+g79ep2x/3TBaskDCCk/50fBVHoEkVxW1PqW7fhgs0OqF4jw4r
+6Y4+ABWfsj4F/ncTKvPNYO2FpoMtgp7LH5lNOsN4bAbE9C6FRaklytDrAoGBAOmo
+T3Z1YCdoIWbeAM0KyxziL6n2+bYH79XwKqfGqniE4KVF7oB0YCBmJRlloowSsexu
+55e9amXqeQfMVbjivu3KiQbvMoLfrKqOo1f6jbCcR7+O/eT3IRuYz/Yw2Da+QQJ3
+vwyWM+tAKYX/sId6RezmPEX7xmKcrCjIIY7mmzwLAoGAJ9i9vYibDOJxx2T4S8me
+WhAJiq+/sSe4inIC14B3LhZk2/VlEbK83fya+SEMc8M00wJrVJcUsUnEw74/IpJk
+JqeX7QEY6iauT0bs2MVZioJusALdAslhhSlPbDPoiikBISktBjSsdYoL9i2zlcac
+GJSyYkUzD5p5rQEbwxIPtAcCgYAzjctam7NHfpZAGCOdGhCOoulZWwDlxQKJ9Z+z
+vQXH6amXTcK93O+ItoDhBafDuCxBuoam2EgtjHp/2fnf/UebN+DcAtLmRWvXhflM
+ZB/3I8RA48/pQQ2xBRp9e3F5QqkdXkZtBIcYFOQUiMHuYnAjQPlzh4XSJDdoGCAv
+Y3pE2QKBgCMzVmTHbaCiiAguMI6IYbI76QpBC8AYcS3B9bZu2pvj8nqhIvHHj2O5
+zWSp48duB5aj5WLKUH4HTYd+TL4r0vBxMbxkGt4pksh2U2LKndEg3LoapO59dAbe
+Cyzid4ub/2pCRBwXFdkpVcuR9DR1WilloPu+p14I6+4Oc5+SJq4e
+-----END RSA PRIVATE KEY-----

+ 64 - 0
resources/development/nginx/ssl/test.jackyu.cn/fullchain.cer

@@ -0,0 +1,64 @@
+-----BEGIN CERTIFICATE-----
+MIIFeTCCBGGgAwIBAgITAP8yxO86Bnt3WApmIOscI9aZGzANBgkqhkiG9w0BAQsF
+ADBDMQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMSAwHgYDVQQDExdD
+QSBpbnRlcm1lZGlhdGUgKFJTQSkgQTAeFw0yMzA0MTAwMTU5MjBaFw0yMzA3MDkw
+MTU5MTlaMBkxFzAVBgNVBAMTDnRlc3QuamFja3l1LmNuMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA0DgmFoPB+e+V573Fn04jdk+MGj4K5OA7pNM/72ZM
++u12IzdRl9tkCtq+LEr5rPqd6i+jeQPMR7jYz5rgeeaE6uxgxqjJvL7UcrCaFZs8
+ONFPYp/R/VLsV0fNU6HVFxGlTh6cYpL91JC/SA66xWv14MEvesUPs1Ia8XtroFyc
+OaLwc73DVi6jGzaoJOsx5AjCv1a8FP6/71uenBwkjDt5l5glwkYiRWl6p+XrWvSe
+MBd2/ufneFRoDUnOcnqtAGPagRdJuUZnVzxhXJsSUC+RrK6ZjyWrbUCr0mrYx6qb
+ta5BkyqZnyXTx/iSqTo/87/OX7I/TAhlrfl6B6P5NbkarQIDAQABo4ICjjCCAoow
+DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM
+BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTHv0Rn8yDyoE5Z7djfQJcft3aJ0DArBgNV
+HSMEJDAigCBiNYaopcLUKBTJG1tLVtnGkypo10qehjfefJpl1URD8jBxBggrBgEF
+BQcBAQRlMGMwIgYIKwYBBQUHMAGGFmh0dHA6Ly8xMjcuMC4wLjE6NDAwMi8wPQYI
+KwYBBQUHMAKGMWh0dHA6Ly8xMjcuMC4wLjE6NDAwMS9haWEvaXNzdWVyLzY2MDU0
+NDA0OTgzNjk3NDEwGQYDVR0RBBIwEIIOdGVzdC5qYWNreXUuY24wJwYDVR0fBCAw
+HjAcoBqgGIYWaHR0cDovL2V4YW1wbGUuY29tL2NybDBABgNVHSAEOTA3MAgGBmeB
+DAECATArBgMqAwQwJDAiBggrBgEFBQcCARYWaHR0cDovL2V4YW1wbGUuY29tL2Nw
+czCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AHvdIE8nOCpGaR79qYHkjkttuDHe
+v9tVJliMURfZl73iAAABh2kajMwAAAQDAEcwRQIhAP2N2X1BWt78YEzZaAxJTODa
+pRE/x6RLEt49sNRYuGVvAiBIGFoEpvFGcxBnjA0aMXo0nTP5fxwMlCNT5hSKEQ/Z
+qAB2AB0a00HGvIsteEG68Ov4rYFc1Vk3GhDRd7VpyT++N6avAAABh2kajMsAAAQD
+AEcwRQIgOKLaqF47SLuWMnUnKvD3L8FvVTfwqtAkY/VOCBH4dCsCIQDjjhfO2EQA
+gR1J3mbMpoYko+9fFkH3etYq15+ZGUPUmTANBgkqhkiG9w0BAQsFAAOCAQEATVu8
+bLs32RpgBfLM2197IFSKEdx7UU62WhL2u7SH7NYdFxzGP15pME3UbjPwFFyylu4G
+2zopfUrz7bmnNsQ46eQh8cPVi7KkEnGIywRPLNIWMsLxG5qDA+87NtiOrqHdpIFH
+YynlPO4KQ03MaT2EUDSMDpW33J+1e+9Brsg17LwM/qUHdUH51vxkPL+QnWo576AG
+hKlK9lR8gg6xosEoL9vdzaXWGiWlYvfDAnHWaS/HRS2U1ImYhrAPAWLyqtbHhr6f
+anpGFeu0Qr8X5YxXH349/W1oWU83GkSPeHM0JBjXw+Mo7zM5chaAqksC7efQN6Lm
+JSTu/JAJ4yqhEJcaTw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZzCCA0+gAwIBAgIQHPTBy0utaJ82mHJs9V3u8zANBgkqhkiG9w0BAQsFADA5
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJZ29vZCBndXlzMRYwFAYDVQQDEw1DQSBy
+b290IChSU0EpMB4XDTIwMDEwMTEyMDAwMFoXDTQwMDEwMTEyMDAwMFowQzELMAkG
+A1UEBhMCVVMxEjAQBgNVBAoTCWdvb2QgZ3V5czEgMB4GA1UEAxMXQ0EgaW50ZXJt
+ZWRpYXRlIChSU0EpIEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk
+f1rlGJeno27J8UAltWo8PopRsTP93Il6+L+SScaOUQsM+TCbTO5EJ4xC+d3Unp8v
+iZRLAB1/lGhHh/Uifzov2ux2sa9J4kxlfCxVaaCx6maOs9KnfGUug2hcUCh1oUVv
+zD9X9VkWdBdR+kKJvYqWJlU/EJxEa5ERjFp591LQBpR7ksZrsbLvXeywqVS3ek8s
+d7w+ZqpYOfo6DNLl5aEJlk6F6CiSjmT352n8dnsOEIEL+bOusLhP5F8pED85geU5
+rijc38fZ+gfZAVVenz7kqBh7ld6qT5inIM4uQa7oCuFX2dZ0jqm5TFBBtQp9dkFv
+WFz9kEb/CVJr1IsTdp1PAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMCAYYwHQYD
+VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+KQYDVR0OBCIEIGI1hqilwtQoFMkbW0tW2caTKmjXSp6GN958mmXVREPyMCsGA1Ud
+IwQkMCKAINmvCHaWHo5MD5lKL52otAsr/TsCX5Hwfy5euQ6zIAWEMFgGCCsGAQUF
+BwEBBEwwSjAjBggrBgEFBQcwAYYXaHR0cDovL2V4YW1wbGUuY29tL29jc3AwIwYI
+KwYBBQUHMAKGF2h0dHA6Ly9leGFtcGxlLmNvbS9yb290MCcGA1UdHwQgMB4wHKAa
+oBiGFmh0dHA6Ly9leGFtcGxlLmNvbS9jcmwwOwYDVR0gBDQwMjAEBgIqAzAqBgIt
+BjAkMCIGCCsGAQUFBwIBFhZodHRwOi8vZXhhbXBsZS5jb20vY3BzMA0GCSqGSIb3
+DQEBCwUAA4ICAQCTLNQlCzHynESAvtPRV1FPaOQhx01RofwS/0Zg3IH5oXxSC98C
+n2L0xHN1gCaJai9XutrFtMCjeBmese48QoPa8MxrB1UpmZ1AuFOQAfHWJZbYPp0V
+PxgY34W9Onb+JPnKTbL9ofKUV0aX67eJ5KKFD1G2z+y9Lz1oA3yJpGzqOY/JCWYz
+q46ik0bmgcGfol6F/T5hoE8pZk8Wr+nNUpSuOSNp7c/g2/pKDRWK8trTrG3owtaJ
+LbQc+W4e97AtTg6DGvR5gftar/+4g2o0xhKSnep+s/bf5NFXVDCTvCmemrbR8Hr7
+NLDKXWuGMoMKIxhyPX6ttpU2Um3rQ1rCQbJ5yWIREZvbdaeK8HSRE3GYE71Z3n/0
+0Kmtg2BKGkrJzcqUSG4o+9mdSjhJ65J76ri5tVQby7Ai7W2KlNjpdI6GYtejUAlf
+vZz5N0e2X36XLPZ8tz04Ix9KLHXMEuA7w/aOglH1Lei+PPp7kBjvXAL66soqCTqu
+49yNbPPGIjGO453+jNzxhbeimh6a5/Fwd4SjsdSBe8AwIGGZTzLiIzCNM5OmcoUf
+Tl5RrVXau4DvX5KvfwOLusl/uJH+7oETJlbi8+fNn2ioYfHg5/Tu3zKZw/Y+6wSA
+LsOIJrFmJEgIBUnWp/B1ZC6TeIokmw5FeJsY1UnFDWsPVcax2T/tg6BZ5Q==
+-----END CERTIFICATE-----

+ 27 - 0
resources/development/nginx/ssl/test.jackyu.cn/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpgIBAAKCAQEA0DgmFoPB+e+V573Fn04jdk+MGj4K5OA7pNM/72ZM+u12IzdR
+l9tkCtq+LEr5rPqd6i+jeQPMR7jYz5rgeeaE6uxgxqjJvL7UcrCaFZs8ONFPYp/R
+/VLsV0fNU6HVFxGlTh6cYpL91JC/SA66xWv14MEvesUPs1Ia8XtroFycOaLwc73D
+Vi6jGzaoJOsx5AjCv1a8FP6/71uenBwkjDt5l5glwkYiRWl6p+XrWvSeMBd2/ufn
+eFRoDUnOcnqtAGPagRdJuUZnVzxhXJsSUC+RrK6ZjyWrbUCr0mrYx6qbta5BkyqZ
+nyXTx/iSqTo/87/OX7I/TAhlrfl6B6P5NbkarQIDAQABAoIBAQCKOVv/gkQZGVVY
+XAB++q5L8g69tQYiXkD58Er3L+BWi6DrS+PcfYXyNrgcS0Fn9Y8zUaChy0WnLyyu
+1M/D3KXMFlNATfhuVfhOkrIXOdBGn5ylihcqpgvC/SVvKXV+NPaZ33RNc+sRW22/
+9hdCy6KUK2rLg1oi3pSITjhqYlBLaY7RC+CReIOvtrNs9HSQ3JylEP9Cp6geIdDp
+v9dLbpRaJdhK7scq8Bt6TVwHgb6TyhmB2q86t8PRK8K9zkWPW/GTmg7gWqWYjRPt
+70MJoBboWS4QVrFgulhZNOWgCiqyn15IbXb/AQdkQ9JgsOpWf45+yISt+zM8+xdn
+gmgmIcW5AoGBAOZYyfwdZJ7fSHgpVssYJAuC4nYph+ds3on0nH23Sdj+5zbT7xc8
+XBd0z9RH6/ueIxUte0hkBCA/vmaTOqbMkY0qejdnx+0RpFKonspawCBsXWEmgv+9
+yUngvEkBxOrr/KxkG275h/Ts2XKhUvv41iM+cCN/X1kcJml0gKD/cZMXAoGBAOdo
+gKY6EMYPUFR3kr65DpFC2LofuQPyzrZqLiwaPMtAwX0vGIE3WpMYS9i+Skd5FXSo
+4XbhtwoweRUtbBbVXtkCbIYSH5+2TwHwqCR1sYf2O+3Ak7Dm+t2ZJua8lu52Pi/2
+17eDVb02x3s70X/inIgs7Ta/EYXItfWA8fFFFarbAoGBANgMfV6/vumYaaNyupqB
+CQXW/EtyBqtUnKHuUX1Elbo202zEeWNk1MeXY3sq/jahiyxI3XH3hTsuLoqNGxzp
+kLdEGR+wcn9z5Zk12MHZPozqtubxNTtjqdFmPZOIWkcxkmuq9WQwHYnnQMjYcTou
+mcKyxIE27Zpp4clVYXQgQ0l/AoGBAM/lRN8R/4vQqjyYiE+sdffu9EKSyWdQtoGP
+dxfMIp3/+VT8AKnmDwtjopCAtL7te2cpahWhxw3yanWqLxd8bg/Ma68vjRWxdd2q
+VYUb73jUsRZyEr21Zd+7/gUBnR9zP39b5oVKCrdJBYIjRZ38+uAkbKDom1dz8HU1
+eSdnB4ZNAoGBAJG3qb0yEuTkIjDTV1goYlayLhX/RUPGSgnPMObY36CgarwY/Qxm
+Ny3q0qFuKURg9j56WVWkobxTE4E9/iQopXRmRep8UG41Fwzc6vZaXLUHU7m6GtQ3
+FVl6x+aZmLPf1YLVvMWW1TrVIiX+pElV1RhEXDksYO8vT0dZjIhz81/h
+-----END RSA PRIVATE KEY-----

+ 322 - 304
server/api/cert.go

@@ -1,350 +1,368 @@
 package api
 
 import (
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/0xJacky/Nginx-UI/server/pkg/cert"
-    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-    "github.com/gin-gonic/gin"
-    "github.com/gorilla/websocket"
-    "github.com/spf13/cast"
-    "log"
-    "net/http"
-    "os"
-    "path/filepath"
-    "strings"
+	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/server/pkg/cert"
+	"github.com/0xJacky/Nginx-UI/server/pkg/cert/dns"
+	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"github.com/spf13/cast"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
 )
 
 const (
-    Success = "success"
-    Info    = "info"
-    Error   = "error"
+	Success = "success"
+	Info    = "info"
+	Error   = "error"
 )
 
 type IssueCertResponse struct {
-    Status            string `json:"status"`
-    Message           string `json:"message"`
-    SSLCertificate    string `json:"ssl_certificate,omitempty"`
-    SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
+	Status            string `json:"status"`
+	Message           string `json:"message"`
+	SSLCertificate    string `json:"ssl_certificate,omitempty"`
+	SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
 }
 
 func handleIssueCertLogChan(conn *websocket.Conn, logChan chan string) {
-    defer func() {
-        if err := recover(); err != nil {
-            log.Println("api.handleIssueCertLogChan recover", err)
-        }
-    }()
+	defer func() {
+		if err := recover(); err != nil {
+			log.Println("api.handleIssueCertLogChan recover", err)
+		}
+	}()
 
-    for logString := range logChan {
+	for logString := range logChan {
 
-        err := conn.WriteJSON(IssueCertResponse{
-            Status:  Info,
-            Message: logString,
-        })
+		err := conn.WriteJSON(IssueCertResponse{
+			Status:  Info,
+			Message: logString,
+		})
 
-        if err != nil {
-            log.Println("Error handleIssueCertLogChan", err)
-            return
-        }
+		if err != nil {
+			log.Println("Error handleIssueCertLogChan", err)
+			return
+		}
 
-    }
+	}
 }
 
 func IssueCert(c *gin.Context) {
-    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("defer websocket close err", err)
-        }
-    }(ws)
-
-    // read
-    var buffer struct {
-        ServerName []string `json:"server_name"`
-    }
-
-    err = ws.ReadJSON(&buffer)
-
-    if err != nil {
-        log.Println(err)
-        return
-    }
-
-    certModel, err := model.FirstOrCreateCert(c.Param("name"))
-
-    if err != nil {
-        log.Println(err)
-    }
-
-    logChan := make(chan string, 1)
-    errChan := make(chan error, 1)
-
-    go cert.IssueCert(buffer.ServerName, logChan, errChan)
-
-    go handleIssueCertLogChan(ws, logChan)
-
-    // block, until errChan closes
-    for err = range errChan {
-        errLog := &cert.AutoCertErrorLog{}
-        errLog.SetCertModel(&certModel)
-        errLog.Exit("issue cert", err)
-
-        err = ws.WriteJSON(IssueCertResponse{
-            Status:  Error,
-            Message: err.Error(),
-        })
-
-        if err != nil {
-            log.Println("Error WriteJSON", err)
-            return
-        }
-
-        return
-    }
-
-    certDirName := strings.Join(buffer.ServerName, "_")
-    sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
-    sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
-
-    err = certModel.Updates(&model.Cert{
-        Domains:               buffer.ServerName,
-        SSLCertificatePath:    sslCertificatePath,
-        SSLCertificateKeyPath: sslCertificateKeyPath,
-    })
-
-    if err != nil {
-        log.Println(err)
-        err = ws.WriteJSON(IssueCertResponse{
-            Status:  Error,
-            Message: err.Error(),
-        })
-        return
-    }
-
-    certModel.ClearLog()
-
-    err = ws.WriteJSON(IssueCertResponse{
-        Status:            Success,
-        Message:           "Issued certificate successfully",
-        SSLCertificate:    sslCertificatePath,
-        SSLCertificateKey: sslCertificateKeyPath,
-    })
-
-    if err != nil {
-        log.Println(err)
-        return
-    }
+	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("defer websocket close err", err)
+		}
+	}(ws)
+
+	// read
+	buffer := &cert.ConfigPayload{}
+
+	err = ws.ReadJSON(buffer)
+
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	certModel, err := model.FirstOrCreateCert(c.Param("name"))
+
+	if err != nil {
+		log.Println(err)
+	}
+
+	logChan := make(chan string, 1)
+	errChan := make(chan error, 1)
+
+	go cert.IssueCert(buffer, logChan, errChan)
+
+	go handleIssueCertLogChan(ws, logChan)
+
+	// block, until errChan closes
+	for err = range errChan {
+		errLog := &cert.AutoCertErrorLog{}
+		errLog.SetCertModel(&certModel)
+		errLog.Exit("issue cert", err)
+
+		err = ws.WriteJSON(IssueCertResponse{
+			Status:  Error,
+			Message: err.Error(),
+		})
+
+		if err != nil {
+			log.Println("Error WriteJSON", err)
+			return
+		}
+
+		return
+	}
+
+	certDirName := strings.Join(buffer.ServerName, "_")
+	sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
+	sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
+
+	err = certModel.Updates(&model.Cert{
+		Domains:               buffer.ServerName,
+		SSLCertificatePath:    sslCertificatePath,
+		SSLCertificateKeyPath: sslCertificateKeyPath,
+	})
+
+	if err != nil {
+		log.Println(err)
+		err = ws.WriteJSON(IssueCertResponse{
+			Status:  Error,
+			Message: err.Error(),
+		})
+		return
+	}
+
+	certModel.ClearLog()
+
+	err = ws.WriteJSON(IssueCertResponse{
+		Status:            Success,
+		Message:           "Issued certificate successfully",
+		SSLCertificate:    sslCertificatePath,
+		SSLCertificateKey: sslCertificateKeyPath,
+	})
+
+	if err != nil {
+		log.Println(err)
+		return
+	}
 
 }
 
 func GetCertList(c *gin.Context) {
-    certList := model.GetCertList(c.Query("name"), c.Query("domain"))
+	certList := model.GetCertList(c.Query("name"), c.Query("domain"))
 
-    c.JSON(http.StatusOK, gin.H{
-        "data": certList,
-    })
+	c.JSON(http.StatusOK, gin.H{
+		"data": certList,
+	})
 }
 
 func getCert(c *gin.Context, certModel *model.Cert) {
-    type resp struct {
-        *model.Cert
-        SSLCertification    string           `json:"ssl_certification"`
-        SSLCertificationKey string           `json:"ssl_certification_key"`
-        CertificateInfo     *CertificateInfo `json:"certificate_info,omitempty"`
-    }
-
-    var sslCertificationBytes, sslCertificationKeyBytes []byte
-    var certificateInfo *CertificateInfo
-    if certModel.SSLCertificatePath != "" {
-        if _, err := os.Stat(certModel.SSLCertificatePath); err == nil {
-            sslCertificationBytes, _ = os.ReadFile(certModel.SSLCertificatePath)
-        }
-
-        pubKey, err := cert.GetCertInfo(certModel.SSLCertificatePath)
-
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-
-        certificateInfo = &CertificateInfo{
-            SubjectName: pubKey.Subject.CommonName,
-            IssuerName:  pubKey.Issuer.CommonName,
-            NotAfter:    pubKey.NotAfter,
-            NotBefore:   pubKey.NotBefore,
-        }
-    }
-
-    if certModel.SSLCertificateKeyPath != "" {
-        if _, err := os.Stat(certModel.SSLCertificateKeyPath); err == nil {
-            sslCertificationKeyBytes, _ = os.ReadFile(certModel.SSLCertificateKeyPath)
-        }
-    }
-
-    c.JSON(http.StatusOK, resp{
-        certModel,
-        string(sslCertificationBytes),
-        string(sslCertificationKeyBytes),
-        certificateInfo,
-    })
+	type resp struct {
+		*model.Cert
+		SSLCertification    string           `json:"ssl_certification"`
+		SSLCertificationKey string           `json:"ssl_certification_key"`
+		CertificateInfo     *CertificateInfo `json:"certificate_info,omitempty"`
+	}
+
+	var sslCertificationBytes, sslCertificationKeyBytes []byte
+	var certificateInfo *CertificateInfo
+	if certModel.SSLCertificatePath != "" {
+		if _, err := os.Stat(certModel.SSLCertificatePath); err == nil {
+			sslCertificationBytes, _ = os.ReadFile(certModel.SSLCertificatePath)
+		}
+
+		pubKey, err := cert.GetCertInfo(certModel.SSLCertificatePath)
+
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+
+		certificateInfo = &CertificateInfo{
+			SubjectName: pubKey.Subject.CommonName,
+			IssuerName:  pubKey.Issuer.CommonName,
+			NotAfter:    pubKey.NotAfter,
+			NotBefore:   pubKey.NotBefore,
+		}
+	}
+
+	if certModel.SSLCertificateKeyPath != "" {
+		if _, err := os.Stat(certModel.SSLCertificateKeyPath); err == nil {
+			sslCertificationKeyBytes, _ = os.ReadFile(certModel.SSLCertificateKeyPath)
+		}
+	}
+
+	c.JSON(http.StatusOK, resp{
+		certModel,
+		string(sslCertificationBytes),
+		string(sslCertificationKeyBytes),
+		certificateInfo,
+	})
 }
 
 func GetCert(c *gin.Context) {
-    certModel, err := model.FirstCertByID(cast.ToInt(c.Param("id")))
+	certModel, err := model.FirstCertByID(cast.ToInt(c.Param("id")))
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    getCert(c, &certModel)
+	getCert(c, &certModel)
 }
 
 func AddCert(c *gin.Context) {
-    var json struct {
-        Name                  string `json:"name"`
-        SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
-        SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
-        SSLCertification      string `json:"ssl_certification"`
-        SSLCertificationKey   string `json:"ssl_certification_key"`
-    }
-    if !BindAndValid(c, &json) {
-        return
-    }
-    certModel := &model.Cert{
-        Name:                  json.Name,
-        SSLCertificatePath:    json.SSLCertificatePath,
-        SSLCertificateKeyPath: json.SSLCertificateKeyPath,
-    }
-
-    err := certModel.Insert()
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    if json.SSLCertification != "" {
-        err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-    }
-
-    if json.SSLCertificationKey != "" {
-        err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-    }
-
-    getCert(c, certModel)
+	var json struct {
+		Name                  string `json:"name"`
+		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
+		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
+		SSLCertification      string `json:"ssl_certification"`
+		SSLCertificationKey   string `json:"ssl_certification_key"`
+	}
+	if !BindAndValid(c, &json) {
+		return
+	}
+	certModel := &model.Cert{
+		Name:                  json.Name,
+		SSLCertificatePath:    json.SSLCertificatePath,
+		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
+	}
+
+	err := certModel.Insert()
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	if json.SSLCertification != "" {
+		err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+	}
+
+	if json.SSLCertificationKey != "" {
+		err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+	}
+
+	getCert(c, certModel)
 }
 
 func ModifyCert(c *gin.Context) {
-    id := cast.ToInt(c.Param("id"))
-    certModel, err := model.FirstCertByID(id)
-
-    var json struct {
-        Name                  string `json:"name"`
-        SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
-        SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
-        SSLCertification      string `json:"ssl_certification"`
-        SSLCertificationKey   string `json:"ssl_certification_key"`
-    }
-
-    if !BindAndValid(c, &json) {
-        return
-    }
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = certModel.Updates(&model.Cert{
-        Name:                  json.Name,
-        SSLCertificatePath:    json.SSLCertificatePath,
-        SSLCertificateKeyPath: json.SSLCertificateKeyPath,
-    })
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    if json.SSLCertification != "" {
-        err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-    }
-
-    if json.SSLCertificationKey != "" {
-        err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-    }
-
-    GetCert(c)
+	id := cast.ToInt(c.Param("id"))
+	certModel, err := model.FirstCertByID(id)
+
+	var json struct {
+		Name                  string `json:"name"`
+		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
+		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
+		SSLCertification      string `json:"ssl_certification"`
+		SSLCertificationKey   string `json:"ssl_certification_key"`
+	}
+
+	if !BindAndValid(c, &json) {
+		return
+	}
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = certModel.Updates(&model.Cert{
+		Name:                  json.Name,
+		SSLCertificatePath:    json.SSLCertificatePath,
+		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
+	})
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = os.MkdirAll(filepath.Dir(json.SSLCertificatePath), 0644)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = os.MkdirAll(filepath.Dir(json.SSLCertificateKeyPath), 0644)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	if json.SSLCertification != "" {
+		err = os.WriteFile(json.SSLCertificatePath, []byte(json.SSLCertification), 0644)
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+	}
+
+	if json.SSLCertificationKey != "" {
+		err = os.WriteFile(json.SSLCertificateKeyPath, []byte(json.SSLCertificationKey), 0644)
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+	}
+
+	GetCert(c)
 }
 
 func RemoveCert(c *gin.Context) {
-    id := cast.ToInt(c.Param("id"))
-    certModel, err := model.FirstCertByID(id)
+	id := cast.ToInt(c.Param("id"))
+	certModel, err := model.FirstCertByID(id)
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    err = certModel.Remove()
+	err = certModel.Remove()
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    c.JSON(http.StatusNoContent, nil)
+	c.JSON(http.StatusNoContent, nil)
+}
+
+func GetDNSProvidersList(c *gin.Context) {
+	c.JSON(http.StatusOK, dns.GetProvidersList())
+}
+
+func GetDNSProvider(c *gin.Context) {
+	code := c.Param("code")
+
+	provider, ok := dns.GetProvider(code)
+
+	if !ok {
+		c.JSON(http.StatusNotFound, gin.H{
+			"message": "provider not found",
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, provider)
 }

+ 130 - 0
server/api/dns_credential.go

@@ -0,0 +1,130 @@
+package api
+
+import (
+    "github.com/0xJacky/Nginx-UI/server/model"
+    "github.com/0xJacky/Nginx-UI/server/pkg/cert/dns"
+    "github.com/0xJacky/Nginx-UI/server/query"
+    "github.com/gin-gonic/gin"
+    "github.com/spf13/cast"
+    "net/http"
+)
+
+func GetDnsCredential(c *gin.Context) {
+    id := cast.ToInt(c.Param("id"))
+
+    d := query.DnsCredential
+
+    dnsCredential, err := d.FirstByID(id)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    type apiDnsCredential struct {
+        model.Model
+        Name string `json:"name"`
+        dns.Config
+    }
+    c.JSON(http.StatusOK, apiDnsCredential{
+        Model:  dnsCredential.Model,
+        Name:   dnsCredential.Name,
+        Config: *dnsCredential.Config,
+    })
+}
+
+func GetDnsCredentialList(c *gin.Context) {
+    d := query.DnsCredential
+    provider := c.Query("provider")
+    var data []*model.DnsCredential
+    var err error
+    if provider != "" {
+        data, err = d.Where(d.Provider.Eq(provider)).Find()
+    } else {
+        data, err = d.Find()
+    }
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    c.JSON(http.StatusOK, gin.H{
+        "data": data,
+    })
+}
+
+type DnsCredentialManageJson struct {
+    Name     string `json:"name" binding:"required"`
+    Provider string `json:"provider"`
+    dns.Config
+}
+
+func AddDnsCredential(c *gin.Context) {
+    var json DnsCredentialManageJson
+    if !BindAndValid(c, &json) {
+        return
+    }
+
+    json.Config.Name = json.Provider
+    dnsCredential := model.DnsCredential{
+        Name:     json.Name,
+        Config:   &json.Config,
+        Provider: json.Provider,
+    }
+
+    d := query.DnsCredential
+
+    err := d.Create(&dnsCredential)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    c.JSON(http.StatusOK, dnsCredential)
+}
+
+func EditDnsCredential(c *gin.Context) {
+    id := cast.ToInt(c.Param("id"))
+
+    var json DnsCredentialManageJson
+    if !BindAndValid(c, &json) {
+        return
+    }
+
+    d := query.DnsCredential
+
+    dnsCredential, err := d.FirstByID(id)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    json.Config.Name = json.Provider
+    _, err = d.Where(d.ID.Eq(dnsCredential.ID)).Updates(&model.DnsCredential{
+        Name:     json.Name,
+        Config:   &json.Config,
+        Provider: json.Provider,
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+
+    GetDnsCredential(c)
+}
+
+func DeleteDnsCredential(c *gin.Context) {
+    id := cast.ToInt(c.Param("id"))
+    d := query.DnsCredential
+
+    dnsCredential, err := d.FirstByID(id)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    err = d.DeleteByID(dnsCredential.ID)
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    c.JSON(http.StatusNoContent, nil)
+}

+ 70 - 8
server/api/domain.go

@@ -94,6 +94,39 @@ func GetDomain(c *gin.Context) {
 		enabled = false
 	}
 
+	g := query.ChatGPTLog
+	chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate()
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	s := query.Site
+	site, err := s.Where(s.Path.Eq(path)).FirstOrInit()
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	if site.Advanced {
+		origContent, err := os.ReadFile(path)
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+
+		c.JSON(http.StatusOK, gin.H{
+			"advanced":         site.Advanced,
+			"enabled":          enabled,
+			"name":             name,
+			"config":           string(origContent),
+			"chatgpt_messages": chatgpt.Content,
+		})
+		return
+	}
+
 	c.Set("maybe_error", "nginx_config_syntax_error")
 	config, err := nginx.ParseNgxConfig(path)
 
@@ -130,17 +163,10 @@ func GetDomain(c *gin.Context) {
 
 	certModel, _ := model.FirstCert(name)
 
-	g := query.ChatGPTLog
-	chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate()
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
 	c.Set("maybe_error", "nginx_config_syntax_error")
 
 	c.JSON(http.StatusOK, gin.H{
+		"advanced":         site.Advanced,
 		"enabled":          enabled,
 		"name":             name,
 		"config":           config.FmtCode(),
@@ -190,6 +216,9 @@ func SaveDomain(c *gin.Context) {
 	// rename the config file if needed
 	if name != json.Name {
 		newPath := nginx.GetConfPath("sites-available", json.Name)
+		s := query.Site
+		_, err = s.Where(s.Path.Eq(path)).Update(s.Path, newPath)
+
 		// check if dst file exists, do not rename
 		if helper.FileExists(newPath) {
 			c.JSON(http.StatusNotAcceptable, gin.H{
@@ -448,3 +477,36 @@ func DuplicateSite(c *gin.Context) {
 		"dst": dst,
 	})
 }
+
+func DomainEditByAdvancedMode(c *gin.Context) {
+	var json struct {
+		Advanced bool `json:"advanced"`
+	}
+
+	if !BindAndValid(c, &json) {
+		return
+	}
+
+	name := c.Param("name")
+	path := nginx.GetConfPath("sites-available", name)
+
+	s := query.Site
+
+	_, err := s.Where(s.Path.Eq(path)).FirstOrCreate()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	_, err = s.Where(s.Path.Eq(path)).Update(s.Advanced, json.Advanced)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
+
+}

+ 55 - 54
server/api/install.go

@@ -1,75 +1,76 @@
 package api
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"github.com/0xJacky/Nginx-UI/server/query"
-	"github.com/0xJacky/Nginx-UI/server/settings"
-	"github.com/gin-gonic/gin"
-	"github.com/google/uuid"
-	"golang.org/x/crypto/bcrypt"
-	"net/http"
+    "github.com/0xJacky/Nginx-UI/server/model"
+    "github.com/0xJacky/Nginx-UI/server/query"
+    "github.com/0xJacky/Nginx-UI/server/settings"
+    "github.com/gin-gonic/gin"
+    "github.com/google/uuid"
+    "golang.org/x/crypto/bcrypt"
+    "net/http"
 )
 
 func installLockStatus() bool {
-	return "" != settings.ServerSettings.JwtSecret
+    return "" != settings.ServerSettings.JwtSecret
 }
 
 func InstallLockCheck(c *gin.Context) {
-	c.JSON(http.StatusOK, gin.H{
-		"lock": installLockStatus(),
-	})
+    c.JSON(http.StatusOK, gin.H{
+        "lock": installLockStatus(),
+    })
 }
 
 type InstallJson struct {
-	Email    string `json:"email" binding:"required,email"`
-	Username string `json:"username" binding:"required,max=255"`
-	Password string `json:"password" binding:"required,max=255"`
-	Database string `json:"database"`
+    Email    string `json:"email" binding:"required,email"`
+    Username string `json:"username" binding:"required,max=255"`
+    Password string `json:"password" binding:"required,max=255"`
+    Database string `json:"database"`
 }
 
 func InstallNginxUI(c *gin.Context) {
-	// Visit this api after installed is forbidden
-	if installLockStatus() {
-		c.JSON(http.StatusForbidden, gin.H{
-			"error": "installed",
-		})
-		return
-	}
-	var json InstallJson
-	ok := BindAndValid(c, &json)
-	if !ok {
-		return
-	}
+    // Visit this api after installed is forbidden
+    if installLockStatus() {
+        c.JSON(http.StatusForbidden, gin.H{
+            "error": "installed",
+        })
+        return
+    }
+    var json InstallJson
+    ok := BindAndValid(c, &json)
+    if !ok {
+        return
+    }
 
-	settings.ServerSettings.JwtSecret = uuid.New().String()
-	settings.ServerSettings.Email = json.Email
-	if "" != json.Database {
-		settings.ServerSettings.Database = json.Database
-	}
-	settings.ReflectFrom()
+    settings.ServerSettings.JwtSecret = uuid.New().String()
+    settings.ServerSettings.Email = json.Email
+    if "" != json.Database {
+        settings.ServerSettings.Database = json.Database
+    }
+    settings.ReflectFrom()
 
-	err := settings.Save()
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
+    err := settings.Save()
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
 
-	// Init model
-	db := model.Init()
-	query.Init(db)
+    // Init model
+    db := model.Init()
+    query.Init(db)
 
-	curd := model.NewCurd(&model.Auth{})
-	pwd, _ := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
-	err = curd.Add(&model.Auth{
-		Name:     json.Username,
-		Password: string(pwd),
-	})
+    pwd, _ := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
 
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-	c.JSON(http.StatusOK, gin.H{
-		"message": "ok",
-	})
+    u := query.Auth
+    err = u.Create(&model.Auth{
+        Name:     json.Username,
+        Password: string(pwd),
+    })
+
+    if err != nil {
+        ErrHandler(c, err)
+        return
+    }
+    c.JSON(http.StatusOK, gin.H{
+        "message": "ok",
+    })
 }

+ 3 - 0
server/api/settings.go

@@ -11,6 +11,7 @@ func GetSettings(c *gin.Context) {
 		"server":    settings.ServerSettings,
 		"nginx_log": settings.NginxLogSettings,
 		"openai":    settings.OpenAISettings,
+		"git":       settings.GitSettings,
 	})
 }
 
@@ -27,6 +28,8 @@ func SaveSettings(c *gin.Context) {
 
 	settings.Conf.Section("server").Key("Email").SetValue(json.Server.Email)
 	settings.Conf.Section("server").Key("HTTPChallengePort").SetValue(json.Server.HTTPChallengePort)
+	settings.Conf.Section("server").Key("GithubProxy").SetValue(json.Server.GithubProxy)
+
 	settings.Conf.Section("nginx_log").Key("AccessLogPath").SetValue(json.NginxLog.AccessLogPath)
 	settings.Conf.Section("nginx_log").Key("ErrorLogPath").SetValue(json.NginxLog.ErrorLogPath)
 

+ 11 - 18
server/api/template.go

@@ -3,21 +3,11 @@ package api
 import (
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
 	"github.com/0xJacky/Nginx-UI/server/service"
-	"github.com/0xJacky/Nginx-UI/server/settings"
 	"github.com/gin-gonic/gin"
 	"net/http"
-	"strings"
 )
 
 func GetTemplate(c *gin.Context) {
-	content := `proxy_set_header Host $host;
-proxy_set_header X-Real_IP $remote_addr;
-proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
-proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
-`
-	content = strings.ReplaceAll(content, "{{ HTTP01PORT }}",
-		settings.ServerSettings.HTTPChallengePort)
-
 	var ngxConfig *nginx.NgxConfig
 
 	ngxConfig = &nginx.NgxConfig{
@@ -42,12 +32,6 @@ proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
 						Directive: "index",
 					},
 				},
-				Locations: []*nginx.NgxLocation{
-					{
-						Path:    "/.well-known/acme-challenge",
-						Content: content,
-					},
-				},
 			},
 		},
 	}
@@ -90,13 +74,22 @@ func GetTemplateBlock(c *gin.Context) {
 		service.ConfigInfoItem
 		service.ConfigDetail
 	}
-	detail, err := service.ParseTemplate("block", c.Param("name"))
+	var bindData map[string]service.TVariable
+	_ = c.ShouldBindJSON(&bindData)
+	info := service.GetTemplateInfo("block", c.Param("name"))
+
+	if bindData == nil {
+		bindData = info.Variables
+	}
+
+	detail, err := service.ParseTemplate("block", c.Param("name"), bindData)
 	if err != nil {
 		ErrHandler(c, err)
 		return
 	}
+	info.Variables = bindData
 	c.JSON(http.StatusOK, resp{
-		service.GetTemplateInfo("block", c.Param("name")),
+		info,
 		detail,
 	})
 }

+ 12 - 4
server/api/upgrade.go

@@ -7,7 +7,6 @@ import (
 	"log"
 	"net/http"
 	"os"
-	"path/filepath"
 )
 
 func GetRelease(c *gin.Context) {
@@ -67,7 +66,16 @@ func PerformCoreUpgrade(c *gin.Context) {
 		"status":  "info",
 		"message": "Downloading latest release",
 	})
-	tarName, err := u.DownloadLatestRelease()
+	progressChan := make(chan float64)
+	go func() {
+		for progress := range progressChan {
+			_ = ws.WriteJSON(gin.H{
+				"status":   "progress",
+				"progress": progress,
+			})
+		}
+	}()
+	tarName, err := u.DownloadLatestRelease(progressChan)
 	if err != nil {
 		_ = ws.WriteJSON(gin.H{
 			"status":  "error",
@@ -86,7 +94,7 @@ func PerformCoreUpgrade(c *gin.Context) {
 	})
 	_ = os.Remove(u.Release.ExPath)
 	// bye, overseer will restart nginx-ui
-	err = u.PerformCoreUpgrade(filepath.Dir(u.Release.ExPath), tarName)
+	err = u.PerformCoreUpgrade(u.Release.ExPath, tarName)
 	if err != nil {
 		_ = ws.WriteJSON(gin.H{
 			"status":  "error",
@@ -96,7 +104,7 @@ func PerformCoreUpgrade(c *gin.Context) {
 			"status":  "error",
 			"message": err.Error(),
 		})
-		log.Println("[Error] PerformCoreUpgrade PerformCoreUpgrade", err)
+		log.Println("[Error] PerformCoreUpgrade", err)
 		return
 	}
 }

+ 18 - 15
server/api/user.go

@@ -2,6 +2,7 @@ package api
 
 import (
     "github.com/0xJacky/Nginx-UI/server/model"
+    "github.com/0xJacky/Nginx-UI/server/query"
     "github.com/0xJacky/Nginx-UI/server/settings"
     "github.com/gin-gonic/gin"
     "github.com/spf13/cast"
@@ -16,16 +17,17 @@ func GetUsers(c *gin.Context) {
 }
 
 func GetUser(c *gin.Context) {
-    curd := model.NewCurd(&model.Auth{})
-    id := c.Param("id")
+    id := cast.ToInt(c.Param("id"))
 
-    var user model.Auth
-    err := curd.First(&user, id)
+    u := query.Auth
+
+    user, err := u.FirstByID(id)
 
     if err != nil {
         ErrHandler(c, err)
         return
     }
+
     c.JSON(http.StatusOK, user)
 }
 
@@ -40,7 +42,8 @@ func AddUser(c *gin.Context) {
     if !ok {
         return
     }
-    curd := model.NewCurd(&model.Auth{})
+
+    u := query.Auth
 
     pwd, err := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
     if err != nil {
@@ -54,7 +57,7 @@ func AddUser(c *gin.Context) {
         Password: json.Password,
     }
 
-    err = curd.Add(&user)
+    err = u.Create(&user)
 
     if err != nil {
         ErrHandler(c, err)
@@ -80,17 +83,17 @@ func EditUser(c *gin.Context) {
     if !ok {
         return
     }
-    curd := model.NewCurd(&model.Auth{})
 
-    var user, edit model.Auth
-
-    err := curd.First(&user, userId)
+    u := query.Auth
+    user, err := u.FirstByID(userId)
 
     if err != nil {
         ErrHandler(c, err)
         return
     }
-    edit.Name = json.Name
+    edit := &model.Auth{
+        Name: json.Name,
+    }
 
     // encrypt password
     if json.Password != "" {
@@ -103,7 +106,7 @@ func EditUser(c *gin.Context) {
         edit.Password = string(pwd)
     }
 
-    err = curd.Edit(&user, &edit)
+    _, err = u.Where(u.ID.Eq(userId)).Updates(&edit)
 
     if err != nil {
         ErrHandler(c, err)
@@ -114,7 +117,7 @@ func EditUser(c *gin.Context) {
 }
 
 func DeleteUser(c *gin.Context) {
-    id := c.Param("id")
+    id := cast.ToInt(c.Param("id"))
 
     if cast.ToInt(id) == 1 {
         c.JSON(http.StatusNotAcceptable, gin.H{
@@ -123,8 +126,8 @@ func DeleteUser(c *gin.Context) {
         return
     }
 
-    curd := model.NewCurd(&model.Auth{})
-    err := curd.Delete(&model.Auth{}, "id", id)
+    u := query.Auth
+    err := u.DeleteByID(id)
     if err != nil {
         ErrHandler(c, err)
         return

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.