Commit Graph
8 Commits
Author SHA1 Message Date
laoyang b835afcd04 data/uploads 改为由 PHP 自动创建并自补防护,不依赖部署账号建目录 (#1)
## 问题
部署时用 btops 建 data/ 与 uploads/,属主成了 btops:btops,Web 用户 www 写不进去,
安装向导直接报「目录不可写」,还得请人用 root 手工 chown —— 这不该让用户干。

## 修法:让 PHP 自己建
PHP 以 Web 用户身份运行,它建出来的目录属主天生就是 www,不需要任何 chown。
- 部署脚本不再创建 data/ uploads/(只建 assets/ lib/)
- 首次访问时 fb_db_init() 自动 mkdir data/,并立刻补防护文件
- uploads/ 由 fb_local_ensure_dir() 同样自动建 + 补防护
- 安装向导的环境体检 mkdir 之后也立刻补防护,不留空窗期

## 新增 data/ 防护文件自动生成
原先只有 uploads/ 有自动防护,data/ 没有 —— 数据库目录裸奔。
新增 fb_db_ensure_guard(),写 .htaccess(拒绝直读,禁 db/db-wal/db-shm/sqlite/php)
+ index.html 占位,带 `# fb-data-guard: vN` 版本标记,规则升级时整份重写。

## 顺带确认
站点根 .htaccess 已有全局兜底(拒绝 .db/.log/.md/.bak 等直读 + Options -Indexes),
即使某个目录暂时缺 .htaccess,/data/ 和 /data/feedback.db 也是 403。
现在是双保险,不依赖单点。

验证(WSL 真实环境):
- 全新安装 33/33、老基线 25/25、附件专项 22/22、安装向导 60/60
- data/ 与 uploads/ 均由 PHP 自动创建,属主即运行用户
- 两者 .htaccess 自动生成并带正确版本标记(v1 / v2)
2026-11-01 15:07:53 +08:00
laoyang 1c2e8d5e65 安装向导:装完自动删除 install.php;站点根只读不再阻断安装 (#1 #2)
## 装完自删
install.php 是可写文件,留着等于长期暴露一个「重装入口」。
现在安装成功后自动 unlink 自己,不再要用户手动删。

两种结果都如实告知,不假装:
- 删除成功 → 成功页显示「已自动删除」
- 删不掉   → 显示「没能自动删除」+ 具体原因(目录不可写 / 属主不符)+ 手工处理命令
安装本身不受影响:清理失败不算安装失败。

## 🔴 修掉一个越权动作(本轮最要紧的修改)
初版为了「尽量删掉」,先 @chmod(dirname($self), 0755) 再 unlink。
这等于程序自己把「站点根只读」这个安全设置拆掉了 —— 管理员有意设成只读的目录
被 PHP 改回可写,然后文件就被删了。既违反管理员意图,也让「删不掉」这个真实场景
永远测不出来(我第一版测试就是这么被骗过去的)。

删除失败就如实报告,不准硬删。

## 站点根只读不再阻断安装
原先环境体检把「站点根可写」列为硬性要求,只读就红叉、禁止提交。
但这没道理:config.php 写不进去时,设置会全部落到数据库 settings 表,功能一样。
反过来,站点根只读是更安全的做法。

现在 data/ 与 uploads/ 可写仍是硬要求,站点根只读只显示为提示(不影响判定)。

## rename 兜底收窄
只在目录可写时才试 rename(目录不可写时 rename 同样失败,
硬试会在站点里留下 .deleted.xxxx 垃圾)。改名成功但删不掉则还原。

验证:
- 自删 A 场景(目录可写)→ 文件已删,页面提示「已自动删除」
- 自删 B 场景(目录只读)→ 文件保留,提示「目录对 Web 用户不可写」,安装仍成功
- 全新安装 33/33、老基线 25/25、附件专项 22/22、安装向导 60/60
2026-11-01 12:55:08 +08:00
laoyang 7a578fac47 静态资源版本收敛到 FB_ASSET_V 常量 (#1)
原先 layout.php 与 install.php 各写一份 style.css?v=N,改样式时只改一处,
另一处会漂移 —— 实际就发生了:首页 v=10、安装页还是 v=7,两页样式不一致。

现在统一在 lib/bootstrap.php 定义 FB_ASSET_V,两处都引用它。
以后改 assets 下的 css/js,只改这一个数字。
2026-11-01 12:47:41 +08:00
laoyang 9b822d2d9a 安装向导:加管理员用户名,精简为填表即装、装完进首页 (#1 #2)
## 管理员账号(用户名 + 口令)
原先只有口令,没有用户名。现在两者都有:
- 用户名词入库 settings 的 admin.username(2~32 位,字母数字下划线点连字符)
- 口令仍是 bcrypt 哈希存 admin.password_hash,明文不落任何文件
- 登录校验 fb_admin_verify_login():用户名或口令任一不对都返回同一句
  「用户名或口令不正确」,且用户名错时也照常跑一次 password_verify,避免用耗时探测账号
- 向后兼容:老部署只设了口令没用户名时,仍可只填口令登录,
  并在登录页提示补一个用户名

## 安装向导精简
按用户要求「没数据就初始化,填完进主页」:
- 必填只剩 4 项:网站名称、管理员用户名、口令、确认口令
- 站点描述 / 官方邮箱 / 站点根地址改为 hidden(可留空自动探测)
- 密钥、令牌原本就自动生成
- 安装成功后展示网站名称与用户名,2 秒自动跳首页,也有按钮
- 按钮文案改为「完成安装,进入首页」

## 修掉一个真实 bug
首页把软件说明当成站点描述,导致「软件有 note 时站点描述被顶掉」。
两者是不同的东西:站点描述是全局设置必须始终显示,软件说明另起一行补充。
老基线 D6「首页描述变为新描述」就是被这个 bug 弄失败的。

## 另一个坑
admin.username 忘了加进 fb_setting_keys() 白名单,fb_set_setting() 会静默丢弃,
表现为「安装时填了用户名,装完却没有」。已补。

验证:
- 全新安装流程 32/32(真空站:无 config.php、无 data/*.db)
- 老基线 25/25、附件专项 22/22、安装向导 60/60
- PHP 8.5 全部入口 0 弃用
2026-11-01 12:39:20 +08:00
laoyang 690228e7c1 安装体检:修正 fileinfo 说明(只影响本机存储模式)
原先写「上传校验会更严格」是不准确的:OSS 存储模式下,附件校验走对象存储
返回的 Content-Type(lib/attach.php 的 oss 分支),根本不调用 finfo。真正用到
fileinfo 的只有本机存储分支(lib/attach.php 的 local 分支、media.php)和
media.php 的本机附件 MIME 探测。

改动:
- install.php 体检项说明改为「按文件内容识别附件类型(仅本机存储模式用得上)」,
  缺失文案补上「不影响安装」,补救提示写清 OSS 模式以对象存储返回的类型为准、
  与本扩展无关,避免管理员误以为装个扩展就补了个安全缺口。
- README 同步区分两种存储模式;补充两点排查经验:
  面板显示「有」不等于运行中的进程已加载,要看 php.ini 里的 extension 行;
  且改完 ini 必须重启/重载 PHP-FPM 才生效(reload 即可,不必 restart)。

验证:php -l 双版本通过;安装向导 60/60、主回归 25/25、PHP 8.5 兼容 19/19;
线上部署 17/17,6 个文件 md5 逐个一致,冒烟全绿。
2026-10-23 09:07:39 +08:00
laoyang 23772b37ab 安装体检:把 fileinfo 列为推荐扩展
新装一套 PHP(尤其宝塔里的 8.5)时 fileinfo 常常默认没开,此时
上传的类型校验只能按「扩展名 + 浏览器声明的 MIME」核对。代码本身
有回落链、不会报错,但用户无从得知这层弱化,因此在安装体检里显式
列出,并说明开启后的收益。

- install.php:可选扩展支持 [说明, 缺失文案, 补救提示] 三元组
- README:扩展清单补 fileinfo,并提示换 PHP 版本后要复查一次
2026-10-23 08:42:47 +08:00
laoyang 6135e7b890 兼容 PHP 8.5:消除全部弃用调用
PHP 8.5 把 curl_close()/finfo_close() 这类"空操作函数"标为弃用,
并把预定义局部变量 http_response_header 标为弃用(编译期判定,@ 与三元短路都压不住)。

- 句柄关闭收进 fb_curl_close()/fb_finfo_close():仅 PHP < 8.0 真正调用,
  新版本直接跳过(8.0 起句柄随引用计数释放),保留 7.4 兼容
- 响应头读取改用 http_get_last_response_headers()(8.4+);
  老版本经 get_defined_vars() 从作用域变量表取,源码不再出现已弃用标识符
- 新 API 是全局状态,用「请求前基线」比对,避免把上一次的残留头当成本次结果
- 安装体检与 README 的推荐版本区间更新为 8.1~8.5

验证:PHP 8.5.12 下五套测试全绿(25/25、22/22、60/60、7/7、反向 4 组全被抓住),
error_log 零条弃用记录;PHP 8.4.25 同样全绿。
2026-10-23 08:23:55 +08:00
laoyang 27187b38f1 页脚入口与安装向导;加固管理员口令存储
需求落实:
1. 页脚常驻「管理后台」链接,不用再手敲 admin.php。
2. 页脚「由 FeedLite 驱动」中的 FeedLite 改为超链接(指向开源仓库);
   后台新增「页脚」设置组,可关闭整句(关闭后只留管理后台链接),也可改链接地址。
3. 管理员口令存储方式在后台如实展示:数据库 settings.admin.password_hash,
   存 bcrypt 单向哈希(不可逆),明文不落盘。
4. 新增安装向导 install.php:data/ 下没有数据库时自动 302 引导;
   体检环境 -> 填站点信息与口令 -> 生成 config.php -> 建库建表 -> 完成。

顺带修掉一个真实安全隐患:
  config.php 里的出厂占位口令 CHANGE_ME_admin 原先可被当作有效口令登录后台
  (等于公开仓库里就写着线上后门口令)。现改为占位/空值一律拒绝,
  未设口令时后台明确提示去安装向导,不再含糊报「口令不正确」。

实现要点:
- 新增 fb_cfg_file():只读配置文件、不叠加 settings 表、绝不触达数据库。
  「是否已安装」以数据库文件是否存在为判据,而 fb_cfg() 读 settings 会触发
  fb_db() 就地建库 —— 那会把安装态自己造出来,所以判定必须走不碰库的通道。
- bootstrap 只在已安装时建库;未安装则 302 到 install.php(CLI 下报错退出)。
- fb_settings_flat() 在未安装时直接返回空,杜绝任何隐式建库。
- install.php 用文本级替换生成 config.php,保留全部注释与 OSS/SMTP 段落;
  生成的 PHP 用 token_get_all(TOKEN_PARSE) 校验后才落盘。
- 页脚链接只放行 http/https,后台保存时校验(防 javascript: 伪协议)。
- style.css 页脚行改 flex,版本号 v6 -> v7。

验证:
- php -l 全量通过;现有回归 25/25、附件专项 13/13 保持通过。
- 安装向导专项 60/60(含「明文口令未写入任何文件」「数据库字节里搜不到明文」)。
- 反向验证:撤销三处修复后对应断言确实失败(断言非装饰)。
- 几何检查:本机 15/15 + 页脚专项全过;线上真实 URL 15/15 + 页脚专项全过。
- 线上真机冒烟 22/22。
2026-09-28 22:53:55 +08:00