Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #2

Merged
merged 8 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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<_>>())
}
Loading