covert_system/
config.rs

1use std::process::Command;
2
3use serde::Deserialize;
4use tokio::sync::oneshot;
5
6#[derive(Debug, Deserialize)]
7#[serde(rename_all = "kebab-case")]
8pub struct Config {
9    pub port: u16,
10    #[serde(skip)]
11    pub port_tx: Option<oneshot::Sender<u16>>,
12    pub replication: Option<ReplicationConfig>,
13    pub storage_path: String,
14}
15
16impl Config {
17    #[must_use]
18    pub fn seal_storage_path(&self) -> String {
19        if self.using_inmemory_storage() {
20            self.storage_path.to_string()
21        } else {
22            let maybe_slash = if self.storage_path.ends_with('/') {
23                ""
24            } else {
25                "/"
26            };
27            format!("{}{maybe_slash}{}", self.storage_path, "seal.db")
28        }
29    }
30
31    #[must_use]
32    pub fn encrypted_storage_path(&self) -> String {
33        if self.using_inmemory_storage() {
34            self.storage_path.to_string()
35        } else {
36            let maybe_slash = if self.storage_path.ends_with('/') {
37                ""
38            } else {
39                "/"
40            };
41            format!("{}{maybe_slash}{}", self.storage_path, "covert.db")
42        }
43    }
44
45    #[must_use]
46    pub fn using_inmemory_storage(&self) -> bool {
47        self.storage_path.contains(":memory:")
48    }
49
50    pub fn sanitize(&self) -> anyhow::Result<()> {
51        if self.replication.is_some() {
52            if self.using_inmemory_storage() {
53                return Err(anyhow::Error::msg(
54                    "Replication is not supported for inmemory storage",
55                ));
56            }
57
58            // Check if litestream is installed
59            let cmd = Command::new("litestream").arg("version").status();
60            match cmd {
61                Ok(s) if s.success() => (),
62                _ => {
63                    return Err(anyhow::Error::msg(
64                        "Litestream command not found, can not perform replication",
65                    ));
66                }
67            }
68        }
69
70        if !self.using_inmemory_storage() {
71            let storage_path = std::path::Path::new(&self.storage_path);
72            if !storage_path.exists()
73                && std::fs::DirBuilder::new()
74                    .recursive(true)
75                    .create(storage_path)
76                    .is_err()
77            {
78                return Err(anyhow::Error::msg("Failed to create storage directory"));
79            }
80
81            if !storage_path.is_dir() {
82                return Err(anyhow::Error::msg(
83                    "The storage path provided is not a directory",
84                ));
85            }
86        }
87
88        Ok(())
89    }
90}
91
92#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
93#[serde(rename_all = "kebab-case")]
94pub struct ReplicationConfig {
95    pub access_key_id: String,
96    pub secret_access_key: String,
97    // S3 url format: https://<bucket-name>.s3.<region-code>.amazonaws.com/
98    pub bucket_url: String,
99}
100
101impl ReplicationConfig {
102    #[must_use]
103    pub fn seal_bucket_url(&self) -> String {
104        let maybe_slash = if self.bucket_url.ends_with('/') {
105            ""
106        } else {
107            "/"
108        };
109        format!("{}{maybe_slash}{}", self.bucket_url, "seal.db")
110    }
111
112    #[must_use]
113    pub fn encrypted_bucket_url(&self) -> String {
114        let maybe_slash = if self.bucket_url.ends_with('/') {
115            ""
116        } else {
117            "/"
118        };
119        format!("{}{maybe_slash}{}", self.bucket_url, "covert.db")
120    }
121}