如何创建一个简单 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里面添加的配置就是debhttp://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中sleep和wait的区别
1.sleep方法是Thread类的静态方法: wait方法是Object类的成员方法 2.sleep方法使当前线程暂停执行指定的时间,让出cpu给其他线程,但是它的监控状态依然保持着,当指定的时间到 ...
- Docker容器 MySQL中文乱码解决方案
docker exec进入容器 sudo docker exec -it 588340b778f6 bash 执行以下命令,将 character-set-server=utf8 写入mysql配置文 ...
- swoole中使用task进程异步的处理耗时任务
我们知道,swoole中有两大进程,分别是 master 主进程和 manager 管理进程. 其中 master 主进程中会有一个主 reactor 线程和多个 reactor 线程,主要的作用就是 ...
- flex布局和边框阴影
"妄"眼欲穿-CSS之flex布局和边框阴影 妄:狂妄: 不会的东西只有怀着一颗狂妄的心,假装能把它看穿吧. flex布局 main axis:主轴:cross axis:交叉轴 ...
- Android studio down 的项目中文出现 乱码
发现down的项目file->open找到文件夹打开,里面少很多东西,像build.grade(好像拼错了). 这个问题是要file->import progect找到文件夹打开,as会自 ...
- JS基础语法---函数练习part1---5个练习
练习1:求两个数字的和:获取任意的两个数字的和 function getSum(x, y) { return x + y; } console.log(getSum(10, 20)); 练习2:求1- ...
- 「SAP技术」SAP MM 事务代码ME17的用法
SAP MM 事务代码ME17的用法 1,如下采购信息记录需要被归档: PIR号码,5300007816 2, ME12打上删除标记, 3, 事务代码ME17做归档 3.1 创建archive ...
- Dynamics 365 CE将自定义工作流活动程序集注册到磁盘并引用其他类库
我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...
- 一次Ubuntu16系统的找回root密码的过程
一 背景知识介绍 ubuntn系统的默认配置,超级用户root的密码是随机的,如果没提前对root用户的密码进行设置, 是不能直接通过root的身份来进行某些操作的,必须使用sudo命令,并通过普通用 ...
- SoapUI、Postman测试WebService
之前装了SoapUI,最近装了Postman,分别用它们测试了WebService,下面为用法. 测试的在线WebService(数据来源于中国气象局)Endpoint: http://www.web ...