diff --git a/.github/workflows/promote-floating-tags.yml b/.github/workflows/promote-floating-tags.yml new file mode 100644 index 000000000..c3c4615b3 --- /dev/null +++ b/.github/workflows/promote-floating-tags.yml @@ -0,0 +1,100 @@ +name: Promote Floating Tags + +on: + release: + types: [published] + +jobs: + promote-images: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + OWNER: ${{ github.repository_owner }} + TAG: ${{ github.event.release.tag_name }} + PRERELEASE: ${{ github.event.release.prerelease }} + + steps: + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Promote Pulse server image tags + run: | + set -euo pipefail + TAG="${TAG}" + VERSION="${TAG#v}" + BASE_VERSION="${VERSION%%-*}" + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION" + MINOR=${MINOR:-0} + MAJOR_MINOR="$MAJOR.$MINOR" + + if [ "$PRERELEASE" = "true" ]; then + echo "Promoting prerelease tags for ${TAG}" + docker buildx imagetools create \ + -t rcourtman/pulse:rc \ + rcourtman/pulse:${TAG} + docker buildx imagetools create \ + -t ghcr.io/${OWNER}/pulse:rc \ + ghcr.io/${OWNER}/pulse:${TAG} + else + echo "Promoting stable tags for ${TAG}" + docker buildx imagetools create \ + -t rcourtman/pulse:latest \ + -t rcourtman/pulse:${MAJOR_MINOR} \ + -t rcourtman/pulse:${MAJOR} \ + rcourtman/pulse:${TAG} + docker buildx imagetools create \ + -t ghcr.io/${OWNER}/pulse:latest \ + -t ghcr.io/${OWNER}/pulse:${MAJOR_MINOR} \ + -t ghcr.io/${OWNER}/pulse:${MAJOR} \ + ghcr.io/${OWNER}/pulse:${TAG} + fi + + - name: Promote Docker agent image tags + run: | + set -euo pipefail + TAG="${TAG}" + VERSION="${TAG#v}" + BASE_VERSION="${VERSION%%-*}" + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION" + MINOR=${MINOR:-0} + MAJOR_MINOR="$MAJOR.$MINOR" + + if [ "$PRERELEASE" = "true" ]; then + docker buildx imagetools create \ + -t rcourtman/pulse-docker-agent:rc \ + rcourtman/pulse-docker-agent:${TAG} + docker buildx imagetools create \ + -t ghcr.io/${OWNER}/pulse-docker-agent:rc \ + ghcr.io/${OWNER}/pulse-docker-agent:${TAG} + else + docker buildx imagetools create \ + -t rcourtman/pulse-docker-agent:latest \ + -t rcourtman/pulse-docker-agent:${MAJOR_MINOR} \ + -t rcourtman/pulse-docker-agent:${MAJOR} \ + rcourtman/pulse-docker-agent:${TAG} + docker buildx imagetools create \ + -t ghcr.io/${OWNER}/pulse-docker-agent:latest \ + -t ghcr.io/${OWNER}/pulse-docker-agent:${MAJOR_MINOR} \ + -t ghcr.io/${OWNER}/pulse-docker-agent:${MAJOR} \ + ghcr.io/${OWNER}/pulse-docker-agent:${TAG} + fi + + - name: Promotion summary + run: | + if [ "$PRERELEASE" = "true" ]; then + echo "Updated rc tags to point to ${TAG} for both server and agent images." + else + echo "Updated latest/${MAJOR_MINOR}/${MAJOR} tags to point to ${TAG} for both server and agent images." + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4967cf50..46bc598f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,100 @@ on: type: string jobs: + version_guard: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Ensure VERSION matches requested release + run: | + FILE_VERSION=$(cat VERSION | tr -d '\n') + REQUESTED_VERSION="${{ inputs.version }}" + if [ "$FILE_VERSION" != "$REQUESTED_VERSION" ]; then + echo "::error::VERSION file ($FILE_VERSION) does not match requested version ($REQUESTED_VERSION)." + echo "Update the VERSION file and commit the change before running the release workflow." + exit 1 + fi + echo "VERSION file matches requested release ($REQUESTED_VERSION)." + + preflight-tests: + needs: version_guard + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install frontend dependencies + run: npm --prefix frontend-modern ci + + - name: Build frontend bundle for Go embed + run: | + npm --prefix frontend-modern run build + rm -rf internal/api/frontend-modern + mkdir -p internal/api/frontend-modern + cp -r frontend-modern/dist internal/api/frontend-modern/ + + - name: Lint frontend + run: npm --prefix frontend-modern run lint + + - name: Install docker-compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Run backend tests + run: go test ./... + + - name: Prepare integration test dependencies + working-directory: tests/integration + run: | + npm ci + npx playwright install --with-deps chromium + + - name: Build Pulse for integration tests + run: make build + + - name: Build Docker images for integration tests + run: | + docker build -t pulse-mock-github:test tests/integration/mock-github-server + docker build -t pulse:test -f Dockerfile . + + - name: Run update integration smoke tests + working-directory: tests/integration + env: + MOCK_CHECKSUM_ERROR: "false" + MOCK_NETWORK_ERROR: "false" + MOCK_RATE_LIMIT: "false" + MOCK_STALE_RELEASE: "false" + run: | + docker-compose -f docker-compose.test.yml up -d + # Allow services to settle before running Playwright + sleep 20 + npx playwright test tests/01-happy-path.spec.ts tests/02-bad-checksums.spec.ts --reporter=list + docker-compose -f docker-compose.test.yml down -v + + - name: Cleanup integration environment + if: always() + working-directory: tests/integration + run: docker-compose -f docker-compose.test.yml down -v || true + build-docker-images: + needs: + - version_guard + - preflight-tests runs-on: ubuntu-latest timeout-minutes: 60 permissions: @@ -57,10 +150,10 @@ jobs: push: true provenance: false tags: | - rcourtman/pulse:latest rcourtman/pulse:${{ steps.set_version.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/pulse:latest + rcourtman/pulse:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/pulse:${{ steps.set_version.outputs.tag }} + ghcr.io/${{ github.repository_owner }}/pulse:${{ inputs.version }} labels: | org.opencontainers.image.title=Pulse org.opencontainers.image.description=Proxmox monitoring system @@ -81,10 +174,10 @@ jobs: push: true provenance: false tags: | - rcourtman/pulse-docker-agent:latest rcourtman/pulse-docker-agent:${{ steps.set_version.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:latest + rcourtman/pulse-docker-agent:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:${{ steps.set_version.outputs.tag }} + ghcr.io/${{ github.repository_owner }}/pulse-docker-agent:${{ inputs.version }} labels: | org.opencontainers.image.title=Pulse Docker Agent org.opencontainers.image.description=Docker container monitoring agent for Pulse @@ -101,11 +194,11 @@ jobs: echo "" echo "Server images:" echo " - rcourtman/pulse:${{ steps.set_version.outputs.tag }}" - echo " - rcourtman/pulse:latest" + echo " - rcourtman/pulse:${{ inputs.version }}" echo "" echo "Agent images:" echo " - rcourtman/pulse-docker-agent:${{ steps.set_version.outputs.tag }}" - echo " - rcourtman/pulse-docker-agent:latest" + echo " - rcourtman/pulse-docker-agent:${{ inputs.version }}" create-release: needs: build-docker-images @@ -113,6 +206,11 @@ jobs: timeout-minutes: 30 permissions: contents: write + outputs: + tag: ${{ steps.create_release.outputs.tag }} + release_id: ${{ steps.create_release.outputs.release_id }} + release_url: ${{ steps.create_release.outputs.release_url }} + target_commitish: ${{ steps.commit_metadata.outputs.commitish }} steps: - name: Checkout repository @@ -123,6 +221,10 @@ jobs: - name: Fetch all tags run: git fetch --tags --force + - name: Capture commit metadata + id: commit_metadata + run: echo "commitish=$GITHUB_SHA" >> $GITHUB_OUTPUT + - name: Set up Go uses: actions/setup-go@v5 with: @@ -147,20 +249,6 @@ jobs: echo "Building release v${{ inputs.version }}..." ./scripts/build-release.sh ${{ inputs.version }} - - name: Validate release artifacts - run: | - echo "Validating release v${{ inputs.version }}..." - # Note: Docker image validation is skipped in this workflow - # since we're only validating the built artifacts (tarballs, binaries, checksums) - # Docker images were built and validated in previous job - - # Run validation with --skip-docker flag - ./scripts/validate-release.sh ${{ inputs.version }} --skip-docker || { - echo "❌ Release validation failed!" - echo "The workflow will now fail. No release will be created." - exit 1 - } - - name: Generate release notes id: generate_notes env: @@ -233,6 +321,7 @@ jobs: rm -f "$NOTES_FILE" echo "release_url=$(gh release view ${TAG} --json url -q .url)" >> $GITHUB_OUTPUT + echo "release_id=$(gh release view ${TAG} --json id -q .id)" >> $GITHUB_OUTPUT echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Upload checksums.txt @@ -287,7 +376,18 @@ jobs: echo "2. Update the release notes with changes since last release" echo "3. Publish the release manually when ready" echo "" - echo "All artifacts have been uploaded and validated." + echo "All artifacts have been uploaded." echo "Docker images are available at Docker Hub and GHCR." - echo "checksums.txt matches all uploaded binaries." + echo "Automated validation will now verify the release assets." echo "" + + validate-release-assets: + needs: create-release + uses: ./.github/workflows/validate-release-assets.yml + secrets: inherit + with: + tag: ${{ needs.create-release.outputs.tag }} + version: ${{ inputs.version }} + release_id: ${{ needs.create-release.outputs.release_id }} + draft: true + target_commitish: ${{ needs.create-release.outputs.target_commitish }} diff --git a/.github/workflows/validate-release-assets.yml b/.github/workflows/validate-release-assets.yml index a160372ff..6b6ac9f30 100644 --- a/.github/workflows/validate-release-assets.yml +++ b/.github/workflows/validate-release-assets.yml @@ -1,48 +1,127 @@ name: Validate Release Assets on: - workflow_run: - workflows: ["Release"] - types: [completed] + workflow_call: + inputs: + tag: + description: 'Release tag (e.g., v4.29.0)' + required: true + type: string + version: + description: 'Version number without v prefix (e.g., 4.29.0)' + required: true + type: string + release_id: + description: 'GitHub release ID' + required: true + type: string + draft: + description: 'Whether the release is still a draft' + required: true + type: boolean + target_commitish: + description: 'Commit SHA associated with the release' + required: true + type: string release: - types: [edited] # Still validate on manual edits + types: [edited] + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g., v4.29.0)' + required: true + type: string + version: + description: 'Version number without v prefix (e.g., 4.29.0)' + required: true + type: string + release_id: + description: 'GitHub release ID' + required: true + type: string + draft: + description: 'Set to true to run against a draft release' + required: true + type: boolean + target_commitish: + description: 'Commit SHA associated with the release' + required: true + type: string jobs: validate: - # Only run for draft releases to act as a safety gate before publishing - if: github.event.release.draft == true runs-on: ubuntu-latest - permissions: - contents: write # Needed to delete assets and update release - issues: write # Needed to add labels and comments + contents: write + issues: write steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Extract version from tag - id: version + - name: Determine release context + id: context + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_TAG: ${{ inputs.tag }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_RELEASE_ID: ${{ inputs.release_id }} + INPUT_DRAFT: ${{ inputs.draft }} + INPUT_COMMIT: ${{ inputs.target_commitish }} run: | - TAG="${{ github.event.release.tag_name }}" - # Remove 'v' prefix if present - VERSION="${TAG#v}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "Validating release: $TAG (version: $VERSION)" + python <<'PY' > context.env +import json, os, sys + +event_name = os.environ.get("EVENT_NAME", "") +result = {} + +if event_name == "release": + with open(os.environ["GITHUB_EVENT_PATH"], "r", encoding="utf-8") as handle: + data = json.load(handle) + release = data.get("release") or {} + result["tag"] = release.get("tag_name", "") + tag = result["tag"] + result["version"] = tag[1:] if tag.startswith("v") else tag + result["release_id"] = str(release.get("id", "")) + result["target_commitish"] = release.get("target_commitish", "") + result["draft"] = str(release.get("draft", False)).lower() +else: + result["tag"] = os.environ.get("INPUT_TAG", "") + result["version"] = os.environ.get("INPUT_VERSION", "") + result["release_id"] = os.environ.get("INPUT_RELEASE_ID", "") + result["target_commitish"] = os.environ.get("INPUT_COMMIT", "") + draft_value = os.environ.get("INPUT_DRAFT", "false") + result["draft"] = str(draft_value).lower() + +if not result["tag"] or not result["release_id"]: + sys.stderr.write("::error::Release metadata is missing. Provide tag, version, release_id, and target_commitish.\n") + sys.exit(1) + +should_run = "true" +if event_name == "release" and result["draft"] != "true": + should_run = "false" +result["should_run"] = should_run + +for key, value in result.items(): + print(f"{key}={value}") +PY + cat context.env >> "$GITHUB_OUTPUT" + cat context.env + + - name: Skip validation for published releases + if: steps.context.outputs.should_run != 'true' + run: echo "Release is already published; skipping validation checks." - name: Download all release assets + if: steps.context.outputs.should_run == 'true' id: download run: | - echo "Downloading all assets from release ${{ steps.version.outputs.tag }}..." + echo "Downloading all assets from release ${{ steps.context.outputs.tag }}..." - # Create release directory mkdir -p release cd release - # Get list of all assets for this release (using gh CLI to avoid token exposure) - ASSETS=$(gh api "repos/${{ github.repository }}/releases/${{ github.event.release.id }}/assets" \ - --jq '.[].browser_download_url') + ASSETS=$(gh api "repos/${{ github.repository }}/releases/${{ steps.context.outputs.release_id }}/assets" --jq '.[].browser_download_url') if [ -z "$ASSETS" ]; then echo "::error::No assets found in release" @@ -54,13 +133,11 @@ jobs: echo "Found assets:" echo "$ASSETS" - # Download each asset echo "$ASSETS" | while read -r url; do if [ -n "$url" ]; then filename=$(basename "$url") echo "Downloading $filename..." - # Use gh CLI to download (avoids token exposure in logs) - gh release download "${{ github.event.release.tag_name }}" \ + gh release download "${{ steps.context.outputs.tag }}" \ --pattern "$filename" --dir . --clobber if [ $? -eq 0 ]; then @@ -77,15 +154,15 @@ jobs: ls -lh - name: Install Docker - run: | - # Docker is pre-installed on ubuntu-latest runners - docker --version + if: steps.context.outputs.should_run == 'true' + run: docker --version - name: Pull Docker image (if available) + if: steps.context.outputs.should_run == 'true' id: docker continue-on-error: true run: | - IMAGE="rcourtman/pulse:${{ steps.version.outputs.tag }}" + IMAGE="rcourtman/pulse:${{ steps.context.outputs.tag }}" echo "Attempting to pull Docker image: $IMAGE" if docker pull "$IMAGE" 2>/dev/null; then @@ -99,44 +176,34 @@ jobs: fi - name: Run validation script + if: steps.context.outputs.should_run == 'true' id: validate run: | - set +e # Don't exit on error, we want to capture the output - + set +e echo "Running validation script..." chmod +x scripts/validate-release.sh - - # Create output file for validation results OUTPUT_FILE=$(mktemp) if [ "${{ steps.docker.outputs.image_available }}" = "true" ]; then - # Full validation with Docker image echo "Running full validation (Docker + assets)..." scripts/validate-release.sh \ - "${{ steps.version.outputs.version }}" \ + "${{ steps.context.outputs.version }}" \ "${{ steps.docker.outputs.image }}" \ "release" 2>&1 | tee "$OUTPUT_FILE" else - # Modified validation script that skips Docker checks echo "Running assets-only validation (Docker image not available)..." - - # We'll need to skip Docker validation - create a temporary modified script - # For now, we'll just run the script and let Docker validation fail - # A more robust solution would be to modify the script or create a separate assets-only version scripts/validate-release.sh \ - "${{ steps.version.outputs.version }}" \ - "rcourtman/pulse:${{ steps.version.outputs.tag }}" \ + "${{ steps.context.outputs.version }}" \ + "rcourtman/pulse:${{ steps.context.outputs.tag }}" \ "release" 2>&1 | tee "$OUTPUT_FILE" || true fi VALIDATION_EXIT_CODE=$? - # Save output for later use in comments echo "VALIDATION_OUTPUT<> $GITHUB_OUTPUT cat "$OUTPUT_FILE" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - # Check validation result if [ $VALIDATION_EXIT_CODE -eq 0 ]; then echo "validation_passed=true" >> $GITHUB_OUTPUT echo "✅ Validation PASSED" @@ -148,12 +215,12 @@ jobs: exit $VALIDATION_EXIT_CODE - name: Set commit status - Success - if: steps.validate.outputs.validation_passed == 'true' + if: steps.context.outputs.should_run == 'true' && steps.validate.outputs.validation_passed == 'true' run: | curl -X POST \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.release.target_commitish }}" \ + "https://api.github.com/repos/${{ github.repository }}/statuses/${{ steps.context.outputs.target_commitish }}" \ -d '{ "state": "success", "target_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", @@ -162,19 +229,16 @@ jobs: }' - name: Update release body - Success - if: steps.validate.outputs.validation_passed == 'true' + if: steps.context.outputs.should_run == 'true' && steps.validate.outputs.validation_passed == 'true' run: | echo "✅ Validation passed - updating release description" - # Get current release body CURRENT_BODY=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.context.outputs.release_id }}" \ | jq -r '.body // ""') - # Remove any existing validation status block CURRENT_BODY=$(echo "$CURRENT_BODY" | sed '//,//d') - # Create new validation status block using heredoc read -r -d '' VALIDATION_BLOCK <<'EOF' || true ## ✅ Release Asset Validation: PASSED @@ -194,29 +258,26 @@ jobs: EOF - # Replace placeholders VALIDATION_BLOCK="${VALIDATION_BLOCK//TIMESTAMP_PLACEHOLDER/$(date -u +"%Y-%m-%d %H:%M:%S UTC")}" VALIDATION_BLOCK="${VALIDATION_BLOCK//WORKFLOW_LINK_PLACEHOLDER/[${{ github.workflow }} #${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})}" - # Combine validation block with original body NEW_BODY="$VALIDATION_BLOCK $CURRENT_BODY" - # Update release curl -X PATCH \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.context.outputs.release_id }}" \ -d "$(jq -n --arg body "$NEW_BODY" '{body: $body}')" - name: Set commit status - Failure - if: failure() || steps.validate.outputs.validation_passed == 'false' + if: steps.context.outputs.should_run == 'true' && (failure() || steps.validate.outputs.validation_passed == 'false') run: | curl -X POST \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.release.target_commitish }}" \ + "https://api.github.com/repos/${{ github.repository }}/statuses/${{ steps.context.outputs.target_commitish }}" \ -d '{ "state": "failure", "target_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", @@ -225,13 +286,12 @@ jobs: }' - name: Delete all release assets on failure - if: failure() || steps.validate.outputs.validation_passed == 'false' + if: steps.context.outputs.should_run == 'true' && (failure() || steps.validate.outputs.validation_passed == 'false') run: | echo "❌ Validation failed - deleting all release assets" - # Delete all assets from the release ASSET_IDS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}/assets" \ + "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.context.outputs.release_id }}/assets" \ | jq -r '.[].id') if [ -n "$ASSET_IDS" ]; then @@ -241,12 +301,6 @@ jobs: curl -X DELETE \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id" - - if [ $? -eq 0 ]; then - echo "✓ Deleted asset $asset_id" - else - echo "⚠️ Failed to delete asset $asset_id" - fi fi done echo "✓ Asset deletion process completed" @@ -255,19 +309,16 @@ jobs: fi - name: Update release body - Failure - if: failure() || steps.validate.outputs.validation_passed == 'false' + if: steps.context.outputs.should_run == 'true' && (failure() || steps.validate.outputs.validation_passed == 'false') run: | echo "❌ Validation failed - updating release description" - # Get current release body CURRENT_BODY=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.context.outputs.release_id }}" \ | jq -r '.body // ""') - # Remove any existing validation status block CURRENT_BODY=$(echo "$CURRENT_BODY" | sed '//,//d') - # Extract validation errors if available VALIDATION_OUTPUT="${{ steps.validate.outputs.VALIDATION_OUTPUT }}" if [ -n "$VALIDATION_OUTPUT" ]; then ERRORS=$(echo "$VALIDATION_OUTPUT" | grep -i '\[ERROR\]' | head -20 || echo "See workflow logs for details") @@ -275,7 +326,6 @@ jobs: ERRORS="Validation script failed to run. Check workflow logs." fi - # Create new validation status block using heredoc read -r -d '' VALIDATION_BLOCK <<'EOF' || true ## ❌ Release Asset Validation: FAILED @@ -309,26 +359,23 @@ jobs: EOF - # Replace placeholders VALIDATION_BLOCK="${VALIDATION_BLOCK//TIMESTAMP_PLACEHOLDER/$(date -u +"%Y-%m-%d %H:%M:%S UTC")}" VALIDATION_BLOCK="${VALIDATION_BLOCK//WORKFLOW_LINK_PLACEHOLDER/[${{ github.workflow }} #${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})}" VALIDATION_BLOCK="${VALIDATION_BLOCK//WORKFLOW_URL_PLACEHOLDER/${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}}" VALIDATION_BLOCK="${VALIDATION_BLOCK//ERRORS_PLACEHOLDER/$ERRORS}" - # Combine validation block with original body NEW_BODY="$VALIDATION_BLOCK $CURRENT_BODY" - # Update release curl -X PATCH \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/${{ steps.context.outputs.release_id }}" \ -d "$(jq -n --arg body "$NEW_BODY" '{body: $body}')" - name: Fail the workflow - if: failure() || steps.validate.outputs.validation_passed == 'false' + if: steps.context.outputs.should_run == 'true' && (failure() || steps.validate.outputs.validation_passed == 'false') run: | echo "::error::Release asset validation failed. All assets have been deleted." exit 1 diff --git a/frontend-modern/src/components/Settings/NodeModal.tsx b/frontend-modern/src/components/Settings/NodeModal.tsx index 29a092351..a623f90d6 100644 --- a/frontend-modern/src/components/Settings/NodeModal.tsx +++ b/frontend-modern/src/components/Settings/NodeModal.tsx @@ -1,4 +1,4 @@ -import { Component, Show, createSignal, createEffect } from 'solid-js'; +import { Component, Show, For, createSignal, createEffect } from 'solid-js'; import { Portal } from 'solid-js/web'; import type { NodeConfig } from '@/types/nodes'; import type { SecurityStatus } from '@/types/config'; diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 821e08a63..ee4f5f0a0 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -335,7 +335,7 @@ else printf '%s\n' "$checksum_output" > checksums.txt # Emit per-file .sha256 artifacts for backward compatibility while legacy installers transition off them - while IFS= read -r checksum filename; do + while read -r checksum filename; do printf '%s %s\n' "$checksum" "$filename" > "${filename}.sha256" done <<< "$checksum_output"