论坛bbs1org 生产环境部署手册(Linux 源码部署)
- 太上皇12026-08-12 15:35:20
bbs1org 生产环境部署手册(Linux 源码部署)
本手册面向在 Linux 服务器上以源码方式部署 bbs1org(Nginx + PHP-FPM)。
适用版本:v8.5.24。演示与源码入口:https://bbs1.org1. 架构与目录说明
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/data、app/plugins、app/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
- 安装并启动(Debian 13 默认是 MariaDB):
sudo apt install -y mariadb-server sudo systemctl enable --now mariadb sudo mysql_secure_installation # 交互式:设 root 密码、删匿名用户、禁远程 root、删测试库- 以 root 进入数据库控制台:
sudo mysql # 通过本地 socket 以 root 登录- 在
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即成功):
mysql -h127.0.0.1 -ubbs -p bbs1org -e 'SELECT 1;'- 第 9 步安装时填:类型 MySQL、host
127.0.0.1、port3306、库名bbs1org、用户bbs、密码强密码。
方案 C:PostgreSQL
- 安装并启动:
sudo apt install -y postgresql sudo systemctl enable --now postgresql- 以 postgres 超级用户进入控制台:
sudo -u postgres psql- 在
postgres=#提示符下执行(把强密码换成真实强密码):
CREATE DATABASE bbs1org; CREATE USER bbs WITH PASSWORD '强密码'; ALTER DATABASE bbs1org OWNER TO bbs; -- 关键:让 bbs 成为库属主,安装时才能建表 GRANT ALL PRIVILEGES ON DATABASE bbs1org TO bbs; \qPostgreSQL 15+(trixie 为 17)注意:
publicschema 默认不再允许普通用户建表。上面的ALTER DATABASE ... OWNER TO bbs就是为解决这个问题;否则安装会报permission denied for schema public。若你不想改属主,也可改为:\c bbs1org后执行GRANT ALL ON SCHEMA public TO bbs;。- 验证账号能通过密码连上(Debian 默认对
127.0.0.1用 scram 密码认证,设了密码即可):
PGPASSWORD='强密码' psql -h 127.0.0.1 -p 5432 -U bbs -d bbs1org -c 'SELECT 1;'- 第 9 步安装时填:类型 PostgreSQL、host
127.0.0.1、port5432、库名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 nginxlocation = /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.comcertbot 会自动改写 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,首次访问会跳转安装向导:- 选择数据库类型(SQLite 免填连接信息;MySQL/PostgreSQL 填第 5 步的连接参数)。
- 设置站点名称、默认版块。
- 创建管理员账号(即超级管理员
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/upload、app/avatars、app/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.log与app/data/debug.log(超管uid=1登录后页面会显示异常详情)。安装/装插件报“无法写入” app/data、app/plugins、app/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 caddyA.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-data、0660。所以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 请求,加了也安全)。 学习了,丢给Hermes应该自己能部署好吧
真长,支持下我的插件