宝塔 chattr +i 锁死 /usr/bin,apt install 永远 Operation not permitted
在某台宝塔面板管理的 Linux 生产机上部署一个需要 Playwright 的服务,跑 npx playwright install-deps chromium 直接炸了:
dpkg: error processing archive .../libnss3.deb (--unpack):
unable to create '/usr/bin/gpg.dpkg-new': Operation not permitted
以 root 身份运行,却说"Operation not permitted"——第一反应:文件系统只读?SELinux?检查了半天,最后发现元凶是 chattr +i。
现象
lsattr -d /usr/bin /usr/lib/systemd/user
----i---------e------- /usr/bin
----i---------e------- /usr/lib/systemd/useri 属性(immutable)意味着即使 root 也不能在该目录下创建或删除文件。dpkg 在安装任何包时,会先把旧文件备份成 .dpkg-tmp,再写入 .dpkg-new——这两步都需要在目录下创建文件,直接触发 Operation not permitted。
更麻烦的是:gnupg / dirmngr 这类包在 apt-get update 时就被半安装了,dpkg 状态变成 broken,后续所有 apt install 都会先要求修复,而修复也会失败——形成死锁。
根因
宝塔/云盾的安全加固不只锁了 /usr/bin 目录本身,还对 /usr/lib/systemd/user/ 和 /usr/lib/systemd/system/ 下的单个 unit 文件分别加了 chattr +i。
# 只解锁目录,dpkg 仍然报错
chattr -i /usr/bin
apt-get install libnss3 # 还是 Operation not permitted因为 dpkg 升级 gnupg 时会备份 /usr/lib/systemd/user/gpg-agent-browser.socket,该文件 inode 上也有 +i,目录解锁对它无效。逐一枚举所有锁定文件既不现实,加固工具还可能通过 cron 定期重新加锁。
正解
关键洞察:安装 .so 文件不需要经过 dpkg,只需要:
- 用
apt-get download下载.deb(只拉文件,不碰 dpkg 状态) - 用
dpkg-deb -x解压.deb到临时目录(纯归档解压,不执行任何脚本,不碰/usr/bin) - 把
.so*文件复制到/usr/lib/x86_64-linux-gnu/(该目录通常没有chattr +i) - 跑一次
ldconfig刷新动态链接缓存
# 先确认目标目录可写(这是整个方案的前提)
touch /usr/lib/x86_64-linux-gnu/.test && rm /usr/lib/x86_64-linux-gnu/.test \
|| { echo "目标目录也被锁,方案不适用"; exit 1; }
mkdir -p /tmp/debs && cd /tmp/debs
# Playwright/Chromium 常见运行时依赖(Ubuntu Jammy 包名)
PKGS="libnss3 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libdrm2 \
libxkbcommon0 libgtk-3-0 libgbm1 libasound2 libcairo2 \
libpango-1.0-0 libxdamage1 libharfbuzz0b libthai0 libnspr4 \
libxcomposite1 libxfixes3 libxrandr2 libx11-6 libdbus-1-3 \
libcups2 libxcb1 libfontconfig1 libfreetype6 libglib2.0-0"
for p in $PKGS; do apt-get download "$p"; done
# 解压到临时目录(不走 dpkg install,不动 /usr/bin)
mkdir -p /tmp/extract
for deb in /tmp/debs/*.deb; do dpkg-deb -x "$deb" /tmp/extract; done
# 复制 .so(-P 保留符号链接,-n 不覆盖已存在文件)
cp -P -n /tmp/extract/usr/lib/x86_64-linux-gnu/*.so* \
/usr/lib/x86_64-linux-gnu/
ldconfig
rm -rf /tmp/debs /tmp/extract实测验证
CHROME=$(ls /root/.cache/ms-playwright/chromium-*/chrome-linux*/chrome | head -1)
# 1. 没有 missing 依赖
ldd "$CHROME" 2>&1 | grep -c "not found" # → 0
# 2. Chromium 能无头启动
timeout 8 "$CHROME" --headless=new --disable-gpu --no-sandbox \
--dump-dom about:blank
# 输出 <html><head></head><body></body></html>,退出码 0几个细节
apt-get download必须在目标主机上执行,不能从其他发行版的容器里docker cp.so过来。跨发行版复制会遇到 GLIBC 版本不匹配(比如 Debian Trixie 的.so依赖GLIBC_2.38,而 Ubuntu Jammy 只有GLIBC_2.35),ldd还会报not found。dpkg -l libnss3仍显示"未安装"——因为我们绕过了包管理器。这完全没问题,常规apt不会自动覆盖这些文件,除非你显式apt-get install libnss3。- 加固工具会重新加锁——如果宝塔/云盾有 cron 任务定期重新
chattr +i,本次操作不受影响,因为我们没有改动任何被锁定路径下的文件。
一句话外卖
apt-get download+dpkg-deb -x+cp是在chattr +i加固主机上安装系统共享库的最小侵入方案——绕过 dpkg 的安装流程,只往可写路径复制.so,不动任何被锁定的目录。