diff --git a/.gitignore b/.gitignore index 0f1147ad..c29d8ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ app.ini dist *.exe *.po~ +nginx-ui-server diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0f2b706c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# CGO_ENABLED=1 GOOS=linux CC=x86_64-unknown-linux-gnu-gcc CXX=x86_64-unknown-linux-gnu-g++ GOARCH=amd64 go build -o nginx-ui-server -v main.go +FROM --platform=linux/amd64 debian:buster +WORKDIR /app +COPY ./resources/demo/sources.list /etc/apt/sources.list +RUN cd /app && apt-get update -y && apt install nginx curl -y +EXPOSE 80 +COPY ./resources/demo/nginx.conf /etc/nginx/sites-available/default +COPY ./resources/demo/app.ini /app/app.ini +COPY ./resources/demo/demo.db /app/database.db +COPY ./resources/demo/install.sh /app/install.sh +COPY ./resources/demo/start.sh /app/start.sh +COPY ./nginx-ui-server /app/nginx-ui +RUN cd /app && chmod a+x start.sh +CMD ["./start.sh"] diff --git a/frontend/package.json b/frontend/package.json index 11a26b94..9b7bf91d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "nginx-ui-frontend", - "version": "1.1.0", + "version": "1.2.0", "private": true, "scripts": { "serve": "vue-cli-service serve", diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index eced5e37..66aa9948 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -4,6 +4,7 @@ import auth from './auth' import user from './user' import install from './install' import analytic from './analytic' +import settings from './settings' export default { domain, @@ -11,5 +12,6 @@ export default { auth, user, install, - analytic + analytic, + settings } diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.js new file mode 100644 index 00000000..65741dd9 --- /dev/null +++ b/frontend/src/api/settings.js @@ -0,0 +1,9 @@ +import http from '@/lib/http' + +const settings = { + get() { + return http.get('/settings') + } +} + +export default settings diff --git a/frontend/src/lib/store/settings.js b/frontend/src/lib/store/settings.js index f69f2094..d787f295 100644 --- a/frontend/src/lib/store/settings.js +++ b/frontend/src/lib/store/settings.js @@ -2,27 +2,22 @@ export const settings = { namespace: true, state: { language: '', - translations: {}, + env: {} }, mutations: { set_language(state, payload) { state.language = payload }, - update_translations(state, payload) { - state.translations = payload - } - }, - actions: { - set_language({commit}, data) { - commit('set_language', data) - }, - update_translations({commit}, data) { - commit('update_translations', data) + update_env(state, payload) { + state.env = {...payload} } }, getters: { current_language(state) { return state.language + }, + env(state) { + return state.env } } } diff --git a/frontend/src/locale/en/LC_MESSAGES/app.po b/frontend/src/locale/en/LC_MESSAGES/app.po index 6b150341..e9fe2259 100644 --- a/frontend/src/locale/en/LC_MESSAGES/app.po +++ b/frontend/src/locale/en/LC_MESSAGES/app.po @@ -44,7 +44,7 @@ msgstr "" msgid "Build with" msgstr "" -#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:24 +#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:23 msgid "Cancel" msgstr "" @@ -52,11 +52,11 @@ msgstr "" msgid "Certificate Auto-renewal" msgstr "" -#: src/views/domain/CertInfo.vue:11 src/views/domain/CertInfo.vue:2 +#: src/views/domain/CertInfo.vue:12 src/views/domain/CertInfo.vue:2 msgid "Certificate has expired" msgstr "" -#: src/views/domain/CertInfo.vue:15 src/views/domain/CertInfo.vue:2 +#: src/views/domain/CertInfo.vue:16 src/views/domain/CertInfo.vue:2 msgid "Certificate is valid" msgstr "" @@ -125,7 +125,7 @@ msgstr "" msgid "Do you want to change the template to support the TLS?" msgstr "" -#: src/views/domain/DomainEdit.vue:38 +#: src/views/domain/DomainEdit.vue:42 msgid "Edit %{n}" msgstr "" @@ -133,7 +133,7 @@ msgstr "" msgid "Edit Configuration" msgstr "" -#: src/views/domain/DomainEdit.vue:87 +#: src/views/domain/DomainEdit.vue:95 msgid "Edit Configuration File" msgstr "" @@ -182,7 +182,7 @@ msgstr "" msgid "File Not Found" msgstr "" -#: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:4 +#: src/views/domain/DomainEdit.vue:9 src/views/domain/DomainEdit.vue:3 msgid "Getting Certificate from Let's Encrypt" msgstr "" @@ -243,7 +243,7 @@ msgstr "" msgid "Logout successful" msgstr "" -#: src/views/domain/DomainEdit.vue:13 src/views/domain/DomainEdit.vue:7 +#: src/views/domain/DomainEdit.vue:12 src/views/domain/DomainEdit.vue:6 msgid "" "Make sure you have configured a reverse proxy for .well-known directory to " "HTTPChallengePort (default: 9180) before getting the certificate." @@ -344,7 +344,7 @@ msgid "Root Directory (root)" msgstr "" #: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6 -#: src/views/domain/DomainEdit.vue:25 +#: src/views/domain/DomainEdit.vue:24 msgid "Save" msgstr "" @@ -418,6 +418,10 @@ msgid "" "changed after it has been created." msgstr "" +#: src/views/domain/DomainEdit.vue:11 src/views/domain/DomainEdit.vue:5 +msgid "This feature is not available in demo." +msgstr "" + #: src/views/domain/DomainEdit.vue:134 msgid "This operation will lose the custom configuration." msgstr "" diff --git a/frontend/src/locale/zh_CN/LC_MESSAGES/app.mo b/frontend/src/locale/zh_CN/LC_MESSAGES/app.mo index da76cfee..28532a54 100644 Binary files a/frontend/src/locale/zh_CN/LC_MESSAGES/app.mo and b/frontend/src/locale/zh_CN/LC_MESSAGES/app.mo differ diff --git a/frontend/src/locale/zh_CN/LC_MESSAGES/app.po b/frontend/src/locale/zh_CN/LC_MESSAGES/app.po index 862df2fc..14b21587 100644 --- a/frontend/src/locale/zh_CN/LC_MESSAGES/app.po +++ b/frontend/src/locale/zh_CN/LC_MESSAGES/app.po @@ -46,7 +46,7 @@ msgstr "成功启用 %{name} 自动续签" msgid "Build with" msgstr "构建基于" -#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:24 +#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:23 msgid "Cancel" msgstr "取消" @@ -54,11 +54,11 @@ msgstr "取消" msgid "Certificate Auto-renewal" msgstr "证书自动续签" -#: src/views/domain/CertInfo.vue:11 src/views/domain/CertInfo.vue:2 +#: src/views/domain/CertInfo.vue:12 src/views/domain/CertInfo.vue:2 msgid "Certificate has expired" msgstr "此证书已过期" -#: src/views/domain/CertInfo.vue:15 src/views/domain/CertInfo.vue:2 +#: src/views/domain/CertInfo.vue:16 src/views/domain/CertInfo.vue:2 msgid "Certificate is valid" msgstr "此证书有效" @@ -127,7 +127,7 @@ msgstr "磁盘 IO" msgid "Do you want to change the template to support the TLS?" msgstr "你想要改变模板以支持 TLS 吗?" -#: src/views/domain/DomainEdit.vue:38 +#: src/views/domain/DomainEdit.vue:42 msgid "Edit %{n}" msgstr "编辑 %{n}" @@ -135,7 +135,7 @@ msgstr "编辑 %{n}" msgid "Edit Configuration" msgstr "编辑配置" -#: src/views/domain/DomainEdit.vue:87 +#: src/views/domain/DomainEdit.vue:95 msgid "Edit Configuration File" msgstr "编辑配置文件" @@ -184,7 +184,7 @@ msgstr "启用失败 %{msg}" msgid "File Not Found" msgstr "未找到文件" -#: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:4 +#: src/views/domain/DomainEdit.vue:9 src/views/domain/DomainEdit.vue:3 msgid "Getting Certificate from Let's Encrypt" msgstr "从 Let's Encrypt 获取证书" @@ -245,7 +245,7 @@ msgstr "登录成功" msgid "Logout successful" msgstr "登出成功" -#: src/views/domain/DomainEdit.vue:13 src/views/domain/DomainEdit.vue:7 +#: src/views/domain/DomainEdit.vue:12 src/views/domain/DomainEdit.vue:6 msgid "" "Make sure you have configured a reverse proxy for .well-known directory to " "HTTPChallengePort (default: 9180) before getting the certificate." @@ -275,7 +275,7 @@ msgstr "名称" #: src/views/dashboard/DashBoard.vue:231 msgid "Network" -msgstr "" +msgstr "网络" #: src/views/dashboard/DashBoard.vue:165 msgid "Network Total Receive" @@ -348,7 +348,7 @@ msgid "Root Directory (root)" msgstr "网站根目录 (root)" #: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6 -#: src/views/domain/DomainEdit.vue:25 +#: src/views/domain/DomainEdit.vue:24 msgid "Save" msgstr "保存" @@ -426,6 +426,10 @@ msgstr "" "只有在您的配置文件中有相应字段时,下列的配置才能生效。配置文件名称创建后不" "可修改。" +#: src/views/domain/DomainEdit.vue:11 src/views/domain/DomainEdit.vue:5 +msgid "This feature is not available in demo." +msgstr "该功能在 Demo 中不可用。" + #: src/views/domain/DomainEdit.vue:134 msgid "This operation will lose the custom configuration." msgstr "该操作将会丢失自定义配置。" diff --git a/frontend/src/locale/zh_TW/LC_MESSAGES/app.po b/frontend/src/locale/zh_TW/LC_MESSAGES/app.po index 9f10a7b5..e7ed627a 100644 --- a/frontend/src/locale/zh_TW/LC_MESSAGES/app.po +++ b/frontend/src/locale/zh_TW/LC_MESSAGES/app.po @@ -47,7 +47,7 @@ msgstr "成功啟用 %{name} 自動續簽" msgid "Build with" msgstr "構建基於" -#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:24 +#: src/views/config/ConfigEdit.vue:5 src/views/domain/DomainEdit.vue:23 msgid "Cancel" msgstr "取消" @@ -55,11 +55,11 @@ msgstr "取消" msgid "Certificate Auto-renewal" msgstr "證書自動續簽" -#: src/views/domain/CertInfo.vue:11 src/views/domain/CertInfo.vue:2 +#: src/views/domain/CertInfo.vue:12 src/views/domain/CertInfo.vue:2 msgid "Certificate has expired" msgstr "此證書已過期" -#: src/views/domain/CertInfo.vue:15 src/views/domain/CertInfo.vue:2 +#: src/views/domain/CertInfo.vue:16 src/views/domain/CertInfo.vue:2 msgid "Certificate is valid" msgstr "此證書有效" @@ -129,7 +129,7 @@ msgstr "" msgid "Do you want to change the template to support the TLS?" msgstr "你想要改變模板以支援 TLS 嗎?" -#: src/views/domain/DomainEdit.vue:38 +#: src/views/domain/DomainEdit.vue:42 msgid "Edit %{n}" msgstr "編輯 %{n}" @@ -137,7 +137,7 @@ msgstr "編輯 %{n}" msgid "Edit Configuration" msgstr "編輯配置" -#: src/views/domain/DomainEdit.vue:87 +#: src/views/domain/DomainEdit.vue:95 msgid "Edit Configuration File" msgstr "編輯配置檔案" @@ -186,7 +186,7 @@ msgstr "啟用失敗 %{msg}" msgid "File Not Found" msgstr "未找到檔案" -#: src/views/domain/DomainEdit.vue:10 src/views/domain/DomainEdit.vue:4 +#: src/views/domain/DomainEdit.vue:9 src/views/domain/DomainEdit.vue:3 msgid "Getting Certificate from Let's Encrypt" msgstr "從 Let's Encrypt 獲取證書" @@ -247,7 +247,7 @@ msgstr "登入成功" msgid "Logout successful" msgstr "登出成功" -#: src/views/domain/DomainEdit.vue:13 src/views/domain/DomainEdit.vue:7 +#: src/views/domain/DomainEdit.vue:12 src/views/domain/DomainEdit.vue:6 #, fuzzy msgid "" "Make sure you have configured a reverse proxy for .well-known directory to " @@ -351,7 +351,7 @@ msgid "Root Directory (root)" msgstr "網站根目錄 (root)" #: src/views/config/ConfigEdit.vue:6 src/views/domain/DomainAdd.vue:6 -#: src/views/domain/DomainEdit.vue:25 +#: src/views/domain/DomainEdit.vue:24 msgid "Save" msgstr "儲存" @@ -430,6 +430,10 @@ msgstr "" "只有在您的配置檔案中有相應欄位時,下列的配置才能生效。配置檔名稱建立後不可修" "改。" +#: src/views/domain/DomainEdit.vue:11 src/views/domain/DomainEdit.vue:5 +msgid "This feature is not available in demo." +msgstr "" + #: src/views/domain/DomainEdit.vue:134 msgid "This operation will lose the custom configuration." msgstr "該操作將會丟失自定義配置。" diff --git a/frontend/src/main.js b/frontend/src/main.js index 00c6d376..6291455c 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -20,6 +20,10 @@ Vue.config.productionTip = false Vue.prototype.$routeConfig = routes Vue.prototype.$api = api +api.settings.get().then(r => { + store.commit('update_env', r) +}) + Vue.use(GetTextPlugin, { availableLanguages, defaultLanguage: store.getters.current_language, diff --git a/frontend/src/translations.json b/frontend/src/translations.json index 13bad437..c05c3ee8 100644 --- a/frontend/src/translations.json +++ b/frontend/src/translations.json @@ -1 +1 @@ -{"en":{},"zh_CN":{"404 Not Found":"404 未找到页面","About":"关于","Action":"操作","Add Site":"添加站点","Add site here first, then you can configure TLS on the domain edit page.":"在这里添加站点,完成后可进入编辑页面配置 TLS。","Auto-renewal disabled for %{name}":"成功关闭 %{name} 自动续签","Auto-renewal enabled for %{name}":"成功启用 %{name} 自动续签","Build with":"构建基于","Cancel":"取消","Certificate Auto-renewal":"证书自动续签","Certificate has expired":"此证书已过期","Certificate is valid":"此证书有效","Certificate Path (ssl_certificate)":"TLS 证书路径 (ssl_certificate)","Certificate Status":"证书状态","Configuration Name":"配置名称","Configurations":"配置","CPU Status":"CPU 状态","Created at":"创建时间","Dashboard":"仪表盘","Database (Optional, default: database)":"数据库 (可选,默认: database)","Detected version update, this page will refresh.":"检测到版本更新,页面将会刷新。","Development Mode":"开发模式","Disable auto-renewal failed for %{name}":"关闭 %{name} 自动续签失败","Disabled":"禁用","Disabled successfully":"禁用成功","Disk IO":"磁盘 IO","Do you want to change the template to support the TLS?":"你想要改变模板以支持 TLS 吗?","Edit %{n}":"编辑 %{n}","Edit Configuration":"编辑配置","Edit Configuration File":"编辑配置文件","Edit Site":"编辑站点","Email (*)":"邮箱 (*)","Enable auto-renewal failed for %{name}":"启用 %{name} 自动续签失败","Enable failed":"启用失败","Enable TLS":"启用 TLS","Enabled":"启用","Enabled successfully":"启用成功","Expiration Date: %{date}":"过期时间: %{date}","Failed to disable %{msg}":"禁用失败 %{msg}","Failed to enable %{msg}":"启用失败 %{msg}","File Not Found":"未找到文件","Getting Certificate from Let's Encrypt":"从 Let's Encrypt 获取证书","Getting the certificate, please wait...":"正在获取证书,请稍等...","Home":"首页","HTTP Listen Port":"HTTP 监听端口","HTTPS Listen Port":"HTTPS 监听端口","Index (index)":"网站首页 (index)","Install":"安装","Intermediate Certification Authorities: %{issuer}":"中级证书颁发机构: %{issuer}","Invalid E-mail!":"无效的邮箱!","Leave blank for no change":"留空表示不修改","License":{"Project":"开源许可"},"Load Averages:":"系统负载:","Login":"登录","Login successful":"登录成功","Logout successful":"登出成功","Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort (default: 9180) before getting the certificate.":"在获取签发证书前,请确保配置文件中已将 .well-known 目录反向代理到 HTTPChallengePort (默认: 9180)","Manage Configs":"配置管理","Manage Sites":"网站管理","Manage Users":"用户管理","Memory":"内存","Name":"名称","Network Total Receive":"下载流量","Network Total Send":"上传流量","Not Found":"找不到页面","Not Valid Before: %{date}":"此前无效: %{date}","Note: The server_name in the current configuration must be the domain name you need to get the certificate.":"注意:当前配置中的 server_name 必须为需要申请证书的域名。","OK":"确定","Password":"密码","Password (*)":"密码 (*)","Please input your E-mail!":"请输入您的邮箱!","Please input your password!":"请输入您的密码!","Please input your username!":"请输入您的用户名!","Private Key Path (ssl_certificate_key)":"私钥路径 (ssl_certificate_key)","Project Team":"项目团队","Reads":"读","Receive":"下载","Root Directory (root)":"网站根目录 (root)","Save":"保存","Save error %{msg}":"保存错误 %{msg}","Saved successfully":"保存成功","Send":"上传","Server error":"服务器错误","Server Info":"服务器信息","Server Names (server_name)":"网站域名 (server_name)","Sites List":"站点列表","Status":"状态","Storage":"存储","Subject Name: %{name}":"主体名称: %{name}","System message":"系统消息","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.
If you do not have a certificate before, please click \"Getting Certificate from Let's Encrypt\" first.":"系统将会每小时检测一次该域名证书,若距离上次签发已超过1个月,则将自动续签。
如果您之前没有证书,请先点击 \"从 Let's Encrypt 获取证书\"。","The filename cannot contain the following characters: %{c}":"文件名不能包含以下字符: %{c}","The following values will only take effect if you have the corresponding fields in your configuration file. The configuration filename cannot be changed after it has been created.":"只有在您的配置文件中有相应字段时,下列的配置才能生效。配置文件名称创建后不可修改。","This operation will lose the custom configuration.":"该操作将会丢失自定义配置。","Updated at":"修改时间","Uptime:":"运行时间:","Username":"用户名","Username (*)":"用户名 (*)","Writes":"写"},"zh_TW":{"404 Not Found":"404 未找到頁面","About":"關於","Action":"操作","Add Site":"新增站點","Add site here first, then you can configure TLS on the domain edit page.":"在這裡新增站點,完成後可進入編輯頁面配置 TLS。","Auto-renewal disabled for %{name}":"成功關閉 %{name} 自動續簽","Auto-renewal enabled for %{name}":"成功啟用 %{name} 自動續簽","Build with":"構建基於","Cancel":"取消","Certificate Auto-renewal":"證書自動續簽","Certificate has expired":"此證書已過期","Certificate is valid":"此證書有效","Certificate Path (ssl_certificate)":"TLS 證書路徑 (ssl_certificate)","Certificate Status":"證書狀態","Configuration Name":"配置名稱","Configurations":"配置","Created at":"建立時間","Dashboard":"儀表盤","Database (Optional, default: database)":"資料庫 (可選,預設: database)","Detected version update, this page will refresh.":"檢測到版本更新,頁面將會重新整理。","Development Mode":"開發模式","Disable auto-renewal failed for %{name}":"關閉 %{name} 自動續簽失敗","Disabled":"禁用","Disabled successfully":"禁用成功","Do you want to change the template to support the TLS?":"你想要改變模板以支援 TLS 嗎?","Edit %{n}":"編輯 %{n}","Edit Configuration":"編輯配置","Edit Configuration File":"編輯配置檔案","Edit Site":"編輯站點","Email (*)":"郵箱 (*)","Enable auto-renewal failed for %{name}":"啟用 %{name} 自動續簽失敗","Enable failed":"啟用失敗","Enable TLS":"啟用 TLS","Enabled":"啟用","Enabled successfully":"啟用成功","Expiration Date: %{date}":"過期時間: %{date}","Failed to disable %{msg}":"禁用失敗 %{msg}","Failed to enable %{msg}":"啟用失敗 %{msg}","File Not Found":"未找到檔案","Getting Certificate from Let's Encrypt":"從 Let's Encrypt 獲取證書","Getting the certificate, please wait...":"正在獲取證書,請稍等...","Home":"首頁","HTTP Listen Port":"HTTP 監聽埠","HTTPS Listen Port":"HTTPS 監聽埠","Index (index)":"網站首頁 (index)","Install":"安裝","Intermediate Certification Authorities: %{issuer}":"中級證書頒發機構: %{issuer}","Invalid E-mail!":"無效的郵箱!","Leave blank for no change":"留空表示不修改","License":{"Project":"開源許可"},"Load Averages:":"系統負載:","Login":"登入","Login successful":"登入成功","Logout successful":"登出成功","Manage Configs":"配置管理","Manage Sites":"網站管理","Manage Users":"使用者管理","Memory":"記憶體","Name":"名稱","Not Found":"找不到頁面","Not Valid Before: %{date}":"此前無效: %{date}","Note: The server_name in the current configuration must be the domain name you need to get the certificate.":"注意:當前配置中的 server_name 必須為需要申請證書的域名。","OK":"確定","Password":"密碼","Password (*)":"密碼 (*)","Please input your E-mail!":"請輸入您的郵箱!","Please input your password!":"請輸入您的密碼!","Please input your username!":"請輸入您的使用者名稱!","Private Key Path (ssl_certificate_key)":"私鑰路徑 (ssl_certificate_key)","Project Team":"專案團隊","Root Directory (root)":"網站根目錄 (root)","Save":"儲存","Save error %{msg}":"儲存錯誤 %{msg}","Saved successfully":"儲存成功","Server error":"伺服器錯誤","Server Names (server_name)":"網站域名 (server_name)","Sites List":"站點列表","Status":"狀態","Storage":"儲存","Subject Name: %{name}":"主體名稱: %{name}","System message":"系統訊息","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.
If you do not have a certificate before, please click \"Getting Certificate from Let's Encrypt\" first.":"系統將會每小時檢測一次該域名證書,若距離上次簽發已超過1個月,則將自動續簽。
如果您之前沒有證書,請先點選「從 Let's Encrypt 獲取證書」。","The filename cannot contain the following characters: %{c}":"檔名不能包含以下字元: %{c}","The following values will only take effect if you have the corresponding fields in your configuration file. The configuration filename cannot be changed after it has been created.":"只有在您的配置檔案中有相應欄位時,下列的配置才能生效。配置檔名稱建立後不可修改。","This operation will lose the custom configuration.":"該操作將會丟失自定義配置。","Updated at":"修改時間","Username":"使用者名稱","Username (*)":"使用者名稱 (*)"}} \ No newline at end of file +{"en":{},"zh_CN":{"404 Not Found":"404 未找到页面","About":"关于","Action":"操作","Add Site":"添加站点","Add site here first, then you can configure TLS on the domain edit page.":"在这里添加站点,完成后可进入编辑页面配置 TLS。","Auto-renewal disabled for %{name}":"成功关闭 %{name} 自动续签","Auto-renewal enabled for %{name}":"成功启用 %{name} 自动续签","Build with":"构建基于","Cancel":"取消","Certificate Auto-renewal":"证书自动续签","Certificate has expired":"此证书已过期","Certificate is valid":"此证书有效","Certificate Path (ssl_certificate)":"TLS 证书路径 (ssl_certificate)","Certificate Status":"证书状态","Configuration Name":"配置名称","Configurations":"配置","CPU Status":"CPU 状态","Created at":"创建时间","Dashboard":"仪表盘","Database (Optional, default: database)":"数据库 (可选,默认: database)","Detected version update, this page will refresh.":"检测到版本更新,页面将会刷新。","Development Mode":"开发模式","Disable auto-renewal failed for %{name}":"关闭 %{name} 自动续签失败","Disabled":"禁用","Disabled successfully":"禁用成功","Disk IO":"磁盘 IO","Do you want to change the template to support the TLS?":"你想要改变模板以支持 TLS 吗?","Edit %{n}":"编辑 %{n}","Edit Configuration":"编辑配置","Edit Configuration File":"编辑配置文件","Edit Site":"编辑站点","Email (*)":"邮箱 (*)","Enable auto-renewal failed for %{name}":"启用 %{name} 自动续签失败","Enable failed":"启用失败","Enable TLS":"启用 TLS","Enabled":"启用","Enabled successfully":"启用成功","Expiration Date: %{date}":"过期时间: %{date}","Failed to disable %{msg}":"禁用失败 %{msg}","Failed to enable %{msg}":"启用失败 %{msg}","File Not Found":"未找到文件","Getting Certificate from Let's Encrypt":"从 Let's Encrypt 获取证书","Getting the certificate, please wait...":"正在获取证书,请稍等...","Home":"首页","HTTP Listen Port":"HTTP 监听端口","HTTPS Listen Port":"HTTPS 监听端口","Index (index)":"网站首页 (index)","Install":"安装","Intermediate Certification Authorities: %{issuer}":"中级证书颁发机构: %{issuer}","Invalid E-mail!":"无效的邮箱!","Leave blank for no change":"留空表示不修改","License":{"Project":"开源许可"},"Load Averages:":"系统负载:","Login":"登录","Login successful":"登录成功","Logout successful":"登出成功","Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort (default: 9180) before getting the certificate.":"在获取签发证书前,请确保配置文件中已将 .well-known 目录反向代理到 HTTPChallengePort (默认: 9180)","Manage Configs":"配置管理","Manage Sites":"网站管理","Manage Users":"用户管理","Memory":"内存","Name":"名称","Network":"网络","Network Total Receive":"下载流量","Network Total Send":"上传流量","Not Found":"找不到页面","Not Valid Before: %{date}":"此前无效: %{date}","Note: The server_name in the current configuration must be the domain name you need to get the certificate.":"注意:当前配置中的 server_name 必须为需要申请证书的域名。","OK":"确定","Password":"密码","Password (*)":"密码 (*)","Please input your E-mail!":"请输入您的邮箱!","Please input your password!":"请输入您的密码!","Please input your username!":"请输入您的用户名!","Private Key Path (ssl_certificate_key)":"私钥路径 (ssl_certificate_key)","Project Team":"项目团队","Reads":"读","Receive":"下载","Root Directory (root)":"网站根目录 (root)","Save":"保存","Save error %{msg}":"保存错误 %{msg}","Saved successfully":"保存成功","Send":"上传","Server error":"服务器错误","Server Info":"服务器信息","Server Names (server_name)":"网站域名 (server_name)","Sites List":"站点列表","Status":"状态","Storage":"存储","Subject Name: %{name}":"主体名称: %{name}","System message":"系统消息","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.
If you do not have a certificate before, please click \"Getting Certificate from Let's Encrypt\" first.":"系统将会每小时检测一次该域名证书,若距离上次签发已超过1个月,则将自动续签。
如果您之前没有证书,请先点击 \"从 Let's Encrypt 获取证书\"。","The filename cannot contain the following characters: %{c}":"文件名不能包含以下字符: %{c}","The following values will only take effect if you have the corresponding fields in your configuration file. The configuration filename cannot be changed after it has been created.":"只有在您的配置文件中有相应字段时,下列的配置才能生效。配置文件名称创建后不可修改。","This feature is not available in demo.":"该功能在 Demo 中不可用。","This operation will lose the custom configuration.":"该操作将会丢失自定义配置。","Updated at":"修改时间","Uptime:":"运行时间:","Username":"用户名","Username (*)":"用户名 (*)","Writes":"写"},"zh_TW":{"404 Not Found":"404 未找到頁面","About":"關於","Action":"操作","Add Site":"新增站點","Add site here first, then you can configure TLS on the domain edit page.":"在這裡新增站點,完成後可進入編輯頁面配置 TLS。","Auto-renewal disabled for %{name}":"成功關閉 %{name} 自動續簽","Auto-renewal enabled for %{name}":"成功啟用 %{name} 自動續簽","Build with":"構建基於","Cancel":"取消","Certificate Auto-renewal":"證書自動續簽","Certificate has expired":"此證書已過期","Certificate is valid":"此證書有效","Certificate Path (ssl_certificate)":"TLS 證書路徑 (ssl_certificate)","Certificate Status":"證書狀態","Configuration Name":"配置名稱","Configurations":"配置","Created at":"建立時間","Dashboard":"儀表盤","Database (Optional, default: database)":"資料庫 (可選,預設: database)","Detected version update, this page will refresh.":"檢測到版本更新,頁面將會重新整理。","Development Mode":"開發模式","Disable auto-renewal failed for %{name}":"關閉 %{name} 自動續簽失敗","Disabled":"禁用","Disabled successfully":"禁用成功","Do you want to change the template to support the TLS?":"你想要改變模板以支援 TLS 嗎?","Edit %{n}":"編輯 %{n}","Edit Configuration":"編輯配置","Edit Configuration File":"編輯配置檔案","Edit Site":"編輯站點","Email (*)":"郵箱 (*)","Enable auto-renewal failed for %{name}":"啟用 %{name} 自動續簽失敗","Enable failed":"啟用失敗","Enable TLS":"啟用 TLS","Enabled":"啟用","Enabled successfully":"啟用成功","Expiration Date: %{date}":"過期時間: %{date}","Failed to disable %{msg}":"禁用失敗 %{msg}","Failed to enable %{msg}":"啟用失敗 %{msg}","File Not Found":"未找到檔案","Getting Certificate from Let's Encrypt":"從 Let's Encrypt 獲取證書","Getting the certificate, please wait...":"正在獲取證書,請稍等...","Home":"首頁","HTTP Listen Port":"HTTP 監聽埠","HTTPS Listen Port":"HTTPS 監聽埠","Index (index)":"網站首頁 (index)","Install":"安裝","Intermediate Certification Authorities: %{issuer}":"中級證書頒發機構: %{issuer}","Invalid E-mail!":"無效的郵箱!","Leave blank for no change":"留空表示不修改","License":{"Project":"開源許可"},"Load Averages:":"系統負載:","Login":"登入","Login successful":"登入成功","Logout successful":"登出成功","Manage Configs":"配置管理","Manage Sites":"網站管理","Manage Users":"使用者管理","Memory":"記憶體","Name":"名稱","Not Found":"找不到頁面","Not Valid Before: %{date}":"此前無效: %{date}","Note: The server_name in the current configuration must be the domain name you need to get the certificate.":"注意:當前配置中的 server_name 必須為需要申請證書的域名。","OK":"確定","Password":"密碼","Password (*)":"密碼 (*)","Please input your E-mail!":"請輸入您的郵箱!","Please input your password!":"請輸入您的密碼!","Please input your username!":"請輸入您的使用者名稱!","Private Key Path (ssl_certificate_key)":"私鑰路徑 (ssl_certificate_key)","Project Team":"專案團隊","Root Directory (root)":"網站根目錄 (root)","Save":"儲存","Save error %{msg}":"儲存錯誤 %{msg}","Saved successfully":"儲存成功","Server error":"伺服器錯誤","Server Names (server_name)":"網站域名 (server_name)","Sites List":"站點列表","Status":"狀態","Storage":"儲存","Subject Name: %{name}":"主體名稱: %{name}","System message":"系統訊息","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.
If you do not have a certificate before, please click \"Getting Certificate from Let's Encrypt\" first.":"系統將會每小時檢測一次該域名證書,若距離上次簽發已超過1個月,則將自動續簽。
如果您之前沒有證書,請先點選「從 Let's Encrypt 獲取證書」。","The filename cannot contain the following characters: %{c}":"檔名不能包含以下字元: %{c}","The following values will only take effect if you have the corresponding fields in your configuration file. The configuration filename cannot be changed after it has been created.":"只有在您的配置檔案中有相應欄位時,下列的配置才能生效。配置檔名稱建立後不可修改。","This operation will lose the custom configuration.":"該操作將會丟失自定義配置。","Updated at":"修改時間","Username":"使用者名稱","Username (*)":"使用者名稱 (*)"}} \ No newline at end of file diff --git a/frontend/src/views/domain/DomainEdit.vue b/frontend/src/views/domain/DomainEdit.vue index d30b2598..22439d30 100644 --- a/frontend/src/views/domain/DomainEdit.vue +++ b/frontend/src/views/domain/DomainEdit.vue @@ -6,10 +6,16 @@ @@ -238,6 +244,9 @@ export default { return [...columns] } } + }, + is_demo() { + return this.$store.getters.env.demo===true } } } diff --git a/frontend/version.json b/frontend/version.json index c78554d6..961290df 100644 --- a/frontend/version.json +++ b/frontend/version.json @@ -1 +1 @@ -{"version":"1.1.0","build_id":23,"total_build":40} \ No newline at end of file +{"version":"1.2.0","build_id":2,"total_build":42} \ No newline at end of file diff --git a/go.mod b/go.mod index 7eaee9c5..40375278 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/0xJacky/Nginx-UI go 1.17 require ( - github.com/0xJacky/pofile v0.0.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v1.0.0 github.com/gin-contrib/static v0.0.1 @@ -16,6 +15,7 @@ require ( github.com/gorilla/websocket v1.4.2 github.com/shirou/gopsutil/v3 v3.21.7 github.com/spf13/cast v1.3.1 + github.com/stretchr/testify v1.7.0 github.com/unknwon/com v1.0.1 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad gopkg.in/ini.v1 v1.62.0 @@ -26,10 +26,10 @@ require ( require ( github.com/StackExchange/wmi v1.2.1 // indirect github.com/cenkalti/backoff/v4 v4.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ole/go-ole v1.2.5 // indirect github.com/golang/protobuf v1.3.4 // indirect - github.com/itchyny/timefmt-go v0.1.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.2 // indirect github.com/json-iterator/go v1.1.9 // indirect @@ -39,7 +39,7 @@ require ( github.com/miekg/dns v1.1.40 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tklauser/go-sysconf v0.3.7 // indirect github.com/tklauser/numcpus v0.2.3 // indirect github.com/ugorji/go/codec v1.1.7 // indirect @@ -48,4 +48,5 @@ require ( golang.org/x/text v0.3.4 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index a5088e88..2840a5af 100644 --- a/go.sum +++ b/go.sum @@ -23,10 +23,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/0xJacky/pofile v0.0.0-20220219101524-60ce48e4de23 h1:nqaxj4ZYzLZzhFQeX1ZrXEBz0haUu7PypGFhQbQfDX0= -github.com/0xJacky/pofile v0.0.0-20220219101524-60ce48e4de23/go.mod h1:gSDWobvodMtvwh7FE/F999AQoCwBoXgzyGffYFX9nKA= -github.com/0xJacky/pofile v0.0.1 h1:hVRaw6ZHkajSMAuP58WMDTvGF8+OF297jpAchFK/4rQ= -github.com/0xJacky/pofile v0.0.1/go.mod h1:gSDWobvodMtvwh7FE/F999AQoCwBoXgzyGffYFX9nKA= github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= @@ -229,8 +225,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= -github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU= -github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -341,7 +335,6 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= diff --git a/resources/demo/demo.db b/resources/demo/demo.db new file mode 100644 index 00000000..0be7c5fb Binary files /dev/null and b/resources/demo/demo.db differ diff --git a/resources/demo/install.sh b/resources/demo/install.sh new file mode 100644 index 00000000..1a078ba3 --- /dev/null +++ b/resources/demo/install.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +PROXY="" +RPROXY="https://ghproxy.com/" + +MACHINE="amd64" + +# Font color +FontBlack="\033[30m"; +FontRed="\033[31m"; +FontGreen="\033[32m"; +FontYellow="\033[33m"; +FontBlue="\033[34m"; +FontPurple="\033[35m"; +FontSkyBlue="\033[36m"; +FontWhite="\033[37m"; +FontSuffix="\033[0m"; + +get_latest_version() { + # Get latest release version number + local latest_release + if ! latest_release=$(curl -x "${PROXY}" -sS -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/0xJacky/nginx-ui/releases/latest"); then + echo -e "${FontRed}error: Failed to get release list, please check your network.${FontSuffix}" + exit 1 + fi + + RELEASE_LATEST="$(echo "$latest_release" | sed 'y/,/\n/' | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')" + if [[ -z "$RELEASE_LATEST" ]]; then + if echo "$latest_release" | grep -q "API rate limit exceeded"; then + echo -e "${FontRed}error: github API rate limit exceeded${FontSuffix}" + else + echo -e "${FontRed}error: Failed to get the latest release version.${FontSuffix}" + echo "Welcome bug report: https://github.com/0xJacky/nginx-ui/issues" + fi + exit 1 + fi + RELEASE_LATEST="v${RELEASE_LATEST#v}" +} + +download_nginx_ui() { + local download_link + download_link="${RPROXY}https://github.com/0xJacky/nginx-ui/releases/download/$RELEASE_LATEST/nginx-ui-linux-$MACHINE.tar.gz" + + echo "Downloading Nginx UI archive: $download_link" + if ! curl -x "${PROXY}" -R -H 'Cache-Control: no-cache' -L -o "$TAR_FILE" "$download_link"; then + echo 'error: Download failed! Please check your network or try again.' + return 1 + fi + return 0 +} + +decompression() { + echo "$1" + if ! tar -zxf "$1" -C "$TMP_DIRECTORY"; then + echo -e "${FontRed}error: Nginx UI decompression failed.${FontSuffix}" + "rm" -r "$TMP_DIRECTORY" + echo "removed: $TMP_DIRECTORY" + exit 1 + fi + echo "info: Extract the Nginx UI package to $TMP_DIRECTORY and prepare it for installation." +} + +install_bin() { + NAME="nginx-ui" + install -m 755 "${TMP_DIRECTORY}/$NAME" "/app/$NAME" +} + +main() { + # Important Variables + TMP_DIRECTORY="$(mktemp -d)" + TAR_FILE="${TMP_DIRECTORY}/nginx-ui-linux-$MACHINE.tar.gz" + get_latest_version + download_nginx_ui + decompression "$TAR_FILE" + install_bin +} + +main "$@" diff --git a/resources/demo/nginx.conf b/resources/demo/nginx.conf new file mode 100644 index 00000000..4b625a6d --- /dev/null +++ b/resources/demo/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name localhost; # your domain here + client_max_body_size 128M; # maximum upload size + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_pass http://127.0.0.1:9000/; + } +} diff --git a/resources/demo/sources.list b/resources/demo/sources.list new file mode 100644 index 00000000..59e2f8b8 --- /dev/null +++ b/resources/demo/sources.list @@ -0,0 +1,4 @@ +deb http://mirrors.aliyun.com/debian/ buster main non-free contrib +deb http://mirrors.aliyun.com/debian-security buster/updates main +deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib +deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib diff --git a/resources/demo/start.sh b/resources/demo/start.sh new file mode 100644 index 00000000..8d7723d2 --- /dev/null +++ b/resources/demo/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +nginx +/app/nginx-ui --config app.ini diff --git a/server/api/cert.go b/server/api/cert.go index 62427766..56bd747d 100644 --- a/server/api/cert.go +++ b/server/api/cert.go @@ -1,150 +1,163 @@ package api import ( - "encoding/json" - "github.com/0xJacky/Nginx-UI/server/tool" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "log" - "net/http" - "os" + "encoding/json" + "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/0xJacky/Nginx-UI/server/tool" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "log" + "net/http" + "os" ) func CertInfo(c *gin.Context) { - domain := c.Param("domain") + domain := c.Param("domain") - key := tool.GetCertInfo(domain) + key := tool.GetCertInfo(domain) - c.JSON(http.StatusOK, gin.H{ - "subject_name": key.Subject.CommonName, - "issuer_name": key.Issuer.CommonName, - "not_after": key.NotAfter, - "not_before": key.NotBefore, - }) + c.JSON(http.StatusOK, gin.H{ + "subject_name": key.Subject.CommonName, + "issuer_name": key.Issuer.CommonName, + "not_after": key.NotAfter, + "not_before": key.NotBefore, + }) } func IssueCert(c *gin.Context) { - domain := c.Param("domain") - var upGrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - } + domain := c.Param("domain") + var upGrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } - // upgrade http to websocket - ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - log.Println(err) - return - } + // upgrade http to websocket + ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Println(err) + return + } - defer func(ws *websocket.Conn) { - err := ws.Close() - if err != nil { - log.Println(err) - return - } - }(ws) + defer func(ws *websocket.Conn) { + err := ws.Close() + if err != nil { + log.Println(err) + return + } + }(ws) - for { - // read - mt, message, err := ws.ReadMessage() - if err != nil { - break - } - if string(message) == "go" { - var m []byte + for { + // read + mt, message, err := ws.ReadMessage() + if err != nil { + break + } + if string(message) == "go" { + var m []byte - err = tool.IssueCert(domain) - if err != nil { - m, err = json.Marshal(gin.H{ - "status": "error", - "message": err.Error(), - }) + if settings.ServerSettings.Demo { + m, _ = json.Marshal(gin.H{ + "status": "error", + "message": "this feature is not available in demo", + }) + _ = ws.WriteMessage(mt, m) + return + } - if err != nil { - log.Println(err) - return - } + err = tool.IssueCert(domain) - err = ws.WriteMessage(mt, m) + if err != nil { - if err != nil { - log.Println(err) - return - } + log.Println(err) - log.Println(err) - return - } + m, err = json.Marshal(gin.H{ + "status": "error", + "message": err.Error(), + }) - sslCertificatePath := tool.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") - _, err = os.Stat(sslCertificatePath) + if err != nil { + log.Println(err) + return + } - if err != nil { - log.Println(err) - return - } + err = ws.WriteMessage(mt, m) - log.Println("[found]", "fullchain.cer") - m, err = json.Marshal(gin.H{ - "status": "success", - "message": "[found] fullchain.cer", - }) + if err != nil { + log.Println(err) + return + } - if err != nil { - log.Println(err) - return - } + return + } - err = ws.WriteMessage(mt, m) + sslCertificatePath := tool.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") + _, err = os.Stat(sslCertificatePath) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - sslCertificateKeyPath := tool.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") - _, err = os.Stat(sslCertificateKeyPath) + log.Println("[found]", "fullchain.cer") + m, err = json.Marshal(gin.H{ + "status": "success", + "message": "[found] fullchain.cer", + }) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - log.Println("[found]", "cert key") - m, err = json.Marshal(gin.H{ - "status": "success", - "message": "[found] cert key", - }) + err = ws.WriteMessage(mt, m) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + return + } - err = ws.WriteMessage(mt, m) + sslCertificateKeyPath := tool.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") + _, err = os.Stat(sslCertificateKeyPath) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + return + } - log.Println("申请成功") - m, err = json.Marshal(gin.H{ - "status": "success", - "message": "申请成功", - "ssl_certificate": sslCertificatePath, - "ssl_certificate_key": sslCertificateKeyPath, - }) + log.Println("[found]", "cert key") + m, err = json.Marshal(gin.H{ + "status": "success", + "message": "[found] cert key", + }) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + } - err = ws.WriteMessage(mt, m) + err = ws.WriteMessage(mt, m) - if err != nil { - log.Println(err) - } - } - } + if err != nil { + log.Println(err) + } + + log.Println("申请成功") + m, err = json.Marshal(gin.H{ + "status": "success", + "message": "申请成功", + "ssl_certificate": sslCertificatePath, + "ssl_certificate_key": sslCertificateKeyPath, + }) + + if err != nil { + log.Println(err) + } + + err = ws.WriteMessage(mt, m) + + if err != nil { + log.Println(err) + } + } + } } diff --git a/server/api/template.go b/server/api/template.go index 2f3c1ec0..67990492 100644 --- a/server/api/template.go +++ b/server/api/template.go @@ -2,18 +2,16 @@ package api import ( "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/0xJacky/Nginx-UI/server/template" "github.com/gin-gonic/gin" - "io/ioutil" "net/http" "os" - "path/filepath" "strings" ) func GetTemplate(c *gin.Context) { name := c.Param("name") - path := filepath.Join("template", name) - content, err := ioutil.ReadFile(path) + content, err := template.DistFS.ReadFile(name) _content := string(content) _content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}", diff --git a/server/router/routers.go b/server/router/routers.go index d9291bf1..080b69a8 100644 --- a/server/router/routers.go +++ b/server/router/routers.go @@ -1,77 +1,85 @@ package router import ( - "bufio" - "github.com/0xJacky/Nginx-UI/server/api" - "github.com/gin-contrib/static" - "github.com/gin-gonic/gin" - "net/http" - "strings" + "bufio" + "github.com/0xJacky/Nginx-UI/server/api" + "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/gin-contrib/static" + "github.com/gin-gonic/gin" + "net/http" + "strings" ) func InitRouter() *gin.Engine { - r := gin.New() - r.Use(gin.Logger()) + r := gin.New() + r.Use(gin.Logger()) - r.Use(gin.Recovery()) + r.Use(gin.Recovery()) - r.Use(static.Serve("/", mustFS(""))) + r.Use(static.Serve("/", mustFS(""))) - r.NoRoute(func(c *gin.Context) { - accept := c.Request.Header.Get("Accept") - if strings.Contains(accept, "text/html") { - file, _ := mustFS("").Open("index.html") - stat, _ := file.Stat() - c.DataFromReader(http.StatusOK, stat.Size(), "text/html", - bufio.NewReader(file), nil) - } - }) + r.NoRoute(func(c *gin.Context) { + accept := c.Request.Header.Get("Accept") + if strings.Contains(accept, "text/html") { + file, _ := mustFS("").Open("index.html") + stat, _ := file.Stat() + c.DataFromReader(http.StatusOK, stat.Size(), "text/html", + bufio.NewReader(file), nil) + } + }) - g := r.Group("/api") - { - g.GET("install", api.InstallLockCheck) - g.POST("install", api.InstallNginxUI) + g := r.Group("/api") + { - g.POST("/login", api.Login) - g.DELETE("/logout", api.Logout) + g.GET("settings", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "demo": settings.ServerSettings.Demo, + }) + }) - g := g.Group("/", authRequired()) - { - g.GET("/analytic", api.Analytic) - g.GET("/analytic/init", api.GetAnalyticInit) + g.GET("install", api.InstallLockCheck) + g.POST("install", api.InstallNginxUI) - g.GET("/users", api.GetUsers) - g.GET("/user/:id", api.GetUser) - g.POST("/user", api.AddUser) - g.POST("/user/:id", api.EditUser) - g.DELETE("/user/:id", api.DeleteUser) + g.POST("/login", api.Login) + g.DELETE("/logout", api.Logout) - g.GET("domains", api.GetDomains) - g.GET("domain/:name", api.GetDomain) - g.POST("domain/:name", api.EditDomain) - g.POST("domain/:name/enable", api.EnableDomain) - g.POST("domain/:name/disable", api.DisableDomain) - g.DELETE("domain/:name", api.DeleteDomain) + g := g.Group("/", authRequired()) + { + g.GET("/analytic", api.Analytic) + g.GET("/analytic/init", api.GetAnalyticInit) - g.GET("configs", api.GetConfigs) - g.GET("config/:name", api.GetConfig) - g.POST("config", api.AddConfig) - g.POST("config/:name", api.EditConfig) + g.GET("/users", api.GetUsers) + g.GET("/user/:id", api.GetUser) + g.POST("/user", api.AddUser) + g.POST("/user/:id", api.EditUser) + g.DELETE("/user/:id", api.DeleteUser) - g.GET("backups", api.GetFileBackupList) - g.GET("backup/:id", api.GetFileBackup) + g.GET("domains", api.GetDomains) + g.GET("domain/:name", api.GetDomain) + g.POST("domain/:name", api.EditDomain) + g.POST("domain/:name/enable", api.EnableDomain) + g.POST("domain/:name/disable", api.DisableDomain) + g.DELETE("domain/:name", api.DeleteDomain) - g.GET("template/:name", api.GetTemplate) + g.GET("configs", api.GetConfigs) + g.GET("config/:name", api.GetConfig) + g.POST("config", api.AddConfig) + g.POST("config/:name", api.EditConfig) - g.GET("cert/issue/:domain", api.IssueCert) - g.GET("cert/:domain/info", api.CertInfo) + g.GET("backups", api.GetFileBackupList) + g.GET("backup/:id", api.GetFileBackup) - // 添加域名到自动续期列表 - g.POST("cert/:domain", api.AddDomainToAutoCert) - // 从自动续期列表中删除域名 - g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert) - } - } + g.GET("template/:name", api.GetTemplate) - return r + g.GET("cert/issue/:domain", api.IssueCert) + g.GET("cert/:domain/info", api.CertInfo) + + // 添加域名到自动续期列表 + g.POST("cert/:domain", api.AddDomainToAutoCert) + // 从自动续期列表中删除域名 + g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert) + } + } + + return r } diff --git a/server/settings/settings.go b/server/settings/settings.go index ba0ad6e7..b9eb368d 100644 --- a/server/settings/settings.go +++ b/server/settings/settings.go @@ -15,6 +15,7 @@ type Server struct { HTTPChallengePort string Email string Database string + Demo bool } var ServerSettings = &Server{ @@ -22,6 +23,7 @@ var ServerSettings = &Server{ RunMode: "debug", HTTPChallengePort: "9180", Database: "database", + Demo: false, } var ConfPath string diff --git a/server/template/template.go b/server/template/template.go new file mode 100644 index 00000000..e22bb476 --- /dev/null +++ b/server/template/template.go @@ -0,0 +1,6 @@ +package template + +import "embed" + +//go:embed http-conf https-conf +var DistFS embed.FS diff --git a/server/test/lego_test.go b/server/test/lego_test.go index cc3364ca..c60ef188 100644 --- a/server/test/lego_test.go +++ b/server/test/lego_test.go @@ -48,8 +48,8 @@ func TestLego(t *testing.T) { config := lego.NewConfig(&myUser) - // This CA URL is configured for a local dev instance of Boulder running in Docker in a VM. - //config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" + // This CA URL is configured for a local dev instance of Boulder running in Dockerfile in a VM. + config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" config.Certificate.KeyType = certcrypto.RSA2048 // A client facilitates communication with the CA server. diff --git a/server/tool/cert.go b/server/tool/cert.go index 9d9049db..d6931f5d 100644 --- a/server/tool/cert.go +++ b/server/tool/cert.go @@ -1,169 +1,171 @@ package tool import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "github.com/0xJacky/Nginx-UI/server/model" - "github.com/0xJacky/Nginx-UI/server/settings" - "github.com/go-acme/lego/v4/certcrypto" - "github.com/go-acme/lego/v4/certificate" - "github.com/go-acme/lego/v4/challenge/http01" - "github.com/go-acme/lego/v4/lego" - "github.com/go-acme/lego/v4/registration" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "path/filepath" - "time" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "github.com/0xJacky/Nginx-UI/server/model" + "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge/http01" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/registration" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + "time" ) // MyUser You'll need a user or account type that implements acme.User type MyUser struct { - Email string - Registration *registration.Resource - key crypto.PrivateKey + Email string + Registration *registration.Resource + key crypto.PrivateKey } func (u *MyUser) GetEmail() string { - return u.Email + return u.Email } func (u MyUser) GetRegistration() *registration.Resource { - return u.Registration + return u.Registration } func (u *MyUser) GetPrivateKey() crypto.PrivateKey { - return u.key + return u.key } func AutoCert() { - for { - log.Println("[AutoCert] Start") - autoCertList := model.GetAutoCertList() - for i := range autoCertList { - domain := autoCertList[i].Domain - key := GetCertInfo(domain) - // 未到一个月 - if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) { - continue - } - // 过一个月了 - err := IssueCert(domain) - if err != nil { - log.Println(err) - } - } - time.Sleep(1 * time.Hour) - } + for { + log.Println("[AutoCert] Start") + autoCertList := model.GetAutoCertList() + for i := range autoCertList { + domain := autoCertList[i].Domain + key := GetCertInfo(domain) + // 未到一个月 + if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) { + continue + } + // 过一个月了 + err := IssueCert(domain) + if err != nil { + log.Println(err) + } + } + time.Sleep(1 * time.Hour) + } } func GetCertInfo(domain string) (key *x509.Certificate) { - ts := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } + ts := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } - client := &http.Client{Transport: ts} + client := &http.Client{Transport: ts} - response, err := client.Get("https://" + domain) + response, err := client.Get("https://" + domain) - if err != nil { - return - } + if err != nil { + return + } - defer func(Body io.ReadCloser) { - err = Body.Close() - if err != nil { - log.Println(err) - return - } - }(response.Body) + defer func(Body io.ReadCloser) { + err = Body.Close() + if err != nil { + log.Println(err) + return + } + }(response.Body) - key = response.TLS.PeerCertificates[0] + key = response.TLS.PeerCertificates[0] - return + return } func IssueCert(domain string) error { - // Create a user. New accounts need an email and private key to start. - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - log.Println(err) - return err - } + // Create a user. New accounts need an email and private key to start. + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + log.Println(err) + return err + } - myUser := MyUser{ - Email: settings.ServerSettings.Email, - key: privateKey, - } + myUser := MyUser{ + Email: settings.ServerSettings.Email, + key: privateKey, + } - config := lego.NewConfig(&myUser) + config := lego.NewConfig(&myUser) - //config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" - config.Certificate.KeyType = certcrypto.RSA2048 + if settings.ServerSettings.Demo { + config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" + } + config.Certificate.KeyType = certcrypto.RSA2048 - // A client facilitates communication with the CA server. - client, err := lego.NewClient(config) - if err != nil { - log.Println(err) - return err - } + // A client facilitates communication with the CA server. + client, err := lego.NewClient(config) + if err != nil { + log.Println(err) + return err + } - err = client.Challenge.SetHTTP01Provider( - http01.NewProviderServer("", - settings.ServerSettings.HTTPChallengePort, - ), - ) - if err != nil { - log.Println(err) - return err - } + err = client.Challenge.SetHTTP01Provider( + http01.NewProviderServer("", + settings.ServerSettings.HTTPChallengePort, + ), + ) + if err != nil { + log.Println(err) + return err + } - // New users will need to register - reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - if err != nil { - log.Println(err) - return err - } - myUser.Registration = reg + // New users will need to register + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + if err != nil { + log.Println(err) + return err + } + myUser.Registration = reg - request := certificate.ObtainRequest{ - Domains: []string{domain}, - Bundle: true, - } - certificates, err := client.Certificate.Obtain(request) - if err != nil { - log.Println(err) - return err - } - saveDir := GetNginxConfPath("ssl/" + domain) - if _, err := os.Stat(saveDir); os.IsNotExist(err) { - err = os.Mkdir(saveDir, 0755) - if err != nil { - log.Println("fail to create", saveDir) - return err - } - } + request := certificate.ObtainRequest{ + Domains: []string{domain}, + Bundle: true, + } + certificates, err := client.Certificate.Obtain(request) + if err != nil { + log.Println(err) + return err + } + saveDir := GetNginxConfPath("ssl/" + domain) + if _, err := os.Stat(saveDir); os.IsNotExist(err) { + err = os.Mkdir(saveDir, 0755) + if err != nil { + log.Println("fail to create", saveDir) + return err + } + } - // Each certificate comes back with the cert bytes, the bytes of the client's - // private key, and a certificate URL. SAVE THESE TO DISK. - err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"), - certificates.Certificate, 0644) - if err != nil { - log.Println(err) - return err - } - err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"), - certificates.PrivateKey, 0644) - if err != nil { - log.Println(err) - return err - } + // Each certificate comes back with the cert bytes, the bytes of the client's + // private key, and a certificate URL. SAVE THESE TO DISK. + err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"), + certificates.Certificate, 0644) + if err != nil { + log.Println(err) + return err + } + err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"), + certificates.PrivateKey, 0644) + if err != nil { + log.Println(err) + return err + } - ReloadNginx() + ReloadNginx() - return nil + return nil }