continue implimenting sideloading

This commit is contained in:
nab138
2026-02-02 22:55:14 -05:00
parent eaf65c8ef9
commit 2f01d80a39
7 changed files with 192 additions and 29 deletions

38
Cargo.lock generated
View File

@@ -392,6 +392,21 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.31"
@@ -399,6 +414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -407,6 +423,23 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.31" version = "0.3.31"
@@ -436,10 +469,13 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io",
"futures-macro", "futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab", "slab",
@@ -733,6 +769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4031af51250d2f22f61a0d7fb7ea71ba8b6144b2b9dd3b7ee4a931fccbd1ec0" checksum = "f4031af51250d2f22f61a0d7fb7ea71ba8b6144b2b9dd3b7ee4a931fccbd1ec0"
dependencies = [ dependencies = [
"base64", "base64",
"futures",
"plist", "plist",
"plist-macro", "plist-macro",
"rustls", "rustls",
@@ -924,6 +961,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
name = "minimal" name = "minimal"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"idevice",
"isideload", "isideload",
"plist", "plist",
"plist-macro", "plist-macro",

View File

@@ -10,3 +10,4 @@ plist-macro = "0.1.3"
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] }
tracing = "0.1.44" tracing = "0.1.44"
tracing-subscriber = "0.3.22" tracing-subscriber = "0.3.22"
idevice = { version = "0.1.52", features = ["usbmuxd"]}

View File

@@ -1,12 +1,11 @@
use std::env; use std::{env, path::PathBuf};
use idevice::usbmuxd::{UsbmuxdAddr, UsbmuxdConnection};
use isideload::{ use isideload::{
anisette::remote_v3::RemoteV3AnisetteProvider, anisette::remote_v3::RemoteV3AnisetteProvider,
auth::apple_account::AppleAccount, auth::apple_account::AppleAccount,
dev::{ dev::developer_session::DeveloperSession,
certificates::CertificatesApi, sideload::{SideloadConfiguration, TeamSelection, sideload_app},
developer_session::{DeveloperSession, TeamsApi},
},
}; };
use tracing::Level; use tracing::Level;
@@ -21,14 +20,15 @@ async fn main() {
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
// let _app_path = PathBuf::from(
// args.get(1)
// .expect("Please provide the path to the app to install"),
// );
let apple_id = args let apple_id = args
.get(1) .get(1)
.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(2).expect("Please provide the Apple ID password"); let apple_password = args.get(2).expect("Please provide the Apple ID password");
let app_path = PathBuf::from(
args.get(3)
.expect("Please provide the path to the app to install"),
);
let get_2fa_code = || { let get_2fa_code = || {
let mut code = String::new(); let mut code = String::new();
@@ -53,25 +53,46 @@ async fn main() {
.await .await
.expect("Failed to create developer session"); .expect("Failed to create developer session");
let teams = dev_session let usbmuxd = UsbmuxdConnection::default().await;
.list_teams() if usbmuxd.is_err() {
.await panic!("Failed to connect to usbmuxd: {:?}", usbmuxd.err());
.expect("Failed to list teams"); }
let mut usbmuxd = usbmuxd.unwrap();
let team = teams let devs = usbmuxd.get_devices().await.unwrap();
.get(0) if devs.is_empty() {
.expect("No developer teams available for this account"); panic!("No devices found");
}
// let app_ids = dev_session let provider = devs
// .list_app_ids(team, None) .iter()
// .await .next()
// .expect("Failed to add appid"); .unwrap()
// let app_id = app_ids.app_ids.get(0).cloned().unwrap(); .to_provider(UsbmuxdAddr::from_env_var().unwrap(), "isideload-demo");
let res = dev_session let sideload_config =
.list_all_development_certs(team, None) SideloadConfiguration::builder().team_selection(TeamSelection::Prompt(|teams| {
.await println!("Please select a team:");
.expect("Failed to list dev certs"); for (index, team) in teams.iter().enumerate() {
println!(
"{}: {} ({})",
index + 1,
team.name.as_deref().unwrap_or("<Unnamed>"),
team.team_id
);
}
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let selection = input.trim().parse::<usize>().ok()?;
if selection == 0 || selection > teams.len() {
return None;
}
Some(teams[selection - 1].team_id.clone())
}));
println!("{:?}", res); let result = sideload_app(&provider, &mut dev_session, app_path, &sideload_config).await;
match result {
Ok(_) => println!("App installed successfully"),
Err(e) => panic!("Failed to install app: {:?}", e),
}
} }

View File

@@ -15,7 +15,7 @@ default = ["install"]
install = ["dep:idevice"] install = ["dep:idevice"]
[dependencies] [dependencies]
idevice = { version = "0.1.51", optional = true } idevice = { version = "0.1.52", optional = true }
plist = "1.8" plist = "1.8"
plist-macro = "0.1.3" plist-macro = "0.1.3"
reqwest = { version = "0.13.1", features = ["json", "gzip"] } reqwest = { version = "0.13.1", features = ["json", "gzip"] }

View File

@@ -6,6 +6,7 @@ use crate::dev::{
use plist_macro::plist; use plist_macro::plist;
use rootcause::prelude::*; use rootcause::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use tracing::info;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -59,6 +60,27 @@ pub trait DevicesApi {
Ok(device) Ok(device)
} }
// TODO: This can be skipped if we know the device is already registered
/// Check if the device is a development device, and add it if not
async fn ensure_device_registered(
&mut self,
team: &DeveloperTeam,
name: &str,
udid: &str,
device_type: impl Into<Option<DeveloperDeviceType>> + Send,
) -> Result<(), Report> {
let device_type = device_type.into();
let devices = self.list_devices(team, device_type.clone()).await?;
if !devices.iter().any(|d| d.device_number == udid) {
info!("Registering development device");
self.add_device(team, name, udid, device_type).await?;
}
info!("Device is a development device");
Ok(())
}
} }
impl DevicesApi for DeveloperSession { impl DevicesApi for DeveloperSession {

View File

@@ -0,0 +1,46 @@
use std::fmt::Display;
use crate::dev::teams::DeveloperTeam;
/// Configuration for selecting a developer team during sideloading
///
/// If there is only one team, it will be selected automatically regardless of this setting.
/// If there are multiple teams, the behavior will depend on this setting.
pub enum TeamSelection {
/// Select the first team automatically
First,
/// Prompt the user to select a team
Prompt(fn(&Vec<DeveloperTeam>) -> Option<String>),
}
impl Display for TeamSelection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TeamSelection::First => write!(f, "first team"),
TeamSelection::Prompt(_) => write!(f, "prompting for team"),
}
}
}
pub struct SideloadConfiguration {
pub team_selection: TeamSelection,
}
impl Default for SideloadConfiguration {
fn default() -> Self {
SideloadConfiguration {
team_selection: TeamSelection::First,
}
}
}
impl SideloadConfiguration {
pub fn builder() -> Self {
Self::default()
}
pub fn team_selection(mut self, selection: TeamSelection) -> Self {
self.team_selection = selection;
self
}
}

View File

@@ -2,15 +2,50 @@ use std::path::PathBuf;
use idevice::provider::IdeviceProvider; use idevice::provider::IdeviceProvider;
use rootcause::prelude::*; use rootcause::prelude::*;
use tracing::info;
use crate::dev::developer_session::DeveloperSession; use crate::dev::teams::TeamsApi;
use crate::dev::{developer_session::DeveloperSession, devices::DevicesApi};
use crate::util::device::IdeviceInfo; use crate::util::device::IdeviceInfo;
pub mod config;
pub use config::{SideloadConfiguration, TeamSelection};
pub async fn sideload_app( pub async fn sideload_app(
device_provider: &impl IdeviceProvider, device_provider: &impl IdeviceProvider,
dev_session: &DeveloperSession, dev_session: &mut DeveloperSession,
app_path: PathBuf, app_path: PathBuf,
config: &SideloadConfiguration,
) -> Result<(), Report> { ) -> Result<(), Report> {
let device_info = IdeviceInfo::from_device(device_provider).await?; let device_info = IdeviceInfo::from_device(device_provider).await?;
let teams = dev_session.list_teams().await?;
let team = match teams.len() {
0 => {
bail!("No developer teams available")
}
1 => &teams[0],
_ => {
info!(
"Multiple developer teams found, {} as per configuration",
config.team_selection
);
match &config.team_selection {
TeamSelection::First => &teams[0],
TeamSelection::Prompt(prompt_fn) => {
let selection = prompt_fn(&teams).ok_or_else(|| report!("No team selected"))?;
teams
.iter()
.find(|t| t.team_id == selection)
.ok_or_else(|| report!("No team found with ID {}", selection))?
}
}
}
};
dev_session
.ensure_device_registered(team, &device_info.name, &device_info.udid, None)
.await?;
Ok(()) Ok(())
} }