add i2p to installation manager

This commit is contained in:
creating2morrow 2023-05-08 02:21:52 -04:00
parent 3c7b5f4da1
commit 1babfcd19c
6 changed files with 107 additions and 41 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ notes.txt
*.zip *.zip
*/monero-x86_64-linux-gnu-v0.18.2.2/** */monero-x86_64-linux-gnu-v0.18.2.2/**
*/i2p-zero-linux.v1.20/** */i2p-zero-linux.v1.20/**
*.jar

View file

@ -1,6 +1,5 @@
use std::{fs, env, process::Command}; use std::{fs, env, process::Command};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{args, utils}; use crate::{args, utils};
use clap::Parser; use clap::Parser;
@ -88,7 +87,15 @@ pub async fn start() {
.expect("i2p-zero failed to start"); .expect("i2p-zero failed to start");
debug!("{:?}", output.stdout); debug!("{:?}", output.stdout);
find_tunnels().await; find_tunnels().await;
{ tokio::spawn(async { check_connection(true).await }); } {
tokio::spawn(async move {
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000);
loop {
tick.recv().unwrap();
check_connection().await;
}
});
}
} }
fn create_tunnel() { fn create_tunnel() {
@ -133,26 +140,9 @@ pub fn get_destination() -> String {
utils::empty_string() utils::empty_string()
} }
pub async fn get_proxy_status() -> HttpProxyStatus { pub async fn check_connection() -> HttpProxyStatus {
check_connection(false).await
}
/// Ping i2pd tunnels local server for status
async fn check_connection(bg: bool) -> HttpProxyStatus {
let client: reqwest::Client = reqwest::Client::new(); let client: reqwest::Client = reqwest::Client::new();
let host: &str = "http://localhost:7657/tunnels"; let host: &str = "http://localhost:7657/tunnels";
if bg {
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000);
loop {
tick.recv().unwrap();
return process_connection_info(client, host).await;
}
} else {
return process_connection_info(client, host).await;
}
}
async fn process_connection_info(client: Client, host: &str) -> HttpProxyStatus {
match client.get(host).send().await { match client.get(host).send().await {
Ok(response) => { Ok(response) => {
// do some parsing here to check the status // do some parsing here to check the status

View file

@ -17,7 +17,11 @@ pub const NEVMES_JWT_SECRET_KEY: &str = "NEVMES_JWT_SECRET_KEY";
/// The latest monero release download /// The latest monero release download
pub const MONERO_RELEASE_VERSION: &str = "monero-linux-x64-v0.18.2.2.tar.bz2"; pub const MONERO_RELEASE_VERSION: &str = "monero-linux-x64-v0.18.2.2.tar.bz2";
pub const MONERO_RELEASE_HASH: &str = "186800de18f67cca8475ce392168aabeb5709a8f8058b0f7919d7c693786d56b";
/// The latest i2p-zero release version /// The latest i2p-zero release version
pub const I2P_ZERO_RELEASE_VERSION: &str = "v1.20"; pub const I2P_ZERO_RELEASE_VERSION: &str = "v1.20";
pub const I2P_ZERO_RELEASH_HASH: &str = "7e7216b281624ec464b55217284017576d109eaba7b35f7e4994ae2a78634de7";
/// The latest i2pd release version
pub const I2P_RELEASE_VERSION: &str = "2.2.1";
pub const I2P_RELEASE_HASH: &str = "c9879b8f69ea13c758672c2fa083dc2e0abb289e0fc9a55af98f9f1795f82659";
// DO NOT EDIT BELOW THIS LINE // DO NOT EDIT BELOW THIS LINE

View file

@ -5,6 +5,10 @@ use crate::{args, db, i2p, message, models, monero, gpg, utils, reqres };
use log::{info, debug, error, warn}; use log::{info, debug, error, warn};
use std::time::Duration; use std::time::Duration;
/// Enum for selecting hash validation
#[derive(PartialEq)]
enum ExternalSoftware { I2P, I2PZero, XMR }
/// Handles the state for the installation manager popup /// Handles the state for the installation manager popup
pub struct Installations { pub struct Installations {
pub xmr: bool, pub xmr: bool,
@ -416,9 +420,32 @@ pub fn stage_cleanup(f: String) {
/// software on their own. Note that software pull is over /// software on their own. Note that software pull is over
/// ///
/// clearnet. TODO(c2m): trusted download locations over i2p. /// clearnet. TODO(c2m): trusted download locations over i2p.
pub async fn install_software(installations: Installations) { pub async fn install_software(installations: Installations) -> bool {
let mut valid_i2p_hash = true;
let mut valid_i2p_zero_hash = true;
let mut valid_xmr_hash = true;
if installations.i2p { if installations.i2p {
info!("installing i2p"); info!("installing i2p");
let i2p_version = crate::I2P_RELEASE_VERSION;
let i2p_jar = format!("i2pinstall_{}.jar", i2p_version);
let link = format!("https://download.i2p2.no/releases/{}/{}", i2p_version, i2p_jar);
let curl = std::process::Command::new("curl")
.args(["-O#", &link])
.status();
match curl {
Ok(curl_output) => {
debug!("{:?}", curl_output);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let jar_output = std::process::Command::new("java")
.args(["-jar", &i2p_jar])
.spawn()
.expect("i2p gui installation failed");
debug!("{:?}", jar_output.stdout);
},
_=> error!("i2p download failed")
}
valid_i2p_hash = validate_installation_hash(ExternalSoftware::I2P, &i2p_jar);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
} }
if installations.i2p_zero { if installations.i2p_zero {
info!("installing i2p-zero"); info!("installing i2p-zero");
@ -434,13 +461,15 @@ pub async fn install_software(installations: Installations) {
debug!("{:?}", curl_output); debug!("{:?}", curl_output);
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let unzip_output = std::process::Command::new("unzip") let unzip_output = std::process::Command::new("unzip")
.arg(i2p_zero_zip) .arg(&i2p_zero_zip)
.spawn() .spawn()
.expect("i2p unzip failed"); .expect("i2p unzip failed");
debug!("{:?}", unzip_output.stdout); debug!("{:?}", unzip_output.stdout);
}, },
_=> error!("i2p-zero download failed") _=> error!("i2p-zero download failed")
} }
valid_i2p_zero_hash = validate_installation_hash(ExternalSoftware::I2PZero, &i2p_zero_zip);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
} }
if installations.xmr { if installations.xmr {
info!("installing monero"); info!("installing monero");
@ -460,5 +489,29 @@ pub async fn install_software(installations: Installations) {
}, },
_=> error!("monero download failed") _=> error!("monero download failed")
} }
valid_xmr_hash = validate_installation_hash(ExternalSoftware::XMR, &String::from(crate::MONERO_RELEASE_VERSION));
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
} }
valid_i2p_hash && valid_i2p_zero_hash && valid_xmr_hash
}
/// Linux specific hash validation using the command `sha256sum`
fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
debug!("validating hash");
let expected_hash = if sw == ExternalSoftware::I2P {
String::from(crate::I2P_RELEASE_HASH)
} else if sw == ExternalSoftware::I2PZero {
String::from(crate::I2P_ZERO_RELEASH_HASH)
} else {
String::from(crate::MONERO_RELEASE_HASH)
};
let sha_output = std::process::Command::new("sha256sum")
.arg(filename).output().expect("hash validation failed");
let str_sha = String::from_utf8(sha_output.stdout).unwrap();
let split1 = str_sha.split(" ");
let mut v: Vec<String> = split1.map(|s| String::from(s)).collect();
let actual_hash = v.remove(0);
debug!("actual hash: {}", actual_hash);
debug!("expected hash: {}", expected_hash);
actual_hash == expected_hash
} }

View file

@ -6,12 +6,11 @@ use nevmes_core::*;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::time::Duration; use std::time::Duration;
use crate::{BLOCK_TIME_IN_SECS_EST, BYTES_IN_GB, START_CORE_TIMEOUT_SECS};
pub struct HomeApp { pub struct HomeApp {
connections: utils::Connections, connections: utils::Connections,
core_timeout_tx: Sender<bool>, core_timeout_tx: Sender<bool>,
core_timeout_rx: Receiver<bool>, core_timeout_rx: Receiver<bool>,
has_install_failed: bool,
installations: utils::Installations, installations: utils::Installations,
installation_tx: Sender<bool>, installation_tx: Sender<bool>,
installation_rx: Receiver<bool>, installation_rx: Receiver<bool>,
@ -50,6 +49,7 @@ pub struct HomeApp {
impl Default for HomeApp { impl Default for HomeApp {
fn default() -> Self { fn default() -> Self {
let connections = Default::default(); let connections = Default::default();
let has_install_failed = false;
let installations = Default::default(); let installations = Default::default();
let is_core_running = false; let is_core_running = false;
let is_editing_connections = false; let is_editing_connections = false;
@ -80,6 +80,7 @@ impl Default for HomeApp {
connections, connections,
core_timeout_rx, core_timeout_rx,
core_timeout_tx, core_timeout_tx,
has_install_failed,
installations, installations,
installation_rx, installation_rx,
installation_tx, installation_tx,
@ -139,6 +140,7 @@ impl eframe::App for HomeApp {
} }
if let Ok(install) = self.installation_rx.try_recv() { if let Ok(install) = self.installation_rx.try_recv() {
self.is_installing = !install; self.is_installing = !install;
if !install && self.is_loading { self.has_install_failed = true }
self.is_loading = false; self.is_loading = false;
} }
if let Ok(timeout) = self.core_timeout_rx.try_recv() { if let Ok(timeout) = self.core_timeout_rx.try_recv() {
@ -150,6 +152,20 @@ impl eframe::App for HomeApp {
} }
} }
// Installation Error window
//-----------------------------------------------------------------------------------
let mut has_install_failed = self.has_install_failed;
egui::Window::new("Error")
.open(&mut has_install_failed)
.vscroll(false)
.show(&ctx, |ui| {
ui.heading("Installation Failure");
if ui.button("Exit").clicked() {
self.is_installing = false;
self.is_loading = false;
}
});
// Connection Manager window // Connection Manager window
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
let mut is_editing_connections = self.is_editing_connections; let mut is_editing_connections = self.is_editing_connections;
@ -217,12 +233,12 @@ impl eframe::App for HomeApp {
.open(&mut is_installing) .open(&mut is_installing)
.vscroll(true) .vscroll(true)
.show(&ctx, |ui| { .show(&ctx, |ui| {
// let mut wants_i2p = self.installations.i2p; let mut wants_i2p = self.installations.i2p;
let mut wants_i2p_zero = self.installations.i2p_zero; let mut wants_i2p_zero = self.installations.i2p_zero;
let mut wants_xmr = self.installations.xmr; let mut wants_xmr = self.installations.xmr;
// if ui.checkbox(&mut wants_i2p, "i2p").changed() { if ui.checkbox(&mut wants_i2p, "i2p").changed() {
// self.installations.i2p = !self.installations.i2p; self.installations.i2p = !self.installations.i2p;
// } }
if ui.checkbox(&mut wants_i2p_zero, "i2p-zero").changed() { if ui.checkbox(&mut wants_i2p_zero, "i2p-zero").changed() {
self.installations.i2p_zero = !self.installations.i2p_zero; self.installations.i2p_zero = !self.installations.i2p_zero;
} }
@ -231,9 +247,11 @@ impl eframe::App for HomeApp {
} }
let install = &self.installations; let install = &self.installations;
if install.i2p || install.i2p_zero || install.xmr { if install.i2p || install.i2p_zero || install.xmr {
if ui.button("Install").clicked() { if !self.is_loading {
self.is_loading = true; if ui.button("Install").clicked() {
install_software_req(self.installation_tx.clone(), ctx.clone(), &self.installations); self.is_loading = true;
install_software_req(self.installation_tx.clone(), ctx.clone(), &self.installations);
}
} }
} }
if ui.button("Exit").clicked() { if ui.button("Exit").clicked() {
@ -280,10 +298,10 @@ impl eframe::App for HomeApp {
let address = &self.s_xmr_address.result.address; let address = &self.s_xmr_address.result.address;
let unlocked_balance = self.s_xmr_balance.result.unlocked_balance; let unlocked_balance = self.s_xmr_balance.result.unlocked_balance;
let locked_balance = self.s_xmr_balance.result.balance - unlocked_balance; let locked_balance = self.s_xmr_balance.result.balance - unlocked_balance;
let unlock_time = self.s_xmr_balance.result.blocks_to_unlock * BLOCK_TIME_IN_SECS_EST; let unlock_time = self.s_xmr_balance.result.blocks_to_unlock * crate::BLOCK_TIME_IN_SECS_EST;
let xmrd_info: &reqres::XmrDaemonGetInfoResult = &self.s_xmrd_get_info.result; let xmrd_info: &reqres::XmrDaemonGetInfoResult = &self.s_xmrd_get_info.result;
let free_space = xmrd_info.free_space / BYTES_IN_GB; let free_space = xmrd_info.free_space / crate::BYTES_IN_GB;
let db_size = xmrd_info.database_size / BYTES_IN_GB; let db_size = xmrd_info.database_size / crate::BYTES_IN_GB;
ui.label(format!("- rpc version: {}\n- address: {}\n- balance: {} piconero(s)\n- locked balance: {} piconero(s)\n- unlock time (secs): {}\n- daemon info\n\t- net type: {}\n\t- current hash: {}\n\t- height: {}\n\t- synced: {}\n\t- blockchain size : ~{} GB\n\t- free space : ~{} GB\n\t- version: {}\n", ui.label(format!("- rpc version: {}\n- address: {}\n- balance: {} piconero(s)\n- locked balance: {} piconero(s)\n- unlock time (secs): {}\n- daemon info\n\t- net type: {}\n\t- current hash: {}\n\t- height: {}\n\t- synced: {}\n\t- blockchain size : ~{} GB\n\t- free space : ~{} GB\n\t- version: {}\n",
self.s_xmr_rpc_ver.result.version, address, unlocked_balance, locked_balance, self.s_xmr_rpc_ver.result.version, address, unlocked_balance, locked_balance,
unlock_time, xmrd_info.nettype, xmrd_info.top_block_hash, xmrd_info.height, xmrd_info.synchronized, unlock_time, xmrd_info.nettype, xmrd_info.top_block_hash, xmrd_info.height, xmrd_info.synchronized,
@ -352,7 +370,7 @@ fn send_balance_req(tx: Sender<reqres::XmrRpcBalanceResponse>, ctx: egui::Contex
fn send_i2p_status_req(tx: Sender<i2p::HttpProxyStatus>, ctx: egui::Context) { fn send_i2p_status_req(tx: Sender<i2p::HttpProxyStatus>, ctx: egui::Context) {
tokio::spawn(async move { tokio::spawn(async move {
let status = i2p::get_proxy_status().await; let status = i2p::check_connection().await;
let _ = tx.send(status); let _ = tx.send(status);
ctx.request_repaint(); ctx.request_repaint();
}); });
@ -373,7 +391,7 @@ fn send_reset_refresh(tx: Sender<bool>, ctx: egui::Context, init: bool) {
fn start_core_timeout fn start_core_timeout
(tx: Sender<bool>, ctx: egui::Context) { (tx: Sender<bool>, ctx: egui::Context) {
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(START_CORE_TIMEOUT_SECS)).await; tokio::time::sleep(std::time::Duration::from_secs(crate::START_CORE_TIMEOUT_SECS)).await;
log::error!("start nevmes-core timeout"); log::error!("start nevmes-core timeout");
let _ = tx.send(true); let _ = tx.send(true);
ctx.request_repaint(); ctx.request_repaint();
@ -388,8 +406,8 @@ fn install_software_req
xmr: installations.xmr, xmr: installations.xmr,
}; };
tokio::spawn(async move { tokio::spawn(async move {
utils::install_software(req_install).await; let did_install = utils::install_software(req_install).await;
let _ = tx.send(true); let _ = tx.send(did_install);
ctx.request_repaint(); ctx.request_repaint();
}); });
} }

View file

@ -24,7 +24,7 @@ pub async fn get_version(_jwp: proof::PaymentProof) -> Custom<Json<reqres::XmrRp
/// This also functions as a health check /// This also functions as a health check
#[get("/status")] #[get("/status")]
pub async fn get_i2p_status() -> Custom<Json<i2p::HttpProxyStatus>> { pub async fn get_i2p_status() -> Custom<Json<i2p::HttpProxyStatus>> {
Custom(Status::Ok, Json(i2p::get_proxy_status().await)) Custom(Status::Ok, Json(i2p::check_connection().await))
} }
/// Share your contact information /// Share your contact information