仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ api/docker.php 19.0 KB · 415 行 原始文件
Z zfhsh first commit 1 天前
1<?php
2/**
3 * Docker 对外开通 API
4 * 入口:POST api/docker.php?gn=kt
5 *
6 * 鉴权(与 api/api.php 一致):
7 * mn_key = 系统后台 API 密钥($conf['api'])
8 * mn_bh = Docker 节点编号(MN_docker_node.id)
9 * mn_keye = md5(节点ktmy . 节点qmk)
10 * mn_vs = 插件版本号(>=15)
11 * username= 待开通 Docker 账号
12 *
13 * gn=kt 参数:username / password / dqtime(到期,0=永久) / plan_id(可选) / email(可选)
14 *
15 * 注意:本接口仅开通 Docker 账户,容器由用户登录控制台后自行在应用商店创建(单容器模型)。
16 */
17@header('Content-Type: application/json; charset=UTF-8');
18include("../MPHX/common.php");
19include_once SYSTEM_ROOT . 'bt_docker.php';
20include_once SYSTEM_ROOT . 'docker.member.php';
21
22function api_json_exit($code, $msg, $extra = []) {
23 $result = array_merge([
24 'success' => ((int)$code === 200),
25 'code' => $code,
26 'msg' => $msg,
27 ], $extra);
28 exit(json_encode($result, JSON_UNESCAPED_UNICODE));
29}
30function api_lifecycle_log($type, $content, $status = '记录') {
31 global $DB, $user, $bh;
32 mnbt_log($user ?: '外部API', $type, 'DockerAPI-' . $bh . ' ' . $content, $status, $DB);
33}
34
35if ($conf['apiqk'] === 'false') api_json_exit(100, '错误!API 已关闭!请在系统后台 API 设置处开启');
36$gn = $_GET['gn'] ?? '';
37$bh = $_POST['mn_bh'] ?? '';
38$key = $_POST['mn_key'] ?? '';
39$keye = $_POST['mn_keye'] ?? '';
40$mn_vser = $_POST['mn_vs'] ?? 0;
41$user = $_POST['username'] ?? '';
42if ($mn_vser < 15) api_json_exit(300, '插件版本不支持当前 MNBT 版本');
43if (empty($gn) || empty($bh) || empty($key) || empty($keye) || empty($user)) api_json_exit(100, '错误!表单填写不完整!');
44if ($key != $conf['api']) {
45 mnbt_log('外部API', 'DockerAPI鉴权', 'DockerAPI-' . $bh . ' ' . $user . ' 系统密钥错误', '鉴权失败', $DB);
46 api_json_exit(100, '错误!系统 API 密钥不匹配');
47}
48$cert = $DB->get_row_prepare("SELECT * FROM MN_docker_node WHERE id=? limit 1", [(int)$bh]);
49if (!$cert || $cert['qk'] !== 'true') api_json_exit(100, '错误!该 Docker 节点不存在或已被关闭');
50if ($keye != md5($cert['ktmy'] . $cert['qmk'])) {
51 mnbt_log('外部API', 'DockerAPI鉴权', 'DockerAPI-' . $bh . ' ' . $user . ' 调用密钥错误', '鉴权失败', $DB);
52 api_json_exit(100, '错误!节点调用密钥不匹配');
53}
54
55// —— 连接验证 ——
56if ($gn === 'cfif') {
57 api_json_exit(200, '连接验证成功!');
58}
59
60// —— 开通 Docker 账户 ——
61if ($gn === 'kt') {
62 $pass = (string)($_POST['password'] ?? '');
63 $datae = (($_POST['dqtime'] ?? '0') === '0') ? '0000-00-00' : daddslashes($_POST['dqtime']);
64 $plan_id = (int)($_POST['plan_id'] ?? 0);
65 $email = daddslashes($_POST['email'] ?? '');
66
67 if (mb_strlen($user) < 4 || mb_strlen($pass) < 6) {
68 api_lifecycle_log('API开通Docker', '开通 ' . $user . ' 失败:账号(≥4)或密码(≥6)过短', '开通失败');
69 api_json_exit(100, '错误!账号不少于4位,密码不少于6位');
70 }
71 if ($DB->get_row_prepare("SELECT id FROM MN_docker_user WHERE username=? limit 1", [$user])) {
72 api_lifecycle_log('API开通Docker', '开通 ' . $user . ' 失败:账号已存在', '开通失败');
73 api_json_exit(100, '错误!该 Docker 账号已存在');
74 }
75 // 校验套餐(若指定)
76 if ($plan_id > 0) {
77 $plan = $DB->get_row_prepare("SELECT * FROM MN_docker_plan WHERE id=? limit 1", [$plan_id]);
78 if (!$plan || $plan['qk'] !== 'true') {
79 api_lifecycle_log('API开通Docker', '开通 ' . $user . ' 失败:套餐不可用', '开通失败');
80 api_json_exit(100, '错误!指定的套餐不存在或已下架');
81 }
82 } else {
83 $plan_id = null;
84 }
85
86 $hash = docker_auth_password_hash($pass);
87 $nodeId = (int)$bh;
88 $ok = $DB->query_prepare(
89 "INSERT INTO MN_docker_user (username,password_hash,email,ssbt,data,datae,qk,plan_id,container_status,created_at) VALUES (?,?,?,?,?,?,'active',?, 'none',?)",
90 [$user, $hash, $email, $nodeId, $date, $datae, $plan_id, $date]
91 );
92 if ($ok) {
93 api_lifecycle_log('API开通Docker', '开通 ' . $user . ' 成功(节点 ' . $bh . ')', '开通成功');
94 if (function_exists('mnbt_do_action')) {
95 mnbt_do_action('docker.user.created', ['username' => $user, 'ssbt' => $bh, 'plan_id' => $plan_id], ['source' => 'api']);
96 }
97 api_json_exit(200, 'Docker 账户开通成功!');
98 }
99 api_lifecycle_log('API开通Docker', '开通 ' . $user . ' 失败:数据库错误', '开通失败');
100 api_json_exit(100, '错误!开通失败:' . $DB->error());
101}
102
103// ========== 构建 bt_docker 实例(供后续 gn 使用) ==========
104$btipe = ($cert['ptl'] == 'true' ? 'https' : 'http') . '://' . $cert['btip'] . ':' . $cert['btdk'];
105$btkeye = $cert['btmy'];
106
107// ========== gn=zt 暂停 Docker 账户 ==========
108if ($gn === 'zt') {
109 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
110 if (!$urow) {
111 api_lifecycle_log('API暂停Docker', '暂停 ' . $user . ' 失败:账号不存在', '暂停失败');
112 api_json_exit(100, '错误!该 Docker 账号不存在');
113 }
114 if ($urow['qk'] === 'paused' || $urow['qk'] === 'expired') {
115 api_json_exit(200, 'Docker 账户已处于暂停/到期状态,无需重复操作');
116 }
117 // 有容器则先停止容器;停止失败时保留原状态,不置 paused(避免状态失真)
118 if (!empty($urow['container_id']) && !empty($urow['service_name'])) {
119 $bt = new bt_docker($btipe, $btkeye);
120 $stop_r = $bt->container_stop($urow['container_id'], $urow['service_name']);
121 if (!($stop_r['status'] ?? false)) {
122 mnbt_log('外部API', 'DockerAPI暂停', 'DockerAPI-' . $bh . ' ' . $user . ' 停容器失败:' . ($stop_r['msg'] ?? '未知'), '警告', $DB);
123 api_lifecycle_log('API暂停Docker', '暂停 ' . $user . ' 失败:停容器失败 ' . ($stop_r['msg'] ?? '未知'), '暂停失败');
124 api_json_exit(100, '错误!停止容器失败:' . ($stop_r['msg'] ?? '未知错误') . ',账户状态未变更');
125 }
126 $DB->query_prepare("UPDATE MN_docker_user SET container_status='stopped' WHERE id=?", [$urow['id']]);
127 }
128 $DB->query_prepare("UPDATE MN_docker_user SET qk='paused' WHERE id=?", [$urow['id']]);
129 api_lifecycle_log('API暂停Docker', '暂停 ' . $user . ' 成功', '暂停成功');
130 if (function_exists('mnbt_do_action')) {
131 mnbt_do_action('docker.user.paused', $urow, ['source' => 'api']);
132 }
133 api_json_exit(200, 'Docker 账户已暂停');
134}
135
136// ========== gn=jc 恢复 Docker 账户 ==========
137if ($gn === 'jc') {
138 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
139 if (!$urow) {
140 api_lifecycle_log('API恢复Docker', '恢复 ' . $user . ' 失败:账号不存在', '恢复失败');
141 api_json_exit(100, '错误!该 Docker 账号不存在');
142 }
143 if ($urow['qk'] !== 'paused') {
144 api_json_exit(100, '错误!该账户当前状态为 ' . $urow['qk'] . ',无法恢复(仅 paused 状态可恢复;expired 请走续费)');
145 }
146 // 恢复后若容器处于停止状态则自动启动容器;启动失败时不写 active,保持 paused 原状态
147 if ($urow['container_status'] === 'stopped' && !empty($urow['container_id']) && !empty($urow['service_name'])) {
148 $bt = new bt_docker($btipe, $btkeye);
149 $start_r = $bt->container_start($urow['container_id'], $urow['service_name']);
150 if (!($start_r['status'] ?? false)) {
151 api_lifecycle_log('API恢复Docker', '恢复 ' . $user . ' 失败:启动容器失败 ' . ($start_r['msg'] ?? '未知'), '恢复失败');
152 api_json_exit(100, '错误!启动容器失败:' . ($start_r['msg'] ?? '未知错误') . ',账户未恢复');
153 }
154 $DB->query_prepare("UPDATE MN_docker_user SET qk='active', container_status='running' WHERE id=?", [$urow['id']]);
155 } else {
156 $DB->query_prepare("UPDATE MN_docker_user SET qk='active' WHERE id=?", [$urow['id']]);
157 }
158 api_lifecycle_log('API恢复Docker', '恢复 ' . $user . ' 成功', '恢复成功');
159 if (function_exists('mnbt_do_action')) {
160 mnbt_do_action('docker.user.unpaused', $urow, ['source' => 'api']);
161 }
162 api_json_exit(200, 'Docker 账户已恢复');
163}
164
165// ========== gn=tj 删除 Docker 账户(立即删除容器 + 用户行) ==========
166if ($gn === 'tj') {
167 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
168 if (!$urow) {
169 api_lifecycle_log('API删除Docker', '删除 ' . $user . ' 失败:账号不存在', '删除失败');
170 api_json_exit(100, '错误!该 Docker 账号不存在');
171 }
172 // 有容器则先删除容器(service_name 非空即尝试删除,container_del 按 id+name 传参;失败则返回错误,不删用户行)
173 if (!empty($urow['service_name'])) {
174 $bt = new bt_docker($btipe, $btkeye);
175 $del_r = $bt->container_del((string)($urow['container_id'] ?? ''), $urow['service_name']);
176 if (!($del_r['status'] ?? false)) {
177 api_lifecycle_log('API删除Docker', '删除 ' . $user . ' 失败:删容器失败 ' . ($del_r['msg'] ?? '未知'), '删除失败');
178 api_json_exit(100, '错误!删除容器失败:' . ($del_r['msg'] ?? '未知错误'));
179 }
180 }
181 $DB->query_prepare("DELETE FROM MN_docker_user WHERE id=? LIMIT 1", [$urow['id']]);
182 api_lifecycle_log('API删除Docker', '删除 ' . $user . ' 成功', '删除成功');
183 if (function_exists('mnbt_do_action')) {
184 mnbt_do_action('docker.user.deleted', $urow, ['source' => 'api']);
185 }
186 api_json_exit(200, 'Docker 账户已删除');
187}
188
189// ========== gn=xf 续费 ==========
190if ($gn === 'xf') {
191 $setdate = daddslashes($_POST['setdate'] ?? '');
192 if (empty($setdate)) api_json_exit(100, '错误!缺少续费日期参数 setdate');
193 $new_datae = ($setdate === '0') ? '0000-00-00' : $setdate;
194
195 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
196 if (!$urow) {
197 api_lifecycle_log('API续费Docker', '续费 ' . $user . ' 失败:账号不存在', '续费失败');
198 api_json_exit(100, '错误!该 Docker 账号不存在');
199 }
200 $old_datae = $urow['datae'];
201 $updates = "datae=?";
202 $bind = [$new_datae];
203 $renew_msg = 'Docker 账户续费成功';
204 // 若原 expired 且新到期时间未过 → 恢复 active
205 if ($urow['qk'] === 'expired') {
206 if ($new_datae === '0000-00-00' || strtotime($date) - strtotime($new_datae) < 0) {
207 $updates .= ", qk='active', expired_at=NULL, prune_due=NULL";
208 // 尝试启动容器
209 if (!empty($urow['container_id']) && !empty($urow['service_name'])) {
210 $bt = new bt_docker($btipe, $btkeye);
211 $start_r = $bt->container_start($urow['container_id'], $urow['service_name']);
212 if ($start_r['status'] ?? false) {
213 $DB->query_prepare("UPDATE MN_docker_user SET container_status='running' WHERE id=?", [$urow['id']]);
214 }
215 }
216 }
217 } elseif ($urow['qk'] === 'pruned') {
218 // 容器已被到期清理删除,恢复可用状态;用户需重新创建容器
219 if ($new_datae === '0000-00-00' || strtotime($date) - strtotime($new_datae) < 0) {
220 $updates .= ", qk='active', container_status='none', prune_due=NULL, expired_at=NULL";
221 $renew_msg = '续费成功,原容器已被清理,请重新创建容器';
222 }
223 } elseif ($urow['qk'] === 'paused') {
224 // 暂停状态仅更新到期时间,不解除暂停(解停走 jc 恢复接口)
225 $renew_msg = '已续费,账户处于暂停状态,请联系管理员解停';
226 }
227 $bind[] = $urow['id'];
228 $DB->query_prepare("UPDATE MN_docker_user SET {$updates} WHERE id=?", $bind);
229 api_lifecycle_log('API续费Docker', '续费 ' . $user . ' ' . $old_datae . '=>' . $new_datae, '续费成功');
230 if (function_exists('mnbt_do_action')) {
231 mnbt_do_action('docker.user.renewed', array_merge($urow, ['datae' => $new_datae]), ['source' => 'api', 'old_date' => $old_datae, 'new_date' => $new_datae]);
232 }
233 api_json_exit(200, $renew_msg);
234}
235
236// ========== gn=bg 变更套餐 ==========
237if ($gn === 'bg') {
238 $new_plan_id = (int)($_POST['plan_id'] ?? 0);
239 if ($new_plan_id <= 0) api_json_exit(100, '错误!缺少套餐 ID 参数 plan_id');
240
241 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
242 if (!$urow) {
243 api_lifecycle_log('API变更套餐Docker', $user . ' 套餐变更失败:账号不存在', '变更失败');
244 api_json_exit(100, '错误!该 Docker 账号不存在');
245 }
246 $plan = $DB->get_row_prepare("SELECT * FROM MN_docker_plan WHERE id=? LIMIT 1", [$new_plan_id]);
247 if (!$plan || $plan['qk'] !== 'true') {
248 api_lifecycle_log('API变更套餐Docker', $user . ' 套餐变更失败:套餐不存在或已下架', '变更失败');
249 api_json_exit(100, '错误!指定的套餐不存在或已下架');
250 }
251 $old_plan_id = $urow['plan_id'];
252 $DB->query_prepare("UPDATE MN_docker_user SET plan_id=? WHERE id=?", [$new_plan_id, $urow['id']]);
253 api_lifecycle_log('API变更套餐Docker', $user . ' 套餐变更 ' . ($old_plan_id ?: '无') . '=>' . $new_plan_id, '变更成功');
254 if (function_exists('mnbt_do_action')) {
255 mnbt_do_action('docker.user.package_changed', array_merge($urow, ['plan_id' => $new_plan_id]), ['source' => 'api', 'old_plan_id' => $old_plan_id]);
256 }
257 api_json_exit(200, '套餐变更成功');
258}
259
260// ========== gn=czmm 重置密码 ==========
261if ($gn === 'czmm') {
262 $new_pass = (string)($_POST['password'] ?? '');
263 if (mb_strlen($new_pass) < 6) api_json_exit(100, '错误!密码不少于6位');
264
265 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
266 if (!$urow) {
267 api_lifecycle_log('API重置密码Docker', $user . ' 重置密码失败:账号不存在', '操作失败');
268 api_json_exit(100, '错误!该 Docker 账号不存在');
269 }
270 $new_hash = docker_auth_password_hash($new_pass);
271 $DB->query_prepare("UPDATE MN_docker_user SET password_hash=? WHERE id=?", [$new_hash, $urow['id']]);
272 api_lifecycle_log('API重置密码Docker', $user . ' 密码已重置(旧 session 自动失效)', '操作成功');
273 if (function_exists('mnbt_do_action')) {
274 mnbt_do_action('docker.user.password_reset', $urow, ['source' => 'api']);
275 }
276 api_json_exit(200, '密码重置成功');
277}
278
279// ========== gn=ztcx 状态查询 ==========
280if ($gn === 'ztcx') {
281 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
282 if (!$urow) api_json_exit(100, '错误!该 Docker 账号不存在');
283
284 $userData = [
285 'username' => $urow['username'],
286 'qk' => $urow['qk'],
287 'datae' => $urow['datae'],
288 'plan_id' => $urow['plan_id'] ? (int)$urow['plan_id'] : null,
289 'container_status' => $urow['container_status'] ?: 'none',
290 'service_name' => $urow['service_name'],
291 'app_name' => $urow['app_name'],
292 'disk_usage' => (int)$urow['disk_usage'],
293 'disk_usage_at' => $urow['disk_usage_at'],
294 ];
295 $nodeData = ['btip' => $cert['btip'], 'ptl' => $cert['ptl']];
296 $containerData = null;
297
298 // 有容器则查 installed_apps 获取详情
299 if (!empty($urow['service_name'])) {
300 $bt = new bt_docker($btipe, $btkeye);
301 $apps = $bt->installed_apps();
302 if (is_array($apps)) {
303 $svc = $urow['service_name'];
304 foreach ($apps as $app) {
305 $asvc = $app['service_name'] ?? '';
306 if ($asvc === $svc || (strlen($svc) > 0 && strpos($asvc, $svc) === 0)) {
307 $containerData = [
308 'service_name' => $asvc,
309 'appname' => $app['appname'] ?? '',
310 'apptitle' => $app['apptitle'] ?? '',
311 'appdesc' => $app['appdesc'] ?? '',
312 'status' => $app['status'] ?? 'unknown',
313 'port' => $app['port'] ?? [],
314 'container_id' => $app['container_id'] ?? '',
315 'server_ip' => $app['server_ip'] ?? $cert['btip'],
316 'host_ip' => $app['host_ip'] ?? '',
317 'm_version' => $app['m_version'] ?? '',
318 's_version' => $app['s_version'] ?? '',
319 'version' => $app['version'] ?? '',
320 'appinfo' => $app['appinfo'] ?? [],
321 ];
322 // 同步容器状态到数据库
323 $cs = $app['status'] ?? '';
324 if (in_array($cs, ['running', 'stopped', 'creating']) && $urow['container_status'] !== $cs) {
325 $DB->query_prepare("UPDATE MN_docker_user SET container_status=?, container_id=? WHERE id=?", [$cs, $containerData['container_id'], $urow['id']]);
326 }
327 break;
328 }
329 }
330 }
331 }
332
333 api_json_exit(200, 'ok', [
334 'data' => [
335 'user' => $userData,
336 'container' => $containerData,
337 'node' => $nodeData,
338 ],
339 ]);
340}
341
342// ========== gn=sy 用量查询 ==========
343if ($gn === 'sy') {
344 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
345 if (!$urow) api_json_exit(100, '错误!该 Docker 账号不存在');
346
347 $disk_max = 0;
348 if ($urow['plan_id']) {
349 $plan = $DB->get_row_prepare("SELECT disk_max FROM MN_docker_plan WHERE id=? LIMIT 1", [(int)$urow['plan_id']]);
350 $disk_max = $plan ? (int)$plan['disk_max'] : 0;
351 }
352 $disk_usage = (int)$urow['disk_usage'];
353
354 // 容器运行中则实时刷新磁盘用量
355 if ($urow['container_status'] === 'running' && !empty($urow['service_name'])) {
356 $bt = new bt_docker($btipe, $btkeye);
357 $apps = $bt->installed_apps();
358 if (is_array($apps)) {
359 $svc = $urow['service_name'];
360 foreach ($apps as $app) {
361 $asvc = $app['service_name'] ?? '';
362 if (($asvc === $svc || (strlen($svc) > 0 && strpos($asvc, $svc) === 0)) && !empty($app['path'])) {
363 $sz = $bt->get_path_size($app['path']);
364 if (isset($sz['size'])) {
365 $disk_usage = (int)$sz['size'];
366 $DB->query_prepare("UPDATE MN_docker_user SET disk_usage=?, disk_usage_at=? WHERE id=?", [$disk_usage, $date, $urow['id']]);
367 }
368 break;
369 }
370 }
371 }
372 }
373
374 $quota_reached = ($disk_max > 0 && $disk_usage >= $disk_max * 1048576);
375
376 api_json_exit(200, 'ok', [
377 'data' => [
378 'disk_usage' => $disk_usage,
379 'disk_max' => $disk_max * 1048576, // MB → bytes
380 'disk_max_mb' => $disk_max,
381 'unit' => 'bytes',
382 'quota_reached' => $quota_reached,
383 ],
384 ]);
385}
386
387// ========== gn=start/stop/restart 容器启停 ==========
388if ($gn === 'start' || $gn === 'stop' || $gn === 'restart') {
389 $urow = $DB->get_row_prepare("SELECT * FROM MN_docker_user WHERE username=? LIMIT 1", [$user]);
390 if (!$urow) api_json_exit(100, '错误!该 Docker 账号不存在');
391 if ($urow['qk'] !== 'active') api_json_exit(100, '错误!账户状态为 ' . $urow['qk'] . ',无法操作容器');
392 if (empty($urow['container_id']) || empty($urow['service_name'])) api_json_exit(100, '错误!该账户尚未创建容器');
393
394 $bt = new bt_docker($btipe, $btkeye);
395 $labels = ['start' => '启动', 'stop' => '停止', 'restart' => '重启'];
396 $statusMap = ['start' => 'running', 'stop' => 'stopped', 'restart' => 'running'];
397
398 if ($gn === 'start') {
399 $r = $bt->container_start($urow['container_id'], $urow['service_name']);
400 } elseif ($gn === 'stop') {
401 $r = $bt->container_stop($urow['container_id'], $urow['service_name']);
402 } else {
403 $r = $bt->container_restart($urow['container_id'], $urow['service_name']);
404 }
405
406 if ($r['status'] ?? false) {
407 $DB->query_prepare("UPDATE MN_docker_user SET container_status=? WHERE id=?", [$statusMap[$gn], $urow['id']]);
408 api_lifecycle_log('DockerAPI容器操作', $labels[$gn] . '容器 ' . $user, '操作成功');
409 api_json_exit(200, '容器已' . $labels[$gn]);
410 }
411 api_lifecycle_log('DockerAPI容器操作', $labels[$gn] . '容器 ' . $user . ' 失败:' . ($r['msg'] ?? '未知'), '操作失败');
412 api_json_exit(100, '容器' . $labels[$gn] . '失败:' . ($r['msg'] ?? '未知错误'));
413}
414
415api_json_exit(100, '未知指令');