8000 タグを打つだけでリリースできるようにする by dominion525 · Pull Request #2 · dominion525/atme · GitHub
[go: up one dir, main page]

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
8000
Diff view
152 changes: 152 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
name: Release

on:
push:
tags: ["v*"]

permissions:
contents: write

jobs:
release:
# macos-15 は arm64 のイメージ。atme は Apple silicon の macOS だけを相手にする
runs-on: macos-15
timeout-minutes: 45
env:
# CI と同じ版にそろえる
RUSTUP_TOOLCHAIN: "1.93.0"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# 版は Cargo.toml にある。タグと違うと、atme が名乗る版と違う版を配ってしまう
- name: Check the tag against the version
id: version
run: |
tag="${GITHUB_REF_NAME#v}"
version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)"
if [ "${tag}" != "${version}" ]; then
echo "タグ ${GITHUB_REF_NAME} が Cargo.toml の版 ${version} と違います" >&2
exit 1
fi
echo "version=${version}" >> "${GITHUB_OUTPUT}"

- name: Install the build tools
run: |
rustup toolchain install "${RUSTUP_TOOLCHAIN}" --profile minimal
brew install librsvg
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1

# script/bundle は ATME_SIGN_IDENTITY の名前で証明書を探すので、検索の対象のキーチェーンに入っていればよい
- name: Import the signing certificate
env:
CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
keychain="${RUNNER_TEMP}/build.keychain-db"
certificate="${RUNNER_TEMP}/certificate.p12"
echo -n "${CERTIFICATE_BASE64}" | base64 --decode > "${certificate}"
security create-keychain -p "${KEYCHAIN_PASSWORD}" "${keychain}"
security set-keychain-settings -lut 21600 "${keychain}"
security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${keychain}"
security import "${certificate}" -k "${keychain}" -P "${P12_PASSWORD}" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${KEYCHAIN_PASSWORD}" "${keychain}"
security list-keychain -d user -s "${keychain}" login.keychain-db
rm -f "${certificate}"

# 今配っている appcast に新しい版を足すので、gh-pages から読んでおく。
# 作業ツリーの外に置く(script/release は、作業ツリーに未コミットのものがあると止まる)
- name: Read the current appcast
id: feed
run: |
if git fetch --depth 1 origin gh-pages && git show FETCH_HEAD:appcast.xml > "${RUNNER_TEMP}/appcast.xml"; then
echo "appcast=${RUNNER_TEMP}/appcast.xml" >> "${GITHUB_OUTPUT}"
else
echo "gh-pages に appcast.xml がありません。新しい版だけの appcast を作ります"
fi

- name: Build, notarize and package
env:
NOTARY_KEY_ID: ${{ secrets.NOTARY_KEY_ID }}
NOTARY_ISSUER_ID: ${{ secrets.NOTARY_ISSUER_ID }}
NOTARY_KEY_P8_BASE64: ${{ secrets.NOTARY_KEY_P8_BASE64 }}
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
ATME_APPCAST: ${{ steps.feed.outputs.appcast }}
run: |
key="${RUNNER_TEMP}/notary.p8"
echo -n "${NOTARY_KEY_P8_BASE64}" | base64 --decode > "${key}"
NOTARY_KEY_P8="${key}" script/release
rm -f "${key}"

- name: Create the release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
run: gh release create "${GITHUB_REF_NAME}" "target/dist/${VERSION}/atme-${VERSION}.zip" --generate-notes

# ここから先はリリースができたあとに動く。失敗しても、リリースは残り、appcast か cask だけが遅れる
- name: Check out the update feed
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: gh-pages
path: feed

- name: Publish the update to the appcast
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
cp "target/dist/${VERSION}/appcast.xml" feed/appcast.xml

# 版が入っていない appcast を配ると、だれも更新できないまま気付けないので確かめる
grep -q "<sparkle:version>${VERSION}</sparkle:version>" feed/appcast.xml \
|| { echo "appcast に ${VERSION} が入っていません" >&2; exit 1; }
xmllint --noout feed/appcast.xml

cd feed
git add appcast.xml
if git diff --cached --quiet; then
echo "appcast にはすでに ${VERSION} が入っています"
exit 0
fi
git -c user.name="github-actions[bot]" \
-c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit -m "atme ${VERSION}"
git push

# トークンは、tap だけに書き込める fine-grained token
- name: Check out the Homebrew tap
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: dominion525/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: tap

- name: Update the Homebrew cask
env:
VERSION: ${{ steps.version.outputs.version }}
CASK: tap/Casks/atme.rb
run: |
set -euo pipefail
sha="$(shasum -a 256 "target/dist/${VERSION}/atme-${VERSION}.zip" | awk '{ print $1 }')"

# リリースのたびに変わる 2 行だけを書き換える。ほかは tap の側で持つ
sed -i '' "s|^ version \".*\"| version \"${VERSION}\"|" "${CASK}"
sed -i '' "s|^ sha256 \".*\"| sha256 \"${sha}\"|" "${CASK}"

# sed が何にも当たらないと、古いまま失敗せずに終わるので確かめる
grep -qx " version \"${VERSION}\"" "${CASK}" || { echo "${CASK} の version を書き換えられませんでした" >&2; exit 1; }
grep -qx " sha256 \"${sha}\"" "${CASK}" || { echo "${CASK} の sha256 を書き換えられませんでした" >&2; exit 1; }
brew style "${CASK}"

cd tap
if git diff --quiet; then
echo "cask はすでに ${VERSION} です"
exit 0
fi
git -c user.name="github-actions[bot]" \
-c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit -am "atme ${VERSION}"
git push
62 changes: 51 additions & 11 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,65 @@

版は `Cargo.toml` の `version` で決まり、Info.plist の版にもそのまま入ります。Sparkle はこの版で新旧を比べるので、リリースのたびに上げます。

## 配るものを作る
## リリースする

版を上げたコミットを `main` に入れてから、その版のタグを push します。

```
git tag v<version>
git push origin v<version>
```

タグを push すると、`.github/workflows/release.yml` が次のことを行います。

1. タグと `Cargo.toml` の版が一致することを確かめる(違えば止まる)
2. Developer ID の署名でビルドし、公証と staple を行う
3. GitHub のリリース(タグ `v<version>`)を作り、`atme-<version>.zip` を置く
4. `gh-pages` の `appcast.xml` に新しい版を足す
5. [dominion525/homebrew-tap](https://github.com/dominion525/homebrew-tap) の `Casks/atme.rb` の `version` と `sha256` を書き換える

3 より後で失敗したときも、リリースは残ります。遅れるのは、失敗した手順の分だけです。

## Secrets

ワークフローは、リポジトリの Secrets にある次の値を使います。

| Secret | 内容 |
| -------------------------- | -------------------------------------------------------------------------------------- |
| `BUILD_CERTIFICATE_BASE64` | Developer ID Application の証明書を .p12 に書き出し、base64 にしたもの |
| `P12_PASSWORD` | その .p12 の書き出しのパスワード |
| `KEYCHAIN_PASSWORD` | ワークフローの中で証明書を入れる、一時的なキーチェーンのパスワード(どんな値でもよい) |
| `NOTARY_KEY_ID` | 公証に使う App Store Connect の API キーの Key ID |
| `NOTARY_ISSUER_ID` | その API キーの Iss 8000 uer ID |
| `NOTARY_KEY_P8_BASE64` | その API キーの .p8 を base64 にしたもの |
| `SPARKLE_PRIVATE_KEY` | 更新に署名する Sparkle の秘密鍵(`generate_keys --account atme -x` で書き出したもの) |
| `HOMEBREW_TAP_TOKEN` | dominion525/homebrew-tap の Contents に書き込める fine-grained token |

Secrets は、登録したあとに値を読み出せません。登録し直せるよう、元の値は別の場所に控えておきます。

## 手元で配るものを作る

ワークフローと同じものを、手元でも作れます。

```
script/release
```

`script/release` は、Developer ID の署名でビルドし、公証と staple を行い、`target/dist/` に `atme-<version>.zip` と `appcast.xml` を作ります。未コミットの変更があるときと、公証が通らなかったときは止まります。
`script/release` は、Developer ID の署名でビルドし、公証と staple を行い、`target/dist/<version>/` に `atme-<version>.zip` と `appcast.xml` を作ります。未コミットの変更があるときと、公証が通らなかったときは止まります。

使う資格情報は次のとおりです。
使う値は次のとおりです。環境変数で渡さなかったものは、キーチェーンから読みます。

| 資格情報 | 内容 | 既定 |
| ------------------------- | ----------------------------------------------------------- | -------------------------- |
| `NOTARY_KEYCHAIN_PROFILE` | `xcrun notarytool store-credentials` で保存したプロファイル | `notarytool` |
| `ATME_SIGN_IDENTITY` | 署名に使う証明書 | `Developer ID Application` |
| Sparkle の EdDSA の鍵 | キーチェーンにある account `atme` の鍵 | |
| 環境変数 | 内容 | 渡さないとき |
| ---------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------- |
| `NOTARY_KEY_P8`、`NOTARY_KEY_ID`、`NOTARY_ISSUER_ID` | 公証に使う App Store Connect の API キーのファイル、Key ID、Issuer ID | `NOTARY_KEYCHAIN_PROFILE` のプロファイルで公証する |
| `NOTARY_KEYCHAIN_PROFILE` | `xcrun notarytool store-credentials` で保存したプロファイル | `notarytool` |
| `ATME_SIGN_IDENTITY` | 署名に使う証明書 | `Developer ID Application` |
| `SPARKLE_PRIVATE_KEY` | 更新に署名する Sparkle の秘密鍵 | キーチェーンにある account `atme` の鍵 |
| `ATME_APPCAST` | 今配っている `appcast.xml`。渡すと、前の版の項目を残したまま新しい版を足す | 新しい版だけの `appcast.xml` を作る |

## 配る
## 配る先

zip は GitHub のリリース(タグ `v<version>`)に置き、`appcast.xml` は `gh-pages` ブランチに置きます。`gh-pages` は <https://atme.dominion525.com> で配信され、atme はここの `appcast.xml` を読んで新しい版を知ります。
zip は GitHub のリリースに置き、`appcast.xml` は `gh-pages` ブランチに置きます。`gh-pages` は <https://atme.dominion525.com> で配信され、atme はここの `appcast.xml` を読んで新しい版を知ります。Homebrew の cask は、GitHub のリリースの zip を指します。

## Sparkle の鍵

Expand All @@ -38,4 +78,4 @@ zip は GitHub のリリース(タグ `v<version>`)に置き、`appcast.xml`
| `generate_keys --account atme -x <file>` | 秘密鍵をファイルに書き出す |
| `generate_keys --account atme -f <file>` | 書き出した秘密鍵をキーチェーンに読み込む |

ほかの Mac からリリースするときは、控えた秘密鍵を `-f` でその Mac のキーチェーンに読み込みます。
ほかの Mac 8000 から手元でリリースするときは、控えた秘密鍵を `-f` でその Mac のキーチェーンに読み込むか、`SPARKLE_PRIVATE_KEY` で渡します。
38 changes: 30 additions & 8 deletions script/release
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
#!/bin/bash
# Developer ID で署名・公証した atme.app の zip と、Sparkle の appcast を target/dist に作る。
# 公証の認証情報は NOTARY_KEYCHAIN_PROFILE(既定 notarytool。xcrun notarytool store-credentials で保存したもの)、
# 署名の証明書は ATME_SIGN_IDENTITY(既定 Developer ID Application)、
# 更新の署名は、キーチェーンにある Sparkle の鍵(generate_keys --account atme)を使う。
# Developer ID で署名・公証した atme.app の zip と、Sparkle の appcast を target/dist/<version> に作る。
# ATME_APPCAST に今配っている appcast.xml を渡すと、それに新しい版を足した appcast を作る(前の版の項目は残る)。
# 渡さなければ、新しい版だけの appcast を作る。
# 公証の認証情報は、NOTARY_KEY_P8(App Store Connect の API キーのファイル)と NOTARY_KEY_ID・NOTARY_ISSUER_ID があればそれを、
# 無ければ NOTARY_KEYCHAIN_PROFILE(既定 notarytool。xcrun notarytool store-credentials で保存したもの)を使う。
# 署名の証明書は ATME_SIGN_IDENTITY(既定 Developer ID Application)。
# 更新の署名は、SPARKLE_PRIVATE_KEY(generate_keys -x で書き出した秘密鍵)があればそれを、
# 無ければキーチェーンにある Sparkle の鍵(generate_keys --account atme)を使う。
# 版は Cargo.toml の version。リリースのたびに上げる(Sparkle は Info.plist の版で新旧を比べる)。
set -euo pipefail

root=$(cd "$(dirname "$0")/.." && pwd)
version=$(sed -n 's/^version = "\(.*\)"$/\1/p' "$root/Cargo.toml" | head -n 1)
sparkle_version=$(sed -n 's/^sparkle_version=//p' "$root/script/bundle")
profile="${NOTARY_KEYCHAIN_PROFILE:-notarytool}"
if [ -n "${NOTARY_KEY_P8:-}" ]; then
notary=(
--key "$NOTARY_KEY_P8"
--key-id "${NOTARY_KEY_ID:?NOTARY_KEY_P8 を使うときは NOTARY_KEY_ID も要ります}"
--issuer "${NOTARY_ISSUER_ID:?NOTARY_KEY_P8 を使うときは NOTARY_ISSUER_ID も要ります}"
)
else
notary=(--keychain-profile "${NOTARY_KEYCHAIN_PROFILE:-notarytool}")
fi
export ATME_SIGN_IDENTITY="${ATME_SIGN_IDENTITY:-Developer ID Application}"
app="$root/target/release/atme.app"
dist="$root/target/dist"
# 版ごとに分ける。generate_appcast は置き場所の zip をすべて読み、どれにも新しい版の URL の頭を付けるため
dist="$root/target/dist/$version"
download_prefix="https://github.com/dominion525/atme/releases/download/v$version/"

if [ -n "$(git -C "$root" status --porcelain)" ]; then
Expand All @@ -28,7 +41,7 @@ submission="$root/target/notarize/atme.zip"
mkdir -p "$(dirname "$submission")"
rm -f "$submission"
ditto -c -k --keepParent "$app" "$submission"
result=$(xcrun notarytool submit "$submission" --keychain-profile "$profile" --wait --output-format json)
result=$(xcrun notarytool submit "$submission" "${notary[@]}" --wait --output-format json)
status=$(echo "$result" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
if [ "$status" != "Accepted" ]; then
echo "公証が通りませんでした(status: ${status:-不明}): $result" >&2
Expand All @@ -45,7 +58,16 @@ rm -f "$zip"
ditto -c -k --keepParent "$app" "$zip"

echo "==> appcast"
"$root/target/sparkle-$sparkle_version/bin/generate_appcast" --account atme --download-url-prefix "$download_prefix" "$dist"
if [ -n "${ATME_APPCAST:-}" ]; then
cp "$ATME_APPCAST" "$dist/appcast.xml"
fi
generate_appcast="$root/target/sparkle-$sparkle_version/bin/generate_appcast"
if [ -n "${SPARKLE_PRIVATE_KEY:-}" ]; then
# 鍵はコマンドの引数に載せず、標準入力で渡す
printf '%s' "$SPARKLE_PRIVATE_KEY" | "$generate_appcast" --ed-key-file - --download-url-prefix "$download_prefix" "$dist"
else
"$generate_appcast" --account atme --download-url-prefix "$download_prefix" "$dist"
fi

echo "==> done"
echo "$zip"
Expand Down
Loading
0