update: implement tor/arti, tls, basic metadata fetch

This commit is contained in:
hinto-janaiyo 2022-10-24 22:58:42 -04:00
parent 5a686aaa1d
commit bf81b2c57c
No known key found for this signature in database
GPG key ID: D7483F6CA27D1B1D
8 changed files with 2611 additions and 109 deletions

2153
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,27 +4,41 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
dirs = "4.0.0" anyhow = "1.0.65"
arti-client = "0.7.0"
arti-hyper = "0.7.0"
bytes = "1.2.1"
chrono = "0.4.22" chrono = "0.4.22"
dirs = "4.0.0"
eframe = "0.19.0" eframe = "0.19.0"
egui = "0.19.0" egui = "0.19.0"
egui_extras = { version = "0.19.0", features = ["image"] } egui_extras = { version = "0.19.0", features = ["image"] }
env_logger = "0.9.1" env_logger = "0.9.1"
figment = { version = "0.10.8", features = ["toml"] } figment = { version = "0.10.8", features = ["toml"] }
flate2 = "1.0"
hex-literal = "0.3.4" hex-literal = "0.3.4"
hyper = "0.14.20"
hyper-tls = "0.5.0"
image = { version = "0.24.4", features = ["png"] } image = { version = "0.24.4", features = ["png"] }
log = "0.4.17" log = "0.4.17"
monero = "0.18.0" monero = "0.18.0"
num_cpus = "1.13.1" num_cpus = "1.13.1"
num-format = "0.4.0" num-format = "0.4.0"
openssl = { version = "*", features = ["vendored"] }
rand = "0.8.5" rand = "0.8.5"
regex = "1.6.0" regex = "1.6.0"
reqwest = { version = "0.11.12", features = ["blocking", "json"] } reqwest = { version = "0.11.12", features = ["blocking", "json"] }
serde = "1.0.145" serde = "1.0.145"
serde_derive = "1.0.145" serde_derive = "1.0.145"
serde_json = "1.0"
sha2 = "0.10.6" sha2 = "0.10.6"
tar = "0.4.38"
tls-api = "0.9.0"
tls-api-native-tls = "0.9.0"
tokio = { version = "1.21.2", features = ["full"] }
toml = "0.5.9" toml = "0.5.9"
openssl = { version = "*", features = ["vendored"] } tor-rtcompat = "0.7.0"
walkdir = "2.3.2"
[profile.optimized] [profile.optimized]
codegen-units = 1 codegen-units = 1

View file

@ -26,11 +26,6 @@ pub const P2POOL_BASE_ARGS: &'static str = "";
pub const XMRIG_BASE_ARGS: &'static str = "--http-host=127.0.0.1 --http-port=18088 --algo=rx/0 --coin=Monero --randomx-cache-qos"; pub const XMRIG_BASE_ARGS: &'static str = "--http-host=127.0.0.1 --http-port=18088 --algo=rx/0 --coin=Monero --randomx-cache-qos";
pub const HORIZONTAL: &'static str = "--------------------------------------------"; pub const HORIZONTAL: &'static str = "--------------------------------------------";
// Update data
pub const GITHUB_METADATA_GUPAX: &'static str = "https://api.github.com/repos/hinto-janaiyo/gupax/releases/latest";
pub const GITHUB_METADATA_P2POOL: &'static str = "https://api.github.com/repos/SChernykh/p2pool/releases/latest";
pub const GITHUB_METADATA_XMRIG: &'static str = "https://api.github.com/repos/xmrig/xmrig/releases/latest";
// OS specific // OS specific
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub const OS: &'static str = " Windows"; pub const OS: &'static str = " Windows";

View file

@ -20,6 +20,7 @@ use crate::App;
use egui::WidgetType::Button; use egui::WidgetType::Button;
use crate::constants::*; use crate::constants::*;
use crate::state::Gupax; use crate::state::Gupax;
use crate::update::*;
impl Gupax { impl Gupax {
pub fn show(state: &mut Gupax, ctx: &egui::Context, ui: &mut egui::Ui) { pub fn show(state: &mut Gupax, ctx: &egui::Context, ui: &mut egui::Ui) {

View file

@ -50,7 +50,8 @@ mod status;
mod gupax; mod gupax;
mod p2pool; mod p2pool;
mod xmrig; mod xmrig;
use {ferris::*,constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*}; mod update;
use {ferris::*,constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*,update::*};
//---------------------------------------------------------------------------------------------------- Struct + Impl //---------------------------------------------------------------------------------------------------- Struct + Impl
// The state of the outer main [App]. // The state of the outer main [App].
@ -66,15 +67,11 @@ pub struct App {
node: Arc<Mutex<NodeStruct>>, // Data on community nodes node: Arc<Mutex<NodeStruct>>, // Data on community nodes
width: f32, // Top-level width width: f32, // Top-level width
height: f32, // Top-level height height: f32, // Top-level height
// State: // State
// og = Old state to compare against og: State, // og = Old state to compare against
// state = Working state (current settings) state: State, // state = Working state (current settings)
// Instead of comparing [og == state] every frame, update: Update, // State for update data [update.rs]
// the [diff] bool will be the signal for [Reset/Save]. diff: bool, // Instead of comparing [og == state] every frame, this bool indicates changes
og: State,
state: State,
// update: Update, // State for update data [update.rs]
diff: bool,
// Process/update state: // Process/update state:
// Doesn't make sense to save this on disk // Doesn't make sense to save this on disk
// so it's represented as a bool here. // so it's represented as a bool here.
@ -105,6 +102,7 @@ impl App {
} }
fn new() -> Self { fn new() -> Self {
let throwaway = State::default();
let app = Self { let app = Self {
tab: Tab::default(), tab: Tab::default(),
quit: false, quit: false,
@ -116,7 +114,7 @@ impl App {
node: Arc::new(Mutex::new(NodeStruct::default())), node: Arc::new(Mutex::new(NodeStruct::default())),
og: State::default(), og: State::default(),
state: State::default(), state: State::default(),
// update: Update::default(), update: Update::new(&throwaway.gupax.absolute_p2pool_path, &throwaway.gupax.absolute_xmrig_path, true),
diff: false, diff: false,
p2pool: false, p2pool: false,
xmrig: false, xmrig: false,
@ -133,7 +131,7 @@ impl App {
}; };
// Apply arg state // Apply arg state
let mut app = parse_args(app); let mut app = parse_args(app);
// Get exe path + random tmp folder // Get exe path + random tmp folder
app.exe = match get_exe_dir() { app.exe = match get_exe_dir() {
Ok(exe) => exe, Ok(exe) => exe,
Err(err) => { panic_app(err.to_string()); exit(1); }, Err(err) => { panic_app(err.to_string()); exit(1); },
@ -149,6 +147,7 @@ impl App {
app.og.xmrig.max_threads = num_cpus::get(); app.og.xmrig.max_threads = num_cpus::get();
if app.og.xmrig.current_threads > app.og.xmrig.max_threads { app.og.xmrig.current_threads = app.og.xmrig.max_threads; } if app.og.xmrig.current_threads > app.og.xmrig.max_threads { app.og.xmrig.current_threads = app.og.xmrig.max_threads; }
app.state = app.og.clone(); app.state = app.og.clone();
app.update = Update::new(&app.og.gupax.absolute_p2pool_path, &app.og.gupax.absolute_xmrig_path, app.og.gupax.update_via_tor);
app app
} }
} }
@ -273,14 +272,23 @@ fn parse_args(mut app: App) -> App {
app app
} }
fn get_exe_dir() -> Result<String, std::io::Error> { // Get absolute [Gupax] binary path
pub fn get_exe() -> Result<String, std::io::Error> {
match std::env::current_exe() { match std::env::current_exe() {
Ok(mut path) => { path.pop(); Ok(path.into_os_string().into_string().unwrap()) }, Ok(mut path) => { Ok(path.display().to_string()) },
Err(err) => { error!("Couldn't get exe basepath PATH"); return Err(err) }, Err(err) => { error!("Couldn't get exe basepath PATH"); return Err(err) },
} }
} }
fn get_rand_tmp(path: &String) -> String { // Get absolute [Gupax] directory path
pub fn get_exe_dir() -> Result<String, std::io::Error> {
match std::env::current_exe() {
Ok(mut path) => { path.pop(); Ok(path.display().to_string()) },
Err(err) => { error!("Couldn't get exe basepath PATH"); return Err(err) },
}
}
pub fn get_rand_tmp(path: &String) -> String {
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
let rand: String = thread_rng() let rand: String = thread_rng()
@ -361,6 +369,11 @@ impl eframe::App for App {
} }
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
// *-------*
// | DEBUG |
// *-------*
self.update.start();
thread::sleep;
// This sets the top level Ui dimensions. // This sets the top level Ui dimensions.
// Used as a reference for other uis. // Used as a reference for other uis.
egui::CentralPanel::default().show(ctx, |ui| { self.width = ui.available_width(); self.height = ui.available_height(); }); egui::CentralPanel::default().show(ctx, |ui| { self.width = ui.available_width(); self.height = ui.available_height(); });

View file

@ -103,7 +103,7 @@ impl NodeStruct {
// find the fastest/slowest one for the user. // find the fastest/slowest one for the user.
// The process: // The process:
// - Send [get_info] JSON-RPC requests over HTTP // - Send [get_info] JSON-RPC requests over HTTP
// - To prevent fingerprinting, randomly send [1-5] calls // - To prevent fingerprinting, randomly send [2-4] calls
// - Measure each request in milliseconds as [u128] // - Measure each request in milliseconds as [u128]
// - Timeout on requests over 5 seconds // - Timeout on requests over 5 seconds
// - Calculate average time // - Calculate average time
@ -121,16 +121,26 @@ impl NodeStruct {
// default = GRAY // default = GRAY
pub fn ping() -> PingResult { pub fn ping() -> PingResult {
info!("Starting community node pings..."); info!("Starting community node pings...");
// Get node list
let mut nodes = NodeStruct::default(); let mut nodes = NodeStruct::default();
// Create JSON request
let mut get_info = HashMap::new(); let mut get_info = HashMap::new();
get_info.insert("jsonrpc", "2.0"); get_info.insert("jsonrpc", "2.0");
get_info.insert("id", "0"); get_info.insert("id", "0");
get_info.insert("method", "get_info"); get_info.insert("method", "get_info");
// Misc Settings
let mut vec: Vec<(u128, NodeEnum)> = Vec::new(); let mut vec: Vec<(u128, NodeEnum)> = Vec::new();
let fastest = false;
// Create HTTP Client
let timeout_sec = Duration::from_millis(5000); let timeout_sec = Duration::from_millis(5000);
let client = reqwest::blocking::ClientBuilder::new();
let client = reqwest::blocking::ClientBuilder::timeout(client, timeout_sec);
let client = reqwest::blocking::ClientBuilder::build(client).unwrap();
for ip in NODE_IPS.iter() { for ip in NODE_IPS.iter() {
// Match IP
let id = match *ip { let id = match *ip {
C3POOL => C3pool, C3POOL => C3pool,
CAKE => Cake, CAKE => Cake,
@ -143,27 +153,28 @@ impl NodeStruct {
SETH => Seth, SETH => Seth,
SUPPORTXMR => SupportXmr, SUPPORTXMR => SupportXmr,
SUPPORTXMR_IR => SupportXmrIr, SUPPORTXMR_IR => SupportXmrIr,
// XMRVSBEAST => XmrVsBeast,
_ => XmrVsBeast, _ => XmrVsBeast,
}; };
// Misc
let mut timeout = false; let mut timeout = false;
let mut mid = Duration::new(0, 0); let mut mid = Duration::new(0, 0);
let max = rand::thread_rng().gen_range(1..5); let max = rand::thread_rng().gen_range(2..4);
// Start JSON-RPC request
for i in 1..=max { for i in 1..=max {
let client = reqwest::blocking::ClientBuilder::new();
let client = reqwest::blocking::ClientBuilder::timeout(client, timeout_sec);
let client = reqwest::blocking::ClientBuilder::build(client).unwrap();
let http = "http://".to_owned() + &**ip + "/json_rpc";
let now = Instant::now(); let now = Instant::now();
let http = "http://".to_owned() + &**ip + "/json_rpc";
match client.post(http).json(&get_info).send() { match client.post(http).json(&get_info).send() {
Ok(r) => mid += now.elapsed(), Ok(_) => mid += now.elapsed(),
Err(err) => { Err(err) => {
error!("Timeout on [{:#?}: {}] (over 5 seconds)", id, ip); error!("Timeout on [{:#?}: {}] (over 5 seconds) | {}", id, ip, err);
mid += timeout_sec; mid += timeout_sec;
timeout = true; timeout = true;
}, },
}; };
} }
// Calculate average
let ms = mid.as_millis() / 3; let ms = mid.as_millis() / 3;
vec.push((ms, id)); vec.push((ms, id));
info!("{}ms ... {} calls ... {}", ms, max, ip); info!("{}ms ... {} calls ... {}", ms, max, ip);
@ -192,6 +203,8 @@ impl NodeStruct {
XmrVsBeast => { nodes.xmrvsbeast.ms = ms; nodes.xmrvsbeast.color = color; }, XmrVsBeast => { nodes.xmrvsbeast.ms = ms; nodes.xmrvsbeast.color = color; },
} }
} }
// Calculate fastest out of all nodes
let mut best_ms: u128 = vec[0].0; let mut best_ms: u128 = vec[0].0;
let mut fastest: NodeEnum = vec[0].1; let mut fastest: NodeEnum = vec[0].1;
for (ms, id) in vec.iter() { for (ms, id) in vec.iter() {

View file

@ -51,6 +51,7 @@ impl State {
auto_node: true, auto_node: true,
ask_before_quit: true, ask_before_quit: true,
save_before_quit: true, save_before_quit: true,
update_via_tor: true,
p2pool_path: DEFAULT_P2POOL_PATH.to_string(), p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(), xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
absolute_p2pool_path: Self::into_absolute_path(DEFAULT_P2POOL_PATH.to_string()).unwrap(), absolute_p2pool_path: Self::into_absolute_path(DEFAULT_P2POOL_PATH.to_string()).unwrap(),
@ -300,6 +301,7 @@ pub struct Gupax {
pub auto_node: bool, pub auto_node: bool,
pub ask_before_quit: bool, pub ask_before_quit: bool,
pub save_before_quit: bool, pub save_before_quit: bool,
pub update_via_tor: bool,
pub p2pool_path: String, pub p2pool_path: String,
pub xmrig_path: String, pub xmrig_path: String,
pub absolute_p2pool_path: PathBuf, pub absolute_p2pool_path: PathBuf,

View file

@ -14,54 +14,455 @@
//// You should have received a copy of the GNU General Public License //// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::State; //---------------------------------------------------------------------------------------------------- Imports
use serde_derive::{Serialize,Deserialize};
use tokio::task::JoinHandle;
use std::time::Duration;
use std::sync::{Arc,Mutex};
use std::os::unix::fs::OpenOptionsExt;
use std::io::{Read,Write};
//use crate::{Name::*,State};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use anyhow::Error;
use arti_hyper::*;
use arti_client::{TorClient,TorClientConfig};
use tokio::io::{AsyncReadExt,AsyncWriteExt};
use tls_api::{TlsConnector, TlsConnectorBuilder};
use hyper::header::HeaderValue;
use hyper::{Client,Body,Request};
use hyper_tls::HttpsConnector;
use arti_hyper::*;
use log::*;
use crate::update::Name::*;
use std::path::PathBuf;
struct Update { // use tls_api_native_tls::{TlsConnector,TlsConnectorBuilder};
new_gupax: String,
new_p2pool: String, //---------------------------------------------------------------------------------------------------- Constants
new_xmrig: String, // Package naming schemes:
path_gupax: String, // gupax | gupax-vX.X.X-(windows|macos|linux)-x64.(zip|tar.gz)
path_p2pool: String, // p2pool | p2pool-vX.X.X-(windows|macos|linux)-x64.(zip|tar.gz)
path_xmrig: String, // xmrig | xmrig-X.X.X-(msvc-win64|macos-x64|linux-static-x64).(zip|tar.gz)
updating: Arc<Mutex<bool>> // Is the update in progress? //
update_prog: u8, // Not an [f32] because [Eq] doesn't work // Download link = PREFIX + Version (found at runtime) + SUFFIX + Version + EXT
// Example: https://github.com/hinto-janaiyo/gupax/releases/download/v0.0.1/gupax-v0.0.1-linux-standalone-x64
//
// Exceptions (there are always exceptions...):
// - XMRig doesn't have a [v], so it is [xmrig-6.18.0-...]
// - XMRig separates the hash and signature
// - P2Pool hashes are in UPPERCASE
// - Gupax will be downloaded as a standalone binary (no decompression/extraction needed)
const GUPAX_METADATA: &'static str = "https://api.github.com/repos/hinto-janaiyo/gupax/releases/latest";
const P2POOL_METADATA: &'static str = "https://api.github.com/repos/SChernykh/p2pool/releases/latest";
const XMRIG_METADATA: &'static str = "https://api.github.com/repos/xmrig/xmrig/releases/latest";
const GUPAX_PREFIX: &'static str = "https://github.com/hinto-janaiyo/gupax/releases/download/";
const P2POOL_PREFIX: &'static str = "https://github.com/SChernykh/p2pool/releases/download/";
const XMRIG_PREFIX: &'static str = "https://github.com/xmrig/xmrig/releases/download/";
const GUPAX_SUFFIX: &'static str = "/gupax-";
const P2POOL_SUFFIX: &'static str = "/p2pool-";
const XMRIG_SUFFIX: &'static str = "/xmrig-";
const GUPAX_HASH: &'static str = "SHA256SUMS";
const P2POOL_HASH: &'static str = "sha256sums.txt.asc";
const XMRIG_HASH: &'static str = "SHA256SUMS";
#[cfg(target_os = "windows")]
const GUPAX_EXTENSION: &'static str = "-windows-standalone-x64.exe";
#[cfg(target_os = "windows")]
const P2POOL_EXTENSION: &'static str = "-windows-x64.zip";
#[cfg(target_os = "windows")]
const XMRIG_EXTENSION: &'static str = "-msvc-win64.zip";
#[cfg(target_os = "macos")]
const GUPAX_EXTENSION: &'static str = "-macos-standalone-x64";
#[cfg(target_os = "macos")]
const P2POOL_EXTENSION: &'static str = "-macos-x64.tar.gz";
#[cfg(target_os = "macos")]
const XMRIG_EXTENSION: &'static str = "-macos-x64.tar.gz";
#[cfg(target_os = "linux")]
const GUPAX_EXTENSION: &'static str = "-linux-standalone-x64";
#[cfg(target_os = "linux")]
const P2POOL_EXTENSION: &'static str = "-linux-x64.tar.gz";
#[cfg(target_os = "linux")]
const XMRIG_EXTENSION: &'static str = "-linux-static-x64.tar.gz";
// Some fake Curl/Wget user-agents because GitHub API requires one and a Tor browser
// user-agent might be fingerprintable without all the associated headers.
const FAKE_USER_AGENT: [&'static str; 50] = [
"Wget/1.16.3",
"Wget/1.17",
"Wget/1.17.1",
"Wget/1.18",
"Wget/1.18",
"Wget/1.19",
"Wget/1.19.1",
"Wget/1.19.2",
"Wget/1.19.3",
"Wget/1.19.4",
"Wget/1.19.5",
"Wget/1.20",
"Wget/1.20.1",
"Wget/1.20.2",
"Wget/1.20.3",
"Wget/1.21",
"Wget/1.21.1",
"Wget/1.21.2",
"Wget/1.21.3",
"curl/7.64.1",
"curl/7.65.0",
"curl/7.65.1",
"curl/7.65.2",
"curl/7.65.3",
"curl/7.66.0",
"curl/7.67.0",
"curl/7.68.0",
"curl/7.69.0",
"curl/7.69.1",
"curl/7.70.0",
"curl/7.70.1",
"curl/7.71.0",
"curl/7.71.1",
"curl/7.72.0",
"curl/7.73.0",
"curl/7.74.0",
"curl/7.75.0",
"curl/7.76.0",
"curl/7.76.1",
"curl/7.77.0",
"curl/7.78.0",
"curl/7.79.0",
"curl/7.79.1",
"curl/7.80.0",
"curl/7.81.0",
"curl/7.82.0",
"curl/7.83.0",
"curl/7.83.1",
"curl/7.84.0",
"curl/7.85.0",
];
const MSG_START: &'static str = "Starting update...";
const MSG_TMP: &'static str = "Creating temporary directory";
const MSG_PKG: &'static str = "Creating package list";
const MSG_TOR: &'static str = "Creating Tor+HTTPS client";
const MSG_HTTPS: &'static str = "Creating HTTPS client";
const MSG_METADATA: &'static str = "Fetching package metadata";
const MSG_ARCHIVE: &'static str = "Downloading packages";
// These two are sequential and not async so no need for a constant message.
// The package in question will be known at runtime, so that will be printed.
//const MSG_EXTRACT: &'static str = "Extracting packages";
//const MSG_UPGRADE: &'static str = "Upgrading packages";
//---------------------------------------------------------------------------------------------------- Update struct/impl
// Contains values needed during update
// Progress bar structure:
// 5% | Create tmp directory
// 5% | Create package list
// 10% | Create Tor/HTTPS client
// 20% | Download Metadata (x3)
// 30% | Download Archive (x3)
// 15% | Extract (x3)
// 15% | Upgrade (x3)
pub struct Update {
path_gupax: String, // Full path to current gupax
path_p2pool: String, // Full path to current p2pool
path_xmrig: String, // Full path to current xmrig
tmp_dir: String, // Full path to temporary directory
updating: Arc<Mutex<bool>>, // Is an update in progress?
prog: Arc<Mutex<u8>>, // Holds the 0-100% progress bar number
msg: Arc<Mutex<String>>, // Message to display on [Gupax] tab while updating
tor: bool, // Is Tor enabled or not?
} }
impl Update { impl Update {
fn new(path_p2pool: String, path_xmrig: String) -> Result<Self, Error> { // Takes in current paths from [State]
let path_gupax = std::env::current_exe()?; pub fn new(path_p2pool: &PathBuf, path_xmrig: &PathBuf, tor: bool) -> Self {
Self { Self {
new_gupax: "?".to_string(), path_gupax: crate::get_exe().unwrap(),
new_p2pool: "?".to_string(), path_p2pool: path_p2pool.display().to_string(),
new_xmrig: "?".to_string(), path_xmrig: path_xmrig.display().to_string(),
path_gupax, tmp_dir: "".to_string(),
path_p2pool, updating: Arc::new(Mutex::new(true)),
path_xmrig, prog: Arc::new(Mutex::new(0)),
updating: Arc::new(Mutex::new(false)), msg: Arc::new(Mutex::new("".to_string())),
update_prog: 0, tor,
} }
} }
fn update(state: &mut State) -> Result((), Error) { // Get a temporary random folder
// for package download contents
// Will look like [/tmp/gupax_A1m98FN3fa/] on Unix
pub fn get_tmp_dir() -> String {
let rand_string: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(10)
.map(char::from)
.collect();
let tmp = std::env::temp_dir();
let tmp = format!("{}{}{}{}", tmp.display(), "/gupax_", rand_string, "/");
info!("Update | TMP directory ... {}", tmp);
tmp
} }
// The HTTPS client created when Tor is enabled:
// TLS implementation | tls-api -> native-tls
// Tor implementatoin | arti
pub async fn get_tor_client() -> Result<ClientEnum, anyhow::Error> {
info!("Update | Creating Tor+HTTPS client...");
let tor = TorClient::create_bootstrapped(TorClientConfig::default()).await?;
let tls = tls_api_native_tls::TlsConnector::builder()?.build()?;
let http = ArtiHttpConnector::new(tor, tls);
let client = ClientEnum::Tor(Client::builder().build(http));
info!("Update | Tor client ... OK");
Ok(client)
}
// The HTTPS client created when Tor is disabled:
// TLS implementation | hyper-tls
pub async fn get_client() -> Result<ClientEnum, anyhow::Error> {
info!("Update | Creating HTTPS client...");
let mut https = hyper_tls::HttpsConnector::new();
https.https_only(true);
let client = ClientEnum::Https(Client::builder().build(https));
info!("Update | HTTPS client ... OK");
Ok(client)
}
// Download process:
// 0. setup tor, client, http, etc
// 1. fill vector with all enums
// 2. loop over vec, download metadata
// 3. if current == version, remove from vec
// 4. loop over vec, download links
// 5. extract, upgrade
#[tokio::main]
pub async fn start(&mut self) -> Result<(), anyhow::Error> {
// Set progress bar
*self.msg.lock().unwrap() = MSG_START.to_string();
*self.prog.lock().unwrap() = 0;
// Get temporary directory
*self.msg.lock().unwrap() = MSG_TMP.to_string();
let tmp_dir = Self::get_tmp_dir();
*self.prog.lock().unwrap() = 5;
// Make Pkg vector
*self.msg.lock().unwrap() = MSG_PKG.to_string();
let vec = vec![
Pkg::new(Gupax, &tmp_dir, self.prog.clone(), self.msg.clone()),
Pkg::new(P2pool, &tmp_dir, self.prog.clone(), self.msg.clone()),
Pkg::new(Xmrig, &tmp_dir, self.prog.clone(), self.msg.clone()),
];
let mut handles: Vec<JoinHandle<()>> = vec![];
*self.prog.lock().unwrap() = 5;
// Create Tor/HTTPS client
let mut client: ClientEnum;
if self.tor {
*self.msg.lock().unwrap() = MSG_TOR.to_string();
client = Self::get_tor_client().await?;
} else {
*self.msg.lock().unwrap() = MSG_HTTPS.to_string();
client = Self::get_client().await?;
}
*self.prog.lock().unwrap() = 10;
// loop for metadata
for pkg in vec.iter() {
info!("Update | Metadata | Starting ... {}", pkg.name);
let name = pkg.name.clone();
let version = Arc::clone(&pkg.version);
let request = hyper::Request::builder()
.method("GET")
.uri(pkg.link_metadata)
.header(hyper::header::USER_AGENT, hyper::header::HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"))
.body(Body::empty())?;
let client = client.clone();
let handle: JoinHandle<()> = tokio::spawn(async move {
Pkg::get_tor_response(name, version, client, request).await;
});
handles.push(handle);
}
for handle in handles {
handle.await?;
}
info!("Update | Metadata ... OK\n");
Ok(())
//----------------------------------------------
//
// // loop for download
// let mut handles: Vec<JoinHandle<()>> = vec![];
// for pkg in vec.iter() {
// let name = pkg.name.clone();
// let bytes = Arc::clone(&pkg.bytes);
// let version = pkg.version.lock().unwrap();
// let link;
// if pkg.name == Name::Xmrig {
// link = pkg.link_prefix.clone() + &version + &pkg.link_suffix_1 + &version[1..] + &pkg.link_suffix_2;
// } else {
// link = pkg.link_prefix.clone() + &version + &pkg.link_suffix_1 + &version + &pkg.link_suffix_2;
// }
// println!("download: {:#?} | {}", pkg.name, link);
// let request = Client::get(&client.clone(), &link);
// let handle: JoinHandle<()> = tokio::spawn(async move {
// get_bytes(request, bytes, name).await;
// });
// handles.push(handle);
// }
// for handle in handles {
// handle.await.unwrap();
// }
// println!("download ... OK\n");
//
// // extract
// let TMP = num();
// std::fs::create_dir(&TMP).unwrap();
// for pkg in vec.iter() {
// let name = TMP.to_string() + &pkg.name.to_string();
// println!("extract: {:#?} | {}", pkg.name, name);
// if pkg.name == Name::Gupax {
// std::fs::OpenOptions::new().mode(0o700).create(true).write(true).open(&name);
// std::fs::write(name, pkg.bytes.lock().unwrap().as_ref()).unwrap();
// } else {
// std::fs::create_dir(&name).unwrap();
// tar::Archive::new(flate2::read::GzDecoder::new(pkg.bytes.lock().unwrap().as_ref())).unpack(name).unwrap();
// }
// }
// println!("extract ... OK");
//
//async fn get_bytes(request: RequestBuilder, bytes: Arc<Mutex<bytes::Bytes>>, name: Name) {
// *bytes.lock().unwrap() = request.send().await.unwrap().bytes().await.unwrap();
// println!("{} download ... OK", name);
//}
//
//async fn func(request: RequestBuilder, version: Arc<Mutex<String>>, name: Name) {
// let response = request.send().await.unwrap().bytes().await.unwrap();
//
// let mut bytes = flate2::read::GzDecoder::new(response.as_ref());
// let mut response = String::new();
// bytes.read_to_string(&mut response).unwrap();
//
// let response: Version = serde_json::from_str(&response).unwrap();
// *version.lock().unwrap() = response.tag_name.clone();
// println!("{} {} ... OK", name, response.tag_name);
//}
}
}
// Wrapper type around Tor/HTTPS client
enum ClientEnum {
Tor(Client<ArtiHttpConnector<tor_rtcompat::PreferredRuntime, tls_api_native_tls::TlsConnector>>),
Https(Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>),
}
//---------------------------------------------------------------------------------------------------- Pkg struct/impl
#[derive(Debug)]
pub struct Pkg {
name: Name,
link_metadata: &'static str,
link_prefix: &'static str,
link_suffix: &'static str,
link_extension: &'static str,
tmp_dir: String,
update_prog: Arc<Mutex<u8>>,
update_msg: Arc<Mutex<String>>,
bytes: Arc<Mutex<bytes::Bytes>>,
version: Arc<Mutex<String>>,
}
impl Pkg {
pub fn new(name: Name, tmp_dir: &String, update_prog: Arc<Mutex<u8>>, update_msg: Arc<Mutex<String>>) -> Self {
let link_metadata = match name {
Gupax => GUPAX_METADATA,
P2pool => P2POOL_METADATA,
Xmrig => XMRIG_METADATA,
};
let link_prefix = match name {
Gupax => GUPAX_PREFIX,
P2pool => P2POOL_PREFIX,
Xmrig => XMRIG_PREFIX,
};
let link_suffix = match name {
Gupax => GUPAX_SUFFIX,
P2pool => P2POOL_SUFFIX,
Xmrig => XMRIG_SUFFIX,
};
let link_extension = match name {
Gupax => GUPAX_EXTENSION,
P2pool => P2POOL_EXTENSION,
Xmrig => XMRIG_EXTENSION,
};
Self {
name,
link_metadata,
link_prefix,
link_suffix,
link_extension,
tmp_dir: tmp_dir.to_string(),
update_prog,
update_msg,
bytes: Arc::new(Mutex::new(bytes::Bytes::new())),
version: Arc::new(Mutex::new(String::new())),
}
}
// Generate GET request based off input URI + fake user agent
pub async fn get_request(self) -> Result<Request<Body>, anyhow::Error> {
let user_agent = FAKE_USER_AGENT[thread_rng().gen_range(0..50)];
let request = Request::builder()
.method("GET")
.uri(self.link_metadata)
.header(hyper::header::USER_AGENT, HeaderValue::from_static(user_agent))
.body(Body::empty())?;
Ok(request)
}
// Get response using [Tor client] + [request]
// and change the [version] under an Arc<Mutex>
pub async fn get_tor_response(name: Name, version: Arc<Mutex<String>>, client: Client<ArtiHttpConnector<tor_rtcompat::PreferredRuntime, tls_api_native_tls::TlsConnector>>, request: Request<Body>) -> Result<(), Error> {
let mut response = client.request(request).await?;
let body = hyper::body::to_bytes(response.body_mut()).await?;
let body: Version = serde_json::from_slice(&body)?;
*version.lock().unwrap() = body.tag_name.clone();
info!("Update | Metadata | {} {} ... OK", name, body.tag_name);
Ok(())
}
// Same thing, but without Tor.
pub async fn get_response(name: Name, version: Arc<Mutex<String>>, client: Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>, request: Request<Body>) -> Result<(), Error> {
let mut response = client.request(request).await?;
let body = hyper::body::to_bytes(response.body_mut()).await?;
let body: Version = serde_json::from_slice(&body)?;
*version.lock().unwrap() = body.tag_name.clone();
info!("Update | Metadata | {} {} ... OK", name, body.tag_name);
Ok(())
}
}
// This inherits the value of [tag_name] from GitHub's JSON API
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct TagName { struct Version {
tag_name: String, tag_name: String,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum Error { pub enum Name {
Io(std::io::Error),
Serialize(toml::ser::Error),
Deserialize(toml::de::Error),
}
#[derive(Debug, Serialize, Deserialize)]
enum Package {
Gupax, Gupax,
P2pool, P2pool,
Xmrig, Xmrig,
} }
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}