OXYGENHUB
# 67634e22f48f oxygenflow / crates / flow / src / home.rs
3.9 KiB · Rust · last changed OxygenFlow M0: pipeline parser and canonical job graph, receipts, the runner API types, flow-init, the Firecracker tier, flow check, run, rootfs import and runner 1 month ago source blame history raw
1
//! `$FLOW_HOME` (default `~/.local/share/flow`): the pinned parts of each
2
//! isolation tier, rootfs images by hash, scratch space and the runner's
3
//! configuration, all described by `flow.toml`.
4
//!
5
//! ```toml
6
//! [microvm]
7
//! firecracker = { path = "bin/firecracker", blake3 = "…" }
8
//! kernel = { path = "kernel/vmlinux", blake3 = "…" }
9
//! init = { path = "bin/flow-init", blake3 = "…" }
10
//!
11
//! [runner]                       # only for `flow runner`
12
//! hub = "https://hub.example:7343"
13
//! token = "…"                    # a runner token from the hub's [flow] runner_tokens
14
//! ca = "/path/to/hub-ca.pem"     # when the hub's certificate is self-signed
15
//! name = "earth-1"
16
//! ```
17
18
use anyhow::{bail, Context, Result};
19
use serde::Deserialize;
20
use std::collections::HashMap;
21
use std::path::{Path, PathBuf};
22
use std::sync::Mutex;
23
24
#[derive(Clone, Debug, Deserialize)]
25
pub struct Pinned {
26
    pub path: PathBuf,
27
    pub blake3: String,
28
}
29
30
#[derive(Clone, Debug, Deserialize)]
31
pub struct MicrovmConfig {
32
    pub firecracker: Pinned,
33
    pub kernel: Pinned,
34
    pub init: Pinned,
35
}
36
37
#[derive(Clone, Debug, Deserialize)]
38
pub struct RunnerConfig {
39
    pub hub: String,
40
    pub token: String,
41
    pub ca: Option<PathBuf>,
42
    pub name: Option<String>,
43
}
44
45
#[derive(Clone, Debug, Default, Deserialize)]
46
pub struct Config {
47
    pub microvm: Option<MicrovmConfig>,
48
    pub runner: Option<RunnerConfig>,
49
}
50
51
pub struct Home {
52
    pub root: PathBuf,
53
    pub config: Config,
54
    /// Files already verified, by path, with the (len, mtime) they had then.
55
    verified: Mutex<HashMap<PathBuf, (u64, std::time::SystemTime)>>,
56
}
57
58
impl Home {
59
    pub fn locate(explicit: Option<&Path>) -> Result<Home> {
60
        let root = match explicit {
61
            Some(p) => p.to_path_buf(),
62
            None => match std::env::var_os("FLOW_HOME") {
63
                Some(p) => PathBuf::from(p),
64
                None => PathBuf::from(std::env::var_os("HOME").context("HOME is not set")?).join(".local/share/flow"),
65
            },
66
        };
67
        let file = root.join("flow.toml");
68
        let config = if file.is_file() {
69
            let text = std::fs::read_to_string(&file).with_context(|| format!("read {}", file.display()))?;
70
            toml::from_str(&text).with_context(|| format!("parse {}", file.display()))?
71
        } else {
72
            Config::default()
73
        };
74
        for d in ["cas", "work", "repos"] {
75
            std::fs::create_dir_all(root.join(d)).with_context(|| format!("create {}", root.join(d).display()))?;
76
        }
77
        Ok(Home { root, config, verified: Mutex::new(HashMap::new()) })
78
    }
79
80
    pub fn resolve(&self, p: &Path) -> PathBuf {
81
        if p.is_absolute() {
82
            p.to_path_buf()
83
        } else {
84
            self.root.join(p)
85
        }
86
    }
87
88
    /// Where a rootfs image with this hash lives.
89
    pub fn rootfs(&self, hash: &str) -> PathBuf {
90
        self.root.join("cas").join(format!("{hash}.ext4"))
91
    }
92
93
    /// A pinned file, checked against its BLAKE3 before use (again only
94
    /// when its size or modification time changed since).
95
    pub fn verified(&self, path: &Path, blake3_hex: &str) -> Result<PathBuf> {
96
        let meta = std::fs::metadata(path).with_context(|| format!("{} is missing", path.display()))?;
97
        let stamp = (meta.len(), meta.modified()?);
98
        if self.verified.lock().unwrap().get(path) == Some(&stamp) {
99
            return Ok(path.to_path_buf());
100
        }
101
        let got = hash_file(path)?;
102
        if got != blake3_hex {
103
            bail!("{} has BLAKE3 {got}, but {blake3_hex} is pinned", path.display());
104
        }
105
        self.verified.lock().unwrap().insert(path.to_path_buf(), stamp);
106
        Ok(path.to_path_buf())
107
    }
108
}
109
110
pub fn hash_file(path: &Path) -> Result<String> {
111
    let mut h = blake3::Hasher::new();
112
    h.update_reader(std::fs::File::open(path).with_context(|| format!("open {}", path.display()))?)?;
113
    Ok(h.finalize().to_hex().to_string())
114
}