不动手也能回答,动了就要还原
目标
只用只读手段回答关于客户生产服务器的五个问题。然后编写把目录状态固化成指纹并进行比较的工具,改动一行必须修改的配置,确认效果后真正还原,并证明与快照相比内容不同的文件有 0 个。
为什么重要
调查期间只要改动了什么,之后看到的值就分不清是原来的值还是我们造出来的值。客户在这期间看到的指标也被污染了,之后有人问“是从什么时候开始变成这样的”,我们自己就成了答案的一部分。 改动越小,越容易忘记还原。而且,相信自己已经还原了,与已经还原这件事得到了证明,是两回事。“已经恢复原样”是一句无法验证的话,而“还原之后与快照比较,内容不同的文件有 0 个”,则是可以确认的话。 比这更先要问的是:真的必须改动才能得到答案吗?很多情况下,同一个问题可以用只读的方式回答。不欺骗自己“是不是只读”的办法,是记录所用的命令。 本实验不同于取得客户批准后再实施的变更作业。它是那之前的阶段:调查过程中手滑的那一瞬间,在第一周应当遵守的纪律。 评分器不会相信你写下的文字。五个答案由评分器亲自重新测量;对快照工具,它会构造出新增文件、删除文件、修改内容、只改权限、只改时间这几种位置,看你的工具能否区分这五种情况。
步骤
- 创建并运行 /root/readonly/gen_site.py,生成 /root/readonly/site/。其中包含配置、240 行的订单台账、180 行的日志和查询工具。
- 只用只读手段回答五个问题,写入 /root/readonly/answers.json。每个答案都要一并留下所用的命令。
- 创建 /root/readonly/snapshot.py,遍历目录,为每个文件记录指纹、大小、权限和修改时间,并把当前状态固化到 /root/readonly/baseline.json。
- 增加
--baseline <스냅샷>(占位符为快照),让它输出比较结果。分成 added、removed、changed、mtime_only、same 五类。 - 把配置中的 max_retry 从 3 改为 5,并在 /root/readonly/change.json 中写明改什么、为什么改,以及还原命令。
- 用查询工具确认改动产生的效果,写入 /root/readonly/verify.json。
- 还原,并把与快照比较的结果留在 /root/readonly/revert_diff.json 中。内容不同的文件必须是 0 个。
- 在 /root/readonly/readonly_report.md 中分四节书写。
参考
- 要回答的五个问题的 id 是
app_version、pending_orders、log_errors、backoff_ms、oldest_pending_days。依次是:配置的版本;台账中 status 为 pending 的订单数;日志中含有 ERROR 的行数;配置中的 backoff_ms;最早的 pending 订单按 2026-09-17 计算已有几天。 - 答案文件:
{"answers": [{"id", "value", "how"}]}。value 是数字就写数字,是字符串就写字符串。how 中原样写下实际使用的命令:如果混入了非只读的命令,评分器会发现。 - 快照运行契约:
python3 /root/readonly/snapshot.py --dir <디렉터리> --out <스냅샷 JSON>(占位符依次为目录、快照 JSON)把一行摘要输出到标准输出,并以 0 结束。目录不存在时为 3。 - 快照 JSON:
{"root", "taken_at", "files": {상대경로: {"sha256", "bytes", "mode", "mtime"}}}(占位符为相对路径)。mode 是三位八进制数字的字符串(644),mtime 是以秒为单位的整数。不记录符号链接和目录。 - 比较运行契约:
python3 /root/readonly/snapshot.py --dir <디렉터리> --baseline <스냅샷> --out <차이 JSON>(占位符依次为目录、快照、差异 JSON)。 - 差异 JSON:
{"added": [경로...], "removed": [경로...], "changed": [{"path", "fields": [...]}], "mtime_only": [경로...], "same": 정수}(占位符依次为路径、路径、路径、整数)。changed 的 fields 只按升序列出 sha256、bytes、mode 中发生变化的项。内容、大小和权限都相同而只有时间不同的,不属于 changed,而是 mtime_only。 - 变更记录:
{"target_file", "key", "before", "after", "sha256_before", "sha256_after", "why", "approved_by", "baseline", "apply", "rollback"}。两个 sha256 是改动之前和之后目标文件的指纹,rollback 中必须包含原始值。 - 确认记录:
{"question", "command", "before", "after", "confirmed"}。查询工具用python3 /root/readonly/site/bin/report.py --q retry_budget [--conf <설정>](占位符为配置文件)来调用,重试预算是 max_retry 的两倍。 - 常见错误:改动之后才取快照;改动之后才写还原命令(原始值会变得模糊);为了隐藏修改时间的差异而把时间也调回去;一次改动两样东西。
- 查询工具 report.py 已经存在于客户服务器上的配置,以及把 max_retry 调到 5 这个值,是本实验的假设。
- 参考文档:Python hashlib 文档和 os 文档讲解了指纹和文件信息,POSIX 的 sys/stat.h说明了修改时间和权限位的含义,POSIX 的 touch说明了改动时间的办法。
掌握生产服务器
创建并运行 /root/readonly/gen_site.py,生成 /root/readonly/site/。其中包含 etc/app.conf、data/app.db(orders 240 行)、var/log/app.log(180 行)和 bin/report.py。
把查询工具 report.py 想成客户服务器上本来就有的工具。它不是我们创建的,而是原本就在那里,我们只能运行它来看。创建完成后,先运行一次查询工具,看看能问些什么。
只靠读取来回答
在 /root/readonly/answers.json 中写入五个问题的答案。id 是 app_version、pending_orders、log_errors、backoff_ms、oldest_pending_days,每个答案都把实际使用的命令留在 how 中。
配置打开来读取,台账以只读方式打开来统计,日志数行数即可。使用已有的查询工具也属于读取。how 中所写的命令如果混入了写入,评分器会发现,所以写的时候要自己确认。
固化改动之前的状态
创建 /root/readonly/snapshot.py,用 --dir --out 遍历目录,并把 site 当前的状态固化到 /root/readonly/baseline.json。为每个文件记录 sha256、bytes、mode 和 mtime。
不固化的话,之后就没有东西可以证明已经还原。路径以 --dir 为基准,记录为相对路径,不记录目录和符号链接。mode 只写三位权限,mtime 写成以秒为单位的整数。
增加比较功能
增加 --baseline <스냅샷>(占位符为快照),让它输出比较结果。分成 added、removed、changed、mtime_only、same 五类,changed 中只在 fields 里放发生变化的项的名称。
如果把内容、大小和权限都相同而只有修改时间不同的情况放进 changed,还原之后的结果就永远是标红,提供不了任何信息。把这种情况单独统计,是这个工具的核心。评分器会把五种情况全部构造出来放进去。
只改一项,并做好还原准备
把配置中的 max_retry 从 3 改为 5,并在 /root/readonly/change.json 中写入 target_file、key、before、after、sha256_before、sha256_after、why、approved_by、baseline、apply 和 rollback。rollback 中必须包含原始值。
还原命令要在改动之前写好。如果改完之后才想写,原始值就只能靠记忆,是 3 还是 30,30 分钟后会模糊得令人吃惊。批准也要用一行留下来:没有它,之后这个改动就成了我们偷偷做的。
用值来确认是否产生了预期的效果
用查询工具确认改动产生的效果,并在 /root/readonly/verify.json 中写入 question、command、before、after 和 confirmed。question 是 retry_budget,before 和 after 分别是改动之前和之后查询工具的输出。
改动了这件事,与所改动的内容产生了预期的效果这件事,是两回事。查询工具可以用 --conf 指定另一个配置文件,所以可以用一份包含改动之前值的临时配置运行一次,得到 before。输出原样写成字符串。
还原并证明
按 change.json 中的 rollback 还原之后,把与快照比较的结果留在 /root/readonly/revert_diff.json 中。added 和 removed 必须为空,并且 changed 必须是 0 个。只有修改时间不同的文件会留在 mtime_only 中。
相信自己已经还原了,与已经还原这件事得到了证明,是两回事。比较的结果就是证明。不要为了隐藏修改时间的差异而把时间也调回去,从那时起,就成了我们在抹掉痕迹。
写明动过什么、还原了什么
在 /root/readonly/readonly_report.md 中分 ## 읽기만으로 답한 것 ## 꼭 바꿔야 했던 한 건 ## 바꾼 뒤 무엇을 확인했나 ## 되돌린 증거 四节书写(韩文标题,依次意为“只靠读取回答的内容”“必须改动的那一项”“改动之后确认了什么”“还原的证据”)。五个问题的 id、所改的键和值、查询工具输出的变化、比较结果的数字,都必须出现。
报告不要手写,而是从 answers、change、verify、revert_diff 四个文件生成。最后一节里,留下的痕迹(仅修改时间不同的文件)也要如实写下来:隐瞒的话,从那时起就成了我们在抹掉痕迹。