diff --git a/install.php b/install.php index 95532a3..21ee7db 100644 --- a/install.php +++ b/install.php @@ -44,8 +44,10 @@ function fb_install_checks() { ], ]; + // 只有 data/ 与 uploads/ 是硬要求(数据库和附件必须能写)。 + // 站点根不必可写:config.php 写不进去时,设置会落到数据库 settings 表, + // 站点照常运行。反过来,把站点根设成只读是更安全的做法,不该被判为错误。 $dirs = [ - FB_ROOT => '站点根目录(要写入 config.php)', FB_ROOT . '/data' => '数据库目录 data/', FB_ROOT . '/uploads' => '本机附件目录 uploads/(本机存储模式用)', ]; @@ -82,13 +84,25 @@ function fb_install_checks() { $rows[] = ['目录可写 ' . $shown, $w ? '可写' : '不可写', $w, $why, $w ? '' : ('请给 Web 用户写权限,例如 chown www:www ' . $d)]; } - // config.php 若已存在(例如从模板复制过),必须可写才能就地更新 + // 站点根:只提示,不阻断。 + // 写不进 config.php 时,站点设置会全部落到数据库 settings 表,功能不受影响。 + $rootW = is_writable(FB_ROOT); + $rows[] = [ + '目录可写(站点根)', + $rootW ? '可写' : '只读(不影响使用)', + true, // 不参与 $ok 判定 + '用于生成 config.php;只读时设置改存数据库,功能一样', + $rootW ? '' : '站点根只读是可接受的(更安全)。若希望生成 config.php,再给它写权限。', + ]; + + // config.php 若已存在(例如从模板复制过),只读也不影响运行,只影响能否就地更新 if (is_file(FB_ROOT . '/config.php')) { $w = is_writable(FB_ROOT . '/config.php'); - if (!$w) { $ok = false; } - $rows[] = ['config.php 可写', $w ? '可写' : '不可写', $w, '需要就地更新配置', $w ? '' : '请给该文件写权限,例如 chown www:www config.php']; + $rows[] = ['config.php 可写', $w ? '可写' : '只读(不影响使用)', true, + '需要就地更新配置时才有用', + $w ? '' : '只读可接受:后台改的设置会存数据库,不写这个文件。']; } else { - $rows[] = ['config.php', '尚不存在(将自动生成)', true, '由安装向导创建', '']; + $rows[] = ['config.php', '尚不存在(将自动生成)', true, '由安装向导创建(站点根只读时会跳过)', '']; } $ex = is_file(FB_ROOT . '/config.example.php'); @@ -231,9 +245,62 @@ if (isset($_POST['do_install'])) { $done = true; $GLOBALS['__fb_install_pw_set'] = $pwSet; + + /* ---- 装完自动删除本文件 ---- + * 安装向导本身是可写文件,留着等于长期暴露一个"重装入口"。 + * 虽然已安装态会拒绝重装,但最干净的做法是装完即自毁。 + * + * 删除失败不算安装失败:站点已经装好了,只是清理动作没成。 + * 失败原因通常是文件属主不是 Web 用户(部署账号上传的文件常属 btops), + * 这时把实际情况记到 $GLOBALS 里,成功页如实告知,不假装删掉了。 + */ + $GLOBALS['__fb_install_selfdel'] = fb_install_self_delete(); } } +/** + * 安装完成后删除自身。 + * @return string '' = 已删除;非空 = 没删掉的原因(给成功页显示) + */ +function fb_install_self_delete() { + $self = __FILE__; + if (!is_file($self)) { return ''; } // 已经不在了 + + /* ① 直接尝试删除。 + * + * 🔴 这里绝不能先 chmod 目录!曾经写成 @chmod(dirname($self), 0755) 再删, + * 等于程序自己把"站点根只读"这个安全设置拆掉了 —— 用户有意设成只读的目录 + * 被 PHP 改回可写,然后文件就被删掉了。既违反管理员意图,也让"删不掉" + * 这个真实场景永远测不出来。 + * 删不掉就如实报告,不要硬删。 + */ + if (@unlink($self)) { return ''; } + + // ② 兜底:只有在目录可写时才试 rename(目录不可写时 rename 同样会失败, + // 硬试只会在站点里留下 .deleted.xxxx 垃圾文件) + if (is_writable(dirname($self))) { + $tmp = $self . '.deleted.' . substr(md5((string)time() . random_int(1000, 9999)), 0, 8); + if (@rename($self, $tmp)) { + if (@unlink($tmp)) { return ''; } + // 改名成功但删不掉:还原,别留垃圾 + @rename($tmp, $self); + } + } + + // ③ 都没成,给出可操作的原因 + $dir = dirname($self); + $uid = function_exists('posix_getuid') ? posix_getuid() : -1; + $own = @fileowner($self); + $msg = '自动删除失败'; + if (!is_writable($dir)) { + $msg .= ':' . $dir . ' 目录对 Web 用户不可写'; + } + if ($uid >= 0 && $own !== false && $own !== $uid) { + $msg .= ':文件属主(uid ' . (int)$own . ')与运行用户(uid ' . $uid . ')不同'; + } + return $msg; +} + ?> @@ -296,10 +363,21 @@ if (isset($_POST['do_install'])) {
-

- 建议:装好后把 install.php 从服务器删掉。 - 它在已安装状态下会拒绝重装,但删掉更干净。 -

+ + +

+ ✅ install.php 已自动删除,不会再有重复安装的入口。 +

+ +

+ ⚠️ 安装已完成,但 install.php 没能自动删除。
+ 原因:。
+ 它在已安装状态下会拒绝重装,但建议手工删掉,或在服务器上执行:
+ sudo chown www:www /install.php && sudo rm /install.php +

+