better-staridc-MNBT
1<?php
2/*
3 * Docker 到期软删与物理清理定时任务
4 * 建议每 30 分钟执行一次:/docker_cron.php?my=后台API密钥
5 *
6 * 流程:
7 * 1) active 但已到期 → qk=expired, expired_at=到期时间
8 * 2) expired 满 7 天 → 删除节点容器,qk=pruned, prune_due=当天
9 * 3) pruned 满 7 天 → 物理删除用户行
10 */
11include("./MPHX/common.php");
12include_once SYSTEM_ROOT . 'bt_docker.php';
13if (($_GET['my'] ?? '') != $conf['api']) exit('密钥错误');
14@header('Content-Type: text/plain; charset=UTF-8');
15
16$now = time();
17$today = date('Y-m-d', $now);
18$log = [];
19
20// 1) active → expired
21$expiring = $DB->get_all_prepare("SELECT * FROM MN_docker_user WHERE qk='active' AND datae<>'0000-00-00' AND datae<>'' AND datae < ?", [$today]) ?: [];
22foreach ($expiring as $u) {
23 $DB->query_prepare("UPDATE MN_docker_user SET qk='expired', expired_at=? WHERE id=?", [$u['datae'], $u['id']]);
24 mnbt_log($u['username'], 'Docker到期', '账户到期软删 ' . $u['username'], '已标记 expired', $DB);
25 $log[] = 'expired: ' . $u['username'];
26}
27
28// 2) expired 满 7 天 → 删除容器 → pruned
29// 候选条件附带 prune_due < 当天:回收上次运行中断遗留的抢占标记(prune_due 被占位但未完成删除的行)
30$grace = 7 * 86400;
31$pruneCandidates = $DB->get_all_prepare("SELECT * FROM MN_docker_user WHERE qk='expired' AND (prune_due IS NULL OR prune_due < ?) AND expired_at<>'' AND expired_at IS NOT NULL", [$today]) ?: [];
32foreach ($pruneCandidates as $u) {
33 if (strtotime($u['expired_at']) + $grace > $now) continue; // 未满 7 天
34 // 乐观锁抢占:删容器前用条件更新占位,防止与并发续费(api/docker.php xf)交错,
35 // affected=0 说明用户已被续费复活或状态已变,跳过该用户,不删其容器
36 $DB->query_prepare("UPDATE MN_docker_user SET prune_due=? WHERE id=? AND qk='expired'", [$today, $u['id']]);
37 if ($DB->affected() <= 0) {
38 $log[] = 'prune-skip: ' . $u['username'] . '(抢占失败,已被续费或状态变更)';
39 continue;
40 }
41 // 删除节点容器:container_id 为空但 service_name 非空时按 service_name 尝试删除(container_del 按 id+name 传参)
42 $del_ok = true;
43 $del_msg = '';
44 if (!empty($u['container_id']) || !empty($u['service_name'])) {
45 $node = $DB->get_row_prepare("SELECT * FROM MN_docker_node WHERE id=? limit 1", [(int)$u['ssbt']]);
46 if ($node) {
47 $url = ($node['ptl'] === 'true' ? 'https' : 'http') . '://' . $node['btip'] . ':' . $node['btdk'];
48 $api = new bt_docker($url, $node['btmy']);
49 $del_r = $api->container_del((string)($u['container_id'] ?? ''), (string)($u['service_name'] ?? ''));
50 if (!($del_r['status'] ?? false)) {
51 $del_ok = false;
52 $del_msg = (string)($del_r['msg'] ?? '未知错误');
53 }
54 } else {
55 $del_ok = false;
56 $del_msg = '节点不存在';
57 }
58 }
59 if (!$del_ok) {
60 // 删除失败:不置 pruned(避免出现删不掉容器的死状态),释放抢占标记,下一轮 cron 重试
61 $DB->query_prepare("UPDATE MN_docker_user SET prune_due=NULL WHERE id=? AND qk='expired'", [$u['id']]);
62 mnbt_log($u['username'], 'Docker清理', '到期删除容器失败:' . $del_msg . ',保留待下轮重试', '删除失败', $DB);
63 $log[] = 'prune-failed: ' . $u['username'];
64 continue;
65 }
66 // 置 pruned 同样带 qk='expired' 条件:若期间用户已续费复活则不再改写其状态
67 $DB->query_prepare("UPDATE MN_docker_user SET qk='pruned', prune_due=?, container_id=NULL, container_status='none' WHERE id=? AND qk='expired'", [$today, $u['id']]);
68 if ($DB->affected() > 0) {
69 mnbt_log($u['username'], 'Docker清理', '到期7天删除容器 ' . $u['username'], '已 pruned', $DB);
70 $log[] = 'pruned: ' . $u['username'];
71 } else {
72 mnbt_log($u['username'], 'Docker清理', '置 pruned 前状态已变更(可能已续费),跳过', '警告', $DB);
73 $log[] = 'prune-skip: ' . $u['username'] . '(置 pruned 前状态已变更)';
74 }
75}
76
77// 3) pruned 满 7 天 → 物理删除
78$pruned = $DB->get_all_prepare("SELECT * FROM MN_docker_user WHERE qk='pruned' AND prune_due IS NOT NULL AND prune_due<>''") ?: [];
79foreach ($pruned as $u) {
80 if (strtotime($u['prune_due']) + $grace > $now) continue;
81 $DB->query_prepare("DELETE FROM MN_docker_user WHERE id=? limit 1", [$u['id']]);
82 mnbt_log($u['username'], 'Docker清理', '物理删除到期用户 ' . $u['username'], '已删除', $DB);
83 $log[] = 'deleted: ' . $u['username'];
84}
85
86echo 'docker_cron done @ ' . date('Y-m-d H:i:s') . PHP_EOL;
87echo implode(PHP_EOL, $log);