资讯详情

Ubuntu离线安装:递归下载deb依赖包与本地源实践

📅 2026/10/2 22:36:29 | 华诺云谱 👁 阅读
Ubuntu离线安装:递归下载deb依赖包与本地源实践
离线装软件这件事我前前后后折腾过不下十几次从早期拿 U 盘在一个个机器上手抄dpkg -i到后来给整机房的内网服务器做批量部署踩的坑足够写一本小册子。这篇就把在 Ubuntu 本地下载软件包并递归下载其所有依赖包这件事从头到尾拆一遍——包含怎么确定要下哪些包、用什么命令把依赖树整个拉下来、下载过程中有哪些看着不起眼但会致命的细节以及到了目标机器上怎么装才不会陷入依赖地狱。如果你手里有一台能联网的 Ubuntu还有一台或者一堆不能联网的 Ubuntu这篇文章就是给你写的。不需要你有多深的 Linux 功底只要你能敲sudo跟着走一遍就能拿到一个完整的离线安装包集合。文中所有命令我都在 Ubuntu 20.04 / 22.04 / 24.04 上实测过差异点我会单独标出来。1. 为什么下载一个包从来不是下载一个包1.1 离线环境的真实痛点在哪里很多人第一次做离线安装的直觉是我要装 nginx那我去把 nginx 的 deb 包下下来不就行了。然后拷到目标机器上dpkg -i nginx.deb屏幕立刻甩出一行红字——dpkg: 依赖关系问题使得 nginx 的配置工作不能继续下面跟着一串nginx 依赖于 libpcre3然而未安装软件包 libpcre3。这就是问题的起点。Ubuntu 的包管理体系里一个 deb 包几乎从来不独立存在它只描述我需要什么不负责把需要的东西一起带来。apt在联网环境之所以好用是因为它把三件事串起来了读本地缓存的包索引Packages 文件、做依赖求解、从仓库拉取所有需要的 deb 文件。而一旦断网第一件事的数据源就没了第三件事的执行能力也没了。所以真正要解决的问题不是下载一个包而是在联网机器上完整复现 apt 的依赖求解过程并把求解结果对应的所有 deb 文件一次性抓下来。这个描述里的关键词有三个递归、完整、可校验。缺了任何一个到了离线机器上都会翻车。1.2 依赖字段里到底哪些必须管要递归下载先得知道递归的边界在哪。Debian 包的 control 文件里跟依赖相关的字段有好几个它们的语义差别很大直接决定了你要下载多少东西。Depends依赖最核心的一个。不满足就不能正常配置。分为硬依赖和版本约束依赖比如libc6 ( 2.34)。这是必须递归的。Pre-Depends预依赖比 Depends 更严格要求在解包之前就满足。最常见的是dpkg ( 1.17.5)这种。实操中 Pre-Depends 往往指向非常底层的包通常系统已经有了但做完整递归时不能漏。Recommends推荐默认会被 apt 安装但不是强制的。Ubuntu 从 12.04 开始默认装 Recommends所以你在联网机器上有时候装一个两百 KB 的小工具结果拉下来两百兆的东西多半就是 Recommends 的功劳。离线场景下要不要带取决于目标机器是不是完全干净。如果目标机器是同类系统的标准安装Recommends 通常已经满足大半可以不带如果是极简容器镜像建议带上。Suggests建议默认不装直接忽略。Conflicts冲突/ Breaks破坏描述不能共存的包。做下载时不用关心但到了安装阶段会产生实际影响。Replaces替代描述文件层面的覆盖关系会影响dpkg的安装顺序判断。Provides提供这是最容易被忽略、也最容易导致递归中断的字段。有些包并不叫某个名字但声明我提供了这个能力。比如一堆日志守护进程都 Provides 了syslog-daemon。依赖方写的可能是那个被提供的虚拟名字而不是真实包名。递归时必须把这一层展开否则你会拿到一个写着httpd-cgi的依赖项然后apt-get download httpd-cgi直接报错。理解这几个字段之后递归下载的规模就有数了硬依赖 预依赖是底线推荐依赖是可选虚拟包需要展开版本约束需要考虑——这就是为什么后来我彻底放弃了手写脚本逐个解析改用apt-cache depends --recurse或者apt-rdepends这类工具让工具去做它本来该做的事。1.3 四种主流方案的横向对比在正式开始之前先把可选路径摆出来。这四种我全部用过各有各的适用场景不是哪个更高级的问题而是哪个更贴合你当前的环境。方案核心命令优点局限推荐场景apt 下载模式apt-get install --download-only -d依赖求解完全由 apt 自己做最准确要求目标包尚未安装或可重装产物在固定缓存目录联网机器和目标机器同版本同架构且目标包未装apt-get download 依赖列表apt-cache depends --recurse/apt-rdepends产物集中在一个目录可控性强需要自己处理虚拟包、已安装过滤需要精确控制下载内容或目标包在下载机上已安装apt-offlineapt-offline set/get/install有专门工具链支持签名和校验需要额外安装工具流程稍长单机离线补丁、安全更新全量镜像 本地源apt-mirror/dpkg-scanpackages一次做好长期可用多机复用占用空间大镜像同步耗时内网多台机器长期维护我个人的选择习惯是单次安装、目标包未装直接走方案一要往一个自维护的本地仓库里补充内容走方案二加本地源只有需要长期维护一批机器的时候才会去碰全量镜像。2. 动手之前把下载环境对齐2.1 确认版本、架构与已启用的仓库这一步很多人跳过然后在下完包之后才发现架构不对白忙一场。三个信息必须确认而且要在下载机上确认lsb_release -a # 输出示例 # Distributor ID: Ubuntu # Description: Ubuntu 22.04.4 LTS # Release: 22.04 # Codename: jammydpkg --print-architecture # 输出示例amd64dpkg --print-foreign-architectures # 输出为空说明没有启用额外架构为什么这三个重要因为 deb 包对 libc、对内核 ABI、对编译器版本都有隐含要求。20.04 上编译的包放到 22.04 上多数情况下能装但 glibc 版本对不上的时候会在运行时报GLIBC_2.35 not found这类错误排查起来非常费时间。架构不对更直接amd64的包在arm64上dpkg -i会直接拒绝。还有一个隐藏条件下载机的/etc/apt/sources.list和/etc/apt/sources.list.d/里启用的仓库必须覆盖目标包所在的仓库。Ubuntu 的主仓库main、受限仓库restricted、社区仓库universe、multiverse 是分开的很多第三方软件在 universe 里。如果下载机的 universe 没启用apt install会直接报无法定位软件包。# 快速检查常用仓库是否启用 grep -rh ^deb /etc/apt/sources.list /etc/apt/sources.list.d/ | awk {print $2, $3} | sort -u注意无法定位软件包 ros-noetic-desktop-full这种报错十有八九不是包名写错而是对应的第三方仓库没加进来。ROS、Docker、NodeSource 这些都有自己的源光有 Ubuntu 官方源是找不到的。2.2 源列表清理与更新包索引是元数据apt 的依赖求解完全基于它。索引过期会导致两种后果一是找不到新包二是求解出的依赖版本和你仓库里的实际版本不一致下载时出现404或者版本回退失败。sudo apt-get update这条命令我建议在每次做离线下载之前都跑一次并且观察输出有没有Err或者Ign的行。如果有某个源持续报错说明那个源不可用依赖求解会缺一块。常见的是国内镜像站临时同步延迟换个镜像就行。更新完成之后检查包索引文件是否完整ls -lh /var/lib/apt/lists/ | head -20正常情况下一堆Packages文件加上cn.archive.ubuntu.com_ubuntu_dists_jammy_main_binary-amd64_Packages这类命名。如果某个仓库对应的文件是空的或者大小只有几 KB说明索引没拉下来。还有一个容易忽略的细节如果你之前用apt-mark hold锁过某些包或者配过apt preferences的 Pin依赖求解结果会受影响。下载之前最好确认一下apt-mark showhold ls /etc/apt/preferences.d/这两条输出为空是最理想的。如果不为空你得清楚自己在做什么因为 Pin 会把某个包固定到特定版本连带影响它的依赖版本选择。2.3 缓存目录与磁盘空间规划apt默认把所有下载的 deb 放在/var/cache/apt/archives/。这个目录有个特点apt 会在安装完成后自动清理旧的 deb。所以做离线下载时如果你打算用方案一最好不要在下载之后马上做别的apt install操作或者干脆把产物换个地方。我习惯的做法是下载时直接指定缓存目录到自己的目录sudo mkdir -p /tmp/debs sudo chown $USER:$USER /tmp/debs空间估算很简单先看依赖树规模apt-cache depends --recurse nginx | wc -l粗略按每个包平均 300KB 到 1MB 估几百个包就是几百兆。桌面环境相关的包会大得多比如ros-noetic-desktop-full这种整套依赖下来几个 G 是常态。所以下载之前先看一眼磁盘df -h /tmp如果/tmp是 tmpfs内存盘那就要换个位置。我之前就干过这事下到一半报No space left on device排查半天才发现/tmp挂在内存上只有 2G。3. 方案一用 apt 自己的能力做递归下载3.1 --download-only 到底做了什么这是最省事的方案因为它把依赖求解完全交给 apt。命令本身很简单sudo apt-get install --download-only nginx--download-only简写-d的作用是apt 正常做全套依赖求解把需要的包全部标记为待安装然后把每个包下载到缓存目录但不执行解包和配置。这行命令跑完/var/cache/apt/archives/里就是完整的依赖集合。你可以直接打包拷走sudo tar -czf nginx-offline.tar.gz -C /var/cache/apt/archives/ .有几个细节值得说清楚。第一apt-get install -d在输出里会显示Need to get X MB of archives这个数字就是最终产物的实际大小不用自己估。第二命令结束时可能会有The following packages have unmet dependencies之类的提示那说明你的源里缺少某个依赖这时候必须先解决源的问题强行下载是不完整的。第三如果你想让下载产物落到自定义目录可以这样sudo apt-get install --download-only -o Dir::Cache::archives/tmp/debs nginx注意路径末尾不要带斜杠带了有时候会出现奇怪的目录层级。另外这个覆盖只对本次命令有效不会写进配置文件。3.2 目标包已经装了怎么办这是方案一最大的坑。假设你在下载机上已经装了 nginx那apt-get install -d nginx的输出会是nginx 已经是最新的版本了。 升级了 0 个软件包新安装了 0 个软件包要卸载 0 个软件包有 0 个软件包未被升级。什么都没下载。因为 apt 的逻辑是已满足依赖就不动。解决办法有两个。第一个是加--reinstallsudo apt-get install --download-only --reinstall nginx--reinstall强制 apt 把已经是最新版本的包重新标记为待安装从而触发下载。这个组合我用了很多次效果稳定。但要注意--reinstall不会重新下载它的依赖因为那些依赖已经满足了。所以如果你希望得到完整的依赖树得配合后面的方案二或者在一个干净的容器里操作。第二个办法是用apt-get download它是纯下载命令不关心本地状态cd /tmp/debs apt-get download nginx但它只下载指定的那一个包不做依赖求解。这就是为什么方案二必须存在。3.3 缓存目录里的文件命名与校验下载完成后看看目录里都是什么ls -lh /var/cache/apt/archives/ | head -30文件名格式是包名_版本_架构.deb比如nginx-core_1.18.0-6ubuntu14.4_amd64.deb。这个命名规则不是随便定的dpkg安装时会读文件名来确认包信息所以重命名 deb 文件是有风险的特别是把版本号去掉会导致dpkg报包的版本信息无效。有几个文件名里的细节值得注意带号的版本比如2.34-0ubuntu11是 Ubuntu 打的补丁版本。包名里带%3a的是 epoch 被 URL 编码了。这种情况在从--print-uris拿到的 URL 列表里很常见下载下来之后文件名会恢复正常。同一个包可能有多个版本共存比如libssl3_3.0.2-0ubuntu1.10_amd64.deb和libssl3_3.0.2-0ubuntu1.12_amd64.deb。这通常是历史残留装的时候要注意别把旧的一起装进去。校验方面apt 在下载时已经校验过仓库签名的哈希了所以下载下来的包本身是可信的。但传输过程中可能损坏特别是走 U 盘或者 FTP。稳妥的做法是生成一份校验清单cd /tmp/debs md5sum *.deb MD5SUMS到了目标机器上md5sum -c MD5SUMS这个习惯我强烈建议养成。曾经有一次 FTP 传输中断某个 deb 少了几百字节dpkg -i报包结构无效我完全想不到是文件损坏折腾了半小时。3.4 用 --print-uris 导出完整的下载清单--print-uris是一个被严重低估的选项。它让 apt 只输出求解结果的 URL 和校验信息不实际下载apt-get install --print-uris --yes nginx输出形如http://cn.archive.ubuntu.com/ubuntu/pool/main/n/nginx/nginx-core_1.18.0-6ubuntu14.4_amd64.deb nginx-core_1.18.0-6ubuntu14.4_amd64.deb 1218744 MD5Sum:3f2b1e9a8c4d5e6f7a8b9c0d1e2f3a4b四个字段分别是URL、文件名、字节数、哈希。这个输出的价值在于三点。一是可以直接生成一个wget/curl的下载脚本在有网但不想让下载机动用 apt 的场景下用apt-get install --print-uris --yes nginx \ | grep ^ \ | awk {print $1} \ | tr -d \ urls.txt wget -i urls.txt -P /tmp/debs二是可以核对总大小把第三列加起来就是精确的下载体积apt-get install --print-uris --yes nginx \ | grep ^ \ | awk {sum $3} END {printf %.2f MB\n, sum/1024/1024}三是把哈希留档作为校验依据比事后跑 md5sum 更早发现问题。注意--yes或者-y是必要的否则 apt 会在确认那一步停下来等待输入而--print-uris模式下它不会真的提示只是会卡住。另外如果输出里有E: Unable to locate package说明源有问题这种情况下--print-uris也是列不全的。4. 方案二apt-rdepends 与 apt-get download 组合拳4.1 递归展开依赖树的两种写法方案一有个前提目标包在下载机上没装。一旦装了或者你需要的是一份精确的下载列表而不是缓存目录里的一堆文件就得自己算依赖树。有两种工具能做到。第一种是apt-cache depends --recurseapt 自带不用装apt-cache depends --recurse \ --no-recommends \ --no-suggests \ --no-conflicts \ --no-breaks \ --no-replaces \ --no-enhances \ nginx输出是一棵缩进的树形如nginx Depends: nginx-core Depends: nginx-full Conflicts: nginx-core nginx-core Depends: libc6 Depends: libpcre3 Depends: libssl3 Depends: zlib1g Depends: nginx-core ...提取包名的方式是过滤掉所有以空格开头的行apt-cache depends --recurse \ --no-recommends --no-suggests --no-conflicts \ --no-breaks --no-replaces --no-enhances \ nginx \ | grep ^\w | sort -u--no-*系列选项用来裁剪依赖范围最后只保留 Depends 和 Pre-Depends这是最保险的最小集合。如果你希望带上推荐包把--no-recommends去掉即可。第二种是apt-rdepends需要额外安装sudo apt-get install apt-rdepends用法apt-rdepends nginx输出形如nginx Depends: nginx-core ( 1.18.0-0ubuntu1) Depends: nginx-full nginx-core Depends: libc6 ( 2.34) ...过滤方式是去掉所有以空格开头的行同时去掉冒号后面的版本约束apt-rdepends nginx \ | grep -v ^ \ | sed s/^Depends: // \ | sort -u两种工具我用下来的体会是apt-cache depends --recurse更贴近 apt 自己的求解逻辑而且--no-*选项控制粒度细apt-rdepends输出更干净但它在处理虚拟包的时候行为不够透明。日常我优先用前者。4.2 虚拟包与尖括号依赖的处理这是整个流程里最容易翻车的一环。上面那个输出里有一行Depends: httpd-cgi尖括号包住的是一个虚拟包名意味着某个真实包通过 Provides 声明了它。apt-get download httpd-cgi会直接失败因为仓库里没有这个名字的 deb。处理方式有两种。省事的做法是在提取包名时直接过滤掉带尖括号的行apt-cache depends --recurse --no-recommends --no-suggests \ --no-conflicts --no-breaks --no-replaces --no-enhances \ nginx \ | grep ^\w \ | grep -v \ | sort -u但这样做的风险是如果这个虚拟依赖在当前系统上没有被任何包满足那目标机器上就真的缺了一块。所以更稳的做法是先看看谁提供了它apt-cache search --names-only . | head -0 # 占位 apt-cache showpkg httpd-cgi | head -20apt-cache showpkg会列出反向提供者Reverse Provides形如Reverse Provides: apache2 2.4.52-1ubuntu4 nginx-core 1.18.0-6ubuntu14.4理论上你可以手动挑一个真实包加进列表。但实际操作中我一般这么做把尖括号项单独记下来去目标机器上用apt-cache或者dpkg -l确认这几个能力已经被满足。因为虚拟包大多是很基础的能力日志守护进程、HTTP 服务、邮件传输代理标准 Ubuntu 安装里基本都有。还有个更省心的思路对称性检查。下载完成后把得到的 deb 列表导出在目标机器上用dpkg-query检查还有哪些依赖没被满足cd /tmp/debs dpkg-deb -f *.deb Package | sort /tmp/need.txt # 在目标机器上 dpkg-query -W -f${Package}\n | sort /tmp/have.txt comm -23 /tmp/need.txt /tmp/have.txt这个输出就是下载了但目标机器上还没有的差集配合本地源安装时的报错信息能快速定位漏包。4.3 一份可以直接抄的下载脚本把前面的东西串起来这是我用得最顺手的一个脚本存成fetch-deps.sh#!/bin/bash # 用法: ./fetch-deps.sh 包名 [输出目录] # 作用: 递归下载指定包及其所有硬依赖的 deb 文件 set -euo pipefail PKG${1:?请指定包名例如: ./fetch-deps.sh nginx} OUTDIR${2:-/tmp/${PKG}-offline} if [[ $(id -u) -ne 0 ]]; then echo 请用 sudo 运行apt-get download 需要读取包索引 2 exit 1 fi mkdir -p $OUTDIR cd $OUTDIR echo 正在展开依赖树: $PKG apt-cache depends --recurse \ --no-recommends \ --no-suggests \ --no-conflicts \ --no-breaks \ --no-replaces \ --no-enhances \ $PKG \ | grep ^\w \ | grep -v \ | sort -u packages.txt echo 依赖包总数: $(wc -l packages.txt) echo 开始下载 # 分包下载某个包失败不影响其他包 while read -r p; do if ! apt-get download $p 2download.log; then echo [失败] $p fi done packages.txt echo 下载完成产物目录: $OUTDIR echo 失败记录见 download.log ls -1 *.deb 2/dev/null | wc -l | xargs echo 实际得到 deb 文件数:几个设计上的考虑值得解释一下。为什么用 while 循环而不是一条apt-get download $(cat packages.txt)因为一次性传几百个参数容易超过 shell 的命令行长度限制而且只要有一个包下载失败整条命令就中断了。用2download.log把错误重定向出去最终统计实际拿到的 deb 数量比一次性命令健壮得多。为什么用grep -v 就是前面说的虚拟包过滤。这个脚本定位是快速拿到能用的一套如果追求完整性把这一行去掉然后看download.log里报了哪些虚拟包再手动处理。为什么不用--reinstall因为apt-get download本身就是纯下载不受本地安装状态影响。这一点跟apt-get install -d有本质区别也是我把方案二推荐给下载机上已经装了一堆东西的场景的原因。脚本跑完后把整个目录打包tar -czf ${PKG}-offline.tar.gz -C /tmp ${PKG}-offline4.4 跨架构下载的注意事项如果你的下载机是 amd64目标机器是 arm64比如树莓派、某些开发板就得做跨架构下载。前提是下载机启用了目标架构sudo dpkg --add-architecture arm64 sudo apt-get update然后明确指定架构后缀apt-get download nginx:arm64或者在依赖列表阶段就带上apt-cache depends --recurse nginx # 这会给出 amd64 的依赖问题是这两个命令混用会出错——apt-cache depends在 amd64 上下文中算出来的依赖列表里面的包在apt-get download时如果不指定:arm64拿到的还是 amd64 的包。一个可行的处理方式是在下载脚本里统一追加架构后缀ARCHarm64 while read -r p; do apt-get download ${p}:${ARCH} 2download.log || echo [失败] $p done packages.txt但这里有个坑跨架构的依赖树跟同架构不完全一样。比如某些包在 arm64 上没有对应的版本会直接报无法定位软件包。所以更稳妥的做法是找一台跟目标机器同架构的联网机器来做下载哪怕是临时开一个同架构的虚拟机。这个建议的价值我是在一次给 ARM 开发板准备离线包的时候才深刻体会到的当时折腾跨架构下载花的时间比我装个 qemu 虚拟机多得多。5. 方案三apt-offline 与自建本地源5.1 apt-offline 的签名-下载-安装三段式apt-offline是专门为离线场景设计的工具它的思路很有意思在离线机器上生成一个签名文件描述我需要什么然后拿到联网机器上去填充这个签名生成一个包含所有 deb 和元数据的压缩包再拿回来安装。离线机器上需要先想办法装上 apt-offline这在干净机器上是有矛盾的实际中一般是通过别的方式先拷一个 deb 进去# 在离线机器上生成需求签名 sudo apt-offline set /tmp/pkg.sig --install-packages nginx联网机器上# 用签名去下载 apt-offline get /tmp/pkg.sig --bundle /tmp/nginx-bundle.zip离线机器上sudo apt-offline install /tmp/nginx-bundle.zip它最大的优势是把 apt 的包索引也一起打包了。这一点非常重要因为很多人自己拷 deb 的时候忘了拷元数据导致到了离线机器上apt install依然报无法定位软件包——因为 apt 压根不知道这些 deb 的存在。apt-offline 顺手解决了这个问题。局限也很明显apt-offline本身要装而且它生成签名时会读取本地已安装包的清单对于目标机器完全干净的场景计算出的需求可能不完整因为它假设你已经有的就不需要了但你可能希望带全套。5.2 用 dpkg-scanpackages 搭一个本地源这是我最终给内网多台机器维护时采用的方案虽然前期麻烦一点但一次做好之后后面每台机器就三行命令的事。第一步把所有 deb 放到一个目录比如/srv/offline-reposudo mkdir -p /srv/offline-repo sudo cp /tmp/nginx-offline/*.deb /srv/offline-repo/第二步生成 Packages 索引cd /srv/offline-repo dpkg-scanpackages . /dev/null Packages gzip -9c Packages Packages.gzdpkg-scanpackages的第二个参数是 override 文件路径没有就填/dev/null。它会扫描目录下所有.deb解析 control 信息输出成 apt 能识别的包索引格式。这一步就是补上前面说的元数据。第三步在目标机器上配置本地源echo deb [trustedyes] file:/srv/offline-repo ./ | sudo tee /etc/apt/sources.list.d/offline.list sudo apt-get update[trustedyes]是必须的因为本地目录没有 GPG 签名不加这个选项 apt 会报仓库没有 Release 文件或者签名无效。这是自建源最常见的拦路虎。配置好之后目标机器上的操作就跟联网一样了sudo apt-get install nginxapt 会从本地目录里把依赖全部解析出来并安装顺序问题也由 apt 自动处理。这就是自建源最大的价值——它能表达依赖关系而不只是一堆文件。如果dpkg-scanpackages命令找不到装上对应的工具包sudo apt-get install dpkg-dev5.3 目标机安装顺序与 -f 的边界如果不是用本地源而是直接dpkg -i *.deb顺序就是个大问题。dpkg不做依赖求解它只会按你给的顺序一个个处理如果先装的是依赖方、后装的是被依赖方就会留下一堆 未配置 状态的包。有一个技巧可以缓解先批量解包再统一配置。sudo dpkg --unpack *.deb sudo dpkg --configure -a--unpack只解包不配置--configure -a再去尝试配置所有未配置的包。因为解包阶段不检查依赖的配置状态所以顺序影响小很多。这个方法我在处理几十个包的时候用过成功率明显高于直接dpkg -i。那apt-get install -f呢它的全称是--fix-broken作用是尝试修复损坏的依赖关系。在联网环境下它非常好用会去仓库里找缺的包装上。但在离线环境下如果本地源没有配置好apt-get install -f只会报有几个软件包无法下载然后什么都做不了。所以我的建议很明确离线安装要么老老实实用本地源要么接受dpkg --unpack--configure -a的兜底方案。指望install -f在断网机器上救命是不现实的。还有一个细节dpkg --configure -a对于post-installation 脚本 子进程返回错误状态这类错误的处理能力有限。这种错误通常来自包自己带的 postinst 脚本可能是它调用的某个服务启动失败或者它依赖的运行环境不满足。看日志才能定位sudo dpkg --configure -a 21 | tee configure.log grep -A5 post-installation configure.log6. 常见报错速查与排查思路这些东西我按遭遇频率排了个序做成一张表方便对照。报错信息大概率原因排查方向E: 无法定位软件包 xxx包索引未更新、包名拼写、仓库未启用、包在 universe/multiverse 而该源未开跑apt-get updateapt-cache search检查 sources.list 里的组件没有可用软件包 nginx同上且常见于精简源的容器镜像检查/etc/apt/sources.list是否被清空软件包似乎无效deb 文件损坏或不是合法 deb 格式dpkg-deb -I xxx.deb看能否解析重新下载dpkg: 依赖关系问题使得 xxx 的配置工作不能继续依赖包没装或版本不满足检查依赖包是否在下载列表里dpkg-query -W 依赖包名post-installation 脚本 子进程返回错误状态包的安装后脚本执行失败看dpkg --configure的详细输出通常是服务启动或权限问题项目存在无效依赖项离线包集合里有版本冲突的包检查是否同时放了同一包的多个版本404 Not Found下载时包索引过期对应版本的 deb 已被仓库清理重新apt-get update后重下No space left on device缓存目录空间不足df -h确认注意/tmp可能是 tmpfs公钥不可用 / NO_PUBKEY第三方源的 GPG key 未导入apt-key已废弃用signed-by方式导入 key无法解析域名DNS 或网络问题换镜像源检查/etc/resolv.conf关于第一行那个无法定位软件包我想多说两句因为它是最高频的。有个非常隐蔽的情况包索引更新了但更新的是某一个源另一个源失败了apt 会在update输出里给一行W: 校验数字签名时出错或者E: 部分索引文件下载失败然后继续使用旧的索引。这种情况下apt-get update看起来成功了退出码是 0但实际上某个仓库的索引没更新某些包就找不到。判断方式是看输出里的 W 和 E 行或者用apt-get update 21 | grep -E ^(W|E):这个输出为空才算是真正更新成功。还有个跟下载相关的隐性错误文件名里的 epoch。有些包的版本号里有冒号比如1:2.34-1ubuntu1转成 URL 时冒号会被编码成%3a。从--print-uris拿到的 URL 里是%3a下载下来文件名的却是原样。用wget -i urls.txt的时候wget 会按 URL 最后一段命名于是文件名里就带着%3a了。这种情况dpkg能识别它会自动解码但如果你脚本里按文件名做匹配就会对不上。遇到这种包我一般手动mv一下把%3a换成:。7. 几条用血泪换来的经验7.1 版本错配是头号杀手我遇到过最折腾的一次是从 20.04 的机器上下了一套包准备装到 22.04 的机器上。dpkg -i全部成功服务却起不来日志里一行version GLIBC_2.35 not found。原因是某个依赖包在 20.04 上是 2.31而目标机器上已有的其他包需要 2.35。dpkg 层面的依赖校验是基于包名和版本号的它能识别libc6 ( 2.34)这种约束。但运行时的符号依赖它管不了。所以版本一致这件事不要靠应该没问题的直觉靠命令确认# 下载机 lsb_release -rs dpkg --print-architecture # 目标机 lsb_release -rs dpkg --print-architecture两边的输出必须一致。小版本可以放宽22.04.3 和 22.04.4 一般没问题但大版本20.04 对 22.04和架构绝对不能混。一个实用技巧如果实在找不到同版本的下载机可以用 Docker 或者虚拟机临时起一个docker run --rm -it --platform linux/amd64 ubuntu:22.04 bash进去之后配源、apt-get update、装apt-rdepends然后按前面的脚本下载。用docker cp把产物拷出来。这个方式的好处是环境完全干净不会因为你本地装了什么东西导致依赖树被裁剪。7.2 别忽略元数据前面提过一次这里再强调一遍因为这是最容易被忽略又最致命的一点。当你把一堆 deb 拷到离线机器上然后执行apt-get install nginxapt 会去/var/lib/apt/lists/里找包索引。如果那个目录里没有描述这些 deb 的索引文件apt 会说无法定位软件包哪怕这些 deb 就摆在/var/cache/apt/archives/里。所以完整的离线包应该包含三样东西deb 文件本身包索引Packages和Packages.gz一份校验清单MD5SUMS或SHA256SUMS用 apt-offline 的话它自动帮你打包了索引。自己手工做的话就是用dpkg-scanpackages生成。这一步花的时间比起到了目标机器上发现装不了再回头找值得太多。7.3 空间、清理与长期维护最后说几个琐碎但实用的点。清理策略如果反复做下载/var/cache/apt/archives/会越积越多。用apt-get clean清空全部缓存用apt-get autoclean只清掉已经不在仓库里的旧版本。做离线下载时我一般先clean一下避免旧版本的 deb 混进去。空间监控--print-uris出来的第三列总和就是精确体积这个前面给过命令。桌面类的大包建议先算一下再下。长期维护的目录结构如果是要给多台机器做离线部署我建议按这样的结构组织offline-repo/ ├── Packages ├── Packages.gz ├── MD5SUMS ├── base/ # 基础系统包 ├── tools/ # 常用工具 └── services/ # 业务服务包每个子目录都单独跑一次dpkg-scanpackages然后在 sources.list 里配多行本地源。这样后续增加新的包时只需要更新对应子目录的索引不会影响其他部分。一个容易忽略的检查下载完成后检查一下 deb 列表里有没有重复包名的不同版本ls *.deb | sed s/_.*// | sort | uniq -d输出为空是最好的。如果有重复说明索引里同一包有多个版本装的时候可能装错需要手动清理。关于 snap 包Ubuntu 现在有很多软件是通过 snap 安装的比如某些版本的 Firefox。snap 是独立的打包体系apt-get download拿不到 snap 包。如果目标软件是 snap 形式发布的这套方法不适用。判断方式很简单apt-cache policy 包名里如果只有snap相关的输出那说明它是个过渡包。一个偷懒的小技巧如果你只是临时要在一台差不多的机器上装个东西可以直接把整个/var/cache/apt/archives/打包不清理、不过滤。体积会大一些但省去了所有分析和处理的步骤。我在赶时间的时候用过几次缺点是包里可能混着一堆无关的历史文件而且apt install的产物目录里往往只有最近一次安装的东西并不完整。所以这只是应急不是常规做法。我现在给内网机器准备离线包的标准流程是先确认目标机的发行版和架构然后在一台匹配的虚拟机或者下载机上跑那个fetch-deps.sh脚本产物打个包带上MD5SUMS到目标机上配好本地源再apt install。这一套下来通常二十分钟以内能搞定一台机器的全套环境比一台台手动找包快得多也不会留下依赖破损的隐患。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑