openpencil/tools/check-macos-bundle-plist.sh

113 lines
3.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# Validate OpenPencil's macOS local-network privacy metadata.
#
# With plist arguments, this checks the exact files. With no arguments, it
# checks the cargo-bundle extension metadata and all repository packaging /
# release integration points.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=macos-local-network-plist.sh
source "$ROOT/tools/macos-local-network-plist.sh"
fail() {
printf 'macos-bundle-plist: error: %s\n' "$*" >&2
exit 1
}
require_regex() {
local file=$1
local pattern=$2
local reason=$3
grep -Eq -- "$pattern" "$file" ||
fail "$file: $reason"
}
check_plists() {
OPENPENCIL_EXPECTED_LOCAL_NETWORK_DESCRIPTION="$OPENPENCIL_LOCAL_NETWORK_USAGE_DESCRIPTION" \
OPENPENCIL_EXPECTED_BONJOUR_SERVICE="$OPENPENCIL_BONJOUR_SERVICE" \
python3 - "$@" <<'PY'
import os
import plistlib
import sys
from pathlib import Path
expected_description = os.environ["OPENPENCIL_EXPECTED_LOCAL_NETWORK_DESCRIPTION"]
expected_service = os.environ["OPENPENCIL_EXPECTED_BONJOUR_SERVICE"]
failed = False
for raw_path in sys.argv[1:]:
path = Path(raw_path)
try:
with path.open("rb") as handle:
plist = plistlib.load(handle)
except (OSError, plistlib.InvalidFileException) as error:
print(f"macos-bundle-plist: error: {path}: invalid plist: {error}", file=sys.stderr)
failed = True
continue
description = plist.get("NSLocalNetworkUsageDescription")
if description != expected_description:
print(
"macos-bundle-plist: error: "
f"{path}: NSLocalNetworkUsageDescription must equal "
f"{expected_description!r}, got {description!r}",
file=sys.stderr,
)
failed = True
services = plist.get("NSBonjourServices")
if services != [expected_service]:
print(
"macos-bundle-plist: error: "
f"{path}: NSBonjourServices must equal [{expected_service!r}], "
f"got {services!r}",
file=sys.stderr,
)
failed = True
if failed:
raise SystemExit(1)
PY
}
if (($# > 0)); then
check_plists "$@"
printf 'macos-bundle-plist: PASS (%s plist file(s))\n' "$#"
exit 0
fi
check_plists "$ROOT/crates/op-host-desktop/Info.plist"
for bundle_script in scripts/bundle-macos.sh tools/bundle-macos.sh; do
require_regex \
"$ROOT/$bundle_script" \
'^[[:space:]]*(source|[.])[[:space:]]+.*macos-local-network-plist[.]sh' \
'must source the canonical local-network metadata'
require_regex \
"$ROOT/$bundle_script" \
'^[[:space:]]*openpencil_apply_macos_local_network_plist[[:space:]]+"\$PLIST"[[:space:]]*$' \
'must patch the final bundle Info.plist through the canonical helper'
require_regex \
"$ROOT/$bundle_script" \
'^[[:space:]]*bash[[:space:]]+".*check-macos-bundle-plist[.]sh"[[:space:]]+"\$PLIST"[[:space:]]*$' \
'must validate the final bundle Info.plist'
done
release_workflow="$ROOT/.github/workflows/rust-release.yml"
require_regex \
"$release_workflow" \
'^[[:space:]]*bash[[:space:]]+scripts/bundle-macos[.]sh[[:space:]]*$' \
'release workflow must use the production macOS bundle script'
require_regex \
"$release_workflow" \
'^[[:space:]]*bash[[:space:]]+tools/check-macos-bundle-plist[.]sh[[:space:]]+\\[[:space:]]*$' \
'release workflow must validate the assembled app Info.plist before notarization'
require_regex \
"$release_workflow" \
'^[[:space:]]*"target/\$\{\{ matrix[.]target \}\}/release/bundle/osx/OpenPencil[.]app/Contents/Info[.]plist"[[:space:]]*$' \
'release workflow must validate the exact app path it notarizes'
printf 'macos-bundle-plist: PASS (source metadata and release integration)\n'