仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ MPHX/bt_docker.php 12.6 KB · 377 行 原始文件
Z zfhsh first commit 2 天前
1<?php
2if (!defined('IN_CRONLITE')) exit();
3
4/**
5 * 宝塔面板 Docker API 封装类
6 *
7 * 独立于 bt_api,专用于宝塔 Docker 模块。
8 * - GET 路由:/btdocker/<module>/<method>,签名(request_token/request_time)拼入 query string + cookie
9 * - POST 路由:/mod/docker/com/<method>/stype,签名入 body + cookie
10 *
11 * 签名算法与 bt_api 一致(md5(time . md5(key)) + time),但 transport 自持,不修改 bt_api。
12 *
13 * 参考文档:https://docs.bt.cn/api/docker/
14 * 注意:宝塔官方注明 Docker 接口可能随版本变更、不保证稳定性,仅供 MNBT 内部使用。
15 */
16
17class bt_docker
18{
19 /** @var string 宝塔面板地址(含协议+端口,无尾斜杠) */
20 public $BT_PANEL;
21 /** @var string 宝塔接口密钥 */
22 public $BT_KEY;
23
24 public function __construct($bt_panel = null, $bt_key = null)
25 {
26 $this->BT_PANEL = rtrim((string)$bt_panel, '/');
27 $this->BT_KEY = (string)$bt_key;
28 }
29
30 // ========================================================================
31 // 安装与配置(GET /btdocker/setup/)
32 // ========================================================================
33
34 /** Docker 服务状态(service_status / docker_installed / docker_compose_installed) */
35 public function get_config()
36 {
37 return $this->HttpGet('setup', 'get_config');
38 }
39
40 /** 安装 Docker 程序 */
41 public function install_docker_program()
42 {
43 return $this->HttpGet('setup', 'install_docker_program');
44 }
45
46 /** 镜像加速配置 */
47 public function get_registry_mirrors()
48 {
49 return $this->HttpGet('setup', 'get_registry_mirrors');
50 }
51
52 /** 设置镜像加速 */
53 public function set_registry_mirrors($mirrors)
54 {
55 return $this->HttpGet('setup', 'set_registry_mirrors', ['mirrors' => $mirrors]);
56 }
57
58 /** 监控数据保留天数 */
59 public function set_monitor_save_date($day)
60 {
61 return $this->HttpGet('setup', 'set_monitor_save_date', ['day' => $day]);
62 }
63
64 // ========================================================================
65 // 容器管理(GET /btdocker/container/)
66 // ========================================================================
67
68 /** 所有容器列表 */
69 public function container_list()
70 {
71 return $this->HttpGet('container', 'get_list');
72 }
73
74 /** 启动容器 */
75 public function container_start($id, $name)
76 {
77 return $this->HttpGet('container', 'start', ['id' => $id, 'name' => $name]);
78 }
79
80 /** 停止容器 */
81 public function container_stop($id, $name)
82 {
83 return $this->HttpGet('container', 'stop', ['id' => $id, 'name' => $name]);
84 }
85
86 /** 重启容器 */
87 public function container_restart($id, $name)
88 {
89 return $this->HttpGet('container', 'restart', ['id' => $id, 'name' => $name]);
90 }
91
92 /** 删除容器(按 id+name) */
93 public function container_del($id, $name)
94 {
95 return $this->HttpGet('container', 'del', ['id' => $id, 'name' => $name]);
96 }
97
98 /** 清理无用容器 */
99 public function container_prune()
100 {
101 return $this->HttpGet('container', 'prune');
102 }
103
104 /** 容器执行日志(轮询安装进度用) */
105 public function container_cmd_log($id = null, $name = null)
106 {
107 $params = [];
108 if ($id !== null) $params['id'] = $id;
109 if ($name !== null) $params['name'] = $name;
110 return $this->HttpGet('container', 'get_cmd_log', $params);
111 }
112
113 // ========================================================================
114 // 镜像管理(GET /btdocker/image/)
115 // ========================================================================
116
117 /** 本地镜像列表 */
118 public function image_list()
119 {
120 return $this->HttpGet('image', 'image_list');
121 }
122
123 /** 清理无用镜像 */
124 public function image_prune()
125 {
126 return $this->HttpGet('image', 'prune');
127 }
128
129 // ========================================================================
130 // 存储卷(GET /btdocker/volume/)
131 // ========================================================================
132
133 /** 存储卷列表 */
134 public function volume_list()
135 {
136 return $this->HttpGet('volume', 'get_volume_list');
137 }
138
139 /** 创建存储卷 */
140 public function volume_add($name, $driver = 'local')
141 {
142 return $this->HttpGet('volume', 'add', ['name' => $name, 'driver' => $driver]);
143 }
144
145 /** 清理无用存储卷 */
146 public function volume_prune()
147 {
148 return $this->HttpGet('volume', 'prune');
149 }
150
151 // ========================================================================
152 // 网络(GET /btdocker/network/)
153 // ========================================================================
154
155 /** 网络列表 */
156 public function network_list()
157 {
158 return $this->HttpGet('network', 'get_host_network');
159 }
160
161 /** 创建网络 */
162 public function network_create($name, $driver = 'bridge')
163 {
164 return $this->HttpGet('network', 'create_network', ['name' => $name, 'driver' => $driver]);
165 }
166
167 /** 清理无用网络 */
168 public function network_prune()
169 {
170 return $this->HttpGet('network', 'prune');
171 }
172
173 // ========================================================================
174 // 仓库(GET /btdocker/registry/)
175 // ========================================================================
176
177 /** 镜像仓库列表 */
178 public function registry_list()
179 {
180 return $this->HttpGet('registry', 'registry_list');
181 }
182
183 /** 设置仓库备注 */
184 public function registry_set_remark($id, $remark)
185 {
186 return $this->HttpGet('registry', 'set_remark', ['id' => $id, 'remark' => $remark]);
187 }
188
189 // ========================================================================
190 // Compose 模板与项目(GET /btdocker/compose|project/)
191 // ========================================================================
192
193 /** Compose 模板列表 */
194 public function template_list()
195 {
196 return $this->HttpGet('compose', 'template_list');
197 }
198
199 /** Docker 项目列表 */
200 public function project_list()
201 {
202 return $this->HttpGet('project', 'get_project_list');
203 }
204
205 // ========================================================================
206 // 应用商店(POST /mod/docker/com/)
207 // ========================================================================
208
209 /**
210 * 应用列表(289 个应用及参数定义)
211 * 返回 data 数组,每项含 appname/apptitle/apptype/appversion/depend/env/field
212 */
213 public function app_list()
214 {
215 return $this->HttpPost('get_apps');
216 }
217
218 /**
219 * 安装应用(异步任务:返回"等待 1-5 分钟初始化",前端轮询 get_cmd_log 跟进)
220 * @param array $params create_app 参数(app_name/service_name/m_version/s_version/allow_access/cpus/memory_limit 及应用专属参数)
221 */
222 public function app_create($params)
223 {
224 return $this->HttpPost('create_app', $params);
225 }
226
227 /** 查询依赖应用安装状态 */
228 public function app_dependence($app)
229 {
230 return $this->HttpPost('get_dependence_apps', ['app_name' => $app]);
231 }
232
233 /**
234 * 已安装应用列表(含容器详情、端口、应用参数)
235 * 返回 data 数组,每项含 service_name/appname/apptitle/status/port[]/host_ip/server_ip/container_id/appinfo[] 等
236 * 宝塔 get_list 的 ports 字段为空数组,端口信息实际来自此接口
237 */
238 public function installed_apps()
239 {
240 return $this->HttpPost('get_installed_apps');
241 }
242
243 /** 应用商店配置 */
244 public function apphub_config()
245 {
246 return $this->HttpPost('get_apphub_config');
247 }
248
249 /** 初始化应用商店环境 */
250 public function install_apphub()
251 {
252 return $this->HttpPost('install_apphub');
253 }
254
255 /**
256 * 卸载应用(删除容器及数据)
257 * @param array $params remove_app 参数(service_name 必填)
258 */
259 public function app_remove($params)
260 {
261 return $this->HttpPost('remove_app', $params);
262 }
263
264 // ========================================================================
265 // 文件/磁盘(GET /files?action=get_path_size)
266 // ========================================================================
267
268 /**
269 * 获取指定路径的磁盘占用大小(字节)
270 * 调用宝塔面板文件 API:/files?action=get_path_size
271 * @param string $path 容器安装目录(来自 get_installed_apps 的 path 字段)
272 * @return array 含 size(字节)字段
273 */
274 public function get_path_size($path)
275 {
276 $keyData = $this->GetKeyData();
277 $keyData['path'] = $path;
278 $url = $this->BT_PANEL . '/files?action=get_path_size';
279 return $this->request($url, $keyData, 30);
280 }
281
282 // ========================================================================
283 // 内部工具(签名与请求 transport,自持,不改 bt_api)
284 // ========================================================================
285
286 /**
287 * 签名数据(与 bt_api 一致:request_token = md5(time . md5(key)),request_time = time)
288 */
289 private function GetKeyData()
290 {
291 $now_time = time();
292 return [
293 'request_token' => md5($now_time . '' . md5($this->BT_KEY)),
294 'request_time' => $now_time,
295 ];
296 }
297
298 /**
299 * GET 请求 /btdocker/<module>/<method>,签名入 query string + cookie
300 * @param string $module 模块(setup/container/image/volume/network/registry/compose/project)
301 * @param string $method 方法名
302 * @param array $params 业务参数
303 * @param int $timeout
304 * @return array 解码后的响应
305 */
306 private function HttpGet($module, $method, $params = [], $timeout = 30)
307 {
308 $query = array_merge($this->GetKeyData(), $params);
309 $url = $this->BT_PANEL . '/btdocker/' . $module . '/' . $method . '?' . http_build_query($query);
310 return $this->request($url, null, $timeout);
311 }
312
313 /**
314 * POST 请求 /mod/docker/com/<method>/stype,签名入 body + cookie
315 * @param string $method 方法名(get_apps/create_app/get_dependence_apps 等)
316 * @param array $params 业务参数
317 * @param int $timeout
318 * @return array 解码后的响应
319 */
320 private function HttpPost($method, $params = [], $timeout = 60)
321 {
322 $url = $this->BT_PANEL . '/mod/docker/com/' . $method . '/stype';
323 $data = array_merge($this->GetKeyData(), $params);
324 $r = $this->request($url, $data, $timeout);
325 // 宝塔 POST 接口通常返回 {status, msg, data} 包装,有 data 数组时提取
326 if (is_array($r) && isset($r['data']) && is_array($r['data'])) {
327 return $r['data'];
328 }
329 return $r;
330 }
331
332 /**
333 * 统一 cURL 请求(带 cookie jar,复用 bt_api 的 cookie 目录)
334 * @param string $url 完整 URL
335 * @param array|null $postData null=GET,数组=POST
336 * @param int $timeout
337 * @return array 解码后的响应;解析失败时返回原始字符串包一层
338 */
339 private function request($url, $postData, $timeout)
340 {
341 $cookie_file = ROOT . 'api/cookie/' . md5($this->BT_PANEL) . '.cookie';
342 if (!is_dir(dirname($cookie_file))) {
343 @mkdir(dirname($cookie_file), 0755, true);
344 }
345 if (!file_exists($cookie_file)) {
346 $fp = fopen($cookie_file, 'w+');
347 if (is_resource($fp)) {
348 fclose($fp);
349 }
350 }
351
352 $ch = curl_init();
353 curl_setopt($ch, CURLOPT_URL, $url);
354 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
355 curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
356 curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
357 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
358 curl_setopt($ch, CURLOPT_HEADER, 0);
359 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
360 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
361 if ($postData !== null) {
362 curl_setopt($ch, CURLOPT_POST, 1);
363 curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
364 }
365 $output = curl_exec($ch);
366 $errno = curl_errno($ch);
367 $error = curl_error($ch);
368 curl_close($ch);
369
370 if ($errno) {
371 return ['status' => false, 'msg' => 'cURL 错误(' . $errno . '):' . $error];
372 }
373
374 $decoded = json_decode($output, true);
375 return $decoded !== null ? $decoded : ['status' => false, 'msg' => '响应解析失败', 'raw' => $output];
376 }
377}