Compare commits
5 Commits
91e305ada5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f90c6a912 | |||
| a3133465f9 | |||
| b14162b401 | |||
| 63fa120194 | |||
| 4748363905 |
@@ -0,0 +1,64 @@
|
||||
name: Build MusicBridge compatible iloader
|
||||
on:
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nab138/iloader
|
||||
ref: 348eefd7de78e9bc612c9d619b8b1e7a80ba3ba0
|
||||
path: build/compat/iloader
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nab138/isideload
|
||||
ref: f6a4d5dba717d72fc2af63eaba26b27ba44116be
|
||||
path: build/compat/isideload
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nab138/apple-crates
|
||||
ref: 5da0b8df9b202434b1eff2700059b1ee142592ac
|
||||
path: build/compat/apple-crates
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Install native build prerequisites
|
||||
run: choco install strawberryperl make --no-progress -y
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- name: Patch pinned sources
|
||||
run: python tools/iloader/prepare.py build/compat
|
||||
- name: Install frontend dependencies
|
||||
working-directory: build/compat/iloader
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Test extension discovery, bundle IDs and signing permissions
|
||||
working-directory: build/compat/iloader/src-tauri
|
||||
run: |
|
||||
cargo test -p isideload --lib musicbridge
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
cargo test -p apple-codesign --lib musicbridge
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
- name: Build portable GUI
|
||||
working-directory: build/compat/iloader
|
||||
run: bunx tauri build --no-bundle --config src-tauri/ci.conf.json
|
||||
- name: Package helper with provenance
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force build/iloader-musicbridge
|
||||
Copy-Item build/compat/iloader/src-tauri/target/release/iloader.exe build/iloader-musicbridge/iloader-musicbridge.exe
|
||||
Copy-Item tools/iloader/README.md build/iloader-musicbridge/README.md
|
||||
Get-ChildItem build/compat/iloader -Filter 'LICENSE*' | Copy-Item -Destination build/iloader-musicbridge
|
||||
Get-FileHash build/iloader-musicbridge/iloader-musicbridge.exe | Format-List | Out-File build/iloader-musicbridge/SHA256.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: iloader-musicbridge-windows
|
||||
path: build/iloader-musicbridge
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -13,10 +13,19 @@ Windows-агент — соседний проект `C:/Users/areso/MusicBridge
|
||||
- [x] Токен хранится в общем Keychain, не передаётся в атрибутах системной сессии.
|
||||
- [x] Перемотка переводится из позиции относительно трека в диапазон Windows API.
|
||||
- [x] Выключение и удаление сопряжения удаляют доступ расширения; возврат запоздавшего restore не восстанавливает сессию.
|
||||
- [ ] Успешная итоговая сборка и XCTest на Xcode 27; первая попытка 34783380221 выявила Double/Float и исправлена.
|
||||
- [x] Итоговый `91e305a`: [Actions 34785082289](https://github.com/polskikh13/musicbridgeios/actions/runs/34785082289), Xcode 27, 36 XCTest без отказов, успешный Release archive. Первая попытка 34783380221 выявила Double/Float и исправлена.
|
||||
- [ ] Установка с подписью обоих расширений и общим Keychain; проверка на физическом iPhone.
|
||||
- [ ] Пункт управления: кнопки, перемотка, громкость; блокировка телефона, сон ПК, отзыв токена, повторная подпись.
|
||||
|
||||
IPA скачана в `build/iPhone-0.7.0/MusicBridge-iPhone.ipa` (без подписи).
|
||||
Локально проверены ZIP, ARM64 iPhoneOS всех трёх исполняемых файлов,
|
||||
совпадение версий, ExtensionKit extension point и общий Keychain suffix.
|
||||
SHA-256: `a84e146a5259c0c7250dab829f2eb07093f33d5e212ff1e1880f6c3fd29d65ee`.
|
||||
Логи тестов находятся в `build/iPhone-0.7.0/tests`, снимки — в `preview`.
|
||||
Просмотрены светлый плеер и дополнительный iPhone: кнопка пульта видна снизу,
|
||||
пояснения доступны прокруткой. Системная карточка NowPlaying на этих снимках
|
||||
не проверялась: для запуска нужен подписанный общий Keychain.
|
||||
|
||||
**Ограничение iloader:** проверен официальный исходник `nab138/isideload`, ветка
|
||||
`apple-codesign-quick`, commit `f6a4d5dba717d72fc2af63eaba26b27ba44116be`, который использует текущий iloader.
|
||||
`isideload/src/sideload/bundle.rs` обнаруживает расширения только в `PlugIns`,
|
||||
@@ -29,6 +38,21 @@ ExtensionKit и общего Keychain с реальными provisioning profile
|
||||
всех трёх targets — вариант при наличии совместимого Mac и профилей; разрешение
|
||||
нового extension point бесплатной командой ещё не подтверждено на устройстве.
|
||||
|
||||
2026-09-14 пользователь попробовал установку 0.7.0: IXErrorDomain 2/8,
|
||||
родитель стал `ru.yukinoki.musicbridge.<TeamID>`, а remote остался
|
||||
`ru.yukinoki.musicbridge.remote`. Это подтверждает пропуск Extensions при переименовании.
|
||||
Дополнительно библиотека apple-codesign-quick также обходила только Frameworks/PlugIns.
|
||||
В `tools/iloader` добавлены патчи обоих уровней и воспроизводимая подготовка
|
||||
закреплённых upstream revisions. Отдельный ручной workflow `iloader-compat.yml`
|
||||
собирает portable Windows GUI; исходное приложение iloader на ПК не заменяется.
|
||||
Патч сохраняет публичные Bundle ID приложения до подписи, затем iloader сам
|
||||
применяет TeamID ко всем расширениям и получает для них настоящие Apple profiles.
|
||||
Shared Keychain для MusicBridge добавляется только при разрешении в profile.
|
||||
- [x] Проверено чистое применение патчей к закреплённым исходникам.
|
||||
- [ ] Rust-тесты на переименование/обнаружение ExtensionKit, вложенную подпись и Keychain.
|
||||
- [ ] Готовый portable EXE и повторная установка исходной IPA 0.7.0 через него.
|
||||
- [ ] Подтвердить регистрацию NowPlaying и общий Keychain на iPhone.
|
||||
|
||||
Без APNs расширение читает ПК при команде; открытое приложение обновляет сессию
|
||||
при смене трека/статуса и периодически. Смена трека на самом ПК при приостановленном
|
||||
приложении не гарантирует автоматического обновления карточки. Фиктивное аудио не используется.
|
||||
|
||||
@@ -27,6 +27,9 @@ Windows-агентом и подключение по QR. В 0.4.0 добавл
|
||||
«Включить системный пульт iOS», затем открыть Пункт управления. Реализованы
|
||||
play/pause, следующий/предыдущий трек, перемотка и громкость ПК. Звук остаётся на ПК.
|
||||
Проверка этой функции на физическом iPhone пока не завершена.
|
||||
Код `91e305a` прошёл [Actions](https://github.com/polskikh13/musicbridgeios/actions/runs/34785082289):
|
||||
36 XCTest без отказов и Release archive. Неподписанная IPA:
|
||||
`build/iPhone-0.7.0/MusicBridge-iPhone.ipa`.
|
||||
|
||||
Для этой версии нужно подписать **MusicBridge, MusicBridgeWidgets и MusicBridgeRemote**.
|
||||
У приложения и MusicBridgeRemote должны совпадать разрешённые Keychain Sharing
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# iloader: совместимость с MusicBridge 0.7.0
|
||||
|
||||
Это отдельная неофициальная portable-сборка iloader для Windows. Исходники:
|
||||
- https://github.com/nab138/iloader/tree/348eefd7de78e9bc612c9d619b8b1e7a80ba3ba0
|
||||
- https://github.com/nab138/isideload/tree/f6a4d5dba717d72fc2af63eaba26b27ba44116be
|
||||
- https://github.com/nab138/apple-crates/tree/5da0b8df9b202434b1eff2700059b1ee142592ac
|
||||
|
||||
Патчи хранятся в репозитории MusicBridge в tools/iloader. Workflow
|
||||
iloader-compat.yml запускается вручную, не использует Apple Account/сертификаты.
|
||||
Apple-авторизация и подпись происходят только локально на компьютере пользователя.
|
||||
|
||||
Исправления: обнаружение Extensions вместе с PlugIns, переименование и получение
|
||||
профиля каждого расширения, вложенная подпись ExtensionKit и общий Keychain для
|
||||
MusicBridge только в пределах разрешений настоящего provisioning profile Apple.
|
||||
Регрессионные Rust-тесты проверяют обе папки расширений, сохранение новых Bundle ID,
|
||||
обработку вложенной подписи и отказ при недопустимой Keychain group.
|
||||
|
||||
Использование после успешной сборки:
|
||||
1. Закрыть обычный iloader. Удалять его и установленный MusicBridge не требуется.
|
||||
2. Запустить iloader-musicbridge.exe. Заголовок окна содержит compatibility build.
|
||||
3. Использовать прежний Apple Account; выбрать исходную MusicBridge 0.7.0 IPA.
|
||||
4. Подписать и установить заново. Все расширения нужно сохранить.
|
||||
5. Проверить обычное подключение, затем кнопку системного пульта и Пункт управления.
|
||||
|
||||
Вариант сохраняет идентификатор/хранилище обычного iloader для локального входа,
|
||||
поэтому одновременно запускать обе версии не нужно. Обновления из upstream в
|
||||
этой сборке отключены. Совместимость нового extension point с бесплатным профилем
|
||||
Apple окончательно подтверждается только установкой на устройстве.
|
||||
@@ -0,0 +1,46 @@
|
||||
diff --git a/apple-codesign-quick/src/bundle.rs b/apple-codesign-quick/src/bundle.rs
|
||||
index 040fd07..2bfa359 100644
|
||||
--- a/apple-codesign-quick/src/bundle.rs
|
||||
+++ b/apple-codesign-quick/src/bundle.rs
|
||||
@@ -16,6 +16,23 @@ const FAIRPLAY_DIR: &str = "SC_Info";
|
||||
const CODE_SIGNATURE_DIR: &str = "_CodeSignature";
|
||||
const CODE_RESOURCES_FILE: &str = "CodeResources";
|
||||
|
||||
+#[cfg(test)]
|
||||
+mod musicbridge_extensionkit_tests {
|
||||
+ use super::*;
|
||||
+ #[test]
|
||||
+ fn musicbridge_extensionkit_is_signed_as_nested_bundle() {
|
||||
+ let unique = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
|
||||
+ let root = std::env::temp_dir().join(format!("musicbridge-codesign-test-{unique}"));
|
||||
+ let extension = root.join("Extensions/Remote.appex");
|
||||
+ std::fs::create_dir_all(&extension).unwrap();
|
||||
+ std::fs::write(extension.join("Info.plist"), b"fixture").unwrap();
|
||||
+ let bundle = Bundle { path: root, app_info: Dictionary::new() };
|
||||
+ assert_eq!(bundle.sub_bundles().unwrap(), vec![extension]);
|
||||
+ assert!(!should_hash_resource("Extensions/Remote.appex/Remote", "Main"));
|
||||
+ assert!(should_hash_resource("Assets.car", "Main"));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
pub struct BundleSigningSettings<'a> {
|
||||
pub team_id: String,
|
||||
pub main_entitlements: Dictionary,
|
||||
@@ -202,7 +219,7 @@ impl Bundle {
|
||||
|
||||
fn sub_bundles(&self) -> Result<Vec<PathBuf>> {
|
||||
let mut bundles = Vec::new();
|
||||
- for folder in ["Frameworks", "PlugIns"] {
|
||||
+ for folder in ["Frameworks", "PlugIns", "Extensions"] {
|
||||
let dir = self.path.join(folder);
|
||||
#[cfg(feature = "wasm")]
|
||||
if !isideload_vfs::fs::metadata(&dir)
|
||||
@@ -463,7 +480,7 @@ fn should_hash_resource(relative: &str, executable: &str) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
- for nested_root in ["Frameworks/", "PlugIns/"] {
|
||||
+ for nested_root in ["Frameworks/", "PlugIns/", "Extensions/"] {
|
||||
if let Some(rest) = relative.strip_prefix(nested_root)
|
||||
&& rest.contains('/')
|
||||
{
|
||||
@@ -0,0 +1,159 @@
|
||||
diff --git a/isideload/src/sideload/application.rs b/isideload/src/sideload/application.rs
|
||||
index 4e0ccd0..be80af4 100644
|
||||
--- a/isideload/src/sideload/application.rs
|
||||
+++ b/isideload/src/sideload/application.rs
|
||||
@@ -20,6 +20,33 @@ pub struct Application {
|
||||
//pub temp_path: PathBuf,
|
||||
}
|
||||
|
||||
+#[cfg(test)]
|
||||
+mod musicbridge_tests {
|
||||
+ use super::*;
|
||||
+ #[test]
|
||||
+ fn musicbridge_extensionkit_is_renamed_and_persisted() {
|
||||
+ let root = std::env::temp_dir().join(format!("musicbridge-sign-test-{}", uuid::Uuid::new_v4()));
|
||||
+ for (directory, id) in [("", "ru.yukinoki.musicbridge"),
|
||||
+ ("PlugIns/Widget.appex", "ru.yukinoki.musicbridge.widgets"),
|
||||
+ ("Extensions/Remote.appex", "ru.yukinoki.musicbridge.remote")] {
|
||||
+ let path = root.join(directory);
|
||||
+ std::fs::create_dir_all(&path).unwrap();
|
||||
+ let mut info = plist::Dictionary::new();
|
||||
+ info.insert("CFBundleIdentifier".into(), id.into());
|
||||
+ plist::to_file_xml(path.join("Info.plist"), &info).unwrap();
|
||||
+ }
|
||||
+ let mut app = Application::new(root.clone()).unwrap();
|
||||
+ assert_eq!(app.bundle.app_extensions().len(), 2);
|
||||
+ app.update_bundle_id("ru.yukinoki.musicbridge", "ru.yukinoki.musicbridge.TEAM").unwrap();
|
||||
+ app.bundle.write_info().unwrap();
|
||||
+ for extension in app.bundle.app_extensions_mut() { extension.write_info().unwrap(); }
|
||||
+ let saved = Application::new(root).unwrap();
|
||||
+ for extension in saved.bundle.app_extensions() {
|
||||
+ assert!(extension.bundle_identifier().unwrap().starts_with("ru.yukinoki.musicbridge.TEAM."));
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
impl Application {
|
||||
pub fn new(path: PathBuf) -> Result<Self, Report> {
|
||||
if !isideload_vfs::fs::metadata(&path).is_ok() {
|
||||
diff --git a/isideload/src/sideload/bundle.rs b/isideload/src/sideload/bundle.rs
|
||||
index 67a947d..2cc2282 100644
|
||||
--- a/isideload/src/sideload/bundle.rs
|
||||
+++ b/isideload/src/sideload/bundle.rs
|
||||
@@ -45,23 +45,19 @@ impl Bundle {
|
||||
"Failed to parse Info.plist".to_string(),
|
||||
))?;
|
||||
|
||||
- // Load app extensions from PlugIns directory
|
||||
- let plug_ins_dir = bundle_path.join("PlugIns");
|
||||
- let app_extensions = if isideload_vfs::fs::metadata(&plug_ins_dir).is_ok() {
|
||||
- isideload_vfs::fs::read_dir(&plug_ins_dir)
|
||||
- .context(SideloadError::InvalidBundle(
|
||||
- "Failed to read PlugIns directory".to_string(),
|
||||
- ))?
|
||||
- .filter_map(|entry| entry.ok())
|
||||
- .filter(|entry| {
|
||||
- entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false)
|
||||
- && isideload_vfs::fs::metadata(&entry.path().join("Info.plist")).is_ok()
|
||||
- })
|
||||
- .filter_map(|entry| Bundle::new(entry.path()).ok())
|
||||
- .collect()
|
||||
- } else {
|
||||
- Vec::new()
|
||||
- };
|
||||
+ // ExtensionKit uses Extensions; WidgetKit and older extensions use PlugIns.
|
||||
+ // Invalid nested bundles must fail signing, never be silently skipped.
|
||||
+ let mut app_extensions = Vec::new();
|
||||
+ for folder in ["PlugIns", "Extensions"] {
|
||||
+ let directory = bundle_path.join(folder);
|
||||
+ if !isideload_vfs::fs::metadata(&directory).is_ok() { continue; }
|
||||
+ for entry in isideload_vfs::fs::read_dir(&directory)? {
|
||||
+ let entry = entry?;
|
||||
+ if entry.file_type()?.is_dir() {
|
||||
+ app_extensions.push(Bundle::new(entry.path())?);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
|
||||
// Load frameworks from Frameworks directory
|
||||
let frameworks_dir = bundle_path.join("Frameworks");
|
||||
diff --git a/isideload/src/sideload/sign.rs b/isideload/src/sideload/sign.rs
|
||||
index a3d9799..c7e6154 100644
|
||||
--- a/isideload/src/sideload/sign.rs
|
||||
+++ b/isideload/src/sideload/sign.rs
|
||||
@@ -5,6 +5,7 @@ use apple_codesign::{
|
||||
};
|
||||
|
||||
use plist::Dictionary;
|
||||
+use rootcause::option_ext::OptionExt;
|
||||
use rootcause::prelude::*;
|
||||
|
||||
use crate::{
|
||||
@@ -63,6 +64,10 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
+ let musicbridge = app.main_bundle_id()? == format!("ru.yukinoki.musicbridge.{}", team.team_id)
|
||||
+ && app.bundle.app_info.get("MusicBridgeSharedKeychainGroup").and_then(plist::Value::as_string)
|
||||
+ == Some("ru.yukinoki.musicbridge.remote-control");
|
||||
+ if musicbridge { entitlements = musicbridge_keychain(&entitlements)?; }
|
||||
let mut settings = BundleSigningSettings::new(&team.team_id, entitlements, Some(&signer));
|
||||
settings.embedded_mobileprovision = Some(main_provisioning_profile.encoded_profile.as_ref());
|
||||
|
||||
@@ -75,9 +80,56 @@ where
|
||||
.map(|(bundle_id, _, entitlements)| (bundle_id.clone(), entitlements.clone()))
|
||||
.collect();
|
||||
|
||||
+ if musicbridge {
|
||||
+ let remote_id = format!("{}.remote", app.main_bundle_id()?);
|
||||
+ let remote = settings.entitlements_by_bundle_id.get(&remote_id)
|
||||
+ .ok_or_report().context("MusicBridge remote extension has no provisioning profile")?;
|
||||
+ let remote = musicbridge_keychain(remote)?;
|
||||
+ settings.entitlements_by_bundle_id.insert(remote_id, remote);
|
||||
+ }
|
||||
+
|
||||
if let Some(callback) = &progress_callback {
|
||||
callback(0.5).await;
|
||||
}
|
||||
|
||||
Ok(sign_bundle(&app.bundle.bundle_dir, &settings)?)
|
||||
}
|
||||
+
|
||||
+// Request only a group explicitly permitted by Apple's actual profile.
|
||||
+fn musicbridge_keychain(profile: &Dictionary) -> Result<Dictionary, Report> {
|
||||
+ let application = profile.get("application-identifier").and_then(plist::Value::as_string)
|
||||
+ .ok_or_report().context("Profile has no application-identifier")?;
|
||||
+ let prefix = application.split_once('.').ok_or_report().context("Invalid app identifier")?.0;
|
||||
+ let shared = format!("{}.ru.yukinoki.musicbridge.remote-control", prefix);
|
||||
+ let allowed = profile.get("keychain-access-groups").and_then(plist::Value::as_array)
|
||||
+ .ok_or_report().context("Profile has no Keychain groups")?;
|
||||
+ if !allowed.iter().filter_map(plist::Value::as_string).any(|group|
|
||||
+ group == shared || group == format!("{}.*", prefix)) {
|
||||
+ bail!("Apple's provisioning profile does not permit the MusicBridge shared Keychain group");
|
||||
+ }
|
||||
+ let mut result = profile.clone();
|
||||
+ // A concrete per-app default group comes first, shared group second.
|
||||
+ result.insert("keychain-access-groups".into(), plist::Value::Array(vec![
|
||||
+ plist::Value::String(application.into()), plist::Value::String(shared)]));
|
||||
+ Ok(result)
|
||||
+}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod musicbridge_tests {
|
||||
+ use super::*;
|
||||
+ fn profile(group: &str) -> Dictionary {
|
||||
+ let mut p = Dictionary::new();
|
||||
+ p.insert("application-identifier".into(), "TEAM.ru.yukinoki.musicbridge.TEAM".into());
|
||||
+ p.insert("keychain-access-groups".into(), plist::Value::Array(vec![group.into()]));
|
||||
+ p
|
||||
+ }
|
||||
+ #[test]
|
||||
+ fn musicbridge_sharing_respects_profile() {
|
||||
+ let value = musicbridge_keychain(&profile("TEAM.*")).unwrap();
|
||||
+ let groups = value["keychain-access-groups"].as_array().unwrap();
|
||||
+ assert_eq!(groups[0].as_string(), Some("TEAM.ru.yukinoki.musicbridge.TEAM"));
|
||||
+ assert_eq!(groups[1].as_string(), Some("TEAM.ru.yukinoki.musicbridge.remote-control"));
|
||||
+ assert!(musicbridge_keychain(&profile("OTHER.*")).is_err());
|
||||
+ assert!(musicbridge_keychain(&profile("TEAM.unrelated")).is_err());
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Apply reviewed patches to pinned upstream checkouts. No Apple credentials involved."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
root = Path(sys.argv[1]).resolve()
|
||||
patches = Path(__file__).resolve().parent
|
||||
versions = {
|
||||
"iloader": "348eefd7de78e9bc612c9d619b8b1e7a80ba3ba0",
|
||||
"isideload": "f6a4d5dba717d72fc2af63eaba26b27ba44116be",
|
||||
"apple-crates": "5da0b8df9b202434b1eff2700059b1ee142592ac",
|
||||
}
|
||||
for name, commit in versions.items():
|
||||
path = root / name
|
||||
actual = subprocess.check_output(["git", "-C", str(path), "rev-parse", "HEAD"], text=True).strip()
|
||||
if actual != commit:
|
||||
raise RuntimeError(f"Unexpected {name} revision: {actual}")
|
||||
for name, patch in [("isideload", "isideload.patch"), ("apple-crates", "apple-codesign.patch")]:
|
||||
subprocess.run(["git", "-C", str(root/name), "apply", "--check", str(patches/patch)], check=True)
|
||||
subprocess.run(["git", "-C", str(root/name), "apply", str(patches/patch)], check=True)
|
||||
|
||||
manifest = root / "iloader/src-tauri/Cargo.toml"
|
||||
with manifest.open("a", encoding="utf-8") as file:
|
||||
file.write('\n[patch."https://github.com/nab138/isideload"]\nisideload = { path = "../../isideload/isideload" }\n')
|
||||
file.write('\n[patch."https://github.com/nab138/apple-crates"]\napple-codesign = { path = "../../apple-crates/apple-codesign-quick" }\n')
|
||||
# Portable helper: preserve upstream identifier/storage so existing Apple login can
|
||||
# be reused locally. Use a distinct title and disable upstream update replacement.
|
||||
config = root / "iloader/src-tauri/ci.conf.json"
|
||||
value = json.loads(config.read_text(encoding="utf-8"))
|
||||
value["app"] = {"windows": [{"title": "iloader — MusicBridge compatibility build", "width": 800, "height": 600}]}
|
||||
config.write_text(json.dumps(value, indent=2), encoding="utf-8")
|
||||
print("Patched pinned iloader, extension discovery, nested signing and profile-checked Keychain sharing.")
|
||||
Reference in New Issue
Block a user