Skip to content

Commit

Permalink
feat: macos signing and notarization (#367)
Browse files Browse the repository at this point in the history
See #367
  • Loading branch information
lidel committed Aug 3, 2021
1 parent adc7572 commit 25aade5
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 35 deletions.
127 changes: 111 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,130 @@ on:
pull_request:
branches:
- master
workflow_dispatch:
inputs:
dist_root:
description: 'DIST_ROOT'
required: true
default: '/ipns/dist.ipfs.io'

env:
DIST_ROOT: ${{ github.event.inputs.custom_dist_root || '/ipns/dist.ipfs.io' }} # content root used for calculating diff to build
GO_IPFS_VER: 'v0.9.1' # go-ipfs daemon used for chunking and applying diff
CLUSTER_CTL_VER: 'v0.14.0' # ipfs-cluster-ctl used for pinning

jobs:
build:
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: '14'
- uses: actions/setup-go@v2
- name: Setup IPFS
run: ./scripts/ci/setup-ipfs.sh
env:
CLUSTER_USER: ${{ secrets.CLUSTER_USER }}
CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }}
timeout-minutes: 5
- name: Build any new ./releases
run: ./dockerized make all_dists
- name: Inspect git status and contents of ./releases
run: git status && ls -Rhl ./releases
- name: Temporarily save ./releases artifacts
uses: actions/upload-artifact@v2
with:
go-version: '1.16'
- run: sudo snap install ipfs jq
- run: ipfs init --profile server
- run: ipfs daemon &
- name: Wait for ipfs daemon
run: npx wait-port http://127.0.0.1:8080/api/v0/version
- name: Connect to ipfs cluster
run: ipfs swarm connect /dnsaddr/cluster.ipfs.io
- run: make publish
# todo: add $(cat versions) to cluster (and wait)
# todo: update dist dnslink if changed.

lint:
name: releases-unsigned-diff
path: releases
retention-days: 1

lint:
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm ci --no-audit --progress=false
- run: npm run lint

sign-macos:
runs-on: "macos-latest"
needs: build
steps:
- uses: actions/checkout@v2
- name: Retrieve unsigned artifacts
uses: actions/download-artifact@v2
with:
name: releases-unsigned-diff
path: releases
continue-on-error: true # skip if no releases
- name: List ./releases before
run: ls -Rhl ./releases || echo "No ./releases"
- name: Install gon via HomeBrew for code signing and app notarization
run: |
brew tap mitchellh/gon
brew install ipfs coreutils gawk gnu-sed jq mitchellh/gon/gon
ipfs init --profile test # needed for calculating NEW_CID later
- name: Import Keychain Certs
uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 # v1@2020-02-03
with:
p12-file-base64: ${{ secrets.APPLE_CERTS_P12 }}
p12-password: ${{ secrets.APPLE_CERTS_PASS }}
- name: Verify identity used for signing
run: security find-identity -v
- name: Sign any new releases
run: ./scripts/ci/sign-new-macos-releases.sh
env:
WORK_DIR: ${{ github.workspace }}
AC_USERNAME: ${{ secrets.APPLE_AC_USERNAME }} # implicitly read from env by gon
AC_PASSWORD: ${{ secrets.APPLE_AC_PASSWORD }}
- name: List ./releases after
run: ls -Rhl ./releases || echo "No ./releases"
- name: Temporarily save notarized artifacts
uses: actions/upload-artifact@v2
with:
name: releases-signed-macos-diff
path: releases
retention-days: 1
continue-on-error: true # skip if no releases

persist:
runs-on: "ubuntu-latest"
needs: sign-macos
steps:
- uses: actions/checkout@v2
- name: Retrieve signed artifacts
uses: actions/download-artifact@v2
continue-on-error: true # skip if no releases
with:
name: releases-signed-macos-diff
path: releases
- name: List ./releases
run: ls -Rhl ./releases || echo "No ./releases"
- name: Setup IPFS
run: ./scripts/ci/setup-ipfs.sh
env:
CLUSTER_USER: ${{ secrets.CLUSTER_USER }}
CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }}
timeout-minutes: 5
- run: ./dockerized make publish
- run: git status
- name: Read CID of updated DAG
id: cid-reader
run: echo "::set-output name=CID::$(tail -1 ./versions)"
- name: Pin new website to ipfs-websites.collab.ipfscluster.io
run: ./scripts/ci/pin-to-cluster.sh
env:
PIN_CID: ${{ steps.cid-reader.outputs.CID }}
PIN_NAME: "https://github.com/ipfs/distributions/commits/${{ github.sha }}"
PIN_ADD_EXTRA_ARGS: ""
CLUSTER_USER: ${{ secrets.CLUSTER_USER }}
CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }}
timeout-minutes: 60
- name: Update PR status with preview link
run: ./scripts/ci/github-preview-link.sh
env:
CONTENT_PATH: "/ipfs/${{ steps.cid-reader.outputs.CID }}/"
GIT_REVISION: ${{ github.sha }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
FROM ubuntu:20.04
ARG USER_UID
ARG GO_IPFS_VER
RUN apt-get update -q && apt-get install -y git curl gnupg jq build-essential gawk zip
RUN curl -s https://dist.ipfs.io/go-ipfs/v0.8.0/go-ipfs_v0.8.0_linux-amd64.tar.gz | tar vzx -C /usr/local/bin/ go-ipfs/ipfs --strip-components=1
RUN curl -s "https://dist.ipfs.io/go-ipfs/${GO_IPFS_VER}/go-ipfs_${GO_IPFS_VER}_linux-amd64.tar.gz" | tar vzx -C /usr/local/bin/ go-ipfs/ipfs --strip-components=1

RUN adduser --shell /bin/bash --home /asdf --disabled-password --gecos asdf asdf --uid $USER_UID
ENV PATH="${PATH}:/asdf/.asdf/shims:/asdf/.asdf/bin"
Expand Down
43 changes: 33 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
- [Adding a new (go) distribution](#adding-a-new-go-distribution)
- [Publishing](#publishing)
- [Background](#background)
- [Notes on reproducible builds](#notes-on-reproducible-builds)
- [Contribute](#contribute)
- [Want to hack on IPFS?](#want-to-hack-on-ipfs)
- [License](#license)

## Install

Clone the repo and install the following dependencies via your favorite package manager:
Clone the repo and use Docker via `./dockerized <cmd>` wrapper.

If you don't want to run `./dockerized` build, install
the following dependencies via your favorite package manager:

* `go`
* `npm` (v7.13.0+ with nodejs v16.2.0+)
Expand Down Expand Up @@ -83,13 +87,20 @@ Run:
> ./dist.sh add-version <dist> <version>
```

This will add the version to `dists/<dist>/versions`, set it as the current version in `dists/<dist>/current`, and build it.
This will add the version to `dists/<dist>/versions`, set it as the current version in `dists/<dist>/current`, and build it locally.

Example:
```sh
> ./dist.sh add-version fs-repo-99-to-100 v1.0.1
```

To produce a signed, **official build** for use in DNSLink at `dist.ipfs.io`:

1. Run `./dist.sh add-version` locally.
2. Commit created changes to `dists/<dist>` and open a PR against `ipfs/distributions`.
3. Wait for Github Action to finish PR build. It runs `./dockerized` build, then signs macOS binaries and spits out updated root CID at the end.
4. If everything looks good, write down the CID from the preview link on the PR, and update the DNSlink at `dist.ipfs.io`.

### Adding a new (go) distribution

Run:
Expand All @@ -107,25 +118,27 @@ The optional `sub_package` argument is used to specify a module within a repo.

### Publishing

In the root of the repository, run:
To produce a CID (`<NEW_HASH>`) that includes binaries for all versions defined in `./dists/`, in the root of the repository, run:

```sh
> make publish
```

This will build any new binaries defined by dist and the website to the `releases` dir, add it to ipfs and patch it into the existing dag for the published dist.ipfs.io. Save the hash it spits out (we'll call it `<NEW_HASH>`), that's the new hash for `dists.ipfs.io`. We also append it to a file called `versions` in the repo root (*not* checked into git).

Next, you should probably:
- This will build any new binaries defined by dist and the website to the `releases` dir, add it to ipfs and patch it into the existing dag for the published `/ipns/dist.ipfs.io`.
- Versions that are already present on the website will be reused, speeding up the build.
- Updated CID (`<NEW_HASH>`) will be printed at the end. That's the new hash for `dists.ipfs.io`. We also append it to a file called `versions` in the repo root (*not* checked into git).

1. Load the dists website in your browser to make sure everything looks right: `http://127.0.0.1:8080/ipfs/<NEW_HASH>`.
2. Compare `<NEW_HASH>` with the current `dists.ipfs.io` to make sure nothing is amiss: `ipfs object diff /ipns/dist.ipfs.io /ipfs/<NEW_HASH>`
After the local build is done, make a quick inspection:

If all looks well, **pin the hash using pinbot** (#ipfs-pinbot on Freenode, ask someone if you don't have permission to do so).
2. Load the dists website in your browser to make sure everything looks right: `http://localhost:8080/ipfs/<NEW_HASH>`.
3. Compare `<NEW_HASH>` with the current `dists.ipfs.io` to make sure nothing is amiss: `ipfs object diff /ipns/dist.ipfs.io /ipfs/<NEW_HASH>`

Finally,

1. Commit your changes and make a PR. Specifically, the changes to `dists/<dist>/versions` and `dists/<dist>/current`.
2. Make a PR with an edit on [protocol/infra](https://github.com/protocol/infra/blob/master/dns/config/dist.ipfs.io.yaml) with the hash you got from `make publish` and a link to the PR above.
2. Wait for [Github Action](https://github.com/ipfs/distributions/actions/) on your PR to build **signed** binaries. `<NEW_SIGNED_HASH>` will be different than one from local build.
3. Make a PR with an edit on [protocol/infra](https://github.com/protocol/infra/blob/master/dns/config/dist.ipfs.io.yaml) with `<NEW_SIGNED_HASH>` you got from the Github Action output and a link to the PR above.
- TODO: this step may be automated in the future - see the [discussion](https://github.com/ipfs/distributions/issues/372).

If you have permission, you can just merge the PR, update the DNS, and then immediately, close the issue on ipfs/infrastructure. Ping someone on IRC.

Expand Down Expand Up @@ -194,6 +207,16 @@ So for example, if we had `<dist>` `go-ipfs` and `fs-repo-migrations`, we might

We call this the **distribution index**, the listing of all distributions, their versions, and platform assets.

### Notes on reproducible builds

Running `./dockerized make publish` will produce binaries using the same
runtime as CI. The main difference between local build and official CI one is
signing step on platforms such as `darwin` (macOS).

Signatures are attached at the end of macOS binaries, which means
`*_darwin-*.tar.gz` produced by CI will have additional bytes when compared
with local build.

## Contribute

Issues and PRs welcome! Please [check out the issues](https://github.com/ipfs/distributions/issues).
Expand Down
6 changes: 3 additions & 3 deletions build-go.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export GOPATH
# Always use go modules
export GO111MODULE=on

DIST_PATH=${DIST_PATH:-/ipns/dist.ipfs.io}
DIST_PATH=$(ipfs resolve "$DIST_PATH")
# Content path to use when looking for pre-existing release data
DIST_ROOT=$(ipfs resolve "${DIST_ROOT:-/ipns/dist.ipfs.io}")

# normalize umask
umask 022
Expand Down Expand Up @@ -334,7 +334,7 @@ function startGoBuilds() {
fi

if [ -z "$existing" ]; then
existing="$DIST_PATH"
existing="$DIST_ROOT"
fi

echo "comparing $versions with $existing/$distname/versions"
Expand Down
2 changes: 1 addition & 1 deletion deps-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ if [ "$failed" = true ]; then
fi

echo "npm install"
exec npm install --no-audit --progress=false
exec npm ci --prefer-offline --no-audit --progress=false
7 changes: 5 additions & 2 deletions dockerized
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ set -euxo pipefail
docker pull ubuntu:20.04

# CACHEBUST means this will apply the updates once a day
docker build . -t distributions --build-arg CACHEBUST=`date --iso-8601=date` --build-arg USER_UID=$(id -u "$USER")
docker build . -t distributions \
--build-arg CACHEBUST=`date --iso-8601=date` \
--build-arg USER_UID=$(id -u "$USER") \
--build-arg GO_IPFS_VER=${GO_IPFS_VER:-$(curl -s https://dist.ipfs.io/go-ipfs/versions | tail -n 1)} # match http api client version on CI

# We use host networking as the build process assumes a fairly long-lived ipfs
# node has the CIDs (we give them to the collab cluster to pin)
docker run --rm -it --network host -v `pwd`:/build distributions "$@"
docker run --rm -i --network host -e DIST_ROOT -v `pwd`:/build distributions "$@"
14 changes: 14 additions & 0 deletions scripts/ci/github-preview-link.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -e

PREVIEW_URL="https://dweb.link$CONTENT_PATH"
API_PARAMS=$(jq --monochrome-output --null-input \
--arg state "success" \
--arg target_url "$PREVIEW_URL" \
--arg description "Preview updated website on IPFS" \
--arg context "Preview is ready" \
'{ state: $state, target_url: $target_url, description: $description, context: $context }' )
curl --output /dev/null --silent --show-error \
-X POST -H "Authorization: Bearer $GITHUB_TOKEN" -H 'Content-Type: application/json' \
--data "$API_PARAMS" 'https://api.github.com/repos/ipfs/distributions/statuses/${GIT_REVISION}'
echo "Pinned to IPFS - $PREVIEW_URL"
28 changes: 28 additions & 0 deletions scripts/ci/pin-to-cluster.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -e

echo "::group::pin add"
ipfs-cluster-ctl --enc=json \
--host "/dnsaddr/ipfs-websites.collab.ipfscluster.io" \
--basic-auth "${CLUSTER_USER}:${CLUSTER_PASSWORD}" \
pin add \
--name "${PIN_NAME}" \
--no-status $PIN_ADD_EXTRA_ARGS \
"$PIN_CID"
echo "::endgroup::"

echo "::group::waiting until pinned"
while true; do
ipfs-cluster-ctl --enc=json \
--host "/dnsaddr/ipfs-websites.collab.ipfscluster.io" \
--basic-auth "${CLUSTER_USER}:${CLUSTER_PASSWORD}" \
status "$PIN_CID" | tee cluster-pin-status
if [[ $(jq '.peer_map[].status' cluster-pin-status | grep '"pinned"' | wc -l) -ge 2 ]]; then
echo "Got 2 pin confirmations, finishing the workflow"
break
else
echo "(sleeping for 15 seconds)"
sleep 15
fi
done
echo "::endgroup::"
40 changes: 40 additions & 0 deletions scripts/ci/setup-ipfs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -e

echo "::group::Install go-ipfs and ipfs-cluster-ctl"
curl -s https://dist.ipfs.io/go-ipfs/${GO_IPFS_VER}/go-ipfs_${GO_IPFS_VER}_linux-amd64.tar.gz | sudo tar vzx -C /usr/local/bin/ go-ipfs/ipfs --strip-components=1
curl -s https://dist.ipfs.io/ipfs-cluster-ctl/${CLUSTER_CTL_VER}/ipfs-cluster-ctl_${CLUSTER_CTL_VER}_linux-amd64.tar.gz | sudo tar vzx -C /usr/local/bin/ ipfs-cluster-ctl/ipfs-cluster-ctl --strip-components=1
echo "::endgroup::"

# fix resolv - DNS provided by Github is unreliable for DNSLik/dnsaddr
sudo sed -i -e 's/nameserver 127.0.0.*/nameserver 1.1.1.1/g' /etc/resolv.conf

# QUIC perf: https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
sudo sysctl -w net.core.rmem_max=2500000

# init ipfs
echo "::group::Set up IPFS daemon"
ipfs init --profile flatfs,server,test,lowpower
# make flatfs async for faster ci
new_config=$( jq '.Datastore.Spec.mounts[0].child.sync = false' ~/.ipfs/config) && echo "${new_config}" > ~/.ipfs/config
# restore deterministic port (changed by test profile)
ipfs config Addresses.API "/ip4/127.0.0.1/tcp/5001"
# wait for ipfs daemon
ipfs daemon --enable-gc=false & while (! ipfs id --api "/ip4/127.0.0.1/tcp/5001"); do sleep 1; done
echo "::endgroup::"


echo "::group::Preconnect to cluster peers"
echo '-> preconnect to cluster peers'
ipfs-cluster-ctl --enc=json \
--host "/dnsaddr/ipfs-websites.collab.ipfscluster.io" \
--basic-auth "${CLUSTER_USER}:${CLUSTER_PASSWORD}" \
peers ls > cluster-peers-ls
for maddr in $(jq -r '.[].ipfs.addresses[]?' cluster-peers-ls); do
ipfs swarm connect "$maddr" || continue
done
echo '-> manual connect to cluster.ipfs.io'
ipfs swarm connect /dnsaddr/cluster.ipfs.io
echo '-> list swarm peers'
ipfs swarm peers
echo "::endgroup::"
Loading

0 comments on commit 25aade5

Please sign in to comment.