仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ install/install.api.php 43.8 KB · 934 行 原始文件
Z zfhsh first commit 2 天前
1<?php
2error_reporting(0);
3@header('Content-Type: text/html; charset=UTF-8');
4define('IN_CRONLITE', true);
5include("../cf_up.php");
6include("../MPHX/BL.php");
7include_once("../MPHX/Response.php");
8$action = $_GET['action'] ?? 'index';
9$vs = mnbt_version_num();
10
11function Res(int $code, string $msg = '返回信息', ?array $data = null, ?int $redirect = null)
12{
13 return Response::json($code, $msg, $data, $redirect);
14}
15
16function table_exists_case($tableName) {
17 $result = DB::query("SHOW TABLES");
18 if ($result) {
19 while ($row = DB::fetch($result)) {
20 if (strcasecmp(reset($row), $tableName) === 0) return true;
21 }
22 }
23 return false;
24}
25
26// 返回规范表名 → 实际表名的映射(兼容大小写迁移场景)
27function table_case_map($canonical_list) {
28 $map = array();
29 $result = DB::query("SHOW TABLES");
30 if ($result) {
31 $existing = array();
32 while ($row = DB::fetch($result)) {
33 $existing[] = reset($row);
34 }
35 foreach ($canonical_list as $canon) {
36 foreach ($existing as $real) {
37 if (strcasecmp($real, $canon) === 0) {
38 $map[$canon] = $real;
39 break;
40 }
41 }
42 }
43 }
44 return $map;
45}
46
47// 读取表的全部列名(小写),表不存在时返回空数组
48function mnbt_table_columns($table) {
49 $cols = array();
50 if (!table_exists_case($table)) return $cols;
51 $result = DB::query("SHOW COLUMNS FROM `{$table}`");
52 if ($result) {
53 while ($row = DB::fetch($result)) {
54 $cols[] = strtolower($row['Field']);
55 }
56 }
57 return $cols;
58}
59
60// 标准表清单(含 V1.83 Docker 集成四表、V1.85 迁移游标表 MN_dbver)
61function mnbt_standard_tables() {
62 return array('MN_config','MN_log','MN_bt','MN_zj','MN_bs','MN_ym','MN_dd',
63 'MN_monitor_task','MN_monitor_log','MN_notice_log',
64 'MN_node','MN_node_task','MN_node_nonce',
65 'MN_forbidden_scan','MN_forbidden_match',
66 'MN_plugin','MN_plugin_option',
67 'MN_docker_node','MN_docker_user','MN_docker_plan','MN_docker_order','MN_dbver');
68}
69
70// 各版本增量字段定义(随发版追加,勿在此写死当前版本号):表 => (列名 => ALTER 语句)
71// 实际表名由调用方传入(兼容大小写迁移场景);仅添加缺失列
72function mnbt_upgrade_columns($actual_config, $actual_zj, $actual_bt, $actual_dplan, $actual_duser) {
73 return array(
74 // V1.81 修复字段
75 $actual_config => array(
76 'mailhost' => "ALTER TABLE `{$actual_config}` ADD `mailhost` VARCHAR(50) NULL DEFAULT NULL",
77 'mailuser' => "ALTER TABLE `{$actual_config}` ADD `mailuser` VARCHAR(50) NULL DEFAULT NULL",
78 'mailpassword' => "ALTER TABLE `{$actual_config}` ADD `mailpassword` VARCHAR(50) NULL DEFAULT NULL",
79 'mailport' => "ALTER TABLE `{$actual_config}` ADD `mailport` VARCHAR(20) NOT NULL DEFAULT '465'",
80 'ymjkkg' => "ALTER TABLE `{$actual_config}` ADD `ymjkkg` VARCHAR(20) NOT NULL DEFAULT 'false'",
81 'mtyxfskg' => "ALTER TABLE `{$actual_config}` ADD `mtyxfskg` VARCHAR(20) NOT NULL DEFAULT 'false'",
82 'ymjktsyz' => "ALTER TABLE `{$actual_config}` ADD `ymjktsyz` VARCHAR(20) NOT NULL DEFAULT '7'",
83 'wjjkkg' => "ALTER TABLE `{$actual_config}` ADD `wjjkkg` VARCHAR(20) NOT NULL DEFAULT 'false'",
84 'mtwjfskg' => "ALTER TABLE `{$actual_config}` ADD `mtwjfskg` VARCHAR(50) NOT NULL DEFAULT 'false'",
85 'wjjktsyz' => "ALTER TABLE `{$actual_config}` ADD `wjjktsyz` VARCHAR(20) NOT NULL DEFAULT '7'",
86 'optionzc' => "ALTER TABLE `{$actual_config}` ADD `optionzc` VARCHAR(20) NOT NULL DEFAULT 'stop'",
87 'zjyxbd' => "ALTER TABLE `{$actual_config}` ADD `zjyxbd` VARCHAR(20) NOT NULL DEFAULT 'true'",
88 'wjsckg' => "ALTER TABLE `{$actual_config}` ADD `wjsckg` VARCHAR(20) NOT NULL DEFAULT 'false'",
89 'wjsccnr' => "ALTER TABLE `{$actual_config}` ADD `wjsccnr` TEXT NULL DEFAULT NULL",
90 'wjsckgqbfx' => "ALTER TABLE `{$actual_config}` ADD `wjsckgqbfx` VARCHAR(10) NOT NULL DEFAULT 'true'",
91 'wjscml' => "ALTER TABLE `{$actual_config}` ADD `wjscml` VARCHAR(500) NOT NULL DEFAULT '/www/wwwroot'",
92 'wjstqml' => "ALTER TABLE `{$actual_config}` ADD `wjstqml` TEXT NULL DEFAULT NULL",
93 'wjstqhz' => "ALTER TABLE `{$actual_config}` ADD `wjstqhz` TEXT NULL DEFAULT NULL",
94 'wjscdzmax' => "ALTER TABLE `{$actual_config}` ADD `wjscdzmax` INT(11) NOT NULL DEFAULT 5242880",
95 'wjscdhmax' => "ALTER TABLE `{$actual_config}` ADD `wjscdhmax` INT(11) NOT NULL DEFAULT 1000",
96 'wjscqzcs' => "ALTER TABLE `{$actual_config}` ADD `wjscqzcs` VARCHAR(50) NOT NULL DEFAULT '0 3 * * *'",
97 'wjscqzcskg' => "ALTER TABLE `{$actual_config}` ADD `wjscqzcskg` VARCHAR(20) NOT NULL DEFAULT 'true'",
98 'pay_methods' => "ALTER TABLE `{$actual_config}` ADD `pay_methods` TEXT NOT NULL DEFAULT ''",
99 // V1.84 独立主页系统(与 update/update_v184_home.sql 保持一致)
100 'home_enable' => "ALTER TABLE `{$actual_config}` ADD `home_enable` varchar(10) NOT NULL DEFAULT 'true'",
101 'home_theme' => "ALTER TABLE `{$actual_config}` ADD `home_theme` varchar(50) NOT NULL DEFAULT ''",
102 'home_theme_settings' => "ALTER TABLE `{$actual_config}` ADD `home_theme_settings` text NOT NULL",
103 'home_title' => "ALTER TABLE `{$actual_config}` ADD `home_title` text NOT NULL",
104 'home_hero' => "ALTER TABLE `{$actual_config}` ADD `home_hero` text NOT NULL",
105 'home_primary' => "ALTER TABLE `{$actual_config}` ADD `home_primary` varchar(10) NOT NULL DEFAULT '#4f46e5'",
106 'home_logo' => "ALTER TABLE `{$actual_config}` ADD `home_logo` text NOT NULL",
107 'home_favicon' => "ALTER TABLE `{$actual_config}` ADD `home_favicon` text NOT NULL",
108 'home_footer' => "ALTER TABLE `{$actual_config}` ADD `home_footer` text NOT NULL",
109 'home_show_notice' => "ALTER TABLE `{$actual_config}` ADD `home_show_notice` varchar(10) NOT NULL DEFAULT 'true'",
110 'home_show_plans' => "ALTER TABLE `{$actual_config}` ADD `home_show_plans` varchar(10) NOT NULL DEFAULT 'true'",
111 // V1.85 对外 API 协议模式开关(0=1.83+ 严格,1=1.81 兼容)
112 'api_compat' => "ALTER TABLE `{$actual_config}` ADD `api_compat` varchar(10) NOT NULL DEFAULT '0'",
113 ),
114 $actual_zj => array(
115 'backup' => "ALTER TABLE `{$actual_zj}` ADD `backup` VARCHAR(50) NOT NULL DEFAULT '{\"max\":\"3\",\"dq\":0}'",
116 'mailuser' => "ALTER TABLE `{$actual_zj}` ADD `mailuser` VARCHAR(50) NULL DEFAULT NULL",
117 ),
118 $actual_bt => array(
119 'ftpdz' => "ALTER TABLE `{$actual_bt}` ADD `ftpdz` VARCHAR(50) NOT NULL DEFAULT 'false'",
120 'mrbts_php' => "ALTER TABLE `{$actual_bt}` ADD `mrbts_php` VARCHAR(10) NOT NULL DEFAULT ''",
121 ),
122 // V1.83.1 Docker 磁盘配额字段(表已在 repair_tables.sql 中补齐)
123 $actual_dplan => array(
124 'disk_max' => "ALTER TABLE `{$actual_dplan}` ADD `disk_max` varchar(20) NOT NULL DEFAULT '0' COMMENT '磁盘配额 MB 上限(0=不限制)'",
125 'proxy_max' => "ALTER TABLE `{$actual_dplan}` ADD `proxy_max` varchar(20) NOT NULL DEFAULT '0' COMMENT '反向代理数量上限(0=不限制)'",
126 ),
127 $actual_duser => array(
128 'disk_usage' => "ALTER TABLE `{$actual_duser}` ADD `disk_usage` bigint(20) NOT NULL DEFAULT '0' COMMENT '最近磁盘用量(字节)'",
129 'disk_usage_at' => "ALTER TABLE `{$actual_duser}` ADD `disk_usage_at` varchar(50) DEFAULT NULL COMMENT '磁盘用量采集时间'",
130 ),
131 );
132}
133
134if (file_exists('install.lock')) exit(Res(1, '已安装', ['vs' => $vs,'is_install'=>true], 1));
135
136function send_post()
137{
138 //如果需要进行离线安装请将$ins_tall=true;改为$ins_tall=false;
139 //注意:离线安装不支持进行在线更新!
140 //如果无法安装可尝试进行离线安装!
141 try {
142 $ins_tall = true;
143 if (!$ins_tall) return array('code' => 1, 'authcode' => '您安装时使用的为离线安装!');
144 global $mn_conf;
145 if (empty($mn_conf['url']) || empty($mn_conf['aet']) || empty($mn_conf['install_wj'])) {
146 return array('code' => 1, 'authcode' => '');
147 }
148 include("../MPHX/BL.php");
149 $url = $mn_conf['aet'] . "://" . $mn_conf['url'] . ":" . $mn_conf['port'] . "/" . $mn_conf['install_wj'] . "/coder.php";
150 $post_data = array(
151 'url' => $_SERVER['HTTP_HOST'],
152 'bb' => $WEBQB,
153 );
154 $postdata = http_build_query($post_data);
155 $options = array(
156 'http' => array(
157 'method' => 'POST',
158 'header' => 'Content-type:application/x-www-form-urlencoded',
159 'content' => $postdata,
160 'timeout' => 5 // 缩短超时
161 )
162 );
163 $context = stream_context_create($options);
164 $result = @file_get_contents($url, false, $context);
165 if ($result === false) return array('code' => 1, 'authcode' => '');
166 $decoded = json_decode($result, true);
167 if (!is_array($decoded)) return array('code' => 1, 'authcode' => '');
168 return $decoded;
169 } catch (Exception $e) {
170 return array('code' => 1, 'authcode' => '');
171 }
172}
173
174switch ($action) {
175 case 'index':
176 echo Res(1, '基础信息返回', ['vs' => $vs]);
177 break;
178 case 'system':
179 echo Res(1, '系统基础环境监测', [
180 'vs' => [
181 'info' => PHP_VERSION,
182 'is_vs_install' => version_compare(PHP_VERSION, '7.4.0', '>=')
183 ],
184 'curl_exec' => function_exists('curl_exec'),
185 'mn_link' => ((int)send_post()['code'] ?? 0) === 1,
186 ]);
187 break;
188 case 'database_info_wire':
189 require_once './db.class.php';
190 $db_host = isset($_POST['db_host']) ? $_POST['db_host'] : NULL;
191 $db_port = isset($_POST['db_port']) ? $_POST['db_port'] : NULL;
192 $db_user = isset($_POST['db_user']) ? $_POST['db_user'] : NULL;
193 $db_pwd = isset($_POST['db_pwd']) ? $_POST['db_pwd'] : NULL;
194 $db_name = isset($_POST['db_name']) ? $_POST['db_name'] : NULL;
195 if ($db_host == null || $db_port == null || $db_user == null || $db_pwd == null || $db_name == null) {
196 echo '<div class="alert alert-danger">保存错误,请确保每项都不为空<hr/><a href="javascript:history.back(-1)"><< 返回上一页</a></div>';
197 exit;
198 }
199 $result = send_post();
200 if (is_array($result) && $result['code'] == '0') {
201 exit(Res(0,$result['msg']));
202 } else {
203 $config = "<?php
204 /*数据库配置*/
205 \$dbconfig=array(
206 'host' => '{$db_host}', //数据库服务器
207 'port' => {$db_port}, //数据库端口
208 'user' => '{$db_user}', //数据库用户名
209 'pwd' => '{$db_pwd}', //数据库密码
210 'dbname' => '{$db_name}', //数据库名
211 );
212 ?>";
213 $mnAuthcode="<?php
214 \$authcode='{$result["authcode"]}';
215 ?>";
216 }
217 if(!$con=DB::connect($db_host,$db_user,$db_pwd,$db_name,$db_port)){
218 $enumMsg=[
219 2002=>'连接数据库失败,数据库地址填写错误!',
220 1045=>'连接数据库失败,数据库用户名或密码填写错误!',
221 1049=>'连接数据库失败,数据库名不存在!',
222 ];
223 exit(Res(0,$enumMsg[DB::connect_errno()]??'['.DB::connect_errno().']'.DB::connect_error()));
224 }else{
225 $cfgw = file_put_contents('../config.php', $config);
226 $sqw = file_put_contents('../MPHX/SQ.php', $mnAuthcode);
227 if ($cfgw !== false && $sqw !== false) {
228 // 用 SHOW TABLES 获取全部表名,PHP 端忽略大小写比较
229 $all_tables_result = DB::query("SHOW TABLES");
230 $found = array();
231 $in_table = false;
232 if ($all_tables_result) {
233 while ($row = DB::fetch($all_tables_result)) {
234 $tbl = reset($row);
235 $found[] = $tbl;
236 if (strcasecmp($tbl, 'MN_config') === 0) $in_table = true;
237 }
238 }
239 $diag = array('in_table' => $in_table);
240 if (!$in_table) {
241 $diag['db_error'] = DB::error() ?: '';
242 $diag['all_tables'] = $found;
243 }
244 echo Res(1, '数据库信息保存成功', $diag);
245 } else {
246 $err_detail = '';
247 if ($cfgw === false) $err_detail .= 'config.php 写入失败;';
248 if ($sqw === false) $err_detail .= 'MPHX/SQ.php 写入失败;';
249 if (!is_writable('..')) $err_detail .= '上级目录不可写;';
250 if (!is_writable('../MPHX')) $err_detail .= 'MPHX目录不可写;';
251 if (file_exists('../config.php') && !is_writable('../config.php')) $err_detail .= 'config.php存在但不可覆盖;';
252 if (file_exists('../MPHX/SQ.php') && !is_writable('../MPHX/SQ.php')) $err_detail .= 'MPHX/SQ.php存在但不可覆盖;';
253 echo Res(0, '数据库信息保存失败:' . $err_detail);
254 }
255 }
256 break;
257 case 'check_upgrade':
258 include_once '../config.php';
259 if (!$dbconfig['user'] || !$dbconfig['pwd'] || !$dbconfig['dbname']) {
260 exit(Res(0, '请先填写并保存数据库配置'));
261 }
262 require_once './db.class.php';
263 $cn = DB::connect($dbconfig['host'], $dbconfig['user'], $dbconfig['pwd'], $dbconfig['dbname'], $dbconfig['port']);
264 if (!$cn) {
265 exit(Res(0, '数据库连接失败:' . DB::connect_error()));
266 }
267 DB::query("set sql_mode = ''");
268 DB::query("set names utf8");
269
270 $has_config = table_exists_case('MN_config');
271 $result = array(
272 'is_v179' => false,
273 'need_upgrade' => false,
274 'missing_tables' => array(),
275 'missing_columns' => array(),
276 );
277
278 if ($has_config) {
279 $result['is_v179'] = true;
280
281 // 获取当前数据库中所有表(忽略大小写)
282 $all_tables_result = DB::query("SHOW TABLES");
283 $existing_tables = array();
284 if ($all_tables_result) {
285 while ($row = DB::fetch($all_tables_result)) {
286 $existing_tables[] = strtolower(reset($row));
287 }
288 }
289
290 // 检查所有标准表
291 $all_tables = mnbt_standard_tables();
292 foreach ($all_tables as $tbl) {
293 if (!in_array(strtolower($tbl), $existing_tables, true)) {
294 $result['need_upgrade'] = true;
295 $result['missing_tables'][] = $tbl;
296 }
297 }
298
299 // 检查 V1.81 新增字段
300 $col_pay = DB::get_row("SHOW COLUMNS FROM `MN_config` LIKE 'pay_methods'");
301 if (!$col_pay) {
302 $result['need_upgrade'] = true;
303 $result['missing_columns'][] = 'MN_config.pay_methods';
304 }
305
306 $col_php = DB::get_row("SHOW COLUMNS FROM `MN_bt` LIKE 'mrbts_php'");
307 if (!$col_php) {
308 $result['need_upgrade'] = true;
309 $result['missing_columns'][] = 'MN_bt.mrbts_php';
310 }
311
312 // 检查 V1.84 新增字段(独立主页系统)
313 $col_home = DB::get_row("SHOW COLUMNS FROM `MN_config` LIKE 'home_enable'");
314 if (!$col_home) {
315 $result['need_upgrade'] = true;
316 $result['missing_columns'][] = 'MN_config.home_enable';
317 }
318
319 // 检查 V1.85 新增字段(对外 API 协议模式开关)
320 $col_api_compat = DB::get_row("SHOW COLUMNS FROM `MN_config` LIKE 'api_compat'");
321 if (!$col_api_compat) {
322 $result['need_upgrade'] = true;
323 $result['missing_columns'][] = 'MN_config.api_compat';
324 }
325 }
326
327 echo Res(1, '升级检测完成', $result);
328 break;
329
330 case 'repair':
331 include_once '../config.php';
332 if (!$dbconfig['user'] || !$dbconfig['pwd'] || !$dbconfig['dbname']) {
333 exit(Res(0, '请先填写并保存数据库配置'));
334 }
335 require_once './db.class.php';
336 $cn = DB::connect($dbconfig['host'], $dbconfig['user'], $dbconfig['pwd'], $dbconfig['dbname'], $dbconfig['port']);
337 if (!$cn) {
338 exit(Res(0, '数据库连接失败:' . DB::connect_error()));
339 }
340 DB::query("set sql_mode = ''");
341 DB::query("set names utf8");
342
343 $r_t = 0; $r_e = 0;
344
345 // 获取规范名→实际名映射(兼容跨系统迁移后大小写不一致)
346 $tbl_map = table_case_map(mnbt_standard_tables());
347
348 // 1. 补齐缺失的表(跳过已存在的,不管大小写)
349 $sql = file_get_contents("repair_tables.sql");
350 $sql = explode(';', $sql);
351 for ($i = 0; $i < count($sql); $i++) {
352 $q = trim($sql[$i]);
353 if ($q === '') continue;
354 // 提取表名,检查是否已存在
355 if (preg_match('/CREATE TABLE.*?`(\w+)`/', $q, $m)) {
356 if (isset($tbl_map[$m[1]])) continue; // 表已存在(任意大小写),跳过
357 }
358 if (DB::query($q)) {
359 ++$r_t;
360 } else {
361 ++$r_e;
362 }
363 }
364
365 // 2. 补齐字段(使用实际表名,仅添加缺失列)
366 $actual_config = isset($tbl_map['MN_config']) ? $tbl_map['MN_config'] : 'MN_config';
367 $actual_zj = isset($tbl_map['MN_zj']) ? $tbl_map['MN_zj'] : 'MN_zj';
368 $actual_bt = isset($tbl_map['MN_bt']) ? $tbl_map['MN_bt'] : 'MN_bt';
369 $actual_dplan = isset($tbl_map['MN_docker_plan']) ? $tbl_map['MN_docker_plan'] : 'MN_docker_plan';
370 $actual_duser = isset($tbl_map['MN_docker_user']) ? $tbl_map['MN_docker_user'] : 'MN_docker_user';
371
372 $alter_sqls = mnbt_upgrade_columns($actual_config, $actual_zj, $actual_bt, $actual_dplan, $actual_duser);
373
374 foreach ($alter_sqls as $table_name => $cols) {
375 $existing_cols = mnbt_table_columns($table_name);
376 if (empty($existing_cols)) continue; // 表不存在,repair_tables 已尝试创建
377 foreach ($cols as $col_name => $alter_sql) {
378 if (!in_array(strtolower($col_name), $existing_cols, true)) {
379 if (DB::query($alter_sql)) {
380 ++$r_t;
381 } else {
382 ++$r_e;
383 }
384 }
385 }
386 }
387
388 @file_put_contents("install.lock", '安装锁');
389 echo Res(1, "修复完成!成功{$r_t}项,失败{$r_e}项", array('tbl_map' => $tbl_map));
390 break;
391
392 case 'install':
393 $site_name = isset($_POST['site_name']) ? trim((string)$_POST['site_name']) : '';
394 $site_qq = isset($_POST['site_qq']) ? trim((string)$_POST['site_qq']) : '';
395 $site_gg = isset($_POST['site_gg']) ? trim((string)$_POST['site_gg']) : '';
396 $admin_user = isset($_POST['admin_user']) ? trim((string)$_POST['admin_user']) : '';
397 $admin_pwd = isset($_POST['admin_pwd']) ? (string)$_POST['admin_pwd'] : '';
398
399 if ($site_name === '' || $admin_user === '' || $admin_pwd === '') {
400 exit(Res(0, '请填写站点名称、管理员账号与密码'));
401 }
402 if (mb_strlen($site_name) > 80) {
403 exit(Res(0, '控制面板名称过长'));
404 }
405 if (mb_strlen($admin_user) < 3 || mb_strlen($admin_user) > 50) {
406 exit(Res(0, '管理员账号长度需在 3~50 位'));
407 }
408 if (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}-]+$/u', $admin_user)) {
409 exit(Res(0, '管理员账号含非法字符'));
410 }
411 if (strlen($admin_pwd) < 6 || strlen($admin_pwd) > 64) {
412 exit(Res(0, '管理员密码长度需在 6~64 位'));
413 }
414 if ($site_qq !== '' && !preg_match('/^\d{5,15}$/', $site_qq)) {
415 exit(Res(0, 'QQ 号格式不正确'));
416 }
417 if (mb_strlen($site_gg) > 2000) {
418 exit(Res(0, '网站公告过长'));
419 }
420
421 include_once '../config.php';
422 if (!$dbconfig['user'] || !$dbconfig['pwd'] || !$dbconfig['dbname']) {
423 exit(Res(0,'请先填写好数据库并保存后再安装!',null,1));
424 }
425 require_once './db.class.php';
426 $cn = DB::connect($dbconfig['host'], $dbconfig['user'], $dbconfig['pwd'], $dbconfig['dbname'], $dbconfig['port']);
427 if (!$cn) {
428 exit(Res(0, '数据库错误:' . DB::connect_error(), null, 1));
429 }
430 DB::query("set sql_mode = ''");
431 DB::query("set names utf8");
432
433 $install_mode = isset($_POST['install_mode']) ? (string)$_POST['install_mode'] : 'install';
434 $t = 0;
435 $e = 0;
436 $error = '';
437 $skip_sql = ($install_mode === 'skip');
438
439 if ($install_mode === 'upgrade') {
440 // 获取规范名→实际名映射(含 V1.83 Docker 集成四表)
441 $tbl_map = table_case_map(mnbt_standard_tables());
442 $actual_config = isset($tbl_map['MN_config']) ? $tbl_map['MN_config'] : 'MN_config';
443 $actual_zj = isset($tbl_map['MN_zj']) ? $tbl_map['MN_zj'] : 'MN_zj';
444 $actual_bt = isset($tbl_map['MN_bt']) ? $tbl_map['MN_bt'] : 'MN_bt';
445 $actual_dplan = isset($tbl_map['MN_docker_plan']) ? $tbl_map['MN_docker_plan'] : 'MN_docker_plan';
446 $actual_duser = isset($tbl_map['MN_docker_user']) ? $tbl_map['MN_docker_user'] : 'MN_docker_user';
447
448 // 1. 运行 V1.79→V1.81 升级脚本(脚本已幂等化,1.81 库上重跑安全)
449 $upgrade_sql_file = __DIR__ . '/1.79To1.81.sql';
450 if (file_exists($upgrade_sql_file)) {
451 $sql = file_get_contents($upgrade_sql_file);
452 $sql = explode(';', $sql);
453 for ($i = 0; $i < count($sql); $i++) {
454 $q = trim($sql[$i]);
455 if ($q === '') continue;
456 if (DB::query($q)) {
457 ++$t;
458 } else {
459 ++$e;
460 $error .= DB::error() . '<br/>';
461 }
462 }
463 }
464 // 2. 补齐缺失的表(含 V1.83 Docker 四表,跳过已存在的)
465 $sql = file_get_contents(__DIR__ . '/repair_tables.sql');
466 $sql = explode(';', $sql);
467 for ($i = 0; $i < count($sql); $i++) {
468 $q = trim($sql[$i]);
469 if ($q === '') continue;
470 if (preg_match('/CREATE TABLE.*?`(\w+)`/', $q, $m)) {
471 if (isset($tbl_map[$m[1]])) continue;
472 }
473 if (DB::query($q)) {
474 ++$t;
475 } else {
476 ++$e;
477 $error .= DB::error() . '<br/>';
478 }
479 }
480 // 3. 补齐缺失字段(V1.81 修复字段 + V1.83 Docker 配额字段 + V1.84 主页字段,判重后添加)
481 $alter_sqls = mnbt_upgrade_columns($actual_config, $actual_zj, $actual_bt, $actual_dplan, $actual_duser);
482 foreach ($alter_sqls as $table_name => $cols) {
483 $existing_cols = mnbt_table_columns($table_name);
484 if (empty($existing_cols)) continue; // 表不存在,第 2 步已尝试创建
485 foreach ($cols as $col_name => $alter_sql) {
486 if (!in_array(strtolower($col_name), $existing_cols, true)) {
487 if (DB::query($alter_sql)) {
488 ++$t;
489 } else {
490 ++$e;
491 $error .= DB::error() . '<br/>';
492 }
493 }
494 }
495 }
496 } elseif (!$skip_sql) {
497 $sql = file_get_contents("install.sql");
498 $sql = explode(';', $sql);
499 for ($i = 0; $i < count($sql); $i++) {
500 $q = trim($sql[$i]);
501 if ($q === '') continue;
502 if (DB::query($q)) {
503 ++$t;
504 } else {
505 ++$e;
506 $error .= DB::error() . '<br/>';
507 }
508 }
509 if ($e != 0) {
510 exit(Res(0, "安装失败!SQL成功{$t}句,失败{$e}句,请确保您的数据库版本在Mysql5.6(含)~5.7(含)之间,错误信息:" . $error));
511 }
512 } else {
513 $exists = table_exists_case('MN_config');
514 if (!$exists) {
515 exit(Res(0, '未检测到已有数据表,无法跳过导入,请勾选强制重装或检查数据库'));
516 }
517 }
518
519 date_default_timezone_set("PRC");
520 $date = date("Y-m-d");
521 $esc_user = DB::escape($admin_user);
522 $esc_pwd = DB::escape($admin_pwd);
523 $esc_name = DB::escape($site_name);
524 $esc_qq = DB::escape($site_qq);
525 $esc_gg = DB::escape($site_gg);
526 $esc_date = DB::escape($date);
527 $upd = DB::query("UPDATE `MN_config` SET `user`='{$esc_user}', `pwd`='{$esc_pwd}', `name`='{$esc_name}', `qqh`='{$esc_qq}', `gg`='{$esc_gg}', `date`='{$esc_date}' WHERE `id`='1'");
528 if (!$upd) {
529 exit(Res(0, '站点配置写入失败:' . DB::error()));
530 }
531
532 // V1.85 迁移链基线播种:全新安装 / 覆盖升级已把库结构带到当前版本,
533 // 据此把游标 MN_dbver 抬到 $WEBQB,之后在线更新只跑更高版本的增量、不重放历史迁移。
534 // skip 模式(只改配置不动表结构)库版本未知,故不播种;播种失败也不阻断安装。
535 if ($install_mode !== 'skip') {
536 include_once __DIR__ . '/../MPHX/migrations.php';
537 if (function_exists('mnbt_migrations_seed')) {
538 $seed_err = '';
539 mnbt_migrations_seed($dbconfig, (int)$WEBQB, $seed_err);
540 }
541 }
542
543 @file_put_contents("install.lock", '安装锁');
544 if ($install_mode === 'upgrade') {
545 exit(Res(1, '升级完成!已保留原有数据,成功更新至 ' . mnbt_version()));
546 }
547 if ($skip_sql) {
548 exit(Res(1, '安装完成(保留原表并更新站点/管理员配置)'));
549 }
550 exit(Res(1, '安装成功!'));
551 default:
552 exit(Res(0, '不存在的action'));
553}
554exit();
555
556
557function checkfunc($f, $m = false)
558{
559 if (function_exists($f)) {
560 return '<font color="green">可用</font>';
561 } else {
562 if ($m == false) {
563 return '<font color="black">不支持</font>';
564 } else {
565 return '<font color="red">不支持</font>';
566 }
567 }
568}
569
570function checkclass($f, $m = false)
571{
572 if (class_exists($f)) {
573 return '<font color="green">可用</font>';
574 } else {
575 if ($m == false) {
576 return '<font color="black">不支持</font>';
577 } else {
578 return '<font color="red">不支持</font>';
579 }
580 }
581}
582
583
584function mnqz()
585{
586 global $mn_conf;
587 $fh = file_get_contents($mn_conf['aet'] . "://" . $mn_conf['url'] . ":" . $mn_conf['port'] . "/" . $mn_conf['install_wj'] . "/xx.php");
588 $f = json_decode($fh, true);
589 if ($f['code_qk']) {
590 return '<font color="green">正常</font>';
591 } else {
592 return '<font color="red">不支持</font>';
593 }
594}
595
596?>
597
598
599<html lang="zh-cn">
600<head>
601 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
602 <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0,user-scalable=no,minimal-ui">
603 <title>MN宝塔主机系统</title>
604 <link rel="stylesheet" href="../imsetes/css/bootstrap.min.css">
605 <style>
606 .panel {
607 border: none;
608 border-radius: 10px;
609 }
610
611 .panel {
612 box-shadow: 1px 1px 5px 5px rgba(169, 169, 169, 0.35);
613 -moz-box-shadow: 1px 1px 5px 5px rgba(169, 169, 169, 0.35);
614 }
615 </style>
616</head>
617<body background="https://ww2.sinaimg.cn/large/a15b4afegy1fpp139ax3wj200o00g073.jpg">
618<nav class="navbar navbar-fixed-top navbar-default">
619 <div class="container">
620 <div class="navbar-header">
621 <span class="navbar-brand">安装向导</span>
622 </div>
623 </div>
624</nav>
625<div class="container" style="padding-top:80px;">
626 <div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">
627 <?php if ($do == '0'){ ?>
628 <div class="panel panel-primary">
629 <div class="panel-heading" style="background: #66CCFF;">
630 <h3 class="panel-title" align="center">MN宝塔主机系统</h3>
631 </div>
632 <div class="panel-body">
633 <center>
634 <div class="alert alert-success"><a href="https://mf.mengnai.top" target="_blank"><img
635 class="img-circle m-b-xs"
636 style="border: 2px solid #1281FF; margin-left:3px; margin-right:3px;"
637 src="https://q4.qlogo.cn/headimg_dl?dst_uin=3108807898&spec=100" ; width="60px"
638 height="60px" alt="<?php echo $conf['sitename']; ?>"><br></a>欢迎使用由梦奈基于光年V3框架原创的MN宝塔主机系统(简称MNBT)!本系统免费发布于网络!</br>
639 官网:<a href="http://mf.mengnai.top/" target="_blank">mf.mengnai.top</a><br>未经作者同意严禁任何形式的二次开发及引用!<br><small>2023by:梦奈</br>
640 系统版本:V1.6</small>
641 </div>
642 </center>
643 <?php if ($installed) { ?>
644 <div class="alert alert-warning">您已经安装过,如需重新安装请<font
645 color=red>从官网重新下载源码</font>文件后再安装!
646 </div>
647 <?php }else{ ?>
648 <input type="checkbox" name="gxk" id="eei" value="Car" onclick="eey()"/>勾选则代表您同意遵守<a
649 href='../xy.html' target="_blank"/>MN系统使用协议</a>
650 <p align="center"><a class="btn btn-primary" id="abq" style="opacity: 0.2"
651 href="javascript:return false;">开始安装</a></p>
652 <script type="text/javascript">
653 function eey() {
654 let $xz = document.getElementById("abq");
655 let vio = $xz.href;
656 if (vio === 'javascript:return false;') {
657 $xz.style = '';
658 $xz.href = 'index.php?do=1';
659 } else {
660 alert('若想安装本系统请自觉阅读并勾选《MN系统使用协议》');
661 $xz.style = 'opacity: 0.2';
662 $xz.href = 'javascript:return false;';
663 }
664 }
665 </script>
666 <?php } ?>
667 </div>
668 </div>
669 </div>
670</div>
671<?php } elseif ($do == '1') { ?>
672 <div class="panel panel-primary">
673 <div class="panel-heading" style="background: #66CCFF;">
674 <h3 class="panel-title" align="center">环境检查</h3>
675 </div>
676 <div class="progress progress-striped">
677 <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0"
678 aria-valuemax="100" style="width: 10%">
679 <span class="sr-only">10%</span>
680 </div>
681 </div>
682 <table class="table table-striped">
683 <thead>
684 <tr>
685 <th style="width:20%">函数检测</th>
686 <th style="width:15%">需求</th>
687 <th style="width:15%">当前</th>
688 <th style="width:50%">用途</th>
689 </tr>
690 </thead>
691 <tbody>
692 <tr>
693 <td>PHP 7.4 ~ 7.6</td>
694 <td>必须</td>
695 <td><?php $php_vs = version_compare(PHP_VERSION, '7.4.0', '>') && version_compare(PHP_VERSION, '7.7.0', '<');
696 echo $php_vs ? '<font color="green" id="server-code">' . PHP_VERSION . '</font>' : '<font color="red" id="server-code">' . PHP_VERSION . '</font>'; ?></td>
697 <td>PHP版本支持</td>
698 </tr>
699 <tr>
700 <td>curl_exec()</td>
701 <td>必须</td>
702 <td><?php echo checkfunc('curl_exec', true); ?></td>
703 <td>抓取网页</td>
704 </tr>
705 <tr>
706 <td>file_get_contents()</td>
707 <td>必须</td>
708 <td><?php echo checkfunc('file_get_contents', true); ?></td>
709 <td>读取文件</td>
710 </tr>
711 <tr>
712 <td>MN更新支持</td>
713 <td>非必须</td>
714 <td><?php echo mnqz(); ?></td>
715 <td>获取MN更新</td>
716 </tr>
717 </tbody>
718 </table>
719 <p><span><a class="btn btn-primary" href="index.php?do=0"><<上一步</a></span>
720 <span style="float:right"><a class="btn btn-primary" id="next" href="index.php?do=2"
721 align="right">下一步>></a></span></p>
722 </div>
723 <script>
724 let php_ves = Number(document.getElementById('server-code').innerText.substring(0, 3));
725 let next = document.getElementById('next')
726 if (php_ves < 7.4 || php_ves > 7.6) {
727 next.style = 'opacity: 0.2';
728 next.href = 'javascript:void(0)';
729 }
730
731 </script>
732<?php } elseif ($do == '2') { ?>
733 <div class="panel panel-primary">
734 <div class="panel-heading" style="background: #66CCFF;">
735 <h3 class="panel-title" align="center">数据库配置</h3>
736 </div>
737 <div class="progress progress-striped">
738 <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0"
739 aria-valuemax="100" style="width: 30%">
740 <span class="sr-only">30%</span>
741 </div>
742 </div>
743 <div class="panel-body">
744 <?php
745 echo <<<HTML
746 <form action="?do=3" class="form-sign" method="post">
747 <label for="name">数据库地址:</label>
748 <input type="text" class="form-control" name="db_host" value="localhost">
749 <label for="name">数据库端口:</label>
750 <input type="text" class="form-control" name="db_port" value="3306">
751 <label for="name">数据库用户名:</label>
752 <input type="text" class="form-control" name="db_user">
753 <label for="name">数据库名:</label>
754 <input type="text" class="form-control" name="db_name">
755 <label for="name">数据库密码:</label>
756 <input type="text" class="form-control" name="db_pwd">
757 <br><input type="submit" class="btn btn-primary btn-block" name="submit" value="保存配置">
758 </form>
759HTML;
760 ?>
761 </div>
762 </div>
763<?php } elseif ($do == '3') {
764 ?>
765 <div class="panel panel-primary">
766 <div class="panel-heading" style="background: #66CCFF;">
767 <h3 class="panel-title" align="center">保存配置</h3>
768 </div>
769 <div class="progress progress-striped">
770 <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0"
771 aria-valuemax="100" style="width: 50%">
772 <span class="sr-only">50%</span>
773 </div>
774 </div>
775 <div class="panel-body">
776 <?php
777 require './db.class.php';
778 $db_host = isset($_POST['db_host']) ? $_POST['db_host'] : NULL;
779 $db_port = isset($_POST['db_port']) ? $_POST['db_port'] : NULL;
780 $db_user = isset($_POST['db_user']) ? $_POST['db_user'] : NULL;
781 $db_pwd = isset($_POST['db_pwd']) ? $_POST['db_pwd'] : NULL;
782 $db_name = isset($_POST['db_name']) ? $_POST['db_name'] : NULL;
783 if ($db_host == null || $db_port == null || $db_user == null || $db_pwd == null || $db_name == null) {
784 echo '<div class="alert alert-danger">保存错误,请确保每项都不为空<hr/><a href="javascript:history.back(-1)"><< 返回上一页</a></div>';
785 exit;
786 }
787 $posj = send_post();
788 if ($posj['code'] == '0') {
789 echo '<div class="alert alert-danger">' . $posj['msg'] . '<hr/><a href="javascript:history.back(-1)"><< 返回上一页</a></div>';
790 } else {
791 $config = "<?php
792/*数据库配置*/
793\$dbconfig=array(
794 'host' => '{$db_host}', //数据库服务器
795 'port' => {$db_port}, //数据库端口
796 'user' => '{$db_user}', //数据库用户名
797 'pwd' => '{$db_pwd}', //数据库密码
798 'dbname' => '{$db_name}', //数据库名
799);
800?>";
801 $sqwj = "<?php
802\$authcode='{$posj["authcode"]}';
803?>";
804 if (!$con = DB::connect($db_host, $db_user, $db_pwd, $db_name, $db_port)) {
805 if (DB::connect_errno() == 2002)
806 echo '<div class="alert alert-warning">连接数据库失败,数据库地址填写错误!</div>';
807 elseif (DB::connect_errno() == 1045)
808 echo '<div class="alert alert-warning">连接数据库失败,数据库用户名或密码填写错误!</div>';
809 elseif (DB::connect_errno() == 1049)
810 echo '<div class="alert alert-warning">连接数据库失败,数据库名不存在!</div>';
811 else
812 echo '<div class="alert alert-warning">连接数据库失败,[' . DB::connect_errno() . ']' . DB::connect_error() . '</div>';
813 } elseif (file_put_contents('../config.php', $config) && file_put_contents('../MPHX/SQ.php', $sqwj)) {
814 echo '<div class="alert alert-success">数据库配置文件保存成功!</div>';
815 if (DB::query("select * from MN_config where 1") == FALSE)
816 echo '<p align="right"><a class="btn btn-primary btn-block" href="?do=4">创建数据表>></a></p>';
817 else
818 echo '<div class="list-group-item list-group-item-info">系统检测到你已安装过MN宝塔主机系统</div>
819 <div class="list-group-item">
820 <a href="?do=6" class="btn btn-block btn-primary">跳过安装</a>
821 </div>
822 <div class="list-group-item">
823 <a href="?do=4" onclick="if(!confirm(\'全新安装将会清空所有数据,是否继续?\')){return false;}" class="btn btn-block btn-warning">强制全新安装</a>
824 </div>';
825 } else
826 echo '<div class="alert alert-danger">保存失败,请确保网站根目录有写入权限<hr/><a href="javascript:history.back(-1)"><< 返回上一页</a></div>';
827 }
828 ?>
829 </div>
830 </div>
831<?php } elseif ($do == '4') { ?>
832 <div class="panel panel-primary">
833 <div class="panel-heading" style="background: #66CCFF;">
834 <h3 class="panel-title" align="center">创建数据表</h3>
835 </div>
836 <div class="progress progress-striped">
837 <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0"
838 aria-valuemax="100" style="width: 70%">
839 <span class="sr-only">70%</span>
840 </div>
841 </div>
842 <div class="panel-body">
843 <?php
844 include_once '../config.php';
845 if (!$dbconfig['user'] || !$dbconfig['pwd'] || !$dbconfig['dbname']) {
846 echo '<div class="alert alert-danger">请先填写好数据库并保存后再安装!<hr/><a href="javascript:history.back(-1)"><< 返回上一页</a></div>';
847 } else {
848 require './db.class.php';
849 $sql = file_get_contents("install.sql");
850 $sql = explode(';', $sql);
851 $cn = DB::connect($dbconfig['host'], $dbconfig['user'], $dbconfig['pwd'], $dbconfig['dbname'], $dbconfig['port']);
852 if (!$cn) die('err:' . DB::connect_error());
853 DB::query("set sql_mode = ''");
854 DB::query("set names utf8");
855 $t = 0;
856 $e = 0;
857 $error = '';
858 for ($i = 0; $i < count($sql); $i++) {
859 $q = trim($sql[$i]);
860 if ($q === '') continue;
861 if (DB::query($q)) {
862 ++$t;
863 } else {
864 ++$e;
865 $error .= DB::error() . '<br/>';
866 }
867 }
868 date_default_timezone_set("PRC");
869 $date = date("Y-m-d");
870 DB::query("update `MN_config` set `date` ='$date' where `id`='1'");
871 }
872 $esew = 0;
873 if ($e == 0) {
874 echo '<div class="alert alert-success">安装成功!<br/>SQL成功' . $t . '句/失败' . $e . '句</div><p align="right"><a class="btn btn-block btn-primary" href="index.php?do=5">下一步>></a></p>';
875 } else {
876 echo '<div class="alert alert-success">安装成功!<br/>SQL成功' . $t . '句/失败' . $esew . '句</div><p align="right"><a class="btn btn-block btn-primary" href="index.php?do=5">下一步>></a></p>';
877 }
878 ?>
879 </div>
880 </div>
881
882<?php } elseif ($do == '5') { ?>
883 <div class="panel panel-primary">
884 <div class="panel-heading" style="background: #66CCFF;">
885 <h3 class="panel-title" align="center">安装完成</h3>
886 </div>
887 <div class="progress progress-striped">
888 <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0"
889 aria-valuemax="100" style="width: 100%">
890 <span class="sr-only">100%</span>
891 </div>
892 </div>
893 <div class="panel-body">
894 <?php
895 @file_put_contents("install.lock", '安装锁');
896 echo '<div class="alert alert-success">安装完成!管理账号和密码是:admin/123456</font><br/><br/><a href="../user/">>>控制面板</a>|<a href="../admin/">>>后台管理</a><hr/>更多设置选项请登录后台管理进行修改。<br/><br/><font color="#FF0033">如果你的空间不支持本地文件读写,请自行删除install文件夹!</font></div>';
897 unlink('index.php');
898 unlink('install.lock');
899 unlink('install.sql');
900 unlink('db.class.php');
901 @rmdir('../install/');
902 ?>
903 </div>
904 </div>
905
906<?php } elseif ($do == '6') { ?>
907 <div class="panel panel-primary">
908 <div class="panel-heading" style="background: #66CCFF;">
909 <h3 class="panel-title" align="center">安装完成</h3>
910 </div>
911 <div class="progress progress-striped">
912 <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0"
913 aria-valuemax="100" style="width: 100%">
914 <span class="sr-only">100%</span>
915 </div>
916 </div>
917 <div class="panel-body">
918 <?php
919 @file_put_contents("install.lock", '安装锁');
920 echo '<div class="alert alert-success">安装完成!管理账号和密码为原账号密码如果忘记请进入数据库MN_config表查看账号(user)密码(pwd)</font><br/><br/><a href="../user/">>>控制面板</a>|<a href="../admin/">>>后台管理</a><hr/>更多设置选项请登录后台管理进行修改。<br/><br/><font color="#FF0033">如果你的空间不支持本地文件读写,请自行删除install文件夹!</font></div>';
921 unlink('index.php');
922 unlink('install.lock');
923 unlink('install.sql');
924 unlink('db.class.php');
925 @rmdir('../install/');
926 ?>
927 </div>
928 </div>
929
930<?php } ?>
931
932</div>
933</body>
934</html>