use crate::envs;
use crate::exec;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::str;
use std::{env, fs};
pub fn print_debug(s: &str) {
if cfg!(debug_assertions) && env::var_os("ZC_DEBUG").is_some() {
eprintln!("DEBUG: {}", s);
}
}
pub fn dist() -> Result<String, String> {
if let Ok(dist_value) = exec::run(&["uname", "-m"]) {
if dist_value == "aarch64" {
Ok(String::from("aarch64"))
} else if dist_value == "x86_64" {
Ok(String::from("amd64"))
} else {
Ok(String::from("arm64"))
}
} else {
Err("Couldn't extract dist".to_string())
}
}
pub fn build(image: Option<Vec<String>>) {
if let (Some(image_value), Ok(dist_str)) = (image, dist()) {
let truncated_vector: Vec<String> = image_value.into_iter().skip(2).collect();
let d = if &dist_str == "arm64" {
"aarch64"
} else if &dist_str == "amd64" {
"amd64"
} else {
&dist_str
};
let _ = std::process::Command::new("docker")
.args(["compose", "build"])
.args(&truncated_vector)
.env("BUILDARCH", d)
.env("BUILDARCHI", &dist_str)
.status();
}
}
fn create_directory(path: &str) -> std::io::Result<()> {
let path = Path::new(path);
fs::create_dir_all(path)?;
Ok(())
}
pub fn create_directories() {
envs::update();
if let Ok(zakuro_home) = env::var("ZAKURO_HOME") {
for image in vec![
"config", "network", "storage", "compute", "node", "hub", "lib", "logs", "bin",
] {
if let Err(e) = create_directory(&format!("{}/{}", zakuro_home, image)) {
eprintln!("Error creating directory: {}", e);
}
}
}
}
fn http_download_to_file(agent: &ureq::Agent, url: &str, path: &str) -> Result<(), String> {
let body = agent
.get(url)
.call()
.map_err(|e| format!("GET {}: {}", url, e))?
.into_body()
.read_to_string()
.map_err(|e| format!("reading {}: {}", url, e))?;
fs::write(path, body).map_err(|e| format!("writing {}: {}", path, e))
}
pub fn download_conf() {
envs::update();
if let Ok(zakuro_home) = env::var("ZAKURO_HOME") {
create_directories();
let agent = ureq::Agent::new_with_config(
ureq::Agent::config_builder()
.timeout_global(Some(std::time::Duration::from_secs(30)))
.build(),
);
if let Err(e) = http_download_to_file(
&agent,
"http://get.zakuro-ai.com/zk0?config=default",
&format!("{}/default-zakuro.yaml", zakuro_home),
) {
eprintln!("Error downloading default config: {}", e);
}
for image in ["network", "storage", "compute", "node", "hub"] {
if let Err(e) = http_download_to_file(
&agent,
&format!("http://get.zakuro-ai.com/zk0?config={}", image),
&format!("{}/{}/{}-zakuro.yaml", zakuro_home, image, image),
) {
eprintln!("Error downloading {} config: {}", image, e);
}
if let Err(e) = http_download_to_file(
&agent,
&format!("http://get.zakuro-ai.com/zk0?config={}_env", image),
&format!("{}/{}/.env", zakuro_home, image),
) {
eprintln!("Error downloading {} env: {}", image, e);
}
}
}
}
fn profile_request_value(pkey: &str) -> serde_json::Value {
serde_json::json!({ "pkey": pkey })
}
pub fn download_auth() {
envs::update();
if let (Ok(zakuro_home), Ok(zakuro_auth)) =
(env::var("ZAKURO_HOME"), env::var("ZAKURO_API_KEY"))
{
if !zakuro_home.is_empty() && !zakuro_auth.is_empty() {
print_debug(&format!(
"downloading profile for ZAKURO_HOME={}",
zakuro_home
));
create_directories();
let agent = ureq::Agent::new_with_config(
ureq::Agent::config_builder()
.timeout_global(Some(std::time::Duration::from_secs(30)))
.build(),
);
let result = agent
.post("https://get.zakuro-ai.com/profile")
.header("Content-Type", "application/json")
.send_json(profile_request_value(&zakuro_auth))
.and_then(|resp| resp.into_body().read_to_string());
match result {
Ok(body) => {
let path = format!("{}/config/wg0.conf", zakuro_home);
if let Err(e) = fs::write(&path, body) {
eprintln!("Error writing {}: {}", path, e);
} else {
print_debug(&format!("wrote profile to {}/config/wg0.conf", zakuro_home));
}
}
Err(e) => eprintln!("Error fetching profile: {}", e),
}
}
} else {
eprintln!("Missing ZAKURO_CONTEXT or ZAKURO_API_KEY");
}
}
pub fn context(path: Option<&str>) -> std::io::Result<()> {
let zakuro_env: String = fs::read_to_string(envs::CONFIG_FILE)?;
if let Some(path_str) = path {
let output_line = format!("export ZAKURO_CONTEXT={}", path_str);
let path = Path::new(path_str);
if path.exists() {
let mut lines = Vec::new();
for line in zakuro_env.split("\n") {
if !line.contains("export ZAKURO_CONTEXT") {
lines.push(line);
}
}
lines.push(&output_line);
let concatenated = lines.join("\n");
let mut file = File::create(envs::CONFIG_FILE)?;
file.write_all(concatenated.as_bytes())?;
}
} else {
}
Ok(())
}
pub fn version() {
let git = match option_env!("ZC_GIT_HASH") {
Some(h) => {
let dirty = if option_env!("ZC_GIT_DIRTY") == Some("1") {
"-dirty"
} else {
""
};
format!(" ({}{})", h, dirty)
}
None => String::new(),
};
let built_time = env!("ZC_BUILD_EPOCH")
.parse::<i64>()
.ok()
.and_then(|secs| chrono::DateTime::from_timestamp(secs, 0))
.unwrap_or_default();
println!(
"zc version {}{} built on {}",
env!("CARGO_PKG_VERSION"),
git,
built_time.with_timezone(&chrono::Local)
);
}
pub fn logs(_alive: bool) {
let html = match exec::stdout("curl -s http://spark-master.zakuro-ai.com:8080") {
Ok(html) => html,
Err(why) => {
eprintln!("Failed to execute the command: {:?}", why);
return;
}
};
println!("{}", html);
}
#[cfg(test)]
mod panic_fix_tests {
use super::context;
#[test]
fn context_returns_err_when_config_missing() {
match context(None) {
Ok(()) => { }
Err(e) => {
assert_eq!(e.kind(), std::io::ErrorKind::NotFound);
}
}
}
#[test]
fn profile_request_value_escapes_special_chars() {
let nasty = "ab\"c\\d\ne$(rm -rf /)`x`";
let v = super::profile_request_value(nasty);
let s = serde_json::to_string(&v).unwrap();
let back: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(back["pkey"], nasty);
assert!(
s.contains("\\\""),
"key quote must be escaped in the JSON body: {}",
s
);
}
}