From 85577c1d6ceab0812a37286074a3c9dad08ad9d2 Mon Sep 17 00:00:00 2001 From: Pietro Abate Date: Wed, 22 May 2024 15:24:39 +0200 Subject: [PATCH] packages pre-19.1: add apt repository for debian packages --- .../ci/pipelines/octez_release_tag_test.yml | 54 +++++++ ci/bin/release_tag.ml | 54 +++++++ scripts/ci/create_debian_repo.sh | 144 ++++++++++++++++++ scripts/ci/prepare-apt-repo.sh | 17 +++ scripts/packaging/Readme.md | 23 ++- scripts/packaging/Release.conf | 4 + scripts/packaging/apt-key-gcloud.gpg | 44 ++++++ scripts/packaging/package-signing-key.asc | 41 +++++ 8 files changed, 380 insertions(+), 1 deletion(-) create mode 100755 scripts/ci/create_debian_repo.sh create mode 100755 scripts/ci/prepare-apt-repo.sh create mode 100644 scripts/packaging/Release.conf create mode 100644 scripts/packaging/apt-key-gcloud.gpg create mode 100644 scripts/packaging/package-signing-key.asc diff --git a/.gitlab/ci/pipelines/octez_release_tag_test.yml b/.gitlab/ci/pipelines/octez_release_tag_test.yml index dfde71c1a846..70252c39c0b7 100644 --- a/.gitlab/ci/pipelines/octez_release_tag_test.yml +++ b/.gitlab/ci/pipelines/octez_release_tag_test.yml @@ -205,3 +205,57 @@ gitlab:release: script: - ./scripts/ci/restrict_export_to_octez_source.sh - ./scripts/ci/gitlab-release.sh + +apt_repo_debian_bookworm: + image: debian:bookworm + stage: prepare_release + tags: + - gcp + needs: + - oc.build:dpkg:amd64 + dependencies: + - oc.build:dpkg:amd64 + before_script: + - . ./scripts/version.sh + - ./scripts/ci/prepare-apt-repo.sh + script: + - ./scripts/ci/create_debian_repo.sh debian bookworm + variables: + ARCHITECTURES: amd64 + GNUPGHOME: $CI_PROJECT_DIR/.gnupg + +apt_repo_ubuntu_focal: + image: public.ecr.aws/lts/ubuntu:20.04_stable + stage: prepare_release + tags: + - gcp + needs: + - oc.build:dpkg:amd64 + dependencies: + - oc.build:dpkg:amd64 + before_script: + - . ./scripts/version.sh + - ./scripts/ci/prepare-apt-repo.sh + script: + - ./scripts/ci/create_debian_repo.sh ubuntu focal + variables: + ARCHITECTURES: amd64 + GNUPGHOME: $CI_PROJECT_DIR/.gnupg + +apt_repo_ubuntu_jammy: + image: public.ecr.aws/lts/ubuntu:22.04_stable + stage: prepare_release + tags: + - gcp + needs: + - oc.build:dpkg:amd64 + dependencies: + - oc.build:dpkg:amd64 + before_script: + - . ./scripts/version.sh + - ./scripts/ci/prepare-apt-repo.sh + script: + - ./scripts/ci/create_debian_repo.sh ubuntu jammy + variables: + ARCHITECTURES: amd64 + GNUPGHOME: $CI_PROJECT_DIR/.gnupg diff --git a/ci/bin/release_tag.ml b/ci/bin/release_tag.ml index a8df04b50d91..a6967550ce2a 100644 --- a/ci/bin/release_tag.ml +++ b/ci/bin/release_tag.ml @@ -40,6 +40,28 @@ type release_tag_pipeline_type = | Beta_release_tag | Non_release_tag +(* Push .deb artifacts to storagecloud apt repository. *) +let job_apt_repo ?rules ~__POS__ ~name ?(stage = Stages.prepare_release) + ?dependencies ?(archs = [Amd64]) ~image script : tezos_job = + let variables = + [ + ( "ARCHITECTURES", + String.concat " " (List.map Tezos_ci.arch_to_string_alt archs) ); + ("GNUPGHOME", "$CI_PROJECT_DIR/.gnupg"); + ] + in + job + ?rules + ?dependencies + ~__POS__ + ~stage + ~name + ~image + ~before_script: + (before_script ~source_version:true ["./scripts/ci/prepare-apt-repo.sh"]) + ~variables + script + (** Create an Octez release tag pipeline of type {!release_tag_pipeline_type}. If [test] is true (default is [false]), then the Docker images are @@ -90,6 +112,30 @@ let octez_jobs ?(test = false) release_tag_pipeline_type = in let job_build_dpkg_amd64 = job_build_dpkg_amd64 () in let job_build_rpm_amd64 = job_build_rpm_amd64 () in + let job_apt_repo_ubuntu_jammy = + job_apt_repo + ~__POS__ + ~name:"apt_repo_ubuntu_jammy" + ~dependencies:(Dependent [Artifacts job_build_dpkg_amd64]) + ~image:Images.ubuntu_jammy + ["./scripts/ci/create_debian_repo.sh ubuntu jammy"] + in + let job_apt_repo_ubuntu_focal = + job_apt_repo + ~__POS__ + ~name:"apt_repo_ubuntu_focal" + ~dependencies:(Dependent [Artifacts job_build_dpkg_amd64]) + ~image:Images.ubuntu_focal + ["./scripts/ci/create_debian_repo.sh ubuntu focal"] + in + let job_apt_repo_debian_bookworm = + job_apt_repo + ~__POS__ + ~name:"apt_repo_debian_bookworm" + ~dependencies:(Dependent [Artifacts job_build_dpkg_amd64]) + ~image:Images.debian_bookworm + ["./scripts/ci/create_debian_repo.sh debian bookworm"] + in let job_gitlab_release_or_publish = let dependencies = Dependent @@ -125,7 +171,15 @@ let octez_jobs ?(test = false) release_tag_pipeline_type = ] @ match (test, release_tag_pipeline_type) with + (* for the moment the apt repository are not official, so we do not add to the release + pipeline . *) | false, Release_tag -> [job_opam_release] + | true, Release_tag -> + [ + job_apt_repo_debian_bookworm; + job_apt_repo_ubuntu_focal; + job_apt_repo_ubuntu_jammy; + ] | _ -> [] (** Create an etherlink release tag pipeline of type {!release_tag_pipeline_type}. *) diff --git a/scripts/ci/create_debian_repo.sh b/scripts/ci/create_debian_repo.sh new file mode 100755 index 000000000000..58a2cb240542 --- /dev/null +++ b/scripts/ci/create_debian_repo.sh @@ -0,0 +1,144 @@ +#!/bin/sh + +set -eu + +# Create the APT repository for debian packages and sign it using +# the private key available as ENV variable. + +# uses : +# - scripts/packaging/Release.conf for release metadata +# - scripts/packaging/key.asc as the repository pub key + +# expected env vars +# - ARCHITECTURES +# - GPG_PASSPHRASE +# - GPG_KEY_ID + +if [ $# -lt 2 ]; then + cat << EOF +Usage: $0 + +: The linux distribution, eg. debian or ubuntu + +: The release of the Linux distribution, e.g. 'jammy', 'focal', 'bookworm'. +This argument can be repeated to build for multiple releases. + +Set the ARCHITECTURES env variable of packages built for +multiple architectures are available. Eg 'amd64 arm64' +EOF + exit 1 +fi + +# shellcheck source=./scripts/ci/octez-release.sh +. ./scripts/ci/octez-release.sh + +ARCHITECTURES=${ARCHITECTURES:-"amd64"} + +# The linux distribution for which we are creating the apt repository +# E.g. 'ubuntu' or 'debian' +DISTRIBUTION=${1} +shift +# The release of the linux distribution for which +# we are creating the apt repository +# E.g. 'jammy focal', 'bookworm' +RELEASES=$* + +# make available the private key for signing the release file +echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import -- + +BUCKET="$GCP_LINUX_PACKAGES_BUCKET" + +oldPWD=$PWD + +# if it's a release tag, then it can be a RC release or a final release +if [ -n "${gitlab_release_no_v}" ]; then + # It a release tag, this can be either a real or test release + if [ -n "${gitlab_release_rc_version}" ]; then + # Release candidate + TARGETDIR="public/RC/$DISTRIBUTION" + else + # Release + TARGETDIR="public/$DISTRIBUTION" + fi +else + # Not a release tag. This is strictly for testing + if [ ! "$CI_COMMIT_REF_PROTECTED" = "true" ]; then + if [ "$CI_COMMIT_REF_NAME" = "RC" ]; then + echo "Cannot create a repository for a branch named 'RC'" + exit 1 + else + TARGETDIR="public/$CI_COMMIT_REF_NAME/$DISTRIBUTION" + fi + else + if [ "$CI_COMMIT_REF_NAME" = "master" ]; then + TARGETDIR="public/master/$DISTRIBUTION" + else + echo "Cannot create a repository for a protected branch that \ + is not associated with a release tag or it's master" + exit 1 + fi + fi +fi + +mkdir -p "$TARGETDIR/dists" + +for architecture in $ARCHITECTURES; do # amd64, arm64 ... + for release in $RELEASES; do # unstable, jammy, focal ... + echo "Setting up APT repository for $DISTRIBUTION / $release / $architecture" + + # create the apt repository root directory and copy the public key + cp scripts/packaging/package-signing-key.asc "$TARGETDIR/octez.asc" + + target="dists/${release}/main/binary-${architecture}" + + mkdir -p "$TARGETDIR/${target}/" + + for file in packages/"${DISTRIBUTION}/${release}"/*.deb; do + cp "$file" "$TARGETDIR/${target}/" + echo "Adding package $file to $TARGETDIR/${target}/" + done + + cd "$TARGETDIR" + echo "Create the Packages file" + apt-ftparchive packages "dists/${release}" > "${target}/Packages" + gzip -k -f "${target}/Packages" + + echo "Create the Release files using a static configuration file" + apt-ftparchive \ + -o APT::FTPArchive::Release::Codename="$release" \ + -o APT::FTPArchive::Release::Architectures="noarch $architecture" \ + -c "$oldPWD/scripts/packaging/Release.conf" release \ + "dists/${release}/" > "dists/${release}/Release" + + # sign the release file using GPG. Since gpg is run in a script we need to set + # some variables and extra options to make it work. The InRelease file contains + # both the gpg signature and the content of the Release file. + echo "Sign the release file" + echo "$GPG_PASSPHRASE" | + gpg --batch --passphrase-fd 0 --pinentry-mode loopback \ + -u "$GPG_KEY_ID" --clearsign \ + -o "dists/${release}/InRelease" "dists/${release}/Release" + done + # back to base + cd "$oldPWD" +done + +if [ "$CI_COMMIT_REF_PROTECTED" = "true" ]; then + echo "### Logging into protected repo ..." + echo "${GCP_PROTECTED_SERVICE_ACCOUNT}" | base64 -d > protected_sa.json + gcloud auth activate-service-account --key-file=protected_sa.json +else + echo "### Logging into standard repo ..." + # Nothing to do +fi + +GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token) +export GOOGLE_OAUTH_ACCESS_TOKEN + +echo "Push to $BUCKET" + +gsutil -m cp -r public/* gs://"${BUCKET}" + +echo "Check the content of the bucket gs://${BUCKET}" + +gcloud storage ls -R gs://"${BUCKET}/*" diff --git a/scripts/ci/prepare-apt-repo.sh b/scripts/ci/prepare-apt-repo.sh new file mode 100755 index 000000000000..1823262bacba --- /dev/null +++ b/scripts/ci/prepare-apt-repo.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# Install depedendencies for the apt_repo job + +export DEBIAN_FRONTEND=noninteractive + +apt-get update +apt-get install -y apt-utils debsigs gnupg curl + +# Install google-cloud-cli so we can upload packages to the Google Cloud Storage bucket. +gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg \ + scripts/packaging/apt-key-gcloud.gpg + +echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list + +apt-get update +apt-get -y install google-cloud-cli diff --git a/scripts/packaging/Readme.md b/scripts/packaging/Readme.md index 2cf2e3009605..47ec850d579c 100644 --- a/scripts/packaging/Readme.md +++ b/scripts/packaging/Readme.md @@ -1,4 +1,3 @@ - # Debian packaging The `octez` directory contains the Debian package specification for Octez. @@ -46,3 +45,25 @@ same environment used in the CI. The script builds the Debian packages. The pre-requisite to this script is to run `make build-deps` and ensure all runtime dependencies as well as the build dependencies needed to build the Debian packages are correctly installed. + +# Apt Repository Management + +The file [package-signing-key.asc] contains the public key associated to +the gpg signing key stored in the CI. The CI / Release Manager will +sign the repository using this key, while the user can download +the public key to verify that the signature is indeed valid. + +This user must download the key and make it known to apt. + +Ex. + + curl "/octez.asc" \ + | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/octez.gpg + + +### gutils + +We use the gutils suite to upload the apt repository to the +google cloud storage. The key used to install those packages +is committed in the repository and can be refreshed from this url +`https://packages.cloud.google.com/apt/doc/apt-key.gpg` diff --git a/scripts/packaging/Release.conf b/scripts/packaging/Release.conf new file mode 100644 index 000000000000..4021cc83225d --- /dev/null +++ b/scripts/packaging/Release.conf @@ -0,0 +1,4 @@ +APT::FTPArchive::Release::Origin "Octez"; +APT::FTPArchive::Release::Label "Octez Debian Repository"; +APT::FTPArchive::Release::Components "main"; +APT::FTPArchive::Release::Description "Debian Repository for Octez"; diff --git a/scripts/packaging/apt-key-gcloud.gpg b/scripts/packaging/apt-key-gcloud.gpg new file mode 100644 index 000000000000..e6e856f0c806 --- /dev/null +++ b/scripts/packaging/apt-key-gcloud.gpg @@ -0,0 +1,44 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=UL8X +-----END PGP PUBLIC KEY BLOCK----- diff --git a/scripts/packaging/package-signing-key.asc b/scripts/packaging/package-signing-key.asc new file mode 100644 index 000000000000..1d8bbf97dbe9 --- /dev/null +++ b/scripts/packaging/package-signing-key.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +1Sl1UvXGKRzuFddDleCGIx+4UnJc2FOm0iM3lQ== +=9Ckp +-----END PGP PUBLIC KEY BLOCK----- -- GitLab