mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-10-30 03:21:52 +08:00
init
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
output
|
output
|
||||||
|
pmail.db
|
||||||
@@ -37,7 +37,11 @@
|
|||||||
|
|
||||||
## 1、生成DKIM 秘钥
|
## 1、生成DKIM 秘钥
|
||||||
|
|
||||||
使用[go-msgauth](https://github.com/emersion/go-msgauth)项目的dkim-keygen工具生成公钥和私钥
|
```
|
||||||
|
go install github.com/emersion/go-msgauth/cmd/dkim-keygen@latest
|
||||||
|
dkim-keygen
|
||||||
|
```
|
||||||
|
执行后将得到`dkim.priv`文件,公钥数据会直接输出
|
||||||
|
|
||||||
生成以后将密钥放到`config/dkim`目录中
|
生成以后将密钥放到`config/dkim`目录中
|
||||||
|
|
||||||
|
|||||||
13
build.sh
Normal file → Executable file
13
build.sh
Normal file → Executable file
@@ -4,17 +4,14 @@ cd fe && yarn && yarn build
|
|||||||
# 编译后端代码
|
# 编译后端代码
|
||||||
cd ../server && cp -rf ../fe/dist http_server
|
cd ../server && cp -rf ../fe/dist http_server
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
|
||||||
go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_linux_amd64 main.go
|
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_linux_amd64 main.go
|
||||||
go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_windows_amd64 main.go
|
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_windows_amd64 main.go
|
||||||
go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_amd64 main.go
|
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_amd64 main.go
|
||||||
go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_arm64 main.go
|
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_arm64 main.go
|
||||||
|
|
||||||
# 整理输出文件
|
# 整理输出文件
|
||||||
cd ..
|
cd ..
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import HomeHeader from '@/components/HomeHeader.vue'
|
import HomeHeader from '@/components/HomeHeader.vue'
|
||||||
import HomeAside from '@/components/HomeAside.vue';
|
import HomeAside from '@/components/HomeAside.vue';
|
||||||
import { watch,ref } from 'vue'
|
import { watch, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const pageName = ref(route.name)
|
const pageName = ref(route.name)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.fullPath,
|
() => route.fullPath,
|
||||||
@@ -21,7 +21,7 @@ watch(
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<HomeHeader />
|
<HomeHeader />
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="aside" v-if="pageName != 'login'">
|
<div id="aside" v-if="pageName != 'login' && pageName != 'setup'">
|
||||||
<HomeAside />
|
<HomeAside />
|
||||||
</div>
|
</div>
|
||||||
<div id="body">
|
<div id="body">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var $http = axios.create({
|
|||||||
timeout: 6000, //设置超时
|
timeout: 6000, //设置超时
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=UTF-8;',
|
'Content-Type': 'application/json;charset=UTF-8;',
|
||||||
'Lang':lang.lang
|
'Lang': lang.lang
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ $http.interceptors.request.use((config) => {
|
|||||||
//响应拦截器
|
//响应拦截器
|
||||||
$http.interceptors.response.use((response) => {
|
$http.interceptors.response.use((response) => {
|
||||||
//响应成功
|
//响应成功
|
||||||
if(response.data.errorNo == 403){
|
if (response.data.errorNo == 403) {
|
||||||
router.replace({
|
router.replace({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
query: {
|
query: {
|
||||||
@@ -35,6 +35,15 @@ $http.interceptors.response.use((response) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
//响应成功
|
||||||
|
if (response.data.errorNo == 402) {
|
||||||
|
router.replace({
|
||||||
|
path: '/setup',
|
||||||
|
query: {
|
||||||
|
redirect: router.currentRoute.fullPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
//响应错误
|
//响应错误
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var lang = {
|
|||||||
"cc_desc": "Cc's e-mail address",
|
"cc_desc": "Cc's e-mail address",
|
||||||
"send": "send",
|
"send": "send",
|
||||||
"add_att": "Add Attachment",
|
"add_att": "Add Attachment",
|
||||||
"attachment":"Attachment",
|
"attachment": "Attachment",
|
||||||
"err_sender_must": "Sender's email prefix is required!",
|
"err_sender_must": "Sender's email prefix is required!",
|
||||||
"only_prefix": "Only the email prefix is required!",
|
"only_prefix": "Only the email prefix is required!",
|
||||||
"err_email_format": "Incorrect e-mail address, please check the e-mail format!",
|
"err_email_format": "Incorrect e-mail address, please check the e-mail format!",
|
||||||
@@ -31,8 +31,28 @@ var lang = {
|
|||||||
"succ": "Success!",
|
"succ": "Success!",
|
||||||
"err_pwd_diff": "The passwords entered twice do not match!",
|
"err_pwd_diff": "The passwords entered twice do not match!",
|
||||||
"fail": "Fail!",
|
"fail": "Fail!",
|
||||||
"settings":"Settings",
|
"settings": "Settings",
|
||||||
"security":"Security"
|
"security": "Security",
|
||||||
|
"SetDomail": "Set Domain",
|
||||||
|
"setDNS": "Set DNS",
|
||||||
|
"setSSL": "Set SSL",
|
||||||
|
"setDatabase": "Set Database",
|
||||||
|
"setOther": "Other",
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"next": "Next",
|
||||||
|
"tks_pmail": "Thanks for using Pmail",
|
||||||
|
"guid_desc": "Next, you will be guided to perform initial configuration. If you have already configured, please use your configuration file to overwrite the config folder of the running directory. If you have not configured it, please follow this guide.",
|
||||||
|
"select_db": "select database",
|
||||||
|
"db_desc": "PMail currently supports MySQL and SQLite3 databases, you can choose according to your needs.",
|
||||||
|
"type": "Type",
|
||||||
|
"db_select_ph": "please select your database",
|
||||||
|
"mysql_dsn": "MySQL DSN",
|
||||||
|
"sqlite_db_path": "Data File Path",
|
||||||
|
"domain_desc": "Set your domain infomation.",
|
||||||
|
"smtp_domain": "SMTP Domain",
|
||||||
|
"web_domain": "Web Domain",
|
||||||
|
"dns_desc": "Please add the following information to your DNS records",
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -57,7 +77,7 @@ var zhCN = {
|
|||||||
"cc_desc": "抄送人邮箱地址",
|
"cc_desc": "抄送人邮箱地址",
|
||||||
"send": "发送",
|
"send": "发送",
|
||||||
"add_att": "添加附件",
|
"add_att": "添加附件",
|
||||||
"attachment":"附件",
|
"attachment": "附件",
|
||||||
"err_sender_must": "发件人邮箱前缀必填",
|
"err_sender_must": "发件人邮箱前缀必填",
|
||||||
"only_prefix": "只需要邮箱前缀",
|
"only_prefix": "只需要邮箱前缀",
|
||||||
"err_email_format": "邮箱地址错误,请检查邮箱格式!",
|
"err_email_format": "邮箱地址错误,请检查邮箱格式!",
|
||||||
@@ -70,8 +90,27 @@ var zhCN = {
|
|||||||
"succ": "成功!",
|
"succ": "成功!",
|
||||||
"err_pwd_diff": "两次输入的密码不一致!",
|
"err_pwd_diff": "两次输入的密码不一致!",
|
||||||
"fail": "失败",
|
"fail": "失败",
|
||||||
"settings":"设置",
|
"settings": "设置",
|
||||||
"security":"安全"
|
"security": "安全",
|
||||||
|
"SetDomail": "域名设置",
|
||||||
|
"setDNS": "DNS设置",
|
||||||
|
"setSSL": "SSL设置",
|
||||||
|
"setDatabase": "数据库设置",
|
||||||
|
"setOther": "其他设置",
|
||||||
|
"welcome": "欢迎",
|
||||||
|
"next": "下一步",
|
||||||
|
"tks_pmail": "感谢使用PMail",
|
||||||
|
"guid_desc": "接下来将会指引你进行初始化配置,如果你已有配置,请使用你的配置文件覆盖运行目录的config文件夹。如果你没有配置过,请按照该指引操作。",
|
||||||
|
"select_db": "数据库选择",
|
||||||
|
"db_desc": "PMail目前支持MySQL和SQLite3两种数据库,你可根据需要选择。",
|
||||||
|
"type": "类型",
|
||||||
|
"db_select_ph": "请选择你的数据库",
|
||||||
|
"mysql_dsn": "MySQL DSN",
|
||||||
|
"sqlite_db_path": "存储位置",
|
||||||
|
"domain_desc": "设置你的域名信息。",
|
||||||
|
"smtp_domain": "SMTP域名地址",
|
||||||
|
"web_domain": "Web域名地址",
|
||||||
|
"dns_desc": "请将以下信息添加到DNS记录中"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (navigator.language) {
|
switch (navigator.language) {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import ListView from '../views/ListView.vue'
|
|||||||
import EditerView from '../views/EditerView.vue'
|
import EditerView from '../views/EditerView.vue'
|
||||||
import LoginView from '../views/LoginView.vue'
|
import LoginView from '../views/LoginView.vue'
|
||||||
import EmailDetailView from '../views/EmailDetailView.vue'
|
import EmailDetailView from '../views/EmailDetailView.vue'
|
||||||
|
import SetupView from '../views/SetupView.vue'
|
||||||
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
@@ -26,6 +29,11 @@ const router = createRouter({
|
|||||||
name: "login",
|
name: "login",
|
||||||
component: LoginView
|
component: LoginView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/setup',
|
||||||
|
name: "setup",
|
||||||
|
component: SetupView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/detail/:id',
|
path: '/detail/:id',
|
||||||
name: "detail",
|
name: "detail",
|
||||||
|
|||||||
@@ -76,6 +76,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import $http from '../http/http';
|
||||||
|
|
||||||
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { onBeforeUnmount, ref, shallowRef, reactive, onMounted } from 'vue'
|
import { onBeforeUnmount, ref, shallowRef, reactive, onMounted } from 'vue'
|
||||||
@@ -83,7 +85,6 @@ import { Close } from '@element-plus/icons-vue';
|
|||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
import { i18nChangeLanguage } from '@wangeditor/editor'
|
import { i18nChangeLanguage } from '@wangeditor/editor'
|
||||||
import $http from '../http/http';
|
|
||||||
import router from "@/router"; //根路由对象
|
import router from "@/router"; //根路由对象
|
||||||
import useGroupStore from '../stores/group'
|
import useGroupStore from '../stores/group'
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
|
|||||||
@@ -35,8 +35,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import $http from "../http/http";
|
import $http from "../http/http";
|
||||||
|
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import router from "@/router"; //根路由对象
|
import router from "@/router"; //根路由对象
|
||||||
|
|||||||
@@ -57,8 +57,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import $http from "../http/http";
|
import $http from "../http/http";
|
||||||
|
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import router from "@/router"; //根路由对象
|
import router from "@/router"; //根路由对象
|
||||||
|
|||||||
233
fe/src/views/SetupView.vue
Normal file
233
fe/src/views/SetupView.vue
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
<template>
|
||||||
|
<div id="main">
|
||||||
|
<el-steps :active="active" align-center finish-status="success" id="status">
|
||||||
|
<el-step :title="lang.welcome" />
|
||||||
|
<el-step :title="lang.setDatabase" />
|
||||||
|
<el-step :title="lang.SetDomail" />
|
||||||
|
<el-step :title="lang.setDNS" />
|
||||||
|
<el-step :title="lang.setSSL" />
|
||||||
|
<el-step :title="lang.setOther" />
|
||||||
|
</el-steps>
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="active == 0" class="ctn">
|
||||||
|
<div class="desc">
|
||||||
|
<h2>{{ lang.tks_pmail }}</h2>
|
||||||
|
<div style="margin-top: 10px;">{{ lang.guid_desc }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="active == 1" class="ctn">
|
||||||
|
<div class="desc">
|
||||||
|
<h2>{{ lang.select_db }}</h2>
|
||||||
|
<div style="margin-top: 10px;">{{ lang.db_desc }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="form" style="width: 400px;">
|
||||||
|
<el-form label-width="120px">
|
||||||
|
<el-form-item :label="lang.type">
|
||||||
|
<el-select :placeholder="lang.db_select_ph" v-model="dbSettings.type">
|
||||||
|
<el-option label="MySQL" value="mysql" />
|
||||||
|
<el-option label="SQLite3" value="sqlite" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.mysql_dsn" v-if="dbSettings.type == 'mysql'">
|
||||||
|
<el-input :rows="2" type="textarea" v-model="dbSettings.dsn"
|
||||||
|
placeholder="root:12345@tcp(127.0.0.1:3306)/pmail?parseTime=True&loc=Local"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.sqlite_db_path" v-if="dbSettings.type == 'sqlite'">
|
||||||
|
<el-input v-model="dbSettings.dsn" placeholder="./pmail.db"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="active == 2" class="ctn">
|
||||||
|
<div class="desc">
|
||||||
|
<h2>{{ lang.SetDomail }}</h2>
|
||||||
|
<!-- <div style="margin-top: 10px;">{{ lang.domain_desc }}</div> -->
|
||||||
|
</div>
|
||||||
|
<div class="form" style="width: 400px;">
|
||||||
|
<el-form label-width="120px">
|
||||||
|
|
||||||
|
<el-form-item :label="lang.smtp_domain">
|
||||||
|
<el-input placeholder="domaim.com" v-model="domainSettings.smtp_domain">
|
||||||
|
<template #prepend>smtp.</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.web_domain">
|
||||||
|
<el-input placeholder="pmail.domain.com" v-model="domainSettings.web_domain"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="active == 3" class="ctn_s">
|
||||||
|
<div class="desc">
|
||||||
|
<h2>{{ lang.setDNS }}</h2>
|
||||||
|
<div style="margin-top: 10px;">{{ lang.dns_desc }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="form" width="600px">
|
||||||
|
<el-table :data="dnsInfos" border style="width: 100%">
|
||||||
|
<el-table-column prop="host" label="HOSTNAME" width="110px" />
|
||||||
|
<el-table-column prop="type" label="TYPE" width="110px" />
|
||||||
|
<el-table-column prop="value" label="VALUE">
|
||||||
|
<template #default="scope">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<el-tooltip :content="scope.row.tips" placement="top" v-if="scope.row.tips != ''">
|
||||||
|
{{ scope.row.value }}
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else>{{ scope.row.value }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="ttl" label="TTL" width="110px" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<el-button id="next" style="margin-top: 12px" @click="next">{{ lang.next }}</el-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import $http from "../http/http";
|
||||||
|
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import router from "@/router"; //根路由对象
|
||||||
|
import lang from '../i18n/i18n';
|
||||||
|
|
||||||
|
|
||||||
|
const dbSettings = reactive({
|
||||||
|
"type": "",
|
||||||
|
"dsn": "",
|
||||||
|
"lable": ""
|
||||||
|
})
|
||||||
|
|
||||||
|
const domainSettings = reactive({
|
||||||
|
"web_domain": "",
|
||||||
|
"smtp_domain": ""
|
||||||
|
})
|
||||||
|
|
||||||
|
const active = ref(0)
|
||||||
|
|
||||||
|
const dnsInfos = ref([
|
||||||
|
{ "host": "smtp", "type": "A", "value": "YouServerIp", "prid": "NA", "ttl": "3600" }
|
||||||
|
])
|
||||||
|
|
||||||
|
const getDbConfig = () => {
|
||||||
|
$http.post("/api/setup", { "action": "get", "step": "database" }).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage.error(res.errorMsg)
|
||||||
|
} else {
|
||||||
|
dbSettings.type = res.data.db_type;
|
||||||
|
dbSettings.dsn = res.data.db_dsn;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDomainConfig = () => {
|
||||||
|
$http.post("/api/setup", { "action": "get", "step": "domain" }).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage.error(res.errorMsg)
|
||||||
|
} else {
|
||||||
|
domainSettings.web_domain = res.data.web_domain;
|
||||||
|
domainSettings.smtp_domain = res.data.smtp_domain;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDbConfig = () => {
|
||||||
|
$http.post("/api/setup", { "action": "set", "step": "database", "db_type": dbSettings.type, "db_dsn": dbSettings.dsn }).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage.error(res.errorMsg)
|
||||||
|
} else {
|
||||||
|
active.value++;
|
||||||
|
getDomainConfig();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDNSConfig = () => {
|
||||||
|
$http.post("/api/setup", { "action": "get", "step": "dns" }).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage.error(res.errorMsg)
|
||||||
|
} else {
|
||||||
|
dnsInfos.value = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDomainConfig = () => {
|
||||||
|
$http.post("/api/setup", { "action": "set", "step": "domain", "web_domain": domainSettings.web_domain, "smtp_domain": domainSettings.smtp_domain }).then((res) => {
|
||||||
|
if (res.errorNo != 0) {
|
||||||
|
ElMessage.error(res.errorMsg)
|
||||||
|
} else {
|
||||||
|
active.value++;
|
||||||
|
getDNSConfig();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
switch (active.value) {
|
||||||
|
case 0:
|
||||||
|
active.value++
|
||||||
|
getDbConfig();
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
setDbConfig();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setDomainConfig();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
active.value++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {}
|
||||||
|
|
||||||
|
.ctn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctn_s {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#next {}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
{
|
{
|
||||||
"domain": "jinnrry.com",
|
"domain": "",
|
||||||
|
"webDomain": "",
|
||||||
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
||||||
"SSLPrivateKeyPath": "config/ssl/private.key",
|
"SSLPrivateKeyPath": "config/ssl/private.key",
|
||||||
"SSLPublicKeyPath": "config/ssl/public.crt",
|
"SSLPublicKeyPath": "config/ssl/public.crt",
|
||||||
"mysqlDSN": "",
|
"dbDSN": "",
|
||||||
|
"dbType": "",
|
||||||
"weChatPushAppId": "",
|
"weChatPushAppId": "",
|
||||||
"weChatPushSecret": "",
|
"weChatPushSecret": "",
|
||||||
"weChatPushTemplateId": "",
|
"weChatPushTemplateId": "",
|
||||||
"weChatPushUserId": ""
|
"weChatPushUserId": "",
|
||||||
|
"isInit": false
|
||||||
}
|
}
|
||||||
@@ -8,25 +8,35 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var IsInit bool
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
WebDomain string `json:"webDomain"`
|
||||||
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
|
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
||||||
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
|
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
|
||||||
MysqlDSN string `json:"mysqlDSN"`
|
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
|
||||||
|
DbDSN string `json:"dbDSN"`
|
||||||
WeChatPushAppId string `json:"weChatPushAppId"`
|
DbType string `json:"dbType"`
|
||||||
WeChatPushSecret string `json:"weChatPushSecret"`
|
WeChatPushAppId string `json:"weChatPushAppId"`
|
||||||
WeChatPushTemplateId string `json:"weChatPushTemplateId"`
|
WeChatPushSecret string `json:"weChatPushSecret"`
|
||||||
WeChatPushUserId string `json:"weChatPushUserId"`
|
WeChatPushTemplateId string `json:"weChatPushTemplateId"`
|
||||||
|
WeChatPushUserId string `json:"weChatPushUserId"`
|
||||||
Tables map[string]string
|
IsInit bool `json:"isInit"`
|
||||||
TablesInitData map[string]string
|
Tables map[string]string `json:"-"`
|
||||||
|
TablesInitData map[string]string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed tables/*
|
//go:embed tables/*
|
||||||
var tableConfig embed.FS
|
var tableConfig embed.FS
|
||||||
|
|
||||||
|
const Version = "1.1.0"
|
||||||
|
|
||||||
|
const DBTypeMySQL = "mysql"
|
||||||
|
const DBTypeSQLite = "sqlite"
|
||||||
|
|
||||||
|
var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite}
|
||||||
|
|
||||||
var Instance *Config
|
var Instance *Config
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
@@ -37,25 +47,29 @@ func Init() {
|
|||||||
if len(args) >= 2 && args[len(args)-1] == "dev" {
|
if len(args) >= 2 && args[len(args)-1] == "dev" {
|
||||||
cfgData, err = os.ReadFile("./config/config.dev.json")
|
cfgData, err = os.ReadFile("./config/config.dev.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("dev环境配置文件加载失败" + err.Error())
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cfgData, err = os.ReadFile("./config/config.json")
|
cfgData, err = os.ReadFile("./config/config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("配置文件加载失败" + err.Error())
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(cfgData, &Instance)
|
err = json.Unmarshal(cfgData, &Instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("配置文件加载失败" + err.Error())
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取表设置
|
// 读取表设置
|
||||||
Instance.Tables = map[string]string{}
|
Instance.Tables = map[string]string{}
|
||||||
Instance.TablesInitData = map[string]string{}
|
Instance.TablesInitData = map[string]string{}
|
||||||
|
|
||||||
err = fs.WalkDir(tableConfig, "tables", func(path string, info fs.DirEntry, err error) error {
|
root := "tables/mysql"
|
||||||
|
if Instance.DbType == DBTypeSQLite {
|
||||||
|
root = "tables/sqlite"
|
||||||
|
}
|
||||||
|
err = fs.WalkDir(tableConfig, root, func(path string, info fs.DirEntry, err error) error {
|
||||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") {
|
if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") {
|
||||||
tableName := strings.ReplaceAll(info.Name(), ".sql", "")
|
tableName := strings.ReplaceAll(info.Name(), ".sql", "")
|
||||||
i, e := tableConfig.ReadFile(path)
|
i, e := tableConfig.ReadFile(path)
|
||||||
@@ -76,4 +90,8 @@ func Init() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Instance.Domain != "" && Instance.IsInit {
|
||||||
|
IsInit = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
{
|
{
|
||||||
"domain": "demo.com",
|
"domain": "",
|
||||||
|
"webDomain": "",
|
||||||
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
"dkimPrivateKeyPath": "config/dkim/dkim.priv",
|
||||||
"SSLPrivateKeyPath": "config/ssl/private.key",
|
"SSLPrivateKeyPath": "config/ssl/private.key",
|
||||||
"SSLPublicKeyPath": "config/ssl/public.crt",
|
"SSLPublicKeyPath": "config/ssl/public.crt",
|
||||||
"mysqlDSN": "",
|
"dbDSN": "",
|
||||||
|
"dbType": "",
|
||||||
"weChatPushAppId": "",
|
"weChatPushAppId": "",
|
||||||
"weChatPushSecret": "",
|
"weChatPushSecret": "",
|
||||||
"weChatPushTemplateId": "",
|
"weChatPushTemplateId": "",
|
||||||
"weChatPushUserId": ""
|
"weChatPushUserId": "",
|
||||||
|
"isInit": false
|
||||||
}
|
}
|
||||||
2
server/config/tables/sqlite/data/user.sql
Normal file
2
server/config/tables/sqlite/data/user.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
INSERT INTO user (account, name, password) VALUES ('admin', 'admin', 'faddb6ec2efe16116a342f5512583c48');
|
||||||
|
|
||||||
2
server/config/tables/sqlite/data/user_auth.sql
Normal file
2
server/config/tables/sqlite/data/user_auth.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
INSERT INTO user_auth (user_id, email_account) VALUES (1, '*');
|
||||||
|
|
||||||
26
server/config/tables/sqlite/email.sql
Normal file
26
server/config/tables/sqlite/email.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE table email
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
type tinyint(4) NOT NULL DEFAULT 0 ,
|
||||||
|
subject varchar(1000) NOT NULL DEFAULT '' ,
|
||||||
|
reply_to json ,
|
||||||
|
from_name varchar(50) NOT NULL DEFAULT '' ,
|
||||||
|
from_address varchar(150) NOT NULL DEFAULT '' ,
|
||||||
|
`to` json ,
|
||||||
|
bcc json ,
|
||||||
|
cc json ,
|
||||||
|
`text` text ,
|
||||||
|
html text ,
|
||||||
|
sender json ,
|
||||||
|
attachments json ,
|
||||||
|
spf_check tinyint(1) DEFAULT 0 ,
|
||||||
|
dkim_check tinyint(1) DEFAULT 0 ,
|
||||||
|
status tinyint(4) NOT NULL DEFAULT 0 ,
|
||||||
|
send_user_id int unsigned NOT NULL DEFAULT 0 ,
|
||||||
|
is_read tinyint(1) NOT NULL DEFAULT 0 ,
|
||||||
|
error text ,
|
||||||
|
cron_send_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,
|
||||||
|
send_date datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,
|
||||||
|
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,
|
||||||
|
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
8
server/config/tables/sqlite/sessions.sql
Normal file
8
server/config/tables/sqlite/sessions.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE sessions
|
||||||
|
(
|
||||||
|
token TEXT PRIMARY KEY,
|
||||||
|
data BLOB NOT NULL,
|
||||||
|
expiry REAL NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX sessions_expiry_idx ON sessions (expiry);
|
||||||
8
server/config/tables/sqlite/user.sql
Normal file
8
server/config/tables/sqlite/user.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE user
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
account varchar(20),
|
||||||
|
name varchar(10),
|
||||||
|
password char(32)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX udx_account on user (account);
|
||||||
9
server/config/tables/sqlite/user_auth.sql
Normal file
9
server/config/tables/sqlite/user_auth.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE user_auth
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id int ,
|
||||||
|
email_account varchar(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX udx_uid_ename on user_auth ( user_id, email_account);
|
||||||
|
CREATE UNIQUE INDEX udx_ename_uid on user_auth ( email_account,user_id );
|
||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/hooks"
|
"pmail/hooks"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
"pmail/mysql"
|
|
||||||
"pmail/smtp_server"
|
"pmail/smtp_server"
|
||||||
"pmail/utils/async"
|
"pmail/utils/async"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -139,7 +139,7 @@ func Send(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// 邮件落库
|
// 邮件落库
|
||||||
sql := "INSERT INTO email (type,subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,send_user_id,error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
sql := "INSERT INTO email (type,subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,send_user_id,error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
sqlRes, sqlerr := mysql.Instance.Exec(mysql.WithContext(ctx, sql),
|
sqlRes, sqlerr := db.Instance.Exec(db.WithContext(ctx, sql),
|
||||||
1,
|
1,
|
||||||
e.Subject,
|
e.Subject,
|
||||||
json2string(e.ReplyTo),
|
json2string(e.ReplyTo),
|
||||||
@@ -181,12 +181,12 @@ func Send(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg = err.Error()
|
errMsg = err.Error()
|
||||||
_, err := mysql.Instance.Exec(mysql.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, emailId)
|
_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, emailId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := mysql.Instance.Exec(mysql.WithContext(ctx, "update email set status =1 where id = ? "), emailId)
|
_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), emailId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
log.WithContext(ctx).Errorf("sql Error :%+v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/mysql"
|
|
||||||
"pmail/session"
|
"pmail/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,18 +27,18 @@ func Login(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
var retData loginRequest
|
var reqData loginRequest
|
||||||
err = json.Unmarshal(reqBytes, &retData)
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var user models.User
|
var user models.User
|
||||||
|
|
||||||
encodePwd := md5Encode(md5Encode(retData.Password+"pmail") + "pmail2023")
|
encodePwd := md5Encode(md5Encode(reqData.Password+"pmail") + "pmail2023")
|
||||||
|
|
||||||
err = mysql.Instance.Get(&user, mysql.WithContext(ctx, "select * from user where account =? and password =?"),
|
err = db.Instance.Get(&user, db.WithContext(ctx, "select * from user where account =? and password =?"),
|
||||||
retData.Account, encodePwd)
|
reqData.Account, encodePwd)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
"pmail/mysql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type modifyPasswordRequest struct {
|
type modifyPasswordRequest struct {
|
||||||
@@ -29,7 +29,7 @@ func ModifyPassword(ctx *dto.Context, w http.ResponseWriter, req *http.Request)
|
|||||||
if retData.Password != "" {
|
if retData.Password != "" {
|
||||||
encodePwd := md5Encode(md5Encode(retData.Password+"pmail") + "pmail2023")
|
encodePwd := md5Encode(md5Encode(retData.Password+"pmail") + "pmail2023")
|
||||||
|
|
||||||
_, err := mysql.Instance.Exec(mysql.WithContext(ctx, "update user set password = ? where id =?"), encodePwd, ctx.UserInfo.ID)
|
_, err := db.Instance.Exec(db.WithContext(ctx, "update user set password = ? where id =?"), encodePwd, ctx.UserInfo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, i18n.GetText(ctx.Lang, "unknowError"), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, i18n.GetText(ctx.Lang, "unknowError"), "").FPrint(w)
|
||||||
return
|
return
|
||||||
|
|||||||
92
server/controllers/setup.go
Normal file
92
server/controllers/setup.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/dto/response"
|
||||||
|
"pmail/services/setup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Proxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("proxy"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
reqBytes, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
response.NewSuccessResponse("").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqData map[string]string
|
||||||
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.NewSuccessResponse("").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "database" && reqData["action"] == "get" {
|
||||||
|
dbType, dbDSN, err := setup.GetDatabaseSettings()
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
response.NewSuccessResponse(map[string]string{
|
||||||
|
"db_type": dbType,
|
||||||
|
"db_dsn": dbDSN,
|
||||||
|
}).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "database" && reqData["action"] == "set" {
|
||||||
|
err := setup.SetDatabaseSettings(reqData["db_type"], reqData["db_dsn"])
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
response.NewSuccessResponse("Succ").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "domain" && reqData["action"] == "get" {
|
||||||
|
smtpDomain, webDomain, err := setup.GetDomainSettings()
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "")
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse(map[string]string{
|
||||||
|
"smtp_domain": smtpDomain,
|
||||||
|
"web_domain": webDomain,
|
||||||
|
}).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "domain" && reqData["action"] == "set" {
|
||||||
|
err := setup.SetDomainSettings(reqData["smtp_domain"], reqData["web_domain"])
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "")
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse("Succ").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "dns" && reqData["action"] == "get" {
|
||||||
|
dnsInfos, err := setup.GetDNSSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "")
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse(dnsInfos).FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqData["step"] == "ssl" && reqData["action"] == "get" {
|
||||||
|
err := setup.GenSSL()
|
||||||
|
if err != nil {
|
||||||
|
response.NewErrorResponse(response.ServerError, err.Error(), "")
|
||||||
|
}
|
||||||
|
response.NewSuccessResponse("").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
package mysql
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
)
|
)
|
||||||
@@ -12,15 +13,23 @@ import (
|
|||||||
var Instance *sqlx.DB
|
var Instance *sqlx.DB
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
dsn := config.Instance.MysqlDSN
|
dsn := config.Instance.DbDSN
|
||||||
var err error
|
var err error
|
||||||
Instance, err = sqlx.Open("mysql", dsn)
|
|
||||||
|
switch config.Instance.DbType {
|
||||||
|
case "mysql":
|
||||||
|
Instance, err = sqlx.Open("mysql", dsn)
|
||||||
|
case "sqlite":
|
||||||
|
Instance, err = sqlx.Open("sqlite", dsn)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
Instance.SetMaxOpenConns(100)
|
Instance.SetMaxOpenConns(100)
|
||||||
Instance.SetMaxIdleConns(10)
|
Instance.SetMaxIdleConns(10)
|
||||||
showMySQLCharacterSet()
|
//showMySQLCharacterSet()
|
||||||
checkTable()
|
checkTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +47,13 @@ type tables struct {
|
|||||||
|
|
||||||
func checkTable() {
|
func checkTable() {
|
||||||
var res []*tables
|
var res []*tables
|
||||||
err := Instance.Select(&res, "show tables")
|
|
||||||
|
var err error
|
||||||
|
if config.Instance.DbType == "sqlite" {
|
||||||
|
err = Instance.Select(&res, "select name as `Tables_in_pmail` from sqlite_master where type='table'")
|
||||||
|
} else {
|
||||||
|
err = Instance.Select(&res, "show tables")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
NeedSetup = 402
|
||||||
NeedLogin = 403
|
NeedLogin = 403
|
||||||
ParamsError = 100
|
ParamsError = 100
|
||||||
ServerError = 500
|
ServerError = 500
|
||||||
|
|||||||
@@ -4,25 +4,47 @@ go 1.20
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24
|
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24
|
||||||
|
github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24
|
||||||
github.com/alexedwards/scs/v2 v2.5.1
|
github.com/alexedwards/scs/v2 v2.5.1
|
||||||
|
github.com/emersion/go-message v0.16.0
|
||||||
github.com/emersion/go-msgauth v0.6.6
|
github.com/emersion/go-msgauth v0.6.6
|
||||||
github.com/emersion/go-smtp v0.16.0
|
github.com/emersion/go-smtp v0.16.0
|
||||||
|
github.com/go-acme/lego/v4 v4.13.3
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
github.com/mileusna/spf v0.9.5
|
github.com/mileusna/spf v0.9.5
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cast v1.5.1
|
github.com/spf13/cast v1.5.1
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
|
golang.org/x/crypto v0.10.0
|
||||||
golang.org/x/text v0.3.8
|
golang.org/x/text v0.10.0
|
||||||
|
modernc.org/sqlite v1.24.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24 => github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/emersion/go-message v0.16.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
|
||||||
github.com/miekg/dns v1.1.50 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
|
github.com/miekg/dns v1.1.55 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/mod v0.11.0 // indirect
|
||||||
|
golang.org/x/net v0.11.0 // indirect
|
||||||
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
|
golang.org/x/tools v0.10.0 // indirect
|
||||||
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
|
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||||
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
modernc.org/opt v0.1.3 // indirect
|
||||||
|
modernc.org/strutil v1.1.3 // indirect
|
||||||
|
modernc.org/token v1.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379 h1:i6LB/3lgkRDupe3owyNXtH8dtQrdaReCLeAZKrWcqAE=
|
||||||
|
github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
|
||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24 h1:1jXpX7IE/zuf9FZQJpqZNepXqW8mq6NLzplHDCA43HY=
|
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24 h1:1jXpX7IE/zuf9FZQJpqZNepXqW8mq6NLzplHDCA43HY=
|
||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24/go.mod h1:ShejCOaSJCEjCWjc7YBrgy2xd0Kp+wiyBdzTNQrAGn4=
|
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24/go.mod h1:ShejCOaSJCEjCWjc7YBrgy2xd0Kp+wiyBdzTNQrAGn4=
|
||||||
github.com/alexedwards/scs/v2 v2.5.1 h1:EhAz3Kb3OSQzD8T+Ub23fKsiuvE0GzbF5Lgn0uTwM3Y=
|
github.com/alexedwards/scs/v2 v2.5.1 h1:EhAz3Kb3OSQzD8T+Ub23fKsiuvE0GzbF5Lgn0uTwM3Y=
|
||||||
github.com/alexedwards/scs/v2 v2.5.1/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
github.com/alexedwards/scs/v2 v2.5.1/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/emersion/go-message v0.11.2/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
github.com/emersion/go-message v0.11.2/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
||||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||||
github.com/emersion/go-message v0.16.0 h1:uZLz8ClLv3V5fSFF/fFdW9jXjrZkXIpE1Fn8fKx7pO4=
|
github.com/emersion/go-message v0.16.0 h1:uZLz8ClLv3V5fSFF/fFdW9jXjrZkXIpE1Fn8fKx7pO4=
|
||||||
@@ -20,26 +26,43 @@ github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:
|
|||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||||
|
github.com/go-acme/lego/v4 v4.13.3 h1:aZ1S9FXIkCWG3Uw/rZKSD+MOuO8ZB1t6p9VCg6jJiNY=
|
||||||
|
github.com/go-acme/lego/v4 v4.13.3/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc=
|
||||||
|
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-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
|
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/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
|
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||||
|
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/mileusna/spf v0.9.5 h1:P6cmaIBwrhZaP9stXMzGOtxe+gIu65OVbZCmrAv9rgU=
|
github.com/mileusna/spf v0.9.5 h1:P6cmaIBwrhZaP9stXMzGOtxe+gIu65OVbZCmrAv9rgU=
|
||||||
github.com/mileusna/spf v0.9.5/go.mod h1:o6IdTae6QptAbLgx/+ueXSTSpkG+f1cqLemQJSew8sI=
|
github.com/mileusna/spf v0.9.5/go.mod h1:o6IdTae6QptAbLgx/+ueXSTSpkG+f1cqLemQJSew8sI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
@@ -47,26 +70,29 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
|||||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||||
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
golang.org/x/mod v0.4.2/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 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20210220032951-036812b2e83c/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 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -76,24 +102,50 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20210630005230-0f9fa26af87c/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/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.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-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=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||||
|
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
|
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||||
|
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||||
|
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||||
|
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||||
|
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||||
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||||
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||||
|
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
|
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||||
|
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||||
|
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||||
|
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||||
|
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||||
|
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||||
|
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"pmail/config"
|
||||||
"pmail/controllers"
|
"pmail/controllers"
|
||||||
"pmail/controllers/email"
|
"pmail/controllers/email"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
|
"pmail/i18n"
|
||||||
"pmail/session"
|
"pmail/session"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -28,6 +30,37 @@ var ip string
|
|||||||
|
|
||||||
const HttpPort = 80
|
const HttpPort = 80
|
||||||
|
|
||||||
|
var setupServer *http.Server
|
||||||
|
|
||||||
|
func SetupStart() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
fe, err := fs.Sub(local, "dist")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mux.Handle("/", http.FileServer(http.FS(fe)))
|
||||||
|
mux.HandleFunc("/api/", contextIterceptor(controllers.Setup))
|
||||||
|
mux.HandleFunc("/", controllers.Proxy)
|
||||||
|
|
||||||
|
setupServer := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", HttpPort),
|
||||||
|
Handler: mux,
|
||||||
|
ReadTimeout: time.Second * 60,
|
||||||
|
WriteTimeout: time.Second * 60,
|
||||||
|
}
|
||||||
|
err = setupServer.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupStop() {
|
||||||
|
err := setupServer.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Start() {
|
func Start() {
|
||||||
log.Infof("Http Server Start at :%d", HttpPort)
|
log.Infof("Http Server Start at :%d", HttpPort)
|
||||||
|
|
||||||
@@ -118,15 +151,20 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
ctx.Lang = lang
|
ctx.Lang = lang
|
||||||
|
|
||||||
user := cast.ToString(session.Instance.Get(ctx, "user"))
|
if config.IsInit {
|
||||||
if user != "" {
|
user := cast.ToString(session.Instance.Get(ctx, "user"))
|
||||||
_ = json.Unmarshal([]byte(user), &ctx.UserInfo)
|
if user != "" {
|
||||||
}
|
_ = json.Unmarshal([]byte(user), &ctx.UserInfo)
|
||||||
if ctx.UserInfo == nil || ctx.UserInfo.ID == 0 {
|
|
||||||
if r.URL.Path != "/api/ping" && r.URL.Path != "/api/login" {
|
|
||||||
response.NewErrorResponse(response.NeedLogin, "登陆已失效!", "").FPrint(w)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if ctx.UserInfo == nil || ctx.UserInfo.ID == 0 {
|
||||||
|
if r.URL.Path != "/api/ping" && r.URL.Path != "/api/login" {
|
||||||
|
response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "login_exp"), "").FPrint(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if r.URL.Path != "/api/setup" {
|
||||||
|
response.NewErrorResponse(response.NeedSetup, "", "").FPrint(w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
h(ctx, w, r)
|
h(ctx, w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ var (
|
|||||||
"succ": "成功",
|
"succ": "成功",
|
||||||
"send_fail": "发送失败",
|
"send_fail": "发送失败",
|
||||||
"att_err": "附件解码错误",
|
"att_err": "附件解码错误",
|
||||||
|
"login_exp": "登录已失效",
|
||||||
|
"ip_taps": "这是你服务器IP,确保这个IP正确",
|
||||||
}
|
}
|
||||||
en = map[string]string{
|
en = map[string]string{
|
||||||
"all_email": "All Email",
|
"all_email": "All Email",
|
||||||
@@ -22,6 +24,8 @@ var (
|
|||||||
"succ": "Success",
|
"succ": "Success",
|
||||||
"send_fail": "Send Failure",
|
"send_fail": "Send Failure",
|
||||||
"att_err": "Attachment decoding error",
|
"att_err": "Attachment decoding error",
|
||||||
|
"login_exp": "Login has expired.",
|
||||||
|
"ip_taps": "This is your server's IP, make sure it is correct.",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/parsemail"
|
"pmail/res_init"
|
||||||
"pmail/hooks"
|
|
||||||
"pmail/http_server"
|
|
||||||
"pmail/mysql"
|
|
||||||
"pmail/session"
|
|
||||||
"pmail/smtp_server"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,20 +52,10 @@ func main() {
|
|||||||
var cst, _ = time.LoadLocation("Asia/Shanghai")
|
var cst, _ = time.LoadLocation("Asia/Shanghai")
|
||||||
time.Local = cst
|
time.Local = cst
|
||||||
|
|
||||||
config.Init()
|
res_init.Init()
|
||||||
parsemail.Init()
|
|
||||||
mysql.Init()
|
|
||||||
session.Init()
|
|
||||||
hooks.Init()
|
|
||||||
|
|
||||||
// smtp server start
|
|
||||||
go smtp_server.Start()
|
|
||||||
|
|
||||||
// http server start
|
|
||||||
go http_server.Start()
|
|
||||||
|
|
||||||
log.Infoln("***************************************************")
|
log.Infoln("***************************************************")
|
||||||
log.Infoln("***\tServer Start Success Version:1.0.0")
|
log.Infof("***\tServer Start Success Version:%s\n", config.Version)
|
||||||
log.Infof("***\tGit Commit Hash: %s ", gitHash)
|
log.Infof("***\tGit Commit Hash: %s ", gitHash)
|
||||||
log.Infof("***\tBuild TimeStamp: %s ", buildTime)
|
log.Infof("***\tBuild TimeStamp: %s ", buildTime)
|
||||||
log.Infof("***\tBuild GoLang Version: %s ", goVersion)
|
log.Infof("***\tBuild GoLang Version: %s ", goVersion)
|
||||||
|
|||||||
54
server/res_init/init.go
Normal file
54
server/res_init/init.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package res_init
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"pmail/config"
|
||||||
|
"pmail/db"
|
||||||
|
"pmail/dto/parsemail"
|
||||||
|
"pmail/hooks"
|
||||||
|
"pmail/http_server"
|
||||||
|
"pmail/session"
|
||||||
|
"pmail/smtp_server"
|
||||||
|
"pmail/utils/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
config.Init()
|
||||||
|
|
||||||
|
if config.IsInit {
|
||||||
|
parsemail.Init()
|
||||||
|
db.Init()
|
||||||
|
session.Init()
|
||||||
|
hooks.Init()
|
||||||
|
// smtp server start
|
||||||
|
go smtp_server.Start()
|
||||||
|
// http server start
|
||||||
|
go http_server.Start()
|
||||||
|
} else {
|
||||||
|
dirInit()
|
||||||
|
go http_server.SetupStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirInit() {
|
||||||
|
if !file.PathExist("./config") {
|
||||||
|
err := os.MkdirAll("./config", 0744)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !file.PathExist("./config/dkim") {
|
||||||
|
err := os.MkdirAll("./config/dkim", 0744)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !file.PathExist("./config/ssl") {
|
||||||
|
err := os.MkdirAll("./config/ssl", 0744)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ package attachments
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/mysql"
|
|
||||||
"pmail/services/auth"
|
"pmail/services/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ func GetAttachments(ctx *dto.Context, emailId int, cid string) (string, []byte)
|
|||||||
|
|
||||||
// 获取邮件内容
|
// 获取邮件内容
|
||||||
var email models.Email
|
var email models.Email
|
||||||
err := mysql.Instance.Get(&email, mysql.WithContext(ctx, "select * from email where id = ?"), emailId)
|
err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), emailId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -39,7 +39,7 @@ func GetAttachmentsByIndex(ctx *dto.Context, emailId int, index int) (string, []
|
|||||||
|
|
||||||
// 获取邮件内容
|
// 获取邮件内容
|
||||||
var email models.Email
|
var email models.Email
|
||||||
err := mysql.Instance.Get(&email, mysql.WithContext(ctx, "select * from email where id = ?"), emailId)
|
err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), emailId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/mysql"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +19,7 @@ import (
|
|||||||
func HasAuth(ctx *dto.Context, email *models.Email) bool {
|
func HasAuth(ctx *dto.Context, email *models.Email) bool {
|
||||||
// 获取当前用户的auth
|
// 获取当前用户的auth
|
||||||
var auth []models.UserAuth
|
var auth []models.UserAuth
|
||||||
err := mysql.Instance.Select(&auth, mysql.WithContext(ctx, "select * from user_auth where user_id = ?"), ctx.UserInfo.ID)
|
err := db.Instance.Select(&auth, db.WithContext(ctx, "select * from user_auth where user_id = ?"), ctx.UserInfo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
return false
|
return false
|
||||||
@@ -31,3 +38,74 @@ func HasAuth(ctx *dto.Context, email *models.Email) bool {
|
|||||||
|
|
||||||
return hasAuth
|
return hasAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DkimGen() string {
|
||||||
|
privKeyStr, _ := os.ReadFile("./config/dkim/dkim.priv")
|
||||||
|
publicKeyStr, _ := os.ReadFile("./config/dkim/dkim.public")
|
||||||
|
if len(privKeyStr) > 0 && len(publicKeyStr) > 0 {
|
||||||
|
return string(publicKeyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
privKey crypto.Signer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
privKey, err = rsa.GenerateKey(rand.Reader, 3072)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to generate key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privBytes, err := x509.MarshalPKCS8PrivateKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to marshal private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile("./config/dkim/dkim.priv", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create key file: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
privBlock := pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Bytes: privBytes,
|
||||||
|
}
|
||||||
|
if err := pem.Encode(f, &privBlock); err != nil {
|
||||||
|
log.Fatalf("Failed to write key PEM block: %v", err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
log.Fatalf("Failed to close key file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubBytes []byte
|
||||||
|
|
||||||
|
switch pubKey := privKey.Public().(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
// RFC 6376 is inconsistent about whether RSA public keys should
|
||||||
|
// be formatted as RSAPublicKey or SubjectPublicKeyInfo.
|
||||||
|
// Erratum 3017 (https://www.rfc-editor.org/errata/eid3017)
|
||||||
|
// proposes allowing both. We use SubjectPublicKeyInfo for
|
||||||
|
// consistency with other implementations including opendkim,
|
||||||
|
// Gmail, and Fastmail.
|
||||||
|
pubBytes, err = x509.MarshalPKIXPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to marshal public key: %v", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := []string{
|
||||||
|
"v=DKIM1",
|
||||||
|
"k=rsa",
|
||||||
|
"p=" + base64.StdEncoding.EncodeToString(pubBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey := strings.Join(params, "; ")
|
||||||
|
|
||||||
|
os.WriteFile("./config/dkim/dkim.public", []byte(publicKey), 0666)
|
||||||
|
|
||||||
|
return publicKey
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,24 +5,24 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/mysql"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetEmailDetail(ctx *dto.Context, id int, markRead bool) (*models.Email, error) {
|
func GetEmailDetail(ctx *dto.Context, id int, markRead bool) (*models.Email, error) {
|
||||||
// 获取邮件内容
|
// 获取邮件内容
|
||||||
var email models.Email
|
var email models.Email
|
||||||
err := mysql.Instance.Get(&email, mysql.WithContext(ctx, "select * from email where id = ?"), id)
|
err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if markRead && email.IsRead == 0 {
|
if markRead && email.IsRead == 0 {
|
||||||
_, err = mysql.Instance.Exec(mysql.WithContext(ctx, "update email set is_read =1 where id =?"), email.Id)
|
_, err = db.Instance.Exec(db.WithContext(ctx, "update email set is_read =1 where id =?"), email.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
log.WithContext(ctx).Errorf("SQL error:%+v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package list
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/mysql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetEmailList(ctx *dto.Context, tag string, keyword string, offset, limit int) (emailList []*models.Email, total int) {
|
func GetEmailList(ctx *dto.Context, tag string, keyword string, offset, limit int) (emailList []*models.Email, total int) {
|
||||||
@@ -13,12 +13,12 @@ func GetEmailList(ctx *dto.Context, tag string, keyword string, offset, limit in
|
|||||||
querySQL, queryParams := genSQL(ctx, false, tag, keyword, offset, limit)
|
querySQL, queryParams := genSQL(ctx, false, tag, keyword, offset, limit)
|
||||||
counterSQL, counterParams := genSQL(ctx, true, tag, keyword, offset, limit)
|
counterSQL, counterParams := genSQL(ctx, true, tag, keyword, offset, limit)
|
||||||
|
|
||||||
err := mysql.Instance.Select(&emailList, querySQL, queryParams...)
|
err := db.Instance.Select(&emailList, querySQL, queryParams...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mysql.Instance.Get(&total, counterSQL, counterParams...)
|
err = db.Instance.Get(&total, counterSQL, counterParams...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
|
||||||
}
|
}
|
||||||
|
|||||||
76
server/services/setup/db.go
Normal file
76
server/services/setup/db.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"pmail/config"
|
||||||
|
"pmail/utils/array"
|
||||||
|
"pmail/utils/errors"
|
||||||
|
"pmail/utils/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDatabaseSettings() (string, string, error) {
|
||||||
|
configData, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return configData.DbType, configData.DbDSN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDatabaseSettings(dbType, dbDSN string) error {
|
||||||
|
configData, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !array.InArray(dbType, config.DBTypes) {
|
||||||
|
return errors.New("dbtype error")
|
||||||
|
}
|
||||||
|
|
||||||
|
configData.DbType = dbType
|
||||||
|
configData.DbDSN = dbDSN
|
||||||
|
|
||||||
|
// 检查数据库是否能正确连接 todo
|
||||||
|
|
||||||
|
err = writeConfig(configData)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeConfig(cfg *config.Config) error {
|
||||||
|
bytes, _ := json.Marshal(cfg)
|
||||||
|
err := os.WriteFile("./config/config.json", bytes, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfig() (*config.Config, error) {
|
||||||
|
configData := config.Config{
|
||||||
|
DkimPrivateKeyPath: "config/dkim/dkim.priv",
|
||||||
|
SSLPrivateKeyPath: "config/ssl/private.key",
|
||||||
|
SSLPublicKeyPath: "config/ssl/public.crt",
|
||||||
|
}
|
||||||
|
if !file.PathExist("./config/config.json") {
|
||||||
|
bytes, _ := json.Marshal(configData)
|
||||||
|
err := os.WriteFile("./config/config.json", bytes, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfgData, err := os.ReadFile("./config/config.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(cfgData, &configData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &configData, nil
|
||||||
|
}
|
||||||
54
server/services/setup/dns.go
Normal file
54
server/services/setup/dns.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"pmail/dto"
|
||||||
|
"pmail/i18n"
|
||||||
|
"pmail/services/auth"
|
||||||
|
"pmail/utils/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSItem struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
Tips string `json:"tips"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDNSSettings(ctx *dto.Context) ([]*DNSItem, error) {
|
||||||
|
configData, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []*DNSItem{
|
||||||
|
{Type: "A", Host: "smtp", Value: getIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
||||||
|
{Type: "MX", Host: "-", Value: fmt.Sprintf("smtp.%s", configData.Domain), TTL: 3600},
|
||||||
|
{Type: "TXT", Host: "-", Value: "v=spf1 a mx ~all", TTL: 3600},
|
||||||
|
{Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600},
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIp() string {
|
||||||
|
resp, err := http.Get("http://ip-api.com/json/?lang=zh-CN ")
|
||||||
|
if err != nil {
|
||||||
|
return "Your Server IP"
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err == nil {
|
||||||
|
var queryRes map[string]string
|
||||||
|
_ = json.Unmarshal(body, &queryRes)
|
||||||
|
|
||||||
|
return queryRes["query"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Your Server IP"
|
||||||
|
}
|
||||||
7
server/services/setup/dns_test.go
Normal file
7
server/services/setup/dns_test.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGetIp(t *testing.T) {
|
||||||
|
GetIp()
|
||||||
|
}
|
||||||
32
server/services/setup/domain.go
Normal file
32
server/services/setup/domain.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"pmail/utils/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDomainSettings() (string, string, error) {
|
||||||
|
configData, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return configData.Domain, configData.WebDomain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDomainSettings(smtpDomain, webDomain string) error {
|
||||||
|
configData, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configData.Domain = smtpDomain
|
||||||
|
configData.WebDomain = webDomain
|
||||||
|
|
||||||
|
// 检查域名是否指向本机 todo
|
||||||
|
|
||||||
|
err = writeConfig(configData)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
103
server/services/setup/ssl.go
Normal file
103
server/services/setup/ssl.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"pmail/utils/errors"
|
||||||
|
|
||||||
|
"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/challenge/tlsalpn01"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyUser struct {
|
||||||
|
Email string
|
||||||
|
Registration *registration.Resource
|
||||||
|
key crypto.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *MyUser) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
func (u MyUser) GetRegistration() *registration.Resource {
|
||||||
|
return u.Registration
|
||||||
|
}
|
||||||
|
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
return u.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenSSL() error {
|
||||||
|
|
||||||
|
configData, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(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.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
myUser := MyUser{
|
||||||
|
Email: "i@" + configData.Domain,
|
||||||
|
key: privateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := lego.NewConfig(&myUser)
|
||||||
|
|
||||||
|
config.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
|
// A client facilitates communication with the CA server.
|
||||||
|
client, err := lego.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We specify an HTTP port of 5002 and an TLS port of 5001 on all interfaces
|
||||||
|
// because we aren't running as root and can't bind a listener to port 80 and 443
|
||||||
|
// (used later when we attempt to pass challenges). Keep in mind that you still
|
||||||
|
// need to proxy challenge traffic to port 5002 and 5001.
|
||||||
|
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5001"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "443"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New users will need to register
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
myUser.Registration = reg
|
||||||
|
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: []string{
|
||||||
|
fmt.Sprintf("smtp.%s", configData.Domain),
|
||||||
|
configData.WebDomain,
|
||||||
|
},
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
certificates, err := client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(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.
|
||||||
|
fmt.Printf("%#v\n", certificates)
|
||||||
|
|
||||||
|
// ... all done.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,8 +2,10 @@ package session
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alexedwards/scs/mysqlstore"
|
"github.com/alexedwards/scs/mysqlstore"
|
||||||
|
"github.com/alexedwards/scs/sqlite3store"
|
||||||
"github.com/alexedwards/scs/v2"
|
"github.com/alexedwards/scs/v2"
|
||||||
"pmail/mysql"
|
"pmail/config"
|
||||||
|
"pmail/db"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -13,7 +15,11 @@ var Instance *scs.SessionManager
|
|||||||
func Init() {
|
func Init() {
|
||||||
Instance = scs.New()
|
Instance = scs.New()
|
||||||
Instance.Lifetime = 24 * time.Hour
|
Instance.Lifetime = 24 * time.Hour
|
||||||
// 使用mysql存储session数据,目前为了架构简单,
|
// 使用db存储session数据,目前为了架构简单,
|
||||||
// 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis
|
// 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis
|
||||||
Instance.Store = mysqlstore.New(mysql.Instance.DB)
|
if config.Instance.DbType == "mysql" {
|
||||||
|
Instance.Store = mysqlstore.New(db.Instance.DB)
|
||||||
|
} else {
|
||||||
|
Instance.Store = sqlite3store.New(db.Instance.DB)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"pmail/db"
|
||||||
"pmail/dto/parsemail"
|
"pmail/dto/parsemail"
|
||||||
"pmail/hooks"
|
"pmail/hooks"
|
||||||
"pmail/mysql"
|
|
||||||
"pmail/utils/async"
|
"pmail/utils/async"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -65,7 +65,7 @@ func (s *Session) Data(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sql := "INSERT INTO email (send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
sql := "INSERT INTO email (send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
_, err = mysql.Instance.Exec(sql,
|
_, err = db.Instance.Exec(sql,
|
||||||
email.Date,
|
email.Date,
|
||||||
email.Subject,
|
email.Subject,
|
||||||
json2string(email.ReplyTo),
|
json2string(email.ReplyTo),
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
|
"pmail/db"
|
||||||
parsemail2 "pmail/dto/parsemail"
|
parsemail2 "pmail/dto/parsemail"
|
||||||
"pmail/mysql"
|
|
||||||
"pmail/session"
|
"pmail/session"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -39,7 +39,7 @@ func testInit() {
|
|||||||
|
|
||||||
config.Init()
|
config.Init()
|
||||||
parsemail2.Init()
|
parsemail2.Init()
|
||||||
mysql.Init()
|
db.Init()
|
||||||
session.Init()
|
session.Init()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,3 +17,78 @@ func Join[T any](arg []T, str string) string {
|
|||||||
}
|
}
|
||||||
return ret.String()
|
return ret.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unique 数组去重
|
||||||
|
func Unique[T comparable](slice []T) []T {
|
||||||
|
mp := map[T]bool{}
|
||||||
|
for _, v := range slice {
|
||||||
|
mp[v] = true
|
||||||
|
}
|
||||||
|
ret := []T{}
|
||||||
|
for t, _ := range mp {
|
||||||
|
ret = append(ret, t)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge 求并集
|
||||||
|
func Merge[T any](slice1, slice2 []T) []T {
|
||||||
|
s1Len := len(slice1)
|
||||||
|
|
||||||
|
slice3 := make([]T, s1Len+len(slice2))
|
||||||
|
for i, t := range slice1 {
|
||||||
|
slice3[i] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range slice2 {
|
||||||
|
slice3[s1Len+i] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
return slice3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersect 求交集
|
||||||
|
func Intersect[T comparable](slice1, slice2 []T) []T {
|
||||||
|
m := make(map[T]bool)
|
||||||
|
nn := make([]T, 0)
|
||||||
|
for _, v := range slice1 {
|
||||||
|
m[v] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range slice2 {
|
||||||
|
exist, _ := m[v]
|
||||||
|
if exist {
|
||||||
|
nn = append(nn, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference 求差集 slice1-并集
|
||||||
|
func Difference[T comparable](slice1, slice2 []T) []T {
|
||||||
|
m := make(map[T]bool)
|
||||||
|
nn := make([]T, 0)
|
||||||
|
inter := Intersect(slice1, slice2)
|
||||||
|
for _, v := range inter {
|
||||||
|
m[v] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range slice1 {
|
||||||
|
exist, _ := m[value]
|
||||||
|
if !exist {
|
||||||
|
nn = append(nn, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
// InArray 判断元素是否在数组中
|
||||||
|
func InArray[T comparable](needle T, haystack []T) bool {
|
||||||
|
for _, t := range haystack {
|
||||||
|
if needle == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
34
server/utils/errors/error.go
Normal file
34
server/utils/errors/error.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
oe "errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(text string) error {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
return oe.New(fmt.Sprintf("%s at %s:%d", text, file, line))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wrap(err error) error {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
return fmt.Errorf("at %s:%d\n%w", file, line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapWithMsg(err error, msg string) error {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
return fmt.Errorf("%s at %s:%d\n%w", msg, file, line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unwrap(err error) error {
|
||||||
|
return oe.Unwrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Is(err, target error) bool {
|
||||||
|
return oe.Is(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func As(err error, target any) bool {
|
||||||
|
return oe.As(err, target)
|
||||||
|
}
|
||||||
29
server/utils/errors/error_test.go
Normal file
29
server/utils/errors/error_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
err := New("err")
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarp(t *testing.T) {
|
||||||
|
err := New("err1")
|
||||||
|
err = Wrap(err)
|
||||||
|
err = Wrap(err)
|
||||||
|
err = Wrap(err)
|
||||||
|
err = Wrap(err)
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarpWithMsg(t *testing.T) {
|
||||||
|
err := New("err1")
|
||||||
|
err = Wrap(err)
|
||||||
|
err = Wrap(err)
|
||||||
|
err = Wrap(err)
|
||||||
|
err = WrapWithMsg(err, "last")
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
11
server/utils/file/file.go
Normal file
11
server/utils/file/file.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func PathExist(_path string) bool {
|
||||||
|
_, err := os.Stat(_path)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user