Skip to content

release

release #6

Workflow file for this run

name: release
# Builds, gates, publishes and install-checks the `encypher` CLI for five
# targets (PRD_CLI_Universal_Install in the private monorepo). The canonical
# copy of every file in this repository is packaging/distribution/ in
# encypherai/encypherai-commercial; packaging/release/public-repo.sh sync puts
# it here, and the provenance job refuses to run when the two differ.
#
# - Dispatch only. `publish: false` is a dry run: every job except publish and
# promote, with the install checks run against the built archives served on
# 127.0.0.1.
# - Trusted code first: .github/scripts/provenance.sh, from this repository,
# refuses a source_ref that is not on the private main before any private
# file executes.
# - The release is published as a pre-release, installed and run by the
# release's own install.sh and install.ps1 on all five platforms, and only
# then marked latest, so an unpinned install never receives an unchecked
# release.
# - This repository is public, and so are its logs and artifacts. Private
# tooling (builds, tests, the artifact gate) runs through
# packaging/release/contained.py, which prints only a status line; a failed
# step's output is uploaded encrypted to the two maintainer certificates in
# .github/, so only a holder of one private half can read it.
on:
workflow_dispatch:
inputs:
source_ref:
description: "Commit of encypherai/encypherai-commercial to release (40 hex characters, on its main)"
required: true
type: string
probe_floor:
description: "ENCYPHER_PROBE_FLOOR: the published probe release head"
required: true
type: string
publish:
description: "Publish the release (false: dry run, nothing is published)"
required: true
default: false
type: boolean
permissions:
contents: read
concurrency:
group: release-${{ github.repository }}
cancel-in-progress: false
env:
RUSTUP_TOOLCHAIN: "1.97.1"
CARGO_TERM_COLOR: never
SOURCE_REPOSITORY: encypherai/encypherai-commercial
jobs:
provenance:
name: Source revision, version and pre-checks
runs-on: ubuntu-24.04
environment: build
timeout-minutes: 15
outputs:
revision: ${{ steps.pin.outputs.revision }}
source_date_epoch: ${{ steps.pin.outputs.source_date_epoch }}
version: ${{ steps.pin.outputs.version }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
path: public
persist-credentials: false
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: ${{ env.SOURCE_REPOSITORY }}
ref: ${{ inputs.source_ref }}
ssh-key: ${{ secrets.ENCYPHER_SOURCE_DEPLOY_KEY }}
path: source
fetch-depth: 0
filter: tree:0
sparse-checkout: |
packages/encypher-c2pa
packaging
apps/content-provenance/public/samples
persist-credentials: false
- name: Trusted pre-checks (source on private main, this repository matches it)
env:
SOURCE_REF: ${{ inputs.source_ref }}
SOURCE_DIR: source
PUBLIC_DIR: public
run: bash public/.github/scripts/provenance.sh
- name: Pin the version, revision and SOURCE_DATE_EPOCH
id: pin
working-directory: source
env:
PROBE_FLOOR: ${{ inputs.probe_floor }}
run: |
if ! [[ "${PROBE_FLOOR}" =~ ^[1-9][0-9]*$ ]]; then
echo "probe_floor must be the published probe release head (a number of at least 1), got '${PROBE_FLOOR}'" >&2
exit 1
fi
version="$(python3 packaging/release/release.py version)"
if git -C ../public ls-remote --exit-code --tags origin "refs/tags/v${version}" >/dev/null; then
echo "v${version} already exists here; bump encypher-cli, encypher-client and encypher-client-ffi together" >&2
exit 1
fi
{
echo "revision=$(git rev-parse HEAD)"
echo "source_date_epoch=$(git log -1 --format=%ct)"
echo "version=${version}"
} >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: smoke-fixture
path: |
source/apps/content-provenance/public/samples/signed-test.jpg
source/packages/encypher-c2pa/fixtures/hash-mode/image_png.png
if-no-files-found: error
retention-days: 7
windows-lock:
name: Windows unit tests and installer downloads (windows-2022)
needs: provenance
runs-on: windows-2022
environment: build
timeout-minutes: 45
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: ${{ env.SOURCE_REPOSITORY }}
ref: ${{ needs.provenance.outputs.revision }}
ssh-key: ${{ secrets.ENCYPHER_SOURCE_DEPLOY_KEY }}
path: source
sparse-checkout: |
packages/encypher-c2pa
packaging
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Install the pinned toolchain
run: rustup toolchain install "${RUSTUP_TOOLCHAIN}" --profile minimal
# The output lock (LockFileEx, POSIX-semantics delete of a held lock
# file, delete-pending contention), the no-replace publish (MoveFileExW)
# and the results-file identity are Windows-only code paths.
- name: Client core and CLI unit tests
working-directory: source/packages/encypher-c2pa
env:
ENCYPHER_PROBE_FLOOR: ${{ inputs.probe_floor }}
run: |
contained="python ../../packaging/release/contained.py --show ^test.result:"
${contained} --label "encypher-client unit tests" -- cargo test --locked -p encypher-client --lib
${contained} --label "encypher-cli unit tests" -- cargo test --locked -p encypher-cli --lib
# install.ps1 follows redirects itself and checks every hop before
# requesting it; this runs that downloader under both shells against
# local servers, with a throwaway certificate trusted on this runner only.
# It also checks that install-check.ps1 reads the printed PATH lines
# whole, which Windows PowerShell 5.1's formatting once split.
- name: install.ps1 downloader and PATH lines under Windows PowerShell 5.1 and PowerShell 7
working-directory: source/packaging/release
env:
ENCYPHER_TEST_TRUST_MACHINE_ROOT: "1"
run: python -m unittest -v test_install.SaveEncypherUrl test_install.InstallPs1PathLine
# The plain logs never leave the runner; these are encrypted to the
# maintainer certificate in this repository.
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: withheld-logs-windows-lock
path: ${{ runner.temp }}/contained/*.log.cms
if-no-files-found: ignore
retention-days: 7
build:
name: Build ${{ matrix.target }}
needs: provenance
runs-on: ${{ matrix.os }}
environment: build
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
os: macos-15
- target: x86_64-apple-darwin
os: macos-15-intel
# glibc 2.35 runners: release.py refuses a binary that needs more than GLIBC_2.34.
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04-arm
- target: x86_64-pc-windows-msvc
os: windows-2022
defaults:
run:
shell: bash
env:
TARGET: ${{ matrix.target }}
ENCYPHER_PROBE_FLOOR: ${{ inputs.probe_floor }}
SOURCE_DATE_EPOCH: ${{ needs.provenance.outputs.source_date_epoch }}
REVISION: ${{ needs.provenance.outputs.revision }}
VERSION: ${{ needs.provenance.outputs.version }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: ${{ env.SOURCE_REPOSITORY }}
ref: ${{ needs.provenance.outputs.revision }}
ssh-key: ${{ secrets.ENCYPHER_SOURCE_DEPLOY_KEY }}
path: source
sparse-checkout: |
packages/encypher-c2pa
packaging
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Install the pinned toolchain and llvm-tools
run: rustup toolchain install "${RUSTUP_TOOLCHAIN}" --profile minimal --target "${TARGET}" --component llvm-tools
- name: Refuse a test-hooks feature graph
working-directory: source/packages/encypher-c2pa
run: |
if ! graph="$(cargo tree --locked -p encypher-cli -p encypher-client-ffi -e features,normal --target "${TARGET}" 2>/dev/null)"; then
echo "cargo tree failed; rerun it at the release's source revision" >&2
exit 1
fi
if grep -q 'test-hooks' <<< "${graph}"; then
echo "a shipped build must never enable test-hooks" >&2
exit 1
fi
# One remap convention for every pipeline (scripts/remap_prefixes.py)
# keeps the runner's checkout, cargo, rustup and home paths out of the
# binaries; the artifact gate refuses any other absolute source path.
- name: Build the CLI and the C library (dist profile)
working-directory: source/packages/encypher-c2pa
run: |
RUSTFLAGS="$(python scripts/remap_prefixes.py)"
export RUSTFLAGS
contained="python ../../packaging/release/contained.py"
${contained} --label "dist build: encypher" -- cargo build --locked -p encypher-cli --profile dist --target "${TARGET}"
${contained} --label "dist build: encypher-client-ffi" -- cargo build --locked -p encypher-client-ffi --profile dist --target "${TARGET}"
# Apple Silicon runs only signed code; the ad hoc signature is the whole
# macOS signing story (no Developer ID, no notarization).
- name: Ad hoc signature (macOS)
if: runner.os == 'macOS'
working-directory: source/packages/encypher-c2pa/target/${{ matrix.target }}/dist
run: codesign --force --sign - encypher libencypher_client.dylib
- name: Artifact gate on the final binaries
working-directory: source/packages/encypher-c2pa
run: |
dir="target/${TARGET}/dist"
case "${TARGET}" in
*windows*) files=("${dir}/encypher.exe" "${dir}/encypher_client.dll") ;;
*apple*) files=("${dir}/encypher" "${dir}/libencypher_client.dylib") ;;
*) files=("${dir}/encypher" "${dir}/libencypher_client.so") ;;
esac
python ../../packaging/release/contained.py --label "artifact gate: binaries" -- python scripts/check-client-artifact.py "${files[@]}"
- name: Smoke the binary
working-directory: source/packages/encypher-c2pa
env:
ENCYPHER_API_KEY: ${{ secrets.ENCYPHER_API_KEY }}
run: |
cli="target/${TARGET}/dist/encypher"
[[ "${TARGET}" == *windows* ]] && cli="${cli}.exe"
"${cli}" --version
"${cli}" inspect fixtures/hash-mode/image_jpeg.jpg --json
if [[ -n "${ENCYPHER_API_KEY}" ]]; then
# The envelopes name documents and service URLs; this log is public.
"${cli}" sign fixtures/hash-mode/image_png.png -o "${RUNNER_TEMP}/smoke-signed.png" \
--digital-source-type http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture \
--on-existing-provenance chain --json >/dev/null
"${cli}" verify "${RUNNER_TEMP}/smoke-signed.png" --json >/dev/null
echo "smoke: signed and verified a PNG"
else
echo "ENCYPHER_API_KEY is not configured: live sign skipped"
fi
- name: Archive and inventory
run: |
python source/packaging/release/release.py package --target "${TARGET}" --version "${VERSION}" \
--build-dir "source/packages/encypher-c2pa/target/${TARGET}/dist" --revision "${REVISION}" \
--source-date-epoch "${SOURCE_DATE_EPOCH}" --probe-floor "${ENCYPHER_PROBE_FLOOR}" --out dist
- name: Artifact gate on the archive
run: |
python source/packaging/release/contained.py --label "artifact gate: archive" -- \
python source/packages/encypher-c2pa/scripts/check-client-artifact.py dist/*.tar.gz
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dist-${{ matrix.target }}
path: dist
if-no-files-found: error
retention-days: 7
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: withheld-logs-${{ matrix.target }}
path: ${{ runner.temp }}/contained/*.log.cms
if-no-files-found: ignore
retention-days: 7
merge:
name: SHA256SUMS, inventory and installers
needs: [provenance, windows-lock, build]
runs-on: ubuntu-24.04
environment: build
timeout-minutes: 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: ${{ env.SOURCE_REPOSITORY }}
ref: ${{ needs.provenance.outputs.revision }}
ssh-key: ${{ secrets.ENCYPHER_SOURCE_DEPLOY_KEY }}
path: source
sparse-checkout: |
packages/encypher-c2pa
packaging
persist-credentials: false
- name: Install the pinned toolchain and llvm-tools
run: rustup toolchain install "${RUSTUP_TOOLCHAIN}" --profile minimal --component llvm-tools
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: dist-*
path: dist
merge-multiple: true
- name: SHA256SUMS and inventory.json (one revision, one toolchain)
run: python3 source/packaging/release/release.py merge --dist dist
- name: Artifact gate on every archive
run: |
python3 source/packaging/release/contained.py --label "artifact gate: all archives" -- \
python3 source/packages/encypher-c2pa/scripts/check-client-artifact.py dist/*.tar.gz
- name: Attach the installers
run: cp source/packaging/install/install.sh source/packaging/install/install.ps1 dist/
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dist
path: |
dist/*.tar.gz
dist/SHA256SUMS
dist/inventory.json
dist/install.sh
dist/install.ps1
if-no-files-found: error
retention-days: 7
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: withheld-logs-merge
path: ${{ runner.temp }}/contained/*.log.cms
if-no-files-found: ignore
retention-days: 7
publish:
name: Publish v${{ needs.provenance.outputs.version }} as a pre-release
if: inputs.publish
needs: [provenance, merge]
runs-on: ubuntu-24.04
environment: publish
timeout-minutes: 15
permissions:
contents: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist
# Every asset is attached to a draft first, then the draft is published:
# with immutable releases on, the files and tag are locked from that
# moment, so nothing can be added or swapped afterwards.
- name: Draft, upload, publish as a pre-release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.provenance.outputs.version }}
REVISION: ${{ needs.provenance.outputs.revision }}
PROBE_FLOOR: ${{ inputs.probe_floor }}
run: |
toolchain="$(python3 -c 'import json; print(json.load(open("dist/inventory.json"))["toolchain"])')"
notes="encypher ${VERSION}. Source revision ${REVISION}, ${toolchain}, probe floor ${PROBE_FLOOR}. Install: see the README."
gh release create "v${VERSION}" --repo "${GITHUB_REPOSITORY}" --target "${GITHUB_SHA}" --draft --prerelease \
--title "encypher ${VERSION}" --notes "${notes}" \
dist/*.tar.gz dist/SHA256SUMS dist/inventory.json dist/install.sh dist/install.ps1
gh release edit "v${VERSION}" --repo "${GITHUB_REPOSITORY}" --draft=false
# Every leg signs and verifies with the one smoke key, so the legs share
# one organisation's limits (64 outstanding signing plans, a per-caller
# verify concurrency cap). They run one at a time: legs at once got 429s.
install-check:
name: Install on ${{ matrix.name }}
needs: [provenance, merge, publish]
if: always() && needs.merge.result == 'success' && (needs.publish.result == 'success' || (needs.publish.result == 'skipped' && !inputs.publish))
runs-on: ${{ matrix.os }}
environment: build
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- name: Linux x86_64 and rockylinux:9
os: ubuntu-22.04
unpinned: true
- name: Linux arm64
os: ubuntu-22.04-arm
unpinned: false
- name: macOS Apple Silicon
os: macos-15
unpinned: true
- name: macOS Intel
os: macos-15-intel
unpinned: false
- name: Windows x64
os: windows-2022
unpinned: true
env:
VERSION: ${{ needs.provenance.outputs.version }}
PUBLISHED: ${{ inputs.publish }}
ENCYPHER_API_KEY: ${{ secrets.ENCYPHER_API_KEY }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
path: public
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: smoke-fixture
path: fixture-download
- name: Collect the fixtures
shell: bash
run: |
mkdir -p fixture
find fixture-download -type f \( -name signed-test.jpg -o -name image_png.png \) -exec cp {} fixture/ \;
echo "FIXTURE=${GITHUB_WORKSPACE}/fixture" >> "$GITHUB_ENV"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
if: ${{ !inputs.publish }}
with:
name: dist
path: dist
- name: Download base (published release)
if: inputs.publish
shell: bash
run: echo "BASE=https://github.com/${GITHUB_REPOSITORY}/releases" >> "$GITHUB_ENV"
- name: Download base (dry run, loopback mirror of the built release)
if: ${{ !inputs.publish }}
shell: bash
run: |
mirror="${RUNNER_TEMP}/mirror"
mkdir -p "${mirror}/releases/download/v${VERSION}" "${mirror}/releases/latest/download"
cp dist/* "${mirror}/releases/download/v${VERSION}/"
# GitHub serves the latest release's assets here too, and that is
# where an unpinned install reads both the script and SHA256SUMS.
cp dist/* "${mirror}/releases/latest/download/"
{
echo "MIRROR=${mirror}"
echo "BASE=http://127.0.0.1:8765/releases"
echo "ENCYPHER_DOWNLOAD_URL=http://127.0.0.1:8765/releases"
} >> "$GITHUB_ENV"
- name: Start the loopback mirror (Linux, macOS)
if: ${{ !inputs.publish && runner.os != 'Windows' }}
shell: bash
run: |
nohup python -m http.server 8765 --bind 127.0.0.1 --directory "${MIRROR}" >/dev/null 2>&1 &
for _ in $(seq 1 30); do
curl -fsS -o /dev/null "${BASE}/download/v${VERSION}/SHA256SUMS" && exit 0
sleep 1
done
exit 1
- name: Start the loopback mirror (Windows)
if: ${{ !inputs.publish && runner.os == 'Windows' }}
shell: pwsh
run: |
Start-Process python -ArgumentList '-m', 'http.server', '8765', '--bind', '127.0.0.1', '--directory', $env:MIRROR -WindowStyle Hidden
foreach ($i in 1..30) {
try { Invoke-WebRequest -UseBasicParsing "$env:BASE/download/v$env:VERSION/SHA256SUMS" | Out-Null; exit 0 } catch { Start-Sleep 1 }
}
exit 1
# One leg per operating system takes the README's unpinned command, which
# only a dry run can exercise: until promote, `latest` still points at the
# previous release.
- name: install.sh as a user runs it
if: runner.os != 'Windows'
shell: bash
env:
ENCYPHER_CHECK_UNPINNED: ${{ !inputs.publish && matrix.unpinned && '1' || '' }}
run: bash public/.github/scripts/install-check.sh
- name: install.sh on glibc 2.34 (rockylinux:9)
if: matrix.os == 'ubuntu-22.04'
shell: bash
run: |
docker run --rm --network host -e ENCYPHER_VERSION="${VERSION}" -e ENCYPHER_DOWNLOAD_URL -e BASE \
-v "${FIXTURE}:/fixture:ro" rockylinux/rockylinux:9 \
sh -c 'curl -fsSL "${BASE}/download/v${ENCYPHER_VERSION}/install.sh" | sh && ldd --version | head -n 1 && ~/.local/bin/encypher --version && ~/.local/bin/encypher verify /fixture/signed-test.jpg --json'
- name: install.ps1 under Windows PowerShell 5.1
if: runner.os == 'Windows'
shell: powershell
env:
ENCYPHER_CHECK_UNPINNED: ${{ !inputs.publish && matrix.unpinned && '1' || '' }}
run: ./public/.github/scripts/install-check.ps1
- name: install.ps1 under PowerShell 7
if: runner.os == 'Windows'
shell: pwsh
env:
ENCYPHER_INSTALL_DIR: ${{ runner.temp }}\pwsh7
run: ./public/.github/scripts/install-check.ps1
install-verdict:
name: Install checks passed
needs: [install-check]
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Every install-check leg ran and passed
env:
RESULT: ${{ needs.install-check.result }}
run: |
if [[ "${RESULT}" != success ]]; then
echo "install-check: ${RESULT}" >&2
exit 1
fi
# The macOS end-to-end subset (client-core PRD, section 11), on a dry run
# only: it spends live signatures and needs the built archives, not a
# published release. The cases run from the installed archive with their
# work directory on an exFAT volume; only case verdicts reach this public
# log. It uses the same smoke key as install-check, so it starts after the
# install legs (whatever their result) and the Macs run one at a time.
macos-e2e:
name: macOS end to end on ${{ matrix.name }}
needs: [provenance, merge, install-check]
if: ${{ !cancelled() && !inputs.publish && needs.merge.result == 'success' }}
runs-on: ${{ matrix.os }}
environment: build
timeout-minutes: 120
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- name: Apple Silicon
os: macos-15
target: aarch64-apple-darwin
- name: Intel
os: macos-15-intel
target: x86_64-apple-darwin
env:
VERSION: ${{ needs.provenance.outputs.version }}
TARGET: ${{ matrix.target }}
ENCYPHER_API_KEY: ${{ secrets.ENCYPHER_API_KEY }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: ${{ env.SOURCE_REPOSITORY }}
ref: ${{ needs.provenance.outputs.revision }}
ssh-key: ${{ secrets.ENCYPHER_SOURCE_DEPLOY_KEY }}
path: source
sparse-checkout: |
packages/encypher-c2pa
packaging
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
# The artifact gate reads binaries with llvm-tools. The live matrix
# builds its fixture generator offline against the engine's lock file,
# so the crates are fetched first.
- name: Install the pinned toolchain and llvm-tools, fetch the engine's crates
working-directory: source/packages/encypher-c2pa
run: |
rustup toolchain install "${RUSTUP_TOOLCHAIN}" --profile minimal --component llvm-tools
python ../../packaging/release/contained.py --label "cargo fetch" -- cargo fetch --locked
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dist
path: dist
- name: c2patool 0.27.22 (pinned by SHA-256)
run: |
curl -fsSL -o "${RUNNER_TEMP}/c2patool.zip" \
https://github.com/contentauth/c2pa-rs/releases/download/c2patool-v0.27.22/c2patool-v0.27.22-universal-apple-darwin.zip
echo "d064ca72e599c74e5fdc2ed04b47d6cb62210a2b14db589d078c2ec27c98d3df ${RUNNER_TEMP}/c2patool.zip" | shasum -a 256 -c
unzip -q "${RUNNER_TEMP}/c2patool.zip" -d "${RUNNER_TEMP}/c2patool"
tool="${RUNNER_TEMP}/c2patool/c2patool/c2patool"
chmod +x "${tool}"
"${tool}" --version
echo "C2PATOOL=${tool}" >> "$GITHUB_ENV"
- name: exFAT volume
run: |
hdiutil create -type SPARSE -size 6g -fs ExFAT -volname E2E "${RUNNER_TEMP}/e2e"
hdiutil attach -nobrowse "${RUNNER_TEMP}/e2e.sparseimage" -mountpoint "${RUNNER_TEMP}/exfat"
mount | grep -F "${RUNNER_TEMP}/exfat" | grep -qi exfat
echo "EXFAT=${RUNNER_TEMP}/exfat" >> "$GITHUB_ENV"
- name: Gate inventory of the archive under test
run: |
archive="dist/encypher-${VERSION}-${TARGET}.tar.gz"
python source/packages/encypher-c2pa/scripts/check-client-artifact.py "${archive}" > "${RUNNER_TEMP}/gate.json" 2>/dev/null
python -c 'import json, sys; print(json.dumps({"shipped": json.load(open(sys.argv[1]))}))' "${RUNNER_TEMP}/gate.json" > "${RUNNER_TEMP}/gate-inventory.json"
echo "ARCHIVE=${GITHUB_WORKSPACE}/${archive}" >> "$GITHUB_ENV"
# JPEG sign and verify, JSON-LD metadata, the layered TIFF over 300 MB,
# the batch interrupted and resumed, the lid closed during finalize
# (SIGSTOP) and the interrupted publish, all from the installed archive
# with the work directory on exFAT. Only the `e2e:` verdicts are shown.
- name: macOS end-to-end subset on exFAT
run: |
python source/packaging/release/contained.py --label "e2e: macOS subset" --show '^e2e: ' -- \
python source/packaging/release/macos_e2e.py --tarball "${ARCHIVE}" --work "${EXFAT}/macos" \
--c2patool "${C2PATOOL}" --gate-inventory "${RUNNER_TEMP}/gate-inventory.json" \
--fixture-cache "${RUNNER_TEMP}/fixtures"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: withheld-logs-macos-e2e-${{ matrix.target }}
path: ${{ runner.temp }}/contained/*.log.cms
if-no-files-found: ignore
retention-days: 7
promote:
name: Mark v${{ needs.provenance.outputs.version }} latest
needs: [provenance, install-check]
if: inputs.publish && needs.install-check.result == 'success'
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Promote, then install the latest release unpinned
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.provenance.outputs.version }}
run: |
gh release edit "v${VERSION}" --repo "${GITHUB_REPOSITORY}" --prerelease=false --latest
curl -fsSL "https://github.com/${GITHUB_REPOSITORY}/releases/latest/download/install.sh" \
| ENCYPHER_INSTALL_DIR="${RUNNER_TEMP}/latest" sh
"${RUNNER_TEMP}/latest/encypher" --version | grep -qF "\"client\": \"${VERSION}\""