diff --git a/CHANGELOG.md b/CHANGELOG.md index ac9649a..149e389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- **BREAKING**: Pinata now uses direct CAR upload via the [V3 Files API](https://docs.pinata.cloud/api-reference/endpoint/upload-a-file) instead of the Pinning Service API. This ensures data is uploaded directly without relying on network retrieval. +- **BREAKING**: removed `pinata-pinning-url` input (no longer needed for CAR uploads) +- Pinata can now be used as the sole provider (previously required Storacha, IPFS Cluster, or Kubo) +- added `pinata-retry-attempts` input for configuring upload retries (default: 3) + ## [1.7.0] - 2025-08-25 ### Fixed diff --git a/README.md b/README.md index f1fa528..810612a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Deploy to IPFS Action -This GitHub Action automates the deployment of static sites to IPFS using [CAR files](https://docs.ipfs.tech/concepts/glossary/#car). It pins to either Kubo, IPFS Cluster, or [Storacha](https://storacha.network), as well as supporting additional pinning to [Pinata](https://pinata.cloud). The action will automatically create a preview link and update your PR/commit status with the deployment information. +This GitHub Action automates the deployment of static sites to IPFS using [CAR files](https://docs.ipfs.tech/concepts/glossary/#car). It uploads CAR files to [Storacha](https://storacha.network), [Pinata](https://pinata.cloud), Kubo, or IPFS Cluster. The action will automatically create a preview link and update your PR/commit status with the deployment information. This action is built and maintained by [Interplanetary Shipyard](http://ipshipyard.com/). @@ -28,8 +28,7 @@ The [composite action](https://docs.github.com/en/actions/sharing-automations/cr ## Features - đŸ“Ļ Merkleizes your static site into a CAR file -- 🚀 Uploads CAR file to either Storacha, IPFS Cluster, or Kubo -- 📍 Optional pinning to Pinata +- 🚀 Uploads CAR file to Storacha, Pinata, IPFS Cluster, or Kubo - 💾 Optional CAR file upload to Filebase - 📤 CAR file attached to Github Action run Summary page - 🔗 Automatic preview links @@ -40,9 +39,8 @@ The [composite action](https://docs.github.com/en/actions/sharing-automations/cr This action encapsulates the established best practices for deploying static sites to IPFS in 2025 -- Merkleizes the build into a CAR file in GitHub Actions using `ipfs-car`. This ensures that the CID is generated in the build process and is the same across multiple providers. -- Uploads the CAR file to IPFS via [Storacha](https://storacha.network). -- Optionally pins the CID of the CAR file to Pinata. This is useful for redundancy (multiple providers). The pinning here is done in the background and non-blocking. (When pinning, Pinata will fetch the data from Storacha.) +- Merkleizes the build into a CAR file in GitHub Actions using Kubo. This ensures that the CID is generated in the build process and is the same across multiple providers. +- Uploads the CAR file to IPFS via one or more providers: [Storacha](https://storacha.network), [Pinata](https://pinata.cloud), IPFS Cluster, or Kubo. Uploading locally created CAR ensures data is delivered directly without relying on network retrieval. - Updates the PR/commit status with the deployment information and preview links. ## Storacha configuration @@ -72,26 +70,25 @@ The signing key and proof will be used as [inputs](#inputs) to the action. | `cluster-password` | IPFS Cluster password for basic http auth | | `storacha-key` | Storacha base64 encoded key to use to sign UCAN invocations. Create one using `w3 key create --json` (and use `key` from the output). See: https://github.com/storacha/w3cli#w3_principal | | `storacha-proof` | Storacha Base64 encoded proof UCAN with capabilities for the space. Create one using `w3 delegation create did:key:DID_OF_KEY -c space/blob/add -c space/index/add -c filecoin/offer -c upload/add --base64` | +| `pinata-jwt-token` | Pinata JWT token for authentication | > [!IMPORTANT] -> To use this action, you must configure the inputs for either: **Kubo, IPFS Cluster, or Storacha**. +> To use this action, you must configure the inputs for at least one provider: > +> - Storacha: `storacha-key` and `storacha-proof` +> - Pinata: `pinata-jwt-token` > - Kubo: `kubo-api-url` and `kubo-api-auth` > - IPFS Cluster: `cluster-url`, `cluster-user`, `cluster-password` -> - Storacha: `storacha-key`, `storacha-proof` > -> Pinata can only be used in addition (but not exclusively) to the above providers/nodes. This may change in the future if Pinata adds support for CAR uploads. +> You can configure multiple providers for redundancy. ### Optional Inputs | Input | Description | Default | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `node-version` | Node.js version to use | `'20'` | -| `cluster-ctl-version` | IPFS Cluster CLI version to use | `'v1.1.2'` | -| `kubo-version` | Kubo CLI version to use for pinning API and CAR uploads | `'v0.33.0'` | +| `kubo-version` | Kubo CLI version to use for merkleizing and CAR creation | `'v0.33.0'` | | `ipfs-add-options` | Options to pass to `ipfs add` command that is used to merkleize the build. See [ipfs add docs](https://docs.ipfs.tech/reference/kubo/cli/#ipfs-add) | `'--cid-version 1 --chunker size-1048576'` | -| `pinata-pinning-url` | Pinata Pinning Service URL | `'https://api.pinata.cloud/psa'` | -| `pinata-jwt-token` | Pinata JWT token for authentication | - | +| `pinata-retry-attempts` | Number of retry attempts for Pinata uploads | `'3'` | | `filebase-bucket` | Filebase bucket name | - | | `filebase-access-key` | Filebase access key | - | | `filebase-secret-key` | Filebase secret key | - | @@ -99,10 +96,11 @@ The signing key and proof will be used as [inputs](#inputs) to the action. | `set-pr-comment` | Set PR comments with IPFS deployment information. Use "true" or "false" (as strings) | `'true'` | | `github-status-gw` | Gateway to use for the links in commit status updates (The green checkmark with the CID) | `'inbrowser.link'` | | `upload-car-artifact` | Upload and publish the CAR file on GitHub Action Summary pages | `'true'` | -| `cluster-retry-attempts` | Number of retry attempts for IPFS Cluster uploads | `'5'` | -| `cluster-timeout-minutes` | Timeout in minutes for each IPFS Cluster upload attempt | `'2'` | +| `cluster-ctl-version` | IPFS Cluster CLI version to use | `'v1.1.2'` | +| `cluster-retry-attempts` | Number of retry attempts for IPFS Cluster uploads | `'3'` | +| `cluster-timeout-minutes` | Timeout in minutes for each IPFS Cluster upload attempt | `'5'` | | `cluster-pin-expire-in` | Time duration after which the pin will expire in IPFS Cluster (e.g. 720h for 30 days). If unset, the CID will be pinned with no expiry. | - | -| `pin-name` | Custom name for the pin. If unset, defaults to "{repo-name}-{commit-sha-short}" for both IPFS Cluster and Pinata. | - | +| `pin-name` | Custom name for the pin. If unset, defaults to "{repo-name}-{commit-sha-short}" for IPFS Cluster and Pinata. | - | ## Outputs @@ -264,6 +262,6 @@ See real-world examples: - How can I safely build on PRs from forks? - Use the two-workflow pattern shown above. The build workflow runs on untrusted fork code without secrets access, while the deploy workflow only runs after a successful build and has access to secrets but never executes untrusted code. This pattern uses GitHub's `workflow_run` event to securely pass artifacts between workflows. - What's the difference between uploading a CAR and using the Pinning API? - - Since the CAR is like a tarball of the full build with some additional metadata (merkle proofs), the upload will be as big as the build output. Pinning with the [Pinning API](https://github.com/ipfs/pinning-services-api-spec) in contrast is just a request to instruct the pinning service to retrieve and pin the data. At the time this action is first released, CAR uploads is supported by Kubo, Storacha, and Filebase, but not Pinata. + - Since the CAR is like a tarball of the full build with some additional metadata (merkle proofs), the upload will be as big as the build output. Pinning with the [Pinning API](https://github.com/ipfs/pinning-services-api-spec) in contrast is just a request to instruct the pinning service to retrieve and pin the data from the IPFS network. This action uploads CAR files directly to all providers (Storacha, Pinata, IPFS Cluster, Kubo, Filebase), ensuring reliable data delivery without depending on network retrieval. - How can I update DNSLink? - See https://github.com/ipfs/dnslink-action as a complement to this action. diff --git a/action.yml b/action.yml index c8e2926..d39839a 100644 --- a/action.yml +++ b/action.yml @@ -55,12 +55,13 @@ inputs: storacha-proof: description: 'Storacha Base64 encoded proof UCAN with capabilities for the space `w3 delegation create did:key:DID_OF_KEY -c space/blob/add -c space/index/add -c filecoin/offer -c upload/add --base64`' required: false - pinata-pinning-url: - description: 'Pinata Pinning Service URL' - default: 'https://api.pinata.cloud/psa' pinata-jwt-token: description: 'Pinata JWT token for authentication' required: false + pinata-retry-attempts: + description: 'Number of retry attempts for Pinata uploads' + default: '3' + required: false filebase-bucket: description: 'Filebase bucket name' required: false @@ -101,14 +102,17 @@ runs: - name: Validate action inputs shell: bash run: | - # This checks if neither Storacha, IPFS Cluster, nor Kubo credentials are provided - # It validates that at least one of the three credential sets is complete: + # Validate that at least one provider is configured: # 1. Storacha: both key and proof must be set # 2. IPFS Cluster: url, user and password must all be set # 3. Kubo: api url and auth must both be set + # 4. Pinata: jwt token must be set # If all credential sets are incomplete/empty, it will error - if [[ -z "${{ inputs.storacha-key }}" || -z "${{ inputs.storacha-proof }}" ]] && [[ -z "${{ inputs.cluster-url }}" || -z "${{ inputs.cluster-user }}" || -z "${{ inputs.cluster-password }}" ]] && [[ -z "${{ inputs.kubo-api-url }}" || -z "${{ inputs.kubo-api-auth }}" ]]; then - echo "::error::Either Storacha credentials (`storacha-key` and `storacha-proof`) or IPFS Cluster credentials (`cluster-url`, `cluster-user`, and `cluster-password`) or Kubo credentials (`kubo-api-url` and `kubo-api-auth`) must be configured. Note that Pinata can only be used in addition to the above providers/nodes, but not exclusively." + if [[ -z "${{ inputs.storacha-key }}" || -z "${{ inputs.storacha-proof }}" ]] && \ + [[ -z "${{ inputs.cluster-url }}" || -z "${{ inputs.cluster-user }}" || -z "${{ inputs.cluster-password }}" ]] && \ + [[ -z "${{ inputs.kubo-api-url }}" || -z "${{ inputs.kubo-api-auth }}" ]] && \ + [[ -z "${{ inputs.pinata-jwt-token }}" ]]; then + echo "::error::At least one provider must be configured: Storacha (\`storacha-key\` and \`storacha-proof\`), IPFS Cluster (\`cluster-url\`, \`cluster-user\`, and \`cluster-password\`), Kubo (\`kubo-api-url\` and \`kubo-api-auth\`), or Pinata (\`pinata-jwt-token\`)." exit 1 fi @@ -305,13 +309,55 @@ runs: exit 1 fi - - name: Pin CID to Pinata - if: ${{ inputs.pinata-jwt-token != ''}} + - name: Upload CAR to Pinata + if: ${{ inputs.pinata-jwt-token != '' }} shell: bash + env: + PINATA_JWT_TOKEN: ${{ inputs.pinata-jwt-token }} run: | - ipfs pin remote service add pinata "${{ inputs.pinata-pinning-url }}" ${{ inputs.pinata-jwt-token }} - ipfs pin remote add --service=pinata --background --name="${pin_name}" ${{ steps.merkleize.outputs.cid }} - echo "✅ Pinned CID \`${{ steps.merkleize.outputs.cid }}\` to Pinata" >> $GITHUB_STEP_SUMMARY + echo "â„šī¸ Uploading CAR with CID ${{ steps.merkleize.outputs.cid }} to Pinata" + + attempt=1 + max_attempts=${{ inputs.pinata-retry-attempts }} + + while [ $attempt -le $max_attempts ]; do + echo "Attempt #$attempt" + + response=$(curl -s -w "\n%{http_code}" -X POST \ + "https://uploads.pinata.cloud/v3/files" \ + -H "Authorization: Bearer ${PINATA_JWT_TOKEN}" \ + -F "network=public" \ + -F "file=@build.car" \ + -F "name=${pin_name}" \ + -F "car=true") + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" = "200" ]; then + returned_cid=$(echo "$body" | jq -r '.data.cid') + expected_cid="${{ steps.merkleize.outputs.cid }}" + + if [ "$returned_cid" = "$expected_cid" ]; then + echo "✅ Uploaded CAR with CID \`$expected_cid\` to Pinata" >> $GITHUB_STEP_SUMMARY + exit 0 + else + echo "::warning::CID mismatch from Pinata. Expected $expected_cid, got $returned_cid" + echo "✅ Uploaded CAR to Pinata (returned CID: \`$returned_cid\`)" >> $GITHUB_STEP_SUMMARY + exit 0 + fi + fi + + if [ $attempt -eq $max_attempts ]; then + echo "::error::Failed to upload to Pinata after $max_attempts attempts (HTTP $http_code)" + echo "Response: $body" + exit 1 + fi + + echo "Attempt #$attempt failed (HTTP $http_code), retrying in 5s..." + attempt=$((attempt + 1)) + sleep 5 + done - name: Set GitHub commit status if: ${{ inputs.set-github-status == 'true' }}