diff --git a/.gitignore b/.gitignore index ea8c4bf..3f836ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +/web-build +.local \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index aaf8505..41e2b67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,6 +242,7 @@ name = "cli" version = "0.1.0" dependencies = [ "clap", + "message-tools", ] [[package]] @@ -343,6 +344,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -384,6 +391,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "heck" version = "0.5.0" @@ -424,6 +443,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -504,6 +529,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.177" @@ -543,7 +574,7 @@ version = "0.1.0" dependencies = [ "blake2", "chacha20poly1305", - "getrandom", + "getrandom 0.2.16", "serde", "serde_json", "wasm-bindgen", @@ -556,6 +587,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "mio" version = "1.1.0" @@ -655,13 +696,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] @@ -777,7 +824,12 @@ name = "server" version = "0.1.0" dependencies = [ "axum", + "getrandom 0.3.4", + "lazy_static", + "message-tools", + "serde_json", "tokio", + "tower-http", ] [[package]] @@ -862,6 +914,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.2" @@ -878,6 +943,32 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -916,6 +1007,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -950,6 +1047,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.105" @@ -1084,6 +1190,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "x25519-dalek" version = "2.0.1" diff --git a/README.md b/README.md index 75d2fca..e9b94ea 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # zachery-lol -Code and assets for my website. \ No newline at end of file +Code and assets for my website. + +### usage +`cargo run -- --help` \ No newline at end of file diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index eb33697..8bd2a05 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -6,3 +6,6 @@ default-run = "cli" [dependencies] clap = { version = "4.5.52", features = ["derive"] } + +# local +message-tools = { path = "../message-tools" } \ No newline at end of file diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index edf3e39..f39d50a 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,6 +1,7 @@ -use std::process::Command; - use clap::{Parser, Subcommand}; +use std::{os::unix::process::ExitStatusExt, process::{Command, ExitStatus}}; + +const WEB_BUILD_DIR: &str = "web-build"; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -14,23 +15,36 @@ enum Commands { /// Builds the website and server, including wasm files Build, - /// Starts the server - Run, + /// Runs the server locally + Start { + /// Whether to build again or not + #[arg(long)] + rebuild: bool, + }, /// Generates new encryption keys GenKeys { /// How many keypairs to generate #[arg(short, long, default_value = "100")] - number: Option, + number: usize, + + /// Directory to put the generated secret key files + #[arg(short, long, default_value = ".local/secrets.json")] + sk_out_dir: String, + + /// Directory to put the generated pubklic key pool + #[arg(long, default_value = "target/public_keys.json")] + pk_out_dir: String, }, /// Reads fetched messages ReadMessages { - /// Optional source file path - #[arg(short, long)] - source: Option, + /// Source file path + #[arg(short, long, default_value = ".local/messages")] + source: String, - /// Optional output file path + /// Optional output file path for decoded messages + /// Default: stdout #[arg(short, long)] out_dir: Option, }, @@ -42,29 +56,67 @@ fn main() { match &cli.command { Commands::Build => { println!("Executing build command..."); - build(); } - Commands::Run => { - println!("Executing run command..."); - // Add your run logic here + + Commands::Start { rebuild } => { + println!("Starting server..."); + if *rebuild { + println!("Rebuilding web pages..."); + build(); + } // Change the current working directory of the running Rust program + println!("Changing directory to {WEB_BUILD_DIR}..."); + let cd_result = std::env::set_current_dir(WEB_BUILD_DIR); + + // Use the existing helper to check the result + handle_command_status( + cd_result.map(|_| std::process::ExitStatus::from_raw(0)), + "cd build dir", + ); + + println!("Running server executable..."); + // Now run the server executable from within the new current directory + handle_command_status(Command::new("./server").status(), "run server"); } - Commands::GenKeys { .. } => { + Commands::GenKeys { + number, + sk_out_dir, + pk_out_dir, + } => { println!("Executing gen-keys command..."); - // Add your key generation logic here + message_tools::gen_keys::generate_keys_to_file(pk_out_dir, sk_out_dir, *number) + .expect("write key files"); } - Commands::ReadMessages { source, .. } => { + Commands::ReadMessages { .. } => { println!("Executing read-messages command..."); - match source { - Some(s) => println!("Reading from source: {}", s), - None => println!("Reading from default source."), - } // Add your message reading logic here } } } +// A helper function to process the Result +fn handle_command_status(status_result: std::io::Result, command_name: &str) { + match status_result { + Ok(exit_status) => { + if !exit_status.success() { + eprintln!( + "\nCommand '{}' failed with exit code: {:?}", + command_name, + exit_status.code() + ); + std::process::exit(exit_status.code().unwrap_or(1)); + } + } + Err(e) => { + eprintln!("\nFailed to execute command '{}': {}", command_name, e); + std::process::exit(1); // Exit with an error code + } + } +} + fn build() { + println!("Building wasm package..."); + // build the wasm package let status = Command::new("wasm-pack") .arg("build") .arg("--out-dir") @@ -73,20 +125,55 @@ fn build() { .arg("--target") .arg("no-modules") .status(); + handle_command_status(status, "wasm-pack build"); - match status { - Ok(exit_status) => { - if exit_status.success() { - println!("\nBuild successful!"); - } else { - // Print the exit code if the build failed - eprintln!("\nBuild failed with exit code: {:?}", exit_status.code()); - std::process::exit(exit_status.code().unwrap_or(1)); - } - } - Err(e) => { - eprintln!("\nFailed to execute cargo build command: {}", e); - std::process::exit(1); // Exit with an error code - } + println!("Building server executable..."); + // build the server executable + let status = Command::new("cargo") + .arg("build") + .arg("--release") + .args(["-p", "server"]) + .args(["--target", "x86_64-unknown-linux-musl"]) + .status(); + handle_command_status(status, "cargo build server"); + + let server_bin = "target/x86_64-unknown-linux-musl/release/server"; + + println!("Creating build directory..."); + // make webpage build directory if it doesn't already exist + let status = Command::new("mkdir").args(["-p", WEB_BUILD_DIR]).status(); + handle_command_status(status, "mkdir web-build"); + + println!("Copying files..."); + // copy relevant files + let status = Command::new("cp") + .args(["-R", "routes"]) + .arg(format!("{WEB_BUILD_DIR}/routes")) + .status(); + handle_command_status(status, "cp routes"); + + let status = Command::new("cp") + .arg(server_bin) + .arg(format!("{WEB_BUILD_DIR}")) + .status(); + handle_command_status(status, "cp server bin"); + + let pubkey_file = "target/public_keys.json"; + + if !std::path::Path::new(pubkey_file).exists() { + panic!("No public key file. Please generate keys first."); } + + let status = Command::new("mkdir") + .args(["-p", &format!("{WEB_BUILD_DIR}/pubkeys")]) + .status(); + handle_command_status(status, "mkdir web-build"); + + let status = Command::new("cp") + .arg("target/public_keys.json") + .arg(format!("{WEB_BUILD_DIR}/pubkeys/")) + .status(); + handle_command_status(status, "cp pubkeys"); + + println!("\nBuild successful!"); } diff --git a/crates/message-tools/src/gen_keys.rs b/crates/message-tools/src/gen_keys.rs new file mode 100644 index 0000000..f849142 --- /dev/null +++ b/crates/message-tools/src/gen_keys.rs @@ -0,0 +1,48 @@ +use std::io::Write; +use std::{fs::OpenOptions, path::Path}; + +use serde::Serialize; +use x25519_dalek::{PublicKey, StaticSecret}; + +#[derive(Serialize)] +pub struct Keypair { + sk: Vec, + pk: Vec, +} + +fn generate_keypair() -> Keypair { + let sk = StaticSecret::random(); + let pk = PublicKey::from(&sk); + + Keypair { + sk: sk.as_bytes().to_vec(), + pk: pk.as_bytes().to_vec(), + } +} + +pub fn generate_keys_to_file( + pks_path: impl AsRef, + sks_path: impl AsRef, + num: usize, +) -> std::io::Result<()> { + let mut pks_file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(pks_path)?; + + let mut sks_file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(sks_path)?; + + for _ in 0..num { + let kp = generate_keypair(); + + writeln!(pks_file, "{}", serde_json::to_string(&kp.pk)?)?; + writeln!(sks_file, "{}", serde_json::to_string(&kp.sk)?)?; + } + + Ok(()) +} diff --git a/crates/message-tools/src/lib.rs b/crates/message-tools/src/lib.rs index 7866ec9..9f06860 100644 --- a/crates/message-tools/src/lib.rs +++ b/crates/message-tools/src/lib.rs @@ -4,8 +4,10 @@ use chacha20poly1305::{ aead::{Aead, OsRng}, }; use serde::{Deserialize, Serialize}; -use x25519_dalek::{PublicKey, StaticSecret}; +use x25519_dalek::StaticSecret; +pub mod gen_keys; +pub use x25519_dalek::PublicKey; #[cfg(target_arch = "wasm32")] pub mod wasm; diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 15d0613..61e3524 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -10,3 +10,9 @@ path = "bin/main.rs" [dependencies] axum = "0.8.7" tokio = { version = "1.48.0", features = ["full"] } +tower-http = { version = "0.6.6", features = ["fs"] } +serde_json = "1.0.145" + +message-tools = { path = "../message-tools" } +getrandom = "0.3.4" +lazy_static = "1.5.0" diff --git a/crates/server/bin/main.rs b/crates/server/bin/main.rs index e034754..0e1e8f2 100644 --- a/crates/server/bin/main.rs +++ b/crates/server/bin/main.rs @@ -1,18 +1,60 @@ use axum::{Router, response::Html, routing::get}; -use std::{fs::OpenOptions, io::Read}; +use std::{fs::OpenOptions, io::{BufRead, Read}, path::Path}; +use tower_http::services::ServeDir; + +const PUBKEY_PATH: &str = "./pubkeys/public_keys.json"; +lazy_static::lazy_static! { + static ref PUBLIC_KEYS: Vec> = load_public_keys(PUBKEY_PATH).expect("static path"); +} type Error = Box; type Result = std::result::Result; +fn load_public_keys(path: impl AsRef) -> std::io::Result>> { + // Open the file + let f = OpenOptions::new() + .read(true) + .write(false) + .open(path)?; + + // Wrap it in a BufReader for efficient line-by-line reading + let reader = std::io::BufReader::new(f); + + let mut public_keys = Vec::new(); + + // Iterate over each line in the file + for line_result in reader.lines() { + let line = line_result?; // Handle potential IO errors during reading + + // Skip empty lines or lines with only whitespace + let trimmed_line = line.trim(); + if trimmed_line.is_empty() { + continue; + } + + // Deserialize the line (which is a single JSON array) into a Vec + let key: Vec = serde_json::from_str(trimmed_line).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Failed to parse JSON line: {}", e), + ) + })?; + + public_keys.push(key); + } + + Ok(public_keys) +} + #[tokio::main] async fn main() -> Result<()> { // build our application with a single route let app = Router::new() .route("/", get(serve_path("./routes/root/index.html")?)) .route("/who", get(serve_path("./routes/who/index.html")?)) + .route("/api/pubkey", get(async || select_key(&PUBLIC_KEYS))) .route("/contact", get(serve_path("./routes/contact/index.html")?)) - // .route("/api/pubkey", get(todo!())); - ; + .nest_service("/routes", ServeDir::new("./routes")); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); @@ -21,6 +63,11 @@ async fn main() -> Result<()> { Ok(()) } +fn select_key(pks: &Vec>) -> String { + let idx = getrandom::u64().expect("random integer") as usize % pks.len(); + serde_json::to_string(&pks[idx]).expect("serialize vec of bytes") +} + fn serve_path(path: impl AsRef) -> Result> { let mut s = String::new(); OpenOptions::new() diff --git a/routes/contact/index.html b/routes/contact/index.html index a767f5c..d09eecd 100644 --- a/routes/contact/index.html +++ b/routes/contact/index.html @@ -88,7 +88,7 @@ border-radius: 0 0 4px 4px; } - +