- 域名编辑页新增“从 Caddy 获取证书”按钮:一键把本机 Caddy 已签发的 证书与私钥导入该域名的 TLS 目录并自动启用 TLS,支持通配符证书 (如 *.example.com 可匹配 mail.example.com),成功/失败均回显横幅 - 新增 internal/caddycert:搜索 Caddy 证书存储(同步镜像目录优先、 caddy.data_dir 与常见位置兜底),校验密钥对/有效期/SAN,并给出 可操作的中文错误提示(未找到/证书无效/权限不足) - install.sh 新增 setup-caddy-cert:安装 root 权限的 systemd path+timer 同步任务(mailgo-caddy-sync),把 Caddy 证书树镜像到 /srv/mail_go/tls/caddy(证书续期后自动更新、每日兜底),另授予 ACL 作为直接读取兜底;install 时自动检测并配置 - 新增 [caddy] data_dir 配置节,支持自定义 Caddy 数据目录 - 新增 internal/tlsutil:TLS 证书热加载器,每次握手按需重载证书 文件(mtime 检测),重载失败继续使用旧证书兜底并节流重试; 应用于 SMTPS 465/IMAPS 993/POP3S 995 与 STARTTLS,导入或上传 新证书后无需重启服务即生效 - 证书来源动态切换:协议显式配置优先,否则取首个启用 TLS 且有证书 的域名(10 秒缓存),新域名一键导入证书后自动切换 - 更新 README 与界面文案(去掉“重启服务生效”提示)
138 lines
8.8 KiB
HTML
138 lines
8.8 KiB
HTML
{{define "admin_domain_form"}}
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{if .isEdit}}编辑域名{{else}}新增域名{{end}} - MailGo</title>
|
|
{{template "styles" .}}
|
|
</head>
|
|
<body>
|
|
{{template "navbar" .}}
|
|
<div class="container">
|
|
<div class="clearfix">
|
|
<div class="sidebar">
|
|
<a href="/inbox">返回邮箱</a>
|
|
<a href="/admin" {{if eq .activeFolder "admin"}}class="active"{{end}}>控制面板</a>
|
|
<a href="/admin/domains" {{if eq .activeFolder "domains"}}class="active"{{end}}>域名管理</a>
|
|
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
|
|
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
|
|
<a href="/admin/outbound" {{if eq .activeFolder "outbound"}}class="active"{{end}}>外发队列</a>
|
|
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
|
|
</div>
|
|
<div class="content">
|
|
<div class="card">
|
|
<h2 style="margin-bottom:16px;">{{if .isEdit}}编辑域名{{else}}新增域名{{end}}</h2>
|
|
{{if .error}}<div class="alert alert-error">{{.error}}</div>{{end}}
|
|
{{if .caddyMsg}}<div class="alert {{if eq .caddyMsgType "success"}}alert-success{{else}}alert-error{{end}}">{{.caddyMsg}}</div>{{end}}
|
|
<form method="POST" action="{{if .isEdit}}/admin/domains/{{.domain.ID}}{{else}}/admin/domains{{end}}">
|
|
<div class="form-group">
|
|
<label>域名</label>
|
|
{{if .isEdit}}
|
|
<input type="text" name="name" value="{{.domain.Name}}" disabled style="background:#f5f5f5;">
|
|
{{else}}
|
|
<input type="text" name="name" required value="{{.domain.Name}}" placeholder="example.com">
|
|
{{end}}
|
|
</div>
|
|
<div class="form-group">
|
|
<label>SMTP 端口</label>
|
|
<input type="number" name="smtp_port" value="{{.domain.SmtpPort}}" placeholder="25">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>IMAP 端口</label>
|
|
<input type="number" name="imap_port" value="{{.domain.ImapPort}}" placeholder="143">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>POP3 端口</label>
|
|
<input type="number" name="pop3_port" value="{{.domain.Pop3Port}}" placeholder="110">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="checkbox" name="tls_enabled" id="tls_enabled" {{if .domain.TlsEnabled}}checked{{end}} onchange="togglePorts()">
|
|
启用 TLS
|
|
</label>
|
|
<p style="color:#7f8c8d;font-size:12px;margin-top:4px;">勾选后端口将自动切换为 SSL/TLS 标准端口</p>
|
|
</div>
|
|
{{if .isEdit}}
|
|
<div id="tls_cert_fields" style="display:none;">
|
|
<div class="form-group">
|
|
<label>TLS 私钥 PEM</label>
|
|
<textarea name="tls_private_key" rows="8" placeholder="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----" style="font-family:monospace;"></textarea>
|
|
<p style="color:#7f8c8d;font-size:12px;margin-top:4px;">私钥不会回显;留空则保留现有私钥。</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>TLS 公钥证书 PEM</label>
|
|
<textarea name="tls_public_cert" rows="8" placeholder="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----" style="font-family:monospace;">{{.tlsPublicCert}}</textarea>
|
|
{{if .tlsCertConfigured}}
|
|
<p style="color:#27ae60;font-size:12px;margin-top:4px;">✅ TLS 证书已配置;上传新证书后自动热加载生效。</p>
|
|
{{else}}
|
|
<p style="color:#e67e22;font-size:12px;margin-top:4px;">⚠️ TLS 证书未配置,启用 TLS 时必须同时填写私钥和证书。</p>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
<div class="form-group" style="margin-top:4px;">
|
|
<label>从 Caddy 获取证书</label>
|
|
<p style="color:#7f8c8d;font-size:12px;margin-top:2px;margin-bottom:8px;">若该域名已由本机 Caddy 托管 HTTPS(自动签发证书),可一键导入其证书与私钥,并自动启用 TLS;证书热加载,无需重启服务。</p>
|
|
<button type="button" class="btn" id="btn_fetch_caddy" onclick="fetchCaddyCert()" style="background:#2e86de;color:#fff;">🔒 从 Caddy 获取证书</button>
|
|
<span id="caddy_fetch_msg" style="margin-left:10px;font-size:12px;color:#7f8c8d;"></span>
|
|
<script>
|
|
async function fetchCaddyCert() {
|
|
var btn = document.getElementById('btn_fetch_caddy');
|
|
var msg = document.getElementById('caddy_fetch_msg');
|
|
btn.disabled = true;
|
|
var oldText = btn.textContent;
|
|
btn.textContent = '获取中…';
|
|
msg.textContent = '';
|
|
try {
|
|
var resp = await fetch('/admin/domains/{{.domain.ID}}/fetch-caddy-cert', { method: 'POST' });
|
|
if (resp.redirected) {
|
|
window.location.href = resp.url;
|
|
return;
|
|
}
|
|
msg.textContent = '获取失败: ' + await resp.text();
|
|
} catch (e) {
|
|
msg.textContent = '请求失败: ' + e;
|
|
}
|
|
btn.disabled = false;
|
|
btn.textContent = oldText;
|
|
}
|
|
</script>
|
|
</div>
|
|
{{end}}
|
|
<script>
|
|
function togglePorts() {
|
|
var tls = document.getElementById('tls_enabled').checked;
|
|
document.querySelector('input[name="smtp_port"]').value = tls ? 465 : 25;
|
|
document.querySelector('input[name="imap_port"]').value = tls ? 993 : 143;
|
|
document.querySelector('input[name="pop3_port"]').value = tls ? 995 : 110;
|
|
var certFields = document.getElementById('tls_cert_fields');
|
|
if (certFields) {
|
|
certFields.style.display = tls ? 'block' : 'none';
|
|
}
|
|
}
|
|
document.addEventListener('DOMContentLoaded', togglePorts);
|
|
</script>
|
|
{{if .isEdit}}
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="checkbox" name="regenerate_dkim">
|
|
重新生成 DKIM 密钥
|
|
</label>
|
|
{{if .domain.DkimPublicKey}}
|
|
<p style="color:#27ae60;font-size:12px;margin-top:4px;">✅ DKIM 密钥已配置</p>
|
|
{{else}}
|
|
<p style="color:#e67e22;font-size:12px;margin-top:4px;">⚠️ DKIM 密钥未配置</p>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
<button type="submit" class="btn btn-primary">{{if .isEdit}}保存更改{{else}}创建域名{{end}}</button>
|
|
<a href="/admin/domains" class="btn" style="margin-left:8px;background:#bdc3c7;color:#fff;">取消</a>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
{{end}}
|