Files
media-player-server/build-dist-macos.sh
alexei.dolgolyov ddf4a6cb29
Lint & Test / test (push) Successful in 20s
Lint & Test / linux-smoke (push) Failing after 34s
feat: production-ready Linux & macOS support
- Add `linux` (dbus-python, PyGObject, python-xlib) and `macos`
  (pyobjc) extras to pyproject.toml with sys_platform markers; move
  cross-platform screen-brightness-control + monitorcontrol to base deps.
- build-dist-linux.sh: install `.[linux]`, pkg-config pre-flight for
  dbus-1/glib-2.0, emit a systemd unit with DBUS_SESSION_BUS_ADDRESS +
  XDG_RUNTIME_DIR + ReadWritePaths for ~/.config and ~/.cache so MPRIS
  works and audit-log / thumbnail writes aren't blocked by ProtectHome.
- New build-dist-macos.sh + per-user LaunchAgent installer producing
  MediaServer-vX.Y-macos-{arm64,x86_64}.tar.gz.
- Templated media-server.service updated to match the dist layout with
  proper session-bus env vars and a writable state-dir grant.
- install_linux.sh: drop dead requirements.txt path; install via
  `pip install ".[linux]"` and pre-create the writable state dirs.
- Cross-platform album artwork: abstract MediaController.get_album_art()
  with Linux (mpris:artUrl, file:// + http(s)://) and macOS (Spotify URL)
  impls; routes/media artwork endpoint now awaits the controller.
- LinuxMediaController connects to the session bus lazily — failure no
  longer crashes lifespan startup; MPRIS calls return idle until the bus
  is reachable. Logged once at INFO with a hint about
  `loginctl enable-linger`.
- Startup preflight on Linux warns if DBUS_SESSION_BUS_ADDRESS or
  XDG_RUNTIME_DIR is unset and informs the user when Wayland disables
  the foreground probe.
- /api/media/visualizer/status now reports a per-OS unavailable_reason.
- tray._confirm guarded against ctypes.windll on non-Windows.
- config.example.yaml: per-OS commented script examples; on_turn_off
  default is now a no-op echo (used to silently fail off Windows).
- README: replace stale `pip install -r requirements.txt` instructions
  with the new extras; add systemd lingering doc + troubleshooting
  section; add macOS LaunchAgent section.
- CI: new linux-smoke job (installs `.[linux]`, boots the server under
  dbus-run-session, asserts /api/health). Release workflow gains
  apt-deps step for the Linux build and a best-effort macOS build job.
2026-05-26 12:17:30 +03:00

143 lines
4.3 KiB
Bash

#!/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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>${SCRIPT_DIR}/media-server.sh</string>
<string>--no-tray</string>
</array>
<key>WorkingDirectory</key>
<string>${SCRIPT_DIR}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>${LOG_DIR}/stdout.log</string>
<key>StandardErrorPath</key>
<string>${LOG_DIR}/stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PYTHONUNBUFFERED</key>
<string>1</string>
</dict>
</dict>
</plist>
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"