1use log::*;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4#[cfg(feature = "main")]
5use std::path::Path;
6use std::path::PathBuf;
7use std::{env, fs};
8
9pub fn compare_env_var(name: &str, value: &str) -> bool {
11 match env::var(name) {
12 Ok(val) => val == value,
13 Err(_) => false,
14 }
15}
16
17pub fn line_filter(line: &str) -> Option<String> {
18 let whitespace_removed = line.trim();
19 if whitespace_removed.is_empty() {
20 return None;
21 }
22 let comment_removed = whitespace_removed.split('#').next()?.trim();
23 if comment_removed.is_empty() {
24 return None;
25 }
26 Some(comment_removed.to_string())
27}
28
29pub fn read_allowlist() -> Vec<String> {
30 if let Ok(allowlist_path) = env::var("REMOTE_SIGNER_ALLOWLIST") {
31 return read_allowlist_path(&allowlist_path);
32 }
33 Vec::new()
34}
35
36pub fn read_allowlist_path(path: &str) -> Vec<String> {
37 let file = File::open(path).expect(format!("open {} failed", path).as_str());
38 let allowlist: Vec<String> =
39 BufReader::new(file).lines().filter_map(|l| line_filter(&l.expect("line"))).collect();
40
41 allowlist
42}
43
44pub fn should_auto_approve() -> bool {
46 if compare_env_var("VLS_PERMISSIVE", "1") {
47 warn!("VLS_PERMISSIVE: ALL INVOICES, KEYSENDS, AND PAYMENTS AUTOMATICALLY APPROVED");
48 return true;
49 }
50
51 if compare_env_var("VLS_AUTOAPPROVE", "1") {
52 warn!("VLS_AUTOAPPROVE: ALL INVOICES, KEYSENDS, AND PAYMENTS AUTOMATICALLY APPROVED");
53 return true;
54 }
55
56 info!("VLS_ENFORCING: ALL INVOICES, KEYSENDS, AND PAYMENTS REQUIRE APPROVAL");
57 false
58}
59
60pub fn abort_on_panic() {
64 let old = std::panic::take_hook();
65 std::panic::set_hook(Box::new(move |info| {
66 old(info);
67 std::process::abort();
68 }));
69}
70
71#[cfg(feature = "main")]
72pub fn setup_logging<P: AsRef<Path>>(datadir: P, who: &str, level_arg: &str) {
73 use fern::colors::{Color, ColoredLevelConfig};
74 use std::str::FromStr;
75
76 let level = env::var("RUST_LOG").unwrap_or(level_arg.to_string());
78
79 let who_clone = who.to_string();
81 let logfile = datadir.as_ref().join(format!("{}.log", who));
82 let file_config = fern::Dispatch::new()
83 .format(move |out, message, record| {
84 out.finish(format_args!(
85 "[{} {}/{} {}] {}",
86 tstamp(),
87 who_clone,
88 record.target(),
89 record.level(),
90 message
91 ))
92 })
93 .level(log::LevelFilter::from_str(&level).expect("level"))
94 .level_for("h2", log::LevelFilter::Info)
95 .chain(fern::log_file(logfile).expect("file log config"));
96
97 let who_clone = who.to_string();
99 let colors = ColoredLevelConfig::new().info(Color::Green).error(Color::Red).warn(Color::Yellow);
100 let console_config = fern::Dispatch::new()
101 .format(move |out, message, record| {
102 out.finish(format_args!(
103 "[{} {}/{} {}] {}",
104 tstamp(),
105 who_clone,
106 record.target(),
107 colors.color(record.level()),
108 message
109 ))
110 })
111 .level(log::LevelFilter::from_str(&level).expect("level"))
112 .level_for("h2", log::LevelFilter::Info)
113 .chain(std::io::stdout());
114
115 fern::Dispatch::new().chain(console_config).chain(file_config).apply().expect("log config");
116}
117
118#[cfg(feature = "main")]
121pub fn tstamp() -> String {
122 use time::{macros::format_description, OffsetDateTime};
123
124 OffsetDateTime::now_utc()
125 .format(format_description!(
126 "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"
127 ))
128 .expect("formatted tstamp")
129}
130
131pub fn read_integration_test_seed<P: AsRef<Path>>(datadir: P) -> Option<[u8; 32]> {
132 let path = PathBuf::from(datadir.as_ref()).join("hsm_secret");
133 tracing::warn!("reading integration hsm_secret from {:?}", path);
134 let result = fs::read(path);
135 if let Ok(data) = result {
136 Some(
137 data.as_slice().try_into().unwrap_or_else(|_| {
138 panic!("Expected hsm_secret to be 32 bytes, got {}", data.len())
139 }),
140 )
141 } else {
142 None
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use std::io::Write;
150
151 #[test]
152 fn line_filter_test() {
153 assert_eq!(line_filter("#"), None);
154 assert_eq!(
155 line_filter("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z # comment"),
156 Some("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string())
157 );
158 assert_eq!(line_filter(" "), None);
159 assert_eq!(line_filter(" # "), None);
160 assert_eq!(
161 line_filter(" tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z "),
162 Some("tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string())
163 );
164 }
165
166 #[test]
167 fn read_allowlist_test() {
168 let test_file_content = "\
169 # Sample Allowlist
170 tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z
171 tb1qexampleaddress1234567890123456789012345678
172
173 # Another comment line after blank line
174 ";
175 let mut temp_file = tempfile::NamedTempFile::new().unwrap();
176 write!(temp_file, "{}", test_file_content).unwrap();
177 let allowlist = read_allowlist_path(temp_file.path().to_str().unwrap());
178 assert_eq!(
179 allowlist,
180 vec![
181 "tb1qhetd7l0rv6kca6wvmt25ax5ej05eaat9q29z7z".to_string(),
182 "tb1qexampleaddress1234567890123456789012345678".to_string(),
183 ]
184 );
185 temp_file.close().unwrap();
186 }
187}