OpenVPN 的多份 --up 脚本警告:实际只运行一份
调换两行 up 的顺序并不会让两份都运行。OpenVPN 每个阶段只保留一条命令,因此要找到两处定义,把工作交给单一入口。
多份脚本警告读起来像排程抱怨,于是显而易见的修法是反复调换行序直到它消失。但它不是排程抱怨。后出现的 --up 定义会覆盖该阶段已保存的命令,解析器也如此提示,这意味着你的两份脚本里始终只有一份真正运行过。另一份并没有排在它后面,而是已经消失。
这正是它值得留意却并不致命的原因:它预告的是一次悄无声息停止发生的 DNS 更新或路由变更。
这条警告与顺序无关
OpenVPN 为 up 阶段只保存一条用户脚本命令。当解析器遇到另一处定义——来自配置文件、被包含的文件或命令行——它会替换已保存的那条并发出警告。没有任何拼接,不会运行两次,也不存在顺序问题。存活下来的是解析器最后看到的那条命令,所以移动一行可能改变胜者,同时让另一份脚本继续失效。
同样的模式适用于其他脚本阶段:每个阶段只有一条命令。如果两件事都必须发生,它们需要一个单一入口,而不是两条看起来像列表的指令。
在真实启动中找出两处定义
不要从你编辑过的配置文件出发,而要从守护进程实际收到的东西出发。ps -ef 和单元的 ExecStart 会显示命令行,其中可能从容器入口点或包装脚本追加了 --up;发行版的 init 文件也能做同样的事。systemctl cat openvpn-server@server 揭示服务本身贡献了什么,而递归 grep 配置目录能找到文件层面的定义。重复通常来自不同层,这正是改一个文件毫无变化的原因。
把脚本复制到别的目录或改名同样不会移除指令。解析器数的是定义,不是文件。
让一个 hook 承担全部工作
持久的修法是单一调度脚本,按刻意安排的顺序执行两项操作。一个带 set -eu 的短 shell 脚本先调用所需的集成,再调用本地操作,并转发 "$@" 以保证参数边界不丢失,从而把行为留在一处可审计的地方。转发参数只是保留参数,并不能证明两个助手期望相同的接口,所以要分别读清各自的契约。决定失败时该做什么,以及部分改动是否需要回滚。
如果启动器提供受支持的扩展 hook,优先使用它而不是手工串接。容器镜像或厂商包通常有文档化的位置安放额外工作,使用它可以让升级不覆盖你的调度脚本。
清理属于对应的 down 阶段。重写 DNS 或添加路由的 up 操作需要文档化的回退,而 只处理启动的调度脚本只是半个设计。
确认集成,而不仅仅是安静
去掉重复警告是容易的部分,集成仍然必须工作。连接后用 resolvectl status 或读取 /etc/resolv.conf 检查解析器状态,并用 ip route 确认路由。然后断开再重连,因为匆忙串接的 hook 往往会留下上一次会话的东西。journalctl -u openvpn-server@server -b 会显示你的调度脚本是否运行、打印了什么。
把 hook 的权限放在该放的地方。提高 script-security 或用更大权限运行守护进程,并不是修复被替换的 hook;它扩大了以后每个脚本——包括攻击者能命名的那一个——能做的事。
修法是否真实的标志不是日志安静。而是两次重连之后,DNS 应答、路由条目和清理都表现正常。