audb_core/features/device/
add.rs

1use crate::features::config::device_store::DeviceStore;
2use crate::tools::macros::print_info;
3use crate::tools::ssh::SshClient;
4use crate::tools::types::{Device, Platform};
5use crate::tools::validation::{validate_ip_address, validate_port, validate_ssh_key_exists};
6use anyhow::{anyhow, Result};
7use dialoguer::{Confirm, Input, Password, Select};
8use std::path::PathBuf;
9
10pub async fn execute() -> Result<()> {
11    println!("\x1b[1m\x1b[36mAdd Aurora OS Device\x1b[0m\n");
12
13    // Device name (optional)
14    let name: String = Input::new()
15        .with_prompt("Device name (optional, press Enter to skip)")
16        .allow_empty(true)
17        .interact_text()?;
18
19    let name = if name.trim().is_empty() {
20        None
21    } else {
22        Some(name.trim().to_string())
23    };
24
25    // Host IP address
26    let host: String = Input::new()
27        .with_prompt("Host IP address")
28        .validate_with(|input: &String| -> Result<(), &str> {
29            if validate_ip_address(input).is_ok() {
30                Ok(())
31            } else {
32                Err("Invalid IP address format")
33            }
34        })
35        .interact_text()?;
36
37    // SSH port
38    let port: u16 = Input::new()
39        .with_prompt("SSH port")
40        .default(22)
41        .validate_with(|input: &u16| -> Result<(), &str> {
42            if validate_port(*input).is_ok() {
43                Ok(())
44            } else {
45                Err("Port cannot be 0")
46            }
47        })
48        .interact_text()?;
49
50    // SSH private key path
51    let default_key = shellexpand::tilde("~/.ssh/id_rsa").to_string();
52    let auth: String = Input::new()
53        .with_prompt("SSH private key path")
54        .default(default_key)
55        .validate_with(|input: &String| -> Result<(), &str> {
56            let path = PathBuf::from(shellexpand::tilde(input).to_string());
57            if validate_ssh_key_exists(&path).is_ok() {
58                Ok(())
59            } else {
60                Err("SSH key file does not exist")
61            }
62        })
63        .interact_text()?;
64
65    // Root password for devel-su automation (tap, swipe, screenshot commands)
66    let root_password: String = Password::new()
67        .with_prompt("Root password (for devel-su automation - tap/swipe/screenshot)")
68        .allow_empty_password(true)
69        .interact()?;
70
71    // Platform selection
72    let platforms = vec!["aurora-arm", "aurora-arm64"];
73    let selection = Select::new()
74        .with_prompt("Platform")
75        .items(&platforms)
76        .default(0)
77        .interact()?;
78
79    let platform = match selection {
80        0 => Platform::AuroraArm,
81        1 => Platform::AuroraArm64,
82        _ => return Err(anyhow!("Invalid platform selection")),
83    };
84
85    // Create device
86    let device = Device {
87        name,
88        host: host.clone(),
89        port,
90        auth: auth.clone(),
91        root_password: root_password.clone(),
92        platform,
93        enabled: true,
94    };
95
96    // Test defaultuser SSH connection
97    print_info("Testing SSH connection as defaultuser...");
98    let key_path = device.auth_path();
99    let connection_ok = SshClient::test_connection(&host, port, &key_path);
100
101    if !connection_ok {
102        println!("\x1b[1m\x1b[93mwarning\x1b[0m: Could not establish SSH connection to the device");
103
104        let add_anyway = Confirm::new()
105            .with_prompt("Add device anyway?")
106            .default(false)
107            .interact()?;
108
109        if !add_anyway {
110            return Err(anyhow!("Device not added"));
111        }
112    } else {
113        println!("\x1b[1m\x1b[32msuccess\x1b[0m: defaultuser SSH connection verified");
114    }
115
116    // Save device
117    DeviceStore::add(device)?;
118
119    println!("\n\x1b[1m\x1b[32msuccess\x1b[0m: Device added successfully");
120    if root_password.is_empty() {
121        println!("\x1b[1m\x1b[90mnote\x1b[0m: Tap/swipe/screenshot commands require root password to be configured");
122    }
123    Ok(())
124}