ci: publish signed [breadway] repo to dl.breadway.dev/arch
Some checks failed
Build and release ISO / release-iso (push) Failing after 46s
Some checks failed
Build and release ISO / release-iso (push) Failing after 46s
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.
This commit is contained in:
parent
8cd5ec9e16
commit
9fe02eeea6
5 changed files with 453 additions and 32 deletions
298
scripts/ci-publish-signed-repo.sh
Executable file
298
scripts/ci-publish-signed-repo.sh
Executable file
|
|
@ -0,0 +1,298 @@
|
|||
#!/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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue