From bab43b69c5a04dbd0a144cfcf1bed11aa2c42c27 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 01:58:44 -0600 Subject: [PATCH 01/11] add build pipeline for macos --- .gitea/workflows/build-release.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 5015681..9336033 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -42,6 +42,24 @@ jobs: path: target/release/mumble-web2-proxy retention-days: 5 + macos_build: + runs-on: macos + steps: + - name: Checkout + uses: actions/checkout@v5 + + - uses: Swatinem/rust-cache@v2 + + - name: Build dioxus project + run: dx build --platform macos --release -p mumble-web2-gui + + - name: Upload mumble-web2-gui Artifact + uses: https://gitea.com/actions/gitea-upload-artifact@v4 + with: + name: mumble-web2-gui-macos-arm64 + path: gui/dist + retention-days: 5 + windows_build: runs-on: windows steps: -- 2.52.0 From f91f4cb7b6558df3bd12a328c911adbc1c262f23 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:13:10 -0600 Subject: [PATCH 02/11] add cargo binstall step --- .gitea/workflows/build-release.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 9336033..acab893 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -48,6 +48,12 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Install cargo binstall + run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + + - name: Install dioxus-cli + run: cargo binstall dioxus-cli --version 0.7.3 + - uses: Swatinem/rust-cache@v2 - name: Build dioxus project -- 2.52.0 From b8b022dcdc261529160be18c199494a2eb9e8191 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:15:16 -0600 Subject: [PATCH 03/11] cargo binstall --no-confirm --- .gitea/workflows/build-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index acab893..42d9354 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -52,7 +52,7 @@ jobs: run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - name: Install dioxus-cli - run: cargo binstall dioxus-cli --version 0.7.3 + run: cargo binstall dioxus-cli --version 0.7.3 --no-confirm - uses: Swatinem/rust-cache@v2 -- 2.52.0 From 7a1ff1109c5cb8c008d05fbc84c01ecf0376a86d Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:26:24 -0600 Subject: [PATCH 04/11] debug print --- .gitea/workflows/build-release.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 42d9354..253124b 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -56,6 +56,9 @@ jobs: - uses: Swatinem/rust-cache@v2 + - name: Print env + run: env + - name: Build dioxus project run: dx build --platform macos --release -p mumble-web2-gui -- 2.52.0 From 6dd77d67ee4c7c5ef0245f8efe674af7b3e981ea Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:31:59 -0600 Subject: [PATCH 05/11] remove print env --- .gitea/workflows/build-release.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 253124b..42d9354 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -56,9 +56,6 @@ jobs: - uses: Swatinem/rust-cache@v2 - - name: Print env - run: env - - name: Build dioxus project run: dx build --platform macos --release -p mumble-web2-gui -- 2.52.0 From 9a2e52f9486354339a043e0a23edf24ad50c3750 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:38:30 -0600 Subject: [PATCH 06/11] fixup --- .gitea/workflows/build-release.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 42d9354..c71840d 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -54,10 +54,8 @@ jobs: - name: Install dioxus-cli run: cargo binstall dioxus-cli --version 0.7.3 --no-confirm - - uses: Swatinem/rust-cache@v2 - - name: Build dioxus project - run: dx build --platform macos --release -p mumble-web2-gui + run: dx bundle --platform macos --release -p mumble-web2-gui - name: Upload mumble-web2-gui Artifact uses: https://gitea.com/actions/gitea-upload-artifact@v4 -- 2.52.0 From 9ebccbc1ff92818571a0b2aeaf5c9575aaabd684 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:39:19 -0600 Subject: [PATCH 07/11] custom macos cache logic --- .gitea/workflows/build-release.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index c71840d..945d616 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -48,6 +48,16 @@ jobs: - name: Checkout uses: actions/checkout@v5 + - name: Restore Rust cache + uses: actions/cache/restore@v4 + with: + path: | + ~/.cargo + ./target + key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + rust-${{ runner.os }}- + - name: Install cargo binstall run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash @@ -57,6 +67,15 @@ jobs: - name: Build dioxus project run: dx bundle --platform macos --release -p mumble-web2-gui + - name: Save Rust cache + if: always() + uses: actions/cache/save@v4 + with: + path: | + ~/.cargo + ./target + key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + - name: Upload mumble-web2-gui Artifact uses: https://gitea.com/actions/gitea-upload-artifact@v4 with: -- 2.52.0 From 36ea42e4181e9f90814ef1be33e4149c3da36f23 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 02:58:02 -0600 Subject: [PATCH 08/11] increase artifact upload concurrency --- .gitea/workflows/build-release.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 945d616..b7efc3b 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -82,6 +82,9 @@ jobs: name: mumble-web2-gui-macos-arm64 path: gui/dist retention-days: 5 + env: + ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY: 10 + windows_build: runs-on: windows -- 2.52.0 From cba1187f2dfe448ffccacd7592d698e21be24c51 Mon Sep 17 00:00:00 2001 From: restitux Date: Mon, 9 Mar 2026 03:08:45 -0600 Subject: [PATCH 09/11] manual tarballing --- .gitea/workflows/build-release.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index b7efc3b..1e14969 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -76,15 +76,16 @@ jobs: ./target key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + - name: Tar mumble-web2-gui + run: | + tar -czf mumble-web2-gui-macos-arm64.tar.gz -C gui dist + - name: Upload mumble-web2-gui Artifact uses: https://gitea.com/actions/gitea-upload-artifact@v4 with: name: mumble-web2-gui-macos-arm64 - path: gui/dist + path: mumble-web2-gui-macos-arm64.tar.gz retention-days: 5 - env: - ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY: 10 - windows_build: runs-on: windows -- 2.52.0 From 52e48efdcf2a301ceefe827f372b5b44b956960b Mon Sep 17 00:00:00 2001 From: restitux Date: Tue, 10 Mar 2026 21:03:51 -0600 Subject: [PATCH 10/11] claude script to upload artifacts --- .gitea/workflows/build-release.yaml | 6 +- scripts/upload-artifact.sh | 287 ++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+), 5 deletions(-) create mode 100755 scripts/upload-artifact.sh diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 1e14969..302c01c 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -81,11 +81,7 @@ jobs: tar -czf mumble-web2-gui-macos-arm64.tar.gz -C gui dist - name: Upload mumble-web2-gui Artifact - uses: https://gitea.com/actions/gitea-upload-artifact@v4 - with: - name: mumble-web2-gui-macos-arm64 - path: mumble-web2-gui-macos-arm64.tar.gz - retention-days: 5 + run: ./scripts/upload-artifact.sh --name mumble-web2-gui-macos-arm64 --path mumble-web2-gui-macos-arm64.tar.gz windows_build: runs-on: windows diff --git a/scripts/upload-artifact.sh b/scripts/upload-artifact.sh new file mode 100755 index 0000000..b4c8d1e --- /dev/null +++ b/scripts/upload-artifact.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash +# +# upload-artifact.sh - Upload artifacts to Gitea Actions using the v4 artifact protocol. +# Replaces gitea-upload-artifact@v4 action with a simple shell script. +# +# Usage: +# ./upload-artifact.sh --name --path [--retention-days ] +# +# Required environment variables (set automatically by Gitea Actions runner): +# ACTIONS_RUNTIME_TOKEN - JWT bearer token +# ACTIONS_RESULTS_URL - Artifact service base URL +# +set -euo pipefail + +CHUNK_SIZE=$((8 * 1024 * 1024)) # 8 MB + +# ---------- argument parsing ---------- +ARTIFACT_NAME="" +ARTIFACT_PATH="" +RETENTION_DAYS="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --name) ARTIFACT_NAME="$2"; shift 2 ;; + --path) ARTIFACT_PATH="$2"; shift 2 ;; + --retention-days) RETENTION_DAYS="$2"; shift 2 ;; + *) echo "Unknown option: $1" >&2; exit 1 ;; + esac +done + +if [[ -z "$ARTIFACT_NAME" || -z "$ARTIFACT_PATH" ]]; then + echo "Usage: $0 --name --path [--retention-days ]" >&2 + exit 1 +fi + +if [[ -z "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then + echo "Error: ACTIONS_RUNTIME_TOKEN is not set" >&2 + exit 1 +fi + +if [[ -z "${ACTIONS_RESULTS_URL:-}" ]]; then + echo "Error: ACTIONS_RESULTS_URL is not set" >&2 + exit 1 +fi + +# ---------- helpers ---------- + +# Decode base64url (JWT uses base64url without padding) +base64url_decode() { + local input="$1" + # Replace URL-safe chars with standard base64 chars + input="${input//-/+}" + input="${input//_//}" + # Add padding + local pad=$(( 4 - ${#input} % 4 )) + if [[ $pad -lt 4 ]]; then + for ((i=0; i/dev/null || echo "$input" | base64 -D 2>/dev/null +} + +# Extract backend IDs from the JWT token's scp claim +# Format: "Actions.Results::" +extract_backend_ids() { + local token="$ACTIONS_RUNTIME_TOKEN" + # JWT has 3 parts separated by dots; payload is the second + local payload + payload=$(echo "$token" | cut -d'.' -f2) + local decoded + decoded=$(base64url_decode "$payload") + + # Extract the scp claim - look for the Actions.Results entry + local scp_value + # Try jq first, fall back to manual parsing + if command -v jq &>/dev/null; then + scp_value=$(echo "$decoded" | jq -r '.scp // empty') + else + # Simple extraction: find "scp":"..." or "scp": "..." + scp_value=$(echo "$decoded" | sed -n 's/.*"scp"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + fi + + if [[ -z "$scp_value" ]]; then + echo "Error: Could not extract scp claim from token" >&2 + exit 1 + fi + + # The scp may contain multiple space-separated scopes + # Find the one starting with "Actions.Results:" + local results_scope="" + for scope in $scp_value; do + if [[ "$scope" == Actions.Results:* ]]; then + results_scope="$scope" + break + fi + done + + if [[ -z "$results_scope" ]]; then + echo "Error: No Actions.Results scope found in token" >&2 + exit 1 + fi + + WORKFLOW_RUN_BACKEND_ID=$(echo "$results_scope" | cut -d':' -f2) + WORKFLOW_JOB_RUN_BACKEND_ID=$(echo "$results_scope" | cut -d':' -f3) + + if [[ -z "$WORKFLOW_RUN_BACKEND_ID" || -z "$WORKFLOW_JOB_RUN_BACKEND_ID" ]]; then + echo "Error: Could not parse backend IDs from scope: $results_scope" >&2 + exit 1 + fi +} + +# Strip trailing slash from URL +normalize_url() { + echo "${1%/}" +} + +# ---------- main flow ---------- + +echo "==> Uploading artifact: $ARTIFACT_NAME" +echo " Path: $ARTIFACT_PATH" + +# 1. Extract backend IDs from JWT +extract_backend_ids +echo " Run backend ID: $WORKFLOW_RUN_BACKEND_ID" +echo " Job backend ID: $WORKFLOW_JOB_RUN_BACKEND_ID" + +BASE_URL=$(normalize_url "$ACTIONS_RESULTS_URL") +TWIRP_BASE="${BASE_URL}/twirp/github.actions.results.api.v1.ArtifactService" + +# 2. Create zip of the artifact content +TMPDIR_UPLOAD=$(mktemp -d) +trap 'rm -rf "$TMPDIR_UPLOAD"' EXIT + +ZIP_FILE="${TMPDIR_UPLOAD}/artifact.zip" + +if [[ -f "$ARTIFACT_PATH" ]]; then + # Single file - zip it at the top level + PARENT_DIR=$(dirname "$ARTIFACT_PATH") + FILE_NAME=$(basename "$ARTIFACT_PATH") + (cd "$PARENT_DIR" && zip -q "$ZIP_FILE" "$FILE_NAME") +elif [[ -d "$ARTIFACT_PATH" ]]; then + # Directory - zip its contents + (cd "$ARTIFACT_PATH" && zip -qr "$ZIP_FILE" .) +else + echo "Error: Path does not exist: $ARTIFACT_PATH" >&2 + exit 1 +fi + +ZIP_SIZE=$(wc -c < "$ZIP_FILE" | tr -d ' ') +echo " Zip size: $ZIP_SIZE bytes" + +# Compute SHA-256 hash of the zip +if command -v sha256sum &>/dev/null; then + ZIP_HASH=$(sha256sum "$ZIP_FILE" | cut -d' ' -f1) +elif command -v shasum &>/dev/null; then + ZIP_HASH=$(shasum -a 256 "$ZIP_FILE" | cut -d' ' -f1) +else + echo "Error: Neither sha256sum nor shasum found" >&2 + exit 1 +fi +echo " SHA-256: $ZIP_HASH" + +# 3. CreateArtifact - get signed upload URL +echo "==> Creating artifact..." + +CREATE_BODY="{\"workflow_run_backend_id\":\"${WORKFLOW_RUN_BACKEND_ID}\",\"workflow_job_run_backend_id\":\"${WORKFLOW_JOB_RUN_BACKEND_ID}\",\"name\":\"${ARTIFACT_NAME}\",\"version\":4}" + +CREATE_RESPONSE=$(curl -sS -X POST \ + "${TWIRP_BASE}/CreateArtifact" \ + -H "Authorization: Bearer ${ACTIONS_RUNTIME_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$CREATE_BODY") + +echo " Create response: $CREATE_RESPONSE" + +# Extract signed upload URL +if command -v jq &>/dev/null; then + SIGNED_URL=$(echo "$CREATE_RESPONSE" | jq -r '.signed_upload_url // .signedUploadUrl // empty') + CREATE_OK=$(echo "$CREATE_RESPONSE" | jq -r '.ok // empty') +else + # Fallback: extract URL from JSON manually + SIGNED_URL=$(echo "$CREATE_RESPONSE" | sed -n 's/.*"signed_upload_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + if [[ -z "$SIGNED_URL" ]]; then + SIGNED_URL=$(echo "$CREATE_RESPONSE" | sed -n 's/.*"signedUploadUrl"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + fi + CREATE_OK="true" +fi + +if [[ -z "$SIGNED_URL" ]]; then + echo "Error: Failed to get signed upload URL" >&2 + echo "Response: $CREATE_RESPONSE" >&2 + exit 1 +fi + +echo " Got signed upload URL" + +# 4. Upload zip in chunks using Azure Blob block protocol +NUM_CHUNKS=$(( (ZIP_SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE )) +echo "==> Uploading artifact data (${ZIP_SIZE} bytes in ${NUM_CHUNKS} chunk(s) of up to ${CHUNK_SIZE} bytes)..." + +# Split the zip into chunks +CHUNK_PREFIX="${TMPDIR_UPLOAD}/chunk_" +split -b "$CHUNK_SIZE" "$ZIP_FILE" "$CHUNK_PREFIX" + +BLOCK_IDS=() +CHUNK_INDEX=0 + +for CHUNK_FILE in "${CHUNK_PREFIX}"*; do + THIS_CHUNK=$(wc -c < "$CHUNK_FILE" | tr -d ' ') + + # Generate block ID: zero-padded index, base64-encoded + BLOCK_ID_RAW=$(printf "%05d" "$CHUNK_INDEX") + BLOCK_ID=$(echo -n "$BLOCK_ID_RAW" | base64) + BLOCK_IDS+=("$BLOCK_ID") + + # URL-encode the block ID for the query parameter + BLOCK_ID_ENCODED=$(echo "$BLOCK_ID" | sed 's/+/%2B/g; s/\//%2F/g; s/=/%3D/g') + + UPLOAD_URL="${SIGNED_URL}&comp=block&blockid=${BLOCK_ID_ENCODED}" + + HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \ + "$UPLOAD_URL" \ + -H "Content-Type: application/octet-stream" \ + -H "Content-Length: ${THIS_CHUNK}" \ + --data-binary "@${CHUNK_FILE}") + + if [[ "$HTTP_CODE" != "201" ]]; then + echo "Error: Chunk upload failed with HTTP $HTTP_CODE (chunk $CHUNK_INDEX)" >&2 + exit 1 + fi + + rm -f "$CHUNK_FILE" + echo " Uploaded chunk $((CHUNK_INDEX + 1))/${NUM_CHUNKS} ($THIS_CHUNK bytes)" + CHUNK_INDEX=$((CHUNK_INDEX + 1)) +done + +# 5. Commit block list +echo "==> Committing block list (${#BLOCK_IDS[@]} blocks)..." + +BLOCK_LIST_XML='' +for bid in "${BLOCK_IDS[@]}"; do + BLOCK_LIST_XML+="${bid}" +done +BLOCK_LIST_XML+='' + +BLOCKLIST_URL="${SIGNED_URL}&comp=blocklist" + +HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \ + "$BLOCKLIST_URL" \ + -H "Content-Type: application/xml" \ + -d "$BLOCK_LIST_XML") + +if [[ "$HTTP_CODE" != "201" ]]; then + echo "Error: Block list commit failed with HTTP $HTTP_CODE" >&2 + exit 1 +fi + +echo " Block list committed" + +# 6. FinalizeArtifact +echo "==> Finalizing artifact..." + +FINALIZE_BODY="{\"workflow_run_backend_id\":\"${WORKFLOW_RUN_BACKEND_ID}\",\"workflow_job_run_backend_id\":\"${WORKFLOW_JOB_RUN_BACKEND_ID}\",\"name\":\"${ARTIFACT_NAME}\",\"size\":\"${ZIP_SIZE}\",\"hash\":{\"value\":\"sha256:${ZIP_HASH}\"}}" + +FINALIZE_RESPONSE=$(curl -sS -X POST \ + "${TWIRP_BASE}/FinalizeArtifact" \ + -H "Authorization: Bearer ${ACTIONS_RUNTIME_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$FINALIZE_BODY") + +echo " Finalize response: $FINALIZE_RESPONSE" + +# Check success +if command -v jq &>/dev/null; then + FINALIZE_OK=$(echo "$FINALIZE_RESPONSE" | jq -r '.ok // empty') + ARTIFACT_ID=$(echo "$FINALIZE_RESPONSE" | jq -r '.artifact_id // .artifactId // empty') +else + FINALIZE_OK=$(echo "$FINALIZE_RESPONSE" | sed -n 's/.*"ok"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p') + ARTIFACT_ID=$(echo "$FINALIZE_RESPONSE" | sed -n 's/.*"artifact_id"[[:space:]]*:[[:space:]]*"\{0,1\}\([0-9]*\)"\{0,1\}.*/\1/p') +fi + +if [[ "$FINALIZE_OK" != "true" ]]; then + echo "Error: Finalize failed" >&2 + echo "Response: $FINALIZE_RESPONSE" >&2 + exit 1 +fi + +echo "==> Artifact '$ARTIFACT_NAME' uploaded successfully (ID: ${ARTIFACT_ID:-unknown})" -- 2.52.0 From fc7783336eb6f3c5cc9bf21b112ca1fc0841521f Mon Sep 17 00:00:00 2001 From: restitux Date: Tue, 10 Mar 2026 21:22:23 -0600 Subject: [PATCH 11/11] cleaunp issues --- .gitea/workflows/build-release.yaml | 10 +- scripts/upload-artifact.sh | 287 ---------------------------- 2 files changed, 5 insertions(+), 292 deletions(-) delete mode 100755 scripts/upload-artifact.sh diff --git a/.gitea/workflows/build-release.yaml b/.gitea/workflows/build-release.yaml index 302c01c..945d616 100644 --- a/.gitea/workflows/build-release.yaml +++ b/.gitea/workflows/build-release.yaml @@ -76,12 +76,12 @@ jobs: ./target key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - - name: Tar mumble-web2-gui - run: | - tar -czf mumble-web2-gui-macos-arm64.tar.gz -C gui dist - - name: Upload mumble-web2-gui Artifact - run: ./scripts/upload-artifact.sh --name mumble-web2-gui-macos-arm64 --path mumble-web2-gui-macos-arm64.tar.gz + uses: https://gitea.com/actions/gitea-upload-artifact@v4 + with: + name: mumble-web2-gui-macos-arm64 + path: gui/dist + retention-days: 5 windows_build: runs-on: windows diff --git a/scripts/upload-artifact.sh b/scripts/upload-artifact.sh deleted file mode 100755 index b4c8d1e..0000000 --- a/scripts/upload-artifact.sh +++ /dev/null @@ -1,287 +0,0 @@ -#!/usr/bin/env bash -# -# upload-artifact.sh - Upload artifacts to Gitea Actions using the v4 artifact protocol. -# Replaces gitea-upload-artifact@v4 action with a simple shell script. -# -# Usage: -# ./upload-artifact.sh --name --path [--retention-days ] -# -# Required environment variables (set automatically by Gitea Actions runner): -# ACTIONS_RUNTIME_TOKEN - JWT bearer token -# ACTIONS_RESULTS_URL - Artifact service base URL -# -set -euo pipefail - -CHUNK_SIZE=$((8 * 1024 * 1024)) # 8 MB - -# ---------- argument parsing ---------- -ARTIFACT_NAME="" -ARTIFACT_PATH="" -RETENTION_DAYS="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --name) ARTIFACT_NAME="$2"; shift 2 ;; - --path) ARTIFACT_PATH="$2"; shift 2 ;; - --retention-days) RETENTION_DAYS="$2"; shift 2 ;; - *) echo "Unknown option: $1" >&2; exit 1 ;; - esac -done - -if [[ -z "$ARTIFACT_NAME" || -z "$ARTIFACT_PATH" ]]; then - echo "Usage: $0 --name --path [--retention-days ]" >&2 - exit 1 -fi - -if [[ -z "${ACTIONS_RUNTIME_TOKEN:-}" ]]; then - echo "Error: ACTIONS_RUNTIME_TOKEN is not set" >&2 - exit 1 -fi - -if [[ -z "${ACTIONS_RESULTS_URL:-}" ]]; then - echo "Error: ACTIONS_RESULTS_URL is not set" >&2 - exit 1 -fi - -# ---------- helpers ---------- - -# Decode base64url (JWT uses base64url without padding) -base64url_decode() { - local input="$1" - # Replace URL-safe chars with standard base64 chars - input="${input//-/+}" - input="${input//_//}" - # Add padding - local pad=$(( 4 - ${#input} % 4 )) - if [[ $pad -lt 4 ]]; then - for ((i=0; i/dev/null || echo "$input" | base64 -D 2>/dev/null -} - -# Extract backend IDs from the JWT token's scp claim -# Format: "Actions.Results::" -extract_backend_ids() { - local token="$ACTIONS_RUNTIME_TOKEN" - # JWT has 3 parts separated by dots; payload is the second - local payload - payload=$(echo "$token" | cut -d'.' -f2) - local decoded - decoded=$(base64url_decode "$payload") - - # Extract the scp claim - look for the Actions.Results entry - local scp_value - # Try jq first, fall back to manual parsing - if command -v jq &>/dev/null; then - scp_value=$(echo "$decoded" | jq -r '.scp // empty') - else - # Simple extraction: find "scp":"..." or "scp": "..." - scp_value=$(echo "$decoded" | sed -n 's/.*"scp"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') - fi - - if [[ -z "$scp_value" ]]; then - echo "Error: Could not extract scp claim from token" >&2 - exit 1 - fi - - # The scp may contain multiple space-separated scopes - # Find the one starting with "Actions.Results:" - local results_scope="" - for scope in $scp_value; do - if [[ "$scope" == Actions.Results:* ]]; then - results_scope="$scope" - break - fi - done - - if [[ -z "$results_scope" ]]; then - echo "Error: No Actions.Results scope found in token" >&2 - exit 1 - fi - - WORKFLOW_RUN_BACKEND_ID=$(echo "$results_scope" | cut -d':' -f2) - WORKFLOW_JOB_RUN_BACKEND_ID=$(echo "$results_scope" | cut -d':' -f3) - - if [[ -z "$WORKFLOW_RUN_BACKEND_ID" || -z "$WORKFLOW_JOB_RUN_BACKEND_ID" ]]; then - echo "Error: Could not parse backend IDs from scope: $results_scope" >&2 - exit 1 - fi -} - -# Strip trailing slash from URL -normalize_url() { - echo "${1%/}" -} - -# ---------- main flow ---------- - -echo "==> Uploading artifact: $ARTIFACT_NAME" -echo " Path: $ARTIFACT_PATH" - -# 1. Extract backend IDs from JWT -extract_backend_ids -echo " Run backend ID: $WORKFLOW_RUN_BACKEND_ID" -echo " Job backend ID: $WORKFLOW_JOB_RUN_BACKEND_ID" - -BASE_URL=$(normalize_url "$ACTIONS_RESULTS_URL") -TWIRP_BASE="${BASE_URL}/twirp/github.actions.results.api.v1.ArtifactService" - -# 2. Create zip of the artifact content -TMPDIR_UPLOAD=$(mktemp -d) -trap 'rm -rf "$TMPDIR_UPLOAD"' EXIT - -ZIP_FILE="${TMPDIR_UPLOAD}/artifact.zip" - -if [[ -f "$ARTIFACT_PATH" ]]; then - # Single file - zip it at the top level - PARENT_DIR=$(dirname "$ARTIFACT_PATH") - FILE_NAME=$(basename "$ARTIFACT_PATH") - (cd "$PARENT_DIR" && zip -q "$ZIP_FILE" "$FILE_NAME") -elif [[ -d "$ARTIFACT_PATH" ]]; then - # Directory - zip its contents - (cd "$ARTIFACT_PATH" && zip -qr "$ZIP_FILE" .) -else - echo "Error: Path does not exist: $ARTIFACT_PATH" >&2 - exit 1 -fi - -ZIP_SIZE=$(wc -c < "$ZIP_FILE" | tr -d ' ') -echo " Zip size: $ZIP_SIZE bytes" - -# Compute SHA-256 hash of the zip -if command -v sha256sum &>/dev/null; then - ZIP_HASH=$(sha256sum "$ZIP_FILE" | cut -d' ' -f1) -elif command -v shasum &>/dev/null; then - ZIP_HASH=$(shasum -a 256 "$ZIP_FILE" | cut -d' ' -f1) -else - echo "Error: Neither sha256sum nor shasum found" >&2 - exit 1 -fi -echo " SHA-256: $ZIP_HASH" - -# 3. CreateArtifact - get signed upload URL -echo "==> Creating artifact..." - -CREATE_BODY="{\"workflow_run_backend_id\":\"${WORKFLOW_RUN_BACKEND_ID}\",\"workflow_job_run_backend_id\":\"${WORKFLOW_JOB_RUN_BACKEND_ID}\",\"name\":\"${ARTIFACT_NAME}\",\"version\":4}" - -CREATE_RESPONSE=$(curl -sS -X POST \ - "${TWIRP_BASE}/CreateArtifact" \ - -H "Authorization: Bearer ${ACTIONS_RUNTIME_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$CREATE_BODY") - -echo " Create response: $CREATE_RESPONSE" - -# Extract signed upload URL -if command -v jq &>/dev/null; then - SIGNED_URL=$(echo "$CREATE_RESPONSE" | jq -r '.signed_upload_url // .signedUploadUrl // empty') - CREATE_OK=$(echo "$CREATE_RESPONSE" | jq -r '.ok // empty') -else - # Fallback: extract URL from JSON manually - SIGNED_URL=$(echo "$CREATE_RESPONSE" | sed -n 's/.*"signed_upload_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') - if [[ -z "$SIGNED_URL" ]]; then - SIGNED_URL=$(echo "$CREATE_RESPONSE" | sed -n 's/.*"signedUploadUrl"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') - fi - CREATE_OK="true" -fi - -if [[ -z "$SIGNED_URL" ]]; then - echo "Error: Failed to get signed upload URL" >&2 - echo "Response: $CREATE_RESPONSE" >&2 - exit 1 -fi - -echo " Got signed upload URL" - -# 4. Upload zip in chunks using Azure Blob block protocol -NUM_CHUNKS=$(( (ZIP_SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE )) -echo "==> Uploading artifact data (${ZIP_SIZE} bytes in ${NUM_CHUNKS} chunk(s) of up to ${CHUNK_SIZE} bytes)..." - -# Split the zip into chunks -CHUNK_PREFIX="${TMPDIR_UPLOAD}/chunk_" -split -b "$CHUNK_SIZE" "$ZIP_FILE" "$CHUNK_PREFIX" - -BLOCK_IDS=() -CHUNK_INDEX=0 - -for CHUNK_FILE in "${CHUNK_PREFIX}"*; do - THIS_CHUNK=$(wc -c < "$CHUNK_FILE" | tr -d ' ') - - # Generate block ID: zero-padded index, base64-encoded - BLOCK_ID_RAW=$(printf "%05d" "$CHUNK_INDEX") - BLOCK_ID=$(echo -n "$BLOCK_ID_RAW" | base64) - BLOCK_IDS+=("$BLOCK_ID") - - # URL-encode the block ID for the query parameter - BLOCK_ID_ENCODED=$(echo "$BLOCK_ID" | sed 's/+/%2B/g; s/\//%2F/g; s/=/%3D/g') - - UPLOAD_URL="${SIGNED_URL}&comp=block&blockid=${BLOCK_ID_ENCODED}" - - HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \ - "$UPLOAD_URL" \ - -H "Content-Type: application/octet-stream" \ - -H "Content-Length: ${THIS_CHUNK}" \ - --data-binary "@${CHUNK_FILE}") - - if [[ "$HTTP_CODE" != "201" ]]; then - echo "Error: Chunk upload failed with HTTP $HTTP_CODE (chunk $CHUNK_INDEX)" >&2 - exit 1 - fi - - rm -f "$CHUNK_FILE" - echo " Uploaded chunk $((CHUNK_INDEX + 1))/${NUM_CHUNKS} ($THIS_CHUNK bytes)" - CHUNK_INDEX=$((CHUNK_INDEX + 1)) -done - -# 5. Commit block list -echo "==> Committing block list (${#BLOCK_IDS[@]} blocks)..." - -BLOCK_LIST_XML='' -for bid in "${BLOCK_IDS[@]}"; do - BLOCK_LIST_XML+="${bid}" -done -BLOCK_LIST_XML+='' - -BLOCKLIST_URL="${SIGNED_URL}&comp=blocklist" - -HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" -X PUT \ - "$BLOCKLIST_URL" \ - -H "Content-Type: application/xml" \ - -d "$BLOCK_LIST_XML") - -if [[ "$HTTP_CODE" != "201" ]]; then - echo "Error: Block list commit failed with HTTP $HTTP_CODE" >&2 - exit 1 -fi - -echo " Block list committed" - -# 6. FinalizeArtifact -echo "==> Finalizing artifact..." - -FINALIZE_BODY="{\"workflow_run_backend_id\":\"${WORKFLOW_RUN_BACKEND_ID}\",\"workflow_job_run_backend_id\":\"${WORKFLOW_JOB_RUN_BACKEND_ID}\",\"name\":\"${ARTIFACT_NAME}\",\"size\":\"${ZIP_SIZE}\",\"hash\":{\"value\":\"sha256:${ZIP_HASH}\"}}" - -FINALIZE_RESPONSE=$(curl -sS -X POST \ - "${TWIRP_BASE}/FinalizeArtifact" \ - -H "Authorization: Bearer ${ACTIONS_RUNTIME_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$FINALIZE_BODY") - -echo " Finalize response: $FINALIZE_RESPONSE" - -# Check success -if command -v jq &>/dev/null; then - FINALIZE_OK=$(echo "$FINALIZE_RESPONSE" | jq -r '.ok // empty') - ARTIFACT_ID=$(echo "$FINALIZE_RESPONSE" | jq -r '.artifact_id // .artifactId // empty') -else - FINALIZE_OK=$(echo "$FINALIZE_RESPONSE" | sed -n 's/.*"ok"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p') - ARTIFACT_ID=$(echo "$FINALIZE_RESPONSE" | sed -n 's/.*"artifact_id"[[:space:]]*:[[:space:]]*"\{0,1\}\([0-9]*\)"\{0,1\}.*/\1/p') -fi - -if [[ "$FINALIZE_OK" != "true" ]]; then - echo "Error: Finalize failed" >&2 - echo "Response: $FINALIZE_RESPONSE" >&2 - exit 1 -fi - -echo "==> Artifact '$ARTIFACT_NAME' uploaded successfully (ID: ${ARTIFACT_ID:-unknown})" -- 2.52.0