pull/632/head
Mr Chen 6 months ago
parent 8ae54e30d0
commit 98fbaa099f
  1. 3
      web/admin/setup/option.py
  2. 5
      web/admin/site/__init__.py
  3. 7
      web/admin/site/ssl.py
  4. 52
      web/admin/site/ssl_acme.py
  5. 618
      web/static/app/site.js
  6. 4
      web/thisdb/sites.py
  7. 212
      web/utils/site.py

@ -20,6 +20,9 @@ def init_option():
thisdb.setOption('recycle_bin', 'open')
thisdb.setOption('template', 'default')
# SSL邮件地址
thisdb.setOption('ssl_email', '')
# 后台面板是否关闭
thisdb.setOption('admin_close', 'no')

@ -11,9 +11,12 @@
from .site import *
from .site_types import *
from .site_default import *
from .php import *
from .logs import *
from .dir import *
from .redirect import *
from .proxy import *
from .ssl import *
from .ssl import *
from .ssl_acme import *

@ -39,6 +39,13 @@ def get_cert_list():
return MwSites.instance().getCertList()
# 获取证书配置
@blueprint.route('/get_dnsapi', endpoint='get_dnsapi', methods=['GET','POST'])
@panel_login_required
def get_dnsapi():
return MwSites.instance().getDnsapi()

@ -0,0 +1,52 @@
# coding:utf-8
# ---------------------------------------------------------------------------------
# MW-Linux面板
# ---------------------------------------------------------------------------------
# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved.
# ---------------------------------------------------------------------------------
# Author: midoks <midoks@163.com>
# ---------------------------------------------------------------------------------
import os
import json
from flask import Blueprint, render_template
from flask import request
from admin.user_login_check import panel_login_required
from utils.plugin import plugin as MwPlugin
from utils.site import sites as MwSites
import core.mw as mw
import thisdb
from .site import blueprint
# 获取ACME日志
@blueprint.route('/get_acme_logs', endpoint='get_acme_logs', methods=['POST'])
@panel_login_required
def get_acme_logs():
log_file = mw.getPanelDir() + '/logs/acme.log'
if not os.path.exists(log_file):
mw.execShell('touch ' + log_file)
return mw.returnData(True, 'OK', log_file)
@blueprint.route('/create_acme', endpoint='create_acme', methods=['POST'])
@panel_login_required
def create_acme():
site_name = request.form.get('siteName', '')
domains = request.form.get('domains', '')
force = request.form.get('force', '')
renew = request.form.get('renew', '')
email = request.form.get('email', '')
return MwSites.instance().createAcme(site_name, domains,force,renew,email)

@ -2037,16 +2037,222 @@ function renewSSL(type,id,siteName){
}
function fileCheck(){
$('#dnsapi_option').css('display','none');
}
function dnsCheck(){
$('#dnsapi_option').css('display','block');
}
function renderDnsapiHtml(data){
console.log(data);
layer.open({
type: 1,
area: '500px',
title: '设置'+data['title']+'接口',
closeBtn: 1,
shift: 5,
shadeClose: true,
btn:["确定","取消"],
content: "<form class='bt-form pd15'>\
<div class='line'>\
<span class='tname'>DNSAPI类型</span>\
<div class='info-r'>\
<select class='bt-input-text mr5' name='type' style='width:100%;'>\
<option name='cf'>"+data['name']+"</option>\
</select>\
</div>\
</div>\
<div class='line' id='dnsapi_option'>\
<span class='tname'>CF_Key</span>\
<div class='info-r'>\
<input name='v1' class='bt-input-text mr5' style='width:100%;' placeholder='请输入对应值' type='text'>\
</div>\
<span class='tname'>CF_Email</span>\
<div class='info-r'>\
<input name='v2' class='bt-input-text mr5' style='width:100%;' placeholder='请输入对应值' type='text'>\
</div>\
</div>\
<div class='line'>\
<div>\
<ul class='help-info-text c7' style='margin-top:0px;'>\
<li>使用"+data['title']+"的API接口自动解析申请SSL</li>\
</ul>\
</div>\
</div>\
</form>",
success:function(){
},
yes:function(index) {
}
});
}
//SSL
function opSSL(type, id, siteName, callback){
function renderDnsapi(){
$.post('/site/get_dnsapi', {}, function(data){
var dnsapi_option = '';
for (var i = 0; i < data.length; i++) {
dnsapi_option+='<option value="'+data[i]['name']+'" index="'+i+'">'+data[i]['title']+'</option>';
}
$('#dnsapi_option select').html(dnsapi_option);
$('#dnsapi_option select').on('change',function(){
var val = $(this).val();
var index = $('#dnsapi_option option:selected').attr('index');
if (val == 'none'){
$('#dnsapi_option button').css('display','none');
} else {
$('#dnsapi_option button').css('display','inline-block');
}
renderDnsapiHtml(data[index]);
});
$('#dnsapi_set').on('click', function(){
var index = $('#dnsapi_option option:selected').attr('index');
renderDnsapiHtml(data[index]);
});
},'json');
}
function opSSLAcme(type, id, siteName, callback){
var acme = '<div class="apply_ssl">\
<div class="label-input-group">\
<div class="line mtb10">\
<span class="tname text-center">验证方式</span>\
<div style="margin-top:7px;display:inline-block">\
<input type="radio" name="dns_type" onclick="fileCheck()" id="check_file" checked="checked"/>\
<label class="mr20" for="check_file" style="font-weight:normal">文件验证</label></label>\
<input type="radio" name="dns_type" onclick="dnsCheck()" id="check_dns"/>\
<label class="mr20" for="check_dns" style="font-weight:normal">DNS验证</label></label>\
</div>\
</div>\
<div class="line mtb10" id="dnsapi_option" style="display:none;">\
<span class="tname text-center"></span>\
<div style="margin-top:7px;display:inline-block">\
<select class="bt-input-text mr20">\
<option value="none">手动解析</option>\
</select>\
<button id="dnsapi_set" class="btn btn-default btn-sm btn-title" style="display:none;">配置</button>\
</div>\
</div>\
<div class="check_message line">\
<div style="margin-left:100px">\
<input type="checkbox" name="checkDomain" id="checkDomain" checked="">\
<label class="mr20" for="checkDomain" style="font-weight:normal">提前校验域名(提前发现问题,减少失败率)</label>\
</div>\
</div>\
</div>\
<div class="line mtb10">\
<span class="tname text-center">邮箱</span>\
<input class="bt-input-text" style="width:240px;" type="text" name="admin_email" />\
</div>\
<div class="line mtb10">\
<span class="tname text-center">域名</span>\
<ul id="ymlist" style="padding: 5px 10px;max-height:180px;overflow:auto; width:240px;border:#ccc 1px solid;border-radius:3px"></ul>\
</div>\
<div class="line mtb10" style="margin-left:100px">\
<button class="btn btn-success btn-sm letsApply">申请</button>\
</div>\
<ul class="help-info-text c7" id="lets_help">\
<li>申请之前请确保域名已解析如未解析会导致审核失败</li>\
<li>由ACME免费申请证书有效期3个月支持多域名默认会自动续签</li>\
<li>若您的站点使用了CDN或301重定向会导致续签失败</li>\
<li>在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点</li></ul>\
</ul>\
</div>';
$(".tab-con").html(acme);
renderDnsapi();
$.post('/site/get_ssl','site_name='+siteName+'&ssl_type=acme', function(data){
var rdata = data['data'];
if(rdata.csr == false){
$.post('/site/get_site_domains','id='+id, function(rdata) {
var data = rdata['data'];
var opt='';
for(var i=0;i<data.domains.length;i++){
var isIP = isValidIP(data.domains[i].name);
var x = isContains(data.domains[i].name, '*');
if(!isIP && !x){
opt += '<li style="line-height:26px">\
<input type="checkbox" style="margin-right:5px; vertical-align:-2px" value="'+data.domains[i].name+'">'+data.domains[i].name
+'</li>';
}
}
$("input[name='admin_email']").val(data.email);
$("#ymlist").html(opt);
$("#ymlist li input").click(function(e){
e.stopPropagation();
})
$("#ymlist li").click(function(){
var o = $(this).find("input");
if(o.prop("checked")){
o.prop("checked",false)
}
else{
o.prop("checked",true);
}
})
$(".letsApply").click(function(){
var c = $("#ymlist input[type='checkbox']");
var str = [];
var domains = '';
for(var i=0; i<c.length; i++){
if(c[i].checked){
str.push(c[i].value);
}
}
domains = JSON.stringify(str);
newAcmeSSL(siteName, id, domains);
});
if (typeof (callback) != 'undefined'){
callback(rdata);
}
},'json');
return;
}
var acme = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
<div class="ssl-con-key pull-left mr20" readonly>密钥(KEY)<br><textarea id="key" class="bt-input-text">'+rdata.key+'</textarea></div>\
<div class="ssl-con-key pull-left" readonly>证书(PEM格式)<br><textarea id="csr" class="bt-input-text">'+rdata.csr+'</textarea></div>\
</div>\
<div class="ssl-btn pull-left mtb15" style="width:100%">\
<button class="btn btn-success btn-sm" onclick="deploySSL(\'acme\','+id+',\''+siteName+'\')">部署</button>\
<button class="btn btn-success btn-sm" onclick="deleteSSL(\'acme\','+id+',\''+siteName+'\')">删除</button>\
</div>\
</div>\
<ul class="help-info-text c7 pull-left">\
<li>已为您自动生成ACME免费证书</li>\
<li>由ACME免费申请证书有效期3个月支持多域名默认会自动续签</li>\
<li>如需使用其他SSL,请切换其他证书后粘贴您的KEY以及PEM内容然后保存即可</li>\
</ul>';
$(".tab-con").html(acme);
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
},'json');
}
function opSSLNow(type, id, siteName, callback){
var now = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
@ -2061,10 +2267,66 @@ function opSSL(type, id, siteName, callback){
<li>粘贴您的*.key以及*.pem内容然后保存即可</li>\
<li>如果浏览器提示证书链不完整,请检查是否正确拼接PEM证书</li><li>PEM = .crt + (root_bundle).crt</li>\
<li>在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点</li>\
</ul>';
</ul>';
$(".tab-con").html(now);
var key = '';
var csr = '';
var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']});
$.post('/site/get_ssl','site_name='+siteName,function(data){
layer.close(loadT);
var rdata = data['data'];
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
<div class='state_item'><span>强制HTTPS</span><span class='switch'>\
<input class='btswitch btswitch-ios' id='toHttps' type='checkbox'>\
<label class='btswitch-btn' for='toHttps' onclick=\"httpToHttps('" + siteName + "')\">\
</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
// <input type="radio" name="c_type" onclick="dnsCheck()" id="check_dns"/>\
// <label class="mr20" for="check_dns" style="font-weight:normal">DNS验证</label></label>\
if(rdata.key == false){
rdata.key = '';
} else {
$(".ssl-btn").append('<button style=\'margin-left:3px;\' class="btn btn-success btn-sm" onclick="deleteSSL(\'now\','+id+',\''+siteName+'\')">删除</button>');
}
if(rdata.csr == false){
rdata.csr = '';
}
$("#key").val(rdata.key);
$("#csr").val(rdata.csr);
$("#toHttps").attr('checked',rdata.httpTohttps);
if(rdata.status){
$('.warning_info').css('display','none');
$(".ssl-btn").append("<button class='btn btn-success btn-sm' onclick=\"ocSSL('close_ssl_conf','"+siteName+"')\" style='margin-left:3px;'>关闭SSL</button>");
$('#now_ssl').html('当前证书 - <i style="color:#20a53a;">[已部署SSL]</i>');
} else{
$('.warning_info').css('display','block');
$('#now_ssl').html('当前证书 - <i style="color:red;">[未部署SSL]</i>');
}
if (typeof (callback) != 'undefined'){
callback(rdata);
}
},'json');
}
function opSSLLet(type, id, siteName, callback){
var lets = '<div class="apply_ssl">\
<div class="label-input-group">\
<div class="line mtb10">\
@ -2102,274 +2364,96 @@ function opSSL(type, id, siteName, callback){
</ul>\
</div>';
// <input type="radio" name="c_type" onclick="dnsCheck()" id="check_dns"/>\
// <label class="mr20" for="check_dns" style="font-weight:normal">DNS验证</label></label>\
var acme = '<div class="apply_ssl">\
<div class="label-input-group">\
<div class="line mtb10">\
<form>\
<span class="tname text-center">验证方式</span>\
<div style="margin-top:7px;display:inline-block">\
<input type="radio" name="c_type" onclick="fileCheck()" id="check_file" checked="checked" />\
<label class="mr20" for="check_file" style="font-weight:normal">文件验证</label></label>\
</div>\
</form>\
</div>\
<div class="check_message line">\
<div style="margin-left:100px">\
<input type="checkbox" name="checkDomain" id="checkDomain" checked="">\
<label class="mr20" for="checkDomain" style="font-weight:normal">提前校验域名(提前发现问题,减少失败率)</label>\
</div>\
</div>\
</div>\
<div class="line mtb10">\
<span class="tname text-center">管理员邮箱</span>\
<input class="bt-input-text" style="width:240px;" type="text" name="admin_email" />\
</div>\
<div class="line mtb10">\
<span class="tname text-center">域名</span>\
<ul id="ymlist" style="padding: 5px 10px;max-height:180px;overflow:auto; width:240px;border:#ccc 1px solid;border-radius:3px"></ul>\
</div>\
<div class="line mtb10" style="margin-left:100px">\
<button class="btn btn-success btn-sm letsApply">申请</button>\
</div>\
<ul class="help-info-text c7" id="lets_help">\
<li>申请之前请确保域名已解析如未解析会导致审核失败</li>\
<li>由ACME免费申请证书有效期3个月支持多域名默认会自动续签</li>\
<li>若您的站点使用了CDN或301重定向会导致续签失败</li>\
<li>在未指定SSL默认站点时,未开启SSL的站点使用HTTPS会直接访问到已开启SSL的站点</li></ul>\
</ul>\
</div>';
switch(type){
case 'lets':
$(".tab-con").html(lets);
$.post('/site/get_ssl', 'site_name='+siteName+'&ssl_type=lets', function(data){
var rdata = data['data'];
if(rdata.csr == false){
$.post('/site/get_site_domains','id='+id, function(rdata) {
var data = rdata['data'];
var opt='';
for(var i=0;i<data.domains.length;i++){
var isIP = isValidIP(data.domains[i].name);
var x = isContains(data.domains[i].name, '*');
if(!isIP && !x){
opt+='<li style="line-height:26px"><input type="checkbox" style="margin-right:5px; vertical-align:-2px" value="'+data.domains[i].name+'">'+data.domains[i].name+'</li>'
}
}
$("input[name='admin_email']").val(data.email);
$("#ymlist").html(opt);
$("#ymlist li input").click(function(e){
e.stopPropagation();
})
$("#ymlist li").click(function(){
var o = $(this).find("input");
if(o.prop("checked")){
o.prop("checked",false)
}
else{
o.prop("checked",true);
}
})
$(".letsApply").click(function(){
var c = $("#ymlist input[type='checkbox']");
var str = [];
var domains = '';
for(var i=0; i<c.length; i++){
if(c[i].checked){
str.push(c[i].value);
}
}
domains = JSON.stringify(str);
newSSL(siteName, id, domains);
});
if (typeof (callback) != 'undefined'){
callback(rdata);
}
},'json');
return;
}
var lets = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
<div class="ssl-con-key pull-left mr20" readonly>密钥(KEY)<br><textarea id="key" class="bt-input-text">'+rdata.key+'</textarea></div>\
<div class="ssl-con-key pull-left" readonly>证书(PEM格式)<br><textarea id="csr" class="bt-input-text">'+rdata.csr+'</textarea></div>\
</div>\
<div class="ssl-btn pull-left mtb15" style="width:100%">\
<button class="btn btn-success btn-sm" onclick="deploySSL(\'lets\','+id+',\''+siteName+'\')">部署</button>\
<button class="btn btn-success btn-sm" onclick="renewSSL(\'lets\','+id+',\''+siteName+'\')">续期</button>\
<button class="btn btn-success btn-sm" onclick="deleteSSL(\'lets\','+id+',\''+siteName+'\')">删除</button>\
</div>\
</div>\
<ul class="help-info-text c7 pull-left">\
<li>已为您自动生成Let\'s Encrypt免费证书</li>\
<li>由Let\'s Encrypt免费申请证书有效期3个月支持多域名默认会自动续签</li>\
<li>如需使用其他SSL,请切换其他证书后粘贴您的KEY以及PEM内容然后保存即可</li>\
</ul>';
$(".tab-con").html(lets);
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
$(".tab-con").html(lets);
$.post('/site/get_ssl', 'site_name='+siteName+'&ssl_type=lets', function(data){
var rdata = data['data'];
if(rdata.csr == false){
$.post('/site/get_site_domains','id='+id, function(rdata) {
var data = rdata['data'];
var opt='';
for(var i=0;i<data.domains.length;i++){
var isIP = isValidIP(data.domains[i].name);
var x = isContains(data.domains[i].name, '*');
if(!isIP && !x){
opt+='<li style="line-height:26px"><input type="checkbox" style="margin-right:5px; vertical-align:-2px" value="'+data.domains[i].name+'">'+data.domains[i].name+'</li>'
}
}
},'json');
break;
case 'acme':
$(".tab-con").html(acme);
$.post('/site/get_ssl', 'site_name='+siteName+'&ssl_type=acme', function(data){
var rdata = data['data'];
if(rdata.csr == false){
$.post('/site/get_site_domains','id='+id, function(rdata) {
var data = rdata['data'];
var opt='';
for(var i=0;i<data.domains.length;i++){
var isIP = isValidIP(data.domains[i].name);
var x = isContains(data.domains[i].name, '*');
if(!isIP && !x){
opt += '<li style="line-height:26px">\
<input type="checkbox" style="margin-right:5px; vertical-align:-2px" value="'+data.domains[i].name+'">'+data.domains[i].name
+'</li>';
}
}
$("input[name='admin_email']").val(data.email);
$("#ymlist").html(opt);
$("#ymlist li input").click(function(e){
e.stopPropagation();
})
$("#ymlist li").click(function(){
var o = $(this).find("input");
if(o.prop("checked")){
o.prop("checked",false)
}
else{
o.prop("checked",true);
}
})
$(".letsApply").click(function(){
var c = $("#ymlist input[type='checkbox']");
var str = [];
var domains = '';
for(var i=0; i<c.length; i++){
if(c[i].checked){
str.push(c[i].value);
}
}
domains = JSON.stringify(str);
newAcmeSSL(siteName, id, domains);
});
if (typeof (callback) != 'undefined'){
callback(rdata);
$("input[name='admin_email']").val(data.email);
$("#ymlist").html(opt);
$("#ymlist li input").click(function(e){
e.stopPropagation();
})
$("#ymlist li").click(function(){
var o = $(this).find("input");
if(o.prop("checked")){
o.prop("checked",false)
}
else{
o.prop("checked",true);
}
})
$(".letsApply").click(function(){
var c = $("#ymlist input[type='checkbox']");
var str = [];
var domains = '';
for(var i=0; i<c.length; i++){
if(c[i].checked){
str.push(c[i].value);
}
},'json');
return;
}
var acme = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
<div class="ssl-con-key pull-left mr20" readonly>密钥(KEY)<br><textarea id="key" class="bt-input-text">'+rdata.key+'</textarea></div>\
<div class="ssl-con-key pull-left" readonly>证书(PEM格式)<br><textarea id="csr" class="bt-input-text">'+rdata.csr+'</textarea></div>\
</div>\
<div class="ssl-btn pull-left mtb15" style="width:100%">\
<button class="btn btn-success btn-sm" onclick="deploySSL(\'acme\','+id+',\''+siteName+'\')">部署</button>\
<button class="btn btn-success btn-sm" onclick="deleteSSL(\'acme\','+id+',\''+siteName+'\')">删除</button>\
</div>\
</div>\
<ul class="help-info-text c7 pull-left">\
<li>已为您自动生成ACME免费证书</li>\
<li>由ACME免费申请证书有效期3个月支持多域名默认会自动续签</li>\
<li>如需使用其他SSL,请切换其他证书后粘贴您的KEY以及PEM内容然后保存即可</li>\
</ul>';
$(".tab-con").html(acme);
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
},'json');
break;
case 'now':
$(".tab-con").html(now);
var key = '';
var csr = '';
var loadT = layer.msg('正在提交任务...',{icon:16,time:0,shade: [0.3, '#000']});
$.post('/site/get_ssl','site_name='+siteName,function(data){
layer.close(loadT);
var rdata = data['data'];
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
<div class='state_item'><span>强制HTTPS</span><span class='switch'>\
<input class='btswitch btswitch-ios' id='toHttps' type='checkbox'>\
<label class='btswitch-btn' for='toHttps' onclick=\"httpToHttps('" + siteName + "')\">\
</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
if(rdata.key == false){
rdata.key = '';
} else {
$(".ssl-btn").append('<button style=\'margin-left:3px;\' class="btn btn-success btn-sm" onclick="deleteSSL(\'now\','+id+',\''+siteName+'\')">删除</button>');
}
if(rdata.csr == false){
rdata.csr = '';
}
$("#key").val(rdata.key);
$("#csr").val(rdata.csr);
$("#toHttps").attr('checked',rdata.httpTohttps);
if(rdata.status){
$('.warning_info').css('display','none');
$(".ssl-btn").append("<button class='btn btn-success btn-sm' onclick=\"ocSSL('close_ssl_conf','"+siteName+"')\" style='margin-left:3px;'>关闭SSL</button>");
$('#now_ssl').html('当前证书 - <i style="color:#20a53a;">[已部署SSL]</i>');
} else{
$('.warning_info').css('display','block');
$('#now_ssl').html('当前证书 - <i style="color:red;">[未部署SSL]</i>');
}
}
domains = JSON.stringify(str);
newSSL(siteName, id, domains);
});
if (typeof (callback) != 'undefined'){
callback(rdata);
}
},'json');
break;
default:
layer.msg("错误类型", {icon:5});
break;
return;
}
var lets = '<div class="myKeyCon ptb15">\
<div class="ssl_state_info" style="display:none;"></div>\
<div class="custom_certificate_info">\
<div class="ssl-con-key pull-left mr20" readonly>密钥(KEY)<br><textarea id="key" class="bt-input-text">'+rdata.key+'</textarea></div>\
<div class="ssl-con-key pull-left" readonly>证书(PEM格式)<br><textarea id="csr" class="bt-input-text">'+rdata.csr+'</textarea></div>\
</div>\
<div class="ssl-btn pull-left mtb15" style="width:100%">\
<button class="btn btn-success btn-sm" onclick="deploySSL(\'lets\','+id+',\''+siteName+'\')">部署</button>\
<button class="btn btn-success btn-sm" onclick="renewSSL(\'lets\','+id+',\''+siteName+'\')">续期</button>\
<button class="btn btn-success btn-sm" onclick="deleteSSL(\'lets\','+id+',\''+siteName+'\')">删除</button>\
</div>\
</div>\
<ul class="help-info-text c7 pull-left">\
<li>已为您自动生成Let\'s Encrypt免费证书</li>\
<li>由Let\'s Encrypt免费申请证书有效期3个月支持多域名默认会自动续签</li>\
<li>如需使用其他SSL,请切换其他证书后粘贴您的KEY以及PEM内容然后保存即可</li>\
</ul>';
$(".tab-con").html(lets);
if (rdata['cert_data']){
var issuer = rdata['cert_data']['issuer'].split(" ");
var domains = rdata['cert_data']['dns'].join("、");
var cert_data = "<div class='state_info_flex'>\
<div class='state_item'><span>证书品牌</span><span class='ellipsis_text'>"+issuer[0]+"</span></div>\
<div class='state_item'><span>到期时间</span><span class='btlink'>"+rdata['cert_data']['endtime']+"</span></div>\
</div>\
<div class='state_info_flex'>\
<div class='state_item'><span>认证域名</span><span class='ellipsis_text'>"+domains+"</span></div>\
</div>";
$(".ssl_state_info").html(cert_data);
$(".ssl_state_info").css('display','block');
}
},'json');
}
//SSL
function opSSL(type, id, siteName, callback){
switch(type){
case 'lets':opSSLLet(type, id, siteName, callback);break;
case 'acme':opSSLAcme(type, id, siteName, callback);break;
case 'now':opSSLNow(type, id, siteName, callback);break;
default:layer.msg("错误类型", {icon:5});break;
}
}

@ -10,6 +10,8 @@
import core.mw as mw
from .option import getOption
__FIELD = 'id,name,path,status,ps,edate,type_id,add_time,update_time'
def getSitesCount():
@ -32,7 +34,7 @@ def getSitesDomainById(site_id):
t['id'] = b['id']
domains.append(t)
data['domains'] = domains
data['email'] = mw.M('users').getField('email')
data['email'] = getOption('ssl_email', default='')
return data
def addSites(name, path):

@ -137,6 +137,18 @@ class sites(object):
return path[0:-1]
return path
def getSitePath(self, siteName):
file = self.getHostConf(siteName)
if os.path.exists(file):
conf = mw.readFile(file)
rep = r'\s*root\s*(.+);'
find_cnf = re.search(rep, conf)
if not find_cnf:
return ''
path = find_cnf.groups()[0]
return path
return ''
def createRootDir(self, path):
autoInit = False
if not os.path.exists(path):
@ -1319,7 +1331,7 @@ location ^~ {from} {\n\
to_https = self.isToHttps(site_name)
info = thisdb.getSitesByName(site_name)
domains = mw.M('domain').where("pid=?", (info['id'],)).field('name').select()
domains = mw.M('domain').field('name').where("pid=?", (info['id'],)).select()
csr_path = path + '/fullchain.pem' # 生成证书路径
key_path = path + '/privkey.pem' # 密钥文件路径
@ -1333,15 +1345,13 @@ location ^~ {from} {\n\
csr_path = acme_dir + '/fullchain.cer' # ACME生成证书路径
key_path = acme_dir + '/' + site_name + '.key' # ACME密钥文件路径
key = ''
if os.path.exists(key_path):
key = mw.readFile(key_path)
else:
key = ''
csr = ''
if os.path.exists(csr_path):
csr = mw.readFile(csr_path)
else:
csr = ''
cert_data = mw.getCertName(csr_path)
data = {
@ -1472,6 +1482,198 @@ location ^~ {from} {\n\
thisdb.addSiteTypes(name)
return mw.returnData(True, '添加成功!')
def getDnsapi(self):
dnsapi_data = thisdb.getOptionByJson('dnsapi', default={})
dnsapi_option = [
{"name":"none", "title":'手动解析', 'key':''},
{"name":"dns_ali", "title":'Aliyun', 'key':'Ali_Key:Ali_Secret'},
{"name":"dns_cf", "title":'cloudflare', 'key':'CF_Key:CF_Email'},
{"name":"dns_dp", "title":'dnspod/国内', 'key':'DP_Id:DP_Key'},
{"name":"dns_dpi", "title":'dnspod/国际', 'key':'DPI_Id:DPI_Key'},
{"name":"dns_gd", "title":'GoDaddy', 'key':'GD_Key:GD_Secret'},
{"name":"dns_pdns", "title":'PowerDNS', 'key':'PDNS_Url:PDNS_ServerId:PDNS_Token:PDNS_Ttl'},
{"name":"dns_lua", "title":'LuaDNS', 'key':'LUA_Key:LUA_Email'},
{"name":"dns_me", "title":'DNSMadeEasy', 'key':'ME_Key:ME_Secret'},
{"name":"dns_aws", "title":'Amazon Route53', 'key':'AWS_ACCESS_KEY_ID:AWS_SECRET_ACCESS_KEY'},
{"name":"dns_ispconfig", "title":'ISPConfig', 'key':'ISPC_User:ISPC_Password:ISPC_Api:ISPC_Api_Insecure'},
{"name":"dns_ad", "title":'Alwaysdata', 'key':'AD_API_KEY'},
{"name":"dns_linode_v4", "title":'Linode', 'key':'LINODE_V4_API_KEY'},
{"name":"dns_freedns", "title":'FreeDNS', 'key':'FREEDNS_User:FREEDNS_Password'},
{"name":"dns_cyon", "title":'cyon.ch', 'key':'CY_Username:CY_Password:CY_OTP_Secret'},
{"name":"dns_gandi_livedns", "title":'LiveDNS', 'key':'GANDI_LIVEDNS_TOKEN'},
{"name":"dns_knot", "title":'Knot', 'key':'KNOT_SERVER:KNOT_KEY'},
{"name":"dns_dgon", "title":'DigitalOcean', 'key':'DO_API_KEY'},
{"name":"dns_cloudns", "title":'ClouDNS.net', 'key':'CLOUDNS_SUB_AUTH_ID:CLOUDNS_AUTH_PASSWORD'},
{"name":"dns_namesilo", "title":'Namesilo', 'key':'Namesilo_Key'},
{"name":"dns_azure", "title":'Azure', 'key':'AZUREDNS_SUBSCRIPTIONID:AZUREDNS_TENANTID:AZUREDNS_APPID:AZUREDNS_CLIENTSECRET'},
{"name":"dns_selectel", "title":'selectel.com', 'key':'SL_Key'},
{"name":"dns_zonomi", "title":'zonomi.com', 'key':'ZM_Key'},
{"name":"dns_kinghost", "title":'KingHost', 'key':'KINGHOST_Username:KINGHOST_Password'},
{"name":"dns_zilore", "title":'Zilore', 'key':'Zilore_Key'},
{"name":"dns_gcloud", "title":'Google Cloud DNS', 'key':'CLOUDSDK_ACTIVE_CONFIG_NAME'},
{"name":"dns_mydnsjp", "title":'MyDNS.JP', 'key':'MYDNSJP_MasterID:MYDNSJP_Password'},
{"name":"dns_doapi", "title":'do.de', 'key':'DO_LETOKEN'},
{"name":"dns_online", "title":'Online', 'key':'ONLINE_API_KEY'},
{"name":"dns_cn", "title":'Core-Networks', 'key':'CN_User:CN_Password'},
{"name":"dns_ultra", "title":'UltraDNS', 'key':'ULTRA_USR:ULTRA_PWD'},
{"name":"dns_hetzner", "title":'Hetzner', 'key':'HETZNER_Token'},
{"name":"dns_ddnss", "title":'DDNSS.de', 'key':'DDNSS_Token'},
];
for i in range(len(dnsapi_option)):
dval = dnsapi_option[i]['key']
dname = dnsapi_option[i]['name']
if dname == 'none':
continue
keys = dval.split(':')
data = {}
if dname in dnsapi_data:
dnsapi_option[i]['data'] = dnsapi_data[dname]
dnsapi_option[i]['title'] = dnsapi_option[i]['title'] + '[已配置]'
else:
t = {}
for field in keys:
t[field] = ''
dnsapi_option[i]['data'] = t
return dnsapi_option
def createAcme(self, site_name, domains, force, renew, input_email):
domains = json.loads(domains)
print(site_name,domains,input_email,renew,force)
email = thisdb.getOption('ssl_email', default='')
if input_email.strip() != '':
thisdb.setOption('ssl_email', input_email)
email = input_email
if not len(domains):
return mw.returnData(False, '请选择域名')
file = self.getHostConf(site_name)
if os.path.exists(file):
siteConf = mw.readFile(file)
if siteConf.find('301-END') != -1:
return mw.returnData(False, '检测到您的站点做了301重定向设置,请先关闭重定向!')
# 检测存在反向代理
data_path = self.getProxyDataPath(site_name)
data_content = mw.readFile(data_path)
if data_content != False:
try:
data = json.loads(data_content)
except:
pass
for proxy in data:
proxy_dir = "{}/{}".format(self.proxyPath, site_name)
proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf'
if os.path.exists(proxy_dir_file):
return mw.returnData(False, '检测到您的站点做了反向代理设置,请先关闭反向代理!')
siteInfo = thisdb.getSitesByName(site_name)
path = self.getSitePath(site_name)
if path == '':
return mw.returnData(False, ''+site_name+'】配置文件,异常!')
srcPath = siteInfo['path']
# 检测acme是否安装
acme_dir = mw.getAcmeDir()
if not os.path.exists(acme_dir):
try:
mw.execShell("curl -sS curl https://get.acme.sh | sh")
except:
pass
if not os.path.exists(acme_dir):
return mw.returnData(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装<p>安装命令: curl https://get.acme.sh | sh</p>')
# 避免频繁执行
checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep')
if checkAcmeRun[0] != '':
return mw.returnData(False, '正在申请或更新SSL中...')
if force == 'true':
force_bool = True
if renew == 'true':
execStr = acme_dir + "/acme.sh --renew --yes-I-know-dns-manual-mode-enough-go-ahead-please"
else:
execStr = acme_dir + "/acme.sh --issue --force"
# 确定主域名顺序
domainsTmp = []
if site_name in domains:
domainsTmp.append(site_name)
for domainTmp in domains:
if domainTmp == site_name:
continue
domainsTmp.append(domainTmp)
domains = domainsTmp
domainCount = 0
for domain in domains:
if mw.checkIp(domain):
continue
if domain.find('*.') != -1:
return mw.returnData(False, '泛域名不能使用【文件验证】的方式申请证书!')
execStr += ' -w ' + path
execStr += ' -d ' + domain
domainCount += 1
if domainCount == 0:
return mw.returnData(False, '请选择域名(不包括IP地址与泛域名)!')
log_file = mw.getPanelDir() + '/logs/acme.log'
mw.writeFile(log_file, "开始ACME申请...\n", "wb+")
cmd = 'export ACCOUNT_EMAIL=' + email + ' && ' + execStr + ' >> ' + log_file
# print(domains)
# print(cmd)
result = mw.execShell(cmd)
src_path = mw.getAcmeDomainDir(domains[0])
src_cert = src_path + '/fullchain.cer'
src_key = src_path + '/' + domains[0] + '.key'
src_cert.replace("\\*", "*")
msg = '签发失败,您尝试申请证书的失败次数已达上限!<p>1、检查域名是否绑定到对应站点</p>\
<p>2检查域名是否正确解析到本服务器,或解析还未完全生效</p>\
<p>3如果您的站点设置了反向代理,或使用了CDN,请先将其关闭</p>\
<p>4如果您的站点设置了301重定向,请先将其关闭</p>\
<p>5如果以上检查都确认没有问题请尝试更换DNS服务商</p>'
if not os.path.exists(src_cert):
data = {}
data['err'] = result
data['out'] = result[0]
data['msg'] = msg
data['result'] = {}
if result[1].find('new-authz error:') != -1:
data['result'] = json.loads(
re.search("{.+}", result[1]).group())
if data['result']['status'] == 429:
data['msg'] = msg
data['status'] = False
return mw.getJson(data)
dst_path = self.sslDir + '/' + site_name
dst_cert = dst_path + "/fullchain.pem" # 生成证书路径
dst_key = dst_path + "/privkey.pem" # 密钥文件路径
if not os.path.exists(dst_path):
mw.execShell("mkdir -p " + dst_path)
mw.buildSoftLink(src_cert, dst_cert, True)
mw.buildSoftLink(src_key, dst_key, True)
mw.execShell('echo "acme" > "' + dst_path + '/README"')
# 写入配置文件
result = self.setSslConf(site_name)
if not result['status']:
return mw.getJson(result)
result['csr'] = mw.readFile(src_cert)
result['key'] = mw.readFile(src_key)
mw.restartWeb()
return mw.returnData(True, '证书已更新!', result)
def getPhpVersion(self):
phpVersions = ('00', '52', '53', '54', '55',

Loading…
Cancel
Save