<?php namespace App\Console\Commands; use App\Repositories\BtRepository; use Illuminate\Console\Command; class SelfSiteSsl extends Command { protected $signature = 'self_site_ssl'; protected $description = '自建站项目自动更新证书'; protected $bt_repository; protected $bt; public function __construct() { parent::__construct(); $this->bt_repository = new BtRepository(); $this->bt = $this->bt_repository->getBtObject(); } public function handle() { try { $this->checkDomainSsl(); } catch (\Exception $e) { $this->output($e->getMessage()); } } public function checkDomainSsl() { $end_day = date('Y-m-d H:i:s', time() + 3 * 24 * 3600);//3天后到期 $site_domain = env('SITE_DOMAIN', ''); $site_ip = env('SITE_IP', ''); if (!$site_domain) { throw new \Exception('项目主站域名未配置'); } if (!$site_ip) { throw new \Exception('项目主站IP未配置'); } $ssl_time = $this->getDomainSslTime($site_domain); if ($ssl_time['to'] < $end_day) { //主站证书即将到期 $site_list = $this->bt->WebSiteList($site_domain); if (isset($site_list['data']) && $site_list['data'] && $site_list['data'][0]['status'] == 1) { $site_id = $site_list['data'][0]['id']; $host = $site_list['data'][0]['name']; //获取站点可用于设置证书的域名 $site_domain_list = $this->bt->WebDoaminList($site_id); $apply_ssl_domain_list = []; foreach ($site_domain_list as $val) { if (strpos($val['name'], '*') === false && $this->check_domain_record($val['name'], ['domain' => '', 'ip' => $site_ip])) { $apply_ssl_domain_list[] = $val['name']; } } if (empty($apply_ssl_domain_list)) { throw new \Exception('主站所有域名都未解析在当前服务器'); } //申请证书之前,还原主站配置 $config_before = file_get_contents(public_path('main_site_default.txt')); $re_config_before = $this->bt->SaveFileBody('/www/server/panel/vhost/nginx/' . $host . '.conf', $config_before, 'utf-8', 1); if (!($re_config_before['status'] ?? false)) { throw new \Exception($re_config_before['msg'] ?? '还原主站nginx配置失败'); } //设置站点证书 $this->setDomainSsl($site_id, $host, $apply_ssl_domain_list); //申请证书之后,更新主站配置 $config_after = file_get_contents(public_path('main_site_config.txt')); $re_config_after = $this->bt->SaveFileBody('/www/server/panel/vhost/nginx/' . $host . '.conf', $config_after, 'utf-8', 1); if (!($re_config_after['status'] ?? false)) { throw new \Exception($re_config_after['msg'] ?? '更新主站nginx配置失败'); } $this->output('主站证书更新成功'); } } $amp_domain = env('AMP_DOMAIN', ''); if ($amp_domain) { $amp_ssl_time = $this->getDomainSslTime($amp_domain); if ($amp_ssl_time['to'] < $end_day) { //AMP证书即将到期 $amp_site_list = $this->bt->WebSiteList($amp_domain); if (isset($amp_site_list['data']) && $amp_site_list['data'] && $amp_site_list['data'][0]['status'] == 1) { $amp_site_id = $amp_site_list['data'][0]['id']; $amp_host = $amp_site_list['data'][0]['name']; //申请证书之前,还原主站配置 $amp_config_before = file_get_contents(public_path('amp_site_default.txt')); $re_amp_config_before = $this->bt->SaveFileBody('/www/server/panel/vhost/nginx/' . $amp_host . '.conf', $amp_config_before, 'utf-8', 1); if (!($re_amp_config_before['status'] ?? false)) { throw new \Exception($re_amp_config_before['msg'] ?? '还原AMP站nginx配置失败'); } //设置站点证书 $this->setDomainSsl($amp_site_id, $amp_host, [$amp_host]); //申请证书之后,更新主站配置 $amp_config_after = file_get_contents(public_path('amp_site_config.txt')); $re_amp_config_after = $this->bt->SaveFileBody('/www/server/panel/vhost/nginx/' . $amp_host . '.conf', $amp_config_after, 'utf-8', 1); if (!($re_amp_config_after['status'] ?? false)) { throw new \Exception($re_amp_config_after['msg'] ?? '更新AMP站nginx配置失败'); } $this->output('AMP站证书更新成功'); } } } } /** * 检查域名解析师是否正确 * @param $domain * @param $server_info * @return bool * @author Akun * @date 2025/01/13 14:53 */ public function check_domain_record($domain, $server_info) { try { $records = dns_get_record($domain, DNS_A); if (count($records) != 1) { return false; } $record = $records[0]; if ($record['host'] == $server_info['domain'] || $record['ip'] == $server_info['ip']) { return $domain; } else { return false; } } catch (\Exception $e) { return false; } } /** * 设置域名证书 * @param $site_id * @param $host * @param $domain_list * @param string $key * @param string $cer * @throws \Exception * @author Akun * @date 2025/01/13 14:53 */ public function setDomainSsl($site_id, $host, $domain_list, $key = '', $cer = '') { if (empty($key) || empty($cer)) { $ssl = $this->bt->GetSSL($host); if (isset($ssl['cert_data']['notAfter']) && strtotime($ssl['cert_data']['notAfter']) - time() > 259200) { // 如果已经申请了ssl证书, 并且证书有效期超过3天, 那么就使用已经申请好的证书 $key = $ssl['key']; $cer = $ssl['csr']; $is_set_status = !$ssl['status']; } else { $re_apply_cert = $this->bt->ApplyCert(json_encode($domain_list), $site_id); if (!($re_apply_cert['status'] ?? false)) { $apply_error_msg = '申请免费证书失败'; if (isset($re_apply_cert['msg'])) { if (is_array($re_apply_cert['msg'])) { $apply_error_msg = json_encode($re_apply_cert['msg']); } else { $apply_error_msg = $re_apply_cert['msg']; } } throw new \Exception($apply_error_msg); } $key = $re_apply_cert['private_key']; $cer = $re_apply_cert['cert']; $is_set_status = true; } } else { $is_set_status = true; } if ($key && $cer && $is_set_status) { $re_set_ssl = $this->bt->SetSSL(1, $host, $key, $cer); if (!($re_set_ssl['status'] ?? false)) { throw new \Exception($re_set_ssl['msg'] ?? '设置证书失败'); } } } /** * 获取域名证书有效时间 * @param $domain * @return string[] * @author Akun * @date 2024/08/29 9:59 */ public function getDomainSslTime($domain) { $valid_from = ''; $valid_to = ''; try { $context = stream_context_create([ 'ssl' => [ 'capture_peer_cert' => true, 'capture_peer_cert_chain' => false, 'verify_peer' => false, 'verify_peer_name' => false ], ]); $stream = stream_socket_client('ssl://' . $domain . ':443', $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context); if ($stream) { $remote_cert = stream_context_get_params($stream)['options']['ssl']['peer_certificate']; if ($remote_cert) { $valid_from = date('Y-m-d H:i:s', openssl_x509_parse($remote_cert)['validFrom_time_t']); $valid_to = date('Y-m-d H:i:s', openssl_x509_parse($remote_cert)['validTo_time_t']); } } fclose($stream); } catch (\Exception $e) { $valid_from = ''; $valid_to = ''; } return ['from' => $valid_from, 'to' => $valid_to]; } /** * 输出处理日志 * @param $message */ public function output($message) { echo date('Y-m-d H:i:s') . ' | ' . $message . PHP_EOL; } }