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
75 changes: 75 additions & 0 deletions .github/scripts/release-checksums.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/sh
set -eu

if [ "$#" -ne 2 ]; then
echo "usage: release-checksums.sh VERSION DIST_DIRECTORY" >&2
exit 64
fi

VERSION="$1"
DIST_DIRECTORY="$2"

case "$VERSION" in
""|[!0-9A-Za-z]*|*[!0-9A-Za-z.-]*|*..*|*.)
echo "release checksums: invalid version: $VERSION" >&2
exit 64
;;
esac
if [ "${#VERSION}" -gt 64 ]; then
echo "release checksums: version is too long" >&2
exit 64
fi
if [ ! -d "$DIST_DIRECTORY" ]; then
echo "release checksums: dist directory does not exist: $DIST_DIRECTORY" >&2
exit 66
fi

DIST_DIRECTORY="$(CDPATH='' cd -- "$DIST_DIRECTORY" && pwd -P)"
set -- \
"Headless-${VERSION}-macos.zip" \
"headless-${VERSION}-linux-amd64.tar.gz" \
"headless-${VERSION}-linux-arm64.tar.gz"

for asset in "$@"; do
path="$DIST_DIRECTORY/$asset"
if [ ! -f "$path" ] || [ -L "$path" ] || [ ! -s "$path" ]; then
echo "release checksums: missing or unsafe release asset: $asset" >&2
exit 66
fi
done

checksum_files() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$@"
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$@"
else
echo "release checksums: sha256sum or shasum is required" >&2
exit 69
fi
}

verify_manifest() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum -c SHA256SUMS
else
shasum -a 256 -c SHA256SUMS
fi
}

MANIFEST="$DIST_DIRECTORY/SHA256SUMS"
TEMP_MANIFEST="$(mktemp "$DIST_DIRECTORY/.SHA256SUMS.XXXXXX")"
trap '[ -z "$TEMP_MANIFEST" ] || rm -f "$TEMP_MANIFEST"' EXIT INT TERM

(
cd "$DIST_DIRECTORY"
checksum_files "$@"
) > "$TEMP_MANIFEST"
chmod 0644 "$TEMP_MANIFEST"
mv -f "$TEMP_MANIFEST" "$MANIFEST"
TEMP_MANIFEST=""

(
cd "$DIST_DIRECTORY"
verify_manifest
)
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,13 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
- name: Generate and verify checksums
run: ./.github/scripts/release-checksums.sh "${{ needs.version.outputs.version }}" dist
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
Expand All @@ -115,4 +118,5 @@ jobs:
dist/Headless-${{ needs.version.outputs.version }}-macos.zip
dist/headless-${{ needs.version.outputs.version }}-linux-amd64.tar.gz
dist/headless-${{ needs.version.outputs.version }}-linux-arm64.tar.gz
dist/SHA256SUMS
fail_on_unmatched_files: true
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Cutting that release is tracked in

### Added

- Tagged releases now include a verified `SHA256SUMS` manifest covering every
downloadable macOS and Linux package.
- A single cross-engine conformance scenario now runs against real WKWebView
and Chromium hosts, locking shared response shapes and declared capability
errors without mirrored platform assertions.
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,17 @@ git tag v1.0.0
git push origin v1.0.0
```

Assets: macOS `Headless.app` zip, Linux amd64/arm64 tarballs. See the Actions
`Release` workflow and the release notes on each tag for install caveats
(Gatekeeper; Linux Chromium/FFmpeg).
Assets: macOS `Headless.app` zip, Linux amd64/arm64 tarballs, and
`SHA256SUMS`. Download the manifest beside the selected package and verify it
before installing:

```sh
sha256sum --ignore-missing -c SHA256SUMS # Linux
shasum -a 256 --ignore-missing -c SHA256SUMS # macOS
```

See the Actions `Release` workflow and the release notes on each tag for
install caveats (Gatekeeper; Linux Chromium/FFmpeg).

## Tests

Expand Down
60 changes: 60 additions & 0 deletions apps/headless/Tests/release-checksums.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/sh
set -eu

cd "$(dirname "$0")/.."
SCRIPT="../../.github/scripts/release-checksums.sh"
TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/headless-release-checksums.XXXXXX")"
trap 'rm -rf "$TEST_ROOT"' EXIT INT TERM

VERSION="1.2.3-rc.1"
MACOS="Headless-${VERSION}-macos.zip"
AMD64="headless-${VERSION}-linux-amd64.tar.gz"
ARM64="headless-${VERSION}-linux-arm64.tar.gz"

printf 'macOS package\n' > "$TEST_ROOT/$MACOS"
printf 'Linux amd64 package\n' > "$TEST_ROOT/$AMD64"
printf 'Linux arm64 package\n' > "$TEST_ROOT/$ARM64"

"$SCRIPT" "$VERSION" "$TEST_ROOT" >/dev/null
test "$(wc -l < "$TEST_ROOT/SHA256SUMS" | tr -d ' ')" -eq 3
if [ "$(uname -s)" = "Darwin" ]; then
test "$(stat -f %Lp "$TEST_ROOT/SHA256SUMS")" = "644"
else
test "$(stat -c %a "$TEST_ROOT/SHA256SUMS")" = "644"
fi
awk '{print $2}' "$TEST_ROOT/SHA256SUMS" > "$TEST_ROOT/names"
printf '%s\n' "$MACOS" "$AMD64" "$ARM64" > "$TEST_ROOT/expected-names"
cmp -s "$TEST_ROOT/names" "$TEST_ROOT/expected-names"

if command -v sha256sum >/dev/null 2>&1; then
(cd "$TEST_ROOT" && sha256sum -c SHA256SUMS >/dev/null)
else
(cd "$TEST_ROOT" && shasum -a 256 -c SHA256SUMS >/dev/null)
fi

cp "$TEST_ROOT/SHA256SUMS" "$TEST_ROOT/original-manifest"
"$SCRIPT" "$VERSION" "$TEST_ROOT" >/dev/null
cmp -s "$TEST_ROOT/SHA256SUMS" "$TEST_ROOT/original-manifest"

rm "$TEST_ROOT/$ARM64"
if "$SCRIPT" "$VERSION" "$TEST_ROOT" >/dev/null 2>&1; then
echo "release checksum test: missing assets must fail" >&2
exit 1
fi
cmp -s "$TEST_ROOT/SHA256SUMS" "$TEST_ROOT/original-manifest"

ln -s "$AMD64" "$TEST_ROOT/$ARM64"
if "$SCRIPT" "$VERSION" "$TEST_ROOT" >/dev/null 2>&1; then
echo "release checksum test: symlink assets must fail" >&2
exit 1
fi

for invalid_version in '' '../1.2.3' '.1.2.3' '1.2.3/' '1..2' '1.2.3.' \
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; do
if "$SCRIPT" "$invalid_version" "$TEST_ROOT" >/dev/null 2>&1; then
echo "release checksum test: invalid version accepted: $invalid_version" >&2
exit 1
fi
done

echo "Release checksum tests passed"
2 changes: 2 additions & 0 deletions apps/headless/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
set -euo pipefail
cd "$(dirname "$0")"

./Tests/release-checksums.sh

for tool in swift swiftc; do
if ! command -v "$tool" >/dev/null 2>&1; then
if [[ "$(uname -s)" == "Darwin" ]]; then
Expand Down
6 changes: 4 additions & 2 deletions docs/roadmap/improvements-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,10 @@ Owner-decided scope: package managers, no hosted service.
`HP/ChromiumRuntime.swift:167-184`).
- **E3.** ([#41](https://github.com/LockInTime/headless/issues/41)) Publish the Docker `production` image to GHCR on tag (release notes
currently say "not published"); this is also the interim Windows story.
- **E4.** ([#42](https://github.com/LockInTime/headless/issues/42)) `SHA256SUMS` (+ consider cosign) for all release assets — the QA
evidence bundle already ships sums; releases don't.
- **E4.** ([#42](https://github.com/LockInTime/headless/issues/42)) ~~`SHA256SUMS` for all release assets — the QA evidence bundle already ships
sums; releases don't.~~ **Done:** the publish job requires all three named
regular package files, generates `SHA256SUMS` atomically, verifies it, and
attaches the manifest to the release. Cosign remains optional future work.
- **E5.** ([#43](https://github.com/LockInTime/headless/issues/43)) npm wrapper package (binary download shim) for `npx` reach.
- **E6.** ([#44](https://github.com/LockInTime/headless/issues/44)) Version unification + `headless --version` + CHANGELOG + release
automation (architecture §12). `package.json` says 0.0.0, tags say 1.0.x,
Expand Down