Skip to content

feat: upload ISOs to R2 bucket with S3-compat fallback #2

feat: upload ISOs to R2 bucket with S3-compat fallback

feat: upload ISOs to R2 bucket with S3-compat fallback #2

name: Build Community Editions
on:
schedule:
- cron: '0 6 * * 1' # weekly Monday 6am UTC
workflow_dispatch:
permissions:
contents: read
concurrency:
group: build-community-${{ github.sha }}
cancel-in-progress: false
jobs:
build:
strategy:
fail-fast: false
max-parallel: 2
matrix:
edition:
- slug: gnome
repo: spivanatalie64/acreetionos-gnome
desktop: GNOME
display: Wayland
arch: x86_64
- slug: plasma
repo: spivanatalie64/acreetionos-plasma
desktop: KDE Plasma
display: Wayland
arch: x86_64
- slug: xfce
repo: spivanatalie64/acreetionos-xfce
desktop: XFCE
display: XLibre
arch: x86_64
- slug: i3
repo: spivanatalie64/acreetionos-i3
desktop: i3
display: X11
arch: x86_64
- slug: sway
repo: spivanatalie64/acreetionos-sway
desktop: Sway
display: Wayland
arch: x86_64
- slug: mate
repo: spivanatalie64/acreetionos-mate
desktop: MATE
display: XLibre
arch: x86_64
- slug: openbox
repo: spivanatalie64/acreetionos-openbox
desktop: Openbox
display: XLibre
arch: x86_64
- slug: hyprland
repo: spivanatalie64/acreetionos-hyprland
desktop: Hyprland
display: Wayland
arch: x86_64
- slug: 32bit
repo: spivanatalie64/acreetionos32
desktop: Cinnamon
display: XLibre
arch: i686
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout edition repo
uses: actions/checkout@v4
with:
repository: ${{ matrix.edition.repo }}
path: src
- name: Apply branding
run: |
sed -i 's/sudo //g' src/build.sh 2>/dev/null || true
sed -i 's/iso_label=.*/iso_label="AcreetionOS ${{ matrix.edition.desktop }}"/' src/profiledef.sh 2>/dev/null || true
sed -i 's/iso_publisher=.*/iso_publisher="AcreetionOS Project <https:\/\/acreetionos.org>"/' src/profiledef.sh 2>/dev/null || true
sed -i 's/iso_application=.*/iso_application="AcreetionOS Linux Distribution"/' src/profiledef.sh 2>/dev/null || true
- name: Emit build metadata
id: meta
run: |
echo "slug=${{ matrix.edition.slug }}" >> $GITHUB_OUTPUT
echo "date_tag=$(date +%Y%m%d)" >> $GITHUB_OUTPUT
echo "iso_name=${{ matrix.edition.slug }}-$(date +%Y%m%d).iso" >> $GITHUB_OUTPUT
- name: Write building status to R2
env:
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
STATUS='{"edition":"${{ matrix.edition.desktop }}","slug":"${{ steps.meta.outputs.slug }}","status":"building","started":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","run_url":"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}","iso_name":"${{ steps.meta.outputs.iso_name }}","desktop":"${{ matrix.edition.desktop }}","display":"${{ matrix.edition.display }}","arch":"${{ matrix.edition.arch }}"}'
echo "$STATUS" > /tmp/status.json
curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects/${{ steps.meta.outputs.slug }}-status.json" \
-H "Authorization: Bearer $CF_TOKEN" \
--data-binary @/tmp/status.json \
-H "Content-Type: application/json" || echo "Status write skipped"
- name: Build ISO in Docker
id: build
run: |
mkdir -p /tmp/iso
docker run --privileged --rm \
-v $PWD/src:/workspace \
archlinux@sha256:fb47d15e0fd28369c700edcfa46dd44e234d8ab6fee9627ee5f1ead5f1fc6c21 \
bash -c '
pacman -Syu --noconfirm archiso grub && \
cd /workspace && \
chmod +x *.sh 2>/dev/null; \
bash build.sh 2>&1 || { echo "BUILD_FAILED"; exit 1; }
' 2>&1 | tee /tmp/build.log
ls -la src/out/ 2>/dev/null && cp src/out/*.iso /tmp/iso/ 2>/dev/null || true
if [ -z "$(ls /tmp/iso/*.iso 2>/dev/null)" ]; then
echo "build_ok=false" >> $GITHUB_OUTPUT
else
echo "build_ok=true" >> $GITHUB_OUTPUT
fi
- name: Upload build log
if: always()
env:
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
if [ -f /tmp/build.log ]; then
curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects/${{ steps.meta.outputs.slug }}-build.log" \
-H "Authorization: Bearer $CF_TOKEN" \
--data-binary @/tmp/build.log \
-H "Content-Type: text/plain" || echo "Log upload skipped"
fi
- name: Prepare ISO for upload
id: iso
if: steps.build.outputs.build_ok == 'true'
run: |
ISO=$(ls /tmp/iso/*.iso 2>/dev/null | head -1)
if [ -n "$ISO" ]; then
cp "$ISO" "/tmp/${{ steps.meta.outputs.iso_name }}"
echo "iso_path=/tmp/${{ steps.meta.outputs.iso_name }}" >> $GITHUB_OUTPUT
echo "iso_name=${{ steps.meta.outputs.iso_name }}" >> $GITHUB_OUTPUT
echo "ok=true" >> $GITHUB_OUTPUT
else
echo "ok=false" >> $GITHUB_OUTPUT
fi
- name: Upload ISO to R2
if: steps.iso.outputs.ok == 'true'
env:
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
# Upload to R2 using Cloudflare API
echo "Uploading ${{ steps.meta.outputs.iso_name }} to R2..."
curl -s -X PUT \
"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects/${{ steps.meta.outputs.iso_name }}" \
-H "Authorization: Bearer $CF_TOKEN" \
--data-binary @"${{ steps.iso.outputs.iso_path }}" \
-H "Content-Type: application/octet-stream" && echo "Uploaded: ${{ steps.meta.outputs.iso_name }}" || echo "R2 upload failed"
# Upload as latest too
curl -s -X PUT \
"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects/${{ steps.meta.outputs.slug }}-latest.iso" \
-H "Authorization: Bearer $CF_TOKEN" \
--data-binary @"${{ steps.iso.outputs.iso_path }}" \
-H "Content-Type: application/octet-stream" && echo "Uploaded: ${{ steps.meta.outputs.slug }}-latest.iso" || echo "Latest upload failed"
# Also try S3-compatible upload as fallback
echo "Also trying S3-compatible upload..."
pip3 install -q boto3 2>/dev/null || true
python3 -c "
import boto3, os

Check failure on line 175 in .github/workflows/build-community.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/build-community.yml

Invalid workflow file

You have an error in your yaml syntax on line 175
s3 = boto3.client('s3',
endpoint_url=f'https://{os.environ[\"CF_ACCOUNT\"]}.r2.cloudflarestorage.com',
aws_access_key_id=os.environ['CF_ACCOUNT'],
aws_secret_access_key=os.environ['CF_TOKEN'],
region_name='auto')
try:
s3.upload_file('${{ steps.iso.outputs.iso_path }}', 'acreetionos-isos', '${{ steps.meta.outputs.iso_name }}')
print('S3 upload OK')
s3.copy_object(Bucket='acreetionos-isos', CopySource=f'/acreetionos-isos/${{ steps.meta.outputs.iso_name }}', Key='${{ steps.meta.outputs.slug }}-latest.iso')
print('Latest copy OK')
except Exception as e:
print(f'S3 upload failed: {e}')
" 2>&1 || echo "S3 upload skipped"
- name: Update build status
if: always()
env:
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
if [ "${{ steps.iso.outputs.ok }}" = "true" ]; then
STATUS='{"edition":"${{ matrix.edition.desktop }}","slug":"${{ steps.meta.outputs.slug }}","status":"success","finished":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","run_url":"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}","iso_name":"${{ steps.meta.outputs.iso_name }}","download_url":"https://pub-173a1f638a3b4c95b5f58b09c0b968aa.r2.dev/acreetionos-isos/${{ steps.meta.outputs.iso_name }}"}'
else
STATUS='{"edition":"${{ matrix.edition.desktop }}","slug":"${{ steps.meta.outputs.slug }}","status":"failed","finished":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","run_url":"https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}","desktop":"${{ matrix.edition.desktop }}","display":"${{ matrix.edition.display }}","arch":"${{ matrix.edition.arch }}"}'
fi
echo "$STATUS" > /tmp/status.json
curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects/${{ steps.meta.outputs.slug }}-status.json" \
-H "Authorization: Bearer $CF_TOKEN" \
--data-binary @/tmp/status.json \
-H "Content-Type: application/json" || echo "Status update skipped"
cleanup:
needs: build
runs-on: ubuntu-latest
if: always()
steps:
- name: Delete old ISOs
env:
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
echo "Cleaning up old ISOs..."
OBJECTS=$(curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects" \
-H "Authorization: Bearer $CF_TOKEN")
echo "$OBJECTS" | python3 -c "
import sys, json, os
from datetime import datetime, timezone
cutoff = datetime.now(timezone.utc).timestamp() - (30 * 86400)
data = json.load(sys.stdin)
objects = data.get('result', {}).get('objects', [])
for obj in objects:
key = obj.get('key', '')
if 'latest' in key or 'status' in key or 'build.log' in key:
continue # keep latest/status/log files
uploaded = obj.get('uploaded', '')
try:
ts = datetime.fromisoformat(uploaded.replace('Z', '+00:00')).timestamp()
if ts < cutoff:
os.system(f'curl -s -X DELETE \"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT/r2/buckets/acreetionos-isos/objects/{key}\" -H \"Authorization: Bearer $CF_TOKEN\" && echo \"Deleted {key}\"')
except: pass
" 2>/dev/null || echo "Cleanup skipped"