Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 4 additions & 93 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,39 +91,9 @@ jobs:
path: ipfs-webui_${{ github.sha }}.car
if-no-files-found: error

# Instructions for this are at https://github.com/ipfs/ipfs-webui/issues/2161#issuecomment-1836835068
- name: Pin to web3.storage with w3cli
id: pin-w3up
# only pin for main commits
if: github.repository == 'ipfs/ipfs-webui' && github.ref == 'refs/heads/main'
continue-on-error: true
run: |
# ensure whoami
npx -y --package=@web3-storage/w3cli@latest -- w3 whoami

# convert base64 env var to file
echo $W3CLI_SPACE_DELEGATION_PROOF_BASE64_STRING | base64 -d > ipfs-webui-ci-space.ucan.proof

# Add space
export W3CLI_SPACE_DID=$(npx -y --package=@web3-storage/w3cli@latest -- w3 space add ipfs-webui-ci-space.ucan.proof)

# use space
npx -y --package=@web3-storage/w3cli@latest -- w3 space use $W3CLI_SPACE_DID

# upload car
npx -y --package=@web3-storage/w3cli@latest -- w3 up --no-wrap -c ipfs-webui_${{ github.sha }}.car
env:
W3_STORE_NAME: ipfs-webui-ci
W3_AGENT_DID: ${{ secrets.W3_AGENT_DID }}
# set w3cli principal from https://github.com/web3-storage/w3cli#w3_principal
W3_PRINCIPAL: ${{ secrets.W3_AGENT_PRINCIPAL }}
W3CLI_SPACE_DELEGATION_PROOF_BASE64_STRING: ${{ secrets.W3CLI_SPACE_DELEGATION_PROOF_BASE64_STRING }}

- name: Pin to ipfs-websites.collab.ipfscluster.io
id: pin-cluster
# only pin for main commits
if: github.repository == 'ipfs/ipfs-webui' && github.ref == 'refs/heads/main'
continue-on-error: true
timeout-minutes: 5 # pin should takes a few seconds, 5 min is a hard ceiling
run: |
echo "Avoid doing initial upload to am6-1 node with experimental pebble datastore"
Expand Down Expand Up @@ -159,14 +129,6 @@ jobs:
CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }}
PIN_NAME: "ipfs-webui@${{ github.sha }}"

- name: Fail job due to pinning failure
# only fail if pinning failed for main commits
if: github.ref == 'refs/heads/main' && steps.pin-w3up.outcome == 'failure' && steps.pin-cluster.outcome == 'failure'
uses: actions/github-script@v8
with:
script: |
core.setFailed('Pinning did not succeed')

# dnslink-dnsimple requires go
- uses: actions/setup-go@v6
if: github.repository == 'ipfs/ipfs-webui' && github.ref == 'refs/heads/main'
Expand Down Expand Up @@ -198,59 +160,6 @@ jobs:
force_orphan: true
full_commit_message: 'ipfs-webui@${{ github.sha }} /ipfs/${{ steps.ipfs.outputs.cid }}'

# production dnslink is updated on release (workflow_dispatch on main)
- run: dnslink-dnsimple --domain ipfs.tech --record _dnslink.webui --link /ipfs/${{ steps.ipfs.outputs.cid }}
if: github.repository == 'ipfs/ipfs-webui' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
env:
DNSIMPLE_TOKEN: ${{ secrets.DNSIMPLE_TOKEN }}

# HTTP hosting for https://webui.ipfs.io (GitHub Pages, deployed via Actions).
# Gated exactly like the production dnslink update in publishPreview, so the Pages
# site and DNSLink always publish the same content in the same run. Bytes come
# from the CAR artifact (the only unconditional per-run copy of build/; the
# build artifact is skipped on cache hits), so served content matches the pinned CID.
deployPages:
name: deploy webui.ipfs.io (GitHub Pages)
needs: publishPreview
if: github.repository == 'ipfs/ipfs-webui' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
concurrency:
group: pages-prod
cancel-in-progress: false
steps:
- name: Download CAR artifact
uses: actions/download-artifact@v7
with:
name: ipfs-webui_${{ github.sha }}.car

- uses: ipfs/download-ipfs-distribution-action@v1
with:
name: kubo

- name: Unpack CAR into build/
run: |
ipfs init
ipfs dag import --pin-roots=false ipfs-webui_${{ github.sha }}.car
ipfs get ${{ needs.publishPreview.outputs.cid }} -o build
test -f build/index.html
# fail loudly if the unpacked tree does not match the CID that was pinned and DNSLinked
[ "$(ipfs add --cid-version 1 -Q -r -n ./build)" = "${{ needs.publishPreview.outputs.cid }}" ]

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: build

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5

test-e2e:
name: 'test:e2e'
uses: ./.github/workflows/test-e2e.yml
Expand Down Expand Up @@ -320,8 +229,10 @@ jobs:
exit 1

# creates a GitHub Release with changelog and CAR file when ci.yml is manually
# dispatched on main; the release commit lands with [skip ci], so no follow-up
# workflow run is triggered by it
# dispatched on main. Publishing the release triggers deploy-release.yml, which
# deploys webui.ipfs.io (GitHub Pages) and updates the production dnslink, so a
# failed or skipped release never changes what users see. The release commit
# itself lands with [skip ci] and triggers nothing.
release:
name: 'Run semantic release'
runs-on: ubuntu-latest
Expand Down
97 changes: 97 additions & 0 deletions .github/workflows/deploy-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: deploy-release

# Publishes a tagged release to https://webui.ipfs.io: deploys the CAR file
# attached to the GitHub Release to GitHub Pages, then points the production
# dnslink at the same CID. Triggered automatically when semantic-release
# (ci.yml) publishes a release, so a failed or skipped release never changes
# what users see. Can also be dispatched manually with any existing release
# tag to redeploy the site or roll back to an older release.
#
# NOTE: the automatic trigger relies on ci.yml running semantic-release with a
# bot PAT (secrets.GH_TOKEN). Events created with the workflow-scoped
# GITHUB_TOKEN do not trigger other workflows, so switching that secret to the
# default token would silently stop these deploys.

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'release tag to (re)deploy, e.g. v4.13.0'
required: true
type: string

# queue deploys in order, never cancel one mid-flight
concurrency:
group: deploy-release
cancel-in-progress: false

env:
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}

jobs:
pages:
name: deploy webui.ipfs.io (GitHub Pages)
if: github.repository == 'ipfs/ipfs-webui'
runs-on: ubuntu-latest
outputs:
cid: ${{ steps.import.outputs.cid }}
permissions:
contents: read # download the release asset
pages: write
id-token: write # OIDC for deploy-pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Download CAR from the release
run: gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern '*.car' --output release.car
env:
GH_TOKEN: ${{ github.token }}

- uses: ipfs/download-ipfs-distribution-action@v1
with:
name: kubo

- name: Unpack CAR into build/
id: import
run: |
ipfs init
root_cid=$(ipfs dag import release.car | awk '/^Pinned root/ { print $3 }')
test -n "$root_cid"
ipfs get "$root_cid" -o build
test -f build/index.html
# fail loudly if the unpacked tree does not match the released DAG
[ "$(ipfs add --cid-version 1 -Q -r -n ./build)" = "$root_cid" ]
echo "cid=$root_cid" >> "$GITHUB_OUTPUT"

- uses: actions/upload-pages-artifact@v5
with:
path: build

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5

dnslink:
name: update production dnslink
needs: pages
if: github.repository == 'ipfs/ipfs-webui'
runs-on: ubuntu-latest
environment: Deploy # holds DNSIMPLE_TOKEN
permissions:
contents: read
steps:
- uses: actions/checkout@v6 # only for .tool-versions

- uses: actions/setup-go@v6
with:
go-version-file: '.tool-versions'

- run: go install github.com/ipfs/dnslink-dnsimple@v0.1.0

- name: Point _dnslink.webui at the released CID
run: dnslink-dnsimple --domain ipfs.tech --record _dnslink.webui --link "/ipfs/${{ needs.pages.outputs.cid }}"
env:
DNSIMPLE_TOKEN: ${{ secrets.DNSIMPLE_TOKEN }}
33 changes: 33 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# AGENTS.md

Guidance for coding agents working in this repository. Humans may find it useful too.

## Build and test

- `npm ci` installs dependencies, `npm run build` builds into `build/` (create-react-app via react-app-rewired-esm)
- `npx jest <path>` runs a single unit test file, `npm run test:unit` the suite
- `npm run eslint` and `npm run typecheck` must pass; CI treats warnings as errors
- Node.js version is pinned in `.tool-versions`
- translations live in `public/locales` and are managed on Transifex; see `docs/LOCALIZATION.md` before touching them

## Commit conventions

- follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/); semantic-release derives version bumps and the changelog from commit titles on `main` (config in `package.json` under `release`)
- `chore:`, `docs:`, and `test:` commits trigger patch releases; use the `no-release` scope for commits that should not be released

## Release and deployment flow

The release process for humans is described in `docs/RELEASING.md`. The machinery:

1. Every merge to `main` (`ci.yml`, job `publishPreview`): the build is added to IPFS and pinned, exported as a CAR file kept as a run artifact, the dev DNSLink (`_dnslink.dev.webui`, zone `ipfs.tech`) is updated, and the build is force-pushed to the `ipfs/dev.webui.ipfs.io` repo, which GitHub Pages serves as https://dev.webui.ipfs.io.
2. A maintainer manually dispatches `ci.yml` on `main`: if release-worthy commits exist, semantic-release publishes a GitHub Release with the CID in the notes and the CAR file attached. That CAR is the canonical source of truth for the release; everything user-facing deploys from it.
3. Publishing the release triggers `deploy-release.yml`: it downloads the CAR from the release, verifies the root CID, deploys the content to https://webui.ipfs.io (GitHub Pages, this repo) and points the production DNSLink (`_dnslink.webui`) at the same CID. A failed or skipped release never changes https://webui.ipfs.io.
4. `deploy-release.yml` can be dispatched manually with a release tag to redeploy or roll back https://webui.ipfs.io.

Forks: pinning and publishing steps are gated on `github.repository == 'ipfs/ipfs-webui'`, so CI in forks skips them instead of failing on missing secrets.

## Hosting notes

- the app is a static, hash-routed build with relative asset paths; it works from GitHub Pages, any IPFS gateway path, and as `ipns://webui.ipfs.io` via IPFS Companion
- both hostnames are CNAMEs to `ipfs.github.io` (DNS-only) in the `ipfs.io` zone; the `_dnslink.*` TXT records are load-bearing for IPFS-native access and must never be removed
- Kubo and IPFS Desktop bundle release CIDs directly (see `docs/RELEASING.md`), independent of the hosted sites
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ This repository follows the IPFS [Code of Conduct](https://github.com/ipfs/commu
> [!NOTE]
> This project is maintained by the [Shipyard](https://ipshipyard.com/) team.
>
> [Releasing a new version](docs/RELEASING.md)
> [Releasing a new version](docs/RELEASING.md): every merge to `main` is deployed to https://dev.webui.ipfs.io, and every tagged release to https://webui.ipfs.io.

## License

Expand Down
10 changes: 8 additions & 2 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@

2. If UI is materially different, update screenshots in `README.md` and on [docs.ipfs.tech](https://docs.ipfs.tech/how-to/command-line-quick-start/).

3. Manually dispatch the [ci.yml](https://github.com/ipfs/ipfs-webui/actions/workflows/ci.yml) workflow on `main` branch. This will create a new release.
3. Manually dispatch the [ci.yml](https://github.com/ipfs/ipfs-webui/actions/workflows/ci.yml) workflow on `main` branch. If there are release-worthy commits, semantic-release creates a new GitHub Release with the changelog, the build CID in the notes, and the build CAR file attached.

4. If the release is good enough for LTS, update the CID at projects that use ipfs-webui by submitting PRs:
4. Publishing the release automatically triggers [deploy-release.yml](https://github.com/ipfs/ipfs-webui/actions/workflows/deploy-release.yml), which downloads the CAR from the release, deploys its content to https://webui.ipfs.io (GitHub Pages) and points the production DNSLink at the same CID. Check that both of its jobs are green. If the dispatched run fails, or creates no release, https://webui.ipfs.io stays untouched.

5. If the release is good enough for LTS, update the CID at projects that use ipfs-webui by submitting PRs:
- **Kubo**: https://github.com/ipfs/kubo/blob/master/core/corehttp/webui.go#L4
- **IPFS Desktop**: https://github.com/ipfs/ipfs-desktop/blob/main/package.json#L20

## Redeploying or rolling back webui.ipfs.io

Manually dispatch [deploy-release.yml](https://github.com/ipfs/ipfs-webui/actions/workflows/deploy-release.yml) with any existing release tag (e.g. `v4.13.0`). It deploys that release's CAR file to GitHub Pages and DNSLink; this is the fastest way to recover from a bad release.
Loading