implement installer as library and use it in gui

This commit is contained in:
Markus Unterwaditzer
2025-11-08 15:14:09 +01:00
committed by Markus Unterwaditzer
parent 9e9fe4d392
commit 3b44234ae1
19 changed files with 285 additions and 151 deletions

View File

@@ -1,19 +1,29 @@
use anyhow::{Context, Error, bail};
use anyhow::{Context, Error};
use clap::{Parser, Subcommand};
use env_logger::Env;
#[cfg(not(target_os = "android"))]
use anyhow::bail;
#[cfg(not(target_os = "android"))]
mod orbic;
mod orbic_auth;
mod orbic_network;
mod output;
#[cfg(not(target_os = "android"))]
mod pinephone;
mod tmobile;
mod tplink;
mod util;
#[cfg(not(target_os = "android"))]
mod uz801;
mod wingtech;
pub static CONFIG_TOML: &str = include_str!("../../dist/config.toml.in");
pub static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon");
use crate::output::eprintln;
static CONFIG_TOML: &str = include_str!("../../dist/config.toml.in");
static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon");
#[derive(Parser, Debug)]
#[command(version, about)]
@@ -27,6 +37,7 @@ struct Args {
#[derive(Subcommand, Debug)]
enum Command {
/// Install rayhunter on the Orbic RC400L using the legacy USB+ADB-based installer.
#[cfg(not(target_os = "android"))]
OrbicUsb(InstallOrbic),
/// Install rayhunter on the Orbic RC400L or Moxee Hotspot via network.
#[clap(alias = "orbic-network")]
@@ -34,8 +45,10 @@ enum Command {
/// Install rayhunter on the TMobile TMOHS1.
Tmobile(TmobileArgs),
/// Install rayhunter on the Uz801.
#[cfg(not(target_os = "android"))]
Uz801(Uz801Args),
/// Install rayhunter on a PinePhone's Quectel modem.
#[cfg(not(target_os = "android"))]
Pinephone(InstallPinephone),
/// Install rayhunter on the TP-Link M7350.
Tplink(InstallTpLink),
@@ -98,14 +111,18 @@ struct Util {
#[derive(Subcommand, Debug)]
enum UtilSubCommand {
/// Send a serial command to the Orbic.
#[cfg(not(target_os = "android"))]
Serial(Serial),
/// Start an ADB shell
#[cfg(not(target_os = "android"))]
Shell,
/// Root the Tmobile and launch adb.
#[cfg(not(target_os = "android"))]
TmobileStartAdb(TmobileArgs),
/// Root the Tmobile and launch telnetd.
TmobileStartTelnet(TmobileArgs),
/// Root the Uz801 and launch adb.
#[cfg(not(target_os = "android"))]
Uz801StartAdb(Uz801Args),
/// Root the tplink and launch telnetd.
TplinkStartTelnet(TplinkStartTelnet),
@@ -114,8 +131,10 @@ enum UtilSubCommand {
/// Root the Wingtech and launch adb.
WingtechStartAdb(WingtechArgs),
/// Unlock the Pinephone's modem and start adb.
#[cfg(not(target_os = "android"))]
PinephoneStartAdb,
/// Lock the Pinephone's modem and stop adb.
#[cfg(not(target_os = "android"))]
PinephoneStopAdb,
/// Root the Orbic and launch telnetd.
OrbicStartTelnet(OrbicNetworkArgs),
@@ -196,20 +215,24 @@ struct Serial {
command: Vec<String>,
}
async fn run() -> Result<(), Error> {
async fn run(args: Args) -> Result<(), Error> {
env_logger::Builder::from_env(Env::default().default_filter_or("off")).init();
let Args { command } = Args::parse();
match command {
match args.command {
Command::Tmobile(args) => tmobile::install(args).await.context("Failed to install rayhunter on the Tmobile TMOHS1. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
#[cfg(not(target_os = "android"))]
Command::Uz801(args) => uz801::install(args).await.context("Failed to install rayhunter on the Uz801. Make sure your computer is connected to the hotspot using USB.")?,
Command::Tplink(tplink) => tplink::main_tplink(tplink).await.context("Failed to install rayhunter on the TP-Link M7350. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
#[cfg(not(target_os = "android"))]
Command::Pinephone(_) => pinephone::install().await
.context("Failed to install rayhunter on the Pinephone's Quectel modem")?,
#[cfg(not(target_os = "android"))]
Command::OrbicUsb(_) => orbic::install().await.context("\nFailed to install rayhunter on the Orbic RC400L (USB installer)")?,
Command::Orbic(args) => orbic_network::install(args.admin_ip, args.admin_username, args.admin_password).await.context("\nFailed to install rayhunter on the Orbic RC400L")?,
Command::Wingtech(args) => wingtech::install(args).await.context("\nFailed to install rayhunter on the Wingtech CT2MHS01")?,
Command::Util(subcommand) => match subcommand.command {
Command::Util(subcommand) => {
match subcommand.command {
#[cfg(not(target_os = "android"))]
UtilSubCommand::Serial(serial_cmd) => {
if serial_cmd.root {
if !serial_cmd.command.is_empty() {
@@ -228,9 +251,12 @@ async fn run() -> Result<(), Error> {
}
}
}
#[cfg(not(target_os = "android"))]
UtilSubCommand::Shell => orbic::shell().await.context("\nFailed to open shell on Orbic RC400L")?,
UtilSubCommand::TmobileStartTelnet(args) => wingtech::start_telnet(&args.admin_ip, &args.admin_password).await.context("\nFailed to start telnet on the Tmobile TMOHS1")?,
#[cfg(not(target_os = "android"))]
UtilSubCommand::TmobileStartAdb(args) => wingtech::start_adb(&args.admin_ip, &args.admin_password).await.context("\nFailed to start adb on the Tmobile TMOHS1")?,
#[cfg(not(target_os = "android"))]
UtilSubCommand::Uz801StartAdb(args) => uz801::activate_usb_debug(&args.admin_ip).await.context("\nFailed to activate USB debug on the Uz801")?,
UtilSubCommand::TplinkStartTelnet(options) => {
tplink::start_telnet(&options.admin_ip).await?;
@@ -243,19 +269,75 @@ async fn run() -> Result<(), Error> {
}
UtilSubCommand::WingtechStartTelnet(args) => wingtech::start_telnet(&args.admin_ip, &args.admin_password).await.context("\nFailed to start telnet on the Wingtech CT2MHS01")?,
UtilSubCommand::WingtechStartAdb(args) => wingtech::start_adb(&args.admin_ip, &args.admin_password).await.context("\nFailed to start adb on the Wingtech CT2MHS01")?,
#[cfg(not(target_os = "android"))]
UtilSubCommand::PinephoneStartAdb => pinephone::start_adb().await.context("\nFailed to start adb on the PinePhone's modem")?,
#[cfg(not(target_os = "android"))]
UtilSubCommand::PinephoneStopAdb => pinephone::stop_adb().await.context("\nFailed to stop adb on the PinePhone's modem")?,
UtilSubCommand::OrbicStartTelnet(args) => orbic_network::start_telnet(&args.admin_ip, &args.admin_username, args.admin_password.as_deref()).await.context("\\nFailed to start telnet on the Orbic RC400L")?,
}
}
}
Ok(())
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
if let Err(e) = run().await {
eprintln!("{e:?}");
std::process::exit(1);
/// Type alias for output callback function
pub type OutputCallback = Box<dyn Fn(&str) + Send + Sync>;
/// Run the installer with CLI arguments and optional output callback
///
/// # Arguments
/// * `args` - Command-line arguments (including program name as `args[0]`)
/// * `callback` - Optional function to receive stdout/stderr output
///
/// # Returns
/// * `Ok(())` on success
/// * `Err(anyhow::Error)` on failure with full error context
///
/// # Example
/// ```no_run
/// use installer;
///
/// // if the callback is None, stdout/stderr is going to be used
/// let result = installer::run_with_callback(
/// vec!["installer".to_string(), "orbic-network".to_string(), "--admin-password".to_string(), "12345".to_string()],
/// Some(Box::new(|output| {
/// print!("{}", output);
/// }))
/// );
/// ```
pub fn run_with_callback(args: Vec<String>, callback: Option<OutputCallback>) -> Result<(), Error> {
// Set up the callback if provided
let _guard;
if let Some(cb) = callback {
_guard = output::set_output_callback(move |s: &str| cb(s));
}
// Create a Tokio runtime and run the installer
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("Failed to create Tokio runtime")?
.block_on(async {
// Parse arguments
let parsed_args = Args::try_parse_from(&args).context("Failed to parse arguments")?;
// Run the installer
run(parsed_args).await
})
}
/// Get the version of the installer
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
/// Run the CLI installer
///
/// This function is public so the binary can call it, but library users
/// should use the typed functions like `run_with_callback` instead.
pub async fn main_cli() -> Result<(), Error> {
let args = Args::parse();
run(args).await
}

View File

@@ -1,7 +1,7 @@
#[cfg(target_os = "windows")]
use std::io::stdin;
use std::io::{ErrorKind, Write};
use std::io::ErrorKind;
use std::path::Path;
use std::time::Duration;
@@ -12,7 +12,8 @@ use nusb::transfer::{Control, ControlType, Recipient, RequestBuffer};
use sha2::{Digest, Sha256};
use tokio::time::sleep;
use crate::util::{echo, open_usb_device};
use crate::output::{print, println};
use crate::util::open_usb_device;
use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT};
pub const ORBIC_NOT_FOUND: &str = r#"No Orbic device found.
@@ -54,7 +55,7 @@ const RNDIS_INTERFACE: u8 = 1;
#[cfg(target_os = "windows")]
async fn confirm() -> Result<bool> {
println!("{}", WINDOWS_WARNING);
echo!("Do you wish to proceed? Enter 'yes' to install> ");
print!("Do you wish to proceed? Enter 'yes' to install> ");
let mut input = String::new();
stdin().read_line(&mut input)?;
Ok(input.trim() == "yes")
@@ -75,13 +76,13 @@ pub async fn install() -> Result<()> {
}
let mut adb_device = force_debug_mode().await?;
echo!("Installing rootshell... ");
print!("Installing rootshell... ");
setup_rootshell(&mut adb_device).await?;
println!("done");
echo!("Installing rayhunter... ");
print!("Installing rayhunter... ");
let mut adb_device = setup_rayhunter(adb_device).await?;
println!("done");
echo!("Testing rayhunter... ");
print!("Testing rayhunter... ");
test_rayhunter(&mut adb_device).await?;
println!("done");
Ok(())
@@ -101,11 +102,11 @@ pub async fn shell() -> Result<()> {
async fn force_debug_mode() -> Result<ADBUSBDevice> {
println!("Forcing a switch into the debug mode to enable ADB");
enable_command_mode()?;
echo!("ADB enabled, waiting for reboot... ");
print!("ADB enabled, waiting for reboot... ");
let mut adb_device = get_adb().await?;
adb_setup_serial(&mut adb_device).await?;
println!("it's alive!");
echo!("Waiting for atfwd_daemon to startup... ");
print!("Waiting for atfwd_daemon to startup... ");
adb_command(&mut adb_device, &["pgrep", "atfwd_daemon"])?;
println!("done");
Ok(adb_device)
@@ -159,7 +160,7 @@ async fn setup_rayhunter(mut adb_device: ADBUSBDevice) -> Result<ADBUSBDevice> {
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/rayhunter_daemon").await?;
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/misc-daemon").await?;
println!("done");
echo!("Waiting for reboot... ");
print!("Waiting for reboot... ");
adb_at_syscmd(&mut adb_device, "shutdown -r -t 1 now").await?;
// first wait for shutdown (it can take ~10s)
tokio::time::timeout(Duration::from_secs(30), async {

View File

@@ -1,4 +1,3 @@
use std::io::Write;
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::Duration;
@@ -9,7 +8,8 @@ use serde::Deserialize;
use tokio::time::sleep;
use crate::orbic_auth::{LoginInfo, LoginRequest, LoginResponse, encode_password};
use crate::util::{echo, telnet_send_command, telnet_send_file};
use crate::output::{eprintln, print, println};
use crate::util::{telnet_send_command, telnet_send_file};
use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT};
#[derive(Deserialize, Debug)]
@@ -128,7 +128,7 @@ pub async fn start_telnet(
anyhow::bail!("--admin-password is required");
};
echo!("Logging in and starting telnet... ");
print!("Logging in and starting telnet... ");
login_and_exploit(admin_ip, admin_username, admin_password).await?;
println!("done");
@@ -154,11 +154,11 @@ pub async fn install(
anyhow::bail!("exiting");
};
echo!("Logging in and starting telnet... ");
print!("Logging in and starting telnet... ");
login_and_exploit(&admin_ip, &admin_username, &admin_password).await?;
println!("done");
echo!("Waiting for telnet to become available... ");
print!("Waiting for telnet to become available... ");
wait_for_telnet(&admin_ip).await?;
println!("done");

112
installer/src/output.rs Normal file
View File

@@ -0,0 +1,112 @@
//! Output handling for the installer
//!
//! This module provides custom print macros that can be intercepted by setting
//! a callback function. This is essential for FFI usage where stdout/stderr
//! redirection doesn't work reliably (especially on Android).
use std::io::Write;
use std::sync::Mutex;
/// Type for the output callback function
type OutputCallbackFn = Box<dyn Fn(&str) + Send + Sync>;
/// Global output callback storage
static OUTPUT_CALLBACK: Mutex<Option<OutputCallbackFn>> = Mutex::new(None);
/// Set the global output callback
///
/// All output from `println!` and `eprintln!` will be sent to this callback.
/// If no callback is set, output goes to stdout/stderr as normal.
///
/// Returns a guard that when dropped, resets the callback.
pub(crate) fn set_output_callback<F>(callback: F) -> OutputCallbackGuard
where
F: Fn(&str) + Send + Sync + 'static,
{
*OUTPUT_CALLBACK.lock().unwrap() = Some(Box::new(callback));
OutputCallbackGuard
}
pub struct OutputCallbackGuard;
impl Drop for OutputCallbackGuard {
fn drop(&mut self) {
clear_output_callback();
}
}
/// Clear the global output callback
pub(crate) fn clear_output_callback() {
*OUTPUT_CALLBACK.lock().unwrap() = None;
}
/// Write a line to the output (either callback or stdout)
pub(crate) fn write_output_line(s: &str) {
if let Ok(guard) = OUTPUT_CALLBACK.lock()
&& let Some(ref callback) = *guard
{
callback(s);
callback("\n");
return;
}
// Fallback to stdout if no callback or lock failed
std::println!("{}", s);
let _ = std::io::stdout().flush();
}
/// Write an error line to the output (either callback or stderr)
pub(crate) fn write_error_line(s: &str) {
if let Ok(guard) = OUTPUT_CALLBACK.lock()
&& let Some(ref callback) = *guard
{
callback(s);
callback("\n");
return;
}
// Fallback to stderr if no callback or lock failed
std::eprintln!("{}", s);
let _ = std::io::stderr().flush();
}
/// Write raw output without newline (either callback or stdout)
pub(crate) fn write_output_raw(s: &str) {
if let Ok(guard) = OUTPUT_CALLBACK.lock()
&& let Some(ref callback) = *guard
{
callback(s);
return;
}
// Fallback to stdout if no callback or lock failed
std::print!("{}", s);
let _ = std::io::stdout().flush();
}
/// Shadow println! macro to respect the output callback
macro_rules! println {
() => {
$crate::output::write_output_line("")
};
($($arg:tt)*) => {{
$crate::output::write_output_line(&format!($($arg)*))
}};
}
pub(crate) use println;
/// Shadow eprintln! macro to respect the output callback
macro_rules! eprintln {
() => {
$crate::output::write_error_line("")
};
($($arg:tt)*) => {{
$crate::output::write_error_line(&format!($($arg)*))
}};
}
pub(crate) use eprintln;
/// Shadow print! macro to respect the output callback
macro_rules! print {
($($arg:tt)*) => {{
$crate::output::write_output_raw(&format!($($arg)*))
}};
}
pub(crate) use print;

View File

@@ -1,4 +1,3 @@
use std::io::Write;
use std::path::Path;
use std::time::Duration;
@@ -11,7 +10,8 @@ use nusb::transfer::{Control, ControlType, Recipient, RequestBuffer};
use tokio::time::sleep;
use crate::orbic::test_rayhunter;
use crate::util::{echo, open_usb_device};
use crate::output::{print, println};
use crate::util::open_usb_device;
use crate::{CONFIG_TOML, RAYHUNTER_DAEMON_INIT};
const USB_VENDOR_ID: u16 = 0x2C7C;
@@ -19,7 +19,7 @@ const USB_PRODUCT_ID: u16 = 0x125;
const USB_INTERFACE_NUMBER: u8 = 2;
pub async fn install() -> Result<()> {
echo!("Unlocking modem ... ");
print!("Unlocking modem ... ");
start_adb().await?;
sleep(Duration::from_secs(3)).await;
let mut adb = ADBUSBDevice::new(USB_VENDOR_ID, USB_PRODUCT_ID).unwrap();
@@ -54,13 +54,13 @@ pub async fn install() -> Result<()> {
adb.run_command(&["shutdown -r -t 1 now"], "exit code 0")?;
sleep(Duration::from_secs(30)).await;
echo!("Unlocking modem ... ");
print!("Unlocking modem ... ");
start_adb().await?;
sleep(Duration::from_secs(3)).await;
let mut adb = ADBUSBDevice::new(USB_VENDOR_ID, USB_PRODUCT_ID).unwrap();
println!("ok");
echo!("Testing rayhunter ... ");
print!("Testing rayhunter ... ");
test_rayhunter(&mut adb).await?;
println!("ok");
println!("rayhunter is running on the modem. Use adb to access the web interface.");
@@ -198,7 +198,7 @@ impl Install for ADBUSBDevice {
/// Transfer a file to the modem's filesystem with adb push.
/// Validates the file sends successfully to /tmp before overwriting the destination.
fn install_file(&mut self, dest: &str, mut payload: &[u8]) -> Result<()> {
echo!("Sending file {dest} ... ");
print!("Sending file {dest} ... ");
let file_name = Path::new(dest)
.file_name()
.ok_or_else(|| anyhow!("{dest} does not have a file name"))?

View File

@@ -4,7 +4,6 @@
/// WT_INNER_VERSION=SW_Q89527AA1_V045_M11_TMO_USR_MP
/// WT_PRODUCTION_VERSION=TMOHS1_00.05.20
/// WT_HARDWARE_VERSION=89527_1_11
use std::io::Write;
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::Duration;
@@ -13,7 +12,8 @@ use anyhow::Result;
use tokio::time::sleep;
use crate::TmobileArgs as Args;
use crate::util::{echo, http_ok_every, telnet_send_command, telnet_send_file};
use crate::output::{print, println};
use crate::util::{http_ok_every, telnet_send_command, telnet_send_file};
use crate::wingtech::start_telnet;
pub async fn install(
@@ -26,12 +26,12 @@ pub async fn install(
}
async fn run_install(admin_ip: String, admin_password: String) -> Result<()> {
echo!("Starting telnet ... ");
print!("Starting telnet ... ");
start_telnet(&admin_ip, &admin_password).await?;
sleep(Duration::from_millis(200)).await;
println!("ok");
echo!("Connecting via telnet to {admin_ip} ... ");
print!("Connecting via telnet to {admin_ip} ... ");
let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap();
telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0", true).await?;
println!("ok");
@@ -96,7 +96,7 @@ async fn run_install(admin_ip: String, admin_password: String) -> Result<()> {
telnet_send_command(addr, "reboot", "exit code 0", true).await?;
sleep(Duration::from_secs(30)).await;
echo!("Testing rayhunter ... ");
print!("Testing rayhunter ... ");
let max_failures = 10;
http_ok_every(
format!("http://{admin_ip}:8080/index.html"),

View File

@@ -18,6 +18,7 @@ use serde::Deserialize;
use tokio::time::sleep;
use crate::InstallTpLink;
use crate::output::println;
use crate::util::{telnet_send_command, telnet_send_file};
type HttpProxyClient = hyper_util::client::legacy::Client<HttpConnector, Body>;

View File

@@ -1,4 +1,3 @@
use std::io::Write;
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::Duration;
@@ -10,13 +9,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{sleep, timeout};
macro_rules! echo {
($($arg:tt)*) => {
print!($($arg)*);
let _ = std::io::stdout().flush();
};
}
pub(crate) use echo;
use crate::output::{print, println};
pub async fn telnet_send_command_with_output(
addr: SocketAddr,
@@ -91,7 +84,7 @@ pub async fn telnet_send_file(
payload: &[u8],
wait_for_prompt: bool,
) -> Result<()> {
echo!("Sending file {filename}... ");
print!("Sending file {filename} ... ");
let nc_output = {
let filename = filename.to_owned();
let handle = tokio::spawn(async move {
@@ -122,7 +115,7 @@ pub async fn telnet_send_file(
break;
}
echo!("attempt {attempts}... ");
print!("attempt {attempts}... ");
}
{
@@ -216,6 +209,7 @@ pub async fn http_ok_every(
}
/// General function to open a USB device
#[cfg(not(target_os = "android"))]
pub fn open_usb_device(vid: u16, pid: u16) -> Result<Option<Device>> {
let devices = match nusb::list_devices() {
Ok(d) => d,

View File

@@ -1,4 +1,3 @@
use std::io::Write;
use std::path::Path;
/// Installer for the Uz801 hotspot.
///
@@ -15,30 +14,30 @@ use md5::compute as md5_compute;
use tokio::time::sleep;
use crate::Uz801Args as Args;
use crate::util::echo;
use crate::output::{print, println};
pub async fn install(Args { admin_ip }: Args) -> Result<()> {
run_install(admin_ip).await
}
async fn run_install(admin_ip: String) -> Result<()> {
echo!("Activating USB debugging backdoor... ");
print!("Activating USB debugging backdoor... ");
activate_usb_debug(&admin_ip).await?;
println!("ok");
echo!("Waiting for device reboot and ADB connection... ");
print!("Waiting for device reboot and ADB connection... ");
let mut adb_device = wait_for_adb().await?;
println!("ok");
echo!("Installing rayhunter files... ");
print!("Installing rayhunter files... ");
install_rayhunter_files(&mut adb_device).await?;
println!("ok");
echo!("Modifying startup script... ");
print!("Modifying startup script... ");
modify_startup_script(&mut adb_device).await?;
println!("ok");
echo!("Rebooting the device... ");
print!("Rebooting the device... ");
let _ = adb_device.reboot(adb_client::RebootType::System);
println!("ok");
@@ -55,7 +54,7 @@ pub async fn activate_usb_debug(admin_ip: &str) -> Result<()> {
let origin = format!("http://{admin_ip}");
// Check if device is online
echo!("Checking if device is online... ");
print!("Checking if device is online... ");
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()?;

View File

@@ -4,7 +4,6 @@
/// WT_INNER_VERSION=SW_Q89323AA1_V057_M10_CRICKET_USR_MP
/// WT_PRODUCTION_VERSION=CT2MHS01_0.04.55
/// WT_HARDWARE_VERSION=89323_1_20
use std::io::Write;
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::Duration;
@@ -19,7 +18,8 @@ use serde::Deserialize;
use tokio::time::sleep;
use crate::WingtechArgs as Args;
use crate::util::{echo, http_ok_every, telnet_send_command, telnet_send_file};
use crate::output::{print, println};
use crate::util::{http_ok_every, telnet_send_command, telnet_send_file};
#[derive(Deserialize)]
struct LoginResponse {
@@ -89,11 +89,11 @@ pub async fn run_command(admin_ip: &str, admin_password: &str, cmd: &str) -> Res
}
async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Result<()> {
echo!("Starting telnet ... ");
print!("Starting telnet ... ");
start_telnet(&admin_ip, &admin_password).await?;
println!("ok");
echo!("Connecting via telnet to {admin_ip} ... ");
print!("Connecting via telnet to {admin_ip} ... ");
let addr = SocketAddr::from_str(&format!("{admin_ip}:23")).unwrap();
telnet_send_command(addr, "mkdir -p /data/rayhunter", "exit code 0", true).await?;
println!("ok");
@@ -149,7 +149,7 @@ async fn wingtech_run_install(admin_ip: String, admin_password: String) -> Resul
telnet_send_command(addr, "shutdown -r -t 1 now", "exit code 0", true).await?;
sleep(Duration::from_secs(30)).await;
echo!("Testing rayhunter ... ");
print!("Testing rayhunter ... ");
let max_failures = 10;
http_ok_every(
format!("http://{admin_ip}:8080/index.html"),