新增lucky部署,支持直接对接DNS服务商添加域名

This commit is contained in:
net909
2025-06-25 12:57:09 +08:00
parent d368a0190a
commit 9275256e36
20 changed files with 364 additions and 19 deletions

View File

@@ -142,13 +142,14 @@ class Domain extends BaseController
$accounts = [];
$types = [];
foreach ($list as $row) {
$accounts[$row['id']] = $row['id'] . '_' . DnsHelper::$dns_config[$row['type']]['name'];
$name = $row['id'] . '_' . DnsHelper::$dns_config[$row['type']]['name'];
if (!array_key_exists($row['type'], $types)) {
$types[$row['type']] = DnsHelper::$dns_config[$row['type']]['name'];
}
if (!empty($row['remark'])) {
$accounts[$row['id']] .= '' . $row['remark'] . '';
$name .= '' . $row['remark'] . '';
}
$accounts[] = ['id' => $row['id'], 'name' => $name, 'type' => DnsHelper::$dns_config[$row['type']]['name'], 'add' => DnsHelper::$dns_config[$row['type']]['add']];
}
View::assign('accounts', $accounts);
View::assign('types', $types);
@@ -225,13 +226,21 @@ class Domain extends BaseController
} elseif ($act == 'add') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$aid = input('post.aid/d');
$method = input('post.method/d', 0);
$name = input('post.name', null, 'trim');
$thirdid = input('post.thirdid', null, 'trim');
$recordcount = input('post.recordcount/d', 0);
if (empty($name) || empty($thirdid)) return json(['code' => -1, 'msg' => '参数不能为空']);
if ($method == 1 && empty($name) || $method == 0 && (empty($name) || empty($thirdid))) return json(['code' => -1, 'msg' => '参数不能为空']);
if (Db::name('domain')->where('aid', $aid)->where('name', $name)->find()) {
return json(['code' => -1, 'msg' => '域名已存在']);
}
if ($method == 1) {
$dns = DnsHelper::getModel($aid);
$result = $dns->addDomain($name);
if (!$result) return json(['code' => -1, 'msg' => '添加域名失败,' . $dns->getError()]);
$name = $result['name'];
$thirdid = $result['id'];
}
Db::name('domain')->insert([
'aid' => $aid,
'name' => $name,

View File

@@ -641,6 +641,44 @@ class DeployHelper
],
],
],
'lucky' => [
'name' => 'Lucky',
'class' => 1,
'icon' => 'lucky.png',
'desc' => '更新Lucky证书',
'note' => '在“设置->开发者设置”打开OpenToken开关',
'tasknote' => '系统会根据关联SSL证书的域名自动更新对应证书',
'inputs' => [
'url' => [
'name' => '面板地址',
'type' => 'input',
'placeholder' => 'Lucky 面板地址',
'note' => '填写规则如https://192.168.1.100:16601 ,不要带其他后缀',
'required' => true,
],
'path' => [
'name' => '安全入口',
'type' => 'input',
'note' => '未设置请留空参考Lucky设置中的安全入口设置'
],
'opentoken' => [
'name' => 'OpenToken',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [],
],
'proxmox' => [
'name' => 'Proxmox VE',
'class' => 1,

View File

@@ -19,6 +19,7 @@ class DnsHelper
'log' => true, //是否支持查看日志
'weight' => false, //是否支持权重
'page' => false, //是否客户端分页
'add' => true, //是否支持添加域名
],
'dnspod' => [
'name' => '腾讯云',
@@ -32,6 +33,7 @@ class DnsHelper
'log' => true,
'weight' => true,
'page' => false,
'add' => true,
],
'huawei' => [
'name' => '华为云',
@@ -45,6 +47,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'baidu' => [
'name' => '百度云',
@@ -58,6 +61,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => true,
'add' => true,
],
'west' => [
'name' => '西部数码',
@@ -71,6 +75,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => false,
'add' => false,
],
'huoshan' => [
'name' => '火山引擎',
@@ -84,6 +89,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'jdcloud' => [
'name' => '京东云',
@@ -97,6 +103,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'dnsla' => [
'name' => 'DNSLA',
@@ -110,6 +117,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'cloudflare' => [
'name' => 'Cloudflare',
@@ -123,6 +131,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => false,
'add' => true,
],
'namesilo' => [
'name' => 'NameSilo',
@@ -136,6 +145,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => true,
'add' => false,
],
'powerdns' => [
'name' => 'PowerDNS',
@@ -150,6 +160,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => true,
'add' => true,
],
];

View File

@@ -31,4 +31,6 @@ interface DnsInterface
function getRecordLine();
function getMinTTL();
function addDomain($Domain);
}

114
app/lib/deploy/lucky.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class lucky implements DeployInterface
{
private $logger;
private $url;
private $opentoken;
private $proxy;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/') . (!empty($config['path']) ? $config['path'] : '');
$this->opentoken = $config['opentoken'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->opentoken)) throw new Exception('请填写面板地址和OpenToken');
$this->request("/api/modules/list");
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$domains = $config['domainList'];
if (empty($domains)) throw new Exception('没有设置要部署的域名');
try {
$data = $this->request("/api/ssl");
$this->log('获取证书列表成功');
} catch (Exception $e) {
throw new Exception('获取证书列表失败:' . $e->getMessage());
}
$success = 0;
$errmsg = null;
if (!empty($data['list'])) {
foreach ($data['list'] as $row) {
if (empty($row['CertsInfo']['Domains'])) continue;
$cert_domains = $row['CertsInfo']['Domains'];
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
}
}
if ($flag) {
$params = [
'Key' => $row['Key'],
'CertBase64' => base64_encode($fullchain),
'KeyBase64' => base64_encode($privatekey),
'AddFrom' => 'file',
'Enable' => true,
'MappingToPath' => false,
'Remark' => $row['Remark'] ?: '',
'AllSyncClient' => false,
];
try {
$this->request('/api/ssl', $params, 'PUT');
$this->log("证书ID:{$row['Key']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['Key']}更新失败:" . $errmsg);
}
}
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '没有要更新的证书');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
private function request($path, $params = null, $method = null)
{
$url = $this->url . $path;
$headers = [
'openToken' => $this->opentoken,
];
$body = null;
if ($params) {
$body = json_encode($params);
$headers['Content-Type'] = 'application/json';
}
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['ret']) && $result['ret'] == 0) {
return $result;
} elseif (isset($result['msg'])) {
throw new Exception($result['msg']);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
}

View File

@@ -293,6 +293,16 @@ class aliyun implements DnsInterface
return $this->request($param);
}
public function addDomain($Domain)
{
$param = ['Action' => 'AddDomain', 'DomainName' => $Domain];
$result = $this->request($param, true);
if ($result) {
return ['id' => $result['DomainId'], 'name' => $result['DomainName']];
}
return false;
}
private function convertLineCode($line)
{
$convert_dict = ['0' => 'default', '10=1' => 'unicom', '10=0' => 'telecom', '10=3' => 'mobile', '10=2' => 'edu', '3=0' => 'oversea', '10=22' => 'btvn', '80=0' => 'search', '7=0' => 'internal'];

View File

@@ -222,6 +222,19 @@ class baidu implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$query = ['clientToken' => getSid(), 'name' => $Domain];
$res = $this->send_reuqest('POST', '/v1/dns/zone', null, $query);
if ($res) {
$data = $this->getDomainInfo($Domain);
if ($data) {
return ['id' => $data['DomainId'], 'name' => $data['Domain']];
}
}
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -205,6 +205,16 @@ class cloudflare implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$param = ['name' => $Domain];
$data = $this->send_reuqest('POST', '/zones', $param);
if ($data) {
return ['id' => $data['result']['id'], 'name' => $data['result']['name']];
}
return false;
}
private function convertType($type)
{
$convert_dict = ['REDIRECT_URL' => 'URI', 'FORWARD_URL' => 'URI'];

View File

@@ -210,6 +210,16 @@ class dnsla implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$param = ['domain' => $Domain];
$data = $this->execute('POST', '/api/domain', $param);
if ($data) {
return ['id' => $data['id'], 'name' => $Domain];
}
return false;
}
private function convertType($type)
{
$typeList = array_flip($this->typeList);

View File

@@ -314,6 +314,19 @@ class dnspod implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$action = 'CreateDomain';
$param = [
'Domain' => $Domain,
];
$data = $this->send_request($action, $param);
if ($data) {
return ['id' => $data['DomainInfo']['Id'], 'name' => $data['DomainInfo']['Domain']];
}
return false;
}
private function convertLineCode($line)
{
$convert_dict = ['default' => '0', 'unicom' => '10=1', 'telecom' => '10=0', 'mobile' => '10=3', 'edu' => '10=2', 'oversea' => '3=0', 'btvn' => '10=22', 'search' => '80=0', 'internal' => '7=0'];

View File

@@ -229,6 +229,18 @@ class huawei implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$params = [
'name' => $Domain,
];
$data = $this->send_request('POST', '/v2/zones', null, $params);
if ($data) {
return ['id' => $data['id'], 'name' => rtrim($data['name'], '.')];
}
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -237,6 +237,16 @@ class huoshan implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$params = ['ZoneName' => $Domain];
$data = $this->send_request('POST', 'CreateZone', $params);
if ($data) {
return ['id' => $data['ZID'], 'name' => $data['ZoneName']];
}
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -225,6 +225,16 @@ class jdcloud implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$params = ['packId' => 0, 'domainName' => $Domain];
$data = $this->send_request('POST', '/domain', $params);
if ($data) {
return ['id' => $data['data']['id'], 'name' => $data['data']['domainName']];
}
return false;
}
private function convertType($type)
{
$convert_dict = ['REDIRECT_URL' => 'EXPLICIT_URL', 'FORWARD_URL' => 'IMPLICIT_URL'];

View File

@@ -181,6 +181,11 @@ class namesilo implements DnsInterface
return false;
}
public function addDomain($Domain)
{
return false;
}
private function send_reuqest($operation, $param = null)
{
$url = $this->baseUrl . $operation;

View File

@@ -327,6 +327,23 @@ class powerdns implements DnsInterface
return false;
}
public function addDomain($Domain)
{
if (substr($Domain, -1) != '.') {
$Domain .= '.';
}
$param = [
'name' => $Domain,
'kind' => 'Native',
'soa_edit_api' => 'INCREASE',
];
$result = $this->send_reuqest('POST', '/servers/' . $this->server_id . '/zones', $param);
if ($result) {
return ['id' => $result['id'], 'name' => rtrim($result['name'], '.')];
}
return false;
}
private function rrset_replace($host, $type, $ttl, $records, $remark = null)
{
$name = $host == '@' ? $this->domainid : $host . '.' . $this->domainid;

View File

@@ -171,6 +171,11 @@ class west implements DnsInterface
return false;
}
public function addDomain($Domain)
{
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -17,18 +17,31 @@
<label class="col-sm-3 control-label">域名账户</label>
<div class="col-sm-9">
<select name="aid" class="form-control">
{foreach $accounts as $k=>$v}
<option value="{$k}">{$v}</option>
{foreach $accounts as $item}
<option value="{$item.id}" data-type="{$item.type}" data-add="{$item.add}">{$item.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group">
<div class="form-group" id="methodSelect" style="display: none;">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<label class="radio-inline"><input type="radio" name="method" value="0" checked> 已有域名</label>
<label class="radio-inline"><input type="radio" name="method" value="1"> 新域名</label>
</div>
</div>
<div class="form-group" id="domainSelect">
<label class="col-sm-3 control-label">选择域名</label>
<div class="col-sm-9">
<select name="domain" id="domainList" class="form-control"></select>
</div>
</div>
<div class="form-group" id="domainInput" style="display: none;">
<label class="col-sm-3 control-label">输入域名</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="adddomain" placeholder="输入要新增的域名" value="">
</div>
</div>
</form>
</div>
@@ -289,7 +302,30 @@ $(document).ready(function(){
})
$("#form-store select[name=aid]").change(function(){
getDomainList()
if($(this).val() != ''){
$("#form-store input[name=method][value=0]").prop('checked', true);
$("#domainSelect").show();
$("#domainInput").hide();
var add = $(this).find('option:selected').data('add');
if(add == '1'){
$("#methodSelect").show();
}else{
$("#methodSelect").hide();
}
getDomainList();
}
})
$("#form-store input[name=method]").change(function(){
var value = $("#form-store input[name=method]:checked").val();
if(value == '0'){
$("#domainSelect").show();
$("#domainInput").hide();
getDomainList();
}else{
$("#domainSelect").hide();
$("#domainInput").show();
$('#domainList').empty();
}
})
$('[data-toggle="popover"]').popover()
@@ -298,26 +334,37 @@ function addframe(){
$("#modal-store").modal('show');
var aid = $("#form-store select[name=aid]").val();
if(aid != ''){
getDomainList()
$("#form-store select[name=aid]").change();
}
}
function saveAdd(){
var aid = $("#form-store select[name=aid]").val();
var select = $('#domainList').select2('data');
if(select.length == 0){
layer.alert('请选择域名!');return false;
if(aid == ''){
layer.alert('请选择域名账户!');return false;
}
var name = select[0].text;
var thirdid = select[0].id;
var recordcount = select[0].recordcount;
if(aid=='' || thirdid==''){
layer.alert('请确保各项不能为空!');return false;
var method = $("#form-store input[name=method]:checked").val();
if(method == '1'){
var name = $("#form-store input[name=adddomain]").val();
if(name == ''){
layer.alert('域名不能为空!');return false;
}
}else{
var select = $('#domainList').select2('data');
if(select.length == 0){
layer.alert('请选择域名!');return false;
}
var name = select[0].text;
var thirdid = select[0].id;
var recordcount = select[0].recordcount;
if(aid=='' || thirdid==''){
layer.alert('请确保各项不能为空!');return false;
}
}
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/op/act/add',
data : {aid: aid, thirdid: thirdid, name: name, recordcount: recordcount},
data : {aid: aid, method: method, thirdid: thirdid, name: name, recordcount: recordcount},
dataType : 'json',
success : function(data) {
layer.close(ii);

View File

@@ -38,6 +38,9 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<option value="CAA">CAA</option>
{if $dnsconfig.redirect}<option value="REDIRECT_URL">显性URL</option>
<option value="FORWARD_URL">隐性URL</option>{/if}
{if $dnsconfig.type=='powerdns'}<option value="LOC">LOC</option>
<option value="PTR">PTR</option>
<option value="LUA">LUA</option>{/if}
</select>
</div>
</div>
@@ -112,6 +115,9 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<option value="CAA">CAA</option>
{if $dnsconfig.redirect}<option value="REDIRECT_URL">显性URL</option>
<option value="FORWARD_URL">隐性URL</option>{/if}
{if $dnsconfig.type=='powerdns'}<option value="LOC">LOC</option>
<option value="PTR">PTR</option>
<option value="LUA">LUA</option>{/if}
</select>
</div>
</div>
@@ -201,6 +207,9 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<option value="CAA">CAA</option>
{if $dnsconfig.redirect}<option value="REDIRECT_URL">显性URL</option>
<option value="FORWARD_URL">隐性URL</option>{/if}
{if $dnsconfig.type=='powerdns'}<option value="LOC">LOC</option>
<option value="PTR">PTR</option>
<option value="LUA">LUA</option>{/if}
</select>
</div>
<div class="form-group">

View File

@@ -12,8 +12,8 @@
// [ 应用入口文件 ]
namespace think;
if (version_compare(PHP_VERSION, '7.4.0', '<')) {
die('require PHP >= 7.4 !');
if (version_compare(PHP_VERSION, '8.0.0', '<')) {
die('require PHP >= 8.0 !');
}
require __DIR__ . '/../vendor/autoload.php';

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB