PHP Censor 2.1.6 常用 CLI 与运维命令详解
覆盖 PHP Censor 2.1.6 全部 console 命令与运维动作:迁移、建号、手动构建、队列修复、Worker 优雅退出与 systemd 托管、日志定位、数据库备份与恢复演练,附实测回显与故障处理清单。
PHP Censor 2.1.6 常用 CLI 与运维命令详解
目标:把 PHP Censor 2.1.6 的命令行接口与日常运维动作整理成可直接照抄的手册——
bin/console全部命令、Worker 常驻与优雅退出、构建触发与队列修复、日志定位、数据库备份与恢复。文中命令与回显均在 Ubuntu 22.04.5 LTS + PHP 8.1.2 + MariaDB 10.6.23 + Beanstalkd 1.12 的实机环境采集,版本信息查询日期为 2026-10-28。
一、环境信息
| 项目 | 实测值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS |
| 程序目录 | /var/www/php-censor |
| PHP | 8.1.2(php8.1-fpm + php8.1-cli) |
| 数据库 | MariaDB 10.6.23,库名 php-censor |
| 队列 | Beanstalkd 1.12,监听 127.0.0.1:11300 |
| 程序版本 | PHP Censor v2.1.6 |
| 服务账号 | php-censor(属主 /var/www/php-censor) |
| 站点入口 | http://php-censor.local(Nginx + PHP-FPM) |
| Worker 托管 | systemd 模板单元 php-censor@.service |
确认当前版本与命令入口:
cd /var/www/php-censor
./bin/console --version
实测回显:
PHP Censor v2.1.6
bin/console 是 Symfony Console 应用,所有子命令都从这里进入。生产环境统一使用绝对路径 /var/www/php-censor/bin/console,避免 cron 与 systemd 因工作目录不同而找不到入口。
二、前置条件
执行本文命令前,需要满足四个条件:
- 程序已安装:目录结构完整,
app/config.yml已由php-censor:install生成。安装过程见 《PHP Censor 2.1.6 Ubuntu 22.04 安装指南》。 - 依赖服务可用:
mariadb与beanstalkd处于 running 状态。数据库或队列任一不可用,绝大多数命令会直接抛出连接异常。 - 执行账号正确:读取与写入
runtime/的命令(如worker)必须用php-censor账号执行。用 root 执行会生成 root 属主的缓存与日志文件,后续 service 因权限不足无法启动。 - 配置文件可读:
app/config.yml属主php-censor、权限640。命令以非 root 运行时若读不到该文件,会报file_get_contents(): Failed to open stream。
先做一次依赖体检:
systemctl is-active mariadb beanstalkd php8.1-fpm
实测回显(三个服务逐一输出状态):
active
active
active
数据库连通性用迁移命令间接验证,这是最轻量的一种检查方式:
cd /var/www/php-censor && ./bin/console php-censor-migrations:status
三、Console 命令总览
list 会列出当前版本注册的全部命令。实测输出(节选 php-censor 与 php-censor-migrations 两个命名空间,Symfony 自带的 completion、help、list 从略):
./bin/console list
PHP Censor v2.1.6
Usage:
command [options] [arguments]
Options:
-h, --help Display help for the given command. When no command is given display help for the list command
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
php-censor
php-censor:check-localizations Check localizations.
php-censor:create-admin Create an admin user
php-censor:create-build Create a build for a project
php-censor:install Install PHP Censor
php-censor:rebuild-queue Rebuilds the PHP Censor worker queue.
php-censor:remove-old-builds Remove old builds.
php-censor:worker Runs the PHP Censor build worker.
php-censor-migrations
php-censor-migrations:create Create a new migration
php-censor-migrations:migrate Migrate the database
php-censor-migrations:rollback Rollback the last or to a specific migration
php-censor-migrations:status Show migration status
命令按用途分成四组:
| 分组 | 命令 | 何时使用 |
|---|---|---|
| 安装初始化 | php-censor:install | 首次部署、重建 app/config.yml |
| 迁移管理 | php-censor-migrations:status / migrate / rollback / create | 升级版本、排查表结构异常 |
| 日常操作 | php-censor:create-admin / create-build / worker | 建账号、手动触发构建、常驻消费队列 |
| 维护修复 | php-censor:rebuild-queue / remove-old-builds / check-localizations | 队列卡死、磁盘增长、语言文件校验 |
三个通用约定必须记住:
- 退出码:成功返回
0,失败返回1。脚本里必须判rc,无输出不等于成功。 - 静默命令:
rebuild-queue、remove-old-builds成功时没有任何输出,只返回0,这是设计如此,不是故障。 - 详细模式:排查时加
-v、-vv、-vvv可提升 Symfony 与业务日志的详细程度,其中-vvv会打印异常堆栈。
查看任一命令的完整参数定义:
./bin/console help php-censor:worker
Description:
Runs the PHP Censor build worker.
Usage:
php-censor:worker [options]
Options:
-p, --periodical-work Allow worker run periodical work
-s, --stop-worker[=STOP-WORKER] Gracefully stop one worker (soon=when next job done (default), done=when current jobs done, idle=when waiting for jobs)
-h, --help Display help for the given command. When no command is given display help for the list command
...
注意 -s 是 -s, --stop-worker 的短选项,与通常理解的 -s(silent)不同。在 PHP Censor 里写 -s 不会静默输出,而是触发「优雅停止 Worker」,误用会让线上 Worker 退出。
四、安装与数据库迁移命令
4.1 php-censor:install
交互式安装命令,负责生成 app/config.yml、建库建表、创建首个管理员。参数全部通过问答收集,因此不能在无人值守脚本里直接调用,除非配合 -n 并提供完整环境。实测中该命令已用于首次部署,完整回显见安装指南,此处只说明运维时的三种用途:
# 用途一:修复被误删的 app/config.yml(表结构存在时只重写配置)
./bin/console php-censor:install
# 用途二:升级后补齐新表
./bin/console php-censor-migrations:migrate
# 用途三:只读检查,不改动任何数据
./bin/console php-censor-migrations:status
若 app/config.yml 意外丢失而数据库完好,优先手写配置恢复,不要重跑 install:install 会重建库表路径并可能要求重新输入口令,风险高于手工恢复。
4.2 php-censor-migrations:status
这是排查「页面 500 / 登录异常 / 表不存在」的第一条命令。实测输出:
cd /var/www/php-censor && ./bin/console php-censor-migrations:status
using migration paths
- /var/www/php-censor/src/Migrations
warning no environment specified, defaulting to: php-censor
ordering by creation time
Status [Migration ID] Started Finished Migration Name
----------------------------------------------------------------------------------
up 20200505070903 2026-10-27 17:22:46 2026-10-27 17:22:46 InitialMigrationV2
up 20220223061912 2026-10-27 17:22:46 2026-10-27 17:22:46 WebhookRequestsNewTable
up 20220429095102 2026-10-27 17:22:46 2026-10-27 17:22:46 DashboardCoverage
up 20220604191600 2026-10-27 17:22:46 2026-10-27 17:22:46 SecretsNewTable
四个迁移全部 up 才算完整:InitialMigrationV2 建基础表,WebhookRequestsNewTable 建 webhook_requests,DashboardCoverage 建覆盖率字段,SecretsNewTable 建 secrets。少任何一条,对应功能就会报错。
warning no environment specified, defaulting to: php-censor 是 Phinx 的常规提示,不影响结果;它说明命令读取的是 app/config.yml 中 php-censor.database 段落。
4.3 migrate 与 rollback
升级 PHP Censor 后,先备份数据库,再执行迁移:
# 备份(口令与环境变量写法见第十章)
mysqldump -uphp-censor -pq1w2e3r4 --single-transaction php-censor > /var/backups/php-censor/before-upgrade.sql
# 拉取新代码并更新依赖后执行迁移
cd /var/www/php-censor && ./bin/console php-censor-migrations:migrate
# 回滚最近一次迁移(仅在确认新版本有严重问题时使用)
./bin/console php-censor-migrations:rollback
rollback 只回滚最近一个迁移批次;要回到指定版本用 rollback -t <时间戳>。回滚不会自动恢复被删除的数据,例如 SecretsNewTable 回滚会删掉 secrets 表,密钥全部丢失。生产环境回滚前必须停 Worker,否则迁移与构建并发写表可能留下不一致记录。
create 只给二次开发者使用,用于生成迁移骨架:
./bin/console php-censor-migrations:create AddBuildTagTable
生成文件位于 src/Migrations/,文件名带时间戳。运维场景不需要执行它。
4.4 迁移失败的两种真实表现
表现一:输出空表 + 数据库报错。 口令写错时,命令会先打印表头,再在内部查询时报错,看起来像「没有迁移记录」:
Status [Migration ID] Started Finished Migration Name
------------------------------------------------------------
此时若继续操作 Web 端,登录会返回 500。根因在 app/config.yml 的 password,不在迁移文件。正确做法是先用 mysql -uphp-censor -p php-censor -e "show tables;" 验证口令,再重跑 status。
表现二:migrate 报 Unknown database。 说明 database.name 与实际库名不一致。实测库名是 php-censor(带连字符),而官方示例常写 php-censor-db,照抄示例就会踩这个坑。
mysql -uroot -e "show databases like 'php-censor%';"
+--------------------------+
| Database (php-censor%) |
+--------------------------+
| php-censor |
+--------------------------+
五、用户与项目操作命令
5.1 php-censor:create-admin
创建管理员账号,适合在 Web 端被锁死或忘记口令时救急。三个参数都可省略,省略后进入交互问答;脚本化时全部显式传入:
cd /var/www/php-censor
./bin/console php-censor:create-admin \
--admin-name docadmin \
--admin-password q1w2e3r4 \
--admin-email docadmin@example.com
实测回显:
User account created!
确认账号已落库:
mysql -uphp-censor -pq1w2e3r4 php-censor -e "select id,name,email,is_admin from users;"
id name email is_admin
1 admin admin@php-censor.local 1
2 opsadmin ops@php-censor.local 1
3 docadmin docadmin@example.com 1
要点:
is_admin=1才有项目管理与全局配置权限,该命令创建的账号默认就是管理员。- 邮箱是登录名,重复邮箱会报唯一键冲突;改口令不要用这条命令,应回到 Web 端「用户管理」重置。
- 命令输出只有一句
User account created!,不打印 ID。要拿 ID 需查库,或登录后看右上角用户菜单。
5.2 php-censor:create-build
手动触发一次构建,等价于 Web 端「Build Now」。实测参数定义:
./bin/console help php-censor:create-build
Usage:
php-censor:create-build [options] [--] <projectId>
Arguments:
projectId A project ID
Options:
--commit[=COMMIT] Commit ID to build
--branch[=BRANCH] Branch to build
--email[=EMAIL] Committer email
--message[=MESSAGE] Commit message
实际触发:
./bin/console php-censor:create-build 1 \
--branch master \
--commit HEAD \
--email admin@example.com \
--message "cli doc check"
实测回显:
Build Created
注意是 Build Created(首字母大写、无句号),与安装输出的 User account created! 风格不同,脚本里做字符串匹配时不要想当然。
构建立刻进入队列,由 Worker 消费。约 6 秒后查询数据库:
mysql -uphp-censor -pq1w2e3r4 php-censor -e \
"select id,project_id,status,commit_id,branch,create_date,start_date,finish_date from builds order by id desc limit 5;"
id project_id status commit_id branch create_date start_date finish_date
3 1 2 HEAD master 2026-10-27 17:39:52 2026-10-27 17:39:52 2026-10-27 17:39:52
2 1 2 b2c3d4e5 master 2026-10-27 17:35:56 2026-10-27 17:35:56 2026-10-27 17:35:56
1 1 2 a1b2c3d4 master 2026-10-27 17:27:17 2026-10-27 17:27:17 2026-10-27 17:27:17
status=2 表示成功,start_date/finish_date 均已填充,说明 Worker 正常消费。若 start_date 长期为空,问题在 Worker 或队列,不在命令本身。
三个参数的实际作用:
--commit:只写记录,不改变实际检出的代码。实测传HEAD后库里就存HEAD三个字母,构建仍然用的是工作副本当前内容。--branch:决定使用哪个分支的配置覆盖(.php-censor.yml中branch-*段落),对local类型项目仅作标记。--email/--message:写入committer_email与commit_message,Web 端构建详情页会展示,通知插件也会引用。
5.3 项目不存在时的报错
传错 ID 是最常见的误操作。实测:
./bin/console php-censor:create-build 999
In CreateBuildCommand.php line 83:
Project does not exist: 999
php-censor:create-build [--commit [COMMIT]] [--branch [BRANCH]] [--email [EMAIL]] [--message [MESSAGE]] [--] <projectId>
rc=1
这条报错直接给出了类名与行号(CreateBuildCommand.php line 83),比 Web 端 500 页面友好得多。查项目 ID:
mysql -uphp-censor -pq1w2e3r4 php-censor -e "select id,title,type,reference from projects;"
5.4 Web 端建项目与 CLI 的关系
PHP Censor 没有提供 project:create 命令,项目必须在 Web 端创建。CLI 只能读取既有项目并触发构建。因此自动化脚本的正确形态是:
- 人工在 Web 端建一次项目,取得 ID;
- 之后所有自动化都基于这个固定 ID 调
create-build。
实测 Web 端建项目时遇到的两次失败已记录在安装指南:缺 ssh_public_key 字段会触发 500(ProjectController.php 第 414 行),缺项目组会提示 Project group is required.。这两个问题都无法用 CLI 绕过。
六、队列与构建调度命令
6.1 队列工作机制
Web 端与 create-build 都只做一件事:往 Beanstalkd 的 tube 里塞一条任务。真正的执行者是 php-censor:worker。三者的关系是:
浏览器/CLI --写任务--> Beanstalkd tube(php-censor) --取任务--> worker --写库--> builds
因此「构建一直转圈」的第一判断点是队列里有没有积压,而不是看 PHP 日志。
6.2 php-censor:rebuild-queue
用途:把数据库里 status=0(等待中)的构建重新灌回队列。适用于 Worker 崩溃、队列被清空、Beanstalkd 重启后任务丢失等场景。
cd /var/www/php-censor && ./bin/console php-censor:rebuild-queue; echo "rc=$?"
实测回显:
rc=0
没有任何输出。命令内部只写日志,实测 runtime/console.log 新增一行:
[2026-10-27T17:39:44.857452+00:00] php-censor.INFO: Found 0 builds [] []
Found 0 builds 表示当前没有等待态构建,属于正常结果。若你的系统里有卡住的构建,这一行会显示非零数量,并且这些构建会被重新入队。
判断是否需要执行它的方法:
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e "select count(*) from builds where status=0;"
结果大于 0 且队列为空,就执行 rebuild-queue;结果大于 0 且队列也有积压,说明 Worker 没起来,先修 Worker。
6.3 直接观测队列
PHP Censor 主程序已依赖 Pheanstalk,可以直接写一个探针脚本读取队列状态。实测脚本放在程序目录下执行:
<?php
require '/var/www/php-censor/vendor/autoload.php';
$client = Pheanstalk\Pheanstalk::create('127.0.0.1', 11300);
echo $client->stats()->getResponse();
$tube = $client->statsTube('php-censor');
echo "tube[php-censor] total-jobs: " . $tube['total-jobs'] . PHP_EOL;
cd /var/www/php-censor && php queue_stats.php
实测回显:
version: "1.12"
current-jobs-ready: 0
current-jobs-reserved: 0
current-jobs-delayed: 0
total-jobs: 4
current-connections: 2
tube[php-censor] total-jobs: 2
字段含义与排查动作:
| 字段 | 含义 | 异常时的判断 |
|---|---|---|
current-jobs-ready | 等待被取走的任务 | 持续大于 0 且不下降 → Worker 未运行或连错 tube |
current-jobs-reserved | 已被 Worker 取走但未确认 | 长期大于 0 → 有构建卡死,Worker 需要重启 |
current-jobs-delayed | 延迟任务 | 通常为 0,大于 0 多为重试退避 |
total-jobs | 累计入队任务数 | 只增不减的正常指标 |
current-connections | 当前连接数 | 明显高于 Worker 数量 → 有泄漏连接 |
注意 Pheanstalk::create() 的第一个参数是 host,第二个才是 port。写成 create('127.0.0.1:11300') 会报域名解析失败,这是实测踩过的坑。
6.4 --periodical-work 与周期构建
php-censor:worker -p 让 Worker 额外承担「周期构建」调度。需要先建 app/periodical.yml:
projects:
1:
branches:
- master
- release-1.0
interval: P1W
12:
branches:
- master
interval: PT12H
interval 使用 PHP DateInterval 格式:P1W 为 1 周,PT12H 为 12 小时。语义是「距上次构建超过该间隔且没有新提交时,补一次构建」,用于及时发现上游依赖变化导致的隐性失败。
启用方式二选一:
- 修改 systemd 单元,在
ExecStart末尾加--periodical-work,然后systemctl daemon-reload && systemctl restart php-censor@1; - 单独起一个 Worker 实例
php-censor@2承担周期任务,与普通 Worker 分离,避免长任务拖慢周期调度。
实测中 -p 与普通 Worker 共用同一 tube,因此多个 Worker 之间是竞争消费关系,不存在优先级差异。若要保证周期构建优先,只能用 build_settings.build_priority 在项目级调整。
七、Worker 常驻与生命周期管理
7.1 systemd 模板单元
生产环境不要让 Worker 跑在裸 nohup 里。实测使用的模板单元位于 /etc/systemd/system/php-censor@.service:
[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
关键点:
%I是实例号,php-censor@1、php-censor@2对应多个并行 Worker,各自独立进程。User=php-censor必须显式指定,否则runtime/与runtime/builds/的属主会变成 root,后续构建清理失败。Restart=always是优雅退出的基础:Worker 主动退出后由 systemd 拉起,实现「重启但不丢任务」。After=只保证启动顺序,不保证服务就绪。数据库慢启动时 Worker 仍可能首连失败,靠RestartSec=5重试兜底。
启用并启动第一个实例:
systemctl daemon-reload
systemctl enable --now php-censor@1
systemctl cat php-censor@1
systemctl cat 实测第一行会回显文件路径 # /etc/systemd/system/php-censor@.service,用于确认加载的是本地单元而不是 /usr/lib/systemd/system 里的同名文件。
7.2 Worker 状态与进程确认
systemctl status php-censor@1 --no-pager
实测输出(节选,省略了 CGroup 行):
● 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:29:14 UTC; 10min ago
Main PID: 16582 (php)
Tasks: 1 (limit: 2932)
Memory: 20.4M
CPU: 175ms
└─16582 /usr/bin/php /var/www/php-censor/bin/console php-censor:worker
Oct 27 17:29:14 php-censor systemd[1]: Started PHP Censor Worker 1.
判断健康的三点:Active: active (running)、Main PID 后面的进程名是 php、末行是 Started 而非 Deactivated。
Tasks: 1 表示当前只有一个常驻进程,构建子进程是临时派生的,构建进行中会短暂升高,结束后回落。若 Tasks 长期大于 1 且没有构建在跑,说明有插件子进程没回收,需要重启 Worker。
7.3 优雅停止:--stop-worker 的三种模式
直接 kill 会中断正在执行的构建,导致该构建永远停在 status=0。正确做法是通过命令发停止信号:
cd /var/www/php-censor
./bin/console php-censor:worker --stop-worker=idle
三种取值的行为差异:
| 取值 | 停止时机 | 适用场景 |
|---|---|---|
soon | 当前任务结束即停(默认) | 滚动重启,允许少量等待 |
done | 当前已取走的任务全部完成才停 | 有长构建,不能中断 |
idle | 空闲时立即停 | 夜间维护、无构建窗口 |
实测 idle 模式:命令本身无输出、rc=0,Worker 进程退出后 systemd 在 5 秒后拉起新进程:
./bin/console php-censor:worker --stop-worker=idle; echo "rc=$?"; sleep 8; systemctl status php-censor@1 --no-pager | head -4
rc=0
● 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:40:37 UTC; 2s ago
注意 Active 的时间戳变成了新的,说明这是一次「退出 + 自动重启」,而不是没生效。验证日志:
journalctl -u php-censor@1 --no-pager -n 12
实测(截取关键四行):
Oct 27 17:40:32 php-censor systemd[1]: php-censor@1.service: Deactivated successfully.
Oct 27 17:40:37 php-censor systemd[1]: php-censor@1.service: Scheduled restart job, restart counter is at 2.
Oct 27 17:40:37 php-censor systemd[1]: Stopped PHP Censor Worker 1.
Oct 27 17:40:37 php-censor systemd[1]: Started PHP Censor Worker 1.
Deactivated successfully 是命令触发的主动退出,Scheduled restart job 是 systemd 的兜底重启,restart counter 的数值随重启次数递增。若看到的是 Failed with result 'exit-code',则是崩溃而非优雅停止,需要看 -vvv 输出定位。
7.4 多 Worker 横向扩展
systemctl enable --now php-censor@2
systemctl enable --now php-censor@3
多个实例共享同一 tube,Beanstalkd 按取走顺序分配,天然负载均衡。扩展时的注意事项:
- 数据库连接数:每增加一个 Worker 至少多一条常驻连接,
max_connections要留足余量。 - 工作目录隔离:所有实例共用
runtime/builds/,PHP Censor 会按项目ID/构建ID_hash建独立目录,无需手工隔离。 - 并发上限:
builds表没有全局并发锁,实例越多,同时触发的部署类插件越可能互相踩踏。部署阶段有状态的项目建议只保留 1 个 Worker。
7.5 Worker 不消费的四种原因
按出现频率排序,逐一排查:
- 队列服务没起:
systemctl is-active beanstalkd不是active。Builder 连接失败时不会让构建失败,只会一直等待。 - tube 名称不一致:
app/config.yml的php-censor.queue.name与 Worker 读取的是同一份配置,若改过配置却只重启了 Web(PHP-FPM)没重启 Worker,就会出现「入队成功但无人消费」。修复:systemctl restart php-censor@1。 - Worker 注册的是旧代码:升级后未重启 Worker,进程仍在跑旧版类。
systemctl restart即可。 - 数据库连接失效:Worker 长时间空闲后被 MariaDB 的
wait_timeout断开,取到任务即报错并退出,systemd 拉起后恢复。表现为「每隔一段时间失败一次」,可通过Restart=always掩盖,但根治要调大wait_timeout或给 Worker 加保活。
八、维护命令
8.1 php-censor:remove-old-builds
按 php-censor.build.keep_builds 保留最近 N 条构建,其余连日志一并删除。实测:
cd /var/www/php-censor && ./bin/console php-censor:remove-old-builds; echo "rc=$?"
rc=0
同样无输出。它的行为受两个配置项控制(见 app/config.yml):
build:
remove_builds: true # 是否允许清理
keep_builds: 100 # 每个项目保留多少条
实测默认值就是 remove_builds: true、keep_builds: 100。若把它们改成 false / 0,命令会变成空操作,看起来「跑了但没效果」——这是最容易被误判为命令失效的情况。
清理范围:builds 表记录、builds.log 字段中的构建日志、runtime/builds/<项目ID>/ 下的工作目录。不清理 build_errors 中的历史错误统计以外的东西,也不动 build_metas 的自定义元数据。
建议用 cron 每天凌晨执行一次,避免手工遗忘:
# /etc/cron.d/php-censor-maintenance
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * php-censor cd /var/www/php-censor && ./bin/console php-censor:remove-old-builds >/dev/null 2>&1
写 cron 时两个细节:
- 必须写
cd /var/www/php-censor,因为bin/console依赖相对路径加载vendor/autoload.php。 - 必须指定
php-censor用户,否则清理runtime/builds/时因权限不足静默跳过,磁盘继续增长。
8.2 php-censor:check-localizations
校验 src/Translate/ 下各语言包与英文基准的键差异,升级后用于发现漏翻的界面文案。实测输出为逐语言文件列出缺失键:
./bin/console php-censor:check-localizations | head -12
Check localizations!
lang.da.php:
Not present:
per_page
default
remember_me
environment_x
project_groups
build_now_debug
clone_project
delete_old_builds
上面是丹麦语 lang.da.php 的前几项缺失键,输出还会继续列出其它语言。这项检查只读,不改文件,适合升级后回归。语言包缺失不会导致功能异常,只是对应语言下文案显示为英文键名。
8.3 php-censor:rebuild-queue 与 remove-old-builds 的组合排查
两个命令都没有输出,容易误判。推荐的验证顺序:
# 1. 记录执行前的等待数与构建总数
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e \
"select (select count(*) from builds where status=0) as pending, (select count(*) from builds) as total;"
# 2. 执行维护命令
./bin/console php-censor:rebuild-queue
./bin/console php-censor:remove-old-builds
# 3. 比对执行后的数值
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e \
"select (select count(*) from builds where status=0) as pending, (select count(*) from builds) as total;"
rebuild-queue 之后 pending 应不变(任务从「等待」变成「排队」,状态位仍是 0),队列的 current-jobs-ready 会上升;remove-old-builds 之后 total 应下降或持平。用数值变化判断,不要依赖输出。
九、日志体系与定位方法
PHP Censor 2.1.6 的日志分散在四个位置,各有用途,排查时必须按「先应用后系统」的顺序看。
9.1 应用控制台日志 runtime/console.log
所有 Console 命令经 Monolog 写入 runtime/console.log。实测内容:
cat /var/www/php-censor/runtime/console.log
[2026-10-27T17:27:51.508304+00:00] php-censor.INFO: Found 0 builds [] []
[2026-10-27T17:28:29.623819+00:00] php-censor.INFO: Found 0 builds [] []
[2026-10-27T17:39:44.857452+00:00] php-censor.INFO: Found 0 builds [] []
格式为 Monolog 的 [时间] 通道.级别: 消息 上下文 附加。php-censor.INFO 是通道名,说明日志来自业务代码而非 Symfony 框架。
日志轮转由 app/config.yml 控制:
log:
rotate: true # 开启按大小轮转
max_files: 10 # 保留多少个历史文件
实测默认安装生成的是 rotate: false、max_files: 0(InstallCommand.php 中硬编码的初始值),也就是单文件无限追加。长时间运行会持续变大,生产环境必须改成 true/10 以上并重启 Worker 生效。轮转实现是 Monolog 的 RotatingFileHandler,按文件大小切片,生成 console-2026-10-27.log 这类历史文件。
9.2 Worker 运行日志 journalctl
Worker 由 systemd 托管,标准输出被 journald 捕获,这是排查「进程退出、重启、构建取不到」的首选:
journalctl -u php-censor@1 --since "10 min ago" --no-pager
journalctl -u php-censor@1 -f # 实时跟踪
journalctl -u php-censor@1 -p err # 只看 error 级别
因为 Worker 循环里没有周期性打印,空闲时 journal 是空的,这属正常。看到 Deactivated successfully + Scheduled restart job 的组合,就是一次主动停止或 systemd 重启。
9.3 构建日志:数据库 builds.log
每次构建的完整输出写在 builds.log 字段(longtext),Web 端构建详情页读的就是它。实测读取:
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e "select log from builds where id=3;" | tail -12
实测回显(保留原始 ANSI 转义,展示时已去除颜色码):
Shell command: echo APP_ENV=$APP_ENV DB_NAME=$DB_NAME
APP_ENV=staging DB_NAME=demo_staging
Shell command: php -l src/Calculator.php
No syntax errors detected in src/Calculator.php
PLUGIN: SUCCESS
BUILD SUCCESS!
REMOVING BUILD.
日志里的 <BUILD_PATH>、<PHP_CENSOR_PATH> 是插件执行前的插值标记,实际命令已替换为真实路径,不要误认为是占位符没替换。排查插件失败时,直接搜 PLUGIN: FAIL 或 Shell command: 后面的真实命令。
9.4 站点与系统日志
- Nginx:
/var/log/nginx/php-censor.access.log、/var/log/nginx/php-censor.error.log。Web 端 500 的堆栈先看这里,实测project/add的 500 就是靠它定位到ProjectController.php第 414 行。 - PHP-FPM:
/var/log/php8.1-fpm.log,看WARNING中的pool www是否频繁重启。 - MariaDB:
/var/log/mysql/error.log,看连接数被打满或慢查询。 - Beanstalkd:
journalctl -u beanstalkd,看是否频繁重启导致任务丢失。
四类日志的分工可以概括为:console.log 看命令做了什么,journal 看进程活了没有,builds.log 看构建为什么失败,Nginx 日志看页面为什么 500。
十、备份与恢复
PHP Censor 需要备份的只有三样:数据库、app/config.yml、runtime/ 下的构建产物。代码本身可重新拉取,不必备份。
10.1 数据库备份
mkdir -p /var/backups/php-censor
mysqldump -uphp-censor -pq1w2e3r4 \
--single-transaction --routines --triggers \
php-censor > /var/backups/php-censor/php-censor-20261028.sql
echo "rc=$?"
实测回显与产物:
rc=0
total 20K
-rw-r--r-- 1 root root 19K Oct 27 17:40 php-censor-20261028.sql
备份文件头部:
/*M!999999\- enable the sandbox mode */
-- MariaDB dump 10.19 Distrib 10.6.23-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: php-censor
-- ------------------------------------------------------
三个参数不能省:
--single-transaction:InnoDB 下导出期间不加表锁,构建写库不会被阻塞。--routines:PHP Censor 本身无存储过程,但保留该参数可避免后续自定义脚本漏备。--triggers:同理,保证结构完整。
口令写在命令行会留在 shell 历史里。生产环境改用配置文件:
# /root/.my.cnf
[client]
user=php-censor
password=q1w2e3r4
之后命令简化为 mysqldump --single-transaction php-censor > 备份文件,口令不再出现在进程列表中。
10.2 备份覆盖范围核对
备份前后都应确认表数量,避免「备份成功但缺表」:
mysql -uphp-censor -pq1w2e3r4 php-censor -e "show tables;"
实测 10 张表:
Tables_in_php-censor
build_errors
build_metas
builds
environments
migrations
project_groups
projects
secrets
users
webhook_requests
secrets 表存的是项目级密钥,projects 存仓库地址与凭据。这两张表丢失无法从代码恢复,属于备份的重点对象。
10.3 恢复演练(已实测)
备份不做恢复演练等于没备份。实测把一个 19K 的备份导入临时库并核对数据:
mysql -uroot -e "DROP DATABASE IF EXISTS \`php-censor-restore\`; CREATE DATABASE \`php-censor-restore\` DEFAULT CHARACTER SET utf8mb4;"
mysql -uroot php-censor-restore < /var/backups/php-censor/php-censor-20261028.sql
echo "import_rc=$?"
mysql -uroot -N -e "select count(*) from information_schema.tables where table_schema='php-censor-restore';"
mysql -uroot -N -e "select count(*) from \`php-censor-restore\`.builds;"
mysql -uroot -e "DROP DATABASE \`php-censor-restore\`;"
实测回显:
import_rc=0
10
3
cleanup_rc=0
10 张表、3 条构建记录,与源库一致,导入无报错。演练要点:
- 不要导回生产库验证,建临时库导入即可,避免误操作覆盖数据。
- 导入后必须核对
users与projects的记录数,这两张表最能反映备份是否完整。 - 演练结束后立即删临时库,防止长期占用磁盘或误连。
正式恢复的步骤(会停服务,需窗口):
systemctl stop php-censor@1
cp /var/www/php-censor/app/config.yml /var/backups/php-censor/config.yml.bak
mysql -uroot -e "DROP DATABASE \`php-censor\`; CREATE DATABASE \`php-censor\` DEFAULT CHARACTER SET utf8mb4;"
mysql -uroot php-censor < /var/backups/php-censor/php-censor-20261028.sql
chown -R php-censor:php-censor /var/www/php-censor/runtime
systemctl start php-censor@1
chown 那一步不能省:恢复后 runtime/builds/ 若属主不对,首个构建会在创建目录时失败。
10.4 备份脚本与保留策略
#!/bin/bash
# /usr/local/bin/php-censor-backup.sh
set -euo pipefail
BACKUP_DIR=/var/backups/php-censor
KEEP_DAYS=14
STAMP=$(date +%Y%m%d-%H%M)
mysqldump --single-transaction php-censor > "${BACKUP_DIR}/db-${STAMP}.sql"
tar -czf "${BACKUP_DIR}/runtime-${STAMP}.tar.gz" -C /var/www/php-censor runtime
cp /var/www/php-censor/app/config.yml "${BACKUP_DIR}/config-${STAMP}.yml"
find "${BACKUP_DIR}" -type f -mtime +${KEEP_DAYS} -delete
runtime/ 里是构建工作目录,体积随构建数增长,压缩前可先执行 remove-old-builds 减少体积。数据库备份与产物备份分开保留,恢复时可按需取用。
十一、日常运维清单与故障处理
11.1 每日检查(3 分钟)
systemctl is-active mariadb beanstalkd php8.1-fpm php-censor@1
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e "select count(*) from builds where status=0;"
df -h /var/www/php-censor
三个检查点:四个服务全 active;等待态构建数不持续增长;程序目录剩余空间大于 20%。
11.2 升级标准动作
# 1. 备份
mysqldump --single-transaction php-censor > /var/backups/php-censor/before-upgrade.sql
# 2. 停 Worker,避免迁移与构建并发
systemctl stop php-censor@1
# 3. 更新代码与依赖(Composer 路径)
cd /var/www/php-censor && sudo -u php-censor composer install --no-dev --optimize-autoloader
# 4. 执行迁移并核对
./bin/console php-censor-migrations:status
./bin/console php-censor-migrations:migrate
# 5. 清缓存并启动
sudo -u php-censor rm -rf runtime/cache/* runtime/status_cache/*
systemctl start php-censor@1
顺序不能颠倒:停在迁移之后启动,Worker 会用旧的表结构写数据,可能触发字段缺失异常。
11.3 故障一:构建一直「等待中」
现象:Web 端构建列表显示等待,start_date 为空。
systemctl status php-censor@1 --no-pager
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e "select count(*) from builds where status=0;"
- Worker 未运行 →
systemctl restart php-censor@1。 - Worker 运行但队列
current-jobs-ready为 0 → 任务根本没入队,检查php-censor.queue.host/port与 Beanstalkd 实际监听。 - 队列有任务但
current-jobs-reserved不降 → Worker 卡在某个插件,重启 Worker,并从builds.log找最后一条Shell command。
11.4 故障二:create-build 返回 0 但没有构建记录
命令输出 Build Created 说明任务已入队,记录随后由 Worker 写入。若长时间无记录:
mysql -uphp-censor -pq1w2e3r4 php-censor -e "select max(id) from builds;"
journalctl -u php-censor@1 -n 30 --no-pager
- 队列服务重启过导致任务丢失 →
rebuild-queue可能无效(数据库里没有status=0记录),只能重新触发。 - Worker 的数据库连接断开 → journal 里会有连接异常,重启 Worker。
11.5 故障三:migrations:status 输出空表
先分清两种空表:
- 只有表头没有数据行:说明
migrations表为空,通常是database.name指向了错误的库(如库名写成了php-censor-db)。核对show databases;与app/config.yml。 - 命令直接报
Unknown database:库不存在,需要按安装指南建库并授权。
两种情况都不要急着重跑 migrate,先确认配置指向,否则会对空库建一批表,之后真正的库反而要手工清理。
11.6 故障四:磁盘被 runtime/builds/ 撑满
du -sh /var/www/php-censor/runtime/builds/* | sort -h | tail -5
runtime/builds/<项目ID>/ 下每个构建一个目录,名为 构建ID_提交短哈希。实测在正常配置下构建结束后目录会被删除(日志末尾的 REMOVING BUILD.),残留目录说明插件异常退出,未执行清理钩子。处理方式:
# 先确认没有构建正在运行
systemctl is-active php-censor@1
mysql -uphp-censor -pq1w2e3r4 php-censor -N -e "select count(*) from builds where status=0;"
# 清理 7 天前的残留工作目录
find /var/www/php-censor/runtime/builds -mindepth 2 -maxdepth 2 -type d -mtime +7 -exec rm -rf {} +
删除前必须确认没有构建在跑,否则会删掉正在使用的目录,导致该构建失败。
11.7 故障五:Worker 启动即退出
journalctl -u php-censor@1 -n 50 --no-pager
cd /var/www/php-censor && sudo -u php-censor ./bin/console php-censor:worker -vvv
手工跑一次能复现真实原因。常见三类:
Permission deniedonruntime/→ 属主不对,chown -R php-censor:php-censor runtime。SQLSTATE[HY000] [1045]→ 数据库口令错,改app/config.yml后重启。Class "..." not found→ 升级后未执行composer install或未清runtime/cache/。
手工命令能正常跑、systemd 起不来,差异通常在环境变量与工作目录,重点比对单元文件里的 WorkingDirectory 与 User。
11.8 故障六:优雅停止无效
执行 --stop-worker=idle 后进程仍在,可能有两种情况:
- 当前不是空闲态,正有构建在跑:
idle只在等待任务时生效,等构建结束才会退出。改用--stop-worker=soon或等待。 - 信号发给了别的实例:命令会通知「一个」Worker,多实例场景下无法指定具体实例。要停指定实例用
systemctl stop php-censor@2。
验证方式:看 journalctl 是否出现 Deactivated successfully,以及 systemctl status 的 Active 时间戳是否更新。
十二、参考链接
- 项目仓库:https://github.com/php-censor/php-censor
- 2.1.6 发布页:https://github.com/php-censor/php-censor/releases/tag/2.1.6
- 官方 Console 命令文档:https://github.com/php-censor/php-censor/blob/release-2.1/docs/en/commands.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/periodical_builds.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 Ubuntu 22.04 安装指南》
- 站内相关:《PHP Censor 2.1.6 配置参数详解》