From ee45ddd7ec01ca05b39f403489584e1b27b012c4 Mon Sep 17 00:00:00 2001 From: net909 Date: Wed, 28 Jan 2026 20:53:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eamh=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/DeployHelper.php | 47 +++++++++++++++ app/lib/deploy/amh.php | 108 +++++++++++++++++++++++++++++++++++ public/static/images/amh.ico | Bin 0 -> 4286 bytes 3 files changed, 155 insertions(+) create mode 100644 app/lib/deploy/amh.php create mode 100644 public/static/images/amh.ico diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index f26c253..9aa5c13 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -777,6 +777,53 @@ class DeployHelper ], ], ], + 'amh' => [ + 'name' => 'AMH面板', + 'class' => 1, + 'icon' => 'amh.ico', + 'desc' => '', + 'note' => null, + 'tasknote' => '', + 'inputs' => [ + 'url' => [ + 'name' => '面板地址', + 'type' => 'input', + 'placeholder' => 'AMH面板地址', + 'note' => '填写规则如:http://192.168.1.100:8888 ,不要带其他后缀', + 'required' => true, + ], + 'apikey' => [ + 'name' => 'API接口密钥', + 'type' => 'input', + 'placeholder' => '安装amapi软件后查看,是密钥不是私钥', + 'required' => true, + ], + 'proxy' => [ + 'name' => '使用代理服务器', + 'type' => 'radio', + 'options' => [ + '0' => '否', + '1' => '是', + ], + 'value' => '0' + ], + ], + 'taskinputs' => [ + 'env_name' => [ + 'name' => '环境名称', + 'type' => 'input', + 'placeholder' => '如:lnmp01', + 'required' => true, + ], + 'vhost_name' => [ + 'name' => '网站名称列表', + 'type' => 'textarea', + 'placeholder' => '填写要部署证书的网站标识域名,每行一个', + 'note' => '网站标识域名一列的值,并非绑定域名', + 'required' => true, + ], + ], + ], 'synology' => [ 'name' => '群晖面板', 'class' => 1, diff --git a/app/lib/deploy/amh.php b/app/lib/deploy/amh.php new file mode 100644 index 0000000..88771b8 --- /dev/null +++ b/app/lib/deploy/amh.php @@ -0,0 +1,108 @@ +url = rtrim($config['url'], '/'); + $this->apikey = $config['apikey']; + $this->proxy = $config['proxy'] == 1; + } + + public function check() + { + if (empty($this->url) || empty($this->apikey)) throw new Exception('请填写面板地址和接口密钥'); + $this->login(); + return true; + } + + private function login() + { + $path = '/?c=amapi&a=login'; + $post_data = 'amapi_expires=' . time() + 120; + $post_data .= '&amapi_sign=' . hash_hmac('sha256', $post_data, $this->apikey); + $response = $this->request($path, $post_data); + if ($response['code'] == 302 && strpos($response['redirect_url'], 'amh_token=') !== false) { + if(preg_match('/amh_token=([A-Za-z0-9]+)/', $response['redirect_url'], $matches)) { + return $matches[1]; + }else{ + throw new Exception('面板返回数据异常'); + } + } elseif ($response['code'] == 200 && preg_match('/

(.*?)<\/p>/s', $response['body'], $matches)) { + throw new Exception(strip_tags($matches[1])); + } else { + throw new Exception('面板地址无法连接'); + } + } + + public function deploy($fullchain, $privatekey, $config, &$info) + { + if (empty($config['env_name'])) throw new Exception('环境名称不能为空'); + if (empty($config['vhost_name'])) throw new Exception('网站标识域名不能为空'); + + $amh_token = $this->login(); + + foreach (explode("\n", $config['vhost_name']) as $vhost_name) { + $vhost_name = trim($vhost_name); + if (empty($vhost_name)) continue; + + $path = '/?c=amssl&a=admin_amssl&envs_name=' . $config['env_name'] . '&vhost_name=' . $vhost_name . '&ModuleSort=app'; + $params = [ + 'submit_key_crt' => 'y', + 'key_input1' => 'key_input1', + 'key_content1' => $privatekey, + 'crt_input1' => 'crt_input1', + 'crt_content1' => $fullchain, + 'amh_token' => $amh_token, + ]; + $response = $this->request($path, $params); + if (strpos($response['body'], '

log("网站 {$vhost_name} 证书部署成功"); + } elseif (preg_match('/

(.*?)<\/p>/s', $response['body'], $matches)) { + $errmsg = strip_tags($matches[1]); + $this->log("网站 {$vhost_name} 证书部署失败:" . $errmsg); + throw new Exception($errmsg); + } elseif (preg_match('/

(.*?)
/s', $response['body'], $matches)) { + $errmsg = $matches[1]; + if (strpos($errmsg, '
') !== false) { + $errmsg = explode('
', $errmsg)[0]; + } + $errmsg = strip_tags($errmsg); + $this->log("网站 {$vhost_name} 证书部署失败:" . $errmsg); + throw new Exception($errmsg); + } else { + throw new Exception("网站 {$vhost_name} 证书部署失败:未知错误"); + } + } + } + + public function setLogger($func) + { + $this->logger = $func; + } + + private function log($txt) + { + if ($this->logger) { + call_user_func($this->logger, $txt); + } + } + + private function request($path, $post_data = null) + { + $url = $this->url . $path; + $cookie = 'PHPSESSID=' . hash_hmac('md5', 'php_sessid=' . $this->apikey, $this->apikey); + $response = http_request($url, $post_data, null, $cookie, null, $this->proxy); + return $response; + } +} diff --git a/public/static/images/amh.ico b/public/static/images/amh.ico new file mode 100644 index 0000000000000000000000000000000000000000..2f610dbc2f812f7a4c9bed44e1a9bbe571d9903d GIT binary patch literal 4286 zcmeI0xl5cu7{(`cnvf#3W&Vu(4c_8?A)+E$2qG#Xs0gBXHG+6F0ShlgY(%_36bn(* zN)QAsRPew9^_P7z8J4VZ`3_@JILmzR?0hrt^B&K6BAFxp^J@sw#`kuO_{lo6y}2B_&W)1RWi)wgy*MZWUZ!LSth%{}qSA zLO4E->UAU8v9kkPTQ)v!-pR8wsHg~!FaGWBI<4O2(UHxQ%Vkh0?yszP$8{^Ww?VcC z#dZ5KGAx&!UawL-)LC$P!pPxauc*;=Q&W(h9u8mUOB~K=Nc=OQ^HN_A0|PKQ zX>0nyfvvB8IH8|;P+ktTwb0oK6BBT9;yijT zRr7UM_w+zrovr`6KjfEmdgY+APr1?_b&aKGbQJFHo@0ya