搜索

论坛bbs1org 生产环境部署手册(Linux 源码部署)

1352
  • dabo

    bbs1org 生产环境部署手册(Linux 源码部署)

    本手册面向在 Linux 服务器上以源码方式部署 bbs1org(Nginx + PHP-FPM)。
    适用版本:v8.5.24。演示与源码入口:https://bbs1.org


    1. 架构与目录说明

    bbs1org 是单文件核心的原生 PHP 应用,入口为根目录下的 index.php,所有请求都由它处理(前端控制器模式)。

    关键目录(部署与安全都围绕它们):

    路径用途Web 是否可访问Web 用户是否需写入
    index.php唯一入口是(经 PHP-FPM 执行)
    app/assets/CSS/JS/SVG 静态资源是(直接返回)(插件资源会合并写入 plugins.css/plugins.js
    app/upload/附件是(直接返回)
    app/avatars/头像是(直接返回)
    app/plugins/插件 PHP 源码否(禁止直接执行)是(在线安装插件时写入)
    app/data/SQLite 库、db.php 数据库凭据、日志、锁文件绝对禁止
    app/optional/核心辅助类

    安全底线:app/data/ 含数据库文件和明文数据库凭据(db.php),一旦被 Web 直接下载即等于泄库。下面的 Nginx 配置会强制拦截。


    2. 环境准备

    以 Debian/Ubuntu 为例(CentOS/RHEL 换成 dnf 并用 remi 源即可)。

    推荐用不带版本号的元包,自动安装发行版默认的 PHP(应用要求 8.1+,Debian 13/trixie 默认为 8.4,Ubuntu 24.04 默认为 8.3,均可):

    sudo apt update
    sudo apt install -y nginx php-fpm php-cli \
      php-sqlite3 php-mysql php-pgsql \
      php-mbstring php-opcache unzip curl

    若按 php8.3-fpm 这类固定版本号报 Unable to locate package,就是因为该发行版默认 PHP 不是 8.3。用上面的 php-* 元包即可。确需指定版本(如一定要 8.3)时,Debian/Ubuntu 需先加 Ondřej Surý 源(https://packages.sury.org/php)再装 php8.3-*

    按数据库选装扩展即可:SQLite 需 php-sqlite3,MySQL 需 php-mysql,PostgreSQL 需 php-pgsql

    确认实际 PHP 版本与 FPM socket(后续第 6、7 步要用到,版本号请以此为准):

    php -v                                  # 例如输出 PHP 8.4.x
    php -m | grep -Ei 'pdo|sqlite|mysql|pgsql|opcache'
    ls /run/php/                            # 找到 FPM socket,例如 php8.4-fpm.sock
    ls /etc/php/                            # 找到配置目录版本号,例如 8.4

    后文示例统一以 8.3 书写,请将其替换为你上面查到的实际版本号(如 8.4)。为方便,可先设一个变量:

    PHPVER=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')   # 得到如 8.4
    echo "$PHPVER"

    3. 获取并部署源码

    https://bbs1.org/plugin_market_source 下载 bbs1org.zip,上传解压到网站目录:

    sudo mkdir -p /var/www
    cd /var/www
    sudo unzip -q /tmp/bbs1org.zip        # 解压后确保 index.php 位于 /var/www/bbs1org/index.php
    sudo mv bbs1org bbs1org 2>/dev/null || true

    如果你直接部署本机当前这份源码(已包含对 SQLite 嵌套事务 tx() 的修复),请整目录打包上传。注意:日后使用后台“在线升级”会覆盖 index.php,升级后若使用 SQLite + 积分类插件需重新确认该修复是否仍在。

    最终目录形如 /var/www/bbs1org/,下文统一用此路径。


    4. 目录与权限

    Web 运行用户通常是 www-data(Nginx/PHP-FPM 默认)。以下目录必须可写:

    cd /var/www/bbs1org
    # 代码归属:属主可管理,属组为 www-data
    sudo chown -R $USER:www-data /var/www/bbs1org
    sudo find /var/www/bbs1org -type d -exec chmod 750 {} \;
    sudo find /var/www/bbs1org -type f -exec chmod 640 {} \;
    
    # 运行时可写目录(含 assets 因为要写入合并后的插件资源)
    sudo mkdir -p app/data app/upload app/avatars app/plugins
    sudo chown -R www-data:www-data app/data app/upload app/avatars app/plugins app/assets
    sudo chmod -R 770 app/data app/upload app/avatars app/plugins
    sudo chmod -R u+rwX,g+rwX app/assets

    若安装/装插件时报“无法写入”,基本都是 app/dataapp/pluginsapp/assets 权限或属主不对。


    5. 选择数据库

    三选一。选定后记住连接信息,第 9 步网页安装时要填。命令以 Debian/Ubuntu 为例。

    方案 A:SQLite(默认,最省事,适合中小站点)

    无需安装任何数据库服务,也无需建库建用户。只要第 2 步装了 php-sqlite3 扩展、第 4 步给了 app/data/ 写权限即可——安装时数据库类型选 SQLite,程序会自动在 app/data/ 下创建库文件。

    确认扩展在位:

    php -m | grep -i sqlite      # 应看到 pdo_sqlite 和 sqlite3

    第 9 步安装时:数据库类型选 SQLite,其余连接项留空。跳过本节其余内容即可。


    方案 B:MySQL / MariaDB

    1. 安装并启动(Debian 13 默认是 MariaDB):
    sudo apt install -y mariadb-server
    sudo systemctl enable --now mariadb
    sudo mysql_secure_installation      # 交互式:设 root 密码、删匿名用户、禁远程 root、删测试库
    1. 以 root 进入数据库控制台:
    sudo mysql                          # 通过本地 socket 以 root 登录
    1. MariaDB [(none)]> 提示符下执行(把 强密码 换成真实强密码):
    CREATE DATABASE bbs1org CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'bbs'@'127.0.0.1' IDENTIFIED BY '强密码';
    GRANT ALL PRIVILEGES ON bbs1org.* TO 'bbs'@'127.0.0.1';
    FLUSH PRIVILEGES;
    EXIT;

    应用通过 TCP 127.0.0.1 连接,所以用户主机限定为 'bbs'@'127.0.0.1'(不是 localhost)。数据库与站点同机时,MariaDB 默认只监听 127.0.0.1,无需对公网开放 3306。

    1. 验证账号能连(能打印出 1 即成功):
    mysql -h127.0.0.1 -ubbs -p bbs1org -e 'SELECT 1;'
    1. 第 9 步安装时填:类型 MySQL、host 127.0.0.1、port 3306、库名 bbs1org、用户 bbs、密码 强密码

    方案 C:PostgreSQL

    1. 安装并启动:
    sudo apt install -y postgresql
    sudo systemctl enable --now postgresql
    1. 以 postgres 超级用户进入控制台:
    sudo -u postgres psql
    1. postgres=# 提示符下执行(把 强密码 换成真实强密码):
    CREATE DATABASE bbs1org;
    CREATE USER bbs WITH PASSWORD '强密码';
    ALTER DATABASE bbs1org OWNER TO bbs;     -- 关键:让 bbs 成为库属主,安装时才能建表
    GRANT ALL PRIVILEGES ON DATABASE bbs1org TO bbs;
    \q

    PostgreSQL 15+(trixie 为 17)注意public schema 默认不再允许普通用户建表。上面的 ALTER DATABASE ... OWNER TO bbs 就是为解决这个问题;否则安装会报 permission denied for schema public。若你不想改属主,也可改为:\c bbs1org 后执行 GRANT ALL ON SCHEMA public TO bbs;

    1. 验证账号能通过密码连上(Debian 默认对 127.0.0.1 用 scram 密码认证,设了密码即可):
    PGPASSWORD='强密码' psql -h 127.0.0.1 -p 5432 -U bbs -d bbs1org -c 'SELECT 1;'
    1. 第 9 步安装时填:类型 PostgreSQL、host 127.0.0.1、port 5432、库名 bbs1org、用户 bbs、密码 强密码

    6. Nginx 配置

    创建 /etc/nginx/sites-available/bbs1org.conf

    server {
        listen 80;
        server_name your-domain.com;
        root /var/www/bbs1org;
        index index.php;
    
        # 附件上传大小,按需调整(需与 PHP 的 upload_max_filesize 一致)
        client_max_body_size 20m;
    
        # 前端控制器:真实文件(assets/upload/avatars)直接返回,其余交给 index.php
        location / {
            try_files $uri /index.php$is_args$args;
        }
    
        # 只允许执行 index.php(唯一入口),杜绝直接执行插件等任意 PHP
        location = /index.php {
            include fastcgi_params;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;   # 改成第 2 步查到的实际 socket,如 php8.4-fpm.sock
            fastcgi_param SCRIPT_FILENAME $document_root/index.php;
            fastcgi_read_timeout 60s;
        }
    
        # 硬拦截:数据目录(SQLite 库/凭据/日志)与核心类目录
        location ^~ /app/data/     { deny all; return 404; }
        location ^~ /app/optional/ { deny all; return 404; }
    
        # 其余 .php 一律不解析,防止直接下载或执行插件源码
        location ~ \.php$ { return 404; }
    
        # 隐藏文件(.git、.env 等),放行 ACME 校验
        location ~ /\.(?!well-known) { deny all; return 404; }
    
        # 静态资源缓存(可选)
        location ~* ^/app/(assets|upload|avatars)/.*\.(css|js|svg|png|jpe?g|gif|webp|ico|woff2?)$ {
            expires 7d;
            add_header Cache-Control "public";
            try_files $uri =404;
        }
    }

    启用并重载:

    sudo ln -s /etc/nginx/sites-available/bbs1org.conf /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx

    location = /index.php 是精确匹配,优先级高于 location ~ \.php$,因此入口正常执行而其它 PHP 被拦截。


    7. PHP-FPM 建议配置

    编辑 /etc/php/8.3/fpm/conf.d/99-bbs1org.ini(新建;路径中的 8.3 换成第 2 步查到的实际版本,如 8.4):

    ; 附件相关,二者需 >= Nginx client_max_body_size
    upload_max_filesize = 20M
    post_max_size = 21M
    max_execution_time = 60
    memory_limit = 256M
    
    ; 生产环境不要显示错误(核心代码已默认关闭,这里双保险)
    display_errors = Off
    log_errors = On
    
    ; OPcache(应用带 opcache_refresh 路由)
    opcache.enable = 1
    opcache.memory_consumption = 128
    opcache.max_accelerated_files = 10000
    opcache.validate_timestamps = 1
    opcache.revalidate_freq = 60

    重启:sudo systemctl restart php8.3-fpm(服务名同样把 8.3 换成实际版本,如 php8.4-fpm;不确定可用 sudo systemctl restart "php*-fpm"

    时区已在核心代码中固定为 Asia/Shanghai,无需在 php.ini 单独设置。


    8. HTTPS(强烈建议)

    登录态 Cookie 只有在 HTTPS 下才会带 Secure 标记,生产务必启用 TLS。

    sudo apt install -y certbot python3-certbot-nginx
    sudo certbot --nginx -d your-domain.com

    certbot 会自动改写 Nginx 增加 443 与跳转。请确认 443 的 server 块里 PHP 段包含协议传递,让应用识别为 HTTPS:

        location = /index.php {
            include fastcgi_params;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;      # 同上,改成实际 socket
            fastcgi_param SCRIPT_FILENAME $document_root/index.php;
            fastcgi_param HTTPS on;                          # 关键:告知应用走的是 HTTPS
            fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;     # 如前面还有反代/CDN 时也生效
        }

    改完 sudo nginx -t && sudo systemctl reload nginx


    9. 网页安装

    浏览器打开 https://your-domain.com,首次访问会跳转安装向导:

    1. 选择数据库类型(SQLite 免填连接信息;MySQL/PostgreSQL 填第 5 步的连接参数)。
    2. 设置站点名称、默认版块。
    3. 创建管理员账号(即超级管理员 uid=1)。

    安装完成后会在 app/data/ 生成 db.php(连接配置)和 install.lock(安装锁)。这两个文件绝不能被 Web 访问——第 6 步的配置已拦截。


    10. 定时任务(必须)

    收尾统计、计划任务、日志清理都依赖 cron。核心支持 CLI 方式(无需走 HTTP,最推荐):

    系统 crontab(以 www-data 身份,保证生成文件属主正确):

    sudo crontab -u www-data -e

    加入一行(每分钟一次):

    * * * * * /usr/bin/php /var/www/bbs1org/index.php cron >/dev/null 2>&1

    或用 systemd timer(更可控)。新建 /etc/systemd/system/bbs1org-cron.service

    [Unit]
    Description=bbs1org scheduled tasks
    [Service]
    Type=oneshot
    User=www-data
    ExecStart=/usr/bin/php /var/www/bbs1org/index.php cron

    /etc/systemd/system/bbs1org-cron.timer

    [Unit]
    Description=Run bbs1org cron every minute
    [Timer]
    OnCalendar=*:0/1
    AccuracySec=10s
    Persistent=true
    [Install]
    WantedBy=timers.target

    启用:

    sudo systemctl daemon-reload
    sudo systemctl enable --now bbs1org-cron.timer
    sudo systemctl list-timers | grep bbs1org

    也可用外部服务(如 cron-job.org)每分钟 GET https://your-domain.com/index.php?a=cron,但 CLI 更稳、无公网暴露。


    11. 安全加固清单

    • [x] app/data/app/optional/ 已在 Nginx 拦截(deny all)。
    • [x] 仅 index.php 交给 PHP-FPM,其它 .php 返回 404(插件源码无法被直接执行/下载)。
    • [x] 启用 HTTPS,Cookie 带 Secure
    • [ ] 文件权限最小化:代码 640/目录 750,仅运行时目录给 www-data 写权限。
    • [ ] 数据库使用最小权限账号,密码强度足够;db.php 权限 640 且属主 www-data
    • [ ] 后台设置里关闭“开放注册”或加验证,按需限制发帖频率。
    • [ ] 关注“在线升级/插件市场”会从 https://bbs1.org 下载并落地代码——仅在信任来源时使用;如需锁死,可在防火墙限制该出站或不使用该功能。
    • [ ] 服务器基础加固:防火墙只放行 80/443/SSH,SSH 禁用密码登录。

    12. 备份

    需要备份的内容(数据库 + 三个运行时目录 + 插件):

    #!/usr/bin/env bash
    set -e
    APP=/var/www/bbs1org
    DEST=/var/backups/bbs1org/$(date +%F_%H%M)
    mkdir -p "$DEST"
    
    # 1) 数据库
    #   SQLite:直接备份 data 目录即可(下一步已含)
    #   MySQL:   mysqldump -h127.0.0.1 -ubbs -p bbs1org > "$DEST/db.sql"
    #   Postgres:pg_dump -h127.0.0.1 -Ubbs bbs1org > "$DEST/db.sql"
    
    # 2) 附件/头像/插件/数据目录
    tar czf "$DEST/files.tgz" -C "$APP/app" data upload avatars plugins
    
    # 3) 保留 14 天
    find /var/backups/bbs1org -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;

    挂到 crontab 每日执行。升级或迁移前务必先跑一次备份。


    13. 升级与数据迁移

    • 在线升级:后台设置底部点“升级”,仅在确认后下载源码文件。升级前备份数据库、app/uploadapp/avatarsapp/plugins。升级会覆盖核心文件(如你对 index.php 打过本地补丁,升级后需复查)。
    • 数据库迁移/切换引擎:先在新库完成安装并登录管理员,再访问 index.php?a=migrate,填写旧库连接,程序会迁入全部数据表(含插件表)。附件、头像、插件文件需另行复制 app/upload/app/avatars/app/plugins/

    14. 故障排查

    现象排查方向
    打开即报“请先进行数据初始化安装”尚未安装或 app/data/install.lock 丢失;进 ?a=install
    500 / 白屏sudo tail -f /var/log/nginx/error.logapp/data/debug.log(超管 uid=1 登录后页面会显示异常详情)。
    安装/装插件报“无法写入”app/dataapp/pluginsapp/assets 属主/权限不对,重跑第 4 步。
    登录后频繁掉线 / Cookie 不生效未启用 HTTPS 或未传 HTTPS on/X-Forwarded-Proto,见第 8 步。
    定时任务不生效sudo -u www-data php /var/www/bbs1org/index.php cron 手动跑一次看报错;检查 timer/crontab。
    静态样式/JS 不更新访问 ?a=opcache_refresh 刷新 OPcache;确认 app/assets 可写。
    直接访问 /app/data/xxx 能下载Nginx 拦截未生效,立刻修正第 6 步,并更换数据库密码

    部署完成后,建议用无痕窗口验证:首页可访问、能登录后台、发帖正常、https://your-domain.com/app/data/ 返回 404、?a=cron 的定时任务在跑。


    附录 A:用 Caddy 替代 Nginx(自动 HTTPS)

    如果用 Caddy,则替代第 6 节(Nginx)和第 8 节(HTTPS/certbot),其余章节(2 环境、3 源码、4 权限、5 数据库、9 安装、10 定时任务、11-14)完全照旧。Caddy 会按域名自动申请并续期证书,无需 certbot。

    前置条件:域名 A 记录已解析到本服务器,且服务器 80/443 端口对公网开放(自动签发证书需要)。

    A.1 安装 Caddy(Debian/Ubuntu 官方源)

    sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
      | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
      | sudo tee /etc/apt/sources.list.d/caddy-stable.list
    sudo apt update
    sudo apt install -y caddy

    A.2 让 Caddy 能读取文件、连上 PHP-FPM(关键)

    Caddy 服务默认以 caddy 用户运行,而站点文件和 FPM socket 都属 www-data。把 caddy 加入 www-data 组即可同时解决“读文件”和“连 socket”两个权限问题:

    sudo usermod -aG www-data caddy
    sudo systemctl restart caddy

    第 4 节已设代码目录 750、运行时目录 770、文件 640,组均为 www-data;FPM socket 默认 listen.group=www-data0660。所以 caddy 进组后读文件与连 socket 都放行。
    若不想改组,替代方案是让 FPM 走 TCP:编辑 FPM 池配置把 listen = 127.0.0.1:9000,重启 FPM,再把下面 Caddyfile 里的 unix//run/php/php8.4-fpm.sock 换成 127.0.0.1:9000

    A.3 编写 Caddyfile

    编辑 /etc/caddy/Caddyfile,替换为(把 your-domain.com 换成你的域名,socket 版本号换成第 2 步查到的实际值,如 php8.4-fpm.sock):

    your-domain.com {
        root * /var/www/bbs1org
        encode zstd gzip
    
        # 附件上传大小上限(与 PHP 的 upload_max_filesize 保持一致)
        request_body {
            max_size 20MB
        }
    
        # 用 route 固定处理顺序,安全规则优先
        route {
            # 1) 硬拦截:数据目录(SQLite 库/凭据/日志)与核心类目录
            @deny path /app/data/* /app/optional/*
            respond @deny 404
    
            # 2) 除 index.php 外的 .php 一律 404(防止直接执行/下载插件源码)
            @otherphp {
                path *.php
                not path /index.php
            }
            respond @otherphp 404
    
            # 3) 前端控制器:真实文件(assets/upload/avatars)直接返回,其余交给 index.php
            php_fastcgi unix//run/php/php8.4-fpm.sock {
                try_files {path} /index.php
            }
            file_server
        }
    }

    Caddy 终止 TLS 后会自动把 HTTP 跳转到 HTTPS,并向 FPM 传递 HTTPS=on,因此应用能识别为 HTTPS、登录 Cookie 会带 Secure,无需像 Nginx 那样手动加 fastcgi_param HTTPS on

    A.4 校验并启动

    sudo caddy validate --config /etc/caddy/Caddyfile   # 语法检查
    sudo systemctl reload caddy                          # 重载配置(首次会自动申请证书)
    sudo systemctl status caddy --no-pager               # 看是否正常运行
    sudo journalctl -u caddy -f                          # 观察证书签发/错误日志

    看到证书成功签发后,浏览器访问 https://your-domain.com 即进入第 9 步网页安装。

    A.5 Caddy 场景的排查补充

    现象排查方向
    502 / dial unix ... permission deniedcaddy 未加入 www-data 组(A.2),或 socket 路径/版本号写错;ls -l /run/php/ 核对。
    403 / 静态文件读不到文件权限或组不对,caddy 需能读 app/assets|upload|avatars;重跑 A.2 与第 4 节。
    证书签发失败80/443 未开放或域名未解析到本机;看 journalctl -u caddy
    登录掉线 / Cookie 不带 Secure极少见;可在 php_fastcgi { ... } 内加一行 env HTTPS on 强制(Caddy 已自动跳转 HTTPS,FPM 只会收到 https 请求,加了也安全)。
  • 太上皇
    #1

    学习了,丢给Hermes应该自己能部署好吧

  • 盘古
    #2

    真长,支持下我的插件

发表回复

登录后回复