2 Commits
Author SHA1 Message Date
alvinlollo 7f91c189db Mirror releases and seed README in AlvinArch-releases for discovery
Tests / tests (push) Successful in 2m22s
Create a matching git tag + release in alvinlollo/AlvinArch-releases next to
the main repo release, seed a short README there pointing back to
AlvinArch (workflow status badges on top), and extend release pruning to
the releases repo so both stay capped at 3.
2026-09-16 22:44:05 +10:00
alvinlollo 3816877b12 Fix ISO release uploads >3GB: zstd-compress and store via Git LFS in AlvinArch-releases
Release assets can no longer be attached directly (Gitea 500 on multi-GB
uploads). Compress each ISO with zstd, push the .zst into
alvinlollo/AlvinArch-releases (releases/<tag>/ on main) via Git LFS, verify
the LFS object server-side by OID, and create the release with a download
link plus SHA-256 checksums instead of a binary attachment.
2026-09-16 22:41:35 +10:00
+149 -59
View File
@@ -29,7 +29,7 @@ jobs:
- name: Install archiso and dependencies - name: Install archiso and dependencies
run: | run: |
set -euo pipefail set -euo pipefail
pacman -Syu --noconfirm --needed archiso jq pacman -Syu --noconfirm --needed archiso jq git-lfs zstd
- name: Build Arch ISO - name: Build Arch ISO
run: | run: |
@@ -73,12 +73,14 @@ jobs:
echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT" echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT"
echo "release_name=Arch Linux ISO (${BASE_DATE} Rev ${REVISION})" >> "$GITHUB_OUTPUT" echo "release_name=Arch Linux ISO (${BASE_DATE} Rev ${REVISION})" >> "$GITHUB_OUTPUT"
- name: Create Release and Upload ISO - name: Create Release and Upload ISO via Git LFS
env: env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
# Use internal Gitea URL to bypass traefik proxy timeout on large uploads # Use internal Gitea URL to bypass traefik proxy timeout on large uploads
GITEA_API: http://192.168.1.168:3000/api/v1 GITEA_API: http://192.168.1.168:3000/api/v1
GITEA_PUBLIC_URL: https://git.vilaythong.us
REPOSITORY: ${{ github.repository }} REPOSITORY: ${{ github.repository }}
RELEASES_REPO: alvinlollo/AlvinArch-releases
TAG_NAME: ${{ steps.tag.outputs.tag_name }} TAG_NAME: ${{ steps.tag.outputs.tag_name }}
RELEASE_NAME: ${{ steps.tag.outputs.release_name }} RELEASE_NAME: ${{ steps.tag.outputs.release_name }}
run: | run: |
@@ -94,15 +96,14 @@ jobs:
exit 1 exit 1
fi fi
# Create the release via Gitea API (internal, no traefik) RELEASES_OWNER="${RELEASES_REPO%%/*}"
RELEASE_ID=$(curl -sf -X POST \ RELEASES_NAME="${RELEASES_REPO#*/}"
-H "Authorization: token $GITEA_TOKEN" \ GITEA_BASE="${GITEA_API%/api/v1}"
-H "Content-Type: application/json" \ GITEA_SCHEME="${GITEA_BASE%%://*}"
"$GITEA_API/repos/$REPOSITORY/releases" \ GITEA_HOST="${GITEA_BASE#*://}"
-d "{\"tag_name\": \"$TAG_NAME\", \"name\": \"$RELEASE_NAME\"}" \ CLONE_URL="${GITEA_SCHEME}://${RELEASES_OWNER}:${GITEA_TOKEN}@${GITEA_HOST}/${RELEASES_REPO}.git"
| jq -er '.id') REPO_DIR=/tmp/alvinarch-releases
trap 'rm -rf "$REPO_DIR"' EXIT
echo "Created release ID: $RELEASE_ID"
# Collect the ISOs produced by the build # Collect the ISOs produced by the build
isos=(out/*.iso) isos=(out/*.iso)
@@ -111,6 +112,9 @@ jobs:
exit 1 exit 1
fi fi
# Compress each ISO with zstd and record its SHA-256
zst_files=()
sha256sums=()
for iso in "${isos[@]}"; do for iso in "${isos[@]}"; do
fname=$(basename "$iso") fname=$(basename "$iso")
size=$(stat -c %s "$iso") size=$(stat -c %s "$iso")
@@ -120,42 +124,126 @@ jobs:
exit 1 exit 1
fi fi
echo "Uploading $fname ($(( size / 1024 / 1024 )) MiB)..." echo "Compressing $fname ($(( size / 1024 / 1024 )) MiB) with zstd..."
zstd -T0 -19 -f "$iso" -o "$iso.zst"
http_code="" sha=$(sha256sum "$iso.zst" | awk '{print $1}')
for attempt in 1 2 3; do echo " ✅ ${fname}.zst: $(( $(stat -c %s "$iso.zst") / 1024 / 1024 )) MiB, sha256=$sha"
http_code=$(curl -sS --max-time 1800 \ zst_files+=("$iso.zst")
-o /dev/null -w "%{http_code}" \ sha256sums+=("$sha")
-H "Authorization: token $GITEA_TOKEN" \
-F "attachment=@$iso;filename=$fname" \
"$GITEA_API/repos/$REPOSITORY/releases/$RELEASE_ID/assets" || true)
if [[ "$http_code" == "201" ]]; then
echo " ✅ Upload OK (HTTP 201) on attempt $attempt"
break
fi
echo " ⚠️ Upload attempt $attempt returned HTTP $http_code — retrying..." >&2
sleep 5
done
if [[ "$http_code" != "201" ]]; then
echo "❌ Failed to upload '$fname' (last HTTP code: $http_code)" >&2
exit 1
fi
# Verify the exact asset made it: correct name AND byte size
confirmed=$(curl -sf -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_API/repos/$REPOSITORY/releases/$RELEASE_ID/assets" \
| jq -er --arg n "$fname" --argjson s "$size" \
'first(.[] | select(.name == $n and .size == $s) | .name) // empty')
if [[ -z "$confirmed" ]]; then
echo "❌ Verification failed: asset '$fname' ($size bytes) not found on the release." >&2
exit 1
fi
echo " ✅ Verified '$confirmed' ($size bytes) attached to $TAG_NAME"
done done
echo "Release $TAG_NAME created and ISOs uploaded." # Make sure the releases repo exists (create it via API if missing)
if ! curl -sf -o /dev/null -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_API/repos/$RELEASES_REPO"; then
echo "Creating releases repo '$RELEASES_REPO'..."
curl -sf -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_API/user/repos" \
-d "{\"name\": \"$RELEASES_NAME\", \"description\": \"Compressed Arch Linux live ISOs (Git LFS)\", \"auto_init\": true}" > /dev/null
fi
# Push the compressed ISOs to the releases repo via Git LFS
if ! git clone --quiet "$CLONE_URL" "$REPO_DIR" 2>/dev/null; then
echo "❌ Failed to clone $RELEASES_REPO (check token access)." >&2
exit 1
fi
# Work on the 'main' branch (create it if the remote default differs)
if git -C "$REPO_DIR" rev-parse --verify -q origin/main >/dev/null 2>&1; then
git -C "$REPO_DIR" checkout -q -B main origin/main
else
git -C "$REPO_DIR" checkout -q -B main
fi
git -C "$REPO_DIR" lfs install --local
git -C "$REPO_DIR" lfs track "*.iso.zst"
mkdir -p "$REPO_DIR/releases/$TAG_NAME"
for zst in "${zst_files[@]}"; do
cp "$zst" "$REPO_DIR/releases/$TAG_NAME/"
done
# Seed a short README redirecting to the main repo (status badges on top)
cat > "$REPO_DIR/README.md" <<'MDEOF'
[![Scheduled ArchISO Build](https://git.vilaythong.us/alvinlollo/AlvinArch/actions/workflows/build-iso.yaml/badge.svg?branch=main)](https://git.vilaythong.us/alvinlollo/AlvinArch/actions?workflow=build-iso.yaml)
[![Tests](https://git.vilaythong.us/alvinlollo/AlvinArch/actions/workflows/tests.yaml/badge.svg?branch=main)](https://git.vilaythong.us/alvinlollo/AlvinArch/actions?workflow=tests.yaml)
# AlvinArch Releases
Compressed (zstd) Arch Linux live ISOs built automatically by
[alvinlollo/AlvinArch](https://git.vilaythong.us/alvinlollo/AlvinArch).
**Main repository:** [alvinlollo/AlvinArch](https://git.vilaythong.us/alvinlollo/AlvinArch)
MDEOF
git -C "$REPO_DIR" config user.name "Gitea Actions"
git -C "$REPO_DIR" config user.email "actions@gitea.local"
git -C "$REPO_DIR" add .gitattributes README.md "releases/$TAG_NAME"
if git -C "$REPO_DIR" diff --cached --quiet; then
echo "⚠️ No changes to commit for $TAG_NAME — releasing as-is." >&2
else
git -C "$REPO_DIR" commit --quiet -m "Add $TAG_NAME"
fi
if ! git -C "$REPO_DIR" push --quiet origin main 2>/dev/null; then
echo "❌ Failed to push $TAG_NAME to $RELEASES_REPO." >&2
exit 1
fi
# Verify each LFS object exists server-side (oid == the sha256 we recorded)
for i in "${!zst_files[@]}"; do
fname=$(basename "${zst_files[$i]}")
size=$(stat -c %s "${zst_files[$i]}")
oid=${sha256sums[$i]}
lfs_payload=$(jq -nc --arg oid "$oid" --argjson sz "$size" \
'{operation:"download",transfers:["basic"],objects:[{oid:$oid,size:$sz}]}')
obj=$(curl -sf -u "$RELEASES_OWNER:$GITEA_TOKEN" \
-H "Content-Type: application/vnd.git-lfs+json" \
-d "$lfs_payload" \
"$GITEA_BASE/$RELEASES_REPO.git/info/lfs/objects/batch")
if [[ "$(jq -r '.objects[0] | has("actions")' <<< "$obj")" != "true" ]]; then
echo "❌ Verification failed: LFS object for '$fname' ($size bytes) missing on server." >&2
exit 1
fi
echo " ✅ Verified '$fname' ($size bytes) on $RELEASES_REPO"
done
# Create the release with a link to the file directory + SHA-256 checksums
DOWNLOAD_URL="$GITEA_PUBLIC_URL/$RELEASES_REPO/src/branch/main/releases/$TAG_NAME"
printf -v body 'Arch Linux live ISO %s\n\nDownload: %s\n\nChecksums (SHA-256):\n' \
"$RELEASE_NAME" "$DOWNLOAD_URL"
for i in "${!zst_files[@]}"; do
printf -v line -- '- `%s`: sha256 `%s`\n' "$(basename "${zst_files[$i]}")" "${sha256sums[$i]}"
body+="$line"
done
payload=$(jq -nc --arg tag "$TAG_NAME" --arg name "$RELEASE_NAME" --arg body "$body" \
'{tag_name:$tag,name:$name,body:$body}')
RELEASE_ID=$(curl -sf -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_API/repos/$REPOSITORY/releases" \
-d "$payload" | jq -er '.id')
echo " ✅ Released $TAG_NAME (ID: $RELEASE_ID) with download link + SHA-256 checksums"
# Mirror the release tag on the releases repo for redundancy/discovery
if ! git -C "$REPO_DIR" ls-remote --exit-code origin \
refs/tags/"$TAG_NAME" >/dev/null 2>&1; then
git -C "$REPO_DIR" tag "$TAG_NAME"
if ! git -C "$REPO_DIR" push --quiet origin "$TAG_NAME" 2>/dev/null; then
echo "❌ Failed to push tag $TAG_NAME to $RELEASES_REPO." >&2
exit 1
fi
fi
if ! curl -sf -o /dev/null -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_API/repos/$RELEASES_REPO/releases/tags/$TAG_NAME"; then
curl -sf -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$GITEA_API/repos/$RELEASES_REPO/releases" \
-d "$payload" > /dev/null
echo " ✅ Mirrored $TAG_NAME release on $RELEASES_REPO"
fi
- name: Keep only latest 3 releases - name: Keep only latest 3 releases
if: success() if: success()
@@ -163,25 +251,27 @@ jobs:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_API: http://192.168.1.168:3000/api/v1 GITEA_API: http://192.168.1.168:3000/api/v1
REPOSITORY: ${{ github.repository }} REPOSITORY: ${{ github.repository }}
RELEASES_REPO: alvinlollo/AlvinArch-releases
run: | run: |
set -euo pipefail set -euo pipefail
test -n "$GITEA_TOKEN" test -n "$GITEA_TOKEN"
# Fetch all release ids, oldest first for repo in "$REPOSITORY" "$RELEASES_REPO"; do
readarray -t ids < <(curl -sf -H "Authorization: token $GITEA_TOKEN" \ echo "Pruning old releases in $repo..."
"$GITEA_API/repos/$REPOSITORY/releases" \ readarray -t ids < <(curl -sf -H "Authorization: token $GITEA_TOKEN" \
| jq -er 'sort_by(.created_at) | .[].id') "$GITEA_API/repos/$repo/releases" \
| jq -er 'sort_by(.created_at) | .[].id')
# Delete the oldest releases, keeping only the 3 newest to_delete=$(( ${#ids[@]} - 3 ))
to_delete=$(( ${#ids[@]} - 3 )) if (( to_delete > 0 )); then
if (( to_delete > 0 )); then for ((i = 0; i < to_delete; i++)); do
for ((i = 0; i < to_delete; i++)); do id=${ids[$i]}
id=${ids[$i]} echo "Deleting old release ID: $id (in $repo)"
echo "Deleting old release ID: $id" curl -sf -X DELETE -H "Authorization: token $GITEA_TOKEN" \
curl -sf -X DELETE -H "Authorization: token $GITEA_TOKEN" \ "$GITEA_API/repos/$repo/releases/$id"
"$GITEA_API/repos/$REPOSITORY/releases/$id" done
done fi
fi done
- name: Cleanup mkarchiso mounts and workdirs - name: Cleanup mkarchiso mounts and workdirs
if: always() # Runs on success, failure, or cancellation if: always() # Runs on success, failure, or cancellation