Compare commits
3 Commits
e824a8e8eb
...
2fbf3bf859
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fbf3bf859 | |||
| 1e14bf72a2 | |||
| daf9751ccc |
@@ -15,7 +15,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
simulator:
|
simulator:
|
||||||
runs-on: macos-15
|
runs-on: xcode-27
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -24,6 +24,7 @@ jobs:
|
|||||||
- name: Inspect toolchain and project
|
- name: Inspect toolchain and project
|
||||||
run: |
|
run: |
|
||||||
xcodebuild -version
|
xcodebuild -version
|
||||||
|
xcodebuild -version | head -n 1 | grep -E "^Xcode 27([.]|$)"
|
||||||
swift --version
|
swift --version
|
||||||
python3 tools/validate_project.py
|
python3 tools/validate_project.py
|
||||||
xcodebuild -list -project MusicBridge.xcodeproj
|
xcodebuild -list -project MusicBridge.xcodeproj
|
||||||
@@ -36,7 +37,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
python3 -u tools/tls_fixture.py > build/tls-fixture.log 2>&1 &
|
python3 -m venv build/test-tools
|
||||||
|
build/test-tools/bin/pip install cryptography==46.0.3
|
||||||
|
build/test-tools/bin/python -u tools/tls_fixture.py > build/tls-fixture.log 2>&1 &
|
||||||
TLS_PID=$!
|
TLS_PID=$!
|
||||||
trap 'kill "$TLS_PID" 2>/dev/null || true' EXIT
|
trap 'kill "$TLS_PID" 2>/dev/null || true' EXIT
|
||||||
for attempt in {1..30}; do
|
for attempt in {1..30}; do
|
||||||
@@ -94,7 +97,7 @@ jobs:
|
|||||||
sleep 1
|
sleep 1
|
||||||
xcrun simctl io "$SIMULATOR_ID" screenshot build/live-card-light-screen.png
|
xcrun simctl io "$SIMULATOR_ID" screenshot build/live-card-light-screen.png
|
||||||
RUNTIME=$(python3 -c 'import json,os; d=json.load(open("/tmp/musicbridge-simulators.json")); print(next(k for k,v in d["devices"].items() if any(x["udid"]==os.environ["SIMULATOR_ID"] for x in v)))')
|
RUNTIME=$(python3 -c 'import json,os; d=json.load(open("/tmp/musicbridge-simulators.json")); print(next(k for k,v in d["devices"].items() if any(x["udid"]==os.environ["SIMULATOR_ID"] for x in v)))')
|
||||||
COMPACT_ID=$(xcrun simctl create MusicBridge-Compact com.apple.CoreSimulator.SimDeviceType.iPhone-SE-3rd-generation "$RUNTIME")
|
COMPACT_ID=$(python3 -c 'import json,os; d=json.load(open("/tmp/musicbridge-simulators.json")); v=next(v for v in d["devices"].values() if any(x["udid"]==os.environ["SIMULATOR_ID"] for x in v)); candidates=[x for x in v if x.get("isAvailable") and x.get("name", "").startswith("iPhone") and x["udid"]!=os.environ["SIMULATOR_ID"]]; print(min(candidates,key=lambda x:("Max" in x["name"] or "Plus" in x["name"], "Pro" in x["name"], x["name"]))["udid"])')
|
||||||
xcrun simctl bootstatus "$COMPACT_ID" -b
|
xcrun simctl bootstatus "$COMPACT_ID" -b
|
||||||
xcrun simctl install "$COMPACT_ID" DerivedData/Build/Products/Debug-iphonesimulator/MusicBridge.app
|
xcrun simctl install "$COMPACT_ID" DerivedData/Build/Products/Debug-iphonesimulator/MusicBridge.app
|
||||||
xcrun simctl launch "$COMPACT_ID" ru.yukinoki.musicbridge --preview-player
|
xcrun simctl launch "$COMPACT_ID" ru.yukinoki.musicbridge --preview-player
|
||||||
|
|||||||
@@ -4,7 +4,31 @@
|
|||||||
Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios.
|
Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios.
|
||||||
Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`.
|
Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`.
|
||||||
|
|
||||||
## Правила проекта
|
## Инфраструктура — текущее состояние
|
||||||
|
|
||||||
|
- SSH к macOS проверен: `aresov@10.67.67.28`, macOS 26.6.2, x86_64,
|
||||||
|
около 459 ГБ свободно. Xcode не установлен. Локальный отдельный ключ:
|
||||||
|
`C:/Users/areso/.ssh/musicbridge_macos_ed25519` (закрытый ключ не включать в репо).
|
||||||
|
Использовать `C:/Windows/System32/OpenSSH/ssh.exe` с `-F NUL`,
|
||||||
|
`-o ProxyCommand=none -o ProxyJump=none -o IdentitiesOnly=yes` и этим ключом;
|
||||||
|
другая установленная версия ssh не подключалась. Host key сохранён в known_hosts.
|
||||||
|
- Xcode 27 требует Apple Silicon; эта Intel-виртуалка не подходит для SDK 27.
|
||||||
|
CI переведён на GitHub-hosted `xcode-27`: Xcode 27.0 (27A5252f), Swift 6.4, SDK iOS 27.0.
|
||||||
|
[Actions 34764552877](https://github.com/polskikh13/musicbridgeios/actions/runs/34764552877)
|
||||||
|
для `1e14bf7`: 32 XCTest, 0 отказов, успешный Release archive.
|
||||||
|
IPA: `build/iPhone-0.6.0-xcode27/MusicBridge-iPhone.ipa`; проверены SDK 27, ARM64
|
||||||
|
приложения/расширения и структура пакета. Предпросмотр карточки просмотрен.
|
||||||
|
Версия осталась 0.6.0 (7); новые функции в пересборке не добавлены.
|
||||||
|
Просроченный тестовый сертификат создаётся с явными датами через
|
||||||
|
cryptography==46.0.3 в отдельном venv, поскольку новый OpenSSL отвергает -days -1.
|
||||||
|
- Пользователь подтвердил бесплатный Apple Account через iloader.
|
||||||
|
Фоновая доставка APNs заблокирована отсутствием Push Notifications capability
|
||||||
|
и соответствующей подписи. Платная Developer Program, App ID, ключ APNs и
|
||||||
|
provisioning profile пока отсутствуют; не обещать работу push после простой
|
||||||
|
смены Xcode. После появления доступа нужны регистрация токенов, отправитель
|
||||||
|
обновлений с Windows/сервера и тестирование доставки на физическом iPhone.
|
||||||
|
|
||||||
|
## Правила реализации
|
||||||
|
|
||||||
- SwiftUI, без веб-клиента. Музыка играет на Windows.
|
- SwiftUI, без веб-клиента. Музыка играет на Windows.
|
||||||
- Сверять контракт с Windows PROTOCOL.md; не менять серверный протокол молча.
|
- Сверять контракт с Windows PROTOCOL.md; не менять серверный протокол молча.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Windows-агентом и подключение по QR. В 0.4.0 добавл
|
|||||||
|
|
||||||
## Сборка на Mac
|
## Сборка на Mac
|
||||||
|
|
||||||
1. Открыть `MusicBridge.xcodeproj` в Xcode 16 или новее.
|
1. Открыть `MusicBridge.xcodeproj` в Xcode 27.
|
||||||
2. Выбрать схему MusicBridge и симулятор iPhone. Run запускает приложение,
|
2. Выбрать схему MusicBridge и симулятор iPhone. Run запускает приложение,
|
||||||
Test выполняет `MusicBridgeTests`.
|
Test выполняет `MusicBridgeTests`.
|
||||||
3. Для физического iPhone выбрать свою Team в Signing & Capabilities. Сертификаты
|
3. Для физического iPhone выбрать свою Team в Signing & Capabilities. Сертификаты
|
||||||
@@ -38,7 +38,7 @@ Windows-агентом и подключение по QR. В 0.4.0 добавл
|
|||||||
|
|
||||||
## GitHub Actions
|
## GitHub Actions
|
||||||
|
|
||||||
`.github/workflows/ios.yml` собирает приложение и выполняет XCTest на `macos-15`
|
`.github/workflows/ios.yml` собирает приложение и выполняет XCTest на `xcode-27`
|
||||||
при push в main, pull request или ручном запуске. Сохраняет лог, `.xcresult`
|
при push в main, pull request или ручном запуске. Сохраняет лог, `.xcresult`
|
||||||
и ZIP приложения **для симулятора**, который нельзя установить на физический iPhone.
|
и ZIP приложения **для симулятора**, который нельзя установить на физический iPhone.
|
||||||
Apple ID и сертификаты для этой проверки не требуются. GitHub remote подключён:
|
Apple ID и сертификаты для этой проверки не требуются. GitHub remote подключён:
|
||||||
@@ -69,7 +69,7 @@ Apple ID и сертификаты для этой проверки не тре
|
|||||||
|
|
||||||
Пользователь также подтвердил установку через iloader. Для повторной установки
|
Пользователь также подтвердил установку через iloader. Для повторной установки
|
||||||
с Live Activity используйте версию **0.6.0 (build 7)**:
|
с Live Activity используйте версию **0.6.0 (build 7)**:
|
||||||
`build/iPhone-0.6.0/MusicBridge-iPhone.ipa`. Выберите этот файл в iloader и
|
`build/iPhone-0.6.0-xcode27/MusicBridge-iPhone.ipa`. Выберите этот файл в iloader и
|
||||||
подпишите тем же Apple Account, которым установлена первая версия.
|
подпишите тем же Apple Account, которым установлена первая версия.
|
||||||
|
|
||||||
В 0.4.0 добавлены иконка, карточка QR, раскрываемые ручные поля, крупная
|
В 0.4.0 добавлены иконка, карточка QR, раскрываемые ручные поля, крупная
|
||||||
@@ -141,8 +141,7 @@ play/pause, следующий трек и обновление. Компакт
|
|||||||
|
|
||||||
Apple предлагает `RemoteMediaSession` в новом NowPlaying framework (iOS 27+)
|
Apple предлагает `RemoteMediaSession` в новом NowPlaying framework (iOS 27+)
|
||||||
для отображения музыки внешнего устройства на экране блокировки и в Control Center.
|
для отображения музыки внешнего устройства на экране блокировки и в Control Center.
|
||||||
Это подходит архитектуре MusicBridge. Нужны SDK/Xcode 27 и отдельное расширение
|
Это подходит архитектуре MusicBridge. Нужно отдельное расширение `com.apple.nowplaying.remote-media`. Сборка переведена на Xcode 27; 32 теста и Release archive прошли.
|
||||||
`com.apple.nowplaying.remote-media`; текущая сборка использует Xcode 16.4.
|
|
||||||
Эта интеграция исследована, но ещё не реализована. Для фоновых обновлений удалённых
|
Эта интеграция исследована, но ещё не реализована. Для фоновых обновлений удалённых
|
||||||
сессий Apple также предоставляет push-механизм.
|
сессий Apple также предоставляет push-механизм.
|
||||||
[Документация remote media sessions](https://developer.apple.com/documentation/nowplaying/publishing-remote-media-sessions).
|
[Документация remote media sessions](https://developer.apple.com/documentation/nowplaying/publishing-remote-media-sessions).
|
||||||
@@ -155,3 +154,18 @@ Apple предлагает `RemoteMediaSession` в новом NowPlaying framewo
|
|||||||
- [GitHub: лимиты и оплата Actions](https://docs.github.com/en/billing/concepts/product-billing/github-actions)
|
- [GitHub: лимиты и оплата Actions](https://docs.github.com/en/billing/concepts/product-billing/github-actions)
|
||||||
|
|
||||||
План и ограничения — в `PLAN.md`. Копия протокола агента — в `PROTOCOL.md`.
|
План и ограничения — в `PLAN.md`. Копия протокола агента — в `PROTOCOL.md`.
|
||||||
|
|
||||||
|
## Сервер сборки и фоновые обновления
|
||||||
|
|
||||||
|
GitHub Actions использует Apple Silicon runner `xcode-27` с проверкой версии Xcode.
|
||||||
|
SSH к локальной macOS-виртуалке настроен, однако она x86_64, без Xcode и не может
|
||||||
|
запускать Xcode 27. Для SDK 27 используется GitHub Actions.
|
||||||
|
|
||||||
|
Тестовый TLS-сервер требует `cryptography==46.0.3`; CI устанавливает пакет
|
||||||
|
в отдельное Python-окружение. Это зависимость тестов, не приложения.
|
||||||
|
|
||||||
|
Пользователь использует бесплатный Apple Account. APNs требует Push Notifications
|
||||||
|
capability и подписи с соответствующим provisioning profile, недоступных Personal Team.
|
||||||
|
Поэтому автоматическая фоновая доставка ещё не подключена. Команды Live Activity
|
||||||
|
выполняют запросы при нажатии; смена трека на ПК при приостановленном приложении
|
||||||
|
не обновляет карточку без запроса или push.
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import subprocess
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
|
import datetime
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
|
||||||
root = pathlib.Path(__file__).resolve().parents[1]
|
root = pathlib.Path(__file__).resolve().parents[1]
|
||||||
work = root / "build/tls"
|
work = root / "build/tls"
|
||||||
@@ -167,6 +170,18 @@ for name, days in (("valid", "1825"), ("expired", "-1")):
|
|||||||
key, csr, cert = (work / (name + suffix) for suffix in (".key", ".csr", ".pem"))
|
key, csr, cert = (work / (name + suffix) for suffix in (".key", ".csr", ".pem"))
|
||||||
subprocess.run(["openssl", "req", "-new", "-newkey", "rsa:2048", "-nodes", "-config", str(config),
|
subprocess.run(["openssl", "req", "-new", "-newkey", "rsa:2048", "-nodes", "-config", str(config),
|
||||||
"-keyout", str(key), "-out", str(csr)], check=True, capture_output=True, timeout=15)
|
"-keyout", str(key), "-out", str(csr)], check=True, capture_output=True, timeout=15)
|
||||||
|
if name == "expired":
|
||||||
|
private_key = serialization.load_pem_private_key(key.read_bytes(), password=None)
|
||||||
|
template = x509.load_pem_x509_certificate((work / "valid.pem").read_bytes())
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
builder = (x509.CertificateBuilder().subject_name(template.subject).issuer_name(template.issuer)
|
||||||
|
.public_key(private_key.public_key()).serial_number(x509.random_serial_number())
|
||||||
|
.not_valid_before(now - datetime.timedelta(days=2))
|
||||||
|
.not_valid_after(now - datetime.timedelta(days=1)))
|
||||||
|
for extension in template.extensions:
|
||||||
|
builder = builder.add_extension(extension.value, extension.critical)
|
||||||
|
cert.write_bytes(builder.sign(private_key, hashes.SHA256()).public_bytes(serialization.Encoding.PEM))
|
||||||
|
else:
|
||||||
subprocess.run(["openssl", "x509", "-req", "-in", str(csr), "-signkey", str(key), "-sha256",
|
subprocess.run(["openssl", "x509", "-req", "-in", str(csr), "-signkey", str(key), "-sha256",
|
||||||
"-days", days, "-extfile", str(config), "-extensions", "ext", "-out", str(cert)],
|
"-days", days, "-extfile", str(config), "-extensions", "ext", "-out", str(cert)],
|
||||||
check=True, capture_output=True, timeout=15)
|
check=True, capture_output=True, timeout=15)
|
||||||
|
|||||||
Reference in New Issue
Block a user