varta_watch/config/
loaders.rs1use super::types::Config;
2#[cfg(feature = "secure-udp")]
3use super::validate::read_secret_file;
4use super::validate::validate_recovery_file;
5#[cfg(feature = "prometheus-exporter")]
6use super::validate::validate_secret_file;
7
8impl Config {
9 pub fn resolve_recovery_mode(&self) -> std::io::Result<Option<crate::recovery::RecoveryMode>> {
21 use crate::recovery::RecoveryMode;
22
23 let has_exec = self.recovery_exec_cmd.is_some();
25 let has_exec_file = self.recovery_exec_file.is_some();
26
27 if has_exec && has_exec_file {
28 #[cfg(not(feature = "compile-time-config"))]
29 return Err(std::io::Error::new(
30 std::io::ErrorKind::InvalidInput,
31 "--recovery-exec and --recovery-exec-file are mutually exclusive",
32 ));
33 #[cfg(feature = "compile-time-config")]
34 return Err(std::io::Error::new(
35 std::io::ErrorKind::InvalidInput,
36 "inline exec-recovery command and exec-recovery file are mutually exclusive",
37 ));
38 }
39
40 if let Some(ref cmd) = self.recovery_exec_cmd {
42 let mut parts: Vec<&str> = cmd.split_whitespace().collect();
43 if parts.is_empty() {
44 #[cfg(not(feature = "compile-time-config"))]
45 return Err(std::io::Error::new(
46 std::io::ErrorKind::InvalidInput,
47 "--recovery-exec: command must not be empty",
48 ));
49 #[cfg(feature = "compile-time-config")]
50 return Err(std::io::Error::new(
51 std::io::ErrorKind::InvalidInput,
52 "exec-recovery command must not be empty",
53 ));
54 }
55 let program = parts.remove(0).to_string();
56 let args: Vec<String> = parts.into_iter().map(|s| s.to_string()).collect();
57 return Ok(Some(RecoveryMode::Exec { program, args }));
58 }
59 if let Some(ref path) = self.recovery_exec_file {
60 let cmd = validate_recovery_file(path)?;
61 let mut parts: Vec<&str> = cmd.split_whitespace().collect();
62 if parts.is_empty() {
63 return Err(std::io::Error::new(
64 std::io::ErrorKind::InvalidInput,
65 format!("{}: file is empty", path.display()),
66 ));
67 }
68 let program = parts.remove(0).to_string();
69 let args: Vec<String> = parts.into_iter().map(|s| s.to_string()).collect();
70 return Ok(Some(RecoveryMode::Exec { program, args }));
71 }
72
73 Ok(None)
74 }
75
76 #[cfg(feature = "secure-udp")]
92 pub fn load_secure_keys(
93 &self,
94 ) -> std::io::Result<Option<(varta_vlp::crypto::Key, Vec<varta_vlp::crypto::Key>)>> {
95 use std::io;
96 use varta_vlp::crypto::Key;
97
98 let Some(ref path) = self.secure_key_file else {
99 return Ok(None);
100 };
101
102 let content = read_secret_file(path)?;
103 let mut primary: Option<Key> = None;
104 for line in content.lines() {
105 let line = line.trim();
106 if line.is_empty() || line.starts_with('#') {
107 continue;
108 }
109 if primary.is_some() {
110 return Err(io::Error::new(
111 io::ErrorKind::InvalidData,
112 format!(
113 "{}: multiple primary keys found (expected exactly one)",
114 path.display()
115 ),
116 ));
117 }
118 primary = Some(Key::from_hex(line).map_err(|e| {
119 io::Error::new(
120 io::ErrorKind::InvalidData,
121 format!("{}: {e}", path.display()),
122 )
123 })?);
124 }
125
126 let primary = match primary {
127 Some(k) => k,
128 None => {
129 return Err(io::Error::new(
130 io::ErrorKind::InvalidData,
131 format!("{}: no key found in file", path.display()),
132 ))
133 }
134 };
135
136 let mut accepted = Vec::new();
138 if let Some(ref path) = self.accepted_key_file {
139 let content = read_secret_file(path)?;
140 for line in content.lines() {
141 let line = line.trim();
142 if line.is_empty() || line.starts_with('#') {
143 continue;
144 }
145 let key = Key::from_hex(line).map_err(|e| {
146 io::Error::new(
147 io::ErrorKind::InvalidData,
148 format!("{}: {e}", path.display()),
149 )
150 })?;
151 accepted.push(key);
152 }
153 }
154
155 Ok(Some((primary, accepted)))
156 }
157
158 #[cfg(feature = "secure-udp")]
168 pub fn load_master_key(&self) -> std::io::Result<Option<varta_vlp::crypto::Key>> {
169 use varta_vlp::crypto::Key;
170
171 let Some(ref path) = self.master_key_file else {
172 return Ok(None);
173 };
174 let hex = read_secret_file(path)?;
175 Key::from_hex(hex.trim()).map(Some).map_err(|e| {
176 std::io::Error::new(
177 std::io::ErrorKind::InvalidData,
178 format!("{}: {e}", path.display()),
179 )
180 })
181 }
182
183 #[cfg(feature = "prometheus-exporter")]
184 pub fn load_prom_token(&self) -> std::io::Result<Option<varta_vlp::crypto::BearerToken>> {
199 use std::io;
200 let Some(ref path) = self.prom_token_file else {
201 return Ok(None);
202 };
203 let raw = validate_secret_file(path)?;
204 let trimmed = raw.trim();
205 let bytes = varta_vlp::decode_hex_32(trimmed.as_bytes()).map_err(|e| {
206 io::Error::new(
207 io::ErrorKind::InvalidData,
208 format!("{}: {e}", path.display()),
209 )
210 })?;
211 Ok(Some(varta_vlp::crypto::BearerToken::from_bytes(bytes)))
212 }
213}