Jenkins 构建 deb 包


Jenkins 构建 deb 包。

系统环境:Debian 12。

概览

Jenkins 构建 deb 大致流程,只涉及需要配置的服务或者程序:

Jenkins 构建 deb 概览

APT 缓存

APT 包管理

Jenkins

参考 搭建 Jenkins

Jenkis 节点

配置节点

  1. 安装基础工具:

    sudo apt install curl expect gnupg
    • expect 处理命令交互脚本。

  2. 配置 sbuild 环境,参考:sbuild 构建 deb 包

  3. 配置 dput,参考:dput 章节

Jenkins 相关脚本

tarball

生成 sbuild 需要的相关文件。

.jenkins/tarball.sh
#!/bin/bash -e

usage() {
    printf "Usage: %s -d distribution path\n" "$0"
    printf "    distribution: bookworm | jammy\n"
    exit 1
}

while getopts d: opt
do
    case $opt in
    d) distribution=$OPTARG;;
    ?) usage;;
    esac
done

shift $((OPTIND - 1))
git_repository=$1

case "$distribution" in
    bookworm);;
    jammy);;
    *) usage;;
esac

if [ "$git_repository" == "" ]; then
    git_repository='.'
fi

changelog="$git_repository/debian/changelog"

source=$(dpkg-parsechangelog --file="$changelog" --show-field Source)
# The version number of a package. The format is: [epoch:]upstream_version[-debian_revision]
version=$(dpkg-parsechangelog --file="$changelog"  --show-field Version)

if [[ $version == *+* ]]; then
    echo "invalid version: $version"
    usage
fi

orig_version=$(echo "$version" | sed -E 's/^([0-9]+:)?([^:-]+)(-.*)?$/\2/')
orig_name="${source}_${orig_version}"

git archive --format=tar.gz --prefix="${orig_name}/" --output="${orig_name}.orig.tar.gz" --remote="$git_repository/" HEAD
commit_id=$(git -C source rev-parse HEAD)

echo 'yes' | dch --changelog "$changelog" -v "$version+$distribution" --distribution "$distribution" "Release $commit_id."
dpkg-source -b "$git_repository"

# vim: set tabstop=4 shiftwidth=4 expandtab
  1. 生成 .orig.tar.gz 包;

  2. 添加 changelog,主要目的是为相同发行版不同版本设置不同 deb 包名;

  3. 生成 .debian.tar.xz.dsc

sbuild

构建 debina packages。

.jenkins/sbuild.sh
#!/bin/bash -e

usage() {
    printf "Usage: %s -d distribution -a architecture\n" "$0"
    printf "    distribution: bookworm | jammy\n"
    printf "    architecture: amd64 | arm64\n"
    exit 1
}

while getopts a:d: opt
do
    case $opt in
    a) architecture=$OPTARG;;
    d) distribution=$OPTARG;;
    ?) usage;;
    esac
done

case "$architecture" in
    amd64) opt_arch_all='--arch-all';;
    arm64) opt_arch_all='--no-arch-all';;
    *) usage;;
esac

case "$distribution" in
    bookworm);;
    jammy);;
    *) usage;;
esac

shift $((OPTIND - 1))

sbuild \
    "$opt_arch_all" \
    --arch="$architecture" \
    --dist="$distribution" \
    "$@"

# vim: set tabstop=4 shiftwidth=4 expandtab
  • 为避免包重复,amd64 环境使用 --arch-all 构建跨平台包,而 arm64 平台只构建当前平台相关的包。

添加额外的 apt 源需要修改该文件,如,添加 nodejs 源:

sbuild \
    --dist=bookworm \
    --extra-repository="deb http://deb.nodesource.com/node_20.x nodistro main" \
    --extra-repository-key="./nodesource.asc"
  • extra-repository-key 需要将 GPG 公钥下载到本地后通过文件路径添加。

Ubuntu amd64/i386 源位于 archive,其他如 arm64/riscv64 源位于 ports,在使用镜像加速时源路径不同,如:

# amd64
sbuild \
    --dist=jammy \
    --extra-repository="deb http://mirrors.cloud.tencent.com/ubuntu/ jammy universe" \
    --extra-repository="deb http://mirrors.cloud.tencent.com/ubuntu/ jammy-updates universe"

# arm64
sbuild \
    --dist=jammy \
    --extra-repository="deb http://mirrors.cloud.tencent.com/ubuntu-ports/ jammy universe" \
    --extra-repository="deb http://mirrors.cloud.tencent.com/ubuntu-ports/ jammy-updates universe"

dput

上传 deb 相关数据。

.jenkins/dput.exp
#!/usr/bin/expect -f

# dput.exp [host] <package(s).changes>
set password $env(DPUT_CREDS_PSW)
set host        [lindex $argv 0]
set changes     [lrange $argv 1 end]

set timeout 300

foreach change $changes {
    spawn dput -f $host $change

    expect {
        "Password for *:" {
            send "$password\r"
        }
        timeout {
            puts "error: timeout"
            exit 1
        }
    }
    expect eof
}

# vim: set tabstop=4 shiftwidth=4 expandtab
  • 通过环境变量传入密码。

reprepro

发送 HTTP 请求,添加包到 apt 源。

.jenkins/reprepro.sh
#!/bin/bash

usage() {
    printf "Usage: %s -d distribution\n" "$0"
    printf "    distribution: bookworm | jammy\n"
    exit 1
}

if [ "$DPUT_CREDS_USR" = '' ] || [ "$DPUT_CREDS_PSW" = '' ] || [ "$PACKAGES_URL" = '' ]; then
    printf "env: DPUT_CREDS_USR DPUT_CREDS_PSW PACKAGES_URL empty.\n"
    exit 1
fi

while getopts d: opt
do
    case $opt in
    d) distribution=$OPTARG;;
    ?) usage;;
    esac
done

case "$distribution" in
    bookworm)
        os=debian;;
    jammy)
        os=ubuntu;;
    *)
        usage;;
esac

curl --fail-with-body \
    --location \
    --request POST \
    --silent \
    --user "$DPUT_CREDS_USR:$DPUT_CREDS_PSW" \
    "$PACKAGES_URL/incoming/$os/$distribution/cgi-bin/reprepro.cgi"
  • 通过环境变量传入用户名、密码、和 packages 域名。

Jenkins

jenkins 构建流程:

.jenkins/Jenkinsfile
pipeline {
    agent none

    environment {
        PACKAGES_URL = 'http://packages.notfound.cn'
        DPUT_CREDS = credentials('dput-jenkins')
    }

    stages {
        stage('dist arch') {
            parallel {
                stage('bookworm amd64') {
                    agent {
                        label 'sbuild-bookworm-amd64'
                    }

                    steps {
                        cleanWs()
                        dir('source') {
                            git branch: 'sbuild', credentialsId: 'gitee-deploy-key', url: 'git@git.notfound.cn:gitee-pkgs/hello.git'
                        }
                        sh './source/.jenkins/tarball.sh -d bookworm source'
                        sh './source/.jenkins/sbuild.sh -d bookworm -a amd64 *.dsc'
                        sh './source/.jenkins/dput.exp bookworm *.changes'
                        sh './source/.jenkins/reprepro.sh -d bookworm'
                    }
                } // bookworm amd64

                stage('bookworm arm64') {
                    agent {
                        label 'sbuild-bookworm-arm64'
                    }

                    steps {
                        cleanWs()
                        dir('source') {
                            git branch: 'sbuild', credentialsId: 'gitee-deploy-key', url: 'git@git.notfound.cn:gitee-pkgs/hello.git'
                        }
                        sh './source/.jenkins/tarball.sh -d bookworm source'
                        sh './source/.jenkins/sbuild.sh -d bookworm -a arm64 *.dsc'
                        sh './source/.jenkins/dput.exp bookworm *.changes'
                        sh './source/.jenkins/reprepro.sh -d bookworm'
                    }
                } // bookworm arm64
            }
        }
    }
}