/* [ { "url": "https://api.github.com/repos/octocat/Hello-World/releases/1", "html_url": "https://github.com/octocat/Hello-World/releases/v1.0.0", "assets_url": "https://api.github.com/repos/octocat/Hello-World/releases/1/assets", "upload_url": "https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", "tarball_url": "https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0", "zipball_url": "https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0", "id": 1, "node_id": "MDc6UmVsZWFzZTE=", "tag_name": "v1.0.0", "target_commitish": "master", "name": "v1.0.0", "body": "Description of the release", "draft": false, "prerelease": false, "created_at": "2013-02-27T19:35:32Z", "published_at": "2013-02-27T19:35:32Z", "author": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false }, "assets": [ { "url": "https://api.github.com/repos/octocat/Hello-World/releases/assets/1", "browser_download_url": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip", "id": 1, "node_id": "MDEyOlJlbGVhc2VBc3NldDE=", "name": "example.zip", "label": "short description", "state": "uploaded", "content_type": "application/zip", "size": 1024, "download_count": 42, "created_at": "2013-02-27T19:35:32Z", "updated_at": "2013-02-27T19:35:32Z", "uploader": { "login": "octocat", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", "html_url": "https://github.com/octocat", "followers_url": "https://api.github.com/users/octocat/followers", "following_url": "https://api.github.com/users/octocat/following{/other_user}", "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", "organizations_url": "https://api.github.com/users/octocat/orgs", "repos_url": "https://api.github.com/users/octocat/repos", "events_url": "https://api.github.com/users/octocat/events{/privacy}", "received_events_url": "https://api.github.com/users/octocat/received_events", "type": "User", "site_admin": false } } ] } ] */ use std::fs; use std::fs::{File, remove_file}; use std::io::{Read, Write}; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::process::{Command, Output}; use flate2::read::GzDecoder; use reqwest::blocking::Response; use reqwest::header::HeaderMap; use tar::Archive; use tempfile::{NamedTempFile, tempfile}; #[derive(serde::Deserialize, Debug)] struct GithubRelease { url: String, html_url: String, assets_url: String, upload_url: String, tarball_url: String, zipball_url: String, id: i64, node_id: String, tag_name: String, target_commitish: String, name: String, body: String, draft: bool, prerelease: bool, created_at: String, published_at: String, author: GithubUser, assets: Vec } #[derive(serde::Deserialize, Debug)] struct GithubUser { login: String, id: i64, node_id: String, avatar_url: String, gravatar_id: String, url: String, html_url: String, followers_url: String, following_url: String, gists_url: String, starred_url: String, subscriptions_url: String, organizations_url: String, repos_url: String, events_url: String, received_events_url: String, r#type: String, site_admin: bool } #[derive(serde::Deserialize, Debug)] struct GithubReleaseAsset { url: String, browser_download_url: String, id: i64, node_id: String, name: String, label: String, state: String, content_type: String, size: i64, download_count: i64, created_at: String, updated_at: String, uploader: GithubUser } fn main() { println!("[*] Fetching nebula releaseinfo..."); let mut headers = HeaderMap::new(); let mut has_api_key = false; if let Ok(api_key) = std::env::var("GH_API_KEY") { headers.insert("Authorization", format!("Bearer {}", api_key).parse().unwrap()); has_api_key = true; } let client = reqwest::blocking::Client::builder().user_agent("curl/7.57.1").default_headers(headers).build().unwrap(); let resp: Response = client.get("https://api.github.com/repos/slackhq/nebula/releases/latest").send().unwrap(); if resp.headers().get("X-Ratelimit-Remaining").unwrap().to_str().unwrap() == "0" { println!("You've been ratelimited from the GitHub API. Wait a while (1 hour)"); if !has_api_key { println!("You can also set a GitHub API key with the environment variable GH_API_KEY, which will increase your ratelimit ( a lot )"); } panic!("Ratelimited"); } let release: GithubRelease = resp.json().unwrap(); println!("[*] Fetching target triplet..."); let target = std::env::var("TARGET").unwrap(); println!("[*] Compiling for target {}", target); let target_file = match target.as_str() { "x86_64-apple-darwin" | "aarch64-apple-darwin" => "nebula-darwin", "x86_64-unknown-freebsd" => "nebula-freebsd-amd64", "x86_64-unknown-linux-gnu" => "nebula-linux-amd64", "armv5te-unknown-linux-gnueabi" => "nebula-linux-arm-5", "arm-unknown-linux-gnueabi" | "arm-unknown-linux-gnueabihf" => "nebula-linux-arm-6", "armv7-unknown-linux-gnueabihf" | "armv7-unknown-linux-gnueabi" => "nebula-linux-arm-7", "aarch64-unknown-linux-gnu" => "nebula-linux-arm64", "x86_64-pc-windows-msvc" => "nebula-windows-amd64", "aarch64-pc-windows-msvc" => "nebula-windows-arm64", _ => { println!("This architecture is not supported yet :("); println!("Nebula has a limited set of architectures it is able to function on."); println!("tfclient can only be compiled on these architectures."); println!("See https://github.com/slackhq/nebula/releases for a list of supported architectures"); println!("Is your system supported by Nebula? Shoot a message to the mailing list. Include the target triplet (above) in your response, as well as a link to the functioning Nebula binary. We will happily add your machine to the list!"); panic!("Unsupported architecture"); } }; println!("[*] Embedding {} {}", target_file, release.name); let download = release.assets.iter().find(|r| r.name == format!("{}.tar.gz", target_file)).expect("That architecture isn't avaliable :("); println!("[*] Downloading {}.tar.gz ({}, {} bytes) from {}", target_file, target, download.size, download.browser_download_url); let response = reqwest::blocking::get(&download.browser_download_url).unwrap(); let content = response.bytes().unwrap().to_vec(); let bytes = content.as_slice(); let tar = GzDecoder::new(bytes); let mut archive = Archive::new(tar); let entries = archive.entries().unwrap(); let mut nebula_bin = vec![]; let mut nebula_cert_bin = vec![]; let mut shasum = vec![]; for entry in entries { let mut entry = entry.unwrap(); if entry.path().unwrap() == Path::new("nebula") || entry.path().unwrap() == Path::new("nebula.exe") { nebula_bin.reserve(entry.size() as usize); entry.read_to_end(&mut nebula_bin).unwrap(); } else if entry.path().unwrap() == Path::new("nebula-cert") || entry.path().unwrap() == Path::new("nebula-cert.exe") { nebula_cert_bin.reserve(entry.size() as usize); entry.read_to_end(&mut nebula_cert_bin).unwrap(); } else if entry.path().unwrap() == Path::new("SHASUM256.txt") { shasum.reserve(entry.size() as usize); entry.read_to_end(&mut shasum).unwrap(); } } if nebula_bin.is_empty() { panic!("[x] Release did not contain nebula binary"); } if nebula_cert_bin.is_empty() { panic!("[x] Release did not contain nebula_cert binary"); } let mut nebula_file = File::create(format!("{}/nebula.bin", std::env::var("OUT_DIR").unwrap())).unwrap(); nebula_file.write_all(&nebula_bin).unwrap(); codegen_version(&nebula_bin, "nebula.bin", "NEBULA"); let mut nebula_cert_file = File::create(format!("{}/nebula_cert.bin", std::env::var("OUT_DIR").unwrap())).unwrap(); nebula_cert_file.write_all(&nebula_cert_bin).unwrap(); codegen_version(&nebula_cert_bin, "nebula_cert.bin", "NEBULA_CERT"); // get version info and codegen println!("cargo:rerun-if-changed=build.rs"); } fn codegen_version(bin: &[u8], fp: &str, name: &str) { // get version let output = execim(bin, &vec!["-version"]); let stdout = output.stdout; let stdout_str = String::from_utf8(stdout).unwrap(); if !stdout_str.starts_with("Version: ") { panic!("Binary did not have expected version output. Unable to get version info."); } let mut version = stdout_str.split(' ').collect::>()[1].to_string(); version.pop(); let code = format!("// This code was automatically @generated by build.rs. It should not be modified.\npub const {}_BIN: &[u8] = include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{}\"));\npub const {}_VERSION: &str = \"{}\";", name, fp, name, version); let mut file = File::create(format!("{}/{}.rs", std::env::var("OUT_DIR").unwrap(), fp)).unwrap(); file.write_all(code.as_bytes()).unwrap(); } #[cfg(not(unix))] fn execim(buf: &[u8], args: &Vec<&str>) -> Output { let mut file = File::create("tmpexec.bin").unwrap(); file.write_all(buf).unwrap(); std::mem::drop(file); let output = Command::new("./tmpexec.bin").args(args).output().unwrap(); remove_file("./tmpexec.bin").unwrap(); output } #[cfg(unix)] fn execim(buf: &[u8], args: &Vec<&str>) -> Output { let mut file = File::create("tmpexec.bin").unwrap(); file.write_all(buf).unwrap(); let metadata = file.metadata().unwrap(); let mut permissions = metadata.permissions(); permissions.set_mode(0o0755); fs::set_permissions("./tmpexec.bin", permissions).unwrap(); std::mem::drop(file); let output = Command::new("./tmpexec.bin").args(args).output().unwrap(); remove_file("./tmpexec.bin").unwrap(); output }