PVE 9关闭“无有效订阅”弹窗

PVE 免费使用时会显示“没有有效订阅”的提醒弹窗。社区里常见的做法,是修改 proxmox-widget-toolkit 里的前端文件:

1
/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js

具体来说,是在 checked_command 函数开头插入一句:

1
2
orig_cmd();
return;

让订阅检查直接返回,不再弹提醒。

问题在于,这个文件不是你自己维护的文件,它属于 proxmox-widget-toolkit 软件包。只要 APT 升级时更新了这个包,dpkg 就会把新版文件装回去,你之前的手工修改也就被覆盖了。结果就是:补丁失效,订阅弹窗又回来。

所以真正要解决的不是“怎么改一次文件”,而是“怎么在软件包升级把文件覆盖后,自动再补一次”。

为什么不用 Cron 定时任务?

Cron 也能做这件事,比如每天检查一次 proxmoxlib.js 里还有没有补丁,没有就补上。但它不太贴合问题的触发方式。

补丁失效的时机很明确:APT 安装或升级了 proxmox-widget-toolkit,文件被覆盖。Cron 是按时间轮询的,它不知道刚才有没有升级软件包。于是会出现几个尴尬情况:

  • 升级完成后补丁已经失效,但 Cron 还没到点,弹窗会先回来;

  • 如果把 Cron 设得很频繁,大多数时候只是空跑;

  • 如果设得不频繁,升级后就会有一段空窗期;

  • 还要额外维护一个定时任务。

Cron 不是不能用,而是它解决的是“定时检查”,不是“升级后立刻修复”。

为什么用 APT 钩子更合适?

APT 本身支持在 dpkg 安装完成后执行钩子,也就是 DPkg::Post-Invoke。这个时机正好卡在软件包文件已经被安装、可能覆盖原文件之后。

用它来做这件事有几个好处:

  • 只在 APT 安装或升级软件包后触发,不需要每天空跑;

  • proxmox-widget-toolkit 更新覆盖文件后,能马上检查并重新打补丁;

  • 脚本可以做幂等判断,已经打过补丁就跳过,不会重复插入;

  • 不需要等下一次定时任务,触发时机和问题原因是对应的。

这也是我认为它比 Cron 更适合这个场景的原因。

下面是把这件事自动化的配置。整个方案拆成两个文件:

文件 作用
/usr/local/sbin/pve-no-nag-patch 检查并自动应用补丁
/etc/apt/apt.conf.d/99-pve-no-nag APT 安装完成后自动调用脚本

操作方法

1. 创建自动修复脚本

创建 /usr/local/sbin/pve-no-nag-patch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
cat > /usr/local/sbin/pve-no-nag-patch <<'PY'
#!/usr/bin/env python3

from pathlib import Path
from datetime import datetime
import shutil
import subprocess

JS = Path(
    "/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
)
LOG = Path("/var/log/pve-no-nag-patch.log")

ANCHOR = "checked_command: function (orig_cmd) {"
MARKER = "orig_cmd(); return;"


def log(message):
    with LOG.open("a") as f:
        f.write(
            f"[{datetime.now():%Y-%m-%d %H:%M:%S}] "
            f"{message}\n"
        )


def main():
    if not JS.is_file():
        log("JS file not found; skipped")
        return

    content = JS.read_text(encoding="utf-8")

    if MARKER in content:
        log("Already patched; skipped")
        return

    if content.count(ANCHOR) != 1:
        log("Unknown JS structure; patch not applied")
        return

    backup = JS.with_name(
        JS.name + ".pre-no-nag-" +
        datetime.now().strftime("%Y%m%d-%H%M%S")
    )

    shutil.copy2(JS, backup)

    updated = content.replace(
        ANCHOR,
        ANCHOR + " orig_cmd(); return;",
        1
    )

    JS.write_text(updated, encoding="utf-8")

    if MARKER not in JS.read_text(encoding="utf-8"):
        shutil.copy2(backup, JS)
        log("Verification failed; original restored")
        return

    result = subprocess.run(
        ["systemctl", "restart", "pveproxy"],
        capture_output=True,
        text=True
    )

    if result.returncode == 0:
        log(f"Patch applied successfully; backup: {backup}")
    else:
        log(f"Patch applied, but pveproxy restart failed: {result.stderr}")

    # Keep only the latest three backups
    backups = sorted(
        JS.parent.glob("proxmoxlib.js.pre-no-nag-*"),
        key=lambda p: p.stat().st_mtime,
        reverse=True
    )

    for old in backups[3:]:
        old.unlink(missing_ok=True)


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        log(f"ERROR: {e}")
PY

设置执行权限:

1
chmod 755 /usr/local/sbin/pve-no-nag-patch

这个脚本不是无脑替换。它会先判断文件是否存在,再判断是否已经打过补丁。已经打过就跳过;找不到唯一目标锚点,就记录“结构不认识”然后不动文件。真正修改前会备份,修改后会再验证一次,验证失败就恢复原文件。最后重启 pveproxy,并且只保留最近三个备份。

2. 配置 APT 自动钩子

创建 APT 钩子文件:

1
2
3
4
5
cat > /etc/apt/apt.conf.d/99-pve-no-nag <<'EOF'
DPkg::Post-Invoke {
    "/usr/local/sbin/pve-no-nag-patch";
};
EOF

这样以后通过 APT 安装或升级软件包,dpkg 完成安装后就会自动调用这个脚本。

3. 手动测试一次

先别等下一次升级,手动跑一遍:

1
/usr/local/sbin/pve-no-nag-patch

检查补丁是否存在:

1
2
grep -c 'orig_cmd(); return;' \
/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js

正常输出:

1
1

确认 APT 是否读取了钩子:

1
apt-config dump | grep -F 'pve-no-nag-patch'

应该能看到:

1
DPkg::Post-Invoke:: "/usr/local/sbin/pve-no-nag-patch";

查看日志:

1
tail -20 /var/log/pve-no-nag-patch.log

如果日志显示已经打过补丁并跳过,或者补丁应用成功,就说明流程正常。

触发时机说明

这个钩子是在 dpkg 安装软件包之后触发的。正常的软件包升级,比如:

1
2
apt update
apt full-upgrade

其中 apt full-upgrade 调用 dpkg 安装软件包后,会自动检查和修复补丁。

单独执行 apt update 不会触发,因为它只更新软件包索引,不安装软件包。这一点要分清楚。

以后不想用了怎么恢复?

如果以后不想屏蔽订阅提醒,先删掉钩子和脚本:

1
2
rm -f /etc/apt/apt.conf.d/99-pve-no-nag
rm -f /usr/local/sbin/pve-no-nag-patch

然后重装官方前端包:

1
apt install --reinstall proxmox-widget-toolkit

最后重启:

1
systemctl restart pveproxy

这样就会恢复官方原版前端文件,订阅提醒也会回来。

总结一下

这个方案的核心不是“又写了一个补丁脚本”,而是把修复时机放在正确的位置。补丁失效是因为 APT 升级覆盖文件,那就让 APT 在安装完成后自动检查一次。比起 Cron 定时轮询,DPkg::Post-Invoke 更直接,也更省心。