Skip to main content

monetize_estate_side/pump/
config.rs

1//! **Where this box reports, and as whom** (moved from Holger's `holger-fleet`, P4a). The
2//! installer's feed (the same keys every product's boxes read: `fleet`, `fleet_instance`, `fleet_at`, `fleet_ca`, `monetize_pubkey`, plain
3//! or offered by a VM as `offer.<vm>.<key>`), else what an enrolment kept at
4//! [`CONFIG_REL`].
5
6use std::collections::BTreeMap;
7use std::path::Path;
8use std::time::Duration;
9
10use serde::{Deserialize, Serialize};
11
12/// What an enrolment keeps under the data root.
13pub const CONFIG_REL: &str = "fleet/fleet.json";
14
15pub const FEED_FLEET: &str = "fleet";
16pub const FEED_FLEET_INSTANCE: &str = "fleet_instance";
17pub const FEED_FLEET_AT: &str = "fleet_at";
18pub const FEED_FLEET_CA: &str = "fleet_ca";
19pub const FEED_MONETIZE: &str = "monetize_pubkey";
20
21/// The longest pause between reports when monetize names none.
22pub const DEFAULT_EVERY: Duration = Duration::from_secs(30);
23
24#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(default)]
26pub struct FleetConfig {
27    /// The ONE monetize's `Fleet` door (`https://monetize.ignalina.dk`).
28    pub url: String,
29    /// Where to connect (`host:port`) when the URL's host is not what this box dials.
30    pub at: Option<String>,
31    /// PEM the door's certificate is checked against; none = the public roots.
32    pub ca_pem: Option<String>,
33    /// The estate this box reports for.
34    pub instance: String,
35    /// `main` | `twin`; empty = what the box is.
36    pub role: String,
37    /// The key orders verify under (hex).
38    pub monetize_pubkey: String,
39}
40
41impl FleetConfig {
42    /// From a feed; `None` when it names no door or no estate.
43    pub fn from_feed(feed: &BTreeMap<String, String>) -> Option<FleetConfig> {
44        let get = |k: &str| {
45            feed.get(k).map(|v| v.trim().to_string()).filter(|v| !v.is_empty()).or_else(|| {
46                feed.iter().find(|(key, v)| key.starts_with("offer.") && key.rsplit('.').next() == Some(k) && !v.trim().is_empty()).map(|(_, v)| v.trim().to_string())
47            })
48        };
49        let url = get(FEED_FLEET)?;
50        let instance = get(FEED_FLEET_INSTANCE)?;
51        Some(FleetConfig { url, at: get(FEED_FLEET_AT), ca_pem: get(FEED_FLEET_CA), instance, role: String::new(), monetize_pubkey: get(FEED_MONETIZE).unwrap_or_default() })
52    }
53
54    /// What an enrolment kept, if any.
55    pub fn load(root: &Path) -> std::io::Result<Option<FleetConfig>> {
56        match std::fs::read(root.join(CONFIG_REL)) {
57            Ok(b) => serde_json::from_slice(&b).map(Some).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
58            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
59            Err(e) => Err(e),
60        }
61    }
62
63    pub fn save(&self, root: &Path) -> std::io::Result<()> {
64        let bytes = serde_json::to_vec_pretty(self).map_err(std::io::Error::other)?;
65        crate::write_atomic(&root.join(CONFIG_REL), &bytes, true)
66    }
67
68    /// The key orders verify under, when it is one.
69    pub fn key(&self) -> Option<[u8; 32]> {
70        crate::key32(&self.monetize_pubkey)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn the_feed_names_the_door_the_estate_and_the_key_plain_or_offered() {
80        let k = "11".repeat(32);
81        let feed: BTreeMap<String, String> = [("offer.front.fleet", "https://monetize.ignalina.dk"), ("offer.front.fleet_instance", "acme"), ("monetize_pubkey", k.as_str())].iter().map(|(a, b)| (a.to_string(), b.to_string())).collect();
82        let c = FleetConfig::from_feed(&feed).unwrap();
83        assert_eq!((c.url.as_str(), c.instance.as_str()), ("https://monetize.ignalina.dk", "acme"));
84        assert_eq!(c.key(), Some([0x11; 32]));
85        assert_eq!(FleetConfig::from_feed(&BTreeMap::new()), None);
86        let dir = tempfile::tempdir().unwrap();
87        c.save(dir.path()).unwrap();
88        assert_eq!(FleetConfig::load(dir.path()).unwrap(), Some(c));
89    }
90}