Add a Gitea Actions pipeline that publishes the image as a release

Builds the image on a self-hosted runner and publishes it, with the checksum and
a corresponding-source offer, as a Gitea release.

The schedule is daily but the build is conditional. Proxmox does not ship daily,
so an unconditional daily build would produce roughly 45 GB a month of
near-identical artifacts; instead the job compares the newest pve-manager in the
configured repository against the last published release and stops early when
they match. A manual trigger with `force` rebuilds anyway. Releases are tagged
after the version they contain (`pve-9.2.11`) rather than the date, so the tag
says something useful, and older ones are pruned to keep three.

The logic lives in scripts/ci/ rather than inline in the workflow. Shell inside a
YAML block scalar cannot carry an indented heredoc terminator, and the release
body needs several; scripts also mean the pieces can be run and tested by hand.
A `print-var` target exposes single Makefile variables to them.

Two constraints shaped this:

  * The runner is registered in host mode, so steps run directly on the build
    machine as root. The build needs /dev/kvm, qemu-nbd, FUSE and root, which a
    container would have to be given anyway. The consequence — anything able to
    dispatch a workflow gets root on that machine — is stated in the workflow
    header rather than left implicit.
  * Checkout is a plain git clone. actions/checkout is a JavaScript action and
    the host-mode runner has no Node.js runtime.

The artifact is named maas-image-pve-<version>-amd64.tar.gz, not
proxmox-ve-*.tar.gz, and the release body says the build is unofficial and
unaffiliated. Proxmox permits redistribution under the AGPLv3 but asks that the
trademark not be used in product names.

SOURCES.md is generated per release: the image is an unmodified installation of
Debian and Proxmox packages, so it points at those archives for the
corresponding source, and records that the firmware licence texts ship inside the
image at /usr/share/doc/pve-firmware/licenses/ and must not be stripped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 22:35:30 +02:00
parent 77bf86a1b8
commit e964933af1
6 changed files with 320 additions and 1 deletions

View File

@@ -0,0 +1,65 @@
#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Moves the built image into dist/ under a release-friendly name and writes the
# checksum plus the corresponding-source offer that has to accompany a binary
# distribution of GPL/AGPL software.
#
# assemble-artifacts.sh <pve-manager-version>
set -euo pipefail
cd "$(dirname "$0")/../.."
VERSION="${1:?pve-manager version required}"
OUT=$(make -s print-var VAR=OUTPUT)
ARCH=$(make -s print-var VAR=ARCH)
[ -f "$OUT" ] || { echo "built image not found: $OUT" >&2; exit 1; }
# Deliberately not named "proxmox-ve-*": that would look like an official
# Proxmox artifact. Proxmox asks that their trademark not be used in product
# names, so the image is named after what it is - a MAAS image.
NAME="maas-image-pve-${VERSION}-${ARCH}.tar.gz"
rm -rf dist && mkdir -p dist
mv "$OUT" "dist/${NAME}"
( cd dist && sha256sum "$NAME" > "${NAME}.sha256" )
tar xzf "dist/${NAME}" -O ./etc/pve-maas/image-info > dist/image-info.txt 2>/dev/null \
|| echo "(this image predates /etc/pve-maas/image-info)" > dist/image-info.txt
DEB_VER=$(make -s print-var VAR=DEBIAN_VERSION)
DEB_SUITE=$(make -s print-var VAR=DEBIAN_SERIES)
PVE_REPO=$(make -s print-var VAR=PVE_REPO)
PVE_URI=$(make -s print-var VAR=PVE_REPO_URI)
REPO_URL="${GITHUB_SERVER_URL:-}/${GITHUB_REPOSITORY:-}"
{
echo "# Corresponding source"
echo
echo "This image is an unmodified installation of packages from the archives listed"
echo "below. No package was patched. What is ours is the selection and the"
echo "configuration, and that is the entire content of the repository linked below."
echo
echo "Written offer, per GPLv2 section 3 and GPLv3 section 6: the complete"
echo "corresponding source for every package in this image is available from the"
echo "archive it was installed from, at the versions recorded in the image's own"
echo "package database (\`/var/lib/dpkg/status\`)."
echo
echo "| Component | Source |"
echo "|---|---|"
echo "| Debian ${DEB_VER} \"${DEB_SUITE}\" | <https://deb.debian.org/debian> — \`apt-get source <pkg>\` |"
echo "| Proxmox VE (${PVE_REPO}) | <https://git.proxmox.com/> and \`deb-src ${PVE_URI}\` |"
echo "| Build recipe | <${REPO_URL}> |"
echo
echo "The firmware blobs under \`/usr/lib/firmware\` are redistributed verbatim;"
echo "their licence texts ship inside the image at"
echo "\`/usr/share/doc/pve-firmware/licenses/\` and must not be stripped."
echo
echo "## Image metadata"
echo
echo '```'
cat dist/image-info.txt
echo '```'
} > dist/SOURCES.md
ls -lh dist/

29
scripts/ci/prune-releases.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Keeps the newest N releases and deletes the rest, tags included.
# Each image is ~1.5 GB, so unbounded retention fills the server.
#
# prune-releases.sh [keep] (default 3)
#
# Requires GITEA_API and GITEA_TOKEN in the environment.
set -euo pipefail
KEEP="${1:-3}"
: "${GITEA_API:?GITEA_API required}"
: "${GITEA_TOKEN:?GITEA_TOKEN required}"
curl -fsS -H "Authorization: token ${GITEA_TOKEN}" "${GITEA_API}/releases?limit=50" \
| KEEP="$KEEP" python3 -c '
import json, os, sys
keep = int(os.environ["KEEP"])
for r in json.load(sys.stdin)[keep:]:
print(r["id"], r["tag_name"])
' | while read -r id tag; do
echo "removing old release ${tag}"
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API}/releases/${id}" || true
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_API}/tags/${tag}" || true
done

75
scripts/ci/publish-release.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Creates a Gitea release and uploads the image, its checksum and the
# corresponding-source offer.
#
# publish-release.sh <tag> <version> <dist-dir>
#
# Requires GITEA_API and GITEA_TOKEN in the environment.
set -euo pipefail
TAG="${1:?tag required}"
VERSION="${2:?version required}"
DIST="${3:?dist directory required}"
: "${GITEA_API:?GITEA_API required}"
: "${GITEA_TOKEN:?GITEA_TOKEN required}"
REPO_URL="${GITHUB_SERVER_URL:-}/${GITHUB_REPOSITORY:-}"
RUN="${GITHUB_RUN_NUMBER:-manual}"
SHA="${GITHUB_SHA:-}"
IMAGE=$(basename "$(ls "$DIST"/maas-image-*.tar.gz)")
BODY=$(cat <<BODYEOF
Unofficial build of a MAAS-deployable Proxmox VE image.
**Not affiliated with or endorsed by Proxmox Server Solutions GmbH.**
- pve-manager: \`${VERSION}\`
- Built: $(date -u +%Y-%m-%dT%H:%M:%SZ), run ${RUN}
- Verify: \`sha256sum -c ${IMAGE}.sha256\`
Upload to MAAS:
\`\`\`bash
maas \$PROFILE boot-resources create name='custom/proxmox-ve-9' \\
title='Proxmox VE 9' architecture='amd64/generic' \\
filetype='tgz' content@=${IMAGE}
\`\`\`
The curtin preseed from this repository must also be installed on the MAAS region
controller, otherwise deployment fails — see the README.
\`SOURCES.md\` in this release carries the corresponding-source offer required by
the GPL/AGPL licences of the packages inside the image.
BODYEOF
)
PAYLOAD=$(TAG="$TAG" VERSION="$VERSION" BODY="$BODY" SHA="$SHA" python3 -c '
import json, os
print(json.dumps({
"tag_name": os.environ["TAG"],
"name": "Proxmox VE %s image" % os.environ["VERSION"],
"body": os.environ["BODY"],
"draft": False,
"prerelease": False,
"target_commitish": os.environ["SHA"],
}))')
ID=$(curl -fsS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H 'Content-Type: application/json' \
-d "$PAYLOAD" "${GITEA_API}/releases" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
echo "release id: ${ID}"
for f in "$DIST"/*; do
[ -f "$f" ] || continue
echo "uploading $(basename "$f") ($(du -h "$f" | cut -f1))"
curl -fsS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@${f}" \
"${GITEA_API}/releases/${ID}/assets?name=$(basename "$f")" >/dev/null
done
echo "published: ${REPO_URL}/releases/tag/${TAG}"

21
scripts/ci/upstream-version.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Prints the newest pve-manager version in the configured Proxmox repository.
# Used by CI to decide whether a rebuild is worth doing.
set -euo pipefail
cd "$(dirname "$0")/../.."
SUITE=$(make -s print-var VAR=DEBIAN_SERIES)
COMP=$(make -s print-var VAR=PVE_REPO)
URI=$(make -s print-var VAR=PVE_REPO_URI)
ARCH=$(make -s print-var VAR=ARCH)
VER=$(curl -fsS "${URI}/dists/${SUITE}/${COMP}/binary-${ARCH}/Packages.gz" \
| gunzip \
| awk '/^Package: pve-manager$/{p=1; next} p && /^Version: /{print $2; p=0}' \
| sort -V | tail -1)
[ -n "$VER" ] || { echo "pve-manager version not found in ${URI} ${SUITE}/${COMP}" >&2; exit 1; }
printf '%s\n' "$VER"