fix(update): 更新前自动备份以 drouter 身份跑时写不进备份目录

真机报错:更新功能「备份失败,已中止更新」——
[Errno 13] Permission denied: /opt/drouter/backups/xxx.part。

根因:/opt/drouter/backups 是 root:root drwxr-xr-x,而「更新前备份」这条链
由 drouter-web(drouter 用户)spawn drouter-backupd.py --run-now,backupd 的
call() 用 subprocess 直接跑 helper、没提权 → helper 继承 drouter 身份 →
写备份包 .part 被拒。定时器那条链是 root 跑的,所以平时备份正常,
只有「更新触发的备份」踩雷。

修法:backupd 的 call() 里 helper 调用加 sudo -n 前缀。sudoers 已放行
drouter 免密跑 helper 白名单,root(定时器)/ drouter(更新)两种身份都通。

判据:t-update.py 新增「backupd call() 用 sudo -n 提权」(查命令数组语义),
t-update-inject.py 加对应反向验证注入(34 例 0 异常)。
真机验收:sudo -u drouter backupd --run-now → BK_CREATED files=21 size=23767。
This commit is contained in:
liuzhuohua committed 2026-10-10 16:29:19 +08:00
1 parent b7ca9c89e8
commit 8303fa3359
3 files changed
+30 -1

No files matched your search

+8
View File
@@ -98,6 +98,14 @@ CASES = [
" import drouter_update as U",
'web.py 用 importlib'),
# backupd call() 去掉 sudo 前缀:drouter 身份(更新前备份)写不进
# /opt/drouter/backups → [Errno 13] Permission denied(2026-10-10 真机修)。
('backupd call() 去掉 sudo(drouter 身份写不进备份目录)',
'backend/drouter-backupd.py',
" ['sudo', '-n', '/usr/bin/python3', HELPER, action,",
" ['/usr/bin/python3', HELPER, action,",
'backupd call() 用 sudo -n 提权'),
('/api/update 路由被摘掉',
'backend/drouter-web.py',
" if p.startswith('/api/update'):",
+9
View File
@@ -792,6 +792,15 @@ ck('_do_backup 校验 BK_CREATED(不只看 returncode)',
'(returncode==0 在「自动备份未开启」时也会出现,'
'只看它会把「什么都没备份」当成「备份成功」)')
ck('_do_backup 用 --run-now 调用', "'--run-now'" in uc)
# ⚠️ backupd 的 call() 必须用 sudo -n 提权(2026-10-10 真机修的 bug):
# 「更新前备份」由 drouter-web(drouter 用户)spawn backupd,这条链不带 sudo
# 时 helper 继承 drouter 身份,写 /opt/drouter/backups(root:root)直接
# [Errno 13] Permission denied,更新被卡死在备份这步。sudoers 已放行
# drouter 免密跑 helper 白名单,所以 root/drouter 两种身份都通。
# 判据查**语义**(subprocess 命令数组里真有 sudo 前缀),不能只查「出现 sudo 这个词」。
_bkd = read(P('backend', 'drouter-backupd.py'))
ck('backupd call() 用 sudo -n 提权(否则 drouter 身份写不进备份目录)',
re.search(r"\['sudo',\s*'-n',\s*'/usr/bin/python3',\s*HELPER", _bkd) is not None)
# ④ P0-4:apply 必须有白名单且在写库前
ck('apply_module 有模块白名单', 'APPLY_MODULES' in w)
+13 -1
View File
@@ -74,9 +74,21 @@ def emit(line):
def call(action, payload=None, timeout=CALL_TIMEOUT):
"""调用 helper 的备份能力。
⚠️ 这里用 sudo -n 提权(2026-10-10 修):
helper 写备份包要进 /opt/drouter/backups(root:root drwxr-xr-x),
而 backupd 有两种运行身份 ——
· 定时器(systemd timer)→ root,本来就能写;
· 「更新前自动备份」→ 由 drouter-web(drouter 用户)spawn,
这条链不带 sudo 时 helper 也继承 drouter 身份,写 .part 直接
[Errno 13] Permission denied,更新被卡在备份这步。
sudoers 已放行 drouter ALL=(root) NOPASSWD: python3 .../drouter-helper.py,
所以 root 跑 sudo -n 免密、drouter 跑也走白名单提权,两种身份都通。
"""
try:
p = subprocess.run(
['/usr/bin/python3', HELPER, action,
['sudo', '-n', '/usr/bin/python3', HELPER, action,
json.dumps(payload or {}, ensure_ascii=False)],
capture_output=True, text=True, timeout=timeout, errors='replace')
return json.loads(p.stdout or '{}')