外观
用 omv-rpc 写自动化脚本
要管理好几台 OMV,或者想把常见的设置固化成脚本,用 omv-rpc 比点界面可靠得多:同样的操作每次都一样,还能放进版本库。读完这一篇,你能写出一个遵守「检查、修改、应用、验证」流程的脚本,并且知道出错时怎么撤销。
开始之前
- 先读 omv-rpc 与 JSON-RPC 接口,知道服务、方法和参数是怎么回事。
- 脚本在 OMV 的 shell(root 身份)里运行,用
bash,依赖 OMV 自带的jq。 - 本文的脚本是按 OMV 8.5.x 源码写的示例,没有在实机上运行验证。请先在测试机或者虚拟机里跑通,并且给虚拟机做一个快照。
应用配置会重启服务
影响范围:脚本最后调用 Config.applyChanges,它和点击界面上的应用一样,会重写配置文件并重启相关服务,例如 SMB、SSH、Web 界面。已有连接会短暂中断。
前置条件:脚本运行之前,界面里没有待应用的更改;你知道脚本改了什么;已经在测试机上验证;有系统盘备份或虚拟机快照。
怎么回退:应用之前出错,可以用 Config.revertChanges 撤销所有尚未应用的配置更改,脚本里已经做了这件事。应用之后,要把配置改回原样再应用一次,所以脚本最好保留一份原始值。创建系统用户、创建目录这类附带动作,撤销配置时不会被撤销,要单独清理。
脚本的骨架
修改配置的脚本都遵守同一个流程:
text
1. 检查:没有待应用的更改,才开始
2. 修改:调用服务的方法写入新设置(这一步会把相关模块标记为待应用)
3. 应用:Config.applyChanges,把设置变成真正的配置
4. 验证:确认已经没有待应用的更改,并且结果符合预期下面是一个可以直接套用的骨架。第 1 步先确认没有待应用的更改,这样如果后面出错需要撤销,撤销的只会是这个脚本做的改动,不会误伤你在界面里改了一半的东西:
bash
#!/bin/bash
set -euo pipefail
# 1. 检查:存在待应用的更改就不开始
if [ "$(omv-rpc Config isDirty '{"modules": []}')" != "false" ]; then
echo "存在待应用的配置更改,请先在界面里处理。" >&2
exit 1
fi
# 出错时撤销本脚本做过的、尚未应用的更改
rollback() {
echo "出错了,撤销尚未应用的配置更改。" >&2
omv-rpc Config revertChanges '{"filename": ""}' > /dev/null || true
}
trap rollback ERR
# 2. 修改:在这里调用服务的方法
# ……
# 3. 应用:开始应用之后就不再自动撤销,避免撤销到一半的部署
trap - ERR
omv-rpc Config applyChanges '{"modules": [], "force": false}'
# 4. 验证
if [ "$(omv-rpc Config isDirty '{"modules": []}')" = "false" ]; then
echo "完成"
else
echo "应用之后仍有待应用的更改,请检查。" >&2
exit 1
fi几个要点:
set -euo pipefail:任何一条命令失败,或者管道里的任何一环失败,脚本都会立刻退出。omv-rpc失败时退出码是 1,错误 JSON 在标准错误里。- JSON 参数用
jq生成,不要手写字符串拼接。jq -n --arg 名 值 '{...}'会正确转义引号和特殊字符。 Config.applyChanges的参数里,modules为空数组表示「所有待应用的模块」,force为false表示「只处理被标记的」。因为第 1 步保证了开始时没有别的待应用更改,所以这里只会应用这个脚本改出来的内容。
示例一:只读的状态报告
不改任何东西,适合放进定时任务:
bash
#!/bin/bash
set -euo pipefail
echo "== 系统 =="
omv-rpc System getInformation \
| jq -r '"版本: \(.version)", "运行时间: \(.uptime)", "待应用更改: \(.configDirty)", "需要重启: \(.rebootRequired)", "可升级软件包: \(.availablePkgUpdates)"'
echo "== 服务 =="
omv-rpc Services getStatus | jq -r '.data[] | "\(.name)\t启用=\(.enabled)\t运行=\(.running)"'
echo "== 磁盘 =="
omv-rpc DiskMgmt enumerateDevices \
| jq -r '.[] | "\(.canonicaldevicefile)\t\(.model)\t\(.serialnumber)\t\(.powermode)\t\(.temperature)"'
echo "== 文件系统 =="
omv-rpc FileSystemMgmt enumerateMountedFilesystems '{"includeroot": false}' \
| jq -r '.[] | "\(.mountpoint)\t\(.type)\t已用 \(.used)\t可用 \(.available)"'示例二:修改时区
这个例子和 omv-firstaid 的「Configure time zone」做的事一样:读出当前设置,只改时区,原样写回,再应用。System.setTimeSettings 要求提供 timezone、ntpenable、ntptimeservers 全部字段,所以必须先 get,再带着其他字段一起 set。
在骨架的修改位置放入:
bash
TZ_NEW="Asia/Tokyo"
# 时区名必须在系统支持的列表里
omv-rpc System getTimeZoneList | jq -e --arg tz "$TZ_NEW" 'index($tz) != null' > /dev/null
params=$(omv-rpc System getTimeSettings \
| jq -c --arg tz "$TZ_NEW" '{timezone: $tz, ntpenable, ntptimeservers, ntpclients}')
omv-rpc System setTimeSettings "$params" > /dev/nulljq 里的 {timezone: $tz, ntpenable, ntptimeservers, ntpclients} 是简写,意思是取当前对象的这三个字段原样带上,只把 timezone 换成新值。
示例三:创建共享文件夹
ShareMgmt.set 创建共享文件夹时,如果相对路径对应的目录不存在,会一并创建。它要的参数有:uuid(新建时用保留的「新对象」UUID)、name、reldirpath、comment、mntentref,以及可选的 mode。其中 mntentref 是文件系统的挂载条目 ID,不是磁盘的 UUID,要先查出来:
sh
omv-rpc ShareMgmt getCandidates | jq -r '.[] | [.uuid, .description] | @tsv'输出每行是「挂载条目 ID」和这个文件系统的描述。选出你要用的那个,放进变量。然后在骨架的修改位置放入:
bash
NAME="projects"
MNTENT="<上面查到的挂载条目 ID>"
# 幂等:已经存在就跳过,脚本可以重复运行
if omv-rpc ShareMgmt enumerateSharedFolders \
| jq -e --arg n "$NAME" 'map(select(.name == $n)) | length > 0' > /dev/null; then
echo "共享文件夹 $NAME 已存在,跳过"
else
params=$(jq -nc --arg name "$NAME" --arg ref "$MNTENT" '{
uuid: "fa4b1c66-ef79-11e5-87a0-0002b3a176b4",
name: $name,
reldirpath: ($name + "/"),
comment: "",
mntentref: $ref,
mode: "775"
}')
omv-rpc ShareMgmt set "$params" > /dev/null
fimode 取值有 700、750、755、770、775、777,和界面里权限(Permissions)下拉框的选项一一对应,默认的 775 表示管理员读写、用户读写、其他人只读。权限怎么设计,见 三层权限:共享权限、ACL 与文件系统权限。
示例四:创建用户
创建系统用户涉及 Linux 账户、SMB 账号,是通过 UserMgmt.setUser 完成的。要一次创建很多用户,用 批量导入用户 里的导入功能更方便。单个用户的写法是:
bash
params=$(jq -nc --arg name "alice" --arg pw "$PASSWORD" '{
name: $name, password: $pw, email: "", comment: "",
groups: [], shell: "/bin/sh", disallowusermod: false, sshpubkeys: []
}')
omv-rpc UserMgmt setUser "$params" > /dev/nullname、password、email、groups、disallowusermod、sshpubkeys 是必填字段,uid、shell、comment 可选。要指定 UID,在对象里加上 uid: 2001。
用户是不能靠撤销配置来回退的
setUser 会真的创建 Linux 账户,Config.revertChanges 只撤销数据库里的更改,不会删除账户。脚本里创建用户的部分失败时,要自己清理,或者在测试机上验证好再上线。另外,密码出现在命令行参数里,会短暂地被其他本地用户在进程列表里看到。
应用与等待
示例里用的是同步的 Config.applyChanges。omv-rpc 等待响应的时间有上限(环境变量 OMV_ENGINED_SO_RCVTIMEO,默认 180 秒),应用很多模块时可能超时。这时改用后台版本 applyChangesBg,它立刻返回一个状态文件名,再用 Exec.isRunning 轮询,后台任务失败时 isRunning 会返回错误:
bash
job=$(omv-rpc Config applyChangesBg '{"modules": [], "force": false}' | jq -r .)
req=$(jq -nc --arg f "$job" '{filename: $f}')
while :; do
state=$(omv-rpc Exec isRunning "$req")
[ "$(printf '%s' "$state" | jq -r .running)" = "true" ] || break
sleep 2
donestate=$(...) 这样直接赋值,是为了让 set -e 在 omv-rpc 失败时终止脚本,不要把它写进 [ ... ] 的判断里,否则失败会被悄悄吞掉。
哪些方法不要自动化
下面这些方法一旦运行就无法撤销,不要放进没人看管的脚本里,尤其是不要写成循环或定时任务:
DiskMgmt.wipe:擦除磁盘。FileSystemMgmt.create、FileSystemMgmt.grow、FileSystemMgmt.umountByDir:创建、扩容、卸载文件系统。Plugin.remove:卸载插件,会连带清除它的配置。UserMgmt.deleteUser、ShareMgmt.delete:删除对象,可能影响正在使用的服务。ShareMgmt.delete带上recursive: true参数时,会连同共享文件夹里的全部内容一起删除。System.reboot、System.shutdown:重启、关机。
需要时人工执行,并且先用只读方法确认目标,例如磁盘按型号和序列号核对。
检查结果
脚本跑完后:
sh
omv-rpc Config isDirty '{"modules": []}' # 应为 false
omv-salt deploy list-dirty # 应为 No dirty states found.再检查改动本身:时区用 timedatectl;共享文件夹在 存储器 → 共享文件夹(Storage → Shared Folders)里能看到,并且 ls -ld 显示目录已创建;用户用 id <用户名> 确认。最后把脚本在测试机上重复运行一次,第二次应该什么都不改,这就是幂等。
常见问题
报 Invalid 开头的参数校验错误。 对照方法的参数定义,必填字段是否都给了,类型对不对。查看方法:
sh
jq '.[] | select(.id == "rpc.system.settimesettings")' /usr/share/openmediavault/datamodels/rpc.system.json脚本中途失败,界面上出现了黄色提示条。 先用 omv-rpc Config revertChanges '{"filename": ""}' 撤销,或者在界面上点提示条里的撤销(Undo)。然后修好脚本再运行。
applyChanges 很久没有返回。 改用后台版本,见上一节。
升级 OMV 之后脚本失效。 RPC 不是稳定的公开 API,字段和默认行为会变。升级后先在测试机上重跑一次,并留意 OMV 8.1–8.5 更新要点 里的变化。
参考资料
- OMV 源码:
config.inc(Config服务)、omv-rpc、官方的omv-firstaid模块里有大量调用 RPC 的完整例子 - 官方文档:omv-rpc