如何创建一个简单 APT 仓库
- 无废话版本
需求: 有一堆 .deb 包,想把它们做成一个 APT 仓库,这样就可以用apk install pkgname进行安装了,这样一方面自己可以规避 dpkg -i xxx.deb 时候的依赖问题,另一方面也方便了其他人
解决方法:
mkdir -p /opt/raspi-apt-repos/raspbian8
cp ~/Downloads/raspbian8/*.deb /opt/raspi-apt-repos/raspbian8
cd /opt/raspi-apt-repos/raspbian8
# scan *.deb and create Packages file
dpkg-scanpackages -m . > Packages
# create Release file
apt-ftparchive release . > Release
# TODO: create a GPG key with ‘gpg --gen-key’ if you have no one
gpg --list-keys || gpg --gen-key
# create Release.gpg file
gpg --armor --detach-sign --sign -o Release.gpg Release
# create InRelease file
gpg --clearsign -o InRelease Release
# export your public key
cd /opt/raspi-apt-repos/
gpg --export --armor <uid> -o my-repo.gpg-key.asc
# start web server (you'd better use nginx instead)
python -m SimpleHTTPServer 4000
# ===== 8< ==========================================
# on client
wget http://210.32.142.88:4000/my-repo.gpg-key.asc && sudo apt-key add my-repo.gpg-key.asc
echo “deb http://foo.example.com/repos/raspbian8 ./” | sudo tee /etc/apt/sources.list.d/my-repo.list
- 什么叫"简单 APT 仓库"
这里说的是如何创建一个简单的 APT 仓库(根据 DebianRepository/Setup - Debian Wiki ,这种简单的仓库称为 trivial archive, 而复杂的那种称为 official archive.),之所以说"简单",主要是指仓库不采用 pool 结构,里面只有一个suite(jessie, jessie-backport, trusty, trusty-updates 这些东西叫做 suite),一个 component(main, nonfree, universe 这些东西叫做 component),也就是说 Packages 文件只有一个(也不提供Contents-amd64.gz 这些可有可无的文件).
这个仓库的目录结构如下:
.
├── vim_8.0.1420-0york0~14.04_amd64.deb
├── vim_8.1.1575-0york0~14.04_amd64.deb
├── vim_8.1.1575-0york0~16.04_amd64.deb
├── vim_8.1.1575.orig.tar.gz
├── vim-common_8.0.1420-0york0~14.04_all.deb
├── vim-common_8.1.1575-0york0~16.04_all.deb
├── Packages
└── Release
使用时需要在 /etc/apt/sources.list 里面添加的配置是:
deb http://foo.example.com/repos/raspbian8 ./
(在这个配置里, suite 的值是 ./
, 当suite是路径的情况下 component 必须为空.对于sources.list的格式可以参考 sources.list (5) )
1.1 参考: "不简单"的 APT仓库
Debian/Ubuntu的官方仓库(以及Launchpad上面的ppa)跟上面的结构不一样,典型特征是顶层有个 dists 目录和 pool 目录.目录结构如下:
├── dists/
│ ├── buster/
│ ├── jessie/
│ │ ├── InRelease
│ │ ├── main/
│ │ │ ├── binary-aarmhf/
│ │ │ │ ├── Packages
│ │ │ │ └── Release
│ │ │ ├── binary-all/
│ │ │ │ ├── Packages
│ │ │ │ └── Release
│ │ │ └── binary-amd64/
│ │ │ └── Contents-amd64.gz
│ │ │ └── Contents-armhf.gz
│ │ ├── Release
│ │ └── Release.gpg
│ └── stretch/
└── pool/
└── main/
├── v/
│ └── vim/
│ ├── vim_8.0.1420-0york0~14.04_amd64.deb
│ ├── vim_8.1.1575-0york0~14.04_amd64.deb
│ ├── vim_8.1.1575-0york0~16.04_amd64.deb
│ ├── vim_8.1.1575.orig.tar.gz
│ ├── vim-common_8.0.1420-0york0~14.04_all.deb
│ └── vim-common_8.1.1575-0york0~16.04_all.deb
└── z/
└── zim/
对应的 /etc/apt/sources.list 配置大致如下:
deb http://foo.example.com/repos/raspbian8 jessie main
(在这个配置里,suite=jessie, component=main,一行可以配置多个component,但不能配置多个suite)
可以看到,dists 下面按 suite 分了多个目录(stretch, jessie, buster),suite下面又按CPU架构有不同的 binary-${arch} 目录和 Contents-${arch}.gz 文件;而真正的deb包都放在 pool 目录下,对于vim这样一个软件来说多个suite/cpu的deb包是放在一起的.
这样的好处是:
- 将所有类型CPU的包列表(Packages或者Packages.gz文件)放在一个文件里面,这样每个机器要获取的包列表就比较小(考虑到Debian/Ubuntu所收录的软件数,这个节省是比较可观的;而 Contents-${arch}.gz 里面存放的是所有包里面的文件列表( apt-file 程序会使用到这个信息),这个文件的体积就更大了)
- 不同套件/不同CPU可共用的deb包(主要是那些 _all.deb)和源代码包,也只在 pool/v/vim 这样的目录下存放一份,不同存放多份
- 源代码包( .dsc, orig.tar.xz)有路径存放,这样 dget / apt source 可以取到源代码包
不过缺点就是:这样的仓库很复杂,不靠工具是无法确定构建出一个 deb 之后要往哪里放,要更新哪些索引文件(Packages, Contents-${arch}, Release, InRelease...)
- 创建简单 APT 仓库的方法
根据 DebianRepository/Setup - Debian Wiki ,有很多种工具可以用来创建 APT 仓库,但创建简单仓库(trivial archive)的方法在该页面就只看见一个,于是我就用了:
- 将所有 deb 放到同一个目录(假设这个目录为 /opt/raspi-apt-repos/raspbian8)
cd /opt/raspi-apt-repos/raspbian8 && dpkg-scanpackages -m . > Packages
这就完成了,是不是很简单?(其实事儿没完呢) 可以看到唯一的变化就是多了一个 Packages 文件,里面是各个deb包的相对路径\描述\大小(也就是都是客户端用 apt show xxx 可以查到的内容)
使用:
- 如果仅仅是本机使用,那么可以在
/etc/apt/sources.list
里面添加一行deb file:/opt/raspi-apt-repos/raspbian8 ./
然后就可以apt update && apt install vim
了. - 如果要提供给其它机器使用,那么需要安装配置 nginx 之类的web server (临时试验可以用
python -m SimpleHttpServer
启动一个web server).假设在web server里面将/opt/raspi-apt-repos/
映射到了 http://foo.example.com/repos, 那么对方需要在/etc/apt/sources.list
里面添加的配置就是deb
http://foo.example.com/repos/raspbian8./
补充说明:
dpkg-scanpackages
这个工具在 dpkg-dev 这个包里面,需要先安装这个包才能使用.这里有个好处是 Centos 的EPEL 库里面也有这个包,所以 RHEL / CentOS 上也就可以很方便地装上这个工具再按上述方法创建一个 APT 仓库(其实我工作中同时要给 Ubuntu 14.04/16.04/18.04, CentOS 6/7, SLES 12.4/15 同时提供包仓库,服务器是一台CentOS 7)上面命令行中给dpkg-scanpackages 加了
-m
选项,这个选线的作用是如果同名的包有多个版本,都把它们采集到 Packages 文件中去,这样用户可以自己用apt show vim
看到所有的版本,然后用apt install foo=1.12
这样的方式来安装指定的版本.(如果你的目录内有同名的包,而又没有使用-m
选项的话,后扫描到的包会被忽略,不进入 Packages 文件.另外需要注意的是,abc_1.1-1_i386.deb 和 abc_2.0-1_amd.deb 这种针对不同 CPU也会被认为是同名的包,其中一个会被忽略掉!)Ubuntu 16.04 / Debian 8 以上版本拒绝上述简单仓库的解决办法
3.1 Release 文件
如果用户端是 Ubuntu 16.04 / Debian 8 (jessie) 或者更高版本的话, 这个仓库会被 apt 拒绝接受,并报告如下的错误信息:
$ sudo apt update
Get:1 file:/opt/raspi-apt-repo/ubuntu18 ./ InRelease
Ign:1 file:/opt/raspi-apt-repo/ubuntu18 ./ InRelease
Get:2 file:/opt/raspi-apt-repo/ubuntu18 ./ Release
Err:2 file:/opt/raspi-apt-repo/ubuntu18 ./ Release
File not found - /opt/raspi-apt-repo/ubuntu18/./Release (2: No such file or directory)
Hit:3 http://ports.ubuntu.com bionic InRelease
Hit:4 http://ports.ubuntu.com bionic-updates InRelease
Hit:5 http://ports.ubuntu.com bionic-security InRelease
Hit:6 http://ppa.launchpad.net/ubuntu-pi-flavour-makers/ppa/ubuntu bionic InRelease
Hit:7 http://ports.ubuntu.com bionic-backports InRelease
Reading package lists... Done
E: The repository 'file:/opt/raspi-apt-repo/ubuntu18 ./ Release' does not have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
可以看到,apt 由于没有从仓库读取到 Release
文件,于是不再读取 Packages
文件,这个仓库被拒绝了 (虽然前面也试图读取 InRelease
文件并且失败了,但这个错误是可以忽略的(行首是 Ign
,而读取Release 那行是 Err
)
出现这个问题的原因在于, apt 对了加强安全性, 需要仓库生成一个 Release 文件,这个文件里面包含了 Packages 等文件的大小和校验和(包含MD5/SHA1/SHA256/SHA512 多种值).如果这个文件里面所描述的 Packages 大小与校验和与实际读取到的文件不一致,apt 也会拒绝这个仓库.
Date: Sat, 29 Jun 2019 07:18:59 +0000
MD5Sum:
7a487761b83fc9326ba4a3186df3b950 5384 Packages
256a066585672866ffd831926ab42b77 38 Release
SHA1:
92c867ff5789ae6efdcc500d59d220b7e30bc952 5384 Packages
d1c369af68084f30a6d3cf8d98066d4a723a459c 38 Release
SHA256:
a9ae09e62999cd2b7b7f7fab48ae04e28ac3d2a6a6613df5e3c93bffe11d9166 5384 Packages
2124019cd02360d2f5466d40bb097920f2415e997a9fb20c78380bb04d5c33ff 38 Release
SHA512:
68da75f205561ce06b327502611c0f01d6401e048e17dcf2960d96c04397fbdf20252d2ccb0e997570834572ede7a6376162caec3411560bc5523a06413f634d 5384 Packages
b5b3f6a48013239e70c34f623f5e69594c6b11f27f8da8325067f97f5801c73208c9adb66b0d1cce0186d1102c1013684c615077d8dcebb590027390969e0b54 38 Release
生成这个 Release 文件的方法是: apt-ftparchive release . > Release
3.2 Release.gpg 和 InRelease 文件
生成了 Release
文件之后,用户端运行 apt update
,还是会碰到问题:
$ sudo apt update
Get:1 file:/opt/raspi-apt-repo/ubuntu18 ./ InRelease
Ign:1 file:/opt/raspi-apt-repo/ubuntu18 ./ InRelease
Get:2 file:/opt/raspi-apt-repo/ubuntu18 ./ Release [816 B]
Get:2 file:/opt/raspi-apt-repo/ubuntu18 ./ Release [816 B]
Get:3 file:/opt/raspi-apt-repo/ubuntu18 ./ Release.gpg
Ign:3 file:/opt/raspi-apt-repo/ubuntu18 ./ Release.gpg
Hit:4 http://ports.ubuntu.com bionic InRelease
Hit:5 http://ports.ubuntu.com bionic-updates InRelease
Hit:6 http://ports.ubuntu.com bionic-security InRelease
Hit:7 http://ports.ubuntu.com bionic-backports InRelease
Hit:8 http://ppa.launchpad.net/ubuntu-pi-flavour-makers/ppa/ubuntu bionic InRelease
Reading package lists... Done
E: The repository 'file:/opt/raspi-apt-repo/ubuntu18 ./ Release' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
可以看到,Release 文件读到了,但 apt 认为 *Release is not signed”,于是认为从这个仓库安装软件还是不安全的,Packages 文件还是没有被读取.
这个 Release.gpg又是什么东西,该怎样生成它呢?
根据 DebianRepository/Format - Debian Wiki 的说法, The file "Release.gpg" contains a GPG signature,并且与那个 InRelease 文件有关系:
The file "dists/$DIST/InRelease" shall contain meta-information about the distribution and checksums for the indices, possibly signed with a GPG clearsign signature (for example created by "gpg -a -s --clearsign"). For older clients there can also be a "dists/$DIST/Release" file without any signature and the file "dists/$DIST/Release.gpg" with a detached GPG signature of the "Release" file, compatible with the format used by the GPG options "-a -b -s".
InRelease files are signed in-line while Release files should have an accompanying Release.gpg file.
这里说 Release.gpg 是个签名文件(Technically speaking, this is an ascii-armored detached gpg signature.) ,随同 Release 出现的,比较老的客户端只认这两个文件; 而 InRelease 是内嵌签名的(也就是说,将原来 Release 的内容和 Release.gpg 的内容揉到一起了 - 注意这里不是简单地拼到一起),新的客户端才支持这个这个文件.观察一下 Debian 和 Ubuntu 的仓库 ( http://mirrors.ustc.edu.cn/debian/dists/jessie/, http://mirrors.ustc.edu.cn/ubuntu/dists/xenial/ ) , 可以看到 Debian 的仓库只有 Release 和 Release.gpg 这两个文件,而 Ubuntu 仓库里面这三个文件都有.
下面说说如何生成这两个文件:
- 如果你跟我一样不知道什么是GPG数字签名,那么先仔细读一下 GPG入门 - 简书 或者 GnuPG 入门教程 | IterNull Blog ,大致知道密钥/公钥, 加密/签名, 文本签名/二进制签名,分离式签名等等概念
- 按照上面文档描述的方法,生成自己的gpg key (
gpg --gen-key
) - 生成Release.gpg:
gpg --armor --detach-sign --sign -o Release.gpg Release
- 生成InRelease:
gpg --clearsign -o InRelease Release
3.3 公钥不被信任问题
以为问题就都解决了吗?用户方再执行一遍试试
$ sudo apt update
[sudo] password for bamanzi:
Get:1 file:/opt/raspi-apt-repo/ubuntu18 ./ InRelease
Ign:1 file:/opt/raspi-apt-repo/ubuntu18 ./ InRelease
Get:2 file:/opt/raspi-apt-repo/ubuntu18 ./ Release [816 B]
Get:2 file:/opt/raspi-apt-repo/ubuntu18 ./ Release [816 B]
Get:3 file:/opt/raspi-apt-repo/ubuntu18 ./ Release.gpg [659 B]
Get:3 file:/opt/raspi-apt-repo/ubuntu18 ./ Release.gpg [659 B]
Ign:3 file:/opt/raspi-apt-repo/ubuntu18 ./ Release.gpg
Hit:4 http://ports.ubuntu.com bionic InRelease
Get:5 http://ports.ubuntu.com bionic-updates InRelease [88.7 kB]
Hit:6 http://ppa.launchpad.net/ubuntu-pi-flavour-makers/ppa/ubuntu bionic InRelease
Hit:7 http://ports.ubuntu.com bionic-security InRelease
Hit:8 http://ports.ubuntu.com bionic-backports InRelease
Get:9 http://ports.ubuntu.com bionic-updates/main armhf Packages [508 kB]
Get:10 http://ports.ubuntu.com bionic-updates/universe armhf Packages [812 kB]
Reading package lists... Done
W: GPG error: file:/opt/raspi-apt-repo/ubuntu18 ./ Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 722D2AFAD8BAD548
E: The repository 'file:/opt/raspi-apt-repo/ubuntu18 ./ Release' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.
可以看到, apt 还是没有读取 Packages 文件,原因是 The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 722D2AFAD8BAD548
也就是说 InRelease
/ Release.gpg
虽然签名了,但由于这个签名所用的公钥没有被接受(NO_PUBKEY 722D2AFAD8BAD548)
解决办法有三个:
- 用户端在执行 apt update 的时候,添加
--allow-insecure-repositories
选项;在执行 apt install pkg的时候,添加--allow-unauthenticated
选项
后面这个
--allow-unauthenticated
不加也可以,但 apt 会报告如下告警:
WARNING: The following packages cannot be authenticated!
foo bar baz
Install these packages without verification [y/N]?
需要输入 y 才能成功安装软件包(如果是自动化脚本执行安装的话,
apt update -y foo
这样只添加 -y 是不够的,必须增加上面的选项)
第二个方法是在用户侧修改仓库的配置,改为
deb [trusted=yes] http://foo.example.com/repos/raspbian8 ./
,注意这里添加了trusted=yes
选项 .(这里的修改方法是只对此仓库添加信任选项;网上也有人说在 /etc/apt/apt.conf.d 下面某个文件里面添加APT::Get::AllowUnauthenticated “true"
和Acquire::AllowInsecureRepositories "true"
这样两行,但这样影响的所有仓库,从安全的角度来说并不推荐)第三个方法首先要服务侧将其公钥导出 (
gpg --export --armor <uid> -o my-repo.gpg-key.asc
)并且以某种方式将这个 my-repo.gpg-key.asc 文件提供给用户( 比如公布在仓库根目录让用户手工下载);然后用户将此公钥导入表示他信任此公钥:sudo apt-key add my-repo.gpg-key.asc
链接
- Create Your Own Local apt Repository to Avoid “Dependency Hell” - Stiri It ,Servere Linux , Servicii Linux
- Easy APT Repository · Iain R. Learmonth
- DebianRepository/Format - Debian Wiki
- DebianRepository/Setup - Debian Wiki
- DebianRepository/UseThirdParty - Debian Wiki
- SecureApt - Debian Wiki
- apt-secure(8) — apt — Debian stretch — Debian Manpages
- GPG入门教程 - 阮一峰的网络日志
- GnuPG 入门教程 | IterNull Blog
- GPG入门 - 简书
- Setting up a personal secure apt repository
- 15.3. Creating a Package Repository for APT - The Debian Administrator's Handbook
- Aptly - A Debian Repository Management Tool
如何创建一个简单 APT 仓库的更多相关文章
- 如何创建一个简单的Visual Studio Code扩展
注:本文提到的代码示例下载地址>How to create a simple extension for VS Code VS Code 是微软推出的一款轻量级的代码编辑器,免费,开源,支持多种 ...
- 《Entity Framework 6 Recipes》翻译系列 (3) -----第二章 实体数据建模基础之创建一个简单的模型
第二章 实体数据建模基础 很有可能,你才开始探索实体框架,你可能会问“我们怎么开始?”,如果你真是这样的话,那么本章就是一个很好的开始.如果不是,你已经建模,并在实体分裂和继承方面感觉良好,那么你可以 ...
- 如何创建一个简单的C++同步锁框架(译)
翻译自codeproject上面的一篇文章,题目是:如何创建一个简单的c++同步锁框架 目录 介绍 背景 临界区 & 互斥 & 信号 临界区 互斥 信号 更多信息 建立锁框架的目的 B ...
- Windows 8.1 应用再出发 (WinJS) - 创建一个简单项目
前面几篇我们介绍了如何利用 C# + XAML 完成Windows Store App 功能的实现,接下来的几篇我们来看看如何利用 Html + WinJS 来完成这些功能. 本篇我们使用WinJS ...
- ADF_General JSF系列1_创建一个简单的JSF Application
2015-02-17 Creatd By BaoXinjian
- IntelliJ IDEA 15 部署Tomcat及创建一个简单的Web工程
一.部署Tomcat 二.创建一个简单的Web工程 2.1创建一个新工程 创建一个新工程 设置JDK及选择Web Application (创建的是Web工程) 点击Next,选择工作空间,起个工程名 ...
- Symfony2之创建一个简单的web应用
Symfony2——创建bundle bundle就像插件或者一个功能齐全的应用,我们在应用层上开发的应用的所有代码,包括:PHP文件.配置文件.图片.css文件.js文件等都会包含在bu ...
- 如何创建一个简单的struts2程序
如何创建一个简单的Struts2程序 “计应134(实验班) 凌豪” 1.创建一个新的Web项目test(File->new->Web Project) 2.Struts2框架的核心配置文 ...
- 使用 CodeIgniter 创建一个简单的 Web 站点
原文:使用 CodeIgniter 创建一个简单的 Web 站点 参考源自: http://www.ibm.com/developerworks/cn/web/wa-codeigniter/index ...
随机推荐
- Java正则表达式验证IP,邮箱,电话
引言 java中我们会常用一些判断如IP.电子邮箱.电话号码的是不是合法,那么我们怎么来判断呢,答案就是利用正则表达式来判断了,废话不多说,下面就是上代码. 1:判断是否是正确的IP 1 ...
- 记一次收集APP native崩溃信息
最近在学习 极客时间Android开发高手课 老师推荐了Breakpad开源库来采集native 的crash1.为什么要使用Google Breakpad? 我们在开发过程中,Android JNI ...
- MySQL中的group_concat函数的使用
本文通过实例介绍了MySQL中的group_concat函数的使用方法,比如select group_concat(name) . MySQL中group_concat函数 完整的语法如下: grou ...
- 总结了11条,我对Python 装饰器的理解
对于每一个学习 Python 的同学,想必对 @ 符号一定不陌生了,正如你所知, @ 符号是装饰器的语法糖,@符号后面的函数就是我们本文的主角:装饰器. 装饰器放在一个函数开始定义的地方,它就像一顶帽 ...
- 用Python程序批量删除excel里面的图片
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: Rhinoceros PS:如有需要Python学习资料的小伙伴可以 ...
- 何为http?何为RPC?
RPC(即Remote Procedure Call,远程过程调用)和HTTP(HyperText Transfer Protocol,超文本传输协议)他们最本质的区别,就是RPC主要工作在TCP协议 ...
- 【微信小程序】e.currentTarget和e.target
什么是事件 事件是视图层到逻辑层的通讯方式. 事件可以将用户的行为反馈到逻辑层进行处理. 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数. 事件对象可以携带额外信息,如 id ...
- nRF24L01+启用自动应答ACK及自动重发的实战分享
ACK模式(自动应答)功能的官方说明 当用W_TX_PAYLOAD命令对发送端TX FIFO写数据时,将数据打包后,数据包中包控制字段NO_ACK 标志位复位.接收端接收到一帧有效数据后, 产生RX_ ...
- 【cf915】E. Physical Education Lessons(线段树)
传送门 简单的线段树区间修改区间查询,但是因为数据范围过大,所以采用动态开点的方法(注意一下空间问题). 也可以直接对询问区间的端点离散化然后建树,这种方法时间复杂度和空间复杂度都比较优秀. 给出动态 ...
- eslint代码规范检测
1.如果在 vue init webpack projectname 时选择了eslint(Yes),则 npm uninstall eslint 2.在webpack.base.conf.js里 ...