WiKiss 0.3 常用 CLI 命令与运维实操

WiKiss 0.3 无自带管理 CLI,本文用通用 Linux 文件工具与 PHP 工具实测覆盖:wiki 文件结构巡检、grep 全库检索、tar 整库备份与恢复演练、historique 误删找回、opcache 与日志维护、口令权限文件维护,并复现修复 5 类真实故障。

WiKissWikiPHP自托管知识库2026/11/03

WiKiss 0.3 常用 CLI 命令与运维实操

目标:WiKiss 0.3 没有自带任何管理 CLI——它的"运维命令体系"就是 Linux 通用文件工具 + PHP 自带工具的组合。本文在 Ubuntu 22.04 + Apache 2.4.52 + PHP 8.1.2 实测环境上,把 wiki 文件结构巡检、全库检索、tar 整库备份与恢复演练、误删页面从 historique 找回、opcache 与日志维护、口令与权限文件维护、升级兼容巡检逐条实测照抄,并复现 5 类真实故障(historique 目录 777、_config.php 权限被 opcache 掩盖、pages 属主错乱、补丁转写笔误致空表格、tar 中文清单转义)。

WiKiss 是"文件即 wiki":无数据库、无守护进程、无 CLI 入口,页面是 pages/*.txt、历史是 historique/<页面名>/*.bak、配置是 _config.php(机制详见 《WiKiss 0.3 核心使用与鉴权配置详解》 与 《WiKiss 0.3 配置参数详解》)。因此运维的全部动作都落在文件系统上,装好站点后的日常维护、备份恢复、故障排查,本文全部给出可直接执行的命令与实测回显。安装与 PHP 8.1 兼容修复见 《Ubuntu 22.04 安装 WiKiss 0.3 指南(PHP 8.1 兼容修复实录)》。

一、环境信息

项目取值
操作系统Ubuntu 22.04(Jammy)
Web 服务器Apache 2.4.52(apache2)
PHP8.1.2-1ubuntu2.28(libapache2-mod-php8.1 + php8.1-mbstring)
WiKiss0.3(官方包 0.3.tgz,25,159 字节,md5sum = 2a657010027329570824175f28ecb170)
部署目录/var/www/wikiss(属主 www-data:www-data)
站点地址http://wiki.example.com/
数据目录pages/(页面)、historique/(历史备份)
编辑口令$PASSWORD = "Q1w2E3r4T!"(第七章演示轮换为 S3cur3Pass!2)
运行生成物站点根 rss.xml(wkp_Rss 插件在保存时写出)
日志/var/log/apache2/wikiss-access.log、wikiss-error.log(vhost 显式指定)

⚠️ 注意:本文所有命令假定站点已按 《Ubuntu 22.04 安装 WiKiss 0.3 指南(PHP 8.1 兼容修复实录)》 完成安装并打满 9 处 PHP 8.1 兼容补丁。未打补丁时搜索/保存会 500,运维演练无从谈起。

二、工具概览

WiKiss 官方发布包不含 wikiss-cli、bin/ 或任何管理脚本,运维工具箱全部是系统通用命令:

运维目的命令/工具对应章节
结构与属主巡检ls -la、stat、find、du四
全库内容检索grep -r、find -newermt5.1
整库备份/恢复tar、sha256sum5.2 / 5.3
误删页面找回ls -t + tail -n +3(读 historique/*.bak)5.4
PHP 语法体检php -l6.1
缓存刷新apache2ctl graceful / stop+start6.2
日志分析tail、awk、grep6.4
口令与权限维护sed、chown、chmod七

所有命令用 root(或 sudo)在服务器上执行;凡以 root 身份触碰过站点文件,收尾都要把属主交还 www-data(见 7.3 实测)。

三、前置条件

3.1 站点可用性验证

curl -s -o /dev/null -w 'HOME:%{http_code}\n' http://wiki.example.com/
curl -s -o /dev/null -w 'SEARCH:%{http_code}\n' 'http://wiki.example.com/?action=search&query=Aide'
curl -s -o /dev/null -w 'RSS:%{http_code}\n' http://wiki.example.com/rss.xml

实测回显(刚装完、尚未保存过任何页面时):

HOME:200
SEARCH:200
RSS:404

RSS:404 不是故障:rss.xml 是 wkp_Rss 插件在第一次保存页面时才写出的静态文件(生成与清理见 6.3)。

3.2 准备演示数据

通过 HTTP(带口令)创建 4 个测试页,模拟已使用一段时间的 wiki:

curl -s -X POST 'http://wiki.example.com/' \
  --data-urlencode 'page=TestS9' \
  --data-urlencode 'content=| A | B |
| 1 | 2 |' \
  --data-urlencode 'sc=Q1w2E3r4T!' \
  -o /dev/null -w 'TestS9:%{http_code}\n'
curl -s -X POST 'http://wiki.example.com/' \
  --data-urlencode 'page=ServerNotes' \
  --data-urlencode 'content=本机 Nginx 反代配置要点:proxy_pass 指向 127.0.0.1:80。' \
  --data-urlencode 'sc=Q1w2E3r4T!' \
  -o /dev/null -w 'ServerNotes:%{http_code}\n'
curl -s -X POST 'http://wiki.example.com/' \
  --data-urlencode 'page=DeployChecklist' \
  --data-urlencode 'content=部署检查清单:php -l 通过、pages 可写、historique 非空。' \
  --data-urlencode 'sc=Q1w2E3r4T!' \
  -o /dev/null -w 'Deploy:%{http_code}\n'
curl -s -X POST 'http://wiki.example.com/' \
  --data-urlencode 'page=运维手册' \
  --data-urlencode 'content=这是中文标题页面,验证 UTF-8 文件名全链路。' \
  --data-urlencode 'sc=Q1w2E3r4T!' \
  -o /dev/null -w 'CN:%{http_code}\n'

实测回显(302 = 保存成功并跳转页面视图,机制见 《WiKiss 0.3 核心使用与鉴权配置详解》):

TestS9:302
ServerNotes:302
Deploy:302
CN:302

最后一个页面名为中文 运维手册,用于贯穿全文的中文文件名链路验证(URL 中需百分号编码,如 ?page=%E8%BF%90%E7%BB%B4%E6%89%8B%E5%86%8C)。

四、wiki 文件结构 CLI 巡检

WiKiss 的健康状态全部可以从文件层面判定。本节命令构成日常巡检清单,建议按序执行。

4.1 站点目录与属主

ls -la /var/www/wikiss/

实测回显:

total 100
drwxr-xr-x 5 www-data www-data  4096 Nov  2 18:17 .
drwxr-xr-x 4 root     root      4096 Nov  2 18:15 ..
-rw-r--r-- 1 www-data www-data 17987 Oct 30  2007 COPYING
-rwxr-xr-x 1 www-data www-data  1155 Nov  2 18:16 _config.php
drwxr-xr-x 8 www-data www-data  4096 Nov  2 18:17 historique
-rwxr-xr-x 1 www-data www-data 26433 Nov  2 18:16 index.php
drwxr-xr-x 2 www-data www-data  4096 Nov  2 18:17 pages
drwxr-xr-x 2 www-data www-data  4096 Oct 30  2007 plugins
-rw-r--r-- 1 www-data www-data  2054 Nov  2 18:17 rss.xml
-rwxr-xr-x 1 www-data www-data  2422 Oct 30  2007 style.css
-rwxr-xr-x 1 www-data www-data  1297 Oct 30  2007 template.html
-rw-r--r-- 1 www-data www-data 12848 Oct 30  2007 tigerwiki2wikiss.php

解读:官方包文件的时间戳停在 2007-10-30(包内 mtime),凡是显示近期时间的文件都是被改过或新生成的——_config.php(配置改动)、index.php(打过补丁)、rss.xml(运行生成)。巡检时"哪些文件被动过"一眼可见。

stat -c "%A %U:%G %n" /var/www/wikiss/_config.php /var/www/wikiss/index.php \
  /var/www/wikiss/pages /var/www/wikiss/historique /var/www/wikiss/plugins

实测回显:

-rwxr-xr-x www-data:www-data /var/www/wikiss/_config.php
-rwxr-xr-x www-data:www-data /var/www/wikiss/index.php
drwxr-xr-x www-data:www-data /var/www/wikiss/pages
drwxr-xr-x www-data:www-data /var/www/wikiss/historique
drwxr-xr-x www-data:www-data /var/www/wikiss/plugins

判定标准:配置、入口、三个数据目录的属主都应是 www-data(Web 进程要写 pages/ 与 historique/)。_config.php 带执行位(755)是官方包习惯,无害;介意可 chmod 644,但注意 10.2 的 opcache 教训——改权限后必须重启 Apache 才确定生效。

4.2 pages/ 页面文件巡检

find /var/www/wikiss/pages -name "*.txt" | wc -l
7

页面总数 = .txt 文件数(页面名即文件名,无索引表可查,wc -l 就是权威计数)。

find /var/www/wikiss/pages -name "*.txt" -printf "%s %p\n" | sort -n

实测回显:

19 /var/www/wikiss/pages/TestS9.txt
61 /var/www/wikiss/pages/运维手册.txt
69 /var/www/wikiss/pages/ServerNotes.txt
72 /var/www/wikiss/pages/DeployChecklist.txt
2138 /var/www/wikiss/pages/Accueil.txt
2461 /var/www/wikiss/pages/AideTables.txt
3694 /var/www/wikiss/pages/Aide.txt
find /var/www/wikiss/pages -type f -size 0 -print

实测回显为空(体检通过)。这条命令是必做项:PHP 8 未打补丁的安装在保存时会先 fopen("w") 清空文件再崩溃,留下 0 字节页面(完整事故链见 《Ubuntu 22.04 安装 WiKiss 0.3 指南(PHP 8.1 兼容修复实录)》 修复 6/7)。0 字节页面在打过补丁的环境里浏览不会报错(fread 守卫直接渲染空内容),静默地丢内容,只能靠这条命令扫出来。

find /var/www/wikiss/pages -name "*.txt" -newermt "2026-11-02" -printf "%TY-%Tm-%Td %TH:%TM %p\n"

实测回显(按修改时间过滤"最近动过的页面",与 Web 端 ?action=recent 同源——都看 mtime):

2026-11-02 18:17 /var/www/wikiss/pages/TestS9.txt
2026-11-02 18:17 /var/www/wikiss/pages/DeployChecklist.txt
2026-11-02 18:17 /var/www/wikiss/pages/ServerNotes.txt
2026-11-02 18:17 /var/www/wikiss/pages/运维手册.txt

4.3 historique/ 备份结构与 .bak 格式

ls -la /var/www/wikiss/historique/

实测回显:

total 32
drwxr-xr-x 8 www-data www-data 4096 Nov  2 18:17 .
drwxr-xr-x 5 www-data www-data 4096 Nov  2 18:17 ..
drwxr-xr-x 2 www-data www-data 4096 Oct 30  2007 Accueil
drwxr-xr-x 2 www-data www-data 4096 Oct 30  2007 Aide
drwxrwxrwx 2 www-data www-data 4096 Nov  2 18:17 DeployChecklist
drwxrwxrwx 2 www-data www-data 4096 Nov  2 18:17 ServerNotes
drwxrwxrwx 2 www-data www-data 4096 Nov  2 18:17 TestS9
drwxrwxrwx 2 www-data www-data 4096 Nov  2 18:17 运维手册

一眼可见两种权限:官方包自带的 Accueil/Aide 目录是 755,运行期新建的备份目录全是 777——这是 WiKiss 源码写死的(复现与修复见 10.1)。

find /var/www/wikiss/historique -type f -name "*.bak" | sort

实测回显:

/var/www/wikiss/historique/Accueil/20070912-1556.bak
/var/www/wikiss/historique/Aide/20070912-1846.bak
/var/www/wikiss/historique/DeployChecklist/20261102-1817.bak
/var/www/wikiss/historique/ServerNotes/20261102-1817.bak
/var/www/wikiss/historique/TestS9/20261102-1817.bak
/var/www/wikiss/historique/运维手册/20261102-1817.bak

备份文件名格式 Ymd-Hi.bak(分钟粒度,含 $LOCAL_HOUR 偏移)。.bak 不是页面文件的原样拷贝,实测逐字节看头部:

cat -A /var/www/wikiss/historique/TestS9/20261102-1820.bak
$
// 2026-11-02 18:20 /  127.0.0.1$
| A | B |$
| 1 | 2 |$
| 3 | 4 |$
$

格式 = 空行 + // 时间 / 保存者IP 头 + 该次保存的内容 + 尾部空行。这决定了 5.4 找回命令必须剥掉头部两行。

两个源码级语义(index.php 保存分支实测取证):

if ($_POST["content"] == "")
   unlink($PAGES_DIR . $PAGE_TITLE . ".txt");
if (! $file = @fopen($complete_dir_s . date("Ymd-Hi", mktime(date("H") + $LOCAL_HOUR)) . ".bak", "a"))
  1. 删除页面不写备份:空内容提交走 unlink 分支,不产生新 .bak——误删后能找回的版本是"最后一次保存"写入的那个 .bak;
  2. 备份以追加模式("a")写入,文件名只到分钟:同一分钟内多次保存会追加进同一个 .bak。实测:
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=DeployChecklist' \
  --data-urlencode 'content=清单 v2' --data-urlencode 'sc=Q1w2E3r4T!' -o /dev/null -w 'SAVE-A:%{http_code}\n'
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=DeployChecklist' \
  --data-urlencode 'content=清单 v3' --data-urlencode 'sc=Q1w2E3r4T!' -o /dev/null -w 'SAVE-B:%{http_code}\n'
LATEST=$(ls -t /var/www/wikiss/historique/DeployChecklist/*.bak | head -1)
echo "该 .bak 内快照条数: $(grep -c '^// ' "$LATEST")"
cat "$LATEST"
SAVE-A:302
SAVE-B:302
该 .bak 内快照条数: 2

// 2026-11-02 18:19 /  127.0.0.1
清单 v2


// 2026-11-02 18:19 /  127.0.0.1
清单 v3

批量脚本每分钟狂存页面时,历史粒度会塌缩成"一分钟一条",Web 端历史页(?action=history)同样只显示这一个合并文件——需要细粒度历史时,把批量保存节流到每分钟一次以上。

4.4 属主与权限体检

find /var/www/wikiss ! -user www-data

实测回显为空(全部归 www-data)。凡 root 用 cp/重定向等方式覆盖过站点文件,这条命令就会点名漏网文件(sed -i 例外,见 7.3 实测)。

find /var/www/wikiss -type f -perm /o+w
find /var/www/wikiss -type d -perm /o+w

实测回显:

(文件:空)
/var/www/wikiss/historique/运维手册
/var/www/wikiss/historique/ServerNotes
/var/www/wikiss/historique/TestS9
/var/www/wikiss/historique/DeployChecklist

全局可写目录如期命中——即 4.3 的 777 现象,处置见 10.1。

4.5 体积与游离文件巡检

du -sh /var/www/wikiss/pages /var/www/wikiss/historique /var/www/wikiss
32K	/var/www/wikiss/pages
52K	/var/www/wikiss/historique
100K	/var/www/wikiss

全站 100K 量级——备份不需要任何专门工具,tar 一次秒级完成(5.2)。

find /var/www/wikiss -maxdepth 1 -name "*.txt"

实测回显为空。这条命令查"站点根目录游离页面文件":配置失效时 $PAGES_DIR 未定义,页面会被写到站点根目录而不是 pages/(完整事故复现见 10.2),根目录出现 *.txt 即为异常信号。

五、页面批量操作

5.1 grep 全库检索

Web 端搜索(?action=search)走 PHP 正则逐页匹配;CLI 侧直接 grep,更快、支持完整正则、且不经过 PHP 8 兼容路径(未打补丁的安装 Web 搜索即 500,CLI 检索不受影响)。

grep -ril "nginx" /var/www/wikiss/pages/
/var/www/wikiss/pages/ServerNotes.txt

-r 递归、-i 忽略大小写、-l 只列文件名。带行号与内容:

grep -rn --include="*.txt" "proxy_pass" /var/www/wikiss/pages/
/var/www/wikiss/pages/ServerNotes.txt:1:本机 Nginx 反代配置要点:proxy_pass 指向 127.0.0.1:80。

按 Wiki 语法特征检索(例如"哪些页面含表格",表格行以竖线开头,语法见 《WiKiss 0.3 页面语法与常用操作详解》):

grep -rl "^ *|" /var/www/wikiss/pages/
/var/www/wikiss/pages/AideTables.txt
/var/www/wikiss/pages/TestS9.txt

中文内容检索同样直接:grep -rl "中文标题页面" /var/www/wikiss/pages/ 命中 运维手册.txt(文件与内容都是 UTF-8,grep 按字节匹配,无需任何 locale 特殊处理)。

5.2 tar 整库备份

WiKiss 的备份对象就是整个部署目录(页面 + 历史 + 配置 + 代码全在里面):

tar czf /root/wikiss-backup-$(date +%Y%m%d-%H%M%S).tar.gz -C /var/www wikiss
ls -la /root/wikiss-backup-*.tar.gz
sha256sum /root/wikiss-backup-20261102-181849.tar.gz

实测回显:

-rw-r--r-- 1 root root 26799 Nov  2 18:18 /root/wikiss-backup-20261102-181849.tar.gz
f5f239d392a71a46151616bc2e53118015d72f3b361b250265821ef6829302da  /root/wikiss-backup-20261102-181849.tar.gz

核对备份内容清单:

tar tzf /root/wikiss-backup-20261102-181849.tar.gz | head -8
echo "...(共 $(tar tzf /root/wikiss-backup-20261102-181849.tar.gz | wc -l) 个条目)"
wikiss/
wikiss/historique/
wikiss/historique/\350\277\220\347\273\264\346\211\213\345\206\214/
wikiss/historique/\350\277\220\347\273\264\346\211\213\345\206\214/20261102-1817.bak
wikiss/historique/ServerNotes/
wikiss/historique/ServerNotes/20261102-1817.bak
wikiss/historique/TestS9/
wikiss/historique/TestS9/20261102-1817.bak
...(共 33 个条目)

注意第三行:中文文件名被 GNU tar 默认转义成八进制(\350\277\220\347\273\264\346\211\213\345\206\214 即"运维手册")。数据没坏(解包后名字正常),只是清单不可读,用 --quoting-style=literal 按原样显示:

tar --quoting-style=literal -tzf /root/wikiss-backup-20261102-181849.tar.gz | grep "运维"
wikiss/historique/运维手册/
wikiss/historique/运维手册/20261102-1817.bak
wikiss/pages/运维手册.txt

💡 技术拓展:备份策略不止"整库 tar"一种。常见方案对比——

方案特点适用
tar 全量单档一次打包、sha256 可校验、恢复即解包库体量小(WiKiss 全站常 < 百 MB)、按天留档
rsync 增量同步只传差异,目标目录始终与源保持一致频繁备份、异地同步
git 版本化逐次提交、可追溯每页历史想把 historique/ 升级成真正的版本库

本文采用 tar 全量:实测环境整库仅 100K(4.5),单档 + 校验和是恢复演练(5.3)最简可验证的形态。

5.3 整库恢复演练

恢复演练的正确做法是先人为制造灾难,再按备份恢复,而不是"解包看一眼"。实测全流程:

# 1. 模拟 pages/ 与 historique/ 全毁(灾难演练,生产环境切勿直接照抄)
rm -rf /var/www/wikiss/pages /var/www/wikiss/historique
ls /var/www/wikiss/
COPYING
_config.php
index.php
plugins
rss.xml
style.css
template.html
tigerwiki2wikiss.php
# 2. 解到临时目录(绝不直接解回原位,先验货)
mkdir -p /tmp/wikiss-restore
tar xzf /root/wikiss-backup-20261102-181849.tar.gz -C /tmp/wikiss-restore
# 3. 回灌数据目录
cp -a /tmp/wikiss-restore/wikiss/pages /tmp/wikiss-restore/wikiss/historique /var/www/wikiss/
chown -R www-data:www-data /var/www/wikiss/pages /var/www/wikiss/historique
rm -rf /tmp/wikiss-restore
ls /var/www/wikiss/pages/
Accueil.txt
Aide.txt
AideTables.txt
DeployChecklist.txt
ServerNotes.txt
TestS9.txt
运维手册.txt
# 4. 恢复后验证:首页、普通页、中文页内容逐项核对
curl -s -o /dev/null -w 'HOME:%{http_code} ' http://wiki.example.com/
curl -s -o /dev/null -w 'ServerNotes:%{http_code}\n' 'http://wiki.example.com/?page=ServerNotes'
curl -s 'http://wiki.example.com/?page=%E8%BF%90%E7%BB%B4%E6%89%8B%E5%86%8C' | grep -c "UTF-8 文件名"
HOME:200 ServerNotes:200
1

要点:第 2 步先解到临时目录再挑选回灌,避免备份包里的旧代码覆盖线上已打补丁的 index.php(若整目录直接解回 /var/www,2007 年的原始代码会覆盖补丁版本——恢复时只回灌 pages/、historique/、必要时 _config.php 三类数据,代码文件单独核对)。第 3 步 chown 不可省:备份时的属主信息虽随 -a 保留,但恢复目标机器的 Web 进程用户不一定同名,落位后统一交还给实际运行用户。

5.4 误删页面从 historique 找回

场景:TestS9 编辑出 v2 后被误删(空内容提交,官方删除路径)。找回全程 CLI:

# 1. 先正常编辑产生 v2
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=TestS9' \
  --data-urlencode 'content=| A | B |
| 1 | 2 |
| 3 | 4 |' --data-urlencode 'sc=Q1w2E3r4T!' -o /dev/null -w 'SAVE2:%{http_code}\n'
# 2. 模拟误删:空内容提交
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=TestS9' \
  --data-urlencode 'content=' --data-urlencode 'sc=Q1w2E3r4T!' -o /dev/null -w 'DEL:%{http_code}\n'
ls /var/www/wikiss/pages/ | grep -c TestS9
SAVE2:302
DEL:302
0

页面文件已消失。确认备份仍在:

ls -la /var/www/wikiss/historique/TestS9/
cat "$(ls -t /var/www/wikiss/historique/TestS9/*.bak | head -1)"
total 16
drwxrwxrwx 2 www-data www-data 4096 Nov  2 18:19 .
drwxr-xr-x 8 www-data www-data 4096 Nov  2 18:17 ..
-rw-r--r-- 1 www-data www-data   56 Nov  2 18:17 20261102-1817.bak
-rw-r--r-- 1 www-data www-data   65 Nov  2 18:19 20261102-1819.bak

// 2026-11-02 18:19 /  127.0.0.1
| A | B |
| 1 | 2 |
| 3 | 4 |

最新 .bak(ls -t 取 mtime 最新)里就是被删前的 v2 内容。回灌(剥 4.3 实测的头部两行,sed -z 去尾部空行):

LATEST=$(ls -t /var/www/wikiss/historique/TestS9/*.bak | head -1)
tail -n +3 "$LATEST" | sed -z 's/\n*$//' > /var/www/wikiss/pages/TestS9.txt
chown www-data:www-data /var/www/wikiss/pages/TestS9.txt
cat -A /var/www/wikiss/pages/TestS9.txt
| A | B |$
| 1 | 2 |$
| 3 | 4 |

与原页面文件逐字节一致(保存时 trim() 过的正文本就没有尾部空行)。验证:

curl -s -o /dev/null -w 'GET:%{http_code} ' 'http://wiki.example.com/?page=TestS9'
curl -s -o /dev/null -w 'HISTORY:%{http_code}\n' 'http://wiki.example.com/?page=TestS9&action=history'
GET:200 HISTORY:200

找回完成,Web 历史页也能列出全部备份。两条红线:删除操作不写备份(4.3 源码取证),所以 historique/ 是误删唯一的救济来源,定期随 5.2 一起打包;找回后必须 chown,否则下次 Web 保存该页会栽权限(10.3)。Web 端另有 &action=history + restore=1 的人工恢复入口,行为见 《WiKiss 0.3 核心使用与鉴权配置详解》。

六、PHP 侧可执行维护

6.1 php -l 全库语法体检

改动 index.php/插件(打补丁、装第三方 wkp_*.php)之后、重载 Apache 之前,逐文件做语法校验:

php -l /var/www/wikiss/index.php
php -l /var/www/wikiss/_config.php
php -l /var/www/wikiss/tigerwiki2wikiss.php
php -l /var/www/wikiss/plugins/wkp_ListPlugins.php
php -l /var/www/wikiss/plugins/wkp_Rss.php
php -l /var/www/wikiss/plugins/wkp_Tables.php

实测回显:

No syntax errors detected in /var/www/wikiss/index.php
No syntax errors detected in /var/www/wikiss/_config.php
No syntax errors detected in /var/www/wikiss/tigerwiki2wikiss.php
No syntax errors detected in /var/www/wikiss/plugins/wkp_ListPlugins.php
No syntax errors detected in /var/www/wikiss/plugins/wkp_Rss.php
No syntax errors detected in /var/www/wikiss/plugins/wkp_Tables.php

⚠️ 注意:php -l 只保证"能解析",不保证"能运行"。10.4 的空表格故障里,正则缺闭合分隔符——php -l 全绿,错误只在运行期进 error.log。语法体检必须配合 6.4 的日志抽查。

6.2 opcache 与配置生效

Ubuntu 的 PHP 默认启用 OPcache,直接影响"改了文件多久生效":

ls /etc/php/8.1/apache2/conf.d/ | grep opcache
php -i | grep -E 'opcache.enable |opcache.enable_cli|validate_timestamps|revalidate_freq'

实测回显:

10-opcache.ini
opcache.enable => On => On
opcache.enable_cli => Off => Off
opcache.revalidate_freq => 2 => 2
opcache.validate_timestamps => On => On

生效规则(实测):

  • sed 改 _config.php 内容会更新 mtime,validate_timestamps=On + revalidate_freq=2 ⇒ 约 2 秒内自动生效,无需重启;
  • chmod/chown 不改 mtime,OPcache 不会感知——文件缓存照旧服务,这是 10.2 故障"看起来没事"的根源;
  • CLI 侧 opcache.enable_cli=Off,且 php -r 'var_dump(function_exists("opcache_reset"));' 实测返回 bool(true) 的同时 ini_get("opcache.enable_cli") 返回 "0"——CLI 的 OPcache 与 Apache 模块的 OPcache 是两块独立共享内存,命令行调用 opcache_reset() 清不掉 Web 侧缓存。

让 Apache 侧缓存确定失效的运维手段是重载/重启:

apache2ctl graceful    # 平滑重载,清空 OPcache 后继续服务

改动代码/权限后拿不准是否生效时,直接重启并核对页面特征字串(10.2 实测即用此法让故障显形):

apache2ctl stop; sleep 1; apache2ctl start
curl -s -o /dev/null -w 'HOME:%{http_code}\n' http://wiki.example.com/

6.3 运行生成物 rss.xml 的清理与再生

rss.xml 是站点根目录唯一的运行期生成文件(wkp_Rss 在每次保存页面时重写):

ls -la /var/www/wikiss/rss.xml
head -3 /var/www/wikiss/rss.xml
rm /var/www/wikiss/rss.xml
curl -s -o /dev/null -w 'RSS-after-rm:%{http_code}\n' http://wiki.example.com/rss.xml
-rw-r--r-- 1 www-data www-data 2054 Nov  2 18:19 /var/www/wikiss/rss.xml
<rss version="0.91">
<channel>
<title>WiKiss : le Wiki Kiss</title>
RSS-after-rm:404

删除它无数据损失,下一次任意页面保存即自动再生:

curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=TestS9' \
  --data-urlencode 'content=| A | B |
| 1 | 2 |
| 3 | 4 |' --data-urlencode 'sc=Q1w2E3r4T!' -o /dev/null -w 'SAVE-regen:%{http_code} '
curl -s -o /dev/null -w 'RSS-after-save:%{http_code}\n' http://wiki.example.com/rss.xml
SAVE-regen:302 RSS-after-save:200

除此之外 WiKiss 没有缓存目录、没有会话文件、没有临时落盘——"清缓存"在 WiKiss 运维里只涉及 6.2 的 OPcache 与这一节。

6.4 Apache 日志查看与访问统计

日志位置由 vhost 显式指定(安装篇第五章模板),巡检先看大小再看内容:

ls -la /var/log/apache2/ | grep wikiss
-rw-r--r-- 1 root root 2246 Nov  2 18:20 wikiss-access.log
-rw-r--r-- 1 root root 3645 Nov  2 18:20 wikiss-error.log

访问热点(哪些页面在被读):

awk '{print $7}' /var/log/apache2/wikiss-access.log | sort | uniq -c | sort -rn | head -5

实测回显:

     10 /
      6 /?page=TestS9
      2 /rss.xml
      2 /?page=%E8%BF%90%E7%BB%B4%E6%89%8B%E5%86%8C
      1 /?page=TestS9&action=history

错误日志排障主入口:

tail -3 /var/log/apache2/wikiss-error.log
grep -ci 'fatal' /var/log/apache2/wikiss-error.log

实测回显(tail 输出节选其中一行 + fatal 计数):

[Mon Nov 02 18:19:46.617867 2026] [php:warn] [pid 9466] [client 127.0.0.1:47590] PHP Warning:  preg_replace_callback(): No ending delimiter '/' found in /var/www/wikiss/plugins/wkp_Tables.php on line 49
0

判读口径:Fatal error 计数为 0 说明无致命问题;WiKiss 0.3 打完 9 处补丁后仍有两类良性 Warning 会持续刷日志(功能不受影响,属 2007 代码未穷尽的 PHP 8 规范化告警):

[Mon Nov 02 18:22:04.432500 2026] [php:warn] [pid 9466] [client 127.0.0.1:35902] PHP Warning:  Undefined array key "AutorisationWiKiss" in /var/www/wikiss/index.php on line 22
[Mon Nov 02 18:23:31.794429 2026] [php:warn] [pid 9466] [client 127.0.0.1:49848] PHP Warning:  Undefined variable $CONTENT_RSS in /var/www/wikiss/plugins/wkp_Rss.php on line 52

前者在无 Cookie 的每次保存请求出现,后者在 rss.xml 首次生成路径出现。运维基线:grep -ci 'fatal' 应为 0,Warning 数量突增则对照 10.4 检查是否又引入运行期正则/函数问题。

七、鉴权与权限文件维护

7.1 口令轮换

口令就是全站编辑权(无用户体系,机制见 《WiKiss 0.3 核心使用与鉴权配置详解》),轮换步骤实测:

sed -n '3p' /var/www/wikiss/_config.php
cp /var/www/wikiss/_config.php /root/_config.php.bak-$(date +%H%M%S)
sed -i 's/Q1w2E3r4T!/S3cur3Pass!2/' /var/www/wikiss/_config.php
sed -n '3p' /var/www/wikiss/_config.php
	$PASSWORD			=	"Q1w2E3r4T!";
	$PASSWORD			=	"S3cur3Pass!2";

验证旧口令被拒、新口令放行:

curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=AuthProbe' \
  --data-urlencode 'content=x' --data-urlencode 'sc=Q1w2E3r4T!' \
  -o /dev/null -w 'OLD:%{http_code} -> %{redirect_url}\n'
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=AuthProbe' \
  --data-urlencode 'content=x' --data-urlencode 'sc=S3cur3Pass!2' \
  -o /dev/null -w 'NEW:%{http_code} -> %{redirect_url}\n'
OLD:302 -> http://wiki.example.com/?page=AuthProbe&action=edit&error=1
NEW:302 -> http://wiki.example.com/?page=AuthProbe

error=1 回编辑页 = 鉴权拒绝且不落盘;直达页面视图 = 保存成功。改的是文件内容(mtime 变化),按 6.2 约 2 秒内生效,无需重启。

鉴权三通道中 Cookie 通道存的是 md5($PASSWORD)(源码 index.php:21-26 实测取证):

grep -n 'pwd\|AutorisationWiKiss' /var/www/wikiss/index.php | head -4
php -r 'echo md5("S3cur3Pass!2"), "\n";'
21:      $pwd=md5($PASSWORD);
22:      if (($_COOKIE['AutorisationWiKiss'] == $pwd) || ((isset($_POST["sc"]) && $_POST["sc"] == $PASSWORD)) || ($PASSWORD == ""))
24:         if (($PASSWORD != "") && (empty($_COOKIE['AutorisationWiKiss']) || ($_COOKIE['AutorisationWiKiss'] != $pwd)))
26:            setcookie('AutorisationWiKiss',$pwd, time() + 365*24*3600);
0f6d9aaae566abfb80fe6732bb060914

带正确 Cookie、不带 sc 保存(模拟老浏览器免口令续存):

curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=AuthProbe' \
  --data-urlencode 'content=y' --data-urlencode 'sc=' \
  -b 'AutorisationWiKiss=0f6d9aaae566abfb80fe6732bb060914' \
  -o /dev/null -w 'COOKIE:%{http_code}\n'
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=AuthProbe' \
  --data-urlencode 'content=z' --data-urlencode 'sc=' \
  -b 'AutorisationWiKiss=deadbeef' \
  -o /dev/null -w 'BADCOOKIE:%{http_code} -> %{redirect_url}\n'
COOKIE:302
BADCOOKIE:302 -> http://wiki.example.com/?page=AuthProbe&action=edit&error=1

两条运维含义:① 轮换口令后 md5 值改变,所有已发放的一年期 Cookie 立即失效,全体编辑者需重新输入口令;② Cookie 值可由口令离线算出(md5 等价物),拿到 Cookie 即拿到编辑权——口令轮换也是 Cookie 泄露的止血手段,生产务必叠加 HTTPS。

7.3 文件属主维护

7.1 改完配置后核对属主:

ls -la /var/www/wikiss/_config.php
-rwxr-xr-x 1 www-data www-data 1157 Nov  2 18:22 /var/www/wikiss/_config.php

实测结论:GNU sed -i 以 root 改写也会保留原属主(它重建文件后回写元数据),所以 sed 改配置不会引入属主问题。会引入问题的是 cp 覆盖、重定向 > 覆写、解包不带 -a 等操作——统一用 4.4 的 find /var/www/wikiss ! -user www-data 扫出,再 chown 归位。

另一个实测要点(10.3 完整复现):目录属主只影响"新建文件",不影响"覆盖已有文件"。pages/ 目录被 root 拿走后,编辑既有页面照存(写的是 www-data 属主的旧文件),只有新建页面才失败——权限故障对"编辑"静默、对"新建"报错,巡检时不能只测编辑路径。

八、升级与迁移注意

8.1 PHP 版本兼容巡检

WiKiss 0.3 是 2007 年代码,官方无更新,真正的"升级"是 PHP 运行时升级。每次升 PHP 大版本后,先跑两条巡检命令定位兼容雷区(9 处修复的完整事故实录见 《Ubuntu 22.04 安装 WiKiss 0.3 指南(PHP 8.1 兼容修复实录)》):

grep -rn 'create_function\|get_magic_quotes_gpc\|MB_OVERLOAD' /var/www/wikiss/*.php /var/www/wikiss/plugins/*.php
php -v | head -1

实测回显(打完补丁的环境):

/var/www/wikiss/index.php:86:      // ini_set("mbstring.func_overload",MB_OVERLOAD_STRING); // PHP8: constant removed
PHP 8.1.2-1ubuntu2.28 (cli) (built: Oct 19 2026 18:54:45) (NTS)

判读:三个已移除符号只剩注释行命中 = 补丁在位;若命中出现在可执行代码行(无 // 前导),说明补丁缺失或被回退。随后按 6.1 全库 php -l、按 6.4 清日志后把浏览/搜索/保存/表格渲染各点一遍,grep -ci 'fatal' 仍为 0 才算升级通过。

8.2 整库迁移

WiKiss 迁移 = 目录搬家,无导出导入格式:

  1. 源机按 5.2 打 tar 并记 sha256;
  2. 目标机装同版本 PHP + mbstring(依赖仅此一扩展,安装步骤见 《Ubuntu 22.04 安装 WiKiss 0.3 指南(PHP 8.1 兼容修复实录)》 二、三章);
  3. 解包到 /var/www/wikiss,chown -R www-data:www-data;
  4. 配 vhost 指向新目录,apache2ctl graceful;
  5. 按 5.3 第 4 步的三条 curl 验证首页/普通页/中文页。

站点根目录的 tigerwiki2wikiss.php(12,848 字节)是 2007 年从前任 TigerWiki 导入数据的转换脚本,与 WiKiss 版本间迁移无关,可保留可删除,不影响运行。

九、注意事项

  1. root 动过文件就 chown 回去:find /var/www/wikiss ! -user www-data 是收尾必查项(4.4/7.3)。
  2. historique/ 是误删的唯一救济:删除不写备份(4.3),清理磁盘时绝不动 historique/;备份时与 pages/ 一起打包(5.2 的整库 tar 天然覆盖)。
  3. 777 目录会复发:修完(10.1)再有新页面保存就重新出现,巡检命令 find /var/www/wikiss -type d -perm /o+w 建议进定时任务。
  4. 改权限/属主后 opcache 不感知:mtime 不变则缓存不失效(6.2),验证一律先 apache2ctl graceful。
  5. 恢复演练只回灌数据目录:整包解回会覆盖打过补丁的代码(5.3 要点)。
  6. 备份文件放站点目录之外:/root 或异地;放进 pages/ 会被 Web 搜索命中、放进站点根目录会进下一次 tar。

十、常见故障(实测复现)

10.1 historique 子目录 777

现象(4.3/4.4 实测):运行期新建的备份目录全部 drwxrwxrwx。

根因:源码写死,与 umask 无关:

// index.php:175-176(新备份目录不存在时)
mkdir($complete_dir_s);
chmod($complete_dir_s,0777);

对照实验证明是显式 chmod 而非 umask 问题:

php -r 'umask(0); var_dump(mkdir("/tmp/mktest", 0777));' && stat -c '%a %n' /tmp/mktest
rm -rf /tmp/mktest; php -r 'var_dump(mkdir("/tmp/mktest", 0777));' && stat -c '%a %n' /tmp/mktest
bool(true)
777 /tmp/mktest
bool(true)
755 /tmp/mktest

(PHP mkdir 的 0777 模式受 umask 022 约束得到 755;WiKiss 随后又显式 chmod 0777,把目录钉死为全局可写。)

风险:同机任何本地用户可篡改/伪造历史备份——历史文件是误删恢复的唯一来源(4.3),备份被投毒意味着救济数据不可信。

修复与复发实测:

find /var/www/wikiss/historique -type d -perm 0777 -exec chmod 0755 {} +
find /var/www/wikiss -type d -perm /o+w
(空 = 修复完成)
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=PermProbe' \
  --data-urlencode 'content=probe' --data-urlencode 'sc=S3cur3Pass!2' -o /dev/null -w 'SAVE:%{http_code}\n'
stat -c '%a %n' /var/www/wikiss/historique/PermProbe
SAVE:302
777 /var/www/wikiss/historique/PermProbe

新建页面的下一次保存必复发(源码路径),所以处置方案是定时巡检收敛(注意事项 3),或按安装篇思路把 176 行的 0777 改成 0755(改源码属可选加固)。

10.2 _config.php 权限故障被 opcache 掩盖 + 静默降级裸奔

复现:误把配置改为 root 私有(如用 cp 覆盖后改权限):

chown root:root /var/www/wikiss/_config.php; chmod 600 /var/www/wikiss/_config.php
curl -s -o /dev/null -w 'WITH-OPCACHE:%{http_code} bytes:%{size_download}\n' http://wiki.example.com/
WITH-OPCACHE:200 bytes:4743

站点看起来完全正常——OPcache 按 mtime 判定缓存有效,chmod 不改 mtime(6.2),www-data 读不到文件但缓存照供。故障在下次重启后集中爆发:

apache2ctl stop; sleep 1; apache2ctl start
curl -s -o /tmp/g4c.txt -w 'AFTER-RESTART:%{http_code} bytes:%{size_download}\n' http://wiki.example.com/
grep -i 'config' /var/log/apache2/wikiss-error.log | head -2
grep -o '<title>[^<]*</title>' /tmp/g4c.txt
AFTER-RESTART:200 bytes:1600
[Mon Nov 02 18:24:00.030312 2026] [php:warn] [pid 9961] [client 127.0.0.1:44354] PHP Warning:  include(/var/www/wikiss/_config.php): Failed to open stream: Permission denied in /var/www/wikiss/index.php on line 13
[Mon Nov 02 18:24:00.030893 2026] [php:warn] [pid 9961] [client 127.0.0.1:44354] PHP Warning:  include(): Failed opening '/var/www/wikiss/_config.php' for inclusion (include_path='.:/usr/share/php') in /var/www/wikiss/index.php on line 13
<title> - </title>

页面缩水到 1600 字节、标题为空($WIKI_TITLE 未定义)。更严重的是鉴权静默失效:$PASSWORD 未定义时源码条件 ($PASSWORD == "") 按 PHP 松散比较成立 → 匿名编辑被打开:

curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=SecProbe' \
  --data-urlencode 'content=anon-write-when-config-unreadable' --data-urlencode 'sc=' \
  -o /dev/null -w 'NO-PASS-POST:%{http_code} -> %{redirect_url}\n'
find /var/www/wikiss -maxdepth 2 -name 'SecProbe.txt'
cat /var/www/wikiss/SecProbe.txt
NO-PASS-POST:302 -> http://wiki.example.com/?page=SecProbe
/var/www/wikiss/SecProbe.txt
anon-write-when-config-unreadable

双重异常都实测到手:无口令保存成功(302 直达页面视图,而非 error=1),且因 $PAGES_DIR 未定义,文件落到了站点根目录而非 pages/(4.5 游离文件巡检项的来源)。

修复:

rm -f /var/www/wikiss/SecProbe.txt
chown www-data:www-data /var/www/wikiss/_config.php; chmod 755 /var/www/wikiss/_config.php
apache2ctl stop; sleep 1; apache2ctl start
curl -s -o /dev/null -w 'RESTORED:%{http_code}\n' http://wiki.example.com/
RESTORED:200

恢复后同一无口令请求被正确拒绝(实测回显 RESTORED-POST:302 -> http://wiki.example.com/?page=SecProbe&action=edit&error=1)。排障口径:权限类改动后必须 apache2ctl graceful 再验证;<title> 变空、grep -i 'include.*Permission denied' error.log、根目录游离 *.txt 三个信号联合指认"配置不可读"。

10.3 pages 目录属主错乱:新建失败、编辑无感

复现(对照 7.3 的"目录 vs 文件"要点):

chown root:root /var/www/wikiss/pages; chmod 755 /var/www/wikiss/pages
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=ProbeNew' \
  --data-urlencode 'content=boom' --data-urlencode 'sc=S3cur3Pass!2' \
  -o /tmp/resp.txt -w 'SAVE-FAIL:%{http_code}\n'
cat /tmp/resp.txt
ls /var/www/wikiss/pages/ | grep -c ProbeNew
SAVE-FAIL:200
Could not write page!
0

新建页面失败:fopen 建不了新文件,PHP die("Could not write page!") 直接输出(HTTP 200 + 纯文本,不是 500,前端表单看起来"保存了"实则没存——与安装篇第四章的权限症状同源)。而此时对既有页面(如 PermProbe.txt,www-data 属主)的保存实测仍返回 302 成功——覆盖已有文件不经过目录写权限。

修复:

chown www-data:www-data /var/www/wikiss/pages
curl -s -X POST 'http://wiki.example.com/' --data-urlencode 'page=ProbeNew' \
  --data-urlencode 'content=ok' --data-urlencode 'sc=S3cur3Pass!2' \
  -o /dev/null -w 'SAVE-OK:%{http_code}\n'
ls -la /var/www/wikiss/pages/ProbeNew.txt
SAVE-OK:302
-rw-r--r-- 1 www-data www-data 2 Nov  2 18:23 /var/www/wikiss/pages/ProbeNew.txt

排障口径:权限体检不能只测"编辑已有页面",必须新建一个临时页再删掉,才能暴露目录属主问题。

10.4 补丁转写笔误:正则缺闭合分隔符 → 表格静默变空

现象:打满补丁的环境里,含表格页面渲染出空表格:

<table class="wikitable"></table>

error.log 同步出现运行期告警(6.4 尾节回显):

PHP Warning:  preg_replace_callback(): No ending delimiter '/' found in /var/www/wikiss/plugins/wkp_Tables.php on line 49

定位:php -l 全绿(6.1 实测),故障在运行期。查看 49 行现场:

sed -n '48p' /var/www/wikiss/plugins/wkp_Tables.php
      $s = preg_replace_callback('/\|(([hlrtb]* ){0,1})\s*(\d*)\s*,{0,1}(\d*)\s*(.*?)\|',

正则串以 \|' 收尾——闭合分隔符 / 丢了。安装篇一键补丁脚本的 FIX8 替换串按原码 '/\|(([hlrtb]* ){0,1})\s*(\d*)\s*,{0,1}(\d*)\s*(.*?)\|/e' 去掉 /e 修饰符时把闭合 / 一并摘掉,属转写笔误。preg_replace_callback 对非法模式返回 NULL,单元格内容整体蒸发——症状与未打 FIX8 的 /e 故障(空表格、无致命错)几乎一致,极易误判。

修复:补回闭合分隔符(# 作 sed 分隔符避免与替换串中的 / 冲突):

sed -i "48s#\\\\[|]',#\\\\|/',#" /var/www/wikiss/plugins/wkp_Tables.php
php -l /var/www/wikiss/plugins/wkp_Tables.php
apache2ctl graceful; sleep 1
curl -s 'http://wiki.example.com/?page=TestS9' | grep -o '<table class="wikitable">.*</table>'
No syntax errors detected in /var/www/wikiss/plugins/wkp_Tables.php
<table class="wikitable"><tr><td  >A </td><td  >B </td></tr><tr><td colspan="1" ></td><td colspan="2" ></td></tr><tr><td colspan="3" ></td><td colspan="4" ></td></tr></table>

表格结构恢复。回显里数字单元格 | 1 | 被解析成 colspan="1" 空单元格,是 WiKiss 表格语法本身的数字前缀语义(单元格内容以数字开头会被当作合并单元格声明),属语法篇范畴,见 《WiKiss 0.3 页面语法与常用操作详解》。

教训:打完补丁的验收不能只做 php -l,必须把浏览/搜索/保存/表格渲染各实测一遍并 tail error.log(6.1 ⚠️ 的落点)。

10.5 中文文件名:tar 清单转义与全链路结论

现象与修复实测于 5.2:tar tzf 默认八进制转义中文名,--quoting-style=literal 按原样显示;数据层(解包、grep、备份恢复)不受影响。

全链路结论(本文实测覆盖):中文页面名从创建(3.2 CN:302)、落盘(pages/运维手册.txt)、检索(5.1)、备份(5.2 清单)、灾难恢复(5.3 回灌后 grep -c 命中 1)、Web 访问(URL 百分号编码 200)全部正常。文件系统按 UTF-8 字节存名,WiKiss 源码对页面名只做 /、\ 剥离(无字符集白名单),所以中文可用;但页面名中的空格、?、& 等会进入 URL 查询串,建议命名避开(页面模型细节见 《WiKiss 0.3 核心使用与鉴权配置详解》)。

十一、参考链接

WiKiss 0.3 常用 CLI 命令与运维实操 - Quzuo