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 5 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
101 changes: 75 additions & 26 deletions src/download.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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;
Expand All @@ -13,51 +13,100 @@ 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()
);

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);

// Initialize HTTP client
let client = blocking::Client::new();
let mut response = client
.get(url)
.send()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

// 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)?;
if !response.status().is_success() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to download file: HTTP {}", response.status())
));
}

// 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)?;
let mut file = File::create(output_path)?;
io::copy(&mut response, &mut 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, 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"))
}
}
138 changes: 77 additions & 61 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
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,83 @@
}

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);
let response = resolver.lookup_ip(domain)?;
println!("Resolved DNS for {}: {:?}", domain, response);
Ok(())
}

// Connect to SSH server
let _tcp = TcpStream::connect(format!("{}:22", config.host))?;
let mut sess = ssh2::Session::new()?;
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)))?;

// Handshake with SSH server
if let Err(err) = sess.handshake() {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
let mut sess = Session::new().ok_or_else(|| {
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
io::Error::new(io::ErrorKind::Other, "Failed to create SSH session")
})?;
sess.set_tcp_stream(tcp);
sess.handshake()?;
sess.userauth_password(&config.username, &config.password)?;

// 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
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));
}
fn execute_remote_commands(sess: &mut Session, commands: &[&str]) -> io::Result<()> {
for cmd in commands {
println!("Executing: {}", cmd);
let mut channel = sess.channel_session()?;
channel.exec(cmd)?;
let mut output = String::new();
if let Err(err) = channel.read_to_string(&mut output) {
return Err(io::Error::new(io::ErrorKind::Other, err));
}
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 +111,17 @@
</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