如何創建一個簡單 APT 倉庫


  1. 無廢話版本

需求: 有一堆 .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&lt; ==========================================
# 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
  1. 什么叫"簡單 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...)

  1. 創建簡單 APT 倉庫的方法

根據 DebianRepository/Setup - Debian Wiki ,有很多種工具可以用來創建 APT 倉庫,但創建簡單倉庫(trivial archive)的方法在該頁面就只看見一個,於是我就用了:

  1. 將所有 deb 放到同一個目錄(假設這個目錄為 /opt/raspi-apt-repos/raspbian8)
  2. 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 ./ 

補充說明:

  1. 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)

  2. 上面命令行中給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也會被認為是同名的包,其中一個會被忽略掉!)

  3. 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 倉庫里面這三個文件都有.

下面說說如何生成這兩個文件:

  1. 如果你跟我一樣不知道什么是GPG數字簽名,那么先仔細讀一下 GPG入門 - 簡書 或者 GnuPG 入門教程 | IterNull Blog ,大致知道密鑰/公鑰, 加密/簽名, 文本簽名/二進制簽名,分離式簽名等等概念
  2. 按照上面文檔描述的方法,生成自己的gpg key (gpg --gen-key)
  3. 生成Release.gpg: gpg --armor --detach-sign --sign -o Release.gpg Release
  4. 生成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)

解決辦法有三個:

  1. 用戶端在執行 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 是不夠的,必須增加上面的選項)

  1. 第二個方法是在用戶側修改倉庫的配置,改為 deb [trusted=yes] http://foo.example.com/repos/raspbian8 ./ ,注意這里添加了 trusted=yes 選項 .(這里的修改方法是只對此倉庫添加信任選項;網上也有人說在 /etc/apt/apt.conf.d 下面某個文件里面添加 APT::Get::AllowUnauthenticated “true"Acquire::AllowInsecureRepositories "true" 這樣兩行,但這樣影響的所有倉庫,從安全的角度來說並不推薦)

  2. 第三個方法首先要服務側將其公鑰導出 (gpg --export --armor <uid> -o my-repo.gpg-key.asc )並且以某種方式將這個 my-repo.gpg-key.asc 文件提供給用戶( 比如公布在倉庫根目錄讓用戶手工下載);然后用戶將此公鑰導入表示他信任此公鑰: sudo apt-key add my-repo.gpg-key.asc

  3. 鏈接



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM