Skip to content

Commit

Permalink
Merge pull request #2 from GreenMeteor/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ArchBlood authored Dec 22, 2024
2 parents 65dabc9 + 04a32b9 commit 683de1e
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 96 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
name: 'Dependency review'
on:
pull_request:
branches: [ "main" ]
branches: [ "main", "develop" ]

# If using a dependency submission action in this workflow this permission will need to be set to:
#
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/rust-clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ name: Rust Clippy Analyze

on:
push:
branches: [ "main" ]
branches: [ "main", "develop" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
branches: [ "main", "develop" ]
schedule:
- cron: '36 18 * * 3'

Expand Down Expand Up @@ -42,9 +42,9 @@ jobs:
run: cargo install clippy-sarif sarif-fmt

- name: Run rust-clippy
run:
cargo clippy
--all-features
run: |
cargo clippy \
--all-features \
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
continue-on-error: true

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

# Add a step to set file permissions for the working directory (if needed)
- name: Set file permissions
Expand Down
109 changes: 82 additions & 27 deletions src/download.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::io::{self, Write};
use std::fs::File;
use std::path::Path;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use serde::Deserialize;
use reqwest::blocking;
use zip;
use std::time::Duration;

// Define a struct to deserialize the JSON configuration
#[derive(Debug, Deserialize)]
Expand All @@ -13,51 +14,105 @@ struct Config {

// Function to encapsulate download functionality
pub fn download() -> io::Result<()> {
// Read JSON file and deserialize into Config struct
let config_file = File::open("config.json")?;
let config: Config = serde_json::from_reader(config_file)?;
// Load configuration
let config = load_config("config.json")?;

// URL to download HumHub
let humhub_download_url = "https://download.humhub.com/downloads/install/humhub-1.16.2.zip";
// Download and extract HumHub
let humhub_version = "1.16.2";
let humhub_download_url = format!(
"https://download.humhub.com/downloads/install/humhub-{}.zip",
humhub_version
);
let humhub_zip_path = Path::new(&format!("humhub-{}.zip", humhub_version));
let humhub_extract_dir = Path::new("/var/www/html");

// File path to save the downloaded HumHub ZIP file
let humhub_zip_path = "humhub-1.16.2.zip";
download_file(&humhub_download_url, humhub_zip_path)?;
extract_zip(humhub_zip_path, humhub_extract_dir)?;

// Directory to extract HumHub ZIP file (root directory)
let humhub_extract_dir = "/var/www/html";
println!(
"HumHub version {} downloaded and extracted successfully to {}",
humhub_version,
humhub_extract_dir.display()
);

// Initialize HTTP client
let client = blocking::Client::new();
Ok(())
}

// Function to load configuration from a JSON file
fn load_config(path: &str) -> io::Result<Config> {
let config_file = File::open(path)?;
serde_json::from_reader(config_file).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

// Function to download a file from a URL
fn download_file(url: &str, output_path: &Path) -> io::Result<()> {
println!("Downloading file from: {}", url);

let client = blocking::Client::builder()
.timeout(Duration::from_secs(60)) // Add timeout for the request
.danger_accept_invalid_certs(false) // Make sure SSL validation is done (set to true for secure config)
.build()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to create HTTP client: {}", e)))?;

let mut response = client
.get(url)
.send()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to send request: {}", e)))?;

if !response.status().is_success() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to download file: HTTP {}", response.status())
));
}

// Download HumHub
let mut response = client.get(humhub_download_url).send()?;
let mut zip_file = File::create(humhub_zip_path)?;
io::copy(&mut response, &mut zip_file)?;
let mut file = File::create(output_path)?;
io::copy(&mut response, &mut file)?;

// Extract HumHub ZIP file to the root directory
let extract_dir = Path::new(humhub_extract_dir);
let zip_file = File::open(humhub_zip_path)?;
let mut archive = zip::ZipArchive::new(zip_file)?;
println!("File downloaded to: {}", output_path.display());
Ok(())
}

// Function to extract a ZIP file to a target directory
fn extract_zip(zip_path: &Path, extract_dir: &Path) -> io::Result<()> {
println!("Extracting ZIP file: {}", zip_path.display());

let zip_file = File::open(zip_path)?;
let mut archive = zip::ZipArchive::new(zip_file)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("Failed to read ZIP archive: {}", e)))?;

for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = extract_dir.join(file.sanitized_name());
let outpath = extract_dir.join(sanitize_path(&file.sanitized_name(), extract_dir)?);

if let Some(parent) = outpath.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
fs::create_dir_all(parent)?;
}
}

if (&*file.name()).ends_with('/') {
std::fs::create_dir_all(&outpath)?;
if file.name().ends_with('/') {
fs::create_dir_all(&outpath)?;
} else {
let mut outfile = File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}
}

println!("HumHub downloaded and extracted successfully to {}", humhub_extract_dir);

println!("Extraction completed to: {}", extract_dir.display());
Ok(())
}

// Function to sanitize file paths and ensure they are within the target directory
fn sanitize_path(path: &Path, base_dir: &Path) -> io::Result<PathBuf> {
let sanitized = path
.strip_prefix("/")
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Invalid file path"))?;

let full_path = base_dir.join(sanitized);
if full_path.starts_with(base_dir) {
Ok(full_path)
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Path traversal detected"))
}
}
148 changes: 86 additions & 62 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::fs::File;
use serde::Deserialize;
use trust_dns_resolver::{Resolver, config::ResolverConfig, config::ResolverOpts};
use std::net::TcpStream;
use ssh2::Session;
use std::time::Duration;

#[derive(Debug, Deserialize)]
struct Config {
Expand All @@ -20,57 +22,91 @@ fn main() {
}

fn run() -> io::Result<()> {
// Read configuration from file
let config_file = File::open("config.json")?;
let config: Config = serde_json::from_reader(config_file)?;
// Load configuration
let config = load_config("config.json")?;
// Initialize DNS resolver and resolve domain
resolve_domain(&config.domain_name)?;

// Initialize DNS resolver
// Establish SSH connection
let mut sess = establish_ssh_connection(&config)?;

// Install required software on the remote server
execute_remote_commands(
&mut sess,
&[
"sudo apt update",
"sudo apt upgrade -y",
"sudo apt install -y apache2",
"sudo add-apt-repository -y ppa:ondrej/php",
"sudo apt update",
"sudo apt install -y php8.1 libapache2-mod-php8.1 php8.1-mysql php8.1-common php8.1-cli php8.1-curl php8.1-json php8.1-zip php8.1-gd php8.1-mbstring php8.1-xml",
"sudo apt install -y mariadb-server",
"sudo mysql_secure_installation",
"sudo a2enmod php8.1",
"sudo systemctl restart apache2",
],
)?;

// Configure Apache
configure_apache(&mut sess, &config)?;

println!("Setup completed successfully.");
Ok(())
}

fn load_config(path: &str) -> io::Result<Config> {
let config_file = File::open(path)?;
serde_json::from_reader(config_file).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

fn resolve_domain(domain: &str) -> io::Result<()> {
let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default())?;
let response = resolver.lookup_ip(&config.domain_name)?;
println!("DNS record updated successfully: {:?}", response);
match resolver.lookup_ip(domain) {
Ok(response) => {
println!("Resolved DNS for {}: {:?}", domain, response);
Ok(())
}
Err(e) => Err(io::Error::new(io::ErrorKind::NotFound, format!("Failed to resolve domain: {}", e))),
}
}

fn establish_ssh_connection(config: &Config) -> io::Result<Session> {
let tcp = TcpStream::connect(format!("{}:22", config.host))?;
tcp.set_read_timeout(Some(Duration::from_secs(30)))?;
tcp.set_write_timeout(Some(Duration::from_secs(30)))?;

// Connect to SSH server
let _tcp = TcpStream::connect(format!("{}:22", config.host))?;
let mut sess = ssh2::Session::new()?;
let mut sess = Session::new().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Failed to create SSH session: {}", e))
})?;

sess.set_tcp_stream(tcp);
sess.handshake()?;

// Handshake with SSH server
if let Err(err) = sess.handshake() {
return Err(io::Error::new(io::ErrorKind::Other, err));
// Try to authenticate using password
if let Err(e) = sess.userauth_password(&config.username, &config.password) {
return Err(io::Error::new(io::ErrorKind::PermissionDenied, format!("SSH Authentication failed: {}", e)));
}

// Authenticate with SSH server using username and password
if let Err(err) = sess.userauth_password(&config.username, &config.password) {
return Err(io::Error::new(io::ErrorKind::Other, err));
if !sess.authenticated() {
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "Authentication failed"));
}

// Define commands to install required software
let commands = [
"sudo apt update",
"sudo apt upgrade -y",
"sudo apt install -y apache2",
"sudo add-apt-repository -y ppa:ondrej/php",
"sudo apt update",
"sudo apt install -y php8.1 libapache2-mod-php8.1 php8.1-mysql php8.1-common php8.1-cli php8.1-curl php8.1-json php8.1-zip php8.1-gd php8.1-mbstring php8.1-xml",
"sudo apt install -y mariadb-server",
"sudo mysql_secure_installation",
"sudo a2enmod php8.1",
"sudo systemctl restart apache2",
];
Ok(sess)
}

// Execute commands remotely
fn execute_remote_commands(sess: &mut Session, commands: &[&str]) -> io::Result<()> {
let mut channel = sess.channel_session()?;
for cmd in &commands {
if let Err(err) = channel.exec(cmd) {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
let mut output = String::new();
if let Err(err) = channel.read_to_string(&mut output) {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
println!("{}", output);
}
let commands_str = commands.join(" && ");
println!("Executing: {}", commands_str);
channel.exec(&commands_str)?;
let mut output = String::new();
channel.read_to_string(&mut output)?;
println!("{}", output);
channel.wait_close()?;
Ok(())
}

// Modify Apache virtual host configuration
fn configure_apache(sess: &mut Session, config: &Config) -> io::Result<()> {
let apache_config = format!(
r#"
<VirtualHost *:80>
Expand All @@ -83,29 +119,17 @@ fn run() -> io::Result<()> {
</Directory>
</VirtualHost>
"#,
&config.domain_name, &config.domain_name, &config.domain_name
config.domain_name, config.domain_name, config.domain_name
);

if let Err(err) = channel.exec(&format!(
"echo '{}' | sudo tee /etc/apache2/sites-available/{}.conf",
apache_config, &config.domain_name
)) {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
if let Err(err) = channel.exec(&format!("sudo a2ensite {}.conf", &config.domain_name)) {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
if let Err(err) = channel.exec("sudo systemctl reload apache2") {
return Err(io::Error::new(io::ErrorKind::Other, err));
}

// Close the SSH session
if let Err(err) = channel.send_eof() {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
if let Err(err) = channel.wait_close() {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
let commands = [
format!(
"echo '{}' | sudo tee /etc/apache2/sites-available/{}.conf",
apache_config, config.domain_name
),
format!("sudo a2ensite {}.conf", config.domain_name),
"sudo systemctl reload apache2".to_string(),
];

Ok(())
execute_remote_commands(sess, &commands.iter().map(String::as_str).collect::<Vec<_>>())
}

0 comments on commit 683de1e

Please sign in to comment.