Host job on hestia (no container) collects breadlock plus the ISO AUR republishes from the Forgejo registry, detach-signs them, repo-add -s, and writes /srv/breadway-dl/arch/x86_64/. ISO SigLevel stays Never.
298 lines
8.2 KiB
Bash
Executable file
298 lines
8.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Collect the current [breadway] ISO packages, detach-sign them with the
|
|
# BOS release key (releases@breadway.dev), and publish a signed pacman db
|
|
# under /srv/breadway-dl/arch/x86_64/ (https://dl.breadway.dev/arch/x86_64/).
|
|
#
|
|
# Does not change ISO SigLevel and does not write to the Forgejo Arch
|
|
# registry — existing package.yml / packaging/*.yml PUTs stay as they are.
|
|
#
|
|
# Required env:
|
|
# GPG_PRIVATE_KEY armoured secret key (same secret as release-iso.yml)
|
|
# Optional env:
|
|
# BREADWAY_DEST publish dir (default /srv/breadway-dl/arch/x86_64)
|
|
# BREADWAY_PKG_DIR extra directory of .pkg.tar.zst to prefer over the registry
|
|
# BREADWAY_REGISTRY Forgejo Arch registry base
|
|
# BREADWAY_SIGN_ONLY=1 skip collect; sign+index BREADWAY_REPO_DIR only
|
|
set -euo pipefail
|
|
|
|
PACKAGES=(
|
|
breadlock
|
|
calamares
|
|
zen-browser-bin
|
|
bibata-cursor-theme-bin
|
|
zsh-theme-powerlevel10k
|
|
yay-bin
|
|
)
|
|
|
|
ARCH="${BREADWAY_ARCH:-x86_64}"
|
|
REGISTRY="${BREADWAY_REGISTRY:-https://git.breadway.dev/api/packages/Breadway/arch/os}"
|
|
DEST="${BREADWAY_DEST:-/srv/breadway-dl/arch/${ARCH}}"
|
|
KEY_ID="${BREADWAY_KEY_ID:-releases@breadway.dev}"
|
|
DB_NAME="${BREADWAY_REGISTRY_DB:-Breadway.os.git.breadway.dev.db}"
|
|
REPO_DIR="${BREADWAY_REPO_DIR:-}"
|
|
|
|
SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
|
|
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
|
|
|
need_key() {
|
|
if [[ -z "${GPG_PRIVATE_KEY:-}" ]]; then
|
|
die "GPG_PRIVATE_KEY is missing; refusing to publish an unsigned [breadway] repo."
|
|
fi
|
|
}
|
|
|
|
urlencode() {
|
|
python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe="-._~"))' "$1"
|
|
}
|
|
|
|
pkginfo_name() {
|
|
local pkg="$1" info
|
|
info="$(tar -xOf "$pkg" .PKGINFO 2>/dev/null || zstd -dc "$pkg" | tar -xO .PKGINFO)"
|
|
awk -F ' = ' '$1=="pkgname" {print $2; exit}' <<<"$info"
|
|
}
|
|
|
|
import_key() {
|
|
export GNUPGHOME="${GNUPGHOME:-$(mktemp -d "${TMPDIR:-/tmp}/gnupg-breadway-repo.XXXXXX")}"
|
|
mkdir -m 700 -p "$GNUPGHOME"
|
|
printf '%s\n' "$GPG_PRIVATE_KEY" | gpg --batch --import
|
|
}
|
|
|
|
detach_sign_pkgs() {
|
|
local pkg
|
|
shopt -s nullglob
|
|
for pkg in *.pkg.tar.zst; do
|
|
gpg --batch --yes --local-user "$KEY_ID" --detach-sign "$pkg"
|
|
done
|
|
shopt -u nullglob
|
|
}
|
|
|
|
repo_add_signed() {
|
|
local pkgs=()
|
|
shopt -s nullglob
|
|
pkgs=(*.pkg.tar.zst)
|
|
shopt -u nullglob
|
|
(( ${#pkgs[@]} > 0 )) || die "no .pkg.tar.zst files to index"
|
|
rm -f breadway.db breadway.db.tar.gz breadway.db.sig breadway.db.tar.gz.sig \
|
|
breadway.files breadway.files.tar.gz breadway.files.sig breadway.files.tar.gz.sig
|
|
if repo-add --help 2>&1 | grep -q -- '--include-sigs'; then
|
|
repo-add -s -k "$KEY_ID" --include-sigs breadway.db.tar.gz "${pkgs[@]}"
|
|
else
|
|
repo-add -s -k "$KEY_ID" breadway.db.tar.gz "${pkgs[@]}"
|
|
fi
|
|
[[ -e breadway.db.tar.gz.sig || -e breadway.db.sig ]] \
|
|
|| die "repo-add -s did not write breadway.db*.sig"
|
|
}
|
|
|
|
ensure_arch_tools() {
|
|
if ! command -v gpg >/dev/null 2>&1; then
|
|
command -v pacman >/dev/null 2>&1 || die "gpg not on PATH"
|
|
pacman -Sy --noconfirm --needed gnupg
|
|
fi
|
|
command -v repo-add >/dev/null 2>&1 || die "repo-add not on PATH"
|
|
command -v gpg >/dev/null 2>&1 || die "gpg not on PATH"
|
|
}
|
|
|
|
sign_and_index() {
|
|
local dir="$1"
|
|
[[ -d "$dir" ]] || die "repo dir missing: $dir"
|
|
need_key
|
|
ensure_arch_tools
|
|
import_key
|
|
(
|
|
cd "$dir"
|
|
detach_sign_pkgs
|
|
repo_add_signed
|
|
)
|
|
}
|
|
|
|
container_runtime() {
|
|
if command -v docker >/dev/null 2>&1; then
|
|
printf '%s\n' docker
|
|
elif command -v podman >/dev/null 2>&1; then
|
|
printf '%s\n' podman
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
sign_and_index_anywhere() {
|
|
local dir="$1"
|
|
if command -v repo-add >/dev/null 2>&1 && command -v gpg >/dev/null 2>&1; then
|
|
sign_and_index "$dir"
|
|
return
|
|
fi
|
|
local rt
|
|
rt="$(container_runtime)" || die \
|
|
"need host gpg+repo-add, or docker/podman to run archlinux:latest (no Forgejo container: — host must see /srv/breadway-dl)"
|
|
# Host job + bind-mount, same reason bakery writes /srv without container:.
|
|
"$rt" run --rm --network=host \
|
|
-e GPG_PRIVATE_KEY \
|
|
-e BREADWAY_SIGN_ONLY=1 \
|
|
-e BREADWAY_REPO_DIR=/repo \
|
|
-e BREADWAY_KEY_ID="$KEY_ID" \
|
|
-v "$dir:/repo" \
|
|
-v "$SCRIPT_PATH:/ci-publish-signed-repo.sh:ro" \
|
|
archlinux:latest \
|
|
bash /ci-publish-signed-repo.sh
|
|
}
|
|
|
|
parse_registry_db() {
|
|
local db="$1"
|
|
python3 - "$db" "${PACKAGES[@]}" <<'PY'
|
|
import sys, tarfile
|
|
|
|
db = sys.argv[1]
|
|
want = set(sys.argv[2:])
|
|
found = {}
|
|
with tarfile.open(db, "r:*") as tf:
|
|
for member in tf.getmembers():
|
|
if not member.name.endswith("/desc") or not member.isfile():
|
|
continue
|
|
fh = tf.extractfile(member)
|
|
if fh is None:
|
|
continue
|
|
text = fh.read().decode()
|
|
fields = {}
|
|
key = None
|
|
buf = []
|
|
def flush():
|
|
if key is not None:
|
|
fields[key] = "\n".join(buf).strip()
|
|
for line in text.splitlines():
|
|
if line.startswith("%") and line.endswith("%") and len(line) > 2:
|
|
flush()
|
|
key = line.strip("%")
|
|
buf = []
|
|
else:
|
|
buf.append(line)
|
|
flush()
|
|
name = fields.get("NAME", "")
|
|
filename = fields.get("FILENAME", "")
|
|
if name in want and filename:
|
|
found[name] = filename
|
|
|
|
missing = sorted(want - set(found))
|
|
if missing:
|
|
sys.stderr.write("registry db missing packages: " + " ".join(missing) + "\n")
|
|
raise SystemExit(1)
|
|
for name in sys.argv[2:]:
|
|
print(f"{name}\t{found[name]}")
|
|
PY
|
|
}
|
|
|
|
copy_local_overrides() {
|
|
local dir="$1"
|
|
[[ -n "$dir" && -d "$dir" ]] || return 0
|
|
local pkg name
|
|
shopt -s nullglob
|
|
for pkg in "$dir"/*.pkg.tar.zst "$dir"/*/*.pkg.tar.zst; do
|
|
[[ -f "$pkg" ]] || continue
|
|
name="$(pkginfo_name "$pkg")"
|
|
[[ -n "$name" ]] || continue
|
|
local wanted=0 p
|
|
for p in "${PACKAGES[@]}"; do
|
|
if [[ "$p" == "$name" ]]; then
|
|
wanted=1
|
|
break
|
|
fi
|
|
done
|
|
if (( wanted )); then
|
|
printf 'local override: %s -> %s\n' "$name" "$(basename "$pkg")"
|
|
cp -a "$pkg" "$STAGE/$(basename "$pkg")"
|
|
fi
|
|
done
|
|
shopt -u nullglob
|
|
}
|
|
|
|
has_pkg_named() {
|
|
local name="$1" pkg got
|
|
shopt -s nullglob
|
|
for pkg in "$STAGE"/*.pkg.tar.zst; do
|
|
got="$(pkginfo_name "$pkg")"
|
|
if [[ "$got" == "$name" ]]; then
|
|
shopt -u nullglob
|
|
return 0
|
|
fi
|
|
done
|
|
shopt -u nullglob
|
|
return 1
|
|
}
|
|
|
|
collect_from_registry() {
|
|
local work db name filename enc url
|
|
work="$(mktemp -d "${TMPDIR:-/tmp}/breadway-db.XXXXXX")"
|
|
db="$work/$DB_NAME"
|
|
curl -fL --retry 3 --retry-delay 2 -o "$db" "$REGISTRY/$ARCH/$DB_NAME" \
|
|
|| die "failed to fetch $REGISTRY/$ARCH/$DB_NAME"
|
|
while IFS=$'\t' read -r name filename; do
|
|
if has_pkg_named "$name"; then
|
|
printf 'using local %s, skip registry\n' "$name"
|
|
continue
|
|
fi
|
|
enc="$(urlencode "$filename")"
|
|
url="$REGISTRY/$ARCH/$enc"
|
|
printf 'fetch %s\n' "$filename"
|
|
curl -fL --retry 3 --retry-delay 2 -o "$STAGE/$filename" "$url" \
|
|
|| die "failed to fetch $url"
|
|
done < <(parse_registry_db "$db")
|
|
rm -rf "$work"
|
|
}
|
|
|
|
publish_tree() {
|
|
local parent dest_name prev
|
|
parent="$(dirname "$DEST")"
|
|
dest_name="$(basename "$DEST")"
|
|
mkdir -p "$parent"
|
|
chmod a+rX "$STAGE"
|
|
find "$STAGE" -type f -exec chmod a+r {} +
|
|
prev="$parent/${dest_name}.prev"
|
|
rm -rf "$prev"
|
|
if [[ -e "$DEST" ]]; then
|
|
mv "$DEST" "$prev"
|
|
fi
|
|
mv "$STAGE" "$DEST"
|
|
rm -rf "$prev"
|
|
STAGE=""
|
|
}
|
|
|
|
if [[ "${BREADWAY_SIGN_ONLY:-0}" == 1 ]]; then
|
|
[[ -n "$REPO_DIR" ]] || die "BREADWAY_SIGN_ONLY requires BREADWAY_REPO_DIR"
|
|
sign_and_index "$REPO_DIR"
|
|
exit 0
|
|
fi
|
|
|
|
need_key
|
|
|
|
DEST_PARENT="$(dirname "$DEST")"
|
|
mkdir -p "$DEST_PARENT" || die "cannot create $DEST_PARENT (runner must write /srv/breadway-dl)"
|
|
STAGE="$(mktemp -d "$DEST_PARENT/.stage-XXXXXX")"
|
|
cleanup() {
|
|
if [[ -n "${STAGE:-}" && -d "${STAGE:-}" ]]; then
|
|
rm -rf "$STAGE"
|
|
fi
|
|
if [[ -n "${GNUPGHOME:-}" && "$GNUPGHOME" == *gnupg-breadway-repo* ]]; then
|
|
rm -rf "$GNUPGHOME"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
copy_local_overrides "${BREADWAY_PKG_DIR:-}"
|
|
collect_from_registry
|
|
|
|
missing=()
|
|
for name in "${PACKAGES[@]}"; do
|
|
has_pkg_named "$name" || missing+=("$name")
|
|
done
|
|
if (( ${#missing[@]} > 0 )); then
|
|
die "missing packages after collect: ${missing[*]}"
|
|
fi
|
|
|
|
sign_and_index_anywhere "$STAGE"
|
|
|
|
# Do not publish helper junk if a container left any.
|
|
rm -f "$STAGE/.sign.sh"
|
|
|
|
publish_tree
|
|
|
|
printf 'published signed [breadway] repo -> %s\n' "$DEST"
|
|
ls -lh "$DEST"
|