#!/usr/bin/env bash set -e usage() { echo "usage: $0 -f -f -r [-s]" exit 1 } check_program() { if ! command -v $1 &> /dev/null then echo "$1 is required and could not be found" exit fi } # Used to get comma separated list of architectures join_arr() { local IFS="$1" shift echo "$*" } # Updates the signature of a DEB package in the local repository # # $1: path of the repository. # $2: suite (eg. "stable") # $3: path of the DEB file. sign_deb() { pushd $1/$2 > /dev/null rm -f $(basename -- $3).asc gpg --detach-sign --digest-algo SHA256 --armor $(basename -- $3) popd > /dev/null } # Add a package to the local DEB repository # # $1: path of the repository. # $2: suite (eg. "stable") # $3: path of the DEB file. add_deb() { cp -f $3 $1/$2 sign_deb $1 $2 $3 # Get package architecture from dpkg local arch=$(dpkg --info $3 | awk '/Architecture/ {printf "%s", $2}') # Store architecture in array architectures+=("${arch}") } falco_arch_from_deb_arch() { case "$1" in "amd64") echo -n "x86_64" ;; "arm64") echo -n "aarch64" ;; *) echo "Wrong arch." exit 1 ;; esac } # Sign the local DEB repository # # $1: path of the repository # $2: suite (eg. "stable") sign_repo() { local release_dir=dists/$2 pushd $1 > /dev/null # release signature - Release.gpg file gpg --detach-sign --digest-algo SHA256 --armor ${release_dir}/Release rm -f ${release_dir}/Release.gpg mv ${release_dir}/Release.asc ${release_dir}/Release.gpg # release signature - InRelease file gpg --armor --sign --clearsign --digest-algo SHA256 ${release_dir}/Release rm -f ${release_dir}/InRelease mv ${release_dir}/Release.asc ${release_dir}/InRelease popd > /dev/null } # Update the local DEB repository # # $1: path of the repository # $2: suite (eg. "stable") update_repo() { local component=main local debs_dir=$2 local release_dir=dists/$2 pushd $1 > /dev/null # packages metadata for arch in "${architectures[@]}"; do local packages_dir=${release_dir}/${component}/binary-${arch} mkdir -p ${packages_dir} truncate -s 0 ${packages_dir}/Packages # Find all ${arch} deb files. # Note that debian uses {arm64,amd64}, while # Falco packages use {x86_64,aarch64}. find ${debs_dir} -name "falco-*-$(falco_arch_from_deb_arch ${arch}).deb" -exec apt-ftparchive packages {} \; >> ${packages_dir}/Packages gzip -c ${packages_dir}/Packages > ${packages_dir}/Packages.gz bzip2 -z -c ${packages_dir}/Packages > ${packages_dir}/Packages.bz2 done # release metadata apt-ftparchive release \ -o APT::FTPArchive::Release::Origin=Falco \ -o APT::FTPArchive::Release::Label=Falco \ -o APT::FTPArchive::Release::Suite=$2 \ -o APT::FTPArchive::Release::Codename=$2 \ -o APT::FTPArchive::Release::Components=${component} \ -o APT::FTPArchive::Release::Architectures="$(join_arr , "${architectures[@]}")" \ ${release_dir} > ${release_dir}/Release popd > /dev/null } reduce_dir_size() { local DIR=$1 local MAX_SIZE_GB=$2 local EXTENSION=$3 local MAX_SIZE=$((MAX_SIZE_GB*1024*1024)) # Convert GB to KB for du command # Check if directory exists if [[ ! -d "$DIR" ]]; then echo "The directory $DIR does not exist." return 1 fi # Calculate current directory size in KB local CUR_SIZE=$(du -sk "$DIR" | cut -f1) # Check if we need to delete any files if ((CUR_SIZE <= MAX_SIZE)); then return 0 fi # Calculate size to delete in bytes local DEL_SIZE=$(( (CUR_SIZE - MAX_SIZE) * 1024 )) local ACC_SIZE=0 find "$DIR" -maxdepth 1 -type f -name "*.$EXTENSION" -printf "%T+ %s %p\n" | sort | while read -r date size file; do if ((ACC_SIZE + size < DEL_SIZE)); then rm "$file" ACC_SIZE=$((ACC_SIZE + size)) local asc_file="$file.asc" if [[ -e "$asc_file" ]]; then local asc_size=$(stat --format="%s" "$asc_file") rm "$asc_file" ACC_SIZE=$((ACC_SIZE + asc_size)) fi else break fi done } # parse options while getopts ":f::r::s" opt; do case "${opt}" in f ) files+=("${OPTARG}") ;; r ) repo="${OPTARG}" [[ "${repo}" == "deb" || "${repo}" == "deb-dev" ]] || usage ;; s ) sign_all="true" ;; : ) echo "invalid option: ${OPTARG} requires an argument" 1>&2 exit 1 ;; \?) echo "invalid option: ${OPTARG}" 1>&2 exit 1 ;; esac done shift $((OPTIND-1)) # check options if ([ ${#files[@]} -eq 0 ] && [ -z "${sign_all}" ]) || [ -z "${repo}" ]; then usage fi # check prerequisites check_program apt-ftparchive check_program gzip check_program bzip2 check_program gpg check_program aws check_program dpkg # settings debSuite=stable s3_bucket_repo="s3://falco-distribution/packages/${repo}" cloudfront_path="/packages/${repo}" tmp_repo_path=/tmp/falco-$repo # prepare repository local copy echo "Fetching ${s3_bucket_repo}..." mkdir -p ${tmp_repo_path} aws s3 cp ${s3_bucket_repo} ${tmp_repo_path} --recursive # update signatures for all existing packages if [ "${sign_all}" ]; then for file in ${tmp_repo_path}/${debSuite}/*; do if [ -f "$file" ]; then # exclude directories, symlinks, etc... if [[ ! $file == *.asc ]]; then # exclude signature files package=$(basename -- ${file}) echo "Signing ${package}..." sign_deb ${tmp_repo_path} ${debSuite} ${file} echo "Syncing ${package}.asc to ${s3_bucket_repo}..." aws s3 cp ${tmp_repo_path}/${debSuite}/${package}.asc ${s3_bucket_repo}/${debSuite}/${package}.asc --acl public-read fi fi done aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/*.asc sign_repo ${tmp_repo_path} ${debSuite} fi # remove old dev packages if necessary if [[ ${repo} == "deb-dev" ]]; then reduce_dir_size "${tmp_repo_path}/${debSuite}" 10 deb fi # update the repo by adding new packages if ! [ ${#files[@]} -eq 0 ]; then for file in "${files[@]}"; do echo "Adding ${file}..." add_deb ${tmp_repo_path} ${debSuite} ${file} done update_repo ${tmp_repo_path} ${debSuite} sign_repo ${tmp_repo_path} ${debSuite} # publish for file in "${files[@]}"; do package=$(basename -- ${file}) echo "Publishing ${package} to ${s3_bucket_repo}..." aws s3 cp ${tmp_repo_path}/${debSuite}/${package} ${s3_bucket_repo}/${debSuite}/${package} --acl public-read aws s3 cp ${tmp_repo_path}/${debSuite}/${package}.asc ${s3_bucket_repo}/${debSuite}/${package}.asc --acl public-read aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package} aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}.asc done fi # sync dists aws s3 sync ${tmp_repo_path}/dists ${s3_bucket_repo}/dists --delete --acl public-read aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/dists/* # delete packages that have been pruned # the dryrun option is there so we can check that we're doing the right thing, can be removed after testing if [[ ${repo} == "deb-dev" ]]; then aws s3 sync "${tmp_repo_path}/${debSuite}" ${s3_bucket_repo} --dryrun --delete --acl public-read fi