PHP Censor 2.1.6 Ubuntu 22.04 安装指南
在 Ubuntu 22.04.5 LTS 上从零安装 PHP Censor 2.1.6:Composer 在线与源码包离线两条路径、Nginx+PHP 8.1 站点配置、systemd 托管 Worker、首次构建验证与排障实录。
PHP Censor 2.1.6 Ubuntu 22.04 安装指南
目标:在一台 Ubuntu 22.04.5 LTS 服务器上,从空的系统开始,完成 PHP Censor 2.1.6 的依赖安装、数据库初始化、Web 站点接入、Worker 常驻托管与首次构建验证,全程命令与回显均为实测。文中同时给出「Composer 在线安装」与「官方源码包离线安装」两条路径,读者可按机房网络条件二选一。
一、环境信息
本次安装在一台 Ubuntu 22.04.5 LTS 服务器上完成,配置与版本如下。所有路径、端口均为该服务器的真实取值,可直接对照复现。
| 项目 | 取值 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS (Jammy Jellyfish) | PRETTY_NAME 实测 |
| 主机名 | php-censor | /etc/hostname |
| 访问域名 | php-censor.local | 写入 Nginx server_name 与 app/config.yml 的 url |
| PHP | 8.1.2-1ubuntu2.28(cli / fpm) | Ubuntu 22.04 默认 PHP 版本 |
| PHP-FPM socket | /run/php/php8.1-fpm.sock | 属主 www-data:www-data |
| Nginx | nginx/1.18.0 (Ubuntu) | 监听 80 |
| 数据库 | MariaDB 10.6.23 | 监听 127.0.0.1:3306 |
| 队列 | Beanstalkd 1.12 | 监听 127.0.0.1:11300 |
| Composer | 2.2.6 | Ubuntu 源内版本 |
| PHP Censor | 2.1.6 | VERSION.md 与 ./bin/console --version 双重确认 |
| 应用目录 | /var/www/php-censor | 站点根目录为 public |
| 站点配置 | /etc/nginx/sites-available/php-censor | 软链到 sites-enabled |
| 运行账号 | php-censor(uid 999) | Web 与 Worker 共用 |
| 安装方式 | 方案 A:Composer create-project;方案 B:官方源码包 + composer install | 两方案均实测 |
说明:本文所有
systemctl命令均针对单个单元执行,输出为实测回显;输出过长处标注「输出节选(实测首尾)」。
二、前置条件
2.1 官方系统要求核对
官方 README 的 System requirements 一节列了六条硬要求,安装前应逐条对齐,否则后续步骤会在意料之外的地方失败:
| 官方要求 | 本文取值 | 是否满足 |
|---|---|---|
| Unix-like OS(明确不支持 Windows) | Ubuntu 22.04.5 LTS | 满足 |
PHP 7.4+,需 OpenSSL,且启用 exec()、shell_exec()、proc_open() | PHP 8.1.2 | 满足 |
| Web 服务器(Nginx 或 Apache2) | Nginx 1.18.0 | 满足 |
| 数据库(MySQL/MariaDB 或 PostgreSQL) | MariaDB 10.6.23 | 满足 |
| Beanstalkd 队列 | Beanstalkd 1.12 | 满足 |
可写的应用目录与 runtime 目录 | /var/www/php-censor | 见 7.1 节 |
exec()、shell_exec()、proc_open() 这三个函数经常被安全基线通过 disable_functions 关掉,而 PHP Censor 的插件全靠它们调用外部命令。安装前先确认:
php -r 'echo ini_get("disable_functions"), PHP_EOL;'
实测输出为空行(disable_functions 未设置):
⚠️ 注意:若该行输出里出现
exec,shell_exec,proc_open,必须在/etc/php/8.1/fpm/php.ini与/etc/php/8.1/cli/php.ini中删掉对应项,否则构建立刻失败,且报错只显示「命令执行失败」,排查成本很高。
2.2 网络与软件源
本次安装全部走 Ubuntu 官方源与 Packagist 官方源,未发生下载降级。为确认官方源可用,安装前实测:
curl -sI --max-time 25 https://repo.packagist.org/packages.json | head -1
HTTP/2 200
说明:若机房访问 Packagist 缓慢,可临时切换国内 Composer 源;但 PHP Censor 的
composer.json里有extra.platform.php: 7.4.*的平台固定,不要在同一台机器上混用不同源的锁文件,否则依赖树可能不一致。
2.3 端口规划
| 端口 | 用途 | 监听地址 | 是否对外 |
|---|---|---|---|
| 80 | Nginx / PHP Censor Web | 0.0.0.0 | 是 |
| 3306 | MariaDB | 127.0.0.1 | 否 |
| 11300 | Beanstalkd | 127.0.0.1 | 否 |
数据库与队列都只监听回环地址,是本次安装的安全基线之一;这一点在第九节配置说明中会再次强调。
三、版本选型说明
3.1 为什么选 2.1.6
在 GitHub Releases 页按「最新且非预发布」筛选,2026-10-28 查得的最新稳定版为 2.1.6(2025-03-15 发布)。选择它有三个理由:
- 它是官方 README 标注的「Current stable version」所在分支(
release-2.1)的最新版; - 它包含两项安全修复(remember me key 问题、CVE-2024-50345),低于该版本的 2.1.x 均有已知缺陷;
- 相比
master上的 2.2 WIP 与3.0预发布,2.1.6 有完整的发布说明与可回退的标签。
3.2 与其他版本的差异
| 版本 | 状态 | 最低 PHP | 是否用于生产 |
|---|---|---|---|
| 2.1.6 | 当前稳定版 | >=7.4 | 是(本文采用) |
| 2.0.14 | 上一稳定线的最新修复版 | >=7.4 | 仅存量系统 |
| 2.2 | WIP,master 分支 | >=7.4 | 否 |
| 3.0 | 预发布(Packagist 有 dev-pre-release-3.0.0) | >=8.1 | 否 |
| 1.3.7 及更早 | 官方标注 UNSUPPORTED | >=5.6,<8.0 | 否 |
关键差异:2.x 与 3.0 的 PHP 基线不同(7.4 对 8.1),且 3.0 仍在预发布阶段,接口与配置可能变动。生产环境固定使用 2.1.6,待 3.0 正式发布后再评估迁移。
3.3 PHP 8.1 的兼容性判断
composer.json 声明 php: >=7.4.0,同时 extra.platform.php 固定为 7.4.*。后者意味着 Composer 会按 PHP 7.4 的兼容矩阵解析依赖——它不会因为本机是 8.1 就放开依赖版本。这样装出来的依赖在 8.1 上运行是安全的(Symfony 5.4 支持 8.1),但也意味着未来升级 PHP 8.2/8.3 时要重新验证依赖树。
四、安装方案 A:Composer 在线安装
4.1 安装系统依赖
先补齐 PHP 扩展与支撑服务。PHP Censor 需要 openssl、PDO、json、dom、libxml、SimpleXML、curl、bcmath 八个扩展,Ubuntu 下分别对应 php8.1-* 包:
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
nginx mariadb-server beanstalkd git unzip curl \
php8.1-cli php8.1-fpm php8.1-mysql php8.1-xml \
php8.1-mbstring php8.1-curl php8.1-bcmath php8.1-zip \
php8.1-gd composer
安装完成后逐项核对版本(输出为实测回显):
php -v
composer --version
nginx -v
mariadb --version
beanstalkd -v
PHP 8.1.2-1ubuntu2.28 (cli) (built: Oct 19 2026 18:54:45) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
with Zend OPcache v8.1.2-1ubuntu2.28, Copyright (c), by Zend Technologies
Composer 2.2.6 2022-02-04 17:00:38
nginx version: nginx/1.18.0 (Ubuntu)
mariadb Ver 15.1 Distrib 10.6.23-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper
beanstalkd 1.12
确认 PHP 扩展齐全(输出为单行,此处按空格拆行展示):
[PHP Modules] bcmath calendar Core ctype curl date dom exif FFI fileinfo filter ftp gd
gettext hash iconv intl json libxml mbstring mysqli mysqlnd openssl pcntl pcre PDO
pdo_mysql Phar posix readline Reflection session shmop SimpleXML sockets sodium SPL
standard sysvmsg sysvsem sysvshm tokenizer xml xmlreader xmlwriter xsl Zend OPcache zip zlib
[Zend Modules] Zend OPcache
4.2 启动四个支撑服务
systemctl enable --now mariadb
systemctl enable --now nginx
systemctl enable --now php8.1-fpm
systemctl enable --now beanstalkd
逐个确认状态(每行只对一个单元,回显为实测):
systemctl is-active mariadb
systemctl is-active nginx
systemctl is-active php8.1-fpm
systemctl is-active beanstalkd
active
active
active
active
同时确认端口已监听:
ss -ltnp | grep -E ':(80|3306|11300) '
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=15088,fd=21))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=15049,fd=6))
LISTEN 0 1024 127.0.0.1:11300 0.0.0.0:* users:(("beanstalkd",pid=15044,fd=3))
说明:
LISTEN 0 511中的 511 是 socket backlog,80 是监听端口。三个端口分别绑定在回环(3306、11300)与全地址(80),与 2.3 节的规划一致。
4.3 创建数据库与账号
PHP Censor 不会自动建库,安装命令只负责建表。这里先建库并授权,库名 php-censor、账号 php-censor、口令 q1w2e3r4(演示口令,生产请替换):
mariadb -uroot -e "
CREATE DATABASE \`php-censor\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'php-censor'@'localhost' IDENTIFIED BY 'q1w2e3r4';
GRANT ALL PRIVILEGES ON \`php-censor\`.* TO 'php-censor'@'localhost';
FLUSH PRIVILEGES;"
验证库已建立并可用该账号登录:
mariadb -uphp-censor -pq1w2e3r4 php-censor -e "SELECT DATABASE(), VERSION();"
DATABASE() VERSION()
php-censor 10.6.23-MariaDB-0ubuntu0.22.04.1
⚠️ 注意:库名与账号名都带连字符,在 SQL 中必须用反引号包裹(
`php-censor`),否则会被解析成「库名 php 减 表名 censor」。这是本节最容易踩的坑。
4.4 用 Composer 创建项目
官方推荐用 create-project 拉取项目,它会把代码与依赖一次性装好:
mkdir -p /var/www
cd /var/www
composer create-project php-censor/php-censor php-censor --keep-vcs --no-interaction
安装过程会解析并下载 130 个包,输出很长,此处只保留末尾节选(实测):
- Installing voku/anti-xss (4.1.42): Extracting archive
0/130 [>---------------------------] 0%
38/130 [========>-------------------] 29%
60/130 [============>---------------] 46%
83/130 [=================>----------] 63%
105/130 [======================>-----] 80%
126/130 [===========================>] 96%
130/130 [============================] 100%
Package swiftmailer/swiftmailer is abandoned, you should avoid using it. Use symfony/mailer instead.
Package php-cs-fixer/diff is abandoned, you should avoid using it. No replacement was suggested.
Package sebastian/phpcpd is abandoned, you should avoid using it. No replacement was suggested.
Package webmozart/path-util is abandoned, you should avoid using it. Use symfony/filesystem instead.
Generating autoload files
89 packages you are using are looking for funding.
Use the `composer fund` command to get more info!
infection/extension-installer: No extensions found
create-project 默认连 require-dev 一起装,因此是 130 个包、目录约 151MB。生产环境若不需要开发工具链,可在项目目录内补执行一次 composer install --no-dev --optimize-autoloader 收敛体积(方案 B 即为该形态,实测 62 个包、约 71MB)。
确认代码版本与目录:
cd /var/www/php-censor
git describe --tags
cat VERSION.md
./bin/console --version
2.1.6
2.1.6
PHP Censor v2.1.6
4.5 执行安装命令
php-censor:install 负责检查环境、建表、创建管理员。参数含义见下表(可用 ./bin/console help php-censor:install 查看):
| 参数 | 本次取值 | 说明 |
|---|---|---|
--url | http://php-censor.local | 写入 app/config.yml 的 url,影响页面链接与 Webhook 回调地址 |
--db-type | mysql | 可选 mysql 或 pgsql |
--db-host | localhost | 数据库地址 |
--db-port | default | 特殊值:MySQL 取 3306,PostgreSQL 取 5432 |
--db-name / --db-user / --db-password | php-censor / php-censor / q1w2e3r4 | 4.3 节创建的库与账号 |
--admin-name / --admin-password / --admin-email | admin / q1w2e3r4 / admin@php-censor.local | 初始管理员 |
--queue-host / --queue-port / --queue-name | localhost / 11300 / php-censor | Beanstalkd 连接信息 |
cd /var/www/php-censor
./bin/console php-censor:install \
--url='http://php-censor.local' \
--db-type=mysql \
--db-host=localhost \
--db-port=default \
--db-name=php-censor \
--db-user=php-censor \
--db-password='q1w2e3r4' \
--admin-name=admin \
--admin-password='q1w2e3r4' \
--admin-email='admin@php-censor.local' \
--queue-host=localhost \
--queue-port=11300 \
--queue-name=php-censor
实测输出(完整照录):
***************************************
* Welcome to PHP Censor installation *
***************************************
Checking requirements...
OK
Please answer the following questions:
--------------------------------------
Setting up your database...
using migration paths
- /var/www/php-censor/src/Migrations
warning no environment specified, defaulting to: php-censor
using adapter mysql
using database php-censor
ordering by creation time
== 20200505070903 InitialMigrationV2: migrating
== 20200505070903 InitialMigrationV2: migrated 0.0586s
== 20220223061912 WebhookRequestsNewTable: migrating
== 20220223061912 WebhookRequestsNewTable: migrated 0.0079s
== 20220429095102 DashboardCoverage: migrating
== 20220429095102 DashboardCoverage: migrated 0.0029s
== 20220604191600 SecretsNewTable: migrating
== 20220604191600 SecretsNewTable: migrated 0.0080s
All Done. Took 0.0843s
OK
User account created!
Default project group created!
四张迁移全部 up,User account created! 与 Default project group created! 两行确认管理员与默认项目组已建好。
验证建表结果:
mariadb -uphp-censor -pq1w2e3r4 php-censor -e "SHOW TABLES;"
Tables_in_php-censor
build_errors
build_metas
builds
environments
migrations
project_groups
projects
secrets
users
webhook_requests
五、安装方案 B:官方源码包 + composer install
5.1 适用场景
机房不能直连 Packagist、或需要把代码版本钉死在某个 tag 上时,用官方源码包更合适:先在一台可联网的机器上把包与 vendor 备好,再整体搬到内网服务器。本节的下载与依赖安装均在目标服务器上实测。
5.2 下载与校验
官方源码包地址遵循 archive/refs/tags/<版本>.tar.gz 规则:
cd /tmp
curl -sSL --max-time 120 -o php-censor-2.1.6.tar.gz \
https://github.com/php-censor/php-censor/archive/refs/tags/2.1.6.tar.gz
ls -lh php-censor-2.1.6.tar.gz
sha256sum php-censor-2.1.6.tar.gz
实测:
-rw-r--r-- 1 root root 13M Oct 27 17:29 php-censor-2.1.6.tar.gz
2f441a108d1668d38185f1623edd7c0e62fdb30a7f78e01c13d25f62ca360e99 php-censor-2.1.6.tar.gz
说明:
sha256随手记录,便于内网搬运后比对,防止传输损坏。该值对应 2026-10-28 从 GitHub 拉取的 2.1.6 标签包。
5.3 解压与安装依赖
cd /opt
tar -xzf /tmp/php-censor-2.1.6.tar.gz
mv php-censor-2.1.6 php-censor
cd php-censor
composer install --no-dev --optimize-autoloader --no-interaction
实测输出末尾节选:
62/62 [============================] 100%
Package swiftmailer/swiftmailer is abandoned, you should avoid using it. Use symfony/mailer instead.
Package php-cs-fixer/diff is abandoned, you should avoid using it. No replacement was suggested.
Package sebastian/phpcpd is abandoned, you should avoid using it. No replacement was suggested.
Package webmozart/path-util is abandoned, you should avoid using it. Use symfony/filesystem instead.
Generating optimized autoload files
42 packages you are using are looking for funding.
Use the `composer fund` command to get more info!
--no-dev 只装 62 个运行时包,--optimize-autoloader 生成类映射以加快加载。验证可执行:
cd /opt/php-censor
./bin/console --version
PHP Censor v2.1.6
两条路径的体积对比(du -sh 实测):方案 A 为 151M,方案 B 为 71M。差异全部来自开发依赖(PHPUnit、Psalm、Infection、PHP-CS-Fixer 等)。
5.4 后续初始化
方案 B 的初始化命令与方案 A 完全一致,只是工作目录换成 /opt/php-censor:
cd /opt/php-censor
./bin/console php-censor:install --url='http://php-censor.local' --db-type=mysql \
--db-host=localhost --db-port=default --db-name=php-censor \
--db-user=php-censor --db-password='q1w2e3r4' \
--admin-name=admin --admin-password='q1w2e3r4' \
--admin-email='admin@php-censor.local' \
--queue-host=localhost --queue-port=11300 --queue-name=php-censor
⚠️ 注意:同一台服务器上不要同时安装两套并指向同一个库。
php-censor:install会重复执行迁移,第二次执行会因迁移记录已存在而报错跳过;若确需并存,必须使用不同的--db-name。
六、Web 服务器与 PHP-FPM 配置
6.1 站点配置文件
站点根目录必须指向项目下的 public(而不是项目根),并把所有不存在的路径重写到 index.php:
server {
listen 80;
listen [::]:80;
server_name php-censor.local;
root /var/www/php-censor/public;
index index.php index.html;
charset utf-8;
client_max_body_size 32m;
access_log /var/log/nginx/php-censor.access.log;
error_log /var/log/nginx/php-censor.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 300s;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
逐项说明关键指令:
| 指令 | 取值 | 作用 |
|---|---|---|
root | /var/www/php-censor/public | 只暴露 public,app/config.yml 与数据库口令不会落到 Web 根下 |
try_files ... /index.php?$query_string | 固定写法 | PHP Censor 的 URL 全部由 index.php 派发,缺这行会出现 404 |
fastcgi_pass | unix:/run/php/php8.1-fpm.sock | 与 PHP 版本强绑定,升 PHP 版本时必须同步改 |
client_max_body_size | 32m | 上传构建产物(覆盖率 HTML 等)时的上限 |
fastcgi_read_timeout | 300s | 构建详情页渲染长日志时避免 504 |
location ~ /\. | deny all | 禁止访问 .git、.env 等隐藏文件 |
写入并启用:
ln -sf /etc/nginx/sites-available/php-censor /etc/nginx/sites-enabled/php-censor
rm -f /etc/nginx/sites-enabled/default
nginx -t
实测:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
6.2 重载并验证 HTTP 响应
systemctl reload nginx
curl -sI -H "Host: php-censor.local" http://127.0.0.1/ | head -8
实测:
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: PHPSESSID=r31590l81ooe0588j9mqtvm3t7; path=/
Cache-Control: no-cache, private
Date: Tue, 27 Oct 2026 17:23:04 GMT
302 是预期结果:未登录访问根路径会被重定向到 /session/login。若返回 502,说明 PHP-FPM 未启动或 socket 路径不匹配;若返回 404,说明 try_files 未生效。
6.3 PHP-FPM socket 确认
ls -l /run/php/
total 4
lrwxrwxrwx 1 root root 30 Oct 27 17:21 php-fpm.sock -> /etc/alternatives/php-fpm.sock
-rw-r--r-- 1 root root 5 Oct 27 17:21 php8.1-fpm.pid
srw-rw---- 1 www-data www-data 0 Oct 27 17:21 php8.1-fpm.sock
php8.1-fpm.sock 的属主是 www-data:www-data,权限 srw-rw----,Nginx 主进程通常以 www-data 运行,具备读写权限。若改用自定义 FPM 用户,需把 Nginx 用户加入同一组。
七、systemd 托管与 Worker
7.1 创建运行账号与目录权限
Web 与 Worker 应使用同一个低权限账号,避免构建产物因属主不一致而写入失败:
useradd -r -m -d /home/php-censor -s /bin/bash php-censor
id php-censor
chown -R php-censor:php-censor /var/www/php-censor
chmod -R g+rwX /var/www/php-censor/app /var/www/php-censor/public/artifacts
usermod -aG php-censor www-data
实测:
uid=999(php-censor) gid=999(php-censor) groups=999(php-censor)
drwxr-xr-x 12 php-censor php-censor 4096 Oct 27 17:22 /var/www/php-censor
drwxrwxr-x 2 php-censor php-censor 4096 Oct 27 17:22 /var/www/php-censor/app
app/ 需要写权限(配置、缓存),public/artifacts 需要写权限(覆盖率报告),因此单独放宽到组可写,其余目录保持 755。
7.2 Worker 的 systemd 模板单元
官方 Worker 文档给出的是模板单元,支持用 @N 起多个并发 Worker。本次的单机配置如下:
[Unit]
Description=PHP Censor Worker %I
After=network.target mariadb.service beanstalkd.service
[Service]
Type=simple
User=php-censor
Group=php-censor
WorkingDirectory=/var/www/php-censor
ExecStart=/usr/bin/php /var/www/php-censor/bin/console php-censor:worker
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
关键项说明:
| 配置项 | 取值 | 为什么 |
|---|---|---|
After | 含 mariadb.service beanstalkd.service | Worker 启动即连库与队列,先起依赖可减少启动期重试 |
User / Group | php-censor | 不用 root,构建脚本无法越权 |
WorkingDirectory | /var/www/php-censor | 插件按相对路径找 vendor/bin,必须固定 |
ExecStart | 绝对路径的 php + 绝对路径 console | systemd 不读登录 shell 的 PATH |
Restart=always | 常驻 | Worker 异常退出后自动拉起,替代 nohup |
部署与启动:
systemctl daemon-reload
systemctl enable --now php-censor@1
systemctl is-active php-censor@1
systemctl status php-censor@1 --no-pager -n 5
实测:
Created symlink /etc/systemd/system/multi-user.target.wants/php-censor@1.service → /etc/systemd/system/php-censor@.service.
active
● php-censor@1.service - PHP Censor Worker 1
Loaded: loaded (/etc/systemd/system/php-censor@.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2026-10-27 17:27:10 UTC; 2s ago
Main PID: 16199 (php)
Tasks: 1 (limit: 2932)
Memory: 15.0M
CPU: 47ms
└─16199 /usr/bin/php /var/www/php-censor/bin/console php-censor:worker
Oct 27 17:27:10 php-censor systemd[1]: Started PHP Censor Worker 1.
说明:以上为
systemctl status输出的节选(省略了CGroup行);Memory: 15.0M为 Worker 空载时的常驻内存。
7.3 Worker 常驻确认
ps -o pid,user,rss,etime,args -C php | grep -E "worker|PID"
PID USER RSS ELAPSED COMMAND
16199 php-cen+ 38580 01:13 /usr/bin/php /var/www/php-censor/bin/console php-censor:worker
RSS 约 37MB,进程属主为 php-cen+(即 php-censor 截断显示)。
7.4 登录验证
登录表单带一次性 CSRF 令牌(字段名 login_form),用 curl 走完整流程:先取令牌与 Cookie,再提交账号口令:
cd /tmp
TOKEN=$(curl -s -c c.txt -H "Host: php-censor.local" \
http://127.0.0.1/session/login | grep -oP 'name="login_form" value="\K[^"]+')
curl -s -b c.txt -c c.txt -o /dev/null \
-w "login_status=%{http_code} redirect=%{redirect_url}\n" \
-H "Host: php-censor.local" \
-d "login_form=$TOKEN" -d "email=admin@php-censor.local" -d "password=q1w2e3r4" \
http://127.0.0.1/session/login
curl -s -b c.txt -H "Host: php-censor.local" http://127.0.0.1/ | grep -oE "<title>[^<]*</title>"
实测:
login_status=302 redirect=http://php-censor.local/
<title>Dashboard</title>
302 跳回首页 + 页面标题为 Dashboard,说明登录成功且会话有效。
7.5 首次构建验证
为验证「Web 侧建项目 → 队列 → Worker → 插件执行」整条链路,建一个最小示例项目 /var/www/demo-app:
mkdir -p /var/www/demo-app/src
cat > /var/www/demo-app/composer.json <<'JSON'
{
"name": "demo/calculator",
"require": { "php": ">=7.4" },
"autoload": { "psr-4": { "Demo\\": "src/" } }
}
JSON
cat > /var/www/demo-app/src/Calculator.php <<'PHP'
<?php
namespace Demo;
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
}
PHP
cat > /var/www/demo-app/.php-censor.yml <<'YAML'
build_settings:
ignore:
- "vendor"
setup:
composer_step:
plugin: composer
action: "install"
test:
lint_step:
plugin: shell
commands:
- "php -l src/Calculator.php"
- "echo demo build ok"
YAML
在 Web 界面 project/add 页选择仓库类型 local、引用填 /var/www/demo-app、项目组选默认组,保存后项目落库。随后用命令行触发构建:
cd /var/www/php-censor
./bin/console php-censor:create-build 1 --branch=master --commit=a1b2c3d4 \
--email=admin@php-censor.local --message="demo build"
Build Created
等待 Worker 取任务执行后,查询构建状态与日志:
mariadb -uphp-censor -pq1w2e3r4 php-censor -e \
"SELECT id,project_id,commit_id,branch,status,errors_total FROM builds ORDER BY id DESC LIMIT 1;"
id project_id commit_id branch status errors_total
1 1 a1b2c3d4 master 2 0
status=2 表示构建成功,errors_total=0 表示无错误。构建日志(实测原文,含终端的颜色转义序列):
Shell command: mkdir -p "<PHP_CENSOR_PATH>/runtime/builds/1/1_7b48ec00" && cp -Rf /var/www/demo-app/* "<BUILD_PATH>/"
Working copy created: <BUILD_PATH>/
RUNNING PLUGIN: Composer (Step: composer_step) (Stage: Setup)
Shell command: /usr/bin/composer --no-ansi --no-interaction --working-dir="<BUILD_PATH>/" install
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https:/getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
PLUGIN: SUCCESS
RUNNING PLUGIN: Shell (Step: lint_step) (Stage: Test)
Shell command: php -l src/Calculator.php
No syntax errors detected in src/Calculator.php
Shell command: echo demo build ok
demo build ok
PLUGIN: SUCCESS
BUILD SUCCESS!
REMOVING BUILD.
这条日志证明四件事:Worker 正常工作、Composer 插件在 setup 阶段执行、Shell 插件在 test 阶段执行、构建成功后自动清理工作副本(REMOVING BUILD.)。
八、方案对比与选择建议
| 维度 | 方案 A:Composer create-project | 方案 B:官方源码包 + composer install |
|---|---|---|
| 是否需联网 | 需要 Packagist | 打包时需要;目标机只需 composer install 或直接搬运 vendor |
| 版本锁定 | 跟 Packagist 解析结果,需显式指定版本才钉死 | 由 tag 直接钉死,可记录 sha256 |
| 依赖数量 | 130 个(含 dev) | 62 个(--no-dev) |
| 目录体积 | 约 151MB | 约 71MB |
| 是否带 Git 元数据 | 带(--keep-vcs) | 带(tar 包内含 .git 目录结构) |
| 适用场景 | 可联网、需要开发调试 | 内网、离线、生产瘦身 |
选择建议:能联外网且需要跑测试/改代码的开发机用方案 A;生产服务器与内网环境用方案 B,并在打包机上保留 composer.lock 与 vendor 目录,目标机只需解压即可启动。两条路径初始化命令完全相同,切换成本只在「代码怎么来」这一步。
九、配置说明
安装命令已在 app/config.yml 生成完整配置。关键段与本次取值:
php-censor:
language: en
per_page: 10
url: 'http://php-censor.local'
queue:
port: '11300'
lifetime: 600
host: localhost
name: php-censor
realtime_ui: true
webhook:
log_requests: false
log:
rotate: false
max_files: 0
email_settings:
from_address: 'PHP Censor <no-reply@php-censor.local>'
smtp_address: null
smtp_port: null
smtp_username: null
smtp_password: null
smtp_encryption: false
ssh:
strength: 2048
comment: admin@php-censor
build:
remove_builds: true
writer_buffer_size: 500
allow_public_artifacts: false
keep_builds: 100
security:
disable_auth: false
default_user_id: 1
auth_providers:
internal: { type: internal }
database:
servers:
read: [{ host: localhost, port: 3306 }]
write: [{ host: localhost, port: 3306 }]
type: mysql
name: php-censor
username: php-censor
password: q1w2e3r4
需要立即关注的四项:
url:必须与浏览器访问地址一致,否则邮件通知里的链接、Webhook 回跳地址全部错误。queue.name:Worker 与 Web 必须一致,改了一边就要同步另一边,否则任务永远不被消费。build.keep_builds: 100:每个项目保留最近 100 次构建,超出部分由php-censor:remove-old-builds清理。security.disable_auth: false:保持鉴权开启;若关闭,务必配合反向代理的访问控制。
配置文件权限:
chown php-censor:php-censor /var/www/php-censor/app/config.yml
chmod 640 /var/www/php-censor/app/config.yml
⚠️ 注意:
app/config.yml里有数据库明文口令。站点根目录指向public、且 Nginx 对/.前缀做了deny all,两道措施叠加才能保证该文件不可通过 HTTP 读取。改动 Nginx 配置时不要删掉这两条约束。
十、升级与卸载
10.1 升级
2.1.x 内的小版本升级步骤(以 2.1.6 为基线):
systemctl stop php-censor@1
cd /var/www/php-censor
cp -a app/config.yml /var/backups/config.yml.$(date +%F)
git fetch --tags
git checkout 2.1.6
composer install --no-dev --optimize-autoloader --no-interaction
./bin/console php-censor-migrations:migrate
systemctl start php-censor@1
systemctl is-active php-censor@1
迁移命令会输出每条迁移的 up 记录;迁移前务必先备份数据库(见配套的命令与运维篇)。跨大版本(2.1 → 3.0)不适用本流程,需按官方升级文档评估配置差异。
10.2 卸载
systemctl disable --now php-censor@1
rm -f /etc/systemd/system/php-censor@.service
systemctl daemon-reload
rm -f /etc/nginx/sites-enabled/php-censor
rm -f /etc/nginx/sites-available/php-censor
systemctl reload nginx
rm -rf /var/www/php-censor
mariadb -uroot -e "DROP DATABASE \`php-censor\`; DROP USER 'php-censor'@'localhost';"
如需彻底清理,再删除 php-censor 系统账号与 /home/php-censor。Beanstalkd 与 MariaDB 若仅供本应用使用,可一并 systemctl disable --now,但删除数据目录前务必确认无其他业务共用。
十一、常见问题
11.1 安装时提示 requirements 检查失败
php-censor:install 开头的 Checking requirements... 会检查 PHP 版本、扩展与目录可写性。实测中最常见的两类原因:
ext-*缺失:对照 4.1 节的扩展清单,缺哪个装对应的php8.1-*包,装完重启 PHP-FPM;- 目录不可写:
app/或runtime/属主不是运行账号时直接失败,按 7.1 节chown -R修正。
11.2 页面返回 502 Bad Gateway
按顺序排查:
systemctl is-active php8.1-fpm
ls -l /run/php/php8.1-fpm.sock
tail -5 /var/log/nginx/php-censor.error.log
先确认 FPM 活跃、socket 存在且属主为 www-data,再确认 fastcgi_pass 的路径与 ls 结果一字不差。实测中升级 PHP 版本后旧 socket 名会失效,配置文件忘记改就会出现 502。
11.3 创建项目时返回 500
本次实测遇到两种 500,均记录如下:
现象一:POST /project/add 返回 500,Nginx 错误日志有
PHP message: PHP Warning: Undefined array key "ssh_public_key" in /var/www/php-censor/src/Controller/ProjectController.php on line 414
定位:表单提交缺少 ssh_public_key 字段(用脚本直接 POST 时容易漏),控制器读取该键时未做空值保护。
解决:提交表单时把 ssh_public_key 与 ssh_private_key 一并带上(值可为空)。浏览器正常操作不受影响,因为表单会自动带上隐藏字段。
现象二:同一请求返回 200 但页面停留在表单,help-block 里显示
Project group is required.
定位:group_id 未提交。PHP Censor 要求项目必须归属某个项目组。
解决:提交时带上 group_id=1(安装时自动创建的默认组)。
11.4 Worker 启动了但构建一直「等待」
按链路顺序排查:
systemctl is-active beanstalkd
mariadb -uphp-censor -pq1w2e3r4 php-censor -e "SELECT id,status FROM builds ORDER BY id DESC LIMIT 3;"
journalctl -u php-censor@1 --no-pager -n 20
三个检查点依次是:队列服务在跑、构建记录已入库(status 为等待态)、Worker 日志无异常退出。实测中若 queue.name 与 Web 侧配置不一致,Worker 会连上队列但永远取不到任务,日志里没有任何报错——这是最难发现的一种配置失误。
11.5 Composer 安装依赖时提示 abandoned 包
实测输出的几行提示(swiftmailer/swiftmailer、php-cs-fixer/diff、sebastian/phpcpd、webmozart/path-util)是上游包被标记废弃,不影响安装与运行。PHP Censor 2.1.6 仍依赖它们,属于「知道即可」的提示,不建议手工替换依赖版本。
11.6 构建日志显示 <BUILD_PATH> 之类的占位符
构建日志中的 <BUILD_PATH>、<PHP_CENSOR_PATH> 是插件在执行前的变量插值标记,实际执行的命令已被替换为真实路径。这是正常输出,不是错误;排查时应以数据库 builds.log 中 Shell command: 那一行的后半段为准。
十二、参考链接
- 项目仓库:https://github.com/php-censor/php-censor
- 2.1.6 发布页:https://github.com/php-censor/php-censor/releases/tag/2.1.6
- 官方安装文档:https://github.com/php-censor/php-censor/blob/release-2.1/docs/en/installing.md
- 官方虚拟主机文档:https://github.com/php-censor/php-censor/blob/release-2.1/docs/en/virtual_host.md
- 官方 Worker 文档:https://github.com/php-censor/php-censor/blob/release-2.1/docs/en/workers/worker.md
- 应用配置文档:https://github.com/php-censor/php-censor/blob/release-2.1/docs/en/configuring_application.md
- 项目配置文档:https://github.com/php-censor/php-censor/blob/release-2.1/docs/en/configuring_project.md
- 站内相关: 《PHP Censor 介绍与选型指南》
- 站内相关: 《PHP Censor 2.1.6 配置参数详解》
- 站内相关: 《PHP Censor 2.1.6 常用 CLI 与运维命令详解》