|
3 | 3 | //! prebuilt `coolify-stub` binary onto the VM, launches it next to the |
4 | 4 | //! broker UDS, and drives a real static build through its `/api/*` surface. |
5 | 5 | //! |
6 | | -//! Requires an env var pointing at a prebuilt linux-x64 binary: |
| 6 | +//! Binary selection (first match wins): |
| 7 | +//! 1. `COOLIFY_STUB_BIN=/abs/path/to/coolify-stub` — explicit override. |
| 8 | +//! 2. `COOLIFY_STUB_SOURCE=local` — build locally via |
| 9 | +//! `coolify-stub/scripts/build-binary.ts` and use the resulting |
| 10 | +//! `coolify-stub/dist/coolify-stub`. |
| 11 | +//! 3. Default: download `coolify-stub-linux-amd64.tar.gz` from the |
| 12 | +//! `coolify-stub-tag` release (env `COOLIFY_STUB_TAG`, default |
| 13 | +//! `nightly`) on `COOLIFY_STUB_REPO` (default `coollabsio/coold`) into |
| 14 | +//! `target/coolify-stub-cache/<tag>/` and use it. |
| 15 | +//! |
| 16 | +//! Typical run: |
7 | 17 | //! |
8 | 18 | //! ```text |
9 | | -//! cd coolify-stub |
10 | | -//! bun install && (cd web && bun install) |
11 | | -//! BUN_TARGET=bun-linux-x64 bun scripts/build-binary.ts |
12 | | -//! cd .. |
13 | | -//! COOLIFY_STUB_BIN=$PWD/coolify-stub/dist/coolify-stub \ |
14 | | -//! cargo test -p e2e-tests --test stub -- --ignored --nocapture --test-threads=1 |
| 19 | +//! cargo test -p e2e-tests --test stub -- --ignored --nocapture --test-threads=1 |
15 | 20 | //! ``` |
16 | 21 |
|
17 | 22 | use std::time::Duration; |
@@ -42,13 +47,111 @@ fn ok(msg: &str) { |
42 | 47 |
|
43 | 48 | fn stub_bin_path() -> String { |
44 | 49 | e2e_tests::load_dotenv(); |
45 | | - let p = std::env::var("COOLIFY_STUB_BIN") |
46 | | - .expect("env COOLIFY_STUB_BIN required (path to prebuilt linux-x64 binary)"); |
47 | | - let meta = std::fs::metadata(&p).unwrap_or_else(|e| { |
48 | | - panic!("COOLIFY_STUB_BIN={p} not readable: {e} — build via `bun scripts/build-binary.ts`") |
49 | | - }); |
50 | | - assert!(meta.is_file(), "COOLIFY_STUB_BIN={p} is not a regular file"); |
51 | | - p |
| 50 | + |
| 51 | + if let Ok(p) = std::env::var("COOLIFY_STUB_BIN") { |
| 52 | + let meta = std::fs::metadata(&p) |
| 53 | + .unwrap_or_else(|e| panic!("COOLIFY_STUB_BIN={p} not readable: {e}")); |
| 54 | + assert!(meta.is_file(), "COOLIFY_STUB_BIN={p} is not a regular file"); |
| 55 | + return p; |
| 56 | + } |
| 57 | + |
| 58 | + if std::env::var("COOLIFY_STUB_SOURCE").as_deref() == Ok("local") { |
| 59 | + return build_local_stub(); |
| 60 | + } |
| 61 | + |
| 62 | + fetch_stub_from_release() |
| 63 | +} |
| 64 | + |
| 65 | +fn crate_root() -> std::path::PathBuf { |
| 66 | + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) |
| 67 | +} |
| 68 | + |
| 69 | +fn repo_root() -> std::path::PathBuf { |
| 70 | + // CARGO_MANIFEST_DIR is the e2e-tests crate. Workspace root is one up. |
| 71 | + crate_root().parent().unwrap().to_path_buf() |
| 72 | +} |
| 73 | + |
| 74 | +fn build_local_stub() -> String { |
| 75 | + let stub_dir = repo_root().join("coolify-stub"); |
| 76 | + assert!( |
| 77 | + stub_dir.is_dir(), |
| 78 | + "COOLIFY_STUB_SOURCE=local but {stub_dir} not found", |
| 79 | + stub_dir = stub_dir.display() |
| 80 | + ); |
| 81 | + eprintln!("[stub ] building coolify-stub locally (linux-x64)"); |
| 82 | + let status = std::process::Command::new("bun") |
| 83 | + .args(["scripts/build-binary.ts"]) |
| 84 | + .env("BUN_TARGET", "bun-linux-x64") |
| 85 | + .current_dir(&stub_dir) |
| 86 | + .status() |
| 87 | + .unwrap_or_else(|e| panic!("spawn bun (is bun installed?): {e}")); |
| 88 | + assert!(status.success(), "local stub build failed (exit {:?})", status.code()); |
| 89 | + let out = stub_dir.join("dist").join("coolify-stub"); |
| 90 | + assert!(out.is_file(), "expected build output at {}", out.display()); |
| 91 | + out.to_string_lossy().into_owned() |
| 92 | +} |
| 93 | + |
| 94 | +fn fetch_stub_from_release() -> String { |
| 95 | + let tag = std::env::var("COOLIFY_STUB_TAG").unwrap_or_else(|_| "nightly".into()); |
| 96 | + let repo = std::env::var("COOLIFY_STUB_REPO").unwrap_or_else(|_| "coollabsio/coold".into()); |
| 97 | + let asset = "coolify-stub-linux-amd64.tar.gz"; |
| 98 | + |
| 99 | + let cache_dir = repo_root() |
| 100 | + .join("target") |
| 101 | + .join("coolify-stub-cache") |
| 102 | + .join(&tag); |
| 103 | + let binary = cache_dir.join("coolify-stub"); |
| 104 | + let stamp = cache_dir.join(".stamp"); |
| 105 | + |
| 106 | + // `nightly` is a rolling tag — re-download if older than 10 minutes so |
| 107 | + // the test picks up fresh pushes. Pinned tags (not `nightly`) are |
| 108 | + // immutable; always reuse once cached. |
| 109 | + let fresh = binary.is_file() |
| 110 | + && stamp.is_file() |
| 111 | + && (tag != "nightly" |
| 112 | + || std::fs::metadata(&stamp) |
| 113 | + .and_then(|m| m.modified()) |
| 114 | + .map(|t| t.elapsed().unwrap_or(Duration::from_secs(0)).as_secs() < 600) |
| 115 | + .unwrap_or(false)); |
| 116 | + |
| 117 | + if fresh { |
| 118 | + eprintln!("[stub ] using cached stub binary from {}", binary.display()); |
| 119 | + return binary.to_string_lossy().into_owned(); |
| 120 | + } |
| 121 | + |
| 122 | + std::fs::create_dir_all(&cache_dir).expect("create cache dir"); |
| 123 | + let tarball = cache_dir.join(asset); |
| 124 | + let url = format!("https://github.com/{repo}/releases/download/{tag}/{asset}"); |
| 125 | + eprintln!("[stub ] downloading {url}"); |
| 126 | + let status = std::process::Command::new("curl") |
| 127 | + .args(["-fsSL", "-o"]) |
| 128 | + .arg(&tarball) |
| 129 | + .arg(&url) |
| 130 | + .status() |
| 131 | + .unwrap_or_else(|e| panic!("spawn curl: {e}")); |
| 132 | + assert!( |
| 133 | + status.success(), |
| 134 | + "download {url} failed (exit {:?}) — set COOLIFY_STUB_BIN or COOLIFY_STUB_SOURCE=local to bypass", |
| 135 | + status.code() |
| 136 | + ); |
| 137 | + |
| 138 | + let status = std::process::Command::new("tar") |
| 139 | + .args(["-xzf"]) |
| 140 | + .arg(&tarball) |
| 141 | + .arg("-C") |
| 142 | + .arg(&cache_dir) |
| 143 | + .status() |
| 144 | + .unwrap_or_else(|e| panic!("spawn tar: {e}")); |
| 145 | + assert!(status.success(), "extract {} failed", tarball.display()); |
| 146 | + |
| 147 | + assert!( |
| 148 | + binary.is_file(), |
| 149 | + "expected {} after extract — archive layout changed?", |
| 150 | + binary.display() |
| 151 | + ); |
| 152 | + // Leave execute bit alone; scp_upload preserves file mode. |
| 153 | + let _ = std::fs::write(&stamp, ""); |
| 154 | + binary.to_string_lossy().into_owned() |
52 | 155 | } |
53 | 156 |
|
54 | 157 | fn parse_body(body: &str) -> serde_json::Value { |
|
0 commit comments