dnsmgr/app/lib/deploy/opanel.php
2025-12-17 12:59:51 +08:00

247 lines
8.8 KiB
PHP

<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class opanel implements DeployInterface
{
private $logger;
private $url;
private $key;
private $proxy;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/') . '/api/' . (isset($config['version']) ? $config['version'] : 'v1');
$this->key = $config['key'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->key)) throw new Exception('请填写面板地址和接口密钥');
$this->request("/settings/search");
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
// 解析节点名称列表
$nodeNames = $this->parseNodeNames($config);
if (isset($config['type']) && $config['type'] == '3') {
// 面板本身的证书部署
$params = [
'cert' => $fullchain,
'key' => $privatekey,
'ssl' => 'Enable',
'sslID' => null,
'sslType' => 'import-paste',
];
if (empty($nodeNames)) {
// 没有指定节点,部署到主控节点
try {
$this->request('/core/settings/ssl/update', $params);
$this->log("面板证书更新成功!");
return;
} catch (Exception $e) {
throw new Exception("面板证书更新失败:" . $e->getMessage());
}
} else {
// 部署到多个子节点
$successCount = 0;
$failCount = 0;
foreach ($nodeNames as $nodeName) {
try {
$this->request('/core/settings/ssl/update', $params, $nodeName);
$this->log("节点 [{$nodeName}] 面板证书更新成功!");
$successCount++;
} catch (Exception $e) {
$this->log("节点 [{$nodeName}] 面板证书更新失败:" . $e->getMessage());
$failCount++;
}
}
if ($failCount > 0 && $successCount == 0) {
throw new Exception("所有节点证书更新失败");
}
return;
}
}
// 如果没有指定节点,则部署到主控节点
if (empty($nodeNames)) {
$this->deployToNode($fullchain, $privatekey, $config, null);
} else {
// 部署到多个子节点
$successCount = 0;
$failCount = 0;
foreach ($nodeNames as $nodeName) {
try {
$this->deployToNode($fullchain, $privatekey, $config, $nodeName);
$successCount++;
} catch (Exception $e) {
$this->log("节点 [{$nodeName}] 部署失败:" . $e->getMessage());
$failCount++;
}
}
if ($failCount > 0 && $successCount == 0) {
throw new Exception("所有节点部署失败");
}
}
}
/**
* 部署到指定节点
*/
private function deployToNode($fullchain, $privatekey, $config, $nodeName = null)
{
if (!empty($config['id'])) {
// 指定证书ID的情况
$params = [
'sslID' => intval($config['id']),
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/websites/ssl/upload', $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新成功!" : "证书ID:{$config['id']}更新成功!";
$this->log($logMsg);
return;
} catch (Exception $e) {
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新失败:" : "证书ID:{$config['id']}更新失败:";
throw new Exception($logMsg . $e->getMessage());
}
}
// 根据域名自动匹配证书
$domains = $config['domainList'];
if (empty($domains)) throw new Exception('没有设置要部署的域名');
$params = ['page' => 1, 'pageSize' => 500];
try {
$data = $this->request("/websites/ssl/search", $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
$this->log($logMsg . '获取证书列表成功(total=' . $data['total'] . ')');
} catch (Exception $e) {
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
throw new Exception($logMsg . '获取证书列表失败:' . $e->getMessage());
}
$success = 0;
$errmsg = null;
if (!empty($data['items'])) {
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains = [];
$cert_domains[] = $row['primaryDomain'];
if (!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains) || in_array('*' . substr($domain, strpos($domain, '.')), $domains)) {
$flag = true;
break;
}
}
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/websites/ssl/upload', $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新成功!" : "证书ID:{$row['id']}更新成功!";
$this->log($logMsg);
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新失败:" : "证书ID:{$row['id']}更新失败:";
$this->log($logMsg . $errmsg);
}
}
}
}
if ($success == 0) {
$params = [
'sslID' => 0,
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
$this->request('/websites/ssl/upload', $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书上传成功!" : "证书上传成功!";
$this->log($logMsg);
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
/**
* 解析节点名称列表
*/
private function parseNodeNames($config)
{
if (!isset($config['node_name']) || empty($config['node_name'])) {
return [];
}
$nodeNameStr = trim($config['node_name']);
if (empty($nodeNameStr)) {
return [];
}
// 按行分割,过滤空行
$nodeNames = array_filter(
array_map('trim', explode("\n", $nodeNameStr)),
function($name) {
return !empty($name);
}
);
return array_values($nodeNames);
}
private function request($path, $params = null, $nodeName = null)
{
$url = $this->url . $path;
$timestamp = time() . '';
$token = md5('1panel' . $this->key . $timestamp);
$headers = [
'1Panel-Token' => $token,
'1Panel-Timestamp' => $timestamp,
];
if (!empty($nodeName)) {
$headers['CurrentNode'] = $nodeName;
}
$body = $params ? json_encode($params) : '{}';
if ($body) $headers['Content-Type'] = 'application/json';
$response = http_request($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 200) {
return $result['data'] ?? null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
}