使用 reprepro 制作 apt 源


使用 apt 分发 deb 软件包。

基本工作流程:

  1. 构建机器 执行 debuild 构建 deb 包;

  2. 构建机器 执行 dput 上传包到 APT 服务器(通过 Nginx WebDAV 上传文件,dput 也支持 ftp、scp 等方式);

  3. APT 服务器 执行 reprepro 添加 deb 包。

  4. 通过 HTTP POST 请求 APT 服务器 通过 CGI 执行 Shell。

系统环境:Debian 12。

Ubuntu 22.04 dput 版本太低。

安装

sudo apt install apache2-utils gnupg fcgiwrap nginx reprepro
# docker 中无 systemd 需要安装 spawn-fcgi
  • apache2-utils 密码文件工具;

  • gnupg GPG 签名;

  • fcgiwrap 通过 HTTP 请求执行 Shell;

  • nginx 通过 WebDAV 上传,通过 CGI 执行 Shell,提供静态 apt 数据访问;

  • reprepro 生成 apt 数据。

reprepro

创建目录

  1. 构建的 deb 包等添加到对应的 incoming 目录;

  2. reprepro 从 incoming 读取添加到 apt 元数据中。

# debian/ubuntu apt 源数据
mkdir -p /var/www/packages/{debian,ubuntu}/conf
touch /var/www/packages/{debian,ubuntu}/conf/{distributions,uploaders,options,incoming}

# debian 12 (bookworm) / 11 (bullseye) 新到来的 deb 包
mkdir -p /var/www/packages/incoming/debian/{bookworm,bullseye}
mkdir -p /var/www/packages/incoming/debian/{bookworm-temp,bullseye-temp}

# ubuntu 22.04 (jammy) / 20.04 (focal) 新到来的 deb 包
mkdir -p /var/www/packages/incoming/ubuntu/{jammy,focal}
mkdir -p /var/www/packages/incoming/ubuntu/{jammy-temp,focal-temp}

目录权限

目录权限问题:

  1. Nginx WebDAV 使用 www-data 用户在 incoming 目录下创建文件;

  2. reprepro 命令需要读取、删除 incoming 目录下的文件;

  3. reprepro 需要读取 GPG 私钥,通过私钥对 deb 数据进行签名。

需要统一这三者的权限为 www-data

sudo chown www-data:www-data -R /var/www/packages

GPG 密钥对

用户 www-data 无法直接登录,可通过当前用户生成 GPG 密钥,再导入到 www-data 用户。

生成 GPG 密钥用于签名包:

# 生成 GPG 密钥
gpg --full-generate-key
# 列出 GPG
gpg --list-secret-key --with-subkey-fingerprint
  • 为避免签名脚本处理密码输入,私钥不要添加密钥,输入密码时直接回车即可。

导入私钥到 www-data 用户:

sudo mkdir --mode=700 /var/www/.gnupg
sudo chown www-data:www-data /var/www/.gnupg

# 从当前用户导出私钥,并通过管道导入给用户 www-data
gpg --export-secret-key --armor E81E8376705C4ECEE542E26E002555760B157806 \
    | sudo -u www-data gpg --import --yes --batch --import-options restore

# 检查
sudo -u www-data gpg --list-secret-keys

导出 GPG 公钥,提供给客户端下载:

sudo -u www-data gpg --armor --output /var/www/packages/public.gpg --export E81E8376705C4ECEE542E26E002555760B157806
  • 问题: gpg: agent_genkey failed: 权限不够

    检查 tty 权限是否为当前用户:

    ls -l $(tty)

可以考虑使用其他用户生成再导入。

配置

Debian

Debian 相关配置位于目录 /var/www/packages/debian

配置发行版 debian 12 和 debian 11
conf/distributions
Origin: packages.notfound.cn
Label: notfound
Codename: bookworm
Architectures: amd64 arm64
Components: main
Description: Notfound apt repository
SignWith: E81E8376705C4ECEE542E26E002555760B157806
Uploaders: uploaders

Origin: packages.notfound.cn
Label: notfound
Codename: bullseye
Architectures: amd64 arm64
Components: main
Description: Notfound apt repository
SignWith: E81E8376705C4ECEE542E26E002555760B157806
Uploaders: uploaders
  • Codename 通过命令 lsb_release --short --codename 可以查看:

    • bookworm: Debian 12

    • bullseye: Debian 11

  • Architectures 体系结构,命令 dpkg-architecture -L 可列出所有值;

  • SignWith GPG KEY ID,用于 GPG 签名。

  • Uploaders: 可选,uploaders 文件,配置上传者规则。

配置 uploaders(可选)

指定上传者通过 GPG 签名的内容允许被添加到 APT。

添加 conf/uploaders

conf/uploaders
allow * by key 0B157806
allow * by key 359C1291
  • 允许指定的 GPG KEY ID,这里需要填写短格式 KEY ID,同时需要将 GPG 公钥导入到服务器。

相关 GPG 命令:

# 查看短格式 KEY ID
gpg --list-key --keyid-format short
# 导出公钥
gpg --export --armor --output public.gpg 07CE1788C0D07551532C8C871A6B2334359C1291
# 导入公钥
gpg --import public.gpg

# 或者相同机器下二合一
gpg --export --armor 07CE1788C0D07551532C8C871A6B2334359C1291 \
    | sudo -u www-data gpg --import --yes --batch
配置 reprepro 参数
conf/options
verbose
basedir /var/www/packages/debian
ask-passphrase
  • verbose 显示详情;

  • basedir Debian 包目录;

  • ask-passphrase 需要输入 gpg 密码。

配置 incoming
conf/incoming
Name: bookworm
IncomingDir: /var/www/packages/incoming/debian/bookworm
TempDir: /var/www/packages/incoming/debian/bookworm-temp
Allow: bookworm
Default: bookworm
Permit: unused_files
Cleanup: unused_files on_deny on_error

Name: bullseye
IncomingDir: /var/www/packages/incoming/debian/bullseye
TempDir: /var/www/packages/incoming/debian/bullseye-temp
Allow: bullseye
Default: bullseye
Permit: unused_files
Cleanup: unused_files on_deny on_error
  • Name 规则集名称,执行 reprepro 命令时使用;

  • IncomingDir 用来扫描 .changes 文件的目录;

  • TempDir 处理过程中的临时目录;

  • Allow 允许的发行版本;

  • Default 未通过 Allow 参数时的默认发行版;

  • Permit 允许的出现未使用的文件(unused_files);

  • Cleanup 未使用(unused_files)、拒绝处理(on_deny)、处理出错(on_error)时文件都会被清理。

Ubuntu

和 Debian 类似。

添加 deb 包

操作前切换用户:

sudo su - www-data --shell /bin/bash

方法一

通过 includedeb 直接添加:

reprepro --basedir /var/www/packages/debian includedeb bookworm ~/bookworm/debhello_0.0-1_amd64.deb

方法二

将 .changes 以及 .changes 中指定的相关文件放到 incoming 目录,执行:

reprepro --basedir /var/www/packages/debian processincoming bookworm
  • 规则集名称为 bookworm

这些文件可以在 debuild 后通过 dput 上传,见后文。

reprepro 命令

# 列出
reprepro list bookworm
# 移除
reprepro remove bookworm debhello
# 删除所有不在发行版中的包数据库
reprepro clearvanished

Nginx

Nginx 的功能:

  1. 提供 apt 源数据;

  2. 配置 WebDAV 功能,以支持 dput 上传 deb 包;

  3. 配置 FastCGI 功能,以支持 http 方式触发 reprepro 处理 deb 包。

配置

Basic 认证

sudo mkdir /etc/nginx/htpasswd/
sudo htpasswd /etc/nginx/htpasswd/packages packages
sudo htpasswd /etc/nginx/htpasswd/packages jenkins
  • 为用户 packages/jenkins 生成密码数据

WebDAV 配置

WebDAV Nginx 相关配置:

/etc/nginx/packages_dav_params
limit_except GET HEAD {
    auth_basic              "packages.notfound.cn";
    auth_basic_user_file    /etc/nginx/htpasswd/packages;
}

client_body_temp_path   /var/www/packages/client_temp;
create_full_put_path    off;
dav_access              user:rw group:rw all:r;
dav_methods             PUT DELETE MKCOL COPY MOVE;
  • limit_except 非 GET 和 HEAD 都需要认证;

  • create_full_put_path 禁止创建新的目录,因此需要提前创建目录。

FastCGI

FastCGI 执行脚本文件:

/usr/local/bin/reprepro.cgi
#!/bin/bash

function error_400() {
    echo 'Status: 400 Bad Request'
    echo 'Content-Type: text/plain'
    echo ''
    echo "$1"
    exit 1
}

if [ "$REQUEST_METHOD" != 'POST' ]; then
    error_400 'only support POST'
fi

IFS='/'; set -- $REQUEST_URI
os=$3
distribution=$4

case "$os/$distribution" in
    debian/bookworm)
        ;;
    debian/bullseye)
        ;;
    ubuntu/jammy)
        ;;
    ubuntu/focal)
        ;;
    *)
        error_400 "unsupport $os/$distribution"
        ;;
esac

message=$(reprepro --basedir /var/www/packages/$os processincoming $distribution 2>&1)
if [ $? -ne 0 ]; then
    echo 'Status: 500 Internal Server header'
    echo 'Content-Type: text/plain'
    echo ''
    echo $message
    exit 1
fi

echo 'Status: 200 OK'
echo 'Content-Type: text/plain'
echo ''
echo 'success'
echo "$message"
exit 0

# vim: set tabstop=4 shiftwidth=4 expandtab

添加执行权限:

sudo a+x /usr/local/bin/reprepro.cgi

添加 FastCGI Nginx 配置:

/etc/nginx/packages_fastcgi_params
auth_basic              "packages.notfound.cn";
auth_basic_user_file    /etc/nginx/htpasswd/packages;

gzip off;

include         fastcgi_params;
fastcgi_pass    unix:/run/fcgiwrap.socket;

fastcgi_param SCRIPT_FILENAME /usr/local/bin/reprepro.cgi;

packages 配置

域名 packages.notfound.cn 配置,通过 include 指令整合上文中的配置文件:

/etc/nginx/conf.d/packages.conf
server {
    listen 80;
    # listen 443 ssl;
    server_name packages.notfound.cn;

    # ssl_certificate     /etc/nginx/cert.d/notfound.cn.crt;
    # ssl_certificate_key /etc/nginx/cert.d/notfound.cn.key;
    # ssl_protocols       TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

    root /var/www/packages;

    access_log  /var/log/nginx/packages.access.log;
    error_log   /var/log/nginx/packages.error.log;

    client_max_body_size 100M;

    location / {
        autoindex on;
    }

    location ~ /(.*)/(conf|db) {
        deny all;
    }

    # debian: bookworm, bullseye
    location ~ ^/incoming/debian/(bookworm|bullseye)/cgi-bin/reprepro.cgi$ {
        include packages_fastcgi_params;
    }
    location ~ ^/incoming/debian/(bookworm|bullseye)/ {
        autoindex   on;
        include     packages_dav_params;
    }

    # ubuntu: jammy, focal
    location ~ ^/incoming/ubuntu/(jammy|focal)/cgi-bin/reprepro.cgi$ {
        include packages_fastcgi_params;
    }
    location ~ ^/incoming/ubuntu/(jammy|focal)/ {
        autoindex   on;
        include     packages_dav_params;
    }
}
  • root 目录为 packages 目录;

  • 开启了目录浏览功能;

  • 禁止访问 confdb

  • 通过 packages_dav_params 配置 Nginx WebDAV,只允许访问指定的目录;

  • 通过 packages_fastcgi_params 配置 Nginx FastCGI,只允许访问指定的目录;

  • client_max_body_size 最大上传文件。

目录 /var/www/packages/incoming/ 用于上传 deb 相关文件,需要提前创建。

dput

通过 dput 可上传打包的文件。

dput 支持 ftp、http(s)、scp、sftp、rsync 和 local 方式上传文件。

配置

Debian 12 环境。

$HOME/.config/dput/dput.cf
# vim: set tabstop=4 shiftwidth=4 expandtab
##################### Debian 12 ####################
# mkdir -p $HOME/.config/dput
# curl -o $HOME/.config/dput/dput.cf -fsSL http://packages.notfound.cn/incoming/dput.cf.txt

[DEFAULT]
fqdn                    = packages.notfound.cn
login                   = jenkins
method                  = http
default_host_main       = bookworm
allow_unsigned_uploads  = true

[bookworm]
incoming = /incoming/debian/bookworm

[bullseye]
incoming = /incoming/debian/bullseye

[jammy]
incoming = /incoming/ubuntu/jammy

[focal]
incoming = /incoming/ubuntu/focal
  • default_host_main 默认配置;

  • fqdn 服务器;

  • login 登录用户名;

  • method 支持 ftphttp(s)scpsftprsynclocal

  • incoming 上传的目标目录;

  • allow_unsigned_uploads 可选,允许上传无 GPG 签名的文件。

可以在 Nginx 服务端保存一份,方便部署时使用,可以考虑保存到:/var/www/packages/incoming/dput.cf.txt

参考: man dput.cf

使用

假设已经构建 deb 包。

# 使用默认 host
dput debhello_0.0-1_amd64.changes
# 或者指定 host
dput bookworm debhello_0.0-1_amd64.changes

查看 host 列表:

dput --host-list

客户端

添加 GPG Key:

sudo mkdir -p /etc/apt/keyrings
sudo curl -sSL http://packages.notfound.cn/public.gpg -o /etc/apt/keyrings/notfound.asc

添加 apt 源:

/etc/apt/sources.list.d/notfound.list
# debian 12
deb [signed-by=/etc/apt/keyrings/notfound.asc] http://packages.notfound.cn/debian bookworm main

# deiban 11
deb [signed-by=/etc/apt/keyrings/notfound.asc] http://packages.notfound.cn/debian bullseye main

安装 debhello:

sudo apt update
sudo apt install debhello

优先级

如果其他 APT 源已经存在相同的包,可以通过 apt preferences 设置优先级,如,添加文件 /etc/apt/preferences.d/notfound :

Package: hello
Pin: release l=notfound,c=main
Pin-Priority: 900
  • Package 包名

  • Pin

  • llabel

  • cComponent

  • Pin-Priority 优先级,数字越大优先级越高

查询包的优先级:

apt-cache policy hello

参考:

包命名约定

deb 包名称相同但内容不同时,无法重复添加,即使是不同的发行版:

$ reprepro includedeb bullseye deb/bullseye/debhello_0.0-1_amd64.deb
deb/bullseye/debhello_0.0-1_amd64.deb: component guessed as 'main'
ERROR: 'deb/bullseye/debhello_0.0-1_amd64.deb' cannot be included as 'pool/main/d/debhello/debhello_0.0-1_amd64.deb'.
Already existing files can only be included again, if they are the same, but:
md5 expected: 937114b8826ea3441f2eb3a196db1a8d, got: 169429e1b925b065b866e714ffd10a09
sha1 expected: 1824644849af1b8cca7234a2406d0052163ae27d, got: bedd3f062023aef802e0ae153b2be31e351d8a9d
sha256 expected: 38749fd54428945ec9a93b01ea92c6e153b8592b7ebf786a322d6e7408817a8a, got: fcdc9cfc23f1ca8b5082e0d957ee225bc1219405ddbfc1aa2873088ca5076f89
size expected: 14392, got: 14512
There have been errors!

如果相同的源码需要打包到不同发行版 Codename,需要修改 debian/changelog 中的版本信息改变 deb 包名称。

Debian/Ubuntu 命名约定

通过变更日志查看现有的包命名规则:

apt changelog openjdk-17-jdk
apt changelog curl

结果:

# Debian 12 查看官方包命名:
openjdk-17 (17.0.11+9-1~deb12u1) bookworm-security; urgency=medium
curl (7.88.1-10+deb12u5) bookworm-security; urgency=high

# Debian 11 查看官方包命名:
openjdk-17 (17.0.11+9-1~deb11u1) bullseye-security; urgency=medium
curl (7.74.0-1.3+deb11u11) bullseye-security; urgency=high

# Ubuntu 22.04
openjdk-17 (17.0.10+7-1~22.04.1) jammy-security; urgency=high
curl (7.81.0-1ubuntu1.16) jammy-security; urgency=medium

# ubuntu 20.04
openjdk-17 (17.0.10+7-1~20.04.1) focal-security; urgency=high
curl (7.68.0-1ubuntu2.22) focal-security; urgency=medium

看上去并没有一个强制标识 codename 的统一规范。

可以参考 Naming Convention for Debian Packages 使用规则:

<package_name> (<upstream_version>-<debian_revision>+<dist_codename>)
# 如
debhello (0.0-1+bookworm)
debhello (0.0-1+bullseye)
  • package_name 包名

  • upstream_version 上游软件包版本

  • debian_revision Debian 修订版本

  • dist_codename 发行版 codename

修改 debian/changelog 后重新打包。