flow runner register keeps the CA in the Flow home by absolute path, and a relative ca in runner.toml is read against the home, so a service finds it from any directory (closes #98) 0d663e91c598
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key ed9cee5e545f
- When
- · 2026-10-27 18:22 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
0d663e91c598d7d2f8622364d706a06216b0a3e72062d9fbd8e972012c8aaf43- Depends on
- 1b2229dab1a4 46fe2c815cc3 47c1d64dec8a 5544c8998aa7 bb643c252515 c3db750ed96e e0918b887850 f98a8f7881e0
- Depended on by
- 1a50d4dae89c 060d459ff6c6
- In bookmarks
- main
- In tags
- v0.5.1 v0.5.2 v0.5.3
- Edits
- 9 atoms, 0 tree operations, 3 files
Browse the tree at this change
modified CHANGELOG.md +2 −0
| … | 3 unchanged lines |
||
| 4 | 4 | ||
| 5 | 5 | ## Unreleased |
|
| 6 | 6 | ||
| 7 | + | - **`flow runner register --ca` keeps the CA in the Flow home (#98):** it wrote the path as given, so `--ca hub-ca.pem` left `ca = "hub-ca.pem"` in runner.toml and a service started elsewhere could not find it. Register now copies the file to the home's `hub-ca.pem` and records that copy's absolute path, and a relative `ca` already in runner.toml is read against the home. |
|
| 8 | + | ||
| 7 | 9 | - **The cheap checks run as WASM (#96):** `layout`, `todos`, `size` and `summary` are one WASI module, `crates/flow-checks`, built by `scripts/checks-wasm.sh` and committed as `.flow/checks.wasm`, so they need no image or microVM and any runner with the WASM tier takes them (the phone, plex, windows-1); each runs in under 100 ms. The fmt job fails unless the committed module is what the source builds, and the build image has the wasm32-wasip1 target for that. |
|
| 8 | 10 | ||
| 9 | 11 | ## 0.5.0 (2026-10-27) |
|
| … | 118 unchanged lines |
modified crates/flow/src/home.rs +20 −1
| … | 39 unchanged lines |
||
| 40 | 40 | /// Where `flow runner register` keeps the runner's hub and credential. |
|
| 41 | 41 | pub const RUNNER_TOML: &str = "runner.toml"; |
|
| 42 | 42 | ||
| 43 | + | /// The hub's CA certificate as `flow runner register --ca` keeps it (#98). |
|
| 44 | + | pub const CA_PEM: &str = "hub-ca.pem"; |
|
| 45 | + | ||
| 43 | 46 | #[derive(Clone, Debug, Deserialize)] |
|
| 44 | 47 | pub struct RunnerConfig { |
|
| 45 | 48 | pub hub: String, |
|
| … | 121 unchanged lines |
||
| 167 | 170 | let registered = root.join(RUNNER_TOML); |
|
| 168 | 171 | if config.runner.is_none() && registered.is_file() { |
|
| 169 | 172 | let text = std::fs::read_to_string(®istered).with_context(|| format!("read {}", registered.display()))?; |
|
| 170 | - | config.runner = Some(toml::from_str(&text).with_context(|| format!("parse {}", registered.display()))?); |
|
| 173 | + | let mut runner: RunnerConfig = toml::from_str(&text).with_context(|| format!("parse {}", registered.display()))?; |
|
| 174 | + | // A relative CA there is the home's (#98): register keeps its |
|
| 175 | + | // copy there, and the service may start in any directory. |
|
| 176 | + | runner.ca = runner.ca.map(|ca| if ca.is_absolute() { ca } else { root.join(ca) }); |
|
| 177 | + | config.runner = Some(runner); |
|
| 171 | 178 | } |
|
| 172 | 179 | for d in ["cas", "work", "repos"] { |
|
| 173 | 180 | std::fs::create_dir_all(root.join(d)).with_context(|| format!("create {}", root.join(d).display()))?; |
|
| … | 125 unchanged lines |
||
| 299 | 306 | assert_eq!(a.identity, b.identity, "the same key after a restart"); |
|
| 300 | 307 | let sig = b.secret.sign(oxy_core::identity::Domain::Attest, &b.identity.hash(), &oxy_core::hash::ObjectRef([1; 32])); |
|
| 301 | 308 | oxy_core::identity::verify(&a.identity, &sig, oxy_core::identity::Domain::Attest, &oxy_core::hash::ObjectRef([1; 32])).unwrap(); |
|
| 309 | + | } |
|
| 310 | + | ||
| 311 | + | /// #98: runner.toml's relative CA is the home's, wherever flow starts. |
|
| 312 | + | #[test] |
|
| 313 | + | fn a_registered_runners_relative_ca_is_in_its_home() { |
|
| 314 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 315 | + | std::fs::write(dir.path().join(RUNNER_TOML), "hub = \"https://hub\"\ntoken = \"t\"\nca = \"hub-ca.pem\"\n").unwrap(); |
|
| 316 | + | let home = Home::locate(Some(dir.path())).unwrap(); |
|
| 317 | + | assert_eq!(home.config.runner.unwrap().ca, Some(dir.path().join("hub-ca.pem"))); |
|
| 318 | + | std::fs::write(dir.path().join(RUNNER_TOML), "hub = \"https://hub\"\ntoken = \"t\"\nca = \"/etc/ssl/hub.pem\"\n").unwrap(); |
|
| 319 | + | let home = Home::locate(Some(dir.path())).unwrap(); |
|
| 320 | + | assert_eq!(home.config.runner.unwrap().ca, Some(PathBuf::from("/etc/ssl/hub.pem"))); |
|
| 302 | 321 | } |
|
| 303 | 322 | } |
modified crates/flow/src/runner.rs +30 −1
| … | 399 unchanged lines |
||
| 400 | 400 | got.credential, got.name |
|
| 401 | 401 | ); |
|
| 402 | 402 | if let Some(ca) = &ca { |
|
| 403 | - | text.push_str(&format!("ca = {:?}\n", ca.display().to_string())); |
|
| 403 | + | let kept = keep_ca(&home.root, ca)?; |
|
| 404 | + | text.push_str(&format!("ca = {:?}\n", kept.display().to_string())); |
|
| 404 | 405 | } |
|
| 405 | 406 | if !labels.is_empty() { |
|
| 406 | 407 | text.push_str(&format!("labels = {labels:?}\n")); |
|
| … | 10 unchanged lines |
||
| 417 | 418 | Ok(got) |
|
| 418 | 419 | } |
|
| 419 | 420 | ||
| 421 | + | /// Copy the hub's CA certificate `ca` into the Flow home `root` (#98) and |
|
| 422 | + | /// give the copy's absolute path, for runner.toml: a service starts in |
|
| 423 | + | /// another directory, and the file given may move or go. |
|
| 424 | + | fn keep_ca(root: &Path, ca: &Path) -> Result<PathBuf> { |
|
| 425 | + | let kept = root.join(crate::home::CA_PEM); |
|
| 426 | + | let same = matches!((std::fs::canonicalize(ca), std::fs::canonicalize(&kept)), (Ok(a), Ok(b)) if a == b); |
|
| 427 | + | if !same { |
|
| 428 | + | std::fs::copy(ca, &kept).with_context(|| format!("copy {} into {}", ca.display(), root.display()))?; |
|
| 429 | + | } |
|
| 430 | + | Ok(std::fs::canonicalize(&kept)?) |
|
| 431 | + | } |
|
| 432 | + | ||
| 420 | 433 | /// Waits before trying a hub request again after a server error. |
|
| 421 | 434 | const RETRY_DELAYS: [Duration; 3] = [Duration::from_secs(1), Duration::from_secs(2), Duration::from_secs(4)]; |
|
| 422 | 435 | ||
| … | 1352 unchanged lines |
||
| 1775 | 1788 | ||
| 1776 | 1789 | #[cfg(test)] |
|
| 1777 | 1790 | mod tests { |
|
| 1791 | + | ||
| 1792 | + | /// #98: register keeps a copy of the CA in the home, by absolute path, |
|
| 1793 | + | /// and keeping the home's own copy again is a no-op. |
|
| 1794 | + | #[test] |
|
| 1795 | + | fn the_ca_is_kept_in_the_home_by_absolute_path() { |
|
| 1796 | + | let home = tempfile::tempdir().unwrap(); |
|
| 1797 | + | let elsewhere = tempfile::tempdir().unwrap(); |
|
| 1798 | + | let given = elsewhere.path().join("ca.pem"); |
|
| 1799 | + | std::fs::write(&given, "PEM").unwrap(); |
|
| 1800 | + | let kept = super::keep_ca(home.path(), &given).unwrap(); |
|
| 1801 | + | assert!(kept.is_absolute() && kept.ends_with(crate::home::CA_PEM), "{kept:?}"); |
|
| 1802 | + | assert_eq!(std::fs::read_to_string(&kept).unwrap(), "PEM"); |
|
| 1803 | + | std::fs::remove_file(&given).unwrap(); |
|
| 1804 | + | assert_eq!(super::keep_ca(home.path(), &kept).unwrap(), kept, "the home's copy again"); |
|
| 1805 | + | assert_eq!(std::fs::read_to_string(&kept).unwrap(), "PEM"); |
|
| 1806 | + | } |
|
| 1778 | 1807 | ||
| 1779 | 1808 | #[test] |
|
| 1780 | 1809 | fn an_input_is_placed_only_when_it_is_what_the_receipt_names() { |
|
| … | 546 unchanged lines |