1use std::io::prelude::*;
2use std::net::TcpStream;
3use std::path::Path;
4
5use async_trait::async_trait;
6use ssh2::Session;
7
8use crate::jobs::Job;
9
10const INSTALL_SCRIPT: &str = r#"#!/bin/bash
11set -e
12apt-get update
13apt-get install -y curl git build-essential pkg-config libssl-dev
14curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
15apt-get install -y nodejs
16curl https://sh.rustup.rs -sSf | sh -s -- -y
17source "$HOME/.cargo/env"
18curl -L https://foundry.paradigm.xyz | bash
19"$HOME"/.foundry/bin/foundryup
20"$HOME"/.cargo/bin/cargo install --locked echidna
21"$HOME"/.cargo/bin/cargo install --locked --git https://github.com/crytic/medusa
22"$HOME"/.cargo/bin/cargo install --locked --git https://github.com/crytic/halmos
23"#;
24
25fn install_over_ssh(
26 ip: &str,
27 key_path: &str,
28) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
29 let tcp = TcpStream::connect((ip, 22))?;
30 let mut sess = Session::new()?;
31 sess.set_tcp_stream(tcp);
32 sess.handshake()?;
33 sess.userauth_pubkey_file("root", None, Path::new(key_path), None)?;
34
35 let mut channel = sess.channel_session()?;
36 channel.exec("bash -s")?;
37 channel.write_all(INSTALL_SCRIPT.as_bytes())?;
38 channel.send_eof()?;
39 channel.wait_close()?;
40
41 Ok(())
42}
43
44pub enum ProviderType {
45 Hetzner,
46 AWS,
47}
48
49#[async_trait]
50pub trait Provider: Send + Sync {
51 async fn start_job(&self, name: &str) -> Result<Job, Box<dyn std::error::Error + Send + Sync>>;
52 async fn get_job(
53 &self,
54 job_id: &str,
55 ) -> Result<Option<Job>, Box<dyn std::error::Error + Send + Sync>>;
56 async fn stop_job(&self, job_id: &str)
57 -> Result<Job, Box<dyn std::error::Error + Send + Sync>>;
58 async fn list_jobs(&self) -> Result<Vec<Job>, Box<dyn std::error::Error + Send + Sync>>;
59 async fn tail(
60 &self,
61 job_id: &str,
62 filename: &str,
63 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
64 async fn scp(
65 &self,
66 job_id: &str,
67 filename: &str,
68 destination: &str,
69 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
70
71 async fn install_dependencies(
72 &self,
73 ip: &str,
74 key_path: &str,
75 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
76 let ip = ip.to_string();
77 let key = key_path.to_string();
78 tokio::task::spawn_blocking(move || install_over_ssh(&ip, &key)).await??;
79 Ok(())
80 }
81}
82
83pub struct ProviderFactory;
84
85impl ProviderFactory {
86 #[must_use]
92 pub fn create_provider(name: &str) -> Box<dyn Provider> {
93 match name {
94 "hetzner" => Box::new(hetzner::HetznerProvider::new()),
95 "aws" => Box::new(aws::AWSProvider::new()),
96 _ => panic!("Invalid provider"),
97 }
98 }
99}
100
101pub mod aws;
102pub mod hetzner;