Files
FeedLite/config.example.php
T
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

193 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* FeedLite · 轻反馈 · 配置文件
* ------------------------------------------------------------------
* 部署后请务必修改 admin_password 与 api_token(及 ip_salt),
* 并按需填写下方的 smtp 段以开启"回复邮件通知"。
*/
return [
/* ---------------- 基本 ---------------- */
// SQLite 数据库文件路径。
// 放在站内 data/ 目录(已被 .htaccess 拒绝直接访问);也可改为站点目录之外的绝对路径。
'db_path' => __DIR__ . '/data/feedback.db',
'site_name' => '轻反馈',
'site_desc' => '欢迎在这里反馈使用问题、提出功能建议。任何人都可以提问与回复,无需注册;只需填写昵称与邮箱。',
// 展示用的官方邮箱(可留空)
'admin_email' => 'support@example.com',
// 站点根地址(用于邮件正文里的链接)。留空则按访问域名自动探测。
// 例如:'https://feedback.example.com'
'base_url' => '',
/* ---------------- 页脚 ----------------
* 页脚永远会显示「管理后台」入口;这里的开关只控制右侧那句版权说明。
* show_credit = false:页脚不再出现「由 FeedLite 驱动」,只留管理后台链接。
*/
'footer' => [
'show_credit' => true,
'credit_url' => 'https://code.yangyq.net/laoyang/FeedLite', // 页脚「FeedLite」二字指向的地址
],
/* ---------------- 安全 ----------------
* 管理员口令的存放规则(重要):
* · 正常安装(走 install.php,或登录后台改口令)→ 口令以 **bcrypt 单向哈希**
* 存在数据库 settings 表的 admin.password_hash,**明文不落盘**。
* · 下面这个 admin_password 只是「初始值」通道,且**出厂占位值已被程序明确禁用**:
* 值仍为 CHANGE_ME_admin 时,任何人(包括你)都无法用它登录后台。
* · 想手工指定初始口令,就把下面这行改成你自己的口令;登录一次后建议立即改掉,
* 改完即转为数据库哈希存储、这里可以再改回占位值。
*/
// ★★ 留空或保持占位值 = 未设置口令(后台不可登录,需先跑安装向导)★★
'admin_password' => 'CHANGE_ME_admin',
// API 令牌:供自动化脚本 / AI 读取数据用。★ 请务必修改 ★
'api_token' => 'CHANGE_ME_api_token',
// 对访客 IP 做加盐哈希(数据库只保存哈希,不保存明文 IP)。★ 请修改为随机值 ★
'ip_salt' => 'CHANGE_ME_random_salt',
/* ---------------- 问题分类 ---------------- */
'categories' => [
'usage' => '使用问题',
'bug' => 'Bug 反馈',
'feature' => '功能建议',
'sync' => '同步问题',
'other' => '其他',
],
/* ---------------- 处理状态 ---------------- */
'statuses' => [
'open' => '待处理',
'doing' => '处理中',
'resolved' => '已解决',
'closed' => '已关闭',
],
/* ---------------- 邮件通知(SMTP) ---------------- */
'smtp' => [
'enabled' => false, // 填好下面参数后改为 true 才会真正发信
'host' => 'smtp.example.com', // 如 smtp.qiye.aliyun.com / smtp.exmail.qq.com
'port' => 465,
'secure' => 'ssl', // ssl(465)| tls(587 STARTTLS)| ''(25 不加密)
'user' => 'noreply@example.com',
'pass' => '', // 邮箱密码或"授权码"
'from' => 'noreply@example.com', // 发件地址(通常与 user 相同)
'from_name' => '轻反馈',
'timeout' => 15,
'verify_ssl' => true, // 若服务器缺少 CA 证书可临时设为 false
],
// 通知策略
'notify' => [
'enabled' => true, // 回复时是否尝试发送通知邮件
'on_every_reply' => true, // true=任何人回复都通知发帖人;false=仅"官方"回复才通知
'cooldown_seconds' => 300, // 同一帖同一收件人的通知节流(秒)
],
/* ---------------- 审核(先审后显) ----------------
* enabled = true:新提交的问题默认**不公开**,由管理员在 admin.php 审核;
* · 通过 → 公开显示(visible=1)
* · 关闭 → 判定"不是应用的问题",保持不公开,并把管理员填写的说明邮件发给提问人
* 审核后台入口:/feedback/admin.php(口令= admin_password)
*/
'moderation' => [
'enabled' => true,
'close_mail_subject' => '关于你反馈的问题《%s》', // %s 会替换成问题标题
],
/* ---------------- 附件上传(阿里云 OSS · 服务器代理) ----------------
* ★ 架构:私有桶 + 内网 Endpoint,读写全部经服务器代理
* 桶设为【私有】、且只用【内网 Endpoint】(*-internal.aliyuncs.com,仅同地域 ECS 可达),
* 浏览器既不直传、也不直连。数据流:
* 上传:浏览器 → upload.php → 服务器 PUT 到内网 OSS
* 读取:浏览器 → media.php → 服务器 GET 内网 OSS 并流式转发(支持 Range)
* 好处:桶可彻底私有、公网完全看不到 Endpoint、AccessKey 永不出服务器。
* 代价:附件流量经过服务器(占带宽),需保证服务器磁盘临时空间够放单个上传文件。
*
* ★ 凭证方式:ECS 实例 RAM 角色(推荐,服务器上不保存长期 AccessKey)或静态 AccessKey。
* 实例角色下,程序从内网元数据服务(http://100.100.100.200,链路本地、不出公网)
* 读取一份「有时效、会自动轮换」的临时凭证来签名。AccessKey 永不落盘、永不进本文件。
*
* ★ 开通步骤(详见 README.md):
* ① 创建【与 ECS 同地域】的 Bucket,读写权限设为「私有」
* ⚠️ 内网 Endpoint 跨地域不通!ECS 在哪个地域,桶就建在哪个地域。
* ② RAM 建权限策略:只授予该 Bucket(某前缀下)的 oss:PutObject / GetObject / DeleteObject
* ③ RAM 建角色(如 FeedLiteOssRole)并赋予上一步的策略
* ④ ECS 控制台把该角色【授予本实例】
* ⑤ 把 endpoint / bucket / instance_role 填到下面,use_internal_endpoint 保持 true,enabled = true
* ⑥ 后台「站点设置 → 存储体检」确认桶连通性为 ✅
* (代理架构下【不需要】配置 CORS,也【不需要】绑定自定义域名)
*
* ★ 前提:站点必须运行在【阿里云 ECS】上并且已绑定 RAM 角色;
* 未绑定则取不到凭证,上传接口会明确报错(其它功能不受影响)。
*/
'oss' => [
'enabled' => false, // 填好参数后改为 true 才启用上传
'endpoint' => 'oss-cn-qingdao.aliyuncs.com', // 不含 http://;填公网或内网均可,程序按下方开关自动转换
'bucket' => 'your-bucket-name',
// ★ ECS 实例 RAM 角色名 —— 必须与【ECS 实例实际绑定的角色名】逐字一致。
// 查法:curl http://100.100.100.200/latest/meta-data/ram/security-credentials/
// 注意:RAM 里"创建的角色名"和"授予实例后实例上生效的角色名"可能不同(如
// 建的是 MyOssRole,授予实例后生效名可能是系统预置策略对应的角色),以查询结果为准。
'instance_role' => '',
// ★ 是否用内网 Endpoint(默认开)。开启后程序自动把 endpoint 改写成 *-internal 形式,
// 走阿里云内网访问 OSS:免公网流量费、延迟更低,且公网完全无法直连该地址。
// ⚠️ 仅当【桶与本机 ECS 同地域】时可用;跨地域时内网不可达,必须关掉本开关
// (回落公网 Endpoint)或改用同地域的桶。后台「存储体检」会探测并提示。
'use_internal_endpoint' => true,
// ---- 以下一般不用改 ----
'metadata_url' => '', // 留空=http://100.100.100.200(ECS 内网元数据服务)
'metadata_v2' => false, // true=加固模式(先取一次性 token 再读元数据,防 SSRF 探测)
'metadata_ttl' => 600, // 加固模式 token 有效期(秒,最长 21600)
'credential_cache' => '', // 凭证缓存文件;留空=与数据库同目录的 oss.sts.php
'credential_refresh_margin' => 300, // 到期前多少秒就提前换新(秒)
'domain' => '', // 代理架构下【不再使用】:附件一律经本站 media.php 输出,无需自定义域名
'url_scheme' => 'https', // 公网 Endpoint 时使用的协议(内网 Endpoint 强制走 http)
'prefix' => 'fb', // 对象键前缀:fb/2026/09/xxxxxxxx.jpg
'visibility' => 'private', // 记录桶的读写权限:代理架构下推荐 private(浏览器永不直连桶)
'sign_ttl' => 600, // 服务器内部签名 URL 的有效期(秒),仅服务器侧使用
'link_ttl' => 604800, // 兼容保留;代理读取时签名有效期由代码收敛到 ≤300 秒
'image_process' => true, // 列表缩略图使用 OSS 图片处理(服务端缩放后再转发,省下行流量)
'verify' => true, // 提交时 HEAD 复核对象(防伪造/防超限)
'host_override' => '', // 仅特殊网关/联调使用;留空自动按 bucket.endpoint 拼接
],
/* ---------------- 附件限额 ----------------
* 单位:字节。原图上限指「压缩前」的原始文件大小(前端会先压缩到最长边 1600px)。
*/
'upload' => [
'image_max_per_post' => 4,
'image_max_bytes' => 5242880, // 5 MB / 张
'image_exts' => ['jpg', 'jpeg', 'png', 'webp', 'gif'],
'image_mimes' => ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
'image_max_side' => 1600, // 前端压缩后的最长边(像素)
'video_max_per_post' => 1,
'video_max_bytes' => 52428800, // 50 MB / 个
'video_exts' => ['mp4', 'mov', 'webm'],
'video_mimes' => ['video/mp4', 'video/quicktime', 'video/webm'],
'total_cap_bytes' => 1073741824, // 全站附件总量上限 1 GB,达到后暂停接收新上传
'retention_days' => 365, // 附件保留天数,超期由 cron.php 清理
'upload_per_hour' => 60, // 每 IP 每小时最多申请多少次上传凭证
],
/* ---------------- 反垃圾 ---------------- */
'rate_limit' => [
'thread_per_hour' => 5, // 每个 IP 每小时最多发起的主题数
'reply_per_hour' => 20, // 每个 IP 每小时最多回复数
],
'post_min_seconds' => 3, // 打开表单到提交不得少于该秒数(过快视为机器人)
/* ---------------- 列表 ---------------- */
'page_size' => 20,
];