diff --git a/install.php b/install.php
index 21ee7db..bd2ee2e 100644
--- a/install.php
+++ b/install.php
@@ -78,6 +78,14 @@ function fb_install_checks() {
foreach ($dirs as $d => $why) {
if (!is_dir($d)) { @mkdir($d, 0755, true); }
+ // 目录一建好就立刻补防护文件,不留"目录存在但无防护"的空窗期。
+ // data/ 用 fb_db_ensure_guard(),uploads/ 用 fb_local_ensure_dir(),
+ // 两者都在被保护目录里写 .htaccess + index.html。
+ if (basename($d) === 'data' && function_exists('fb_db_ensure_guard')) {
+ fb_db_ensure_guard($d);
+ } elseif (basename($d) === 'uploads' && function_exists('fb_local_ensure_dir')) {
+ fb_local_ensure_dir();
+ }
$w = is_dir($d) && is_writable($d);
if (!$w) { $ok = false; }
$shown = ($d === FB_ROOT) ? '(站点根)' : str_replace(FB_ROOT, '', $d);
diff --git a/lib/db.php b/lib/db.php
index df9d79b..17f8121 100644
--- a/lib/db.php
+++ b/lib/db.php
@@ -11,7 +11,11 @@ function fb_db_init($cfg) {
$path = (string)$cfg['db_path'];
$dir = dirname($path);
+ // 目录不存在就自己建:PHP 以 Web 用户身份运行,建出来的属主天然正确,
+ // 不需要部署账号预先建好、也不需要事后 chown。
if (!is_dir($dir)) { @mkdir($dir, 0755, true); }
+ // 每次进来都确保防护文件在位(函数内部有版本判断,命中后几乎零开销)
+ fb_db_ensure_guard($dir);
$fresh = !file_exists($path);
@@ -29,6 +33,51 @@ function fb_db_init($cfg) {
return $pdo;
}
+/**
+ * 确保数据库目录存在防护文件(.htaccess 拒绝直读 + index.html 占位)。
+ *
+ * 为什么不靠部署时上传:data/ 是可写目录,部署账号建它会让属主变成部署账号,
+ * Web 用户反而写不进去。让 PHP 自己建 + 自己补防护,属主永远正确。
+ * 版本号机制与 uploads/ 一致:规则串里埋 `# fb-data-guard: vN`,
+ * 低于当前版本就整份重写,避免老站点留着没防护的旧文件。
+ */
+define('FB_DATA_GUARD_VER', 1);
+
+function fb_data_guard_rule() {
+ return "# 数据库目录(由程序自动生成,请勿删除)\n"
+ . "# fb-data-guard: v" . FB_DATA_GUARD_VER . "\n"
+ . "# ★ 一律禁止 Web 直接访问:数据库文件绝不能被下载\n"
+ . "Options -Indexes\n\n"
+ . "\n Require all denied\n\n"
+ . "\n Order allow,deny\n Deny from all\n\n\n"
+ . "\n"
+ . " \n Require all denied\n \n"
+ . " \n Order allow,deny\n Deny from all\n \n"
+ . "\n";
+}
+
+function fb_db_ensure_guard($dir) {
+ if (!is_dir($dir)) { return false; }
+ $ht = $dir . '/.htaccess';
+ $need = true;
+ if (is_file($ht)) {
+ $head = @file_get_contents($ht, false, null, 0, 512);
+ if ($head !== false && strpos($head, '# fb-data-guard: v' . FB_DATA_GUARD_VER) !== false) {
+ $need = false;
+ }
+ }
+ if ($need) {
+ @file_put_contents($ht, fb_data_guard_rule());
+ @chmod($ht, 0644);
+ }
+ $idx = $dir . '/index.html';
+ if (!is_file($idx)) {
+ @file_put_contents($idx, '');
+ @chmod($idx, 0644);
+ }
+ return true;
+}
+
/** 便捷访问器 */
function fb_db() {
return fb_db_init(fb_cfg());