Development

Generating a Deb Repository with Github Actions

By Kim Pepper โ€ข 9th November 2023

Good news! The Skpr CLI is now available from a secure deb repository.

This means you can install the Skpr CLI with a simple set of commands:

wget -q https://packages.skpr.io/apt/packages.skpr.io.pub -O- | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/packages.skpr.io.pub > /dev/null
echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.skpr.io.pub] https://packages.skpr.io/apt stable main" | sudo tee -a /etc/apt/sources.list.d/skpr.list > /dev/null
sudo apt update && sudo apt install skpr

From then on, the Skpr CLI will be updated automatically when you run apt upgrade.

How it works

In this post we will share how to create a self-hosted deb repository using Github Actions, a GPG keypair and Amazon S3.

Generate a GPG keypair

First, we need to generate a GPG keypair. This will be used to sign the packages in the repository.

Instead of repeating all the same steps, I recommend reading through the GitHub docs on Generating a new GPG key

You will need to export the private and public keys to use with GitHub actions.

Use the following commands to find your key ID:

gpg --list-secret-keys --keyid-format=long

Then assuming you set KEY_ID=1234567890ABCDEF to your key ID:

gpg --armor --export ${KEY_ID} > public.key
gpg --armor --export-secret-keys ${KEY_ID} > private.key

Note: You should keep the private key safe, and not commit it to your repository.

In your GitHub repository, go to Settings > Secrets and variables > Actions add GPG_PRIVATE_KEY and GPG_PUBLIC_KEY with the contents of the private and public keys respectively.

Use the generate-apt-repo.sh script

The following bash script can be used to generate the apt repository.

You will need dpkg-dev installed to use this script.

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

main() {
  ARCHS=(amd64 arm64)

  SUITE_DIR="dists/${SUITE:-stable}"
  COMPONENT_DIR="${SUITE_DIR}/${COMPONENTS-main}"

  echo "Cleaning up dist dir"
  rm -rf ./dist

  echo "Generating Packages files"
  for ARCH in "${ARCHS[@]}"; do
    PACKAGE_DIR=${COMPONENT_DIR}/binary-${ARCH}
    mkdir -p ${PACKAGE_DIR}
    dpkg-scanpackages --multiversion --arch ${ARCH} pool/ > ${PACKAGE_DIR}/Packages
    gzip -fk ${PACKAGE_DIR}/Packages
    bzip2 -fk ${PACKAGE_DIR}/Packages
  done

  pushd "${SUITE_DIR}" >/dev/null
  echo "Making Release file"
  {
    echo "Origin: ${ORIGIN:-skpr}"
    echo "Label: ${REPO_OWNER:-skpr}"
    echo "Suite: ${SUITE:-stable}"
    echo "Codename: ${SUITE:-stable}"
    echo "Version: 1.0"
    echo "Architectures: amd64 arm64"
    echo "Components: ${COMPONENTS:-main}"
    echo "Description: ${DESCRIPTION:-A repository for packages released by ${REPO_OWNER:-skpr}}"
    echo "Date: $(date -Ru)"
    generate_hashes MD5Sum md5sum
    generate_hashes SHA1 sha1sum
    generate_hashes SHA256 sha256sum
    generate_hashes SHA512 sha512sum
  } > Release

  echo "Signing Release files"
  gpg --batch --yes --armor --sign --detach-sign --output Release.gpg Release
  gpg --batch --yes --armor --sign --detach-sign --clearsign --output InRelease Release

  popd >/dev/null
  echo "Apt repo generated"
}

generate_hashes() {
  HASH_TYPE="$1"
  HASH_COMMAND="$2"
  echo "${HASH_TYPE}:"
  find ${COMPONENTS-main} -type f | while read -r file
  do
    echo " $(${HASH_COMMAND} "$file" | cut -d" " -f1) $(wc -c "$file")"
  done
}

main

Create a Github Action

You can use the following as an example.

name: ๐Ÿ“ฆ Build Apt Repo

# Maunal trigger builds
on:
  workflow_dispatch:

env:
  BUCKET_NAME: my-bucket
  AWS_REGION: us-east-1

jobs:
  publish_debs:
    runs-on: ubuntu-latest
    steps:
      - name: โฌ‡๏ธ Git clone the repository
        uses: actions/checkout@v4
      - name: ๐Ÿ“ Make apt repo dirs
        run: mkdir -p apt-repo/pool/main/skpr
        # Download all deb files from the latest release.
      - name: ๐Ÿ“ฆ Download latest release
        uses: robinraju/release-downloader@v1.8
        with:
          repository: skpr/cli
          latest: true
          fileName: "*.deb"
          out-file-path: apt-repo/pool/main/skpr
      - name: โš™๏ธ Configure aws credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}
      - name: ๐Ÿ”‘ Import GPG Private Key
        uses: crazy-max/ghaction-import-gpg@v6
        with:
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
      - name: ๐Ÿ” List keys
        run: gpg -K
      - name: ๐Ÿ“ฆ Generate Apt Repo
        run: |
          cd apt-repo
          ../scripts/generate-apt-repo.sh
          cd -
      - name: ๐Ÿ”‘ Export Public Key
        run: |
          cd apt-repo
          echo "${{ secrets.GPG_PUBLIC_KEY }}" > packages.skpr.io.pub
          cd -
      - name: โ˜๏ธ Sync to S3
        run: |
          cd apt-repo
          aws s3 sync --delete . s3://${{ env.BUCKET_NAME }}/apt/
          cd -
      - name: ๐Ÿงน Invalidate Cloudfront
        run: |
          aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_ID }} --paths /apt/*

Acknowledgements

Much of the heavy lifting was taken from Benoit Perroud and this blog post.

Tags

Gitub Actions
Ubuntu
Continuous integration

Getting Started

Interested in a demo?

๐ŸŽ‰ Awesome!

Please check your inbox for a confirmation email. It might take a minute or so.

๐Ÿค” Whoops!

Something went wrong. Check that you have entered a valid email and try submitting the form again.

We'll be in touch shortly.