Limbas 7.1.15 常用运维命令与排障实操

Limbas 7.1.15 运维实操实测:cli.php/cron.php 计划任务与 8 个命令类、REST /admin/jobs 管理接口、psql/pg_dump 数据库侧运维、三通道备份与恢复演练、整包升级替换与补丁重放演练,含 10 条真实报错回显的排障实录

Limbas低代码自托管PHP 应用PostgreSQL表单构建2026/11/03

Limbas 7.1.15 常用运维命令与排障实操

目标:把 Limbas 7.1.15 的日常运维面——服务健康检查、计划任务命令行(cli.php/cron.php 与 8 个命令类)、REST 管理接口 /admin/jobs、数据库侧 SQL 运维、三通道备份与恢复演练、整包升级替换与补丁重放——全部用真实命令与实测回显讲清,并沉淀升级与运行中真实踩到的坑。所有命令与输出均来自真实服务器 limbas-server(Ubuntu 22.04.5 + Apache 2.4.52 + PHP 8.3.36 + PostgreSQL 14.24,Limbas 7.1.15,安装路径 /opt/limbas/openlimbas-7.1.15,数据库 limbasdb)实测。安装过程见《Ubuntu 22.04 安装 Limbas 7.1.15 指南》,参数含义见《Limbas 7.1.15 配置参数详解》,软件背景见《Limbas 介绍与选型指南》。

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS(主机名 limbas-server,IP 10.89.59.2)
Web 服务器Apache/2.4.52 (Ubuntu),mpm_prefork + libapache2-mod-php8.3,监听 80
PHPPHP 8.3.36 (cli)(ondrej/php PPA,与 apache2 模块同版本)
数据库PostgreSQL 14.24(Ubuntu 22.04 默认源),库 limbasdb,应用角色 limbas
Limbasopenlimbas 7.1.15(limbas_src/lib/version.inf 实测 7.1.15.2519;lmb_dbpatch 最新补丁 id=726)
安装路径/opt/limbas/openlimbas-7.1.15(Web 根为其 public/ 子目录)
应用备份目录/opt/limbas/openlimbas-7.1.15/dependent/BACKUP/
运维日志/var/log/apache2/limbas_access.log、limbas_error.log;cron 输出 /var/log/limbas-cron.log
演示账号应用管理员 admin / q1w2e3r4;数据库角色 limbas / q1w2e3r4;系统 root
系统规模实测public schema 152 张表、lmb_umgvar 143 项全局配置、lmb_crontab 4 个作业、lmb_dbpatch 724 条补丁记录

二、运维工具链总览与前置条件

Limbas 没有独立的运维控制台命令(不存在类似 bin/console 的管理 CLI),日常运维由四条命令线支撑:系统服务命令(systemctl/apache2ctl)、应用任务命令行(limbas_src/cli.php、limbas_src/cron.php 与 8 个命令类)、REST 管理接口(/admin/jobs 资源路由)、数据库侧 SQL(psql/pg_dump 直改 lmb_* 系统表)。Web 管理界面(配置中心、备份页、作业页)与命令行操作的是同一份数据,可任选入口。

运维对象命令/入口频率
服务进程(Apache/PostgreSQL/cron)systemctl / apache2ctl巡检
应用健康与版本curl 登录页 + version.inf + lmb_dbpatch每日
计划任务(索引/OCR/备份/提醒等)php limbas_src/cli.php <job-id|命令类>按需/crontab
全量作业批跑php limbas_src/cron.php每分钟(可选)
作业增删改(REST)PUT/GET/DELETE /admin/jobs[/{id}[/run]]按需
全局配置Web action=setup_umgvar 或 SQL 直改 lmb_umgvar按需
备份Web 备份页 / cli.php Backup -c / pg_dump每日
升级官方 tar 整包替换 + 登录触发 Updater 补丁按版本
日志limbas_access.log / limbas_error.log巡检

前置条件:Limbas 已按《Ubuntu 22.04 安装 Limbas 7.1.15 指南》完成安装并可登录;本文命令默认在 root 下执行,涉及应用写操作的(作业、备份产物)注意产物属主(见 9.9)。

💡 技术拓展:Limbas 计划任务的驱动方式不止一种。常见三种对比——

驱动方式原理适用场景
系统 crontab 逐作业调 cli.php <id>每个 lmb_crontab 作业对应一条 crontab 行需要按作业分粒度记日志、单独停启
系统 crontab 每分钟调 cron.php应用自己遍历全部 active 作业判断到点执行作业多、不想改一次配置动一次系统 crontab
REST GET /admin/jobs/{id}/runHTTP 触发单作业与外部编排系统(如 CI、告警平台)集成

本文实际采用第一种(逐作业 crontab),理由:作业日志可分文件落盘、单作业失败不影响其他作业的调度记录;cron.php 批跑方式在 4.4 节同样给出实测。

三、服务管理与健康检查

服务面只有 Apache、PostgreSQL 两个常驻进程(外加驱动计划任务的 cron)。本节命令是每日巡检与变更后验证的最小集合。

3.1 服务状态速查

systemctl is-active apache2
systemctl is-active postgresql
systemctl is-enabled apache2
systemctl is-enabled postgresql

实测回显:

active
active
enabled
enabled

看进程详情:

systemctl status apache2 --no-pager | head -5
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-11-02 22:27:06 UTC; 3h 35min ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 19156 (apache2)

改完 vhost 或 php.ini 后先语法检查再重载:

apachectl configtest
systemctl reload apache2

实测回显(AH00558 为未设全局 ServerName 的告警,不影响运行,可在 apache2.conf 加 ServerName limbas-server 消除):

AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 10.89.59.2. Set the 'ServerName' directive globally to suppress this message
Syntax OK

3.2 版本与补丁层级速查

apache2ctl -v | head -2
php -v | head -1
su - postgres -c "psql --version"
cat /opt/limbas/openlimbas-7.1.15/limbas_src/lib/version.inf; echo

实测回显:

Server version: Apache/2.4.52 (Ubuntu)
Server built:   2026-10-08T19:59:46
PHP 8.3.36 (cli) (built: Oct 21 2026 14:31:50) (NTS)
psql (PostgreSQL) 14.24 (Ubuntu 14.24-0ubuntu0.22.04.1)
7.1.15.2519

Limbas 的「代码版本」看 version.inf,「数据库结构版本」看 lmb_dbpatch 表最大行,两者一致才算升级闭环(机制见第八章):

su - postgres -c "psql -d limbasdb -c \"select id, major, version, revision, status from lmb_dbpatch order by id desc limit 3;\""
su - postgres -c "psql -d limbasdb -tAc 'select count(*) from lmb_dbpatch;'"
 id  | major | version | revision | status 
-----+-------+---------+----------+--------
 726 |     7 |       1 |       15 | t
 725 |     7 |       1 |       14 | t
 724 |     7 |       1 |       13 | t
(3 rows)

724

3.3 HTTP 与数据库连通性

curl -s -o /dev/null -w "GET / -> %{http_code}, %{size_download} bytes\n" http://localhost/
curl -s http://localhost/ | grep -o '<title>[^<]*</title>'
pg_isready -h localhost -p 5432

实测回显(未登录时登录页标题带 lmb_umgvar id=110 配置的自定义前缀 Limbas-Demo - ,可顺带验证全局配置生效):

GET / -> 200, 1959 bytes
<title>Limbas-Demo - Login</title>
localhost:5432 - accepting connections

3.4 日志巡检

tail -n 4 /var/log/apache2/limbas_access.log
tail -n 6 /var/log/apache2/limbas_error.log

实测回显(access 节选;error 为未认证 GET 请求触发的 PHP Warning,属 7.x 遗留代码噪音、非致命,判读原则见 9.9):

127.0.0.1 - - [03/Nov/2026:01:58:20 +0000] "GET / HTTP/1.1" 200 2240 "-" "curl/7.81.0"
127.0.0.1 - - [03/Nov/2026:02:02:15 +0000] "GET / HTTP/1.1" 200 2240 "-" "curl/7.81.0"
[Tue Nov 03 02:02:15.650539 2026] [php:warn] [pid 19424] [client 127.0.0.1:50950] PHP Warning:  Undefined array key "PHP_AUTH_PW" in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/auth/AuthDefault.php on line 90
[Tue Nov 03 02:02:15.650555 2026] [php:warn] [pid 19424] [client 127.0.0.1:50950] PHP Warning:  session_name(): Session name cannot be changed when a session is active in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/auth/Session.php on line 26
[Tue Nov 03 02:02:15.697271 2026] [php:warn] [pid 19163] [client 127.0.0.1:50958] PHP Warning:  Undefined array key "authId" in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/auth/Auth.php on line 121

四、计划任务命令行:cli.php 与 cron.php

Limbas 的作业体系数据在 lmb_crontab 表(4 个作业:2 个 INDIZE、1 个 TEMPLATE、1 个 OCR),执行入口有两个:单作业/命令类用 cli.php,批跑全部到点作业用 cron.php。本节逐条实测。

4.1 cli.php 用法与无参回显

cli.php 的入口逻辑(实测源码 limbas_src/cli.php 节选):第一个参数是数字则按 lmb_crontab.id 跑作业,是字符串则按命令类名跑 \Limbas\admin\tools\cron\commands\<Name>,命令类还支持 -c/--config <json> 覆盖作业配置:

$command = $argv[1];
if(empty($command)) {
    echo 'Command or job id not specified' . PHP_EOL;
    exit(1);
}
if(is_numeric($command)) {
    $command = intval($command);
}
$status = $limbas->runCommand($command, $argv);
exit($status);

不带参数执行的实测回显(exit 1,可直接当用法说明):

cd /opt/limbas/openlimbas-7.1.15/limbas_src
php cli.php; echo "exit=$?"
PHP Warning:  Undefined array key 1 in /opt/limbas/openlimbas-7.1.15/limbas_src/cli.php on line 19
Command or job id not specified
exit=1

4.2 按作业 ID 执行

先看待跑作业清单(该 SQL 也是 REST/Web 的数据源):

su - postgres -c "psql -d limbasdb -c \"select id, type, active, description, config, cron_minutes, cron_hours from lmb_crontab order by id;\""

实测回显(job4 的 config 已是空数组——它原本配置指向 demo 库不存在的字段,清理过程见 9.5):

 id |   type   | active |   description    |                    config                    | cron_minutes | cron_hours 
----+----------+--------+------------------+----------------------------------------------+--------------+------------
  1 | INDIZE   | t      | Indexing (1)     | {"includeSubdirs":true,"files":["2"]}        | 0            | 1
  2 | TEMPLATE | t      | TEMPLATE (1)     | {"template":"\/EXTENSIONS\/ocr\/update.job"} | 0            | 1
  3 | OCR      | t      | OCR (1)          | {"files":["1"]}                              | 0            | 1
  4 | INDIZE   | t      | indize-feldtypen | []                                           | 0            | 3
(4 rows)

跑作业 1(文件结构索引):

php cli.php 1 > /tmp/cli1.out 2>&1; echo "exit=$?"; wc -c /tmp/cli1.out
head -c 600 /tmp/cli1.out; echo; echo '...TAIL...'; tail -c 300 /tmp/cli1.out

实测回显(进度以 HTML 片段输出,Dokumente 即被索引的文件目录;尾部是 7.1.15 的一个真实 SQL 缺陷——索引历史写入语句多一个尾逗号,索引本身已完成,判读见 9.6):

exit=0
1080 /tmp/cli1.out
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/Limbas.php on line 235
PHP Warning:  Undefined global variable $umgvar in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/include_mbstring.lib on line 34
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/include_mbstring.lib on line 34

        <script>$('#indcontainer').show();</script>
        <div class='lmb-progress-container'>
            <div id='file_2_desc'>Dokumente</div>
            <div class='lmb-progress'>
                <span id='file_2_valtext' class='lmb-progress-valtext'></span>
                <span id='file_2_val' class='lmb-progress-val'></span>
            </div>
        </div>Error: ERROR:  syntax error at or near ")"
LINE 1: ...NUM,JOB) VALUES(1,'Filestructure Index',TRUE,0.0032,'',0,0,)
                                                                      ^ for query INSERT INTO LMB_INDIZE_HISTORY (ID,ACTION,RESULT,USED_TIME,MESSAGE,INUM,JNUM,JOB) VALUES(1,'Filestructure Index',TRUE,0.0032,'',0,0,)

跑作业 4(清理配置后)与不存在的作业 99:

php cli.php 4 2>&1; echo "exit=$?"
php cli.php 99 2>&1 | head -4; echo "exit=$?"

实测回显(job4 已无报错;job99 只留一条 Warning 就静默返回 exit=0——CLI 对不存在的作业 ID 不报错,脚本化时要自己核对 lmb_crontab):

PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/Limbas.php on line 235
PHP Warning:  Undefined global variable $umgvar in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/include_mbstring.lib on line 34
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/lib/include_mbstring.lib on line 34
exit=0
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/Limbas.php on line 235
exit=0

4.3 命令类直调与 Backup 全量备份

limbas_src/admin/tools/cron/commands/ 下共 8 个命令类(实测目录清单):

Backup.php  Datasync.php  Indize.php  Ocr.php  Reminder.php  Rsync.php  StructureSync.php  Template.php

不带 -c 时命令类读取对应作业的配置;带 -c 可用 JSON 直接传参。以 Backup 类做一次完整数据备份实测:

php cli.php Backup -c '{"backupType":"complete_data_backup","backupMedium":"file","backupTarget":"/opt/limbas/openlimbas-7.1.15/dependent/BACKUP/","backupAlive":7}' > /tmp/backup.out 2>&1
echo "exit=$?"; wc -c /tmp/backup.out
ls -la /opt/limbas/openlimbas-7.1.15/dependent/BACKUP/

实测回显(exit=0,输出仅 119 字节即一行 legacy Warning,成功与否以产物文件为准;新产物 26_11_03_02_35.DATA.gz 522064 字节,命名规则 YY_MM_DD_HH_MM.DATA.gz):

exit=0
119 /tmp/backup.out
total 2732
drwxrwxrwx 2 www-data www-data   4096 Nov  3 02:02 .
drwxrwxrwx 8 www-data www-data   4096 Aug 12 12:19 ..
-rw-rw-rw- 1 www-data www-data     38 Aug 12 12:19 .gitignore
-rw-r--r-- 1 root     root     171525 Nov  2 22:39 26_11_02_39_40.DATA.gz
-rw-r--r-- 1 root     root     171561 Nov  2 22:41 26_11_02_41_20.DATA.gz
-rw-r--r-- 1 www-data www-data 171592 Nov  2 22:41 26_11_02_41_26.DATA.gz
-rw-r--r-- 1 root     root     522064 Nov  3 02:02 26_11_03_02_35.DATA.gz
-rw-r--r-- 1 root     root     521930 Nov  2 23:24 26_11_03_24_29.DATA.gz
-rw-r--r-- 1 root     root     522043 Nov  2 23:24 26_11_03_24_37.DATA.gz
-rw-rw-rw- 1 www-data www-data 174536 Aug 12 12:19 clean.tar.gz
-rw-r--r-- 1 www-data www-data 518754 Aug 12 12:20 demo.tar.gz

Backup 类接受 4 个配置键(实测源码 Backup.php):backupType(complete_data_backup/incremental_data_backup/log_backup)、backupMedium(file→1、tape→2)、backupTarget(目标目录)、backupAlive(保留天数,0/缺省则不过期)。注意键名是 backupMedium 不是 backupMedia,写错不报错但备份不落目标位(见 9.3)。每次成功备份还会向 lmb_history_backup 表写一行台账:

su - postgres -c "psql -d limbasdb -c 'select id, erstdatum, label, action, result, medium, size from lmb_history_backup order by id;'"
 id |         erstdatum          | label |  action   | result |     medium     | size 
----+----------------------------+-------+-----------+--------+----------------+------
  1 | 2026-11-02 23:24:30.080954 |       | SAVE DATA | t      | DATA2611032429 |  510
  2 | 2026-11-02 23:24:37.278181 |       | SAVE DATA | t      | DATA2611032437 |  510
  3 | 2026-11-03 02:02:36.402559 |       | SAVE DATA | t      | DATA2611030235 |  510
(3 rows)

第 3 行正是上面 CLI 备份的台账(medium 列即产物文件名去扩展名,size 单位 KB)。

其余命令类以 Reminder 为例(无到期提醒数据时只留 Warning、exit=0):

php cli.php Reminder 2>&1 | head -6; echo "exit=$?"
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/Limbas.php on line 235
exit=0

4.4 cron.php 批跑全部到点作业

cron.php 与 cli.php 共用引导,只是直接调 Limbas::runCron()(实测源码全文 17 行,核心即末两行):

$request = Request::createFromGlobals();
$limbas = Limbas::get($request);
$limbas->runCron();

runCron() 内部 LimbasCron::loadJobs() 取 CronJob::active() 全部 active 作业按分钟匹配执行。实测直接跑:

timeout 120 php cron.php > /tmp/cronall.out 2>&1; echo "exit=$?"; wc -l /tmp/cronall.out
grep -c 'PHP Warning' /tmp/cronall.out
head -4 /tmp/cronall.out

实测回显(exit=0;20 行输出全部是 Warning——filestructure.lib/jobs_ext.lib/include.lib 等 legacy 代码在 CLI 上下文缺全局变量的噪音,不影响作业执行,判读原则见 9.9):

exit=0
20 /tmp/cronall.out
20
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/Limbas.php on line 235
PHP Warning:  Trying to access array offset on null in /opt/limbas/openlimbas-7.1.15/limbas_src/extra/explorer/filestructure.lib on line 26
PHP Warning:  Undefined variable $action in /opt/limbas/openlimbas-7.1.15/limbas_src/extra/explorer/filestructure.lib on line 32
PHP Warning:  Undefined variable $gmimetypes in /opt/limbas/openlimbas-7.1.15/limbas_src/admin/tools/jobs_ext.lib on line 20

4.5 系统 crontab 部署

本机初始未装 cron(which crontab 无输出,dpkg -l cron 显示 un 未安装状态)。安装并注册作业:

DEBIAN_FRONTEND=noninteractive apt-get install -y cron

实测回显(输出节选(实测首尾);policy-rc.d denied 表示包安装脚本被策略阻止自启,必须手工 enable+start,见 9.8):

Setting up cron (3.0pl1-137ubuntu3) ...
Adding group `crontab' (GID 109) ...
Done.
invoke-rc.d: policy-rc.d denied execution of start.
Created symlink /etc/systemd/system/multi-user.target.wants/cron.service → /lib/systemd/system/cron.service.
/usr/sbin/policy-rc.d returned 101, not running 'start cron.service'
systemctl enable cron
systemctl is-active cron
Synchronizing state of cron.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable cron
inactive

/etc/cron.d/limbas 按 lmb_crontab 的调度(job1 每日 01:00、job4 每日 03:00)落盘,属主 root、权限 644:

# Limbas 7.1.15 计划任务: 驱动 lmb_crontab 作业(job1 01:00, job4 03:00)
SHELL=/bin/bash
0 1 * * * www-data php /opt/limbas/openlimbas-7.1.15/limbas_src/cli.php 1 >> /var/log/limbas-cron.log 2>&1
0 3 * * * www-data php /opt/limbas/openlimbas-7.1.15/limbas_src/cli.php 4 >> /var/log/limbas-cron.log 2>&1

启动并验证守护进程:

systemctl restart cron
systemctl is-active cron
journalctl -u cron --no-pager -n 3 | tail -3

实测回显:

active
Nov 03 02:04:57 limbas-server systemd[1]: Started Regular background program processing daemon.
Nov 03 02:04:57 limbas-server cron[25483]: (CRON) INFO (pidfile fd = 3)
Nov 03 02:04:57 limbas-server cron[25483]: (CRON) INFO (Running @reboot jobs)

⚠️ 注意:lmb_crontab 表里的 cron 表达式只是「应用内配置」,Limbas 自身不常驻调度进程;要么按本节把它翻译成系统 crontab(逐作业),要么每分钟一条 * * * * * www-data php .../cron.php(批跑)。两者选一,不要同时启用同一作业,否则双跑。

五、REST 管理接口:/admin/jobs

作业管理除 Web 界面外有一组 REST 路由(实测源码 limbas_src/routes/admin.php 全文):

Route::group(['prefix' => 'admin', 'name' => 'admin.'], function () {
    Route::resource('jobs', JobController::class, 'jobs', options: ['exclude' => ['show']]);
    Route::get('jobs/{id}/run', [JobController::class, 'run'])->name('jobs.run');
});

即支持 GET/POST/PUT/DELETE /admin/jobs[/{id}] 与 GET /admin/jobs/{id}/run。鉴权复用应用会话:先用 admin / q1w2e3r4 登录拿 PHPSESSID Cookie,后续请求带 Cookie 即可。

5.1 登录取会话

curl -s -c /tmp/lmb_cookie.txt -o /tmp/login.html -w "POST /index.php -> %{http_code}, %{size_download} bytes\n" \
  -d 'username=admin&password=q1w2e3r4' http://localhost/index.php
grep -o '<title>[^<]*</title>' /tmp/login.html | head -1

实测回显(111KB 主界面 + 标题带版本号即登录成功):

POST /index.php -> 200, 111440 bytes
<title>Limbas Enterprise Unifying Framework V 7.1.15</title>

5.2 触发作业运行

curl -s -b /tmp/lmb_cookie.txt -o /tmp/run1.html -w "GET /admin/jobs/1/run -> %{http_code}, %{size_download} bytes\n" http://localhost/admin/jobs/1/run
cat /tmp/run1.html

实测回显(461 字节 HTML 进度片段,0 done 表示本轮文件索引处理计数;同一请求在 error log 留下与 4.2 节相同的 INDIZE_HISTORY 尾逗号报错,两通道同缺陷):

GET /admin/jobs/1/run -> 200, 461 bytes

        <script>$('#indcontainer').show();</script>
        <div class='lmb-progress-container'>
            <div id='file_2_desc'>Dokumente</div>
            <div class='lmb-progress'>
                <span id='file_2_valtext' class='lmb-progress-valtext'></span>
                <span id='file_2_val' class='lmb-progress-val'></span>
            </div>
        </div><script>$('#file_2_val').css('width', '100%');$('#file_2_valtext').html('0 done');</script>

5.3 修改作业(PUT 必须全量键)

JobController::update() 逐键取表单参数赋给强类型属性(实测源码节选):

$job->cronMinutes = $request->get('cron_minutes');
$job->cronHours = $request->get('cron_hours');
$job->cronMonthdays = $request->get('cron_monthdays');
$job->cronMonths = $request->get('cron_months');
$job->cronWeekdays = $request->get('cron_weekdays');
$job->active = boolval($request->get('active'));
$job->description = $request->get('description', '');
$job->config->backupType = $request->get('backup_type', '');
$job->config->backupMedium = $request->get('backup_medium', '');
$job->config->backupTarget = $request->get('backup_target', '');
$job->config->backupAlive = $request->get('backup_alive', 0);
$job->userId = (int)$request->get('jobuser');

因此 PUT 必须 x-www-form-urlencoded 且带齐全套键(cron_minutes/cron_hours/cron_monthdays/cron_months/cron_weekdays/active/description/include_subdirs/template/backup_type/backup_medium/backup_target/backup_alive/jobuser),且 backup_alive 必须是数字。以作业 1 做一次与现值一致的幂等回写实测:

curl -s -b /tmp/lmb_cookie.txt -o /tmp/put1.json -w "PUT /admin/jobs/1 -> %{http_code}\n" -X PUT \
  -d 'cron_minutes=0&cron_hours=1&cron_monthdays=*&cron_months=*&cron_weekdays=*&active=1&description=Indexing (1)&include_subdirs=1&template=&files[]=2&backup_type=&backup_medium=&backup_target=&backup_alive=0&jobuser=1' \
  http://localhost/admin/jobs/1
head -c 500 /tmp/put1.json; echo

实测回显(success:true + 回传新行 HTML,0 1 * * * 即调度串):

PUT /admin/jobs/1 -> 200
{"success":true,"html":"\u003Ctr id=\u0022job-1\u0022\u003E\n    \u003Ctd\u003E\u003Ci class=\u0022fas fa-pencil cursor-pointer link-primary\u0022 data-url=\u0022http:\/\/localhost\/admin\/jobs\/1\/edit\u0022 data-bs-toggle=\u0022modal\u0022 data-bs-target=\u0022#modal-jobs\u0022 data-id=\u00221\u0022\u003E\u003C\/i\u003E\u003C\/td\u003E\n    \u003Ctd\u003E1\u003C\/td\u003E\n    \u003Ctd\u003EINDIZE\u003C\/td\u003E\n    \u003Ctd\u003E\u003C\/td\u003E\n    \u003Ctd\u003E0 1 * * *\u003C\/td\u003E\

两个失败形态的实测回显(判读见 9.7):

curl -s -b /tmp/lmb_cookie.txt -X PUT -H 'Content-Type: application/json' -d '{"description":"x"}' http://localhost/admin/jobs/4 -o /tmp/put4b.html
grep -m2 -oE 'TypeError[^<]{0,140}' /tmp/put4b.html
TypeError

error log 中的完整异常(缺键 → cronMinutes 收到 null;backup_alive= 空串 → 字符串赋给 int 属性):

limbas.ERROR: Cannot assign string to property Limbas\admin\tools\cron\CronConfig::$backupAlive of type int
limbas.ERROR: Cannot assign null to property Limbas\admin\tools\cron\CronJob::$cronMinutes of type string

按正确姿势补齐 backup_alive=0 后重放作业 4 的 PUT,实测成功:

PUT /admin/jobs/4 -> 200
{"success":true,"html":"\u003Ctr id=\u0022job-4\u0022\u003E\n    \u003Ctd\u003E\u003Ci class=\u0022fas fa-pencil cursor-pointer link-primary\u0022 data-url=\u0022http:\/\/localhost\/admin\/jobs\/4\/edit\u0022 data-bs-toggle=\u0022modal\u0022 data-bs-target=\u0

5.4 作业列表接口的已知缺陷

GET /admin/jobs(列表)实测返回 HTTP 200 但页面是 Server Error:

curl -s -b /tmp/lmb_cookie.txt -o /tmp/jobs_list.html -w "GET /admin/jobs -> %{http_code}, %{size_download} bytes\n" http://localhost/admin/jobs
grep -m1 -oE 'The controller must return.{0,160}' /tmp/jobs_list.html
GET /admin/jobs -> 200, 2546 bytes
The controller must return a &quot;Symfony\Component\HttpFoundation\Response&quot; object but it returned a string (&quot;

error log 完整堆栈(JobController::index() 用 ob_get_clean() 返回 string 而非 Response 对象,7.1.15 框架缺陷;列表请走 Web 界面 action=setup_cron 或直接查 lmb_crontab 表):

limbas.ERROR: The controller must return a "Symfony\\Component\\HttpFoundation\\Response" object but it returned a string ("\n<script src="assets/vendor/select2/select2.full.min.js"></script>\n<link href="assets/vendor/select2/select2.min.css" rel="stylesheet">\n\n<div class="container-fluid p-3">\n    \n    <div class="card mb-3">\n        <div class="card-body">\n            <p>Nutz...").
#0 /opt/limbas/openlimbas-7.1.15/vendor/symfony/http-kernel/HttpKernel.php(183): ()
#1 /opt/limbas/openlimbas-7.1.15/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\Component\HttpKernel\HttpKernel->handleRaw()
#2 /opt/limbas/openlimbas-7.1.15/limbas_src/Limbas.php(99): Symfony\Component\HttpKernel\HttpKernel->handle()
#3 /opt/limbas/openlimbas-7.1.15/limbas_src/index.php(21): Limbas\Limbas->handle()
#4 /opt/limbas/openlimbas-7.1.15/public/index.php(12): require_once('...')
#5 {main}

六、数据库侧 SQL 运维

Limbas 的配置、作业、用户、会话、补丁台账全在 PostgreSQL limbasdb 的 lmb_* 系统表里,SQL 通道是命令行/Web 之外的第三入口,也是排障时的「真相源」。

6.1 连接身份切换实录

root 直接 psql 会被 PostgreSQL 的 peer 认证拦下(两种典型报错,实测逐行照抄):

psql -U postgres -d limbasdb -c 'select 1;'
psql -d limbasdb -c 'select 1;'
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  Peer authentication failed for user "postgres"
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  role "root" does not exist

正确姿势一:切到 postgres 系统用户(走 pg_hba.conf 的 peer 行):

su - postgres -c "psql -d limbasdb -tAc 'select current_user, current_database();'"
postgres|limbasdb

正确姿势二:用应用角色走 TCP(pg_hba.conf 对 127.0.0.1/32 配的是 scram-sha-256,口令即 include_db.lib 里的 q1w2e3r4):

PGPASSWORD=q1w2e3r4 psql -h 127.0.0.1 -U limbas -d limbasdb -tAc 'select current_user, current_database();'
limbas|limbasdb

权限边界实测:limbas 角色非超管,不能建库(备份恢复演练建目标库必须借道 postgres,见 7.5):

PGPASSWORD=q1w2e3r4 createdb -h 127.0.0.1 -U limbas testdb
createdb: error: database creation failed: ERROR:  permission denied to create database

6.2 核心系统表速查

表作用实测规模
lmb_umgvar全局配置(143 项,值在 norm 列)143 行
lmb_crontab计划作业定义4 行(全 active)
lmb_userdb应用用户(bcrypt 口令)3 行
lmb_session登录会话6 行(清理见 6.3)
lmb_dbpatch数据库补丁台账(版本判据)724 行,top=726/7.1.15/t
lmb_history_backup备份台账3 行
lmb_auth_tokenAPI 令牌0 行

会话表结构实测(\d lmb_session,清理过期会话按 erstdatum/logout 两列):

                                 Table "public.lmb_session"
       Column       |            Type             | Collation | Nullable |      Default      
--------------------+-----------------------------+-----------+----------+-------------------
 id                 | character varying(128)      |           | not null | 
 user_id            | integer                     |           |          | 
 group_id           | integer                     |           |          | 
 logout             | boolean                     |           |          | false
 erstdatum          | timestamp without time zone |           |          | CURRENT_TIMESTAMP
 ip                 | character varying(20)       |           |          | 
 filestruct_changed | boolean                     |           |          | false
 table_changed      | boolean                     |           |          | false
 snap_changed       | boolean                     |           |          | false
Indexes:
    "lmb_session_pkey" PRIMARY KEY, btree (id)

样例数据实测(cli- 前缀行是 CLI 会话,ip 记 noip):

              id              | user_id | group_id | logout |         erstdatum          |    ip     | filestruct_changed | table_changed | snap_changed 
------------------------------+---------+----------+--------+----------------------------+-----------+--------------------+---------------+--------------
 bcaj4boe377vpp6r4r26jh67tn   |       1 |        0 | f      | 2026-11-02 22:45:41.994209 | 127.0.0.1 | f                  | f             | f
 u2824uupcojuple7bkai5s4ekc   |       1 |        0 | f      | 2026-11-02 22:58:19.829499 | 127.0.0.1 | f                  | f             | f
 cli-e5cab71de172505d37590d83 |       1 |        0 | f      | 2026-11-02 22:59:00.59789  | noip      | f                  | f             | f
(3 rows)

6.3 常用运维 SQL

查关键全局配置(路径类变量 id=3/21/27/59 出厂值仍是 vendor 目录 /srv/www/htdocs/projects/openlimbas/,与实装路径不符时应按《Limbas 7.1.15 配置参数详解》修正):

su - postgres -c "psql -d limbasdb -c \"select id, beschreibung, norm from lmb_umgvar where id in (3,21,27,59,110,111,134) order by id;\""

实测回显(宽列有删节):

 id  |                                      beschreibung                                      |                                norm                                
-----+----------------------------------------------------------------------------------------+--------------------------------------------------------------------
   3 | absolute path of installation                                                          | /srv/www/htdocs/projects/openlimbas/dependent/
  21 | default path of Backup                                                                 | localhost:////srv/www/htdocs/projects/openlimbas/dependent/BACKUP/
  27 | Base URL of installation                                                               | 
  59 | path to crontab                                                                        | /var/spool/cron/tabs/webuser
 110 | custom title %s                                                                        | Limbas-Demo - %s
 111 | check for updates (0/1)                                                                | 0
 134 | show update page to user or try automatically in background (background / foreground)  | foreground
(7 rows)

查用户(注意列名是 user_id/username/name/vorname,没有 id/lastname 列,按惯性写 SELECT id 会报错):

su - postgres -c "psql -d limbasdb -c 'select user_id, username, name, vorname from lmb_userdb order by user_id;'"
 user_id | username |     name      | vorname 
---------+----------+---------------+---------
       1 | admin    | Administratos | Adminus
       5 | landros  | Soffos        | Landros
       8 | Katinka  | Parabel       | Katinka
(3 rows)

清理过期/已登出会话(实测当前 6 个会话均为活跃新会话,DELETE 0 属正常结果;长期运行后该语句可回收 lmb_session):

su - postgres -c "psql -d limbasdb -tAc 'select count(*) from lmb_session;'"
su - postgres -c "psql -d limbasdb -c \"delete from lmb_session where logout = true or erstdatum < current_timestamp - interval '1 day';\""
su - postgres -c "psql -d limbasdb -tAc 'select count(*) from lmb_session;'"
6
DELETE 0
6

核对表规模(升级/恢复后验证用,实测 152):

su - postgres -c "psql -d limbasdb -tAc \"select count(*) from pg_tables where schemaname='public';\""
152

6.4 SQL 直改配置的生效方式

Web 配置中心(index.php?action=setup_umgvar,实测登录态 GET 返回 200、111447 字节)读写的不外乎 lmb_umgvar.norm,SQL 直改同样生效,区别只在生效时机:多数界面类变量下次请求即读新值,而 update_mode(id=134)这类流程变量改完立刻改变登录行为——第八章的补丁重放演练就是用 UPDATE lmb_umgvar SET norm='background' WHERE id=134 打开后台自动升级开关的,实测回显:

 id  |    norm    
-----+------------
 134 | background
(1 row)

UPDATE 1

七、备份与恢复实操

💡 技术拓展:本文涉及三种备份形态,能力边界不同——

形态命令/入口覆盖恢复方式
应用内备份(DATA.gz)Web 备份页 / cli.php Backup库数据 + 文件结构元数据(应用语义)应用恢复页
pg_dump 逻辑备份pg_dump -f xx.sql全库 SQL(152 表含结构)psql -f 到任意同名版本库
文件级备份tar 打包 dependent/附件/本地文件/配置解包回原位

生产建议三者都做:DATA.gz 供应用内恢复与审计(有 lmb_history_backup 台账),pg_dump 是跨版本迁移与灾难恢复的兜底,dependent/ 目录 tar 补上附件文件面。

7.1 Web 管理备份入口

登录态下两个备份页 action 实测均可达(页面为 SPA 壳,功能在页内):

curl -s -b /tmp/lmb_cookie.txt -o /dev/null -w "GET index.php?action=setup_backup_man -> %{http_code}, %{size_download} bytes\n" http://localhost/index.php?action=setup_backup_man
curl -s -b /tmp/lmb_cookie.txt -o /dev/null -w "GET index.php?action=setup_backup_hist -> %{http_code}, %{size_download} bytes\n" http://localhost/index.php?action=setup_backup_hist
GET index.php?action=setup_backup_man -> 200, 111451 bytes
GET index.php?action=setup_backup_hist -> 200, 111452 bytes

Web 通道产物属主为 www-data(对照 4.3 节 CLI 产物属主 root,可凭属主区分通道来源):

-rw-r--r-- 1 www-data www-data 171592 Nov  2 22:41 26_11_02_41_26.DATA.gz

7.2 命令行全量备份

即 4.3 节 cli.php Backup -c 实测,产物 26_11_03_02_35.DATA.gz(522064 字节)与台账第 3 行 DATA2611030235 相互印证。要点:backupTarget 必须给绝对路径且执行用户对目标目录可写;backupAlive=7 表示保留 7 天。

7.3 pg_dump 逻辑备份

用应用角色走 TCP 全库导出:

mkdir -p /opt/limbas/backup
PGPASSWORD=q1w2e3r4 pg_dump -h 127.0.0.1 -U limbas -d limbasdb -f /opt/limbas/backup/limbasdb-$(date +%F).sql
echo "exit=$?"
ls -l /opt/limbas/backup/
grep -c '^CREATE TABLE' /opt/limbas/backup/limbasdb-$(date +%F).sql
head -5 /opt/limbas/backup/limbasdb-$(date +%F).sql

实测回显(2137290 字节、152 张表与 6.3 节表数一致;limbas 角色对自己库有完整读权限即可导出,无需 postgres 超管):

2026-11-03 02:05:10
exit=0
total 2088
-rw-r--r-- 1 root root 2137290 Nov  3 02:05 limbasdb-2026-11-03.sql
152
--
-- PostgreSQL database dump
--

\restrict YwK6beusETFkGLwdFKqSMWJ22c7OcqWwuHiXXnfpI9dOv2lBur2ekXgjAX46WTU

7.4 恢复演练

恢复到独立库 limbas_restore 验证备份可用性,不动生产。第一步清理目标库,实测该库不存在时的回显(首次演练属正常,直接建库即可):

su - postgres -c "dropdb limbas_restore"
su - postgres -c "createdb -O limbas limbas_restore"
echo "createdb exit=$?"
dropdb: error: database removal failed: ERROR:  database "limbas_restore" does not exist
createdb exit=0

导入并验证(psql -f 全量执行,实测 exit=0、日志 0 个 error):

PGPASSWORD=q1w2e3r4 psql -h 127.0.0.1 -U limbas -d limbas_restore -f /opt/limbas/backup/limbasdb-$(date +%F).sql > /tmp/restore.log 2>&1
echo "psql -f exit=$?"
grep -ciE 'error' /tmp/restore.log
psql -f exit=0
0

对象数逐项与生产比对(实测恢复库 152 表 / 143 配置 / 3 用户 / 4 作业,与生产库完全一致):

su - postgres -c "psql -d limbas_restore -tAc \"select count(*) from pg_tables where schemaname='public';\""
su - postgres -c "psql -d limbas_restore -tAc 'select count(*) from lmb_umgvar;'"
su - postgres -c "psql -d limbas_restore -tAc 'select count(*) from lmb_userdb;'"
su - postgres -c "psql -d limbas_restore -tAc 'select count(*) from lmb_crontab;'"
152
143
3
4

生产恢复即:createdb -O limbas limbasdb + 导入 dump + 解回 dependent/ 文件目录 + systemctl reload apache2。跨版本恢复务必先确认 lmb_dbpatch 与代码版本关系(见第八章),库结构落后时登录会自动触发补丁补齐。

7.5 备份保留与官方种子包

dependent/BACKUP/ 里除轮转的 *.DATA.gz 外,还有官方安装器随带的两个种子包 clean.tar.gz(174536 字节,空库模板)与 demo.tar.gz(518754 字节,示例业务数据),属主 www-data、时间戳即装机日(Aug 12 12:19/12:20),不要当业务备份误删,但可另作「重置演示数据」的恢复源。

八、升级实操(整包替换与补丁重放演练)

Limbas 升级没有专用命令,机制是「代码整包替换 + 数据补丁自动重放」:登录会话触发 Updater::checkVersion(),比较代码版本(limbas_src/lib/version.inf)与库版本(lmb_dbpatch 最大行),库落后即按 admin/tools/update/updates/ 下的补丁类(实测存在 Update5m0.php ~ Update7m1.php 共 8 个 minor 类)逐级补到最新。本节用 7.1.15 官方包做一次完整的替换+补丁重放演练,跨小版本升级流程与回显形态相同。

8.1 升级判据源码

Updater::checkVersion() 核心逻辑实测(limbas_src/admin/tools/update/Updater.php 节选):

$versions = self::getVersions();
$updateNeeded = version_compare($versions['source'], $versions['db'], '>');

if (!$noAutoUpdate && $updateNeeded && array_key_exists('update_mode', $umgvar) && $umgvar['update_mode'] === 'background') {
    return self::runUpdate();
}

if ($updateNeeded && $redirect) {
    if ($LINK['setup_update']) {
        header('Location: main_admin.php?action=setup_update');
    } else {
        header('Location: main.php?action=maintenance');
    }
    exit(0);
}

即:update_mode=background 时登录自动补补丁;foreground(默认,见 6.3 实测值)时重定向到升级页/维护页。getVersions() 的库版本 SQL 就是 SELECT REVISION,VERSION,MAJOR FROM LMB_DBPATCH ORDER BY MAJOR DESC, VERSION DESC, REVISION DESC。

8.2 升级前三件套备份

按第七章:pg_dump 全库(limbasdb-2026-11-03.sql,2137290 字节)+ 应用内 Backup 类 DATA.gz(26_11_03_02_35.DATA.gz)+ 代码目录快照:

cp -a /opt/limbas/openlimbas-7.1.15 /opt/limbas/openlimbas-7.1.15.bak-$(date +%F-%H%M)

实测快照目录就位(后续回滚直接 mv 回来):

/opt/limbas/openlimbas-7.1.15.bak-2026-11-03-0206

8.3 官方包下载与校验

cd /opt/limbas
curl -sSL -w "HTTP %{http_code}, %{size_download} bytes, %{time_total}s\n" \
  -o openlimbas-7.1.15-new.tar.gz \
  https://github.com/limbas/limbas/releases/download/v7.1.15/openlimbas-7.1.15.tar.gz
sha256sum openlimbas-7.1.15-new.tar.gz openlimbas-7.1.15.tar.gz

实测回显(GitHub 官方源直连可用、2.4 秒完成,未发生代理降级;两个包 sha256 一致,与首次安装留档相同):

HTTP 200, 11667224 bytes, 2.372642s
052f503973129ddb673209344d650132da2d357eac08a374fdf6ee5fb0e7ead5  openlimbas-7.1.15-new.tar.gz
052f503973129ddb673209344d650132da2d357eac08a374fdf6ee5fb0e7ead5  openlimbas-7.1.15.tar.gz

8.4 解包与整包替换(含一次真实事故)

mkdir -p upgrade-staging && tar xzf openlimbas-7.1.15-new.tar.gz -C upgrade-staging
ls upgrade-staging/

实测回显——注意:官方 tar 包没有顶层目录,解出来包根直接就是应用根(limbas_src、public、dependent、vendor 平铺),不存在 upgrade-staging/openlimbas-7.1.15/ 这层:

LICENSE
Limbarine.png
README.md
composer.json
dependent
limbas_src
package.json
public
vendor

此时若沿用「先 mv 走旧目录、再 mv 新目录就位」的惯性写法,第二步会失败而站点已经没了——本次演练真实踩中,实测回显:

mv: cannot stat 'upgrade-staging/openlimbas-7.1.15': No such file or directory
cp: cannot create directory '/opt/limbas/openlimbas-7.1.15/dependent/': No such file or directory
swap exit=1
POST login -> 403, 274 bytes
<title>403 Forbidden</title>

修复(staging 本身就是新根,直接改名就位;再把旧 dependent/ 全量迁进新树保住数据库连接配置与备份产物):

mv /opt/limbas/upgrade-staging /opt/limbas/openlimbas-7.1.15
cp -a /opt/limbas/openlimbas-7.1.15.bak-2026-11-03-0206/dependent/. /opt/limbas/openlimbas-7.1.15/dependent/
chown -R www-data:www-data /opt/limbas/openlimbas-7.1.15/dependent
systemctl reload apache2

替换后验证(数据库连接文件 md5 与替换前一致 ddb4089c1a695a11d5fc241708044cc7,登录恢复 200、版本号正确、备份产物目录完整):

fix exit=0
ddb4089c1a695a11d5fc241708044cc7  /opt/limbas/openlimbas-7.1.15/dependent/inc/include_db.lib
POST login -> 200, 111440 bytes
<title>Limbas Enterprise Unifying Framework V 7.1.15</title>
-rw-r--r-- 1 www-data www-data 522064 Nov  3 02:02 26_11_03_02_35.DATA.gz
-rw-r--r-- 1 www-data www-data 521930 Nov  2 23:24 26_11_03_24_29.DATA.gz
-rw-r--r-- 1 www-data www-data 522043 Nov  2 23:24 26_11_03_24_37.DATA.gz

⚠️ 升级替换铁律:新包解出的 dependent/ 是出厂默认(数据库连接为空、无 LMB_ENC_KEY),必须把旧 dependent/ 内容迁移进新树(或替换前先把旧 dependent/inc/include_db.lib 单独备份再覆盖回去),否则站点立刻连不上库。include_db.lib 中的 LMB_ENC_KEY 一旦更换,历史加密字段将无法解密。

8.5 补丁重放演练(模拟「代码新、库旧」)

真跨版本升级时新代码自带更高 version.inf,登录即触发补丁。演练做法:保持代码 7.1.15,人为把库版本退回 7.1.14(删台账顶行),并打开后台自动升级开关:

su - postgres -c "psql -d limbasdb -c \"update lmb_umgvar set norm='background' where id=134 returning id, norm;\""
su - postgres -c "psql -d limbasdb -c 'delete from lmb_dbpatch where id=726 returning id, major, version, revision, status;'"
su - postgres -c "psql -d limbasdb -c 'select id, major, version, revision from lmb_dbpatch order by id desc limit 2;'"

实测回显(库版本降到 7.1.14):

 id  |    norm    
-----+------------
 134 | background
(1 row)

UPDATE 1
 id  | major | version | revision | status 
-----+-------+---------+----------+--------
 726 |     7 |       1 |       15 | t
(1 row)

DELETE 1
 id  | major | version | revision 
-----+-------+---------+----------
 725 |     7 |       1 |       14
 724 |     7 |       1 |       13
(2 rows)

登录触发 Updater:

curl -s -c /tmp/lmb_cookie3.txt -o /tmp/login3.html -w "POST login -> %{http_code}, %{size_download} bytes\n" -d 'username=admin&password=q1w2e3r4' http://localhost/index.php

实测回显(200 但只有 659 字节、标题版本号被清空——这是补丁执行中的中间页,正是 foreground 模式下用户会看到的升级/维护形态):

POST login -> 200, 659 bytes
<title>Limbas Enterprise Unifying Framework V </title>

验证补丁自动重放结果(726 行以 status=t 回到台账、总数恢复 724):

su - postgres -c "psql -d limbasdb -c 'select id, major, version, revision, status, left(msg,60) as msg from lmb_dbpatch order by id desc limit 3;'"
su - postgres -c "psql -d limbasdb -tAc 'select count(*) from lmb_dbpatch;'"
 id  | major | version | revision | status | msg 
-----+-------+---------+----------+--------+-----
 726 |     7 |       1 |       15 | t      | 
 725 |     7 |       1 |       14 | t      | 
 724 |     7 |       1 |       13 | t      | 
(3 rows)

724

恢复 foreground 并复验登录(回到 111440 字节完整界面、版本号齐全,升级闭环):

 id  |    norm    
-----+------------
 134 | foreground
(1 row)

UPDATE 1
POST login -> 200, 111440 bytes
<title>Limbas Enterprise Unifying Framework V 7.1.15</title>

补丁执行期间 error log 有一条 Undefined array key "update_check" Warning(Updater.php:43 读联网检查开关的键名与 umgvar 实际键不完全一致,7.x 遗留噪音,不阻断补丁,判读见 9.9)。

8.6 升级标准流程清单与回滚

  1. 三件套备份:pg_dump + cli.php Backup + cp -a 代码目录(8.2);
  2. 下载新包并 sha256sum 校验(8.3);
  3. 解包后先看结构:官方 tar 无顶层目录,staging 即新根(8.4 事故实录);
  4. 旧目录 mv 改名 → 新树就位 → 旧 dependent/ 全量迁入新树 → chown -R www-data:www-data dependent → systemctl reload apache2;
  5. 管理员登录触发补丁(background 自动补 / foreground 走升级页),核对 lmb_dbpatch 顶行与新版本一致、version.inf 一致(8.5);
  6. 验证:登录页 200、GET /admin/jobs/1/run 200、error log 无新增 FATAL;
  7. 回滚:新树 mv 走、.bak-* 目录 mv 回来、reload;库侧若补丁已执行且不可逆,用 7.4 节 dump 恢复。

九、常见问题排障实录

以下问题全部在本环境真实发生并定位,错误回显逐行照抄。

9.1 psql 直连报 Peer authentication failed / role "root" does not exist

现象见 6.1 实测。定位:pg_hba.conf 对 local socket 的 postgres 行是 peer(要求 OS 同名用户),root 无对应角色。解决:su - postgres -c "psql ..." 或应用角色走 TCP PGPASSWORD=q1w2e3r4 psql -h 127.0.0.1 -U limbas。不建议为图方便把 local 改成 trust。

9.2 查配置表报 relation "umgvar" does not exist

现象:select * from umgvar; 报错。定位:Limbas 系统表全部带 lmb_ 前缀(库内 152 表实测均 lmb_* 或 demo 业务表名)。解决:查 lmb_umgvar。

9.3 备份作业配了参数却不出产物

现象:Backup 作业 config 写 "backupMedia" 时备份不落目标目录、也不报错。定位:Backup.php 识别的键是 backupMedium(medium 不是 media),错键被静默忽略。解决:键名改为 backupMedium;验证方法以 4.3 节实测为准——-c 传 backupMedium 后 exit=0、BACKUP 目录新增 DATA.gz 产物且 lmb_history_backup 多一行台账,三证齐全才算配对。

9.4 GET /admin/jobs 返回 Server Error 页

现象与完整异常堆栈见 5.4:JobController::index() 返回 string 触发 ControllerDoesNotReturnResponseException,7.1.15 已知缺陷。解决:列表核对走 lmb_crontab SQL(4.2 实测)或 Web 作业页;单作业 PUT/GET run/DELETE 接口不受影响(5.2/5.3 实测 200)。

9.5 cli.php 报 column "ind_7" does not exist

现象(早期实测原文):php cli.php 4 打印 Error: ERROR: column "ind_7" does not exist ... SELECT COUNT(*) AS ANZAHL FROM versandart WHERE IND_7 = FALSE AND DEL = FALSE。定位:作业 4 的 config 指定索引 tab 13(versandart)的字段 7/8,而 demo 库该表没有 ind_7/ind_8 列——作业配置与表结构脱钩。解决:清空该作业字段配置(config 置 [])或改配真实字段;修复后实测 php cli.php 4 仅剩 Warning、exit=0、无 SQL 错误(4.2 节回显)。

9.6 INDIZE 作业尾部 INSERT INTO LMB_INDIZE_HISTORY 语法错误

现象:作业 1 跑完进度 HTML 后打印 ERROR: syntax error at or near ")" ... VALUES(1,'Filestructure Index',TRUE,0.0032,'',0,0,)(CLI 与 REST run 两通道同现,见 4.2/5.2)。定位:7.1.15 索引历史写入 SQL 拼接多一个尾逗号,属上游代码缺陷;索引动作本身已完成(USED_TIME 已产生)。解决:功能不受影响可忽略;需要干净台账时给 LMB_INDIZE_HISTORY 建触发器代写或升级后关注上游修复,勿手改 vendor 外的 legacy 源码。

9.7 PUT /admin/jobs/{id} 报 TypeError

两种实测形态(5.3 节):只传部分键 → Cannot assign null to property CronJob::$cronMinutes of type string;backup_alive= 传空串 → Cannot assign string to property CronConfig::$backupAlive of type int。解决:x-www-form-urlencoded 带全 14 个键、backup_alive 给数字(0 起步),实测 {"success":true,...}。

9.8 apt 装 cron 后服务没起来

现象:安装回显 invoke-rc.d: policy-rc.d denied execution of start.、随后 systemctl is-active cron 为 inactive(4.5 实测)。定位:系统存在 policy-rc.d 策略时包脚本不自动启动服务。解决:systemctl enable cron + systemctl restart cron,用 journalctl -u cron 确认 (CRON) INFO (pidfile fd = 3)。

9.9 满屏 PHP Warning 怎么判读

CLI/Web 日志大量 Undefined array key/Trying to access array offset on null/Undefined variable Warning(4.2/4.4/8.5 多处实测),是 7.x legacy 代码在 PHP 8.3 严格上下文下的噪音,判读原则:看 exit code 与产物/台账行(如 exit=0 + 新 DATA.gz + lmb_history_backup 新行 = 成功),只有 Error:/FATAL/limbas.ERROR 前缀行才是真故障。

9.10 升级替换后站点 403

现象与修复全程见 8.4:DocumentRoot 指向的目录被 mv 走后新树没就位(tar 无顶层目录导致 mv 失败)。解决:按 8.4 修复三连(staging 改名就位 + dependent 迁移 + reload);预防:升级脚本先 ls 解包结构再动旧目录,且永远保留 .bak-* 目录直到验证通过。

十、注意事项

  • 产物属主:root 跑 cli.php Backup 产出的 DATA.gz 属 root,Web 通道产物属 www-data;混用时注意后续 Web 恢复页可能读不了 root 属主文件,统一用 chown www-data:www-data 或固定以 www-data 身份跑(su -s /bin/bash www-data -c "php ...")。
  • include_db.lib 权限:内含数据库口令与 LMB_ENC_KEY,保持 640 以内权限、绝不进 Web 可达路径(它在 dependent/inc/,.htaccess 已只放行 public/)。
  • REST 会话:/admin/jobs 复用登录会话 Cookie,Cookie 过期后接口返回登录页 HTML(非 401),脚本里应校验响应体而非只看状态码。
  • 升级开关:生产建议保持 update_mode=foreground(默认),升级窗口内人工确认补丁;background 适合自动化流水线但要配合升级前备份。
  • 下载渠道:本文 GitHub 官方 Release 直连实测可用(8.3,2.4s),未使用代理降级;若你所在网络官方源不可达,按「官方源 → 国内源 → 代理」降级链处理并在变更单注明。
  • 备份三件套常态化:crontab 里除业务作业外,加一条每日 pg_dump + find /opt/limbas/backup -mtime +14 -delete 的轮转,与 backupAlive 应用内轮转互补。

十一、相关文档

十二、参考链接

Limbas 7.1.15 常用运维命令与排障实操 - Quzuo