diff --git a/.github/workflows/custom-release.yml b/.github/workflows/custom-release.yml index a962291d5..bb12cd21e 100644 --- a/.github/workflows/custom-release.yml +++ b/.github/workflows/custom-release.yml @@ -48,6 +48,53 @@ jobs: env: SYNC_REMOTE: origin + - name: Import Developer ID certificate + run: | + set -euo pipefail + keychain_path="$RUNNER_TEMP/syncthing-release-signing.keychain-db" + keychain_password="$(openssl rand -hex 24)" + certificate_path="$RUNNER_TEMP/developer-id-application.p12" + previous_default_keychain="$(security default-keychain -d user 2>/dev/null | sed 's/[ "]//g' || true)" + + echo "CUSTOM_RELEASE_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV" + echo "CUSTOM_RELEASE_KEYCHAIN_PASSWORD=$keychain_password" >> "$GITHUB_ENV" + echo "CUSTOM_RELEASE_CERTIFICATE_PATH=$certificate_path" >> "$GITHUB_ENV" + echo "CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN=$previous_default_keychain" >> "$GITHUB_ENV" + + if [ -z "$DEVELOPER_ID_APPLICATION_P12_BASE64" ]; then + echo "DEVELOPER_ID_APPLICATION_P12_BASE64 secret is required" >&2 + exit 1 + fi + if [ -z "$DEVELOPER_ID_APPLICATION_P12_PASSWORD" ]; then + echo "DEVELOPER_ID_APPLICATION_P12_PASSWORD secret is required" >&2 + exit 1 + fi + + printf '%s' "$DEVELOPER_ID_APPLICATION_P12_BASE64" | base64 -D > "$certificate_path" + security create-keychain -p "$keychain_password" "$keychain_path" + security set-keychain-settings -lut 21600 "$keychain_path" + security unlock-keychain -p "$keychain_password" "$keychain_path" + security import "$certificate_path" -k "$keychain_path" -P "$DEVELOPER_ID_APPLICATION_P12_PASSWORD" -A -T /usr/bin/codesign -T /usr/bin/security + security list-keychains -d user -s "$keychain_path" + security default-keychain -d user -s "$keychain_path" + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$keychain_password" "$keychain_path" + + identity_output="$(security find-identity -v -p codesigning "$keychain_path")" + printf '%s\n' "$identity_output" + codesign_identity="$(printf '%s\n' "$identity_output" | sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | head -n 1)" + if [ -z "$codesign_identity" ]; then + echo "Developer ID Application signing identity is required in DEVELOPER_ID_APPLICATION_P12_BASE64" >&2 + exit 1 + fi + + probe_binary="$RUNNER_TEMP/codesign-probe" + cp /usr/bin/true "$probe_binary" + codesign --force --dryrun --sign "$codesign_identity" --keychain "$keychain_path" --options runtime --timestamp "$probe_binary" + echo "CUSTOM_RELEASE_CODESIGN_IDENTITY=$codesign_identity" >> "$GITHUB_ENV" + env: + DEVELOPER_ID_APPLICATION_P12_BASE64: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_BASE64 }} + DEVELOPER_ID_APPLICATION_P12_PASSWORD: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD }} + - name: Build patched Syncthing release run: | case "$(uname -m)" in @@ -63,7 +110,9 @@ jobs: CUSTOM_RELEASE_PUSH: "1" CUSTOM_RELEASE_PUSH_BRANCH: "0" CUSTOM_RELEASE_REMOTE: origin - CUSTOM_RELEASE_SIGN_DARWIN: "0" + CUSTOM_RELEASE_CODESIGN_TEAM_ID: "NG5W75WE8U" + CUSTOM_RELEASE_SIGN_DARWIN: "1" + CUSTOM_RELEASE_REQUIRE_GATEKEEPER_ASSESSMENT: "0" CUSTOM_RELEASE_CREATE_GITEA_RELEASE: "0" GH_TOKEN: ${{ github.token }} @@ -85,3 +134,16 @@ jobs: CUSTOM_RELEASE_UPSTREAM_TAG: ${{ github.event.inputs.upstream_tag }} CUSTOM_RELEASE_SUFFIX: ${{ github.event.inputs.suffix || 'stignore.7' }} GH_TOKEN: ${{ github.token }} + + - name: Delete temporary keychain + if: always() + run: | + if [ -n "${CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN:-}" ] && [ -e "$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN" ]; then + security default-keychain -d user -s "$CUSTOM_RELEASE_PREVIOUS_DEFAULT_KEYCHAIN" || true + fi + if [ -n "${CUSTOM_RELEASE_KEYCHAIN_PATH:-}" ]; then + security delete-keychain "$CUSTOM_RELEASE_KEYCHAIN_PATH" || true + fi + if [ -n "${CUSTOM_RELEASE_CERTIFICATE_PATH:-}" ]; then + rm -f "$CUSTOM_RELEASE_CERTIFICATE_PATH" + fi diff --git a/patches/README.md b/patches/README.md index d6930ea9b..6384d1e03 100644 --- a/patches/README.md +++ b/patches/README.md @@ -41,9 +41,8 @@ matching custom tag already exists, pushes only the `-stignore.7` tag by default, and publishes release assets on the CI host running the workflow. The local `custom/` branch is only pushed when `CUSTOM_RELEASE_PUSH_BRANCH=1` is set. The workflow builds macOS arm64, Linux -amd64, and Linux arm64 archives. Gitea signs its macOS build with the configured -Developer ID certificate; GitHub builds an unsigned macOS archive until the -equivalent signing secrets are configured there. +amd64, and Linux arm64 archives. Both hosts sign their macOS builds with their +configured Developer ID certificate. The patch makes root-level `.stignore` sync like regular folder content while keeping `.stfolder` and `.stversions` protected as internal Syncthing paths. diff --git a/scripts/tests/test-custom-release-macos-runner.bats b/scripts/tests/test-custom-release-macos-runner.bats index 1f9b8baf1..f186219ae 100644 --- a/scripts/tests/test-custom-release-macos-runner.bats +++ b/scripts/tests/test-custom-release-macos-runner.bats @@ -30,13 +30,33 @@ setup() { run rg -n 'CUSTOM_RELEASE_CREATE_GITEA_RELEASE: "0"' "$GITHUB_WORKFLOW" [ "$status" -eq 0 ] - run rg -n 'CUSTOM_RELEASE_SIGN_DARWIN: "0"' "$GITHUB_WORKFLOW" + run rg -n 'CUSTOM_RELEASE_SIGN_DARWIN: "1"' "$GITHUB_WORKFLOW" [ "$status" -eq 0 ] run rg -n 'tea|ffmini_macos_arm64' "$GITHUB_WORKFLOW" [ "$status" -ne 0 ] } +@test "github imports signing secrets into a disposable keychain" { + run rg -n 'DEVELOPER_ID_APPLICATION_P12_BASE64:.*secrets.DEVELOPER_ID_APPLICATION_P12_BASE64' "$GITHUB_WORKFLOW" + [ "$status" -eq 0 ] + + run rg -n 'DEVELOPER_ID_APPLICATION_P12_PASSWORD:.*secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD' "$GITHUB_WORKFLOW" + [ "$status" -eq 0 ] + + run rg -n 'security import .* -P "\$DEVELOPER_ID_APPLICATION_P12_PASSWORD"' "$GITHUB_WORKFLOW" + [ "$status" -eq 0 ] + + run rg -n 'if: always\(\)' "$GITHUB_WORKFLOW" + [ "$status" -eq 0 ] + + run rg -n 'security delete-keychain "\$CUSTOM_RELEASE_KEYCHAIN_PATH"' "$GITHUB_WORKFLOW" + [ "$status" -eq 0 ] + + run rg -n 'rm -f "\$CUSTOM_RELEASE_CERTIFICATE_PATH"' "$GITHUB_WORKFLOW" + [ "$status" -eq 0 ] +} + @test "custom release runs as one job on ffmini macos runner" { run rg -n 'runs-on:[[:space:]]*ffmini_macos_arm64' "$WORKFLOW" [ "$status" -eq 0 ]