#!/usr/bin/env bash # Manages aptly input repositories and publishes a merged output repository. # Structure is as follows: # repo1 ───> snapshot "repo1-%Y-%m-%d" ─┬─> snapshot "%Y-%m-%d" ───> publish inside $TBASE/public # │ # repo2 ───> snapshot "repo2-%Y-%m-%d" ─┘ # # Furthermore, our PACKAGE input folder is /tmp/aptly with repository names as subfolder, so if # you want a new package added inside repo1, you would place a file inside /tmp/aptly/repo1/. # The package would then be added and its file inside /tmp be removed. # # Why bash? # 1. This script is INTERACTIVE, it asks for your GPG passphrase before signing packages # 2. It still uses a lot of syscalls like aptly # 3. The steps we do are still not that resource-hungry, so the downsides while using bash are rather low # The combination of these three led to bash in the first iteration. # # What doesn't this script do? # 1. It does not create the source repos or the aptly config. # 2. It does not repair if one of the parts of the structure above is missing; in that case, # we assume an extraordinary failure and will fail ourselves. Exception: We skip failing # on removal if a published repo or a snapshot does not exist. # 3. It does not manage multiple snapshots yet. Nor does it cleanup snapshot remainders in case the script # is interrupted. Both can be considered a TODO. if [ ! -r "/etc/aptly-lirionde/aptly.conf" ];then printf '/etc/aptly-lirionde/aptly.conf cannot be read, exiting!\n' >&2 exit 254 else # shellcheck disable=SC1091 source /etc/aptly-lirionde/aptly.conf || exit 254 fi MALFORMED=0 [ -z "$MYREPS" ] && MALFORMED=1 [ -z "$GPGKEY" ] && MALFORMED=1 [ -z "$GPGTESTKEY" ] && GPGKEY="$GPGTESTKEY" [ -z "$PBASE" ] && MALFORMED=1 [ -z "$TBASE" ] && MALFORMED=1 [ "$MALFORMED" -eq 1 ] && printf '/etc/aptly-lirionde/aptly.conf malformed, exiting.\n' >&2 && exit 253 printf -v repjoined "%s-$(date -I) " "${MYREPS[@]}" # shellcheck disable=SC1091 source /usr/lib/lirion/ln-initfunctions || exit 10 SNDATE="$(aptly snapshot list -raw | head -n1)" if ! printf '%b' "$SNDATE" | grep -P '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' > /dev/null; then SNDATE="$(date -I)" fi printf -v repoldjoined "%s-$SNDATE " "${MYREPS[@]}" printf 'Snapshot suffix that will be deleted: %b\n' "$SNDATE" || exit 11 printf 'Have you added all packages? :)\n' printf 'Starting snapshots and publication\033[s in ' for ((i=5;i>0;--i)); do printf '\r\033[u\033[K in %b...' "$i" sleep 1 done printf '\r\033[u\033[K:\n' for rep in "${MYREPS[@]}"; do if ! aptly repo list -raw 2>/dev/null | grep -P "^${rep}$" >/dev/null; then lnfail "repository ${rep} does not exist!" fi lnbegin "Adding packages to repo $rep" if [ ! -d "${PBASE}/$rep" ]; then lnskip "source directory not existing" continue fi readarray -t debfiles < <( find "${PBASE}/$rep" -type l -name "*deb" 2>/dev/null find "${PBASE}/$rep" -type f -name "*deb" 2>/dev/null ) case "${#debfiles[@]}" in 0) lnskip "no files in ${PBASE}/$rep" ;; *) for debfile in "${debfiles[@]}"; do lnprog "$(basename "$debfile")" sleep 0.271828 if ! aptly repo add "$rep" "$debfile" >/dev/null 2>&1; then lnfail "adding file failed" exit 100 fi if ! rm -f "$debfile" >/dev/null 2>&1; then lnfail "source file removal failed" exit 101 fi done lnok ;; esac done lnbegin "Unpublishing repo \"all\"" if [ "$(aptly publish list | grep -cv '^No snapshots/local repos')" -lt 1 ]; then lnskip "No snapshots / published repos" else if ! aptly publish drop all >/dev/null 2>&1; then lnfail exit 110 fi fi lnok lnbegin "Dropping snapshots" for ss in "$SNDATE" ${repoldjoined%,};do lnprog "$ss" sleep 0.271828 if ! aptly snapshot list -raw | grep "^$ss\$" > /dev/null; then lnprog "skipping $ss, not present" sleep 0.314159 else if ! aptly snapshot drop "$ss" >/dev/null 2>&1; then lnfail exit 111 fi fi done lnok lnbegin "Creating fresh snapshots" for rep in "${MYREPS[@]}"; do lnprog "$rep" sleep 0.271828 if ! faketime "$(date -I) 13:37:08" aptly snapshot create \ "${rep}-$(date -I)" from repo "$rep" >/dev/null 2>&1; then lnfail exit 120 fi done lnok lnbegin "Merging snapshots" printf -v repjoined "%s-$(date -I) " "${MYREPS[@]}" # shellcheck disable=SC2086 if ! aptly snapshot merge "$(date -I)" ${repjoined%,} >/dev/null 2>&1; then lnfail exit 121 fi lnok printf 'GPG pseudo operation...' printf 'lel\n' > /tmp/lel gpg -eu "$GPGKEY" -r "$GPGTESTKEY" /tmp/lel rm /tmp/lel* || exit 122 printf ' done.\n' lnbegin "Publishing snapshot result" if ! faketime "$(date -I) 13:37:11" aptly publish snapshot \ -gpg-key="$GPGKEY" -distribution='all' "$(date -I)" >/dev/null 2>&1; then lnfail exit 123 fi lnok lnbegin "Creating aptly graphs" for layout in horizontal vertical; do lnprog "$layout" if ! aptly graph -layout="$layout" \ -output="${TBASE}/public/aptly-graph-${layout}.png" >/dev/null 2>&1; then lnfail exit 130 fi done lnok lnbegin "Creating sha256 checksums" lnprog "directories" if ! mkdir -p "${TBASE}/public/dists/all/utils"/binary-{amd64,arm64}/by-hash/SHA{256,512} 2>/dev/null; then lnfail "directory creation failed" exit 140 fi lnprog "old hash removal" for dir in "${TBASE}/public/dists/all/utils"/binary-{amd64,arm64}/by-hash/SHA{256,512}; do if ! find "$dir" -type l -exec rm -f '{}' \; ; then lnfail "hash file removal failed in $dir" exit 141 fi done lnprog "hash creation" for file in "${TBASE}/public/dists/all/utils"/binary-{amd64,arm64}/{Packages,Release}*;do S256="$(sha256sum "$file"|awk '{print $1}')" S512="$(sha512sum "$file"|awk '{print $1}')" PROGSTR="SHA256 hash for $(dirname "$file")/$(basename "$file")" PROGSTR="$(printf '%b' "$PROGSTR" | sed 's#.*\(/dists/\)#...\1#')" lnprog "$PROGSTR" unset PROGSTR sleep 0.271828 if ! ln -fs "../../$(basename "$file")" "$(dirname "$file")/by-hash/SHA256/$S256" 2>/dev/null; then lnfail "hash failed for $(dirname "$file")/$(basename "$file")" exit 142 fi PROGSTR="SHA512 hash for $(dirname "$file")/$(basename "$file")" PROGSTR="$(printf '%b' "$PROGSTR" | sed 's#.*\(/dists/\)#...\1#')" lnprog "$PROGSTR" unset PROGSTR sleep 0.271828 if ! ln -fs "../../$(basename "$file")" "$(dirname "$file")/by-hash/SHA512/$S512" 2>/dev/null; then lnfail "hash failed for $(dirname "$file")/$(basename "$file")" exit 142 fi done lnok