diff --git a/Cargo.toml b/Cargo.toml index cb9876c..a94707f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,63 +1,3 @@ -[package] -name = "sunbeam" -version = "0.1.0" -edition = "2024" -description = "Sunbeam local dev stack manager" - -[dependencies] -# Core -thiserror = "2" -tokio = { version = "1", features = ["full"] } -clap = { version = "4", features = ["derive"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -serde_yaml = "0.9" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Kubernetes -kube = { version = "0.99", features = ["client", "runtime", "derive", "ws"] } -k8s-openapi = { version = "0.24", features = ["v1_32"] } - -# HTTP + TLS -reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"] } -rustls = { version = "0.23", features = ["ring"] } - -# SSH -russh = "0.46" -russh-keys = "0.46" - -# Crypto -rsa = "0.9" -pkcs8 = { version = "0.10", features = ["pem"] } -pkcs1 = { version = "0.7", features = ["pem"] } -sha2 = "0.10" -hmac = "0.12" -base64 = "0.22" -rand = "0.8" - -# Certificate generation -rcgen = "0.14" - -# SMTP -lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "builder", "hostname"] } - -# Archive handling -flate2 = "1" -tar = "0.4" - -# Async -futures = "0.3" -tokio-stream = "0.1" - -# Utility -tempfile = "3" -dirs = "5" -chrono = { version = "0.4", features = ["serde"] } - -[build-dependencies] -reqwest = { version = "0.12", features = ["blocking", "rustls-tls"] } -sha2 = "0.10" -flate2 = "1" -tar = "0.4" -chrono = "0.4" +[workspace] +members = ["sunbeam-sdk", "sunbeam"] +resolver = "3" diff --git a/sunbeam-sdk/Cargo.toml b/sunbeam-sdk/Cargo.toml new file mode 100644 index 0000000..9c99747 --- /dev/null +++ b/sunbeam-sdk/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "sunbeam-sdk" +version = "0.1.0" +edition = "2024" +description = "Sunbeam SDK — reusable library for cluster management" + +[features] +default = [] +cli = ["clap"] + +[dependencies] +# Core +thiserror = "2" +tokio = { version = "1", features = ["full"] } +clap = { version = "4", features = ["derive"], optional = true } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yaml = "0.9" +tracing = "0.1" + +# Kubernetes +kube = { version = "0.99", features = ["client", "runtime", "derive", "ws"] } +k8s-openapi = { version = "0.24", features = ["v1_32"] } + +# HTTP + TLS +reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"] } + +# SSH +russh = "0.46" +russh-keys = "0.46" + +# Crypto +rsa = "0.9" +pkcs8 = { version = "0.10", features = ["pem"] } +pkcs1 = { version = "0.7", features = ["pem"] } +sha2 = "0.10" +hmac = "0.12" +base64 = "0.22" +rand = "0.8" + +# Certificate generation +rcgen = "0.14" + +# SMTP +lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "builder", "hostname"] } + +# Archive handling +flate2 = "1" +tar = "0.4" + +# Async +futures = "0.3" +tokio-stream = "0.1" + +# Utility +tempfile = "3" +dirs = "5" +chrono = { version = "0.4", features = ["serde"] } + +[build-dependencies] +reqwest = { version = "0.12", features = ["blocking", "rustls-tls"] } +sha2 = "0.10" +flate2 = "1" +tar = "0.4" +chrono = "0.4" diff --git a/sunbeam-sdk/build.rs b/sunbeam-sdk/build.rs new file mode 100644 index 0000000..3bfd1d2 --- /dev/null +++ b/sunbeam-sdk/build.rs @@ -0,0 +1,132 @@ +use flate2::read::GzDecoder; +use std::env; +use std::fs; +use std::io::Read; +use std::path::PathBuf; +use std::process::Command; +use tar::Archive; + +const KUSTOMIZE_VERSION: &str = "v5.8.1"; +const HELM_VERSION: &str = "v4.1.0"; + +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let target = env::var("TARGET").unwrap_or_default(); + let (os, arch) = parse_target(&target); + + download_and_embed("kustomize", KUSTOMIZE_VERSION, &os, &arch, &out_dir); + download_and_embed("helm", HELM_VERSION, &os, &arch, &out_dir); + + // Set version info from git + let commit = git_commit_sha(); + println!("cargo:rustc-env=SUNBEAM_COMMIT={commit}"); + + // Build target triple and build date + println!("cargo:rustc-env=SUNBEAM_TARGET={target}"); + let date = chrono::Utc::now().format("%Y-%m-%d").to_string(); + println!("cargo:rustc-env=SUNBEAM_BUILD_DATE={date}"); + + // Rebuild if git HEAD changes (workspace root is two levels up) + println!("cargo:rerun-if-changed=../../.git/HEAD"); +} + +fn parse_target(target: &str) -> (String, String) { + let os = if target.contains("darwin") { + "darwin" + } else if target.contains("linux") { + "linux" + } else if cfg!(target_os = "macos") { + "darwin" + } else { + "linux" + }; + + let arch = if target.contains("aarch64") || target.contains("arm64") { + "arm64" + } else if target.contains("x86_64") || target.contains("amd64") { + "amd64" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else { + "amd64" + }; + + (os.to_string(), arch.to_string()) +} + +fn download_and_embed(tool: &str, version: &str, os: &str, arch: &str, out_dir: &PathBuf) { + let dest = out_dir.join(tool); + if dest.exists() { + return; + } + + let url = match tool { + "kustomize" => format!( + "https://github.com/kubernetes-sigs/kustomize/releases/download/\ + kustomize%2F{version}/kustomize_{version}_{os}_{arch}.tar.gz" + ), + "helm" => format!( + "https://get.helm.sh/helm-{version}-{os}-{arch}.tar.gz" + ), + _ => panic!("Unknown tool: {tool}"), + }; + + let extract_path = match tool { + "kustomize" => "kustomize".to_string(), + "helm" => format!("{os}-{arch}/helm"), + _ => unreachable!(), + }; + + eprintln!("cargo:warning=Downloading {tool} {version} for {os}/{arch}..."); + + let response = reqwest::blocking::get(&url) + .unwrap_or_else(|e| panic!("Failed to download {tool}: {e}")); + let bytes = response + .bytes() + .unwrap_or_else(|e| panic!("Failed to read {tool} response: {e}")); + + let decoder = GzDecoder::new(&bytes[..]); + let mut archive = Archive::new(decoder); + + for entry in archive.entries().expect("Failed to read tar entries") { + let mut entry = entry.expect("Failed to read tar entry"); + let path = entry + .path() + .expect("Failed to read entry path") + .to_path_buf(); + if path.to_string_lossy() == extract_path { + let mut data = Vec::new(); + entry + .read_to_end(&mut data) + .expect("Failed to read binary"); + fs::write(&dest, &data).expect("Failed to write binary"); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&dest, fs::Permissions::from_mode(0o755)) + .expect("Failed to set permissions"); + } + + eprintln!("cargo:warning=Embedded {tool} ({} bytes)", data.len()); + return; + } + } + + panic!("Could not find {extract_path} in {tool} archive"); +} + +fn git_commit_sha() -> String { + Command::new("git") + .args(["rev-parse", "--short=8", "HEAD"]) + .output() + .ok() + .and_then(|o| { + if o.status.success() { + Some(String::from_utf8_lossy(&o.stdout).trim().to_string()) + } else { + None + } + }) + .unwrap_or_else(|| "unknown".to_string()) +} diff --git a/sunbeam/Cargo.toml b/sunbeam/Cargo.toml new file mode 100644 index 0000000..afb3265 --- /dev/null +++ b/sunbeam/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sunbeam" +version = "0.1.0" +edition = "2024" +description = "Sunbeam local dev stack manager" + +[[bin]] +name = "sunbeam" +path = "src/main.rs" + +[dependencies] +sunbeam-sdk = { path = "../sunbeam-sdk", features = ["cli"] } +tokio = { version = "1", features = ["full"] } +clap = { version = "4", features = ["derive"] } +chrono = "0.4" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +rustls = { version = "0.23", features = ["ring"] }