├── .github └── workflows │ ├── generated-pr.yml │ ├── release.yml │ └── stale.yml ├── CHANGELOG.md ├── README.md ├── action.yml ├── screenshot-commit-status.png └── screenshot-pr-comment.png /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | paths: [CHANGELOG.md] 6 | branches: [main] 7 | pull_request: 8 | paths: [CHANGELOG.md] 9 | branches: [main] 10 | 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | 15 | jobs: 16 | release: 17 | uses: ipdxco/changelog-driven-release/.github/workflows/pr.yml@v1 18 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ## [1.6.0] - 2025-05-16 11 | 12 | ### Added 13 | 14 | - Add optional `github-status-gw` input to allow for customizing the gateway used for the commit status updates. 15 | 16 | ## [1.5.0] - 2025-03-07 17 | 18 | ### Added 19 | 20 | - Add `set-pr-comment` input to control PR comment creation separately from GitHub commit status 21 | 22 | ### Fixed 23 | 24 | - Fix bug where GitHub commit status was still being set when `set-github-status` was set to 'false' 25 | - Update descriptions to clarify that string values 'true' and 'false' are expected for boolean inputs 26 | 27 | ## [1.4.1] - 2025-03-06 28 | 29 | ### Fixed 30 | 31 | - Fix commit status and PR comment when action is triggered by `pull_request_target` event. 32 | - Fix bug in `ipfs-cluster-ctl-version` input not being used correctly. 33 | 34 | ## [1.4.0] - 2025-03-05 35 | 36 | ### Added 37 | 38 | - Add support for time-bound pins in IPFS Cluster via the `cluster-pin-expire-in` input parameter. 39 | - Add support for custom pin names via the `pin-name` input parameter. 40 | 41 | ## [1.3.0] - 2025-03-05 42 | 43 | ### Added 44 | 45 | - Add `ipfs-add-options` input to allow for customizing the `ipfs add` command used to merkleize the build into a CAR file. 46 | 47 | ### Changed 48 | 49 | - Remove dependency on `ipfs-car` npm package, and use kubo instead to create the CAR file, since we need Kubo for pinning anyways. 50 | 51 | ## [1.2.1] - 2025-03-03 52 | 53 | ### Fixed 54 | 55 | - Fix bash bug where the debug logging was not being set correctly. 56 | 57 | ## [1.2.0] - 2025-03-03 58 | 59 | ### Added 60 | 61 | - Add `upload-car-artifact` input which will upload the CAR file as an artifact visible on GitHub Action Summary pages. 62 | 63 | ## [1.1.2] - 2025-02-26 64 | 65 | ### Fixed 66 | 67 | - Improve error handling and logging when the action is not configured correctly, like when the folder to deploy is not found or empty. 68 | 69 | ## [1.1.1] - 2025-02-26 70 | 71 | ### Fixed 72 | 73 | - Improve formatting of the action summary output. 74 | 75 | ## [1.1.0] - 2025-02-26 76 | 77 | ### Fixed 78 | 79 | - Improve formatting of the action summary output. 80 | - Add `dweb.link` and `w3s.link` (if `storacha-key` is provided) to the list of preview links. 81 | 82 | ## [1.0.0] - 2025-02-19 83 | 84 | ### Added 85 | 86 | - Add timeout and retry logic to IPFS Cluster uploads. 87 | - Uploads to IPFS Cluster have a default timeout of 5 minutes. 88 | - If the upload fails, the action will retry by default 3 times with a 5 second delay between attempts. 89 | - The number of retry attempts and timeout can be customized using the `cluster-retry-attempts` and `cluster-timeout-minutes` inputs. 90 | 91 | ### Fixed 92 | 93 | - Remove duplicate preview link from PR comment. 94 | 95 | ## [0.3.1] - 2025-02-10 96 | 97 | ### Fixed 98 | 99 | - Log info to stdout instead of GitHub workflow summary 100 | 101 | ## [0.3.0] - 2025-02-10 102 | 103 | ### Added 104 | 105 | - Add support for CAR uploads to Kubo via the Kubo RPC API. 106 | 107 | ### Removed 108 | 109 | - Removed `cluster-upload-timeout` input as GitHub Actions does not support setting [timeout-minutes](https://github.com/actions/runner/blob/main/docs/adrs/0549-composite-run-steps.md#composite-run-steps-features) for steps in composite actions. 110 | 111 | ## [0.2.2] - 2025-02-10 112 | 113 | ### Fixed 114 | 115 | - Default for `cluster-upload-timeout` input is a number instead of a string. 116 | 117 | ## [0.2.1] - 2025-02-04 118 | 119 | ### Fixed 120 | 121 | - Make sure that storacha inputs are not required by action to allow for IPFS Cluster only deployments (inputs will be validated at the beginning of the action ensuring that either Storacha or IPFS Cluster inputs are provided). 122 | 123 | ## [0.2.0] - 2025-02-04 124 | 125 | ### Added 126 | 127 | - Add support for IPFS Cluster CAR uploads. 128 | - Add `cluster-upload-timeout` input to set the timeout for IPFS Cluster CAR uploads. 129 | 130 | ### Changed 131 | 132 | - Storacha is now optional. You can now choose to upload the build CAR to IPFS Cluster instead. 133 | 134 | ### Fixed 135 | 136 | - Fix action step summary output from Merkleizing into CAR step. 137 | 138 | ## [0.1.0] - 2025-01-31 139 | 140 | ### Added 141 | 142 | - Initial release of of the ipfs-deploy-action 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deploy to IPFS Action 2 | 3 | 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. 4 | 5 | This action is built and maintained by [Interplanetary Shipyard](http://ipshipyard.com/). 6 | 7 | 8 | The [composite action](https://docs.github.com/en/actions/sharing-automations/creating-actions/about-custom-actions#composite-actions) makes no assumptions about your build process. You should just run your build and then call this action (as a step in an existing job) with the `path-to-deploy` input set to the path of your build output directory. 9 | 10 | ![Setting commit status](./screenshot-commit-status.png) 11 | 12 | ![PR comment with CID and preview links](./screenshot-pr-comment.png) 13 | 14 | ## Features 15 | 16 | - 📦 Merkleizes your static site into a CAR file 17 | - 🚀 Uploads CAR file to either Storacha, IPFS Cluster, or Kubo 18 | - 📍 Optional pinning to Pinata 19 | - 💾 Optional CAR file upload to Filebase 20 | - 📤 CAR file attached to Github Action run Summary page 21 | - 🔗 Automatic preview links 22 | - 💬 Optional PR comments with CID and preview links 23 | - ✅ Optional commit status updates with build CID 24 | 25 | ## How does this compare to the other IPFS actions? 26 | 27 | This action encapsulates the established best practices for deploying static sites to IPFS in 2025 28 | 29 | - 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. 30 | - Uploads the CAR file to IPFS via [Storacha](https://storacha.network). 31 | - 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.) 32 | - Updates the PR/commit status with the deployment information and preview links. 33 | 34 | ## Storacha configuration 35 | 36 | To set up the Storacha, you will need to install [w3cli](https://github.com/storacha/w3cli) and login with your Storacha account. 37 | 38 | Once logged in: 39 | 40 | - [Create a new space](https://docs.storacha.network/how-to/ci/#create-a-space) (like an S3 bucket) to which you will upload the merkleized CAR files. 41 | - [Create a signing key](https://docs.storacha.network/how-to/ci/#create-a-signing-key) that will be used in CI to sign requests to Storacha. 42 | - [Create a UCAN proof](https://docs.storacha.network/how-to/ci/#create-a-proof) that will be used in CI to sign requests to Storacha. 43 | 44 | The signing key and proof will be used as [inputs](#inputs) to the action. 45 | 46 | ## Inputs 47 | 48 | ### Required Inputs 49 | 50 | | Input | Description | 51 | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 52 | | `path-to-deploy` | Path to the directory containing the frontend build to merkleize into a CAR file and deploy to IPFS | 53 | | `github-token` | GitHub token for updating commit status and PR comments | 54 | | `kubo-api-url` | Kubo RPC API URL to pass to `ipfs --api`, e.g. `/dns/YOUR_DOMAIN/tcp/443/https` | 55 | | `kubo-api-auth` | Kubo RPC API auth secret to pass to `ipfs --api-auth`, e.g. `basic:hello:world` (defined as `AuthSecret` in `API.Authorizations` config) | 56 | | `cluster-url` | IPFS Cluster URL to pass to `ipfs-cluster-ctl --host` | 57 | | `cluster-user` | IPFS Cluster username for basic http auth | 58 | | `cluster-password` | IPFS Cluster password for basic http auth | 59 | | `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 | 60 | | `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` | 61 | 62 | > [!IMPORTANT] 63 | > To use this action, you must configure the inputs for either: **Kubo, IPFS Cluster, or Storacha**. 64 | > 65 | > - Kubo: `kubo-api-url` and `kubo-api-auth` 66 | > - IPFS Cluster: `cluster-url`, `cluster-user`, `cluster-password` 67 | > - Storacha: `storacha-key`, `storacha-proof` 68 | > 69 | > 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. 70 | 71 | ### Optional Inputs 72 | 73 | | Input | Description | Default | 74 | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | 75 | | `node-version` | Node.js version to use | `'20'` | 76 | | `cluster-ctl-version` | IPFS Cluster CLI version to use | `'v1.1.2'` | 77 | | `kubo-version` | Kubo CLI version to use for pinning API and CAR uploads | `'v0.33.0'` | 78 | | `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'` | 79 | | `pinata-pinning-url` | Pinata Pinning Service URL | `'https://api.pinata.cloud/psa'` | 80 | | `pinata-jwt-token` | Pinata JWT token for authentication | - | 81 | | `filebase-bucket` | Filebase bucket name | - | 82 | | `filebase-access-key` | Filebase access key | - | 83 | | `filebase-secret-key` | Filebase secret key | - | 84 | | `set-github-status` | Set GitHub commit status with build CID. Use "true" or "false" (as strings) | `'true'` | 85 | | `set-pr-comment` | Set PR comments with IPFS deployment information. Use "true" or "false" (as strings) | `'true'` | 86 | | `github-status-gw` | Gateway to use for the links in commit status updates (The green checkmark with the CID) | `'inbrowser.link'` | 87 | | `upload-car-artifact` | Upload and publish the CAR file on GitHub Action Summary pages | `'true'` | 88 | | `cluster-retry-attempts` | Number of retry attempts for IPFS Cluster uploads | `'5'` | 89 | | `cluster-timeout-minutes` | Timeout in minutes for each IPFS Cluster upload attempt | `'2'` | 90 | | `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. | - | 91 | | `pin-name` | Custom name for the pin. If unset, defaults to "{repo-name}-{commit-sha-short}" for both IPFS Cluster and Pinata. | - | 92 | 93 | ## Outputs 94 | 95 | | Output | Description | 96 | | ------ | ------------------------------------ | 97 | | `cid` | The IPFS CID of the uploaded content | 98 | 99 | ## Usage 100 | 101 | See the [IPNS Inspector](https://github.com/ipfs/ipns-inspector/blob/main/.github/workflows/build.yml) for a real-world example of this action in use. 102 | 103 | Here's a basic example of how to use this action in your workflow: 104 | 105 | ```yaml 106 | name: Build and Deploy to IPFS 107 | 108 | permissions: 109 | contents: read 110 | pull-requests: write 111 | statuses: write 112 | on: 113 | push: 114 | branches: 115 | - main 116 | pull_request: 117 | 118 | jobs: 119 | build-and-deploy: 120 | runs-on: ubuntu-latest 121 | outputs: # This exposes the CID output of the action to the rest of the workflow 122 | cid: ${{ steps.deploy.outputs.cid }} 123 | steps: 124 | - name: Checkout code 125 | uses: actions/checkout@v4 126 | 127 | - name: Setup Node.js 128 | uses: actions/setup-node@v4 129 | with: 130 | node-version: '20' 131 | cache: 'npm' 132 | 133 | - name: Install dependencies 134 | run: npm ci 135 | 136 | - name: Build project 137 | run: npm run build 138 | 139 | - uses: ipfs/ipfs-deploy-action@v1 140 | name: Deploy to IPFS 141 | id: deploy 142 | with: 143 | path-to-deploy: out 144 | storacha-key: ${{ secrets.STORACHA_KEY }} 145 | storacha-proof: ${{ secrets.STORACHA_PROOF }} 146 | github-token: ${{ github.token }} 147 | ``` 148 | 149 | ## FAQ 150 | 151 | - What's the difference between uploading a CAR and using the Pinning API? 152 | - 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. 153 | - How can I update DNSLink? 154 | - See https://github.com/ipfs/dnslink-action as a complement to this action. 155 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Deploy to IPFS' 2 | description: 'Merkleize and deploy static sites to IPFS with Storacha, with optional Pinata and Filebase pinning' 3 | branding: 4 | icon: 'box' 5 | color: 'blue' 6 | 7 | inputs: 8 | kubo-version: 9 | description: 'Kubo version used to merkleize, create CAR file, and pin https://dist.ipfs.tech/kubo/versions' 10 | default: 'v0.33.0' 11 | required: false 12 | path-to-deploy: 13 | description: 'Path to the directory containing the frontend build to merkleize into a CAR file and deploy to IPFS' 14 | required: true 15 | ipfs-add-options: 16 | description: 'Options to pass to `ipfs add` command of Kubo See https://docs.ipfs.tech/reference/kubo/cli/#ipfs-add' 17 | default: '--cid-version 1 --chunker size-1048576' 18 | required: false 19 | kubo-api-url: 20 | description: 'Kubo RPC API URL to pass to `ipfs --api`, e.g. `/dns/YOUR_DOMAIN/tcp/443/https`' 21 | required: false 22 | kubo-api-auth: 23 | description: 'Kubo RPC API auth secret to pass to `ipfs --api-auth`, e.g. `basic:hello:world` (defined as `AuthSecret` in `API.Authorizations` Kubo config)' 24 | required: false 25 | cluster-url: 26 | description: 'IPFS Cluster URL to pass to ipfs-cluster-ctl --host, e.g. /dnsaddr/...' 27 | required: false 28 | cluster-user: 29 | description: 'IPFS Cluster username used for basic http auth' 30 | required: false 31 | cluster-password: 32 | description: 'IPFS Cluster password used for basic http auth' 33 | required: false 34 | cluster-retry-attempts: 35 | description: 'Number of retry attempts for IPFS Cluster uploads' 36 | default: '3' 37 | required: false 38 | cluster-timeout-minutes: 39 | description: 'Timeout in minutes for each IPFS Cluster upload attempt' 40 | default: '5' 41 | required: false 42 | ipfs-cluster-ctl-version: 43 | description: 'IPFS Cluster CLI version to use' 44 | default: 'v1.1.2' 45 | required: false 46 | cluster-pin-expire-in: 47 | description: 'Time duration after which the pin will expire in IPFS Cluster (e.g. 720h for 30 days). Only supported by IPFS Cluster.' 48 | required: false 49 | pin-name: 50 | description: 'Custom name for the pin. If unset, defaults to "{repo-name}-{commit-sha-short}"' 51 | required: false 52 | storacha-key: 53 | description: 'Storacha base64 encoded key to use to sign UCAN invocations. Create one using `w3 key create --json`. See: https://github.com/storacha/w3cli#w3_principal' 54 | required: false 55 | storacha-proof: 56 | 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`' 57 | required: false 58 | pinata-pinning-url: 59 | description: 'Pinata Pinning Service URL' 60 | default: 'https://api.pinata.cloud/psa' 61 | pinata-jwt-token: 62 | description: 'Pinata JWT token for authentication' 63 | required: false 64 | filebase-bucket: 65 | description: 'Filebase bucket name' 66 | required: false 67 | filebase-access-key: 68 | description: 'Filebase access key' 69 | required: false 70 | filebase-secret-key: 71 | description: 'Filebase secret key' 72 | required: false 73 | github-token: 74 | description: 'GitHub token for updating commit status and PR comments' 75 | required: true 76 | set-github-status: 77 | description: 'Set GitHub commit status with build CID. Use "true" or "false" (as strings)' 78 | default: 'true' 79 | required: false 80 | github-status-gw: 81 | description: 'Gateway URL to use for the commit status target_url. Defaults to `inbrowser.link`' 82 | default: 'inbrowser.link' 83 | required: false 84 | set-pr-comment: 85 | description: 'Set PR comments with IPFS deployment information. Use "true" or "false" (as strings)' 86 | default: 'true' 87 | required: false 88 | upload-car-artifact: 89 | description: 'Upload the CAR file as a GitHub artifact' 90 | default: 'true' 91 | required: false 92 | 93 | outputs: 94 | cid: 95 | description: 'The IPFS CID of the uploaded content' 96 | value: ${{ steps.merkleize.outputs.cid }} 97 | 98 | runs: 99 | using: 'composite' 100 | steps: 101 | - name: Validate action inputs 102 | shell: bash 103 | run: | 104 | # This checks if neither Storacha, IPFS Cluster, nor Kubo credentials are provided 105 | # It validates that at least one of the three credential sets is complete: 106 | # 1. Storacha: both key and proof must be set 107 | # 2. IPFS Cluster: url, user and password must all be set 108 | # 3. Kubo: api url and auth must both be set 109 | # If all credential sets are incomplete/empty, it will error 110 | 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 111 | 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." 112 | exit 1 113 | fi 114 | 115 | - name: Setup Kubo CLI 116 | uses: ipfs/download-ipfs-distribution-action@v1 117 | with: 118 | name: kubo 119 | version: ${{ inputs.kubo-version }} 120 | 121 | - name: ipfs init 122 | shell: bash 123 | # ipfs init is required to use many of the ipfs commands 124 | run: | 125 | ipfs init 126 | 127 | - name: Merkleize into CAR file 128 | id: merkleize 129 | shell: bash 130 | run: | 131 | echo "ℹ️ Merkleizing ${{ inputs.path-to-deploy }} into CAR file" 132 | 133 | # Verify the directory exists 134 | if [ ! -d "${{ inputs.path-to-deploy }}" ]; then 135 | echo "::error::Directory '${{ inputs.path-to-deploy }}' does not exist or is not accessible" 136 | exit 1 137 | fi 138 | 139 | # Verify the directory is not empty 140 | if [ -z "$(ls -A ${{ inputs.path-to-deploy }})" ]; then 141 | echo "::error::Directory '${{ inputs.path-to-deploy }}' is empty" 142 | exit 1 143 | fi 144 | 145 | # Merkleize the directory into a CAR file 146 | CID=$(ipfs add ${{ inputs.ipfs-add-options }} -Q -r ${{ inputs.path-to-deploy }}) 147 | ipfs dag export $CID > build.car 148 | 149 | # Verify that we got a valid CID 150 | if [ -z "$CID" ]; then 151 | echo "::error::Failed to extract CID from ipfs add output" 152 | exit 1 153 | fi 154 | 155 | echo "cid=$CID" >> "$GITHUB_OUTPUT" 156 | echo $CID 157 | echo "✅ Merkleized path: \`${{ inputs.path-to-deploy }}\` into CAR file with root CID \`$CID\`" >> $GITHUB_STEP_SUMMARY 158 | 159 | - name: CAR file artifact name 160 | if: ${{ inputs.upload-car-artifact != 'false' }} 161 | shell: bash 162 | run: | 163 | REPO_NAME=$(echo "${{ github.repository }}" | tr '/' '-') 164 | COMMIT_SHA="${{ github.event.pull_request.head.sha || github.sha }}" 165 | COMMIT_SHA_SHORT="${COMMIT_SHA:0:7}" 166 | echo "artifact_name=$REPO_NAME-$COMMIT_SHA_SHORT-${{ steps.merkleize.outputs.cid }}.car" >> "$GITHUB_ENV" 167 | 168 | - name: CAR file artifact upload 169 | if: ${{ inputs.upload-car-artifact != 'false' }} 170 | uses: actions/upload-artifact@v4 171 | with: 172 | name: ${{ env.artifact_name }} 173 | path: build.car 174 | retention-days: 7 175 | if-no-files-found: error 176 | 177 | - name: Configure and upload CAR to Storacha 178 | if: ${{ inputs.storacha-key != '' && inputs.storacha-proof != ''}} 179 | shell: bash 180 | env: 181 | W3_PRINCIPAL: ${{ inputs.storacha-key }} 182 | run: | 183 | npm install -g @web3-storage/w3cli 184 | echo "ℹ️ Uploading CAR with CID ${{ steps.merkleize.outputs.cid }} to Storacha" 185 | w3 space add ${{ inputs.storacha-proof }} 186 | if ! w3 up --car build.car; then 187 | echo "::error::Failed to upload to Storacha" 188 | exit 1 189 | else 190 | echo "✅ Uploaded CAR with CID \`${{ steps.merkleize.outputs.cid }}\` to Storacha" >> $GITHUB_STEP_SUMMARY 191 | 192 | echo "### 🚀 Build Preview on IPFS ready" >> $GITHUB_STEP_SUMMARY 193 | echo "- 🔎 Commit: ${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_STEP_SUMMARY 194 | echo "- 🔏 CID: \`${{ steps.merkleize.outputs.cid }}\`" >> $GITHUB_STEP_SUMMARY 195 | echo "- 📦 Preview:" >> $GITHUB_STEP_SUMMARY 196 | echo "- [dweb.link](https://dweb.link/ipfs/${{ steps.merkleize.outputs.cid }})" >> $GITHUB_STEP_SUMMARY 197 | if [[ -n "${{ inputs.storacha-key }}" && -n "${{ inputs.storacha-proof }}" ]]; then 198 | echo "- [w3s.link](https://w3s.link/ipfs/${{ steps.merkleize.outputs.cid }})" >> $GITHUB_STEP_SUMMARY 199 | fi 200 | echo "- [inbrowser.link](https://inbrowser.link/ipfs/${{ steps.merkleize.outputs.cid }})" >> $GITHUB_STEP_SUMMARY 201 | fi 202 | 203 | - name: Upload CAR to Kubo 204 | if: ${{ inputs.kubo-api-url != '' && inputs.kubo-api-auth != ''}} 205 | shell: bash 206 | env: 207 | KUBO_API_URL: ${{ inputs.kubo-api-url }} 208 | KUBO_API_AUTH: ${{ inputs.kubo-api-auth }} 209 | run: | 210 | echo "ℹ️ Uploading CAR with CID ${{ steps.merkleize.outputs.cid }} to Kubo" 211 | if [ -n "${ACTIONS_RUNNER_DEBUG}" ]; then 212 | export GOLOG_LOG_LEVEL='debug' 213 | fi 214 | ipfs dag import build.car --api ${KUBO_API_URL} --api-auth ${KUBO_API_AUTH} 215 | 216 | echo "✅ Uploaded CAR with CID ${{ steps.merkleize.outputs.cid }} to Kubo" >> $GITHUB_STEP_SUMMARY 217 | 218 | - name: Setup IPFS Cluster CLI 219 | if: ${{ inputs.cluster-url != ''}} 220 | uses: ipfs/download-ipfs-distribution-action@v1 221 | with: 222 | name: ipfs-cluster-ctl 223 | version: ${{ inputs.ipfs-cluster-ctl-version }} 224 | 225 | - name: Set pin name 226 | id: set-pin-name 227 | shell: bash 228 | run: | 229 | REPO_NAME=$(echo "${{ github.repository }}" | tr '/' '-') 230 | COMMIT_SHA="${{ github.event.pull_request.head.sha || github.sha }}" 231 | COMMIT_SHA_SHORT="${COMMIT_SHA:0:7}" 232 | 233 | # Set the pin name - either use the input or default to repo-commit format 234 | if [ -n "${{ inputs.pin-name }}" ]; then 235 | PIN_NAME="${{ inputs.pin-name }}" 236 | else 237 | PIN_NAME="${REPO_NAME}-${COMMIT_SHA_SHORT}" 238 | fi 239 | 240 | echo "pin_name=${PIN_NAME}" >> "$GITHUB_ENV" 241 | echo "Using pin name: ${PIN_NAME}" 242 | 243 | - name: Upload CAR to IPFS Cluster 244 | if: ${{ inputs.cluster-url != '' && inputs.cluster-user != '' && inputs.cluster-password != '' }} 245 | shell: bash 246 | env: 247 | IPFS_CLUSTER_URL: ${{ inputs.cluster-url }} 248 | IPFS_CLUSTER_USER: ${{ inputs.cluster-user }} 249 | IPFS_CLUSTER_PASSWORD: ${{ inputs.cluster-password }} 250 | run: | 251 | echo "ℹ️ Uploading CAR with CID ${{ steps.merkleize.outputs.cid }} to IPFS Cluster" 252 | 253 | # run in a loop and retry 254 | attempt=1 255 | while true; do 256 | echo "Attempt #$attempt" 257 | 258 | # Make sure we don't loop forever 259 | if [ $attempt -eq ${{ inputs.cluster-retry-attempts }} ]; then 260 | echo "❌ Failed to upload CAR with CID ${{ steps.merkleize.outputs.cid }} to IPFS Cluster" >> $GITHUB_STEP_SUMMARY 261 | exit 1 262 | fi 263 | 264 | if [[ -n "${ACTIONS_RUNNER_DEBUG}" || $attempt -ge 2 ]]; then 265 | export GOLOG_LOG_LEVEL='debug' 266 | fi 267 | 268 | # the --local flag will add the CAR to the local IPFS daemon of the peer receiving the request, but not wait for it to be fully replicated. 269 | timeout ${{ inputs.cluster-timeout-minutes }}m ipfs-cluster-ctl \ 270 | --enc=json \ 271 | --host ${IPFS_CLUSTER_URL} \ 272 | --basic-auth ${IPFS_CLUSTER_USER}:${IPFS_CLUSTER_PASSWORD} \ 273 | add --format=car \ 274 | --local \ 275 | --name "${pin_name}" \ 276 | ${{ inputs.cluster-pin-expire-in != '' && format('--expire-in {0}', inputs.cluster-pin-expire-in) || '' }} \ 277 | build.car && { 278 | echo "✅ Uploaded CAR with CID ${{ steps.merkleize.outputs.cid }} to IPFS Cluster" >> $GITHUB_STEP_SUMMARY 279 | if [[ -n "${{ inputs.cluster-pin-expire-in }}" ]]; then 280 | echo "⏰ IPFS Cluster Pin is set to expire in ${{ inputs.cluster-pin-expire-in }}" >> $GITHUB_STEP_SUMMARY 281 | fi 282 | exit 0 283 | } 284 | 285 | echo "Attempt #$attempt failed, retrying in 5 seconds..." 286 | attempt=$((attempt + 1)) 287 | sleep 5 288 | done 289 | 290 | 291 | - name: Upload CAR to Filebase 292 | if: ${{ inputs.filebase-access-key != '' }} 293 | shell: bash 294 | env: 295 | AWS_ACCESS_KEY_ID: ${{ inputs.filebase-access-key }} 296 | AWS_SECRET_ACCESS_KEY: ${{ inputs.filebase-secret-key }} 297 | FILEBASE_BUCKET: ${{ inputs.filebase-bucket }} 298 | run: | 299 | echo "ℹ️ Uploading CAR with CID ${{ steps.merkleize.outputs.cid }} to Filebase" 300 | aws --endpoint https://s3.filebase.com s3 cp build.car s3://${FILEBASE_BUCKET} --metadata 'import=car' 301 | if [ $? -eq 0 ]; then 302 | echo "✅ Uploaded CAR with CID \`${{ steps.merkleize.outputs.cid }}\` to Filebase" >> $GITHUB_STEP_SUMMARY 303 | else 304 | echo "::error::Failed to upload to Filebase" 305 | exit 1 306 | fi 307 | 308 | - name: Pin CID to Pinata 309 | if: ${{ inputs.pinata-jwt-token != ''}} 310 | shell: bash 311 | run: | 312 | ipfs pin remote service add pinata "${{ inputs.pinata-pinning-url }}" ${{ inputs.pinata-jwt-token }} 313 | ipfs pin remote add --service=pinata --background --name="${pin_name}" ${{ steps.merkleize.outputs.cid }} 314 | echo "✅ Pinned CID \`${{ steps.merkleize.outputs.cid }}\` to Pinata" >> $GITHUB_STEP_SUMMARY 315 | 316 | - name: Set GitHub commit status 317 | if: ${{ inputs.set-github-status == 'true' }} 318 | uses: actions/github-script@v7 319 | with: 320 | github-token: ${{ inputs.github-token }} 321 | script: | 322 | const cid = '${{ steps.merkleize.outputs.cid }}'; 323 | 324 | // For PR events, we need to use the head SHA 325 | const sha = (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') 326 | ? context.payload.pull_request.head.sha 327 | : context.sha; 328 | 329 | await github.rest.repos.createCommitStatus({ 330 | owner: context.repo.owner, 331 | repo: context.repo.repo, 332 | sha: sha, 333 | state: 'success', 334 | target_url: `https://${{ inputs.github-status-gw }}/ipfs/${cid}`, 335 | description: `CID: ${cid}`, 336 | context: 'IPFS' 337 | }); 338 | 339 | - name: Find Comment to update 340 | if: ${{ inputs.set-pr-comment == 'true' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') }} 341 | uses: peter-evans/find-comment@v3 342 | id: fc 343 | with: 344 | issue-number: ${{ github.event.pull_request.number }} 345 | comment-author: 'github-actions[bot]' 346 | body-includes: '🚀 Build' 347 | token: ${{ inputs.github-token }} 348 | 349 | - name: Create or update comment 350 | if: ${{ inputs.set-pr-comment == 'true' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') }} 351 | uses: peter-evans/create-or-update-comment@v4 352 | with: 353 | token: ${{ inputs.github-token }} 354 | comment-id: ${{ steps.fc.outputs.comment-id }} 355 | issue-number: ${{ github.event.pull_request.number }} 356 | body: | 357 | ### 🚀 Build Preview on IPFS ready 358 | - 🔎 Commit: ${{ github.event.pull_request.head.sha || github.sha }} 359 | - 🔏 CID `${{ steps.merkleize.outputs.cid }}` 360 | - 📦 Preview: 361 | - [dweb.link](https://dweb.link/ipfs/${{ steps.merkleize.outputs.cid }}) 362 | ${{ inputs.storacha-key && format('- [w3s.link](https://w3s.link/ipfs/{0})', steps.merkleize.outputs.cid) || '' }} 363 | - [inbrowser.link](https://inbrowser.link/ipfs/${{ steps.merkleize.outputs.cid }}) 364 | edit-mode: replace 365 | -------------------------------------------------------------------------------- /screenshot-commit-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipshipyard/ipfs-deploy-action/b8094b739bb7595e3f1d37222a09fc9c7b95ca3f/screenshot-commit-status.png -------------------------------------------------------------------------------- /screenshot-pr-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipshipyard/ipfs-deploy-action/b8094b739bb7595e3f1d37222a09fc9c7b95ca3f/screenshot-pr-comment.png --------------------------------------------------------------------------------