ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

Ubuntu离线安装:递归下载deb依赖包与本地源实战 内网机器要装东西真正折磨人的从来不是敲下apt install那一行命令本身。你在联网机上一条命令搞定的事到了不能出网的机器上就变成先在一台有网的机器上把这个包以及它牵扯出来的整棵依赖树全下下来再想办法搬到目标机最后还要保证安装顺序别出错。我第一次干这活是给一间实验室的内网机器配开发环境装一个编译器相关的包结果apt-cache depends一列出来几十个依赖头都大了。后来折腾了几次把 Ubuntu 本地下载软件包并递归下载依赖包这套流程彻底摸熟才明白这件事有它自己的一套门道不是简单地把apt install换成apt-get download就完事。下面这套东西适合几类人给内网生产服务器打补丁的运维、在虚拟机或者双系统里搭环境但网络不稳的开发者、需要把整套依赖搬到离线机房做批量部署的实施人员还有那些被无法定位软件包和依赖报错反复教育的同学。整套流程不需要什么特殊工具用的都是 Ubuntu 自带的 apt 系列命令和一个 dpkg-dev但每一步为什么这么写、哪些参数省不得我会掰开讲清楚。1. 先把问题拆开离线装 Ubuntu 软件包到底难在哪1.1 apt install 那一步并不是最难的地方很多人一开始的直觉是既然联网机上sudo apt install nginx就能装好那我只要把下好的 deb 拷过去dpkg -i不就行了。真正动手才发现直接dpkg -i nginx.deb十有八九会报依赖错误它缺libpcre3、缺libssl你得一个个补。而每个被补上的包自己又可能带新的依赖。这就像搭积木你抽掉中间一层上面全都塌。apt 之所以能把这件事做得优雅是因为它手里有一份完整的软件包索引知道每个包的依赖关系能自己算出安装顺序。离线环境里缺的不是 deb 文件缺的是这套知道先装谁后装谁的信息。所以真正要解决的是两件事第一把完整的依赖集合一个不漏地下载下来第二让目标机上的 apt 或者 dpkg 能正确理解这些包的安装顺序。第一件事靠下载侧的递归解析第二件事靠打包时的本地源或者顺序安装技巧。1.2 依赖树为什么比你想的要深一个包在apt-cache depends里直接依赖可能只有三五个但你把它递归展开往往会翻到二三十个甚至上百个。原因在于依赖是分层的还分好几种类型。直接依赖Depends是必须的Pre-Depends要求在配置前就装好而Recommends和Suggests是可选的默认 apt 会装上Recommends这就让下载集合一下子膨胀。举个例子装一个桌面相关的包Recommends里可能挂着字体、图标主题这些几十兆的东西你要是原样递归下载包体积会大得离谱。反过来如果你把Recommends全砍了装到目标机上有些功能又会缺胳膊少腿。所以递归下载的关键不是尽量多下而是先想清楚哪些依赖类型你需要然后精确地控制。1.3 三类典型的离线场景第一类是同版本迁移比如一台 Ubuntu 22.04 的机器要装到另一台同样 22.04 的内网机这种情况最省心只要两台机器的源配置一致、架构一致下载的包挪过去基本能用。第二类是跨版本或者跨架构比如你手头是 amd64 的下载机目标却是 arm64 的开发板这就需要用到多架构下载技巧坑会明显变多。第三类是纯补丁场景目标机上大部分东西都在只差几个包这时候你没必要下整棵树按需补几个就行。判断自己属于哪一类直接决定后面用哪套方案、参数怎么调。搞错场景比如给 arm64 的目标机下了 amd64 的包拷过去dpkg -i会直接提示架构不匹配白忙一场。所以开工前第一件事永远是确认目标机的发行版代号、架构和源配置。2. 方案选型三套下载思路怎么取舍2.1 方案A缓存目录重定向让 apt 自己算这是最省脑子的一种。apt 默认把下载的 deb 放在/var/cache/apt/archives/你只要告诉它我只要下载不要安装再顺手把缓存目录换成一个你指定的文件夹apt 就会把完整的依赖树算好、全部下载进去。核心命令就一条sudo apt-get -o Dir::Cache::archives/home/你的用户名/offline-debs install \ --download-only --reinstall --no-install-recommends -y nginx这套方法的优势在于依赖解析全部交给 apt 自己你不用担心漏包、也不用担心虚拟包报错。缺点是它会顺带下载已经是最新版的包因为加了--reinstall包数量和体积会比你实际需要的大一圈。适合我不在乎多下几个只要保证不漏的场景。目录路径一定用绝对路径apt 会在里面自动建一个partial子目录和锁文件这是正常现象。2.2 方案B递归列包加逐包下载精确可控如果你想要精确控制下载哪些包就用apt-cache depends递归列出来再用apt-get download一个个下。思路是先用递归参数把整棵依赖树展开成一份包名清单去重之后作为apt-get download的参数一次性喂进去。命令大致长这样apt-cache depends --recurse --no-recommends --no-suggests \ --no-conflicts --no-breaks --no-replaces --no-enhances \ nginx | grep ^\w | sort -u--recurse负责递归展开后面那一串--no-*是把各种非必需的关系排除掉grep ^\w只保留顶格的包名行sort -u去重。这个方法能精确到你想下的每一个包但有个麻烦清单里可能出现虚拟包名apt-get download遇到虚拟包会报无法找到软件包得手动过滤。2.3 方案Cprint-uris 拿 URL跨机下载最灵活如果你连下载机都不是同架构的比如需要在 x86 的电脑上给 arm64 的开发板准备包最稳的是用--print-uris。它不下载只把 apt 算出来的完整依赖对应的下载地址和文件名打印出来apt-get install --print-uris --reinstall --no-install-recommends -y nginx \ | grep -oP http[^ ]\.deb拿到这一串 URL你可以拿去任意一台能上网的机器用wget或者下载工具批量拉甚至可以直接拷进迅雷之类的工具里下。它的好处是绕开了架构限制因为 URL 里已经带上了正确的架构后缀谁下都行只要地址能通。缺点是你得自己组织 URL 列表稍微麻烦一点。2.4 三套方案横向对比对比维度方案A 缓存重定向方案B 递归列包下载方案C print-uris依赖解析apt 全权负责自己递归解析apt 全权负责是否漏包风险极低中虚拟包要处理极低包体积偏大可控可控跨架构支持弱弱强命令复杂度低中中推荐场景同版本同架构迁移精确按需下载跨架构或异地下载我个人最常用的是方案A先用它把完整的包下下来心里有底如果发现体积太夸张再用方案B配合--no-install-recommends精简一遍。方案C留给那些架构对不上、必须换机器下载的场合。选方案不用纠结先从A跑一遍看结果多数情况就够用了。3. 逐条拆参数为什么这些命令要这么写3.1 --reinstall 到底解决了什么很多人第一次试方案A会觉得奇怪明明加了--download-only为什么还要加--reinstall。原因在于 apt 的默认逻辑是只处理需要变更的包。如果下载机上已经装了 nginx 的最新版你再执行apt-get install --download-only nginxapt 会认为已经是最新的了没什么要下的结果一个 deb 都没下下来。--reinstall的作用就是强制 apt 把这些包当成需要重新安装从而把对应的 deb 全部重新下载一遍。第一次接触会觉得这个参数多余实际上它是离线下载能不能奏效的关键。反过来说如果你明确知道下载机上什么包都没装这个参数加了也无妨顶多多下一点。所以我的习惯是只要做离线下载就带上它省得回头发现漏了包还要重来。注意--reinstall会把已安装包的最新版统统再下一遍包体积会相应变大。如果目标机其实只需要几个新包这招会造成不必要的下载量先用方案B看一眼清单再决定。3.2 --no-install-recommends 要不要加这个参数看你的目标机需求。默认情况下 apt 会安装Recommends里列出的包这些包在功能上推荐但不强制。在联网机上问题不大顶多多占点磁盘但在离线打包场景里Recommends可能给你带出几十个根本用不上的包尤其是桌面类软件。判断标准很简单如果目标机本来就缺这些东西、功能上确实依赖它们那就别加让它下全如果目标机只是要跑一个命令行服务桌面字体、图标主题这类推荐包纯属浪费传输带宽那就加上。我一般对服务器软件毫不犹豫地加--no-install-recommends对桌面软件会谨慎一点先不加看下出来的清单再决定砍不砍。3.3 Dir::Cache::archives 的重定向原理-o Dir::Cache::archives路径是 apt 的一个配置覆盖写法。apt 内部所有可配置项都能用-o 配置项值临时改Dir::Cache::archives对应的就是下载的 deb 存在哪。默认值在/etc/apt/apt.conf.d/里通常指向/var/cache/apt/archives你用-o临时覆盖成自己的目录apt 就会把本次下载产生的所有 deb 放进去。这个写法有个细节路径末尾最好带上斜杠。不带斜杠时 apt 在拼接内部路径时偶尔会出问题带上斜杠最保险。另外你指定的目录需要有写权限普通用户执行apt-get download不需要 root但用apt-get install --download-only涉及读锁和写缓存稳妥起见用sudo。用sudo的时候路径千万别写成$PWD这种依赖当前目录的变量因为 sudo 下环境变量可能被重置老老实实写绝对路径最省心。3.4 架构与版本这两个变量架构用dpkg --print-architecture查常见的有amd64、arm64、armhf。版本代号用lsb_release -cs或者cat /etc/os-release看比如jammy对应 22.04focal对应 20.04。这两个值一致下载的包才能原样在目标机上装。如果下载机和目标机架构不一样方案A和方案B都会失效因为 apt 默认只下载本机架构的包这时候就得用方案C的 URL 方式或者配置多架构让 apt 认识arm64等外来架构。版本不一致更麻烦因为同一个包在不同 Ubuntu 版本里依赖的库版本号可能不同跨版本硬装虽然经常也能用但出问题的概率明显上升。所以最省事的做法永远是找一台和目标机同版本同架构的机器来当下载机实在没有再用跨版本方案。4. 完整实操从一台联网机到一整套 deb4.1 开工前的环境核对清单动手前先花两分钟做这几件事能省下后面一大堆返工。先确认目标机的发行版代号、架构、源配置把cat /etc/os-release和dpkg --print-architecture的结果记下来。再确认下载机能连上目标机所用的源apt update不报错这一步很重要因为源里定位不到包后面全都白搭。然后建一个干净的下载目录比如/home/yourname/offline-debs别用系统临时目录避免被清理。最后想清楚你要装的目标包名命令里写对完整的包名像ros-noetic-desktop-full这种长名字一个字母都不能错写错了 apt 会直接报无法定位软件包。这几步做完环境就干净了可以开始真正的下载动作。提示如果目标包来自第三方源比如某些开发框架的标准发行版仓库里没有一定要先在下载机上把对应的源和密钥配好apt update成功之后再执行下载命令否则再递归也下不到。4.2 方式一实操缓存重定向一把梭先演示最省心的方案A完整流程。假设要给一台内网机装 nginx 以及它全部依赖下载机是同版本同架构的 Ubuntu。第一步创建目录mkdir -p /home/yourname/offline-debs cd /home/yourname/offline-debs sudo apt updateapt update是刷新本地索引确保 apt 手里有最新最全的包信息这步漏掉后面的依赖解析可能不全。接着执行下载sudo apt-get -o Dir::Cache::archives/home/yourname/offline-debs install \ --download-only --reinstall --no-install-recommends -y nginx命令跑完当前目录里应该躺着一堆 deb 文件同时有个partial子目录和lock文件这是 apt 的运行时产物正常。用ls *.deb | wc -l数一下包的数量用du -sh /home/yourname/offline-debs看总体积。到这一步方案A就完成了包已经在你手里。4.3 方式二实操依赖列表逐包下载如果你想要更大的控制权走方案B。先把依赖树列出来存到一个文件里apt-cache depends --recurse --no-recommends --no-suggests \ --no-conflicts --no-breaks --no-replaces --no-enhances \ --no-pre-depends nginx | grep ^\w | sort -u pkglist.txt--no-pre-depends也可以加上把前置依赖一并排除让清单更干净。看一眼pkglist.txt的内容确认顶格的包名都是你要的。然后逐包下载cd /home/yourname/offline-debs xargs -a pkglist.txt apt-get download这一步常见的坑是虚拟包会报错比如E: 无法找到软件包 libfoo这种因为某个虚拟包名在源里没有实体包对应。处理方式是看错误输出把报错的包名从清单里删掉因为它们通常已经被某个真实包替代了。也可以给xargs加个容错让它遇到错误继续xargs -a pkglist.txt -n 1 apt-get download || true逐包下载的好处是你能精确看到每个包下没下到坏处就是要盯着错误输出手动清理包多了会有点烦。4.4 虚拟包、多架构与特殊依赖的坑虚拟包是最常见的拦路虎。像awk、mail-transport-agent这类名字在源里根本没有同名的 deb它们由真实包通过Provides声明提供。递归清单里出现它们时apt-get download会直接失败。解决办法是识别出来手动剔除或者干脆对整份清单用apt-get install --print-uris过一遍用 apt 算好的真实 URL 去下载。架构不匹配的问题前面提过用 URL 方案绕开。还有一种特殊依赖是本地编译包或者 DKMS 模块比如显卡驱动、某些内核模块它们在安装时会现场编译依赖内核头文件。这类包在离线环境下安装往往会报post-installation 脚本子进程返回错误状态。处理办法是先把linux-headers-$(uname -r)这类头文件包也一并下载进去安装时先装头文件再装驱动模块。4.5 校验与打包传输包下好了别急着拷先做两件事。一是核对数量把下载目录里的 deb 数量和你在清单里预期的数量对一下差太多说明有遗漏。二是用dpkg-deb -I看几个关键包的信息确认版本号和你目标机要求的一致dpkg-deb -I nginx_*.deb | grep -E Package|Version|Architecture确认没问题后用 tar 打包打包时把整个目录一起打保留结构tar czvf offline-debs.tar.gz -C /home/yourname offline-debs传输方式随你U盘、内网 SCP、共享目录都行。打包而不是零散拷贝是为了避免传输过程中文件损坏或者丢失。传输完在目标机上tar xzvf解压md5sum抽查几个大包核对一下确保文件完整。5. 离线机上安装dpkg -i 与自建本地源5.1 dpkg -i 直装与顺序问题最直接的方式是sudo dpkg -i *.debshell 会把所有 deb 按文件名字母序展开传给 dpkg。问题在于字母序不等于依赖顺序你可能先装了依赖别人的包dpkg 会报缺依赖然后跳过一部分。别慌多跑几遍就行sudo dpkg -i *.deb sudo dpkg -i *.deb sudo dpkg -i *.deb第一遍装能装的第二遍装第一遍因为依赖就绪而可以装的第三遍收尾。这种方式简单粗暴对中等规模的包集合基本够用缺点是报错信息刷屏看着心累。如果目标机上有网最后可以用sudo apt-get install -f修一下残留的依赖问题。纯离线环境的话就靠多跑几遍。5.2 dpkg-scanpackages 建本地源包一多dpkg -i刷屏就更难受这时候建本地源是更好的选择。本地源的本质是给一组 deb 文件生成索引让目标机的 apt 把它们当成一个正常的软件源后续 apt 会自己处理依赖顺序。先装dpkg-dev如果你在下载机上就在下载机上做sudo apt install dpkg-dev cd /path/to/debs dpkg-scanpackages . /dev/null | gzip -9c Packages.gzdpkg-scanpackages会扫描当前目录下所有 deb生成Packages索引文件gzip -9c把它压缩成Packages.gz这是 apt 认的标准格式。把整个目录连同索引一起拷到目标机。5.3 sources.list 写法和信任问题在目标机上把 deb 目录放到一个固定位置比如/opt/local-debs然后编辑/etc/apt/sources.list.d/local.list加上一行deb [trustedyes] file:///opt/local-debs ./这里几个点要注意。路径必须是绝对路径file://后面跟的是目录。最后的./表示这个目录里的 Packages 索引对应的就是当前路径下的包。[trustedyes]是因为本地源没有 GPG 签名不加这个 apt 会拒绝使用加上就跳过签名校验。本地自用的源这样做是常规操作。加完之后执行sudo apt update sudo apt install nginx这一步 apt 会读取本地索引把 nginx 和它的一整串依赖按正确顺序装好干净利落。5.4 用 apt install 走本地源的优势相比dpkg -i反复跑本地源的最大优势是依赖顺序完全交给 apt你屏幕上看不到一堆红色报错装完状态也干净。还有一个隐性好处是apt 会记录这些包的安装来源以后apt list --installed、apt purge都能正常操作不会出现 dpkg 直装后 apt 不认的尴尬。如果目标机本来就想长期留一个离线源作为备份这套配置可以一直放着以后有新包丢进/opt/local-debs重新跑一遍dpkg-scanpackages再apt update就更新了非常顺手。对经常要在内网部署的人来说这个习惯值得养成。6. 常见报错速查与排查思路6.1 常见问题速查表报错信息原因处理方式无法定位软件包 xxx源里没有该包或索引未更新检查源配置执行apt update没有可用软件包 xxx包名拼错或源不包含核对完整包名确认源无法找到软件包虚拟包清单里有虚拟包从清单剔除用 print-uris 替代依赖关系错误安装顺序不对dpkg 策略重跑到无报错或建本地源软件包架构不匹配下载与目标架构不同用 URL 方案按目标架构下载post-installation 脚本子进程返回错误状态DKMS 或内核模块编译失败补下对应内核头文件包has no installation candidate源被禁用或包被移除检查源是否适用当前发行版6.2 定位不到包这一类问题怎么查无法定位软件包 ros-noetic-desktop-full这类报错几乎都不是下载命令本身的问题而是源的配置问题。先确认你添加的源对应的发行版代号和当前系统一致比如给 22.04 配了 20.04 的源就会定位不到。再确认源的密钥是否导入成功apt update时如果有 GPG 报错索引根本不会更新。最后确认包名本身正确第三方框架的包名经常很长容易写错。排查顺序就是从源到索引到包名一层层往下。一个实用技巧是先用apt-cache search搜关键词看这个包到底叫什么名字、在不在源里apt-cache search ros-noetic | head搜得到说明源没问题是包名写错了搜不到说明源配置有问题得回头检查源列表和密钥。6.3 依赖和签名问题的排查依赖类报错先在下载机上apt-get install --print-uris跑一遍目标包如果这一步能顺利列出所有 URL说明 apt 那边解析没问题是离线安装侧的顺序问题。反过来如果这一步就报错说明下载侧的源或者包名有问题得先解决。签名问题通常出现在本地源上忘了加[trustedyes]就会报NO_PUBKEY之类的错误补上即可。还有一种隐蔽的坑是包被半装了dpkg 数据库里状态是iF或者iU这时候后续操作容易连环报错。用dpkg -l | grep -v ^ii找出这些异常状态的包用sudo dpkg --configure -a尝试配置完成实在修不好就用sudo dpkg --remove --force-remove-reinstreq 包名清掉重来。7. 踩过坑之后总结的几条心得7.1 下载机和目标机尽量保持一致这条是省事程度的分水岭。两台机器发行版代号、架构、源配置完全一致下出来的包拷过去基本就是无脑装。一旦不一致你就要面对架构不匹配、库版本冲突、依赖链断裂这些麻烦。我现在的习惯是只要有条件就专门留一台和线上机器同版本的虚拟机当下载机需要下什么就在上面下稳定省心。没有条件时再考虑跨版本方案心里要清楚这是折中。7.2 内核模块和驱动类包要单独盯DKMS 驱动的离线安装是最容易被忽略的。这类包本身不大但它在安装时会调用本地编译依赖linux-headers。如果你的下载集合里没有对应版本的内核头文件安装到post-installation阶段必报错。处理办法是把linux-headers-$(uname -r)一并加进下载清单而且注意目标机的内核版本要和你下载的头文件版本对得上uname -r的输出务必在两边都核对一遍。7.3 大依赖树分批处理更稳当依赖树扩大到上百个包时一次性全装偶尔会碰上个别包之间的版本冲突排查起来很痛苦。我后来学乖了把包按功能或者层级分组比如基础库一批、运行时一批、主程序一批分批用本地源安装出问题能快速定位到是哪一批的问题。这不是必须的但对复杂场景确实能省下大量排查时间。尤其是给一台关键生产机做离线部署时分批验证能让你更有掌控感。如果你手上也有内网机器要折腾我建议第一步别急着下先把目标机的发行版、架构、内核版本三个信息抄下来贴在屏幕边上然后所有命令都围绕这三个值来选参数。这一步花的两分钟后面能帮你省下不止两个小时。
返回列表