mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26:16 +01:00
Improve API
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1366,6 +1366,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|||||||
name = "minimal"
|
name = "minimal"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"idevice",
|
||||||
"isideload",
|
"isideload",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -19,8 +19,10 @@ Then, you can use it like so:
|
|||||||
|
|
||||||
```rs
|
```rs
|
||||||
use std::{env, path::PathBuf, sync::Arc};
|
use std::{env, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use idevice::usbmuxd::{UsbmuxdAddr, UsbmuxdConnection};
|
||||||
use isideload::{
|
use isideload::{
|
||||||
AnisetteConfiguration, AppleAccount, DefaultLogger, DeveloperSession, device::list_devices,
|
AnisetteConfiguration, AppleAccount, DeveloperSession, SideloadConfiguration,
|
||||||
sideload::sideload_app,
|
sideload::sideload_app,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,11 +38,23 @@ async fn main() {
|
|||||||
.expect("Please provide the Apple ID to use for installation");
|
.expect("Please provide the Apple ID to use for installation");
|
||||||
let apple_password = args.get(3).expect("Please provide the Apple ID password");
|
let apple_password = args.get(3).expect("Please provide the Apple ID password");
|
||||||
|
|
||||||
// You don't have to use the builtin list_devices method if you don't want to use usbmuxd
|
// You don't have to use usbmuxd, you can use any IdeviceProvider
|
||||||
// You can use idevice to get the device info however you want
|
let usbmuxd = UsbmuxdConnection::default().await;
|
||||||
// This is just easier
|
if usbmuxd.is_err() {
|
||||||
let device = list_devices().await.unwrap().into_iter().next().unwrap();
|
panic!("Failed to connect to usbmuxd: {:?}", usbmuxd.err());
|
||||||
println!("Target device: {}", device.name);
|
}
|
||||||
|
let mut usbmuxd = usbmuxd.unwrap();
|
||||||
|
|
||||||
|
let devs = usbmuxd.get_devices().await.unwrap();
|
||||||
|
if devs.is_empty() {
|
||||||
|
panic!("No devices found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let provider = devs
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo");
|
||||||
|
|
||||||
// Change the anisette url and such here
|
// Change the anisette url and such here
|
||||||
// Note that right now only remote anisette servers are supported
|
// Note that right now only remote anisette servers are supported
|
||||||
@@ -63,11 +77,10 @@ async fn main() {
|
|||||||
|
|
||||||
let dev_session = DeveloperSession::new(Arc::new(account));
|
let dev_session = DeveloperSession::new(Arc::new(account));
|
||||||
|
|
||||||
// This is where certificates, mobileprovision, and anisette data will be stored
|
// You can change the machine name, store directory (for certs, anisette data, & provision files), and logger
|
||||||
let store_dir = std::env::current_dir().unwrap();
|
let config = SideloadConfiguration::default().set_machine_name("isideload-demo".to_string());
|
||||||
|
|
||||||
// DefaultLogger just prints to the stdout/stderr, but you can provide your own implementation
|
sideload_app(&provider, &dev_session, app_path, config)
|
||||||
sideload_app(DefaultLogger {}, &dev_session, &device, app_path, store_dir)
|
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
isideload = { path = "../../isideload", features = ["vendored-openssl", "vendored-botan"] }
|
isideload = { path = "../../isideload", features = ["vendored-openssl", "vendored-botan"] }
|
||||||
|
idevice = { version = "0.1.37", features = ["usbmuxd"]}
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use std::{env, path::PathBuf, sync::Arc};
|
use std::{env, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use idevice::usbmuxd::{UsbmuxdAddr, UsbmuxdConnection};
|
||||||
use isideload::{
|
use isideload::{
|
||||||
AnisetteConfiguration, AppleAccount, DefaultLogger, DeveloperSession, device::list_devices,
|
AnisetteConfiguration, AppleAccount, DeveloperSession, SideloadConfiguration,
|
||||||
sideload::sideload_app,
|
sideload::sideload_app,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,11 +18,23 @@ async fn main() {
|
|||||||
.expect("Please provide the Apple ID to use for installation");
|
.expect("Please provide the Apple ID to use for installation");
|
||||||
let apple_password = args.get(3).expect("Please provide the Apple ID password");
|
let apple_password = args.get(3).expect("Please provide the Apple ID password");
|
||||||
|
|
||||||
// You don't have to use the builtin list_devices method if you don't want to use usbmuxd
|
// You don't have to use usbmuxd, you can use any IdeviceProvider
|
||||||
// You can use idevice to get the device info however you want
|
let usbmuxd = UsbmuxdConnection::default().await;
|
||||||
// This is just easier
|
if usbmuxd.is_err() {
|
||||||
let device = list_devices().await.unwrap().into_iter().next().unwrap();
|
panic!("Failed to connect to usbmuxd: {:?}", usbmuxd.err());
|
||||||
println!("Target device: {}", device.name);
|
}
|
||||||
|
let mut usbmuxd = usbmuxd.unwrap();
|
||||||
|
|
||||||
|
let devs = usbmuxd.get_devices().await.unwrap();
|
||||||
|
if devs.is_empty() {
|
||||||
|
panic!("No devices found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let provider = devs
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo");
|
||||||
|
|
||||||
// Change the anisette url and such here
|
// Change the anisette url and such here
|
||||||
// Note that right now only remote anisette servers are supported
|
// Note that right now only remote anisette servers are supported
|
||||||
@@ -44,11 +57,10 @@ async fn main() {
|
|||||||
|
|
||||||
let dev_session = DeveloperSession::new(Arc::new(account));
|
let dev_session = DeveloperSession::new(Arc::new(account));
|
||||||
|
|
||||||
// This is where certificates, mobileprovision, and anisette data will be stored
|
// You can change the machine name, store directory (for certs, anisette data, & provision files), and logger
|
||||||
let store_dir = std::env::current_dir().unwrap();
|
let config = SideloadConfiguration::default().set_machine_name("isideload-demo".to_string());
|
||||||
|
|
||||||
// DefaultLogger just prints to the stdout/stderr, but you can provide your own implementation
|
sideload_app(&provider, &dev_session, app_path, config)
|
||||||
sideload_app(DefaultLogger {}, &dev_session, &device, app_path, store_dir)
|
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ keywords = ["ios", "sideload"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
vendored-openssl = ["openssl/vendored"]
|
vendored-openssl = ["openssl/vendored", "zsign-rust/vendored-openssl"]
|
||||||
vendored-botan = ["icloud_auth/vendored-botan"]
|
vendored-botan = ["icloud_auth/vendored-botan"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -22,7 +22,7 @@ uuid = { version = "1.17.0", features = ["v4"] }
|
|||||||
zip = "4.3"
|
zip = "4.3"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
idevice = { version = "0.1.37", features = ["afc", "usbmuxd", "installation_proxy"] }
|
idevice = { version = "0.1.37", features = ["afc", "installation_proxy"] }
|
||||||
openssl = "0.10"
|
openssl = "0.10"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
zsign-rust = "0.1"
|
zsign-rust = "0.1"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub struct CertificateIdentity {
|
|||||||
pub private_key: PKey<Private>,
|
pub private_key: PKey<Private>,
|
||||||
pub key_file: PathBuf,
|
pub key_file: PathBuf,
|
||||||
pub cert_file: PathBuf,
|
pub cert_file: PathBuf,
|
||||||
|
pub machine_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CertificateIdentity {
|
impl CertificateIdentity {
|
||||||
@@ -29,6 +30,7 @@ impl CertificateIdentity {
|
|||||||
configuration_path: &Path,
|
configuration_path: &Path,
|
||||||
dev_session: &DeveloperSession,
|
dev_session: &DeveloperSession,
|
||||||
apple_id: String,
|
apple_id: String,
|
||||||
|
machine_name: String,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
hasher.update(apple_id.as_bytes());
|
hasher.update(apple_id.as_bytes());
|
||||||
@@ -64,6 +66,7 @@ impl CertificateIdentity {
|
|||||||
private_key,
|
private_key,
|
||||||
key_file,
|
key_file,
|
||||||
cert_file,
|
cert_file,
|
||||||
|
machine_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(cert) = cert_identity
|
if let Ok(cert) = cert_identity
|
||||||
@@ -103,7 +106,7 @@ impl CertificateIdentity {
|
|||||||
|
|
||||||
for cert in certificates
|
for cert in certificates
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| c.machine_name == "YCode".to_string())
|
.filter(|c| c.machine_name == self.machine_name)
|
||||||
{
|
{
|
||||||
if let Ok(x509_cert) = X509::from_der(&cert.cert_content) {
|
if let Ok(x509_cert) = X509::from_der(&cert.cert_content) {
|
||||||
if let Ok(cert_public_key) = x509_cert.public_key() {
|
if let Ok(cert_public_key) = x509_cert.public_key() {
|
||||||
@@ -166,6 +169,7 @@ impl CertificateIdentity {
|
|||||||
DeveloperDeviceType::Ios,
|
DeveloperDeviceType::Ios,
|
||||||
team,
|
team,
|
||||||
String::from_utf8_lossy(&csr_pem).to_string(),
|
String::from_utf8_lossy(&csr_pem).to_string(),
|
||||||
|
self.machine_name.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ impl DeveloperSession {
|
|||||||
device_type: DeveloperDeviceType,
|
device_type: DeveloperDeviceType,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
csr_content: String,
|
csr_content: String,
|
||||||
|
machine_name: String,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let url = dev_url(device_type, "submitDevelopmentCSR");
|
let url = dev_url(device_type, "submitDevelopmentCSR");
|
||||||
let mut body = Dictionary::new();
|
let mut body = Dictionary::new();
|
||||||
@@ -306,10 +307,7 @@ impl DeveloperSession {
|
|||||||
"machineId".to_string(),
|
"machineId".to_string(),
|
||||||
Value::String(uuid::Uuid::new_v4().to_string().to_uppercase()),
|
Value::String(uuid::Uuid::new_v4().to_string().to_uppercase()),
|
||||||
);
|
);
|
||||||
body.insert(
|
body.insert("machineName".to_string(), Value::String(machine_name));
|
||||||
"machineName".to_string(),
|
|
||||||
Value::String("YCode".to_string()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let response = self.send_developer_request(&url, Some(body)).await?;
|
let response = self.send_developer_request(&url, Some(body)).await?;
|
||||||
let cert_dict = response
|
let cert_dict = response
|
||||||
|
|||||||
@@ -1,89 +1,19 @@
|
|||||||
use idevice::{
|
use idevice::{
|
||||||
IdeviceService,
|
IdeviceService, afc::AfcClient, installation_proxy::InstallationProxyClient,
|
||||||
afc::AfcClient,
|
provider::IdeviceProvider,
|
||||||
installation_proxy::InstallationProxyClient,
|
|
||||||
lockdown::LockdownClient,
|
|
||||||
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection},
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::{future::Future, path::Path};
|
use std::{future::Future, path::Path};
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
/// Installs an ***already signed*** app onto your device.
|
||||||
pub struct DeviceInfo {
|
|
||||||
pub name: String,
|
|
||||||
pub id: u32,
|
|
||||||
pub uuid: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_devices() -> Result<Vec<DeviceInfo>, String> {
|
|
||||||
let usbmuxd = UsbmuxdConnection::default().await;
|
|
||||||
if usbmuxd.is_err() {
|
|
||||||
eprintln!("Failed to connect to usbmuxd: {:?}", usbmuxd.err());
|
|
||||||
return Err("Failed to connect to usbmuxd".to_string());
|
|
||||||
}
|
|
||||||
let mut usbmuxd = usbmuxd.unwrap();
|
|
||||||
|
|
||||||
let devs = usbmuxd.get_devices().await.unwrap();
|
|
||||||
if devs.is_empty() {
|
|
||||||
return Ok(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let device_info_futures: Vec<_> = devs
|
|
||||||
.iter()
|
|
||||||
.map(|d| async move {
|
|
||||||
let provider = d.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "y-code");
|
|
||||||
let device_uid = d.device_id;
|
|
||||||
|
|
||||||
let mut lockdown_client = match LockdownClient::connect(&provider).await {
|
|
||||||
Ok(l) => l,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Unable to connect to lockdown: {e:?}");
|
|
||||||
return DeviceInfo {
|
|
||||||
name: String::from("Unknown Device"),
|
|
||||||
id: device_uid,
|
|
||||||
uuid: d.udid.clone(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let device_name = lockdown_client
|
|
||||||
.get_value("DeviceName", None)
|
|
||||||
.await
|
|
||||||
.expect("Failed to get device name")
|
|
||||||
.as_string()
|
|
||||||
.expect("Failed to convert device name to string")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
DeviceInfo {
|
|
||||||
name: device_name,
|
|
||||||
id: device_uid,
|
|
||||||
uuid: d.udid.clone(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(futures::future::join_all(device_info_futures).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn install_app(
|
pub async fn install_app(
|
||||||
device: &DeviceInfo,
|
provider: &impl IdeviceProvider,
|
||||||
app_path: &Path,
|
app_path: &Path,
|
||||||
callback: impl Fn(u64) -> (),
|
progress_callback: impl Fn(u64) -> (),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut usbmuxd = UsbmuxdConnection::default()
|
let mut afc_client = AfcClient::connect(provider)
|
||||||
.await
|
|
||||||
.map_err(|e| Error::IdeviceError(e))?;
|
|
||||||
let device = usbmuxd
|
|
||||||
.get_device(&device.uuid)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::IdeviceError(e))?;
|
|
||||||
|
|
||||||
let provider = device.to_provider(UsbmuxdAddr::from_env_var().unwrap(), "y-code");
|
|
||||||
|
|
||||||
let mut afc_client = AfcClient::connect(&provider)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::IdeviceError(e))?;
|
.map_err(|e| Error::IdeviceError(e))?;
|
||||||
|
|
||||||
@@ -93,7 +23,7 @@ pub async fn install_app(
|
|||||||
);
|
);
|
||||||
afc_upload_dir(&mut afc_client, app_path, &dir).await?;
|
afc_upload_dir(&mut afc_client, app_path, &dir).await?;
|
||||||
|
|
||||||
let mut instproxy_client = InstallationProxyClient::connect(&provider)
|
let mut instproxy_client = InstallationProxyClient::connect(provider)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::IdeviceError(e))?;
|
.map_err(|e| Error::IdeviceError(e))?;
|
||||||
|
|
||||||
@@ -104,7 +34,7 @@ pub async fn install_app(
|
|||||||
dir,
|
dir,
|
||||||
Some(plist::Value::Dictionary(options)),
|
Some(plist::Value::Dictionary(options)),
|
||||||
async |(percentage, _)| {
|
async |(percentage, _)| {
|
||||||
callback(percentage);
|
progress_callback(percentage);
|
||||||
},
|
},
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub enum Error {
|
|||||||
ZSignError(#[from] ZSignError),
|
ZSignError(#[from] ZSignError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SideloadLogger {
|
pub trait SideloadLogger: Send + Sync {
|
||||||
fn log(&self, message: &str);
|
fn log(&self, message: &str);
|
||||||
fn error(&self, error: &Error);
|
fn error(&self, error: &Error);
|
||||||
}
|
}
|
||||||
@@ -55,3 +55,44 @@ impl SideloadLogger for DefaultLogger {
|
|||||||
eprintln!("Error: {}", error);
|
eprintln!("Error: {}", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sideload configuration options.
|
||||||
|
pub struct SideloadConfiguration {
|
||||||
|
/// An arbitrary machine name to appear on the certificate (e.x. "YCode")
|
||||||
|
pub machine_name: String,
|
||||||
|
/// Logger for reporting progress and errors
|
||||||
|
pub logger: Box<dyn SideloadLogger>,
|
||||||
|
/// Directory used to store intermediate artifacts (profiles, certs, etc.). This directory will not be cleared at the end.
|
||||||
|
pub store_dir: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SideloadConfiguration {
|
||||||
|
fn default() -> Self {
|
||||||
|
SideloadConfiguration::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SideloadConfiguration {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SideloadConfiguration {
|
||||||
|
machine_name: "isideload".to_string(),
|
||||||
|
logger: Box::new(DefaultLogger),
|
||||||
|
store_dir: std::env::current_dir().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_machine_name(mut self, machine_name: String) -> Self {
|
||||||
|
self.machine_name = machine_name;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_logger(mut self, logger: Box<dyn SideloadLogger>) -> Self {
|
||||||
|
self.logger = logger;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_store_dir(mut self, store_dir: std::path::PathBuf) -> Self {
|
||||||
|
self.store_dir = store_dir;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,39 +1,64 @@
|
|||||||
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
|
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
|
||||||
|
|
||||||
|
use idevice::IdeviceService;
|
||||||
|
use idevice::lockdown::LockdownClient;
|
||||||
|
use idevice::provider::IdeviceProvider;
|
||||||
use zsign_rust::ZSignOptions;
|
use zsign_rust::ZSignOptions;
|
||||||
|
|
||||||
use crate::application::Application;
|
use crate::application::Application;
|
||||||
use crate::{DeveloperTeam, Error, SideloadLogger};
|
use crate::device::install_app;
|
||||||
|
use crate::{DeveloperTeam, Error, SideloadConfiguration, SideloadLogger};
|
||||||
use crate::{
|
use crate::{
|
||||||
certificate::CertificateIdentity,
|
certificate::CertificateIdentity,
|
||||||
developer_session::{DeveloperDeviceType, DeveloperSession},
|
developer_session::{DeveloperDeviceType, DeveloperSession},
|
||||||
device::{DeviceInfo, install_app},
|
|
||||||
};
|
};
|
||||||
use std::{io::Write, path::PathBuf};
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
fn error_and_return(logger: &impl SideloadLogger, error: Error) -> Result<(), Error> {
|
fn error_and_return(logger: &Box<dyn SideloadLogger>, error: Error) -> Result<(), Error> {
|
||||||
logger.error(&error);
|
logger.error(&error);
|
||||||
Err(error)
|
Err(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sideloads an `.ipa` or `.app` onto a device.
|
/// Signs and installs an `.ipa` or `.app` onto a device.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// - `logger` — Reports progress and errors.
|
/// - `device_provider` - [`idevice::provider::IdeviceProvider`] for the device
|
||||||
/// - `dev_session` — Authenticated Apple developer session ([`crate::developer_session::DeveloperSession`]).
|
/// - `dev_session` - Authenticated Apple developer session ([`crate::developer_session::DeveloperSession`]).
|
||||||
/// - `device` — Target device information ([`crate::device::DeviceInfo`]).
|
/// - `app_path` - Path to the `.ipa` file or `.app` bundle to sign and install
|
||||||
/// - `app_path` — Path to the `.ipa` file or `.app` bundle to sign and install
|
/// - `config` - Sideload configuration options ([`crate::SideloadConfiguration`])
|
||||||
/// - `store_dir` — Directory used to store intermediate artifacts (profiles, certs, etc.). This directory will not be cleared at the end.
|
|
||||||
pub async fn sideload_app(
|
pub async fn sideload_app(
|
||||||
logger: impl SideloadLogger,
|
device_provider: &impl IdeviceProvider,
|
||||||
dev_session: &DeveloperSession,
|
dev_session: &DeveloperSession,
|
||||||
device: &DeviceInfo,
|
|
||||||
app_path: PathBuf,
|
app_path: PathBuf,
|
||||||
store_dir: PathBuf,
|
config: SideloadConfiguration,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if device.uuid.is_empty() {
|
let logger = config.logger;
|
||||||
return error_and_return(&logger, Error::Generic("No device selected".to_string()));
|
let mut lockdown_client = match LockdownClient::connect(device_provider).await {
|
||||||
}
|
Ok(l) => l,
|
||||||
|
Err(e) => {
|
||||||
|
return error_and_return(&logger, Error::IdeviceError(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let device_name = lockdown_client
|
||||||
|
.get_value("DeviceName", None)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::IdeviceError(e))?
|
||||||
|
.as_string()
|
||||||
|
.ok_or(Error::Generic(
|
||||||
|
"Failed to convert DeviceName to string".to_string(),
|
||||||
|
))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let device_uuid = lockdown_client
|
||||||
|
.get_value("UniqueDeviceID", None)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::IdeviceError(e))?
|
||||||
|
.as_string()
|
||||||
|
.ok_or(Error::Generic(
|
||||||
|
"Failed to convert UniqueDeviceID to string".to_string(),
|
||||||
|
))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let team = match dev_session.get_team().await {
|
let team = match dev_session.get_team().await {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
@@ -44,12 +69,13 @@ pub async fn sideload_app(
|
|||||||
|
|
||||||
logger.log("Successfully retrieved team");
|
logger.log("Successfully retrieved team");
|
||||||
|
|
||||||
ensure_device_registered(&logger, dev_session, &team, device).await?;
|
ensure_device_registered(&logger, dev_session, &team, &device_uuid, &device_name).await?;
|
||||||
|
|
||||||
let cert = match CertificateIdentity::new(
|
let cert = match CertificateIdentity::new(
|
||||||
&store_dir,
|
&config.store_dir,
|
||||||
&dev_session,
|
&dev_session,
|
||||||
dev_session.account.apple_id.clone(),
|
dev_session.account.apple_id.clone(),
|
||||||
|
config.machine_name,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -301,7 +327,9 @@ pub async fn sideload_app(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let profile_path = store_dir.join(format!("{}.mobileprovision", main_app_id_str));
|
let profile_path = config
|
||||||
|
.store_dir
|
||||||
|
.join(format!("{}.mobileprovision", main_app_id_str));
|
||||||
|
|
||||||
if profile_path.exists() {
|
if profile_path.exists() {
|
||||||
std::fs::remove_file(&profile_path).map_err(|e| Error::Filesystem(e))?;
|
std::fs::remove_file(&profile_path).map_err(|e| Error::Filesystem(e))?;
|
||||||
@@ -337,7 +365,7 @@ pub async fn sideload_app(
|
|||||||
|
|
||||||
logger.log("Installing app (Transfer)... 0%");
|
logger.log("Installing app (Transfer)... 0%");
|
||||||
|
|
||||||
let res = install_app(&device, &app.bundle.bundle_dir, |percentage| {
|
let res = install_app(device_provider, &app.bundle.bundle_dir, |percentage| {
|
||||||
logger.log(&format!("Installing app... {}%", percentage));
|
logger.log(&format!("Installing app... {}%", percentage));
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@@ -349,10 +377,11 @@ pub async fn sideload_app(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ensure_device_registered(
|
pub async fn ensure_device_registered(
|
||||||
logger: &impl SideloadLogger,
|
logger: &Box<dyn SideloadLogger>,
|
||||||
dev_session: &DeveloperSession,
|
dev_session: &DeveloperSession,
|
||||||
team: &DeveloperTeam,
|
team: &DeveloperTeam,
|
||||||
device: &DeviceInfo,
|
uuid: &str,
|
||||||
|
name: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let devices = dev_session
|
let devices = dev_session
|
||||||
.list_devices(DeveloperDeviceType::Ios, team)
|
.list_devices(DeveloperDeviceType::Ios, team)
|
||||||
@@ -361,11 +390,11 @@ pub async fn ensure_device_registered(
|
|||||||
return error_and_return(logger, e);
|
return error_and_return(logger, e);
|
||||||
}
|
}
|
||||||
let devices = devices.unwrap();
|
let devices = devices.unwrap();
|
||||||
if !devices.iter().any(|d| d.device_number == device.uuid) {
|
if !devices.iter().any(|d| d.device_number == uuid) {
|
||||||
logger.log("Device not found in your account");
|
logger.log("Device not found in your account");
|
||||||
// TODO: Actually test!
|
// TODO: Actually test!
|
||||||
dev_session
|
dev_session
|
||||||
.add_device(DeveloperDeviceType::Ios, team, &device.name, &device.uuid)
|
.add_device(DeveloperDeviceType::Ios, team, name, uuid)
|
||||||
.await?;
|
.await?;
|
||||||
logger.log("Successfully added device to your account");
|
logger.log("Successfully added device to your account");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user