2026-09-28 22:53:55 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
/**
|
|
|
|
|
|
* FeedLite · 安装向导
|
|
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
|
|
* 什么时候会用到:`data/` 下还没有数据库文件(全新部署)时,任何页面都会把
|
|
|
|
|
|
* 访问者送到这里。向导只做四件事:
|
|
|
|
|
|
* ① 体检:PHP 版本、必需扩展、目录可写性;
|
|
|
|
|
|
* ② 收信息:站点标题/描述、管理员口令、官方邮箱、站点根地址;
|
|
|
|
|
|
* ③ 写配置:生成 config.php(在 config.example.php 基础上逐项替换,
|
|
|
|
|
|
* 已有的 OSS / SMTP 段落原样保留,不覆盖你手工改过的内容);
|
|
|
|
|
|
* ④ 建库建表 + 把口令以 bcrypt 哈希写进数据库 ——
|
|
|
|
|
|
* **明文口令不落盘**,config.php 里始终保持出厂占位值。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 装完之后:删除本文件与否都可以(已安装态下它会礼貌地拒绝重复安装),
|
|
|
|
|
|
* 但建议删掉,少一个入口少一分风险。
|
|
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
|
|
* 安装向导必须能在「尚未安装」的状态下运行,所以这里自己引导,
|
|
|
|
|
|
* 并显式声明跳过 bootstrap 的「未安装就跳 install」逻辑,否则会自己跳自己。
|
|
|
|
|
|
*/
|
|
|
|
|
|
define('FB_SKIP_INSTALL_REDIRECT', 1);
|
|
|
|
|
|
require __DIR__ . '/lib/bootstrap.php';
|
|
|
|
|
|
|
|
|
|
|
|
$root = FB_ROOT;
|
|
|
|
|
|
$cfgFile = $root . '/config.php';
|
|
|
|
|
|
|
|
|
|
|
|
/* ==================================================================
|
|
|
|
|
|
* 已安装 → 拒绝重复安装
|
|
|
|
|
|
* ================================================================== */
|
|
|
|
|
|
$already = fb_installed();
|
|
|
|
|
|
|
|
|
|
|
|
/* ==================================================================
|
|
|
|
|
|
* 环境体检
|
|
|
|
|
|
* ================================================================== */
|
|
|
|
|
|
function fb_install_checks() {
|
|
|
|
|
|
$need = ['pdo_sqlite' => 'SQLite 数据库(PDO)', 'openssl' => 'SMTP / OSS 加密通信'];
|
2026-10-23 08:42:47 +08:00
|
|
|
|
// 可选扩展:值是说明字符串;也可写成 [说明, 缺失时的状态文案, 缺失时的补救提示]
|
|
|
|
|
|
$opt = [
|
|
|
|
|
|
'curl' => '访问 OSS(可选,没有会退回 PHP 流式请求)',
|
|
|
|
|
|
'mbstring' => '中文字数统计(可选)',
|
|
|
|
|
|
'fileinfo' => [
|
2026-10-23 09:07:39 +08:00
|
|
|
|
'按文件内容识别附件类型(仅本机存储模式用得上)',
|
|
|
|
|
|
'未启用(不影响安装;本机存储时只按扩展名核对)',
|
|
|
|
|
|
'建议开启。注意 OSS 存储模式的类型判定以对象存储返回的 Content-Type 为准,不经过本扩展',
|
2026-10-23 08:42:47 +08:00
|
|
|
|
],
|
|
|
|
|
|
];
|
2026-09-28 22:53:55 +08:00
|
|
|
|
|
2026-11-01 12:55:08 +08:00
|
|
|
|
// 只有 data/ 与 uploads/ 是硬要求(数据库和附件必须能写)。
|
|
|
|
|
|
// 站点根不必可写:config.php 写不进去时,设置会落到数据库 settings 表,
|
|
|
|
|
|
// 站点照常运行。反过来,把站点根设成只读是更安全的做法,不该被判为错误。
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$dirs = [
|
|
|
|
|
|
FB_ROOT . '/data' => '数据库目录 data/',
|
|
|
|
|
|
FB_ROOT . '/uploads' => '本机附件目录 uploads/(本机存储模式用)',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$rows = [];
|
|
|
|
|
|
$ok = true;
|
|
|
|
|
|
|
|
|
|
|
|
$verOk = version_compare(PHP_VERSION, '7.4.0', '>=');
|
2026-10-23 08:23:55 +08:00
|
|
|
|
$rows[] = ['PHP 版本', PHP_VERSION, $verOk, '需要 7.4 及以上(推荐 8.1~8.5,已在 8.5 实测)', $verOk ? '' : '当前版本过低,请切换站点的 PHP 版本'];
|
2026-09-28 22:53:55 +08:00
|
|
|
|
if (!$verOk) { $ok = false; }
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($need as $ext => $why) {
|
|
|
|
|
|
$has = extension_loaded($ext);
|
|
|
|
|
|
if (!$has) { $ok = false; }
|
|
|
|
|
|
$rows[] = ['PHP 扩展:' . $ext, $has ? '已启用' : '未启用', $has, $why, $has ? '' : ('请在 PHP 设置里开启 ' . $ext . ' 扩展')];
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ($opt as $ext => $why) {
|
2026-10-23 08:42:47 +08:00
|
|
|
|
$note = '未启用(不影响安装)';
|
|
|
|
|
|
$hint = '';
|
|
|
|
|
|
if (is_array($why)) {
|
|
|
|
|
|
$note = isset($why[1]) ? $why[1] : $note;
|
|
|
|
|
|
$hint = isset($why[2]) ? $why[2] : '';
|
|
|
|
|
|
$why = isset($why[0]) ? $why[0] : '';
|
|
|
|
|
|
}
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$has = extension_loaded($ext);
|
2026-10-23 08:42:47 +08:00
|
|
|
|
$rows[] = ['PHP 扩展:' . $ext, $has ? '已启用' : $note, true, $why, $has ? '' : $hint];
|
2026-09-28 22:53:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($dirs as $d => $why) {
|
|
|
|
|
|
if (!is_dir($d)) { @mkdir($d, 0755, true); }
|
|
|
|
|
|
$w = is_dir($d) && is_writable($d);
|
|
|
|
|
|
if (!$w) { $ok = false; }
|
|
|
|
|
|
$shown = ($d === FB_ROOT) ? '(站点根)' : str_replace(FB_ROOT, '', $d);
|
|
|
|
|
|
$rows[] = ['目录可写 ' . $shown, $w ? '可写' : '不可写', $w, $why, $w ? '' : ('请给 Web 用户写权限,例如 chown www:www ' . $d)];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-01 12:55:08 +08:00
|
|
|
|
// 站点根:只提示,不阻断。
|
|
|
|
|
|
// 写不进 config.php 时,站点设置会全部落到数据库 settings 表,功能不受影响。
|
|
|
|
|
|
$rootW = is_writable(FB_ROOT);
|
|
|
|
|
|
$rows[] = [
|
|
|
|
|
|
'目录可写(站点根)',
|
|
|
|
|
|
$rootW ? '可写' : '只读(不影响使用)',
|
|
|
|
|
|
true, // 不参与 $ok 判定
|
|
|
|
|
|
'用于生成 config.php;只读时设置改存数据库,功能一样',
|
|
|
|
|
|
$rootW ? '' : '站点根只读是可接受的(更安全)。若希望生成 config.php,再给它写权限。',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// config.php 若已存在(例如从模板复制过),只读也不影响运行,只影响能否就地更新
|
2026-09-28 22:53:55 +08:00
|
|
|
|
if (is_file(FB_ROOT . '/config.php')) {
|
|
|
|
|
|
$w = is_writable(FB_ROOT . '/config.php');
|
2026-11-01 12:55:08 +08:00
|
|
|
|
$rows[] = ['config.php 可写', $w ? '可写' : '只读(不影响使用)', true,
|
|
|
|
|
|
'需要就地更新配置时才有用',
|
|
|
|
|
|
$w ? '' : '只读可接受:后台改的设置会存数据库,不写这个文件。'];
|
2026-09-28 22:53:55 +08:00
|
|
|
|
} else {
|
2026-11-01 12:55:08 +08:00
|
|
|
|
$rows[] = ['config.php', '尚不存在(将自动生成)', true, '由安装向导创建(站点根只读时会跳过)', ''];
|
2026-09-28 22:53:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$ex = is_file(FB_ROOT . '/config.example.php');
|
|
|
|
|
|
$rows[] = ['配置模板 config.example.php', $ex ? '存在' : '缺失', $ex, '生成 config.php 的底稿', $ex ? '' : '仓库文件不完整,请重新获取源码'];
|
|
|
|
|
|
if (!$ex) { $ok = false; }
|
|
|
|
|
|
|
|
|
|
|
|
return [$ok, $rows];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ==================================================================
|
|
|
|
|
|
* 生成 config.php(在模板文本上逐项替换,保留注释与其它段落)
|
|
|
|
|
|
* ================================================================== */
|
|
|
|
|
|
function fb_install_write_config(array $v) {
|
|
|
|
|
|
$example = FB_ROOT . '/config.example.php';
|
|
|
|
|
|
$src = (string)@file_get_contents($example);
|
|
|
|
|
|
if ($src === '') { return '读不到 config.example.php。'; }
|
|
|
|
|
|
|
|
|
|
|
|
/** 把顶层键(含引号形式)整项替换成新值;找不到就返回原串由调用方兜底 */
|
|
|
|
|
|
$setTop = function ($key, $valueExpr, $txt) {
|
|
|
|
|
|
$pat = "/^([ \t]*'" . preg_quote($key, '/') . "'[ \t]*=>[ \t]*)(.*?)(,[ \t]*)(\r?\n)/m";
|
|
|
|
|
|
$rep = '$1' . $valueExpr . '$3$4';
|
|
|
|
|
|
return preg_replace($pat, $rep, $txt, 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
$phpStr = function ($s) { return "'" . str_replace(["\\", "'"], ["\\\\", "\\'"], (string)$s) . "'"; };
|
|
|
|
|
|
|
|
|
|
|
|
$dbPath = str_replace('\\', '/', FB_ROOT . '/data/feedback.db');
|
|
|
|
|
|
|
|
|
|
|
|
$src = $setTop('db_path', "'" . $dbPath . "'", $src);
|
|
|
|
|
|
$src = $setTop('site_name', $phpStr($v['site_name']), $src);
|
|
|
|
|
|
$src = $setTop('site_desc', $phpStr($v['site_desc']), $src);
|
|
|
|
|
|
$src = $setTop('admin_email', $phpStr($v['admin_email']), $src);
|
|
|
|
|
|
$src = $setTop('base_url', $phpStr($v['base_url']), $src);
|
|
|
|
|
|
// 口令不写进文件:此处只把残留的旧明文重置为占位值(正常都是占位值,等价于不动)
|
|
|
|
|
|
$src = $setTop('admin_password', "'CHANGE_ME_admin'", $src);
|
|
|
|
|
|
$src = $setTop('api_token', $phpStr($v['api_token']), $src);
|
|
|
|
|
|
$src = $setTop('ip_salt', $phpStr($v['ip_salt']), $src);
|
|
|
|
|
|
|
|
|
|
|
|
// 兜底校验:生成的文本必须是可解析的 PHP。
|
|
|
|
|
|
// 注意必须带 TOKEN_PARSE —— 不加这一位 token_get_all 只做词法切分、不报语法错,等于没校验。
|
|
|
|
|
|
$syntaxOk = true;
|
|
|
|
|
|
if (defined('TOKEN_PARSE') && function_exists('token_get_all')) {
|
|
|
|
|
|
try { @token_get_all($src, TOKEN_PARSE); }
|
|
|
|
|
|
catch (Throwable $e) { $syntaxOk = false; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!$syntaxOk) { return '生成的配置内容语法不正确,已放弃写入(请把 config.php 恢复为模板后再试)。'; }
|
|
|
|
|
|
|
|
|
|
|
|
$stamp = date('Y-m-d H:i:s');
|
|
|
|
|
|
$header = "<?php\n"
|
|
|
|
|
|
. "/**\n"
|
|
|
|
|
|
. " * FeedLite · 轻反馈 · 配置文件\n"
|
|
|
|
|
|
. " * ------------------------------------------------------------------\n"
|
|
|
|
|
|
. " * 本文件由安装向导于 " . $stamp . " 生成,已按你填写的内容更新;\n"
|
|
|
|
|
|
. " * 段落注释与 OSS / SMTP 等配置保持模板原样,可继续手工编辑。\n"
|
|
|
|
|
|
. " * 注意:管理员口令【不在这里】——它以 bcrypt 哈希存在数据库 settings 表,\n"
|
|
|
|
|
|
. " * 下行的 admin_password 只是工厂占位值(已被程序明确禁用,无法登录)。\n"
|
|
|
|
|
|
. " */\n\n";
|
|
|
|
|
|
$src = $header . preg_replace('/^<\?php\s*/', '', $src, 1);
|
|
|
|
|
|
|
|
|
|
|
|
$ok = @file_put_contents(FB_ROOT . '/config.php', $src);
|
|
|
|
|
|
if ($ok === false) { return '写入 config.php 失败(目录或文件不可写)。'; }
|
|
|
|
|
|
@chmod(FB_ROOT . '/config.php', 0640);
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 随机串(口令 / 令牌 / 盐) */
|
|
|
|
|
|
function fb_install_rand($n = 32) {
|
|
|
|
|
|
try { return bin2hex(random_bytes($n)); } catch (Throwable $e) { return md5(uniqid('', true) . mt_rand()); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ==================================================================
|
|
|
|
|
|
* 提交处理
|
|
|
|
|
|
* ================================================================== */
|
|
|
|
|
|
$err = '';
|
|
|
|
|
|
$done = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($_POST['do_install'])) {
|
|
|
|
|
|
list($envOk, ) = fb_install_checks();
|
|
|
|
|
|
|
|
|
|
|
|
$post = function ($k, $d = '') { return isset($_POST[$k]) ? trim((string)$_POST[$k]) : $d; };
|
|
|
|
|
|
|
|
|
|
|
|
$siteName = $post('site_name');
|
|
|
|
|
|
$siteDesc = $post('site_desc');
|
|
|
|
|
|
$admEmail = $post('admin_email');
|
|
|
|
|
|
$baseUrl = rtrim($post('base_url'), '/');
|
2026-11-01 12:39:20 +08:00
|
|
|
|
$admUser = $post('admin_user');
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$pw = (string)(isset($_POST['admin_password']) ? $_POST['admin_password'] : '');
|
|
|
|
|
|
$pw2 = (string)(isset($_POST['admin_password2']) ? $_POST['admin_password2'] : '');
|
|
|
|
|
|
$setPw = !empty($_POST['set_password']);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$envOk) {
|
|
|
|
|
|
$err = '环境体检未通过,请先按提示处理好上面标红的问题。';
|
|
|
|
|
|
} elseif (!fb_csrf_ok(isset($_POST['csrf']) ? (string)$_POST['csrf'] : '')) {
|
|
|
|
|
|
$err = '会话已过期,请重新提交。';
|
|
|
|
|
|
} elseif ($siteName === '' || fb_len($siteName) > 80) {
|
|
|
|
|
|
$err = '站点标题请填写 1~80 个字符。';
|
|
|
|
|
|
} elseif ($admEmail !== '' && !filter_var($admEmail, FILTER_VALIDATE_EMAIL)) {
|
|
|
|
|
|
$err = '官方邮箱格式不正确(也可以留空)。';
|
|
|
|
|
|
} elseif ($baseUrl !== '' && !preg_match('~^https?://~i', $baseUrl)) {
|
|
|
|
|
|
$err = '站点根地址需要以 http:// 或 https:// 开头(留空则自动探测)。';
|
2026-11-01 12:39:20 +08:00
|
|
|
|
} elseif ($admUser === '') {
|
|
|
|
|
|
$err = '请填写管理员用户名。';
|
|
|
|
|
|
} elseif (!preg_match('/^[A-Za-z0-9_.\-]{2,32}$/', $admUser)) {
|
|
|
|
|
|
$err = '管理员用户名只能用字母、数字、下划线、点、连字符,长度 2~32。';
|
|
|
|
|
|
} elseif (fb_len($pw) < 8) {
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$err = '管理员口令至少 8 个字符。';
|
2026-11-01 12:39:20 +08:00
|
|
|
|
} elseif ($pw !== $pw2) {
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$err = '两次输入的口令不一致。';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// ① 先建库建表 —— 后面写设置才有地方写
|
|
|
|
|
|
$pdo = fb_db();
|
|
|
|
|
|
|
|
|
|
|
|
// ② 写 config.php(口令不入文件)
|
|
|
|
|
|
$cfgErr = fb_install_write_config([
|
|
|
|
|
|
'site_name' => $siteName,
|
|
|
|
|
|
'site_desc' => ($siteDesc !== '' ? $siteDesc : (string)fb_cfg('site_desc', '')),
|
|
|
|
|
|
'admin_email' => $admEmail,
|
|
|
|
|
|
'base_url' => $baseUrl,
|
|
|
|
|
|
'api_token' => fb_install_rand(16),
|
|
|
|
|
|
'ip_salt' => fb_install_rand(16),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
if ($cfgErr === '') {
|
|
|
|
|
|
fb_cfg_flush(); // 让后续取值看到刚落盘的新 config.php
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// config.php 写不进去不算致命:settings 表里的值同样生效,先把系统跑起来并如实告知
|
|
|
|
|
|
$err = '提示:' . $cfgErr . '(站内设置已写入数据库,系统可用;但建议修好后手工补齐 config.php)';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ③ 站点信息入库(优先生效,且不依赖 config.php 是否可写)
|
|
|
|
|
|
fb_set_setting('site_name', $siteName);
|
|
|
|
|
|
if ($siteDesc !== '') { fb_set_setting('site_desc', $siteDesc); }
|
|
|
|
|
|
fb_set_setting('installed_at', time());
|
|
|
|
|
|
|
2026-11-01 12:39:20 +08:00
|
|
|
|
// ④ 管理员账号:用户名入库,口令只写 bcrypt 哈希,绝不落盘明文
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$pwSet = false;
|
2026-11-01 12:39:20 +08:00
|
|
|
|
fb_admin_set_username($admUser);
|
|
|
|
|
|
if (fb_len($pw) >= 8 && $pw === $pw2) {
|
2026-09-28 22:53:55 +08:00
|
|
|
|
$pwSet = fb_admin_set_password($pw);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$done = true;
|
|
|
|
|
|
$GLOBALS['__fb_install_pw_set'] = $pwSet;
|
2026-11-01 12:55:08 +08:00
|
|
|
|
|
|
|
|
|
|
/* ---- 装完自动删除本文件 ----
|
|
|
|
|
|
* 安装向导本身是可写文件,留着等于长期暴露一个"重装入口"。
|
|
|
|
|
|
* 虽然已安装态会拒绝重装,但最干净的做法是装完即自毁。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 删除失败不算安装失败:站点已经装好了,只是清理动作没成。
|
|
|
|
|
|
* 失败原因通常是文件属主不是 Web 用户(部署账号上传的文件常属 btops),
|
|
|
|
|
|
* 这时把实际情况记到 $GLOBALS 里,成功页如实告知,不假装删掉了。
|
|
|
|
|
|
*/
|
|
|
|
|
|
$GLOBALS['__fb_install_selfdel'] = fb_install_self_delete();
|
2026-09-28 22:53:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-01 12:55:08 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 安装完成后删除自身。
|
|
|
|
|
|
* @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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 22:53:55 +08:00
|
|
|
|
?>
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
|
<meta name="robots" content="noindex,nofollow">
|
|
|
|
|
|
<title>安装 · 轻反馈 FeedLite</title>
|
|
|
|
|
|
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
|
|
|
|
|
|
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
|
|
|
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon.png">
|
2026-11-01 12:47:41 +08:00
|
|
|
|
<link rel="stylesheet" href="assets/style.css?v=<?php echo (int)FB_ASSET_V; ?>">
|
2026-09-28 22:53:55 +08:00
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<header class="topbar">
|
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
<span class="brand-mini">
|
|
|
|
|
|
<span class="logo-mark" role="img" aria-label="轻反馈图标"></span>
|
|
|
|
|
|
<span class="brand-text">轻反馈 FeedLite</span>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div class="spacer"></div>
|
|
|
|
|
|
<span class="navlink hide-sm">安装向导</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<main class="wrap">
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($already && !$done): ?>
|
|
|
|
|
|
<section class="narrow">
|
|
|
|
|
|
<h1>已经装好了</h1>
|
|
|
|
|
|
<p class="muted">检测到数据库已存在,无需重复安装。</p>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<a class="btn primary" href="index.php">进入问题列表</a>
|
|
|
|
|
|
<a class="btn" href="admin.php">管理后台</a>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="card-form" style="margin-top:18px">
|
|
|
|
|
|
<p class="muted small">如果你确实想重装(例如换了数据库位置),请先手动移走
|
|
|
|
|
|
<code>data/feedback.db</code> 再刷新本页。本向导不会替你删除任何数据。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<?php elseif ($done): ?>
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
$pwSet = !empty($GLOBALS['__fb_install_pw_set']);
|
|
|
|
|
|
$justUser = fb_admin_username();
|
|
|
|
|
|
?>
|
|
|
|
|
|
<meta http-equiv="refresh" content="2;url=index.php">
|
2026-09-28 22:53:55 +08:00
|
|
|
|
<section class="narrow">
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<h1>安装完成</h1>
|
|
|
|
|
|
<p class="muted">已建好数据库、写入站点设置。<b>2 秒后自动进入首页</b>(也可点下方按钮)。</p>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="card-form" style="margin:14px 0">
|
|
|
|
|
|
<div style="display:flex;flex-direction:column;gap:8px">
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<span><b>网站名称</b>:<?php echo e((string)fb_cfg('site_name', '')); ?></span>
|
|
|
|
|
|
<span><b>管理员用户名</b>:<?php echo e($justUser !== '' ? $justUser : '(未设置)'); ?></span>
|
|
|
|
|
|
<span><b>登录入口</b>:<a href="admin.php">admin.php</a>(用户名 + 口令)</span>
|
|
|
|
|
|
<span><b>口令存放</b>:bcrypt 哈希,存于数据库;<code>config.php</code> 里没有任何明文口令。</span>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($err !== ''): ?><?php fb_flash_box($err, 'error'); ?><?php endif; ?>
|
|
|
|
|
|
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<div class="card-form" style="margin:14px 0">
|
2026-11-01 12:55:08 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
$selfDel = isset($GLOBALS['__fb_install_selfdel']) ? (string)$GLOBALS['__fb_install_selfdel'] : '';
|
|
|
|
|
|
?>
|
|
|
|
|
|
<?php if ($selfDel === ''): ?>
|
|
|
|
|
|
<p class="muted small" style="margin:0">
|
|
|
|
|
|
<b>✅ <code>install.php</code> 已自动删除</b>,不会再有重复安装的入口。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
<p class="muted small" style="margin:0">
|
|
|
|
|
|
<b>⚠️ 安装已完成,但 <code>install.php</code> 没能自动删除。</b><br>
|
|
|
|
|
|
原因:<?php echo e($selfDel); ?>。<br>
|
|
|
|
|
|
它在已安装状态下会拒绝重装,但建议手工删掉,或在服务器上执行:<br>
|
|
|
|
|
|
<code>sudo chown www:www <?php echo e(__DIR__); ?>/install.php && sudo rm <?php echo e(__DIR__); ?>/install.php</code>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<?php endif; ?>
|
2026-11-01 12:39:20 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-28 22:53:55 +08:00
|
|
|
|
<p>
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<a class="btn primary" href="index.php">进入首页</a>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
<a class="btn" href="admin.php">管理后台</a>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
<?php list($envOk, $rows) = fb_install_checks(); ?>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="page-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1>安装向导</h1>
|
|
|
|
|
|
<p class="muted">数据库目录 <code>data/</code> 下还没有数据,先完成这几步设置,装完即可使用。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<?php if (!$envOk): ?>
|
|
|
|
|
|
<?php fb_flash_box('环境体检未通过:请先处理下方标红项,处理好后刷新本页。', 'error'); ?>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
|
|
|
|
<h2 style="margin:22px 0 10px">一、环境体检</h2>
|
|
|
|
|
|
<div class="card-form" style="margin:0 0 18px">
|
|
|
|
|
|
<div style="display:flex;flex-direction:column;gap:6px">
|
|
|
|
|
|
<?php foreach ($rows as $r): ?>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<?php echo $r[2] ? '✅' : '❌'; ?>
|
|
|
|
|
|
<b><?php echo e($r[0]); ?></b>:<?php echo e($r[1]); ?>
|
|
|
|
|
|
<?php if (!empty($r[3])): ?><i class="hint">(<?php echo e($r[3]); ?>)</i><?php endif; ?>
|
|
|
|
|
|
<?php if (!empty($r[4])): ?><br><span class="muted small" style="margin-left:1.4em">↳ <?php echo e($r[4]); ?></span><?php endif; ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<h2 style="margin:22px 0 10px">二、初始化站点</h2>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
<?php fb_flash_box($err, 'error'); ?>
|
|
|
|
|
|
<form method="post" action="install.php" class="card-form">
|
|
|
|
|
|
<input type="hidden" name="csrf" value="<?php echo e(fb_csrf_token()); ?>">
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<input type="hidden" name="site_desc" value="<?php echo e(isset($_POST['site_desc']) ? (string)$_POST['site_desc'] : ''); ?>">
|
|
|
|
|
|
<input type="hidden" name="admin_email" value="<?php echo e(isset($_POST['admin_email']) ? (string)$_POST['admin_email'] : ''); ?>">
|
|
|
|
|
|
<input type="hidden" name="base_url" value="<?php echo e(isset($_POST['base_url']) ? (string)$_POST['base_url'] : ''); ?>">
|
|
|
|
|
|
<input type="hidden" name="set_password" value="1">
|
2026-09-28 22:53:55 +08:00
|
|
|
|
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<label class="field"><span>网站名称 <b class="req">*</b></span>
|
|
|
|
|
|
<input type="text" name="site_name" maxlength="80" required autofocus
|
|
|
|
|
|
value="<?php echo e(isset($_POST['site_name']) ? (string)$_POST['site_name'] : (string)fb_cfg('site_name', '')); ?>"
|
|
|
|
|
|
placeholder="例如:我的软件 · 问题反馈">
|
|
|
|
|
|
<i class="hint">显示在浏览器标签、页面顶栏和页脚。</i></label>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<h2 style="margin:20px 0 10px">管理员账号</h2>
|
|
|
|
|
|
<p class="muted small" style="margin:-4px 0 12px">
|
|
|
|
|
|
用来登录管理后台、以「官方」身份回复。<b>口令以 bcrypt 单向哈希存进数据库,
|
|
|
|
|
|
明文不会写进 config.php 或任何文件。</b>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
</p>
|
2026-11-01 12:39:20 +08:00
|
|
|
|
|
|
|
|
|
|
<label class="field"><span>用户名 <b class="req">*</b></span>
|
|
|
|
|
|
<input type="text" name="admin_user" maxlength="32" required
|
|
|
|
|
|
value="<?php echo e(isset($_POST['admin_user']) ? (string)$_POST['admin_user'] : 'admin'); ?>"
|
|
|
|
|
|
placeholder="admin" autocomplete="username">
|
|
|
|
|
|
<i class="hint">字母、数字、下划线、点、连字符,2~32 位。</i></label>
|
|
|
|
|
|
|
2026-09-28 22:53:55 +08:00
|
|
|
|
<div class="grid2">
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<label class="field"><span>口令 <b class="req">*</b></span>
|
|
|
|
|
|
<input type="password" name="admin_password" minlength="8" required autocomplete="new-password"></label>
|
|
|
|
|
|
<label class="field"><span>再输入一次 <b class="req">*</b></span>
|
|
|
|
|
|
<input type="password" name="admin_password2" minlength="8" required autocomplete="new-password"></label>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-actions">
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<button class="btn primary" type="submit" name="do_install" value="1"<?php echo $envOk ? '' : ' disabled'; ?>>完成安装,进入首页</button>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php if (!$envOk): ?>
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<p class="muted small">环境体检未通过前无法提交,请先处理上方标红项。</p>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card-form" style="margin-top:18px">
|
2026-11-01 12:39:20 +08:00
|
|
|
|
<p class="muted small">
|
|
|
|
|
|
其余设置都已自动处理:站点地址按访问域名自动探测,密钥与令牌已自动生成。<br>
|
|
|
|
|
|
邮件通知(SMTP)与附件上传(OSS)在装好后到后台「站点设置」里配,这里不用填。<br>
|
|
|
|
|
|
<b>装完请删掉 install.php</b>(它本身也会拒绝重复安装)。
|
|
|
|
|
|
</p>
|
2026-09-28 22:53:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
|
|
|
|
</main>
|
|
|
|
|
|
<footer class="foot">
|
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
<p class="muted foot-line"><span>轻反馈 FeedLite</span><span class="dot">·</span><a href="<?php echo e(fb_credit_url_default()); ?>" target="_blank" rel="noopener">FeedLite</a></p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|