Last active
December 11, 2023 16:36
-
-
Save suominen/69b65ba8fd66cf488db7fe5c233d4415 to your computer and use it in GitHub Desktop.
Manage a deb repository
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# @(#)$KimmoSuominen: .cfg/home/bin/share/debrepo,v 1.12 2023/12/11 16:35:53 kim Exp $ | |
# | |
# Copyright (c) 2022-2023 Kimmo Suominen | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions | |
# are met: | |
# | |
# 1. Redistributions of source code must retain the above copyright | |
# notice, this list of conditions and the following disclaimer. | |
# | |
# 2. Redistributions in binary form must reproduce the above copyright | |
# notice, this list of conditions and the following disclaimer | |
# in the documentation and/or other materials provided with the | |
# distribution. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
# | |
set -eu | |
umask 077 | |
PATH=/usr/pkg/bin:/bin:/usr/bin | |
export PATH | |
PROG="${0##*/}" | |
CONFFILE="${HOME}/.config/${PROG}/config" | |
die() | |
{ | |
echo "${PROG}: ${@}" 1>&2 | |
exit 1 | |
} | |
generate_key() | |
{ | |
gpg --batch --generate-key <<-EOF | |
%echo Generating a new GPG key | |
Key-Type: RSA | |
Key-Length: 4096 | |
Name-Real: ${KEY_NAME} | |
Name-Email: ${KEY_EMAIL} | |
Expire-Date: 0 | |
%no-protection | |
%commit | |
EOF | |
} | |
list_archs() | |
{ | |
echo amd64 arm64 | |
} | |
list_codenames() | |
{ | |
local os | |
os="${1}" | |
case "${os}" in | |
debian) echo trixie bookworm bullseye buster;; | |
ubuntu) echo mantic lunar kinetic jammy focal bionic;; | |
esac | |
} | |
list_files() | |
{ | |
local comp root | |
root="${1}" | |
comp="${2}" | |
find "${root}/${comp}" -type f -print \ | |
| sed -e "s,^${root}/,," | |
} | |
msg() | |
{ | |
echo "${PROG}: ${@}" 1>&2 | |
} | |
output_file_hashes() | |
{ | |
local algo file hash root size | |
algo="${1}" | |
root="${2}" | |
while read file | |
do | |
hash="$("${algo}"sum "${root}/${file}" | cut -d' ' -f1)" | |
size="$(stat -c '%s' "${root}/${file}")" | |
printf ' %s %s %s\n' "${hash}" "${size}" "${file}" | |
done | |
} | |
output_release_files() | |
{ | |
local algo comp files root | |
root="${1}" | |
comp="${2}" | |
files="$(list_files "${root}" "${comp}")" | |
for algo in md5 sha1 sha256 # sha512 | |
do | |
case "${algo}" in | |
md5) echo 'MD5Sum:';; | |
sha1) echo 'SHA1:';; | |
sha256) echo 'SHA256:';; | |
sha512) echo 'SHA512:';; | |
esac | |
echo "${files}" \ | |
| output_file_hashes "${algo}" "${root}" | |
done | |
} | |
output_release_head() | |
{ | |
local codename os | |
os="${1}" | |
codename="${2}" | |
sed -e '/^ *$/d' <<-EOF | |
Origin: ${REPO_ORIGIN} | |
${REPO_LABEL:+Label: ${REPO_LABEL}} | |
Suite: ${REPO_SUITE} | |
Codename: ${codename} | |
Components: main | |
Architectures: $(list_archs "${os}") | |
${REPO_NOT_AUTOMATIC:+NotAutomatic: ${REPO_NOT_AUTOMATIC}} | |
${REPO_BUT_AUTOMATIC:+ButAutomaticUpgrades: ${REPO_BUT_AUTOMATIC}} | |
Date: ${DATE} | |
${REPO_DESCRIPTION:+Description: ${REPO_DESCRIPTION}} | |
EOF | |
} | |
rebuild_repo() | |
{ | |
local arch bin codename comp dist os pool | |
os="${1}" | |
codename="${2}" | |
comp="main" | |
dist="dists/${codename}" | |
pool="pool/${codename}/${comp}" | |
if [ -d "${pool}" ] | |
then | |
for arch in $(list_archs "${os}" "${codename}") | |
do | |
bin="${dist}/${comp}/binary-${arch}" | |
mkdir -p "${bin}" | |
dpkg-scanpackages -m -a "${arch}" "${pool}" \ | |
> "${bin}/.Packages.new" | |
if cmp -s "${bin}/.Packages.new" "${bin}/Packages" | |
then | |
rm -f "${bin}/.Packages.new" | |
else | |
rm -f "${bin}/Packages"* | |
mv "${bin}/.Packages.new" "${bin}/Packages" | |
xz --keep "${bin}/Packages" | |
fi | |
done | |
[ -f "${dist}/Release" ] || echo Empty > "${dist}/Release" | |
{ | |
output_release_head "${os}" "${codename}" | |
output_release_files "${dist}" "${comp}" | |
} > "${dist}/.Release.new" | |
grep -v '^Date: ' "${dist}/.Release.new" \ | |
> "${dist}/.Release.new.tmp" | |
grep -v '^Date: ' "${dist}/Release" \ | |
> "${dist}/.Release.old.tmp" | |
if cmp -s "${dist}/.Release.new.tmp" "${dist}/.Release.old.tmp" | |
then | |
rm -f "${dist}/.Release."* | |
else | |
rm -f "${dist}/Release"* "${dist}/InRelease" | |
mv "${dist}/.Release.new" "${dist}/Release" | |
rm -f "${dist}/.Release."* | |
gpg --default-key "${KEY_NAME}" -abs \ | |
< "${dist}/Release" \ | |
> "${dist}/Release.gpg" | |
gpg --default-key "${KEY_NAME}" -abs --clearsign \ | |
< "${dist}/Release" \ | |
> "${dist}/InRelease" | |
fi | |
fi | |
} | |
release() | |
{ | |
local codename os root | |
root="${1}" | |
umask 022 | |
for os in debian ubuntu | |
do | |
for codename in $(list_codenames "${os}") | |
do | |
[ -d "${root}/${os}" ] || continue | |
( cd "${root}/${os}" && rebuild_repo "${os}" "${codename}" ) | |
done | |
done | |
} | |
usage() | |
{ | |
cat <<EOF | |
Usage: ${PROG} -r repo command | |
Options: | |
-h Show this usage | |
-r Select the repository to work on | |
Commands: | |
- dist Sync local repo to web server | |
- edit-key Edit GPG key (e.g. to change pass phrase) | |
- export-private-key Output secret GPG key in ASCII armor | |
- export-public-key Output public GPG key in ASCII armor | |
- export-public-key-bin Output public GPG key in GPG format | |
- generate-key Generate a new GPG key | |
- help Show this usage | |
- list-keys List GPG keys | |
- release Rebuild the repository metadata | |
- usage Show this usage | |
Default values can be set and changed in the configuration file. | |
Using configuration file: ${CONFFILE} (${CONFSTATUS}). | |
EOF | |
# 345678901234567890123456789012345678901234567890123456789012345678901234567890 | |
} | |
DATE="$(date -Ru)" | |
YEAR="$(date +%Y)" | |
if [ -f "${CONFFILE}" ] | |
then | |
if [ -r "${CONFFILE}" ] | |
then | |
. "${CONFFILE}" | |
CONFSTATUS="found, readable" | |
else | |
CONFSTATUS="found, unreadable" | |
fi | |
else | |
CONFSTATUS="not found" | |
fi | |
# Don't allow setting these in config file | |
repo= | |
while getopts hr: opt | |
do | |
case "${opt}" in | |
h) usage; exit 0;; | |
r) repo="${OPTARG}";; | |
*) usage 1>&2; exit 1;; | |
esac | |
done | |
shift $((${OPTIND} - 1)) | |
case "${repo}" in | |
'') | |
die 'Use -r to specify a repo' | |
;; | |
esac | |
if [ -f "${CONFFILE}-${repo}" ] | |
then | |
if [ -r "${CONFFILE}-${repo}" ] | |
then | |
. "${CONFFILE}-${repo}" | |
else | |
die "${CONFFILE}-${repo}: Permission denied" | |
fi | |
else | |
die "${CONFFILE}-${repo}: No such file" | |
fi | |
: ${GNUPGHOME} | |
: ${KEY_EMAIL} | |
: ${KEY_NAME} | |
: ${LOCAL_REPO} | |
: ${REMOTES} | |
: ${REPO_ORIGIN} | |
: ${REPO_SUITE} | |
export GNUPGHOME | |
GPG_TTY="$(tty)" | |
export GPG_TTY | |
gpgconf --kill gpg-agent | |
trap "gpgconf --kill gpg-agent" 0 1 2 15 | |
case "${1:-usage}" in | |
dist) | |
if [ ! -d "${LOCAL_REPO}" ] | |
then | |
die "${LOCAL_REPO}: No such directory" | |
fi | |
for remote in ${REMOTES} | |
do | |
msg "Publishing to ${remote}" | |
case "${remote}" in | |
s3:*) | |
aws \ | |
${AWS_PROFILE:+--profile="${AWS_PROFILE}"} \ | |
s3 sync \ | |
"${LOCAL_REPO}/" \ | |
"${remote}/" \ | |
--delete | |
eval aws --no-paginate \ | |
${AWS_PROFILE:+--profile="'${AWS_PROFILE}'"} \ | |
cloudfront create-invalidation \ | |
--distribution-id "'${CF_DISTRIBUTION_ID}'" \ | |
--paths "${CF_DISTRIBUTION_PATHS}" | |
;; | |
*) | |
rsync -crstOogp --delete \ | |
--chmod Fgo-w,a+rX \ | |
"${LOCAL_REPO}/" \ | |
"${remote}/" | |
;; | |
esac | |
done | |
;; | |
edit-key) | |
gpg --edit-key "${KEY_NAME}" | |
;; | |
export-private-key) | |
gpg --armor --export-secret-keys "${KEY_NAME}" | |
;; | |
export-public-key) | |
gpg --armor --export "${KEY_NAME}" | |
;; | |
export-public-key-bin) | |
gpg --export "${KEY_NAME}" | |
;; | |
generate-key) | |
if [ -d "${GNUPGHOME}" ] | |
then | |
die "${GNUPGHOME} already exists!" | |
fi | |
mkdir -p "${GNUPGHOME}" | |
generate_key | |
;; | |
help) | |
usage | |
;; | |
list-keys) | |
gpg --batch --list-keys | |
;; | |
release) | |
release "${LOCAL_REPO}" | |
;; | |
usage) | |
usage | |
;; | |
*) | |
die "Unknown command: ${1}" | |
;; | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment