#!/bin/bash # Create or update a shared Forgejo release with branded macOS assets. set -e VERSION="" BRANDS=() FORGEJO_USER="${FORGEJO_USER}" FORGEJO_PASS="${FORGEJO_PASS}" FORGEJO_URL="https://git.him-tools.de" REPO="HIM-public/webdrop-bridge" CLEAR_CREDS=false DRY_RUN=false PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" BRAND_HELPER="$PROJECT_ROOT/build/scripts/brand_config.py" MANIFEST_OUTPUT="$PROJECT_ROOT/build/dist/release-manifest.json" LOCAL_MANIFEST_OUTPUT="$PROJECT_ROOT/build/dist/release-manifest.local.json" EXISTING_MANIFEST_OUTPUT="$PROJECT_ROOT/build/dist/release-manifest.existing.json" LOCAL_DATA_OUTPUT="$PROJECT_ROOT/build/dist/release-data.local.json" while [[ $# -gt 0 ]]; do case $1 in -v|--version) VERSION="$2"; shift 2 ;; -u|--url) FORGEJO_URL="$2"; shift 2 ;; --brand) BRANDS+=("$2"); shift 2 ;; --clear-credentials) CLEAR_CREDS=true; shift ;; --dry-run) DRY_RUN=true; shift ;; *) echo "Unknown option: $1"; exit 1 ;; esac done if [ "$CLEAR_CREDS" = true ]; then unset FORGEJO_USER unset FORGEJO_PASS echo "[OK] Credentials cleared from this session" exit 0 fi if [ -z "$VERSION" ]; then VERSION="$(python3 -c "from pathlib import Path; import sys; sys.path.insert(0, str(Path(r'$PROJECT_ROOT/build/scripts').resolve())); from version_utils import get_current_version; print(get_current_version())")" fi LOCAL_ARGS=("$BRAND_HELPER" "local-release-data" "--platform" "macos" "--version" "$VERSION") if [ ${#BRANDS[@]} -gt 0 ]; then LOCAL_ARGS+=("--brands" "${BRANDS[@]}") fi python3 "${LOCAL_ARGS[@]}" > "$LOCAL_DATA_OUTPUT" mapfile -t ARTIFACTS < <(python3 - "$LOCAL_DATA_OUTPUT" "$LOCAL_MANIFEST_OUTPUT" <<'PY' import json import sys from pathlib import Path data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) Path(sys.argv[2]).write_text(json.dumps(data["manifest"], indent=2), encoding="utf-8") for artifact in data["artifacts"]: print(artifact) PY ) for ARTIFACT in "${ARTIFACTS[@]}"; do if [ -f "$ARTIFACT" ] && [ "${ARTIFACT##*.}" = "dmg" ]; then DMG_SIZE=$(du -m "$ARTIFACT" | cut -f1) echo "macOS artifact: $(basename "$ARTIFACT") ($DMG_SIZE MB)" fi done if [ ${#ARTIFACTS[@]} -eq 0 ]; then echo "ERROR: No local macOS artifacts found" exit 1 fi if [ "$DRY_RUN" = true ]; then cp "$LOCAL_MANIFEST_OUTPUT" "$MANIFEST_OUTPUT" DISCOVERED_BRANDS=$(python3 - "$LOCAL_DATA_OUTPUT" <<'PY' import json import sys from pathlib import Path data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) print(", ".join(data.get("brands", [])) or "") PY ) echo "[DRY RUN] No network requests or uploads will be performed." echo "[DRY RUN] Release tag: v$VERSION" echo "[DRY RUN] Release URL: $FORGEJO_URL/$REPO/releases/tag/v$VERSION" echo "[DRY RUN] Discovered brands: $DISCOVERED_BRANDS" echo "[DRY RUN] Artifacts that would be uploaded:" for ARTIFACT in "${ARTIFACTS[@]}"; do echo " - $ARTIFACT" done echo "[DRY RUN] Local manifest preview: $MANIFEST_OUTPUT" exit 0 fi if [ -z "$FORGEJO_USER" ] || [ -z "$FORGEJO_PASS" ]; then echo "Forgejo credentials not found. Enter your credentials:" if [ -z "$FORGEJO_USER" ]; then read -r -p "Username: " FORGEJO_USER fi if [ -z "$FORGEJO_PASS" ]; then read -r -s -p "Password: " FORGEJO_PASS echo "" fi export FORGEJO_USER export FORGEJO_PASS fi BASIC_AUTH=$(echo -n "${FORGEJO_USER}:${FORGEJO_PASS}" | base64) RELEASE_URL="$FORGEJO_URL/api/v1/repos/$REPO/releases" RELEASE_LOOKUP_URL="$FORGEJO_URL/api/v1/repos/$REPO/releases/tags/v$VERSION" RELEASE_RESPONSE_FILE=$(mktemp) HTTP_CODE=$(curl -s -o "$RELEASE_RESPONSE_FILE" -w "%{http_code}" -H "Authorization: Basic $BASIC_AUTH" "$RELEASE_LOOKUP_URL") if [ "$HTTP_CODE" = "200" ]; then RELEASE_ID=$(python3 - "$RELEASE_RESPONSE_FILE" <<'PY' import json import sys from pathlib import Path payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) print(payload.get("id", "")) PY ) else RELEASE_ID="" fi if [ -z "$RELEASE_ID" ]; then RELEASE_DATA=$(cat </dev/null else cp "$LOCAL_MANIFEST_OUTPUT" "$MANIFEST_OUTPUT" fi ARTIFACTS+=("$MANIFEST_OUTPUT") UPLOAD_URL="$FORGEJO_URL/api/v1/repos/$REPO/releases/$RELEASE_ID/assets" for ARTIFACT in "${ARTIFACTS[@]}"; do ASSET_NAME="$(basename "$ARTIFACT")" EXISTING_ASSET_ID=$(python3 - "$RELEASE_RESPONSE_FILE" "$ASSET_NAME" <<'PY' import json import sys from pathlib import Path payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) asset_name = sys.argv[2] for asset in payload.get("assets", []): if asset.get("name") == asset_name: print(asset.get("id", "")) break PY ) if [ -n "$EXISTING_ASSET_ID" ]; then curl -s -X DELETE \ -H "Authorization: Basic $BASIC_AUTH" \ "$FORGEJO_URL/api/v1/repos/$REPO/releases/$RELEASE_ID/assets/$EXISTING_ASSET_ID" >/dev/null echo "[OK] Replaced existing asset $ASSET_NAME" fi HTTP_CODE=$(curl -s -w "%{http_code}" -X POST \ -H "Authorization: Basic $BASIC_AUTH" \ -F "attachment=@$ARTIFACT" \ "$UPLOAD_URL" \ -o /tmp/curl_response.txt) if [ "$HTTP_CODE" -eq 201 ] || [ "$HTTP_CODE" -eq 200 ]; then echo "[OK] Uploaded $ASSET_NAME" else echo "ERROR uploading $ASSET_NAME (HTTP $HTTP_CODE)" cat /tmp/curl_response.txt exit 1 fi done echo "" echo "[OK] Release complete!" echo "View at: $FORGEJO_URL/$REPO/releases/tag/v$VERSION"