#!/usr/bin/env bash set -euo pipefail # Build macOS distribution (self-contained venv + tarball) # Usage: ./build-dist-macos.sh [VERSION] # # Must be run on macOS (PyObjC wheels are platform-specific). For CI use # the github-hosted macos-latest runner. source "$(dirname "$0")/build-common.sh" if [ "$(uname -s)" != "Darwin" ]; then echo "ERROR: build-dist-macos.sh must run on macOS (uname=$(uname -s))" >&2 exit 1 fi detect_version "${1:-}" echo "Building Media Server v${VERSION_CLEAN} for macOS" # Detect host architecture for archive naming (arm64 = Apple Silicon, x86_64 = Intel). ARCH="$(uname -m)" DIST_DIR="dist/media-server" BUILD_OUTPUT="build/MediaServer-v${VERSION_CLEAN}-macos-${ARCH}" clean_dist "${DIST_DIR}" build verify_frontend # --- Create self-contained virtualenv --- echo "Creating virtualenv..." python3 -m venv "${DIST_DIR}/venv" # shellcheck disable=SC1091 source "${DIST_DIR}/venv/bin/activate" pip install --quiet --upgrade pip if ! pip install --quiet ".[macos]"; then echo "WARN: '.[macos]' extra unavailable; installing base deps only" >&2 pip install --quiet "." fi # Remove the installed package (app source is on PYTHONPATH via launcher) rm -rf "${DIST_DIR}"/venv/lib/python*/site-packages/media_server* rm -rf "${DIST_DIR}"/venv/lib/python*/site-packages/media_server*.dist-info deactivate # Trim venv site-packages — macOS native ext is .so, dylibs are .dylib MAC_SP=$(echo "${DIST_DIR}"/venv/lib/python*/site-packages) cleanup_site_packages "$MAC_SP" "so" "dylib" copy_app_files "$DIST_DIR" # --- Launcher --- cat > "${DIST_DIR}/media-server.sh" << 'LAUNCHER' #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" export PYTHONPATH="$SCRIPT_DIR/app" # shellcheck disable=SC1091 source "$SCRIPT_DIR/venv/bin/activate" exec python -m media_server.main "$@" LAUNCHER chmod +x "${DIST_DIR}/media-server.sh" # --- LaunchAgent installer --- # LaunchAgents run as the user, with the user's GUI session — exactly what # we want for AppleScript / Music.app / Spotify control. KeepAlive auto- # restarts on crash; RunAtLoad starts at login. cat > "${DIST_DIR}/install-launchagent.sh" << 'LAUNCHAGENT' #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" LABEL="com.dolgolyov.media-server" PLIST_DIR="${HOME}/Library/LaunchAgents" PLIST="${PLIST_DIR}/${LABEL}.plist" LOG_DIR="${HOME}/Library/Logs/media-server" mkdir -p "$PLIST_DIR" "$LOG_DIR" cat > "$PLIST" << EOF Label ${LABEL} ProgramArguments ${SCRIPT_DIR}/media-server.sh --no-tray WorkingDirectory ${SCRIPT_DIR} RunAtLoad KeepAlive SuccessfulExit StandardOutPath ${LOG_DIR}/stdout.log StandardErrorPath ${LOG_DIR}/stderr.log EnvironmentVariables PYTHONUNBUFFERED 1 EOF # Reload if already loaded, then load fresh. launchctl unload "$PLIST" 2>/dev/null || true launchctl load -w "$PLIST" echo "LaunchAgent '${LABEL}' installed and started." echo "Plist: $PLIST" echo "Logs: $LOG_DIR/{stdout,stderr}.log" echo "Stop: launchctl unload \"$PLIST\"" echo "Start: launchctl load -w \"$PLIST\"" LAUNCHAGENT chmod +x "${DIST_DIR}/install-launchagent.sh" # Convenience uninstaller. cat > "${DIST_DIR}/uninstall-launchagent.sh" << 'UNINSTALL' #!/usr/bin/env bash set -euo pipefail LABEL="com.dolgolyov.media-server" PLIST="${HOME}/Library/LaunchAgents/${LABEL}.plist" launchctl unload "$PLIST" 2>/dev/null || true rm -f "$PLIST" echo "LaunchAgent '${LABEL}' removed (config preserved under ~/.config/media-server)." UNINSTALL chmod +x "${DIST_DIR}/uninstall-launchagent.sh" # --- Package --- echo "Creating archive..." cp -r "${DIST_DIR}" "${BUILD_OUTPUT}" tar -czf "${BUILD_OUTPUT}.tar.gz" -C build "MediaServer-v${VERSION_CLEAN}-macos-${ARCH}" echo "Build complete: ${BUILD_OUTPUT}.tar.gz"