diff --git a/building/macos/build-pkg.py b/building/macos/build-pkg.py index f4333eb..348fbce 100755 --- a/building/macos/build-pkg.py +++ b/building/macos/build-pkg.py @@ -381,6 +381,15 @@ def ensure_freerdp_libs() -> None: fail(f"Required FreeRDP library not found: {lib_path}") +def find_bundled_openh264(app_dir: Path) -> list[Path]: + """ + Our licence from Cisco covers downloading libopenh264, never redistributing it, so no + copy of it may ship inside the bundle. The postinstall script fetches it at install + time instead. This obligation runs until 2031. + """ + return [path for path in app_dir.rglob("*") if "openh264" in path.name.lower()] + + def validate_bundle_dependencies(app_dir: Path) -> bool: """ Validate that all binaries and dylibs inside the app bundle only depend on: @@ -563,6 +572,14 @@ def main() -> None: if not validate_bundle_dependencies(APP_DIR): fail("App bundle contains invalid dependencies") + print("==> Checking that no openh264 library ships inside the bundle") + bundled_openh264 = find_bundled_openh264(APP_DIR) + if bundled_openh264: + for path in bundled_openh264: + print(f" !! {path.relative_to(APP_DIR)}") + fail("Bundle ships libopenh264; we may only download it, never redistribute it") + print(">> No openh264 inside the bundle") + print("==> App bundle structure created successfully") print(f"Output path: {APP_DIR}") diff --git a/building/macos/scripts/postinstall b/building/macos/scripts/postinstall index 6f86fc5..dfd5162 100755 --- a/building/macos/scripts/postinstall +++ b/building/macos/scripts/postinstall @@ -1,63 +1,85 @@ #!/bin/bash set -e +# --- Configuration ----------------------------------------------------------- + +OPENH264_VERSION="2.6.0" +OPENH264_BASE_URL="https://ciscobinary.openh264.org" + +# Our licence covers downloading this library from Cisco, never redistributing it, so it +# must come from their site and carry their signature. The launcher runs with library +# validation disabled, so dyld no longer enforces that team id: this check is what is +# left guarding the file. +CISCO_TEAM_ID="DE8Y96K9QP" + TARGET_DIR="/Library/Application Support/UDSLauncher/openh264" -LOG_FILE="/tmp/udslauncher-postinstall.log" +# FreeRDP's load command asks for the soname, so the file has to land under that name. +TARGET_FILE="libopenh264.8.dylib" -touch /tmp/udslauncher-postinstall-ran 2>/dev/null +LOG_DIR="/var/log" +LOG_FILE="$LOG_DIR/udslauncher-install.log" + +# --- Helpers ----------------------------------------------------------------- log() { echo "$@" - echo "$@" >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE" +} + +# Failing to install the codec must not fail the installation: the launcher works +# without it, and the alternative is leaving the user with no client at all. +give_up() { + log "WARNING: $1" + log "OpenH264 was not installed. Webcam H264 will fall back to MJPEG." + log "=== UDSLauncher postinstall finished WITH WARNINGS at $(date) ===" + exit 0 } +download_url() { + case "$(uname -m)" in + arm64) echo "$OPENH264_BASE_URL/libopenh264-$OPENH264_VERSION-mac-arm64.dylib.bz2" ;; + *) echo "$OPENH264_BASE_URL/libopenh264-$OPENH264_VERSION-mac-x64.dylib.bz2" ;; + esac +} + +# --- Install ----------------------------------------------------------------- + log "=== UDSLauncher postinstall started at $(date) ===" -log "Running as: $(whoami)" +log "Running as $(whoami) on $(uname -m)" -log "Creating target directory: $TARGET_DIR" -if ! mkdir -p "$TARGET_DIR"; then - log "FATAL: Could not create $TARGET_DIR" - exit 1 -fi +mkdir -p "$TARGET_DIR" || give_up "could not create $TARGET_DIR" -ARCH=$(uname -m) -VERSION="2.6.0" +work_dir=$(mktemp -d) || give_up "could not create a temporary directory" +trap 'rm -rf "$work_dir"' EXIT -if [ "$ARCH" = "arm64" ]; then - URL="http://ciscobinary.openh264.org/libopenh264-${VERSION}-mac-arm64.dylib.bz2" -else - URL="http://ciscobinary.openh264.org/libopenh264-${VERSION}-mac-x64.dylib.bz2" -fi +archive="$work_dir/openh264.dylib.bz2" +url=$(download_url) -TEMP_FILE="/tmp/openh264.dylib.bz2" -DECOMPRESSED="/tmp/openh264.dylib" -TARGET_FILE="libopenh264.8.dylib" +log "Downloading OpenH264 from $url" +curl -L -s -f -o "$archive" "$url" || give_up "download failed" +log "Downloaded $(stat -f%z "$archive") bytes" + +bunzip2 -f "$archive" || give_up "bunzip2 failed" +dylib="$work_dir/openh264.dylib" +[ -f "$dylib" ] || give_up "bunzip2 produced no $dylib" -log "Architecture: $ARCH" -log "Downloading OpenH264 from $URL..." -if curl -L -s -f -o "$TEMP_FILE" "$URL"; then - log "Downloaded OK ($(stat -f%z "$TEMP_FILE") bytes)" - log "Decompressing OpenH264..." - if bunzip2 -f "$TEMP_FILE"; then - if mv "$DECOMPRESSED" "$TARGET_DIR/$TARGET_FILE"; then - chmod 644 "$TARGET_DIR/$TARGET_FILE" - xattr -d com.apple.quarantine "$TARGET_DIR/$TARGET_FILE" 2>/dev/null || true - log "OpenH264 installed successfully to $TARGET_DIR" - rm -f "$TEMP_FILE" "$DECOMPRESSED" - log "=== UDSLauncher postinstall finished OK at $(date) ===" - exit 0 - else - log "ERROR: Failed to move dylib to $TARGET_DIR" - fi - else - log "ERROR: bunzip2 failed" +# Reject anything not signed by Cisco before it reaches a system-wide directory. +# codesign ships with macOS, but never assume a tool is there on someone else's machine: +# without it we still install, because the target directory is root-owned and the old +# script verified nothing at all. +if command -v codesign > /dev/null 2>&1; then + team_id=$(codesign -dvvv "$dylib" 2>&1 | sed -n 's/^TeamIdentifier=//p') + if [ "$team_id" != "$CISCO_TEAM_ID" ]; then + give_up "downloaded library is signed by '${team_id:-nothing}', expected $CISCO_TEAM_ID" fi + log "Signature verified: TeamIdentifier=$team_id" else - log "ERROR: curl download failed" + log "WARNING: codesign not available, installing without verifying the signature" fi -rm -f "$TEMP_FILE" "$DECOMPRESSED" +install -m 644 "$dylib" "$TARGET_DIR/$TARGET_FILE" || give_up "could not install into $TARGET_DIR" +xattr -d com.apple.quarantine "$TARGET_DIR/$TARGET_FILE" 2>/dev/null || true -log "Warning: Failed to download or extract OpenH264. Fallback to MJPEG will be used." -log "=== UDSLauncher postinstall finished WITH WARNINGS at $(date) ===" +log "OpenH264 installed at $TARGET_DIR/$TARGET_FILE" +log "=== UDSLauncher postinstall finished OK at $(date) ===" exit 0 diff --git a/building/macos/scripts/postremove b/building/macos/scripts/postremove index 2add559..a6b7b02 100755 --- a/building/macos/scripts/postremove +++ b/building/macos/scripts/postremove @@ -1,31 +1,36 @@ #!/bin/bash set -e -TARGET_DIR="/Library/Application Support/UDSLauncher/openh264" -LOG_FILE="/tmp/udslauncher-postremove.log" +# --- Configuration ----------------------------------------------------------- + +SUPPORT_DIR="/Library/Application Support/UDSLauncher" +TARGET_DIR="$SUPPORT_DIR/openh264" +TARGET_FILE="libopenh264.8.dylib" + +LOG_DIR="/var/log" +LOG_FILE="$LOG_DIR/udslauncher-install.log" + +# --- Helpers ----------------------------------------------------------------- log() { echo "$@" - echo "$@" >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE" } +# --- Remove ------------------------------------------------------------------ + log "=== UDSLauncher postremove started at $(date) ===" -if [ -f "$TARGET_DIR/libopenh264.8.dylib" ]; then - rm -f "$TARGET_DIR/libopenh264.8.dylib" - log "Removed OpenH264 dylib from $TARGET_DIR" +if [ -f "$TARGET_DIR/$TARGET_FILE" ]; then + rm -f "$TARGET_DIR/$TARGET_FILE" + log "Removed $TARGET_DIR/$TARGET_FILE" else - log "No OpenH264 dylib found at $TARGET_DIR" + log "Nothing to remove at $TARGET_DIR/$TARGET_FILE" fi -if [ -d "$TARGET_DIR" ]; then - rmdir "$TARGET_DIR" 2>/dev/null || true -fi - -PARENT_DIR="/Library/Application Support/UDSLauncher" -if [ -d "$PARENT_DIR" ]; then - rmdir "$PARENT_DIR" 2>/dev/null || true -fi +# Only prune the directories we created, and only while they are empty. +rmdir "$TARGET_DIR" 2>/dev/null || true +rmdir "$SUPPORT_DIR" 2>/dev/null || true log "=== UDSLauncher postremove finished at $(date) ===" exit 0