1use {
2 crate::update_manifest::UpdateManifest,
3 serde::{Deserialize, Serialize},
4 solana_sdk::pubkey::Pubkey,
5 std::{
6 fs::{create_dir_all, File},
7 io::{self, Write},
8 path::{Path, PathBuf},
9 },
10};
11
12#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
13pub enum ExplicitRelease {
14 Semver(String),
15 Channel(String),
16}
17
18#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
19pub struct Config {
20 pub json_rpc_url: String,
21 pub update_manifest_pubkey: Pubkey,
22 pub current_update_manifest: Option<UpdateManifest>,
23 pub update_poll_secs: u64,
24 pub explicit_release: Option<ExplicitRelease>,
25 pub releases_dir: PathBuf,
26 active_release_dir: PathBuf,
27}
28
29const LEGACY_FMT_LOAD_ERR: &str =
30 "explicit_release: invalid type: map, expected a YAML tag starting with '!'";
31
32impl Config {
33 pub fn new(
34 data_dir: &str,
35 json_rpc_url: &str,
36 update_manifest_pubkey: &Pubkey,
37 explicit_release: Option<ExplicitRelease>,
38 ) -> Self {
39 Self {
40 json_rpc_url: json_rpc_url.to_string(),
41 update_manifest_pubkey: *update_manifest_pubkey,
42 current_update_manifest: None,
43 update_poll_secs: 60 * 60, explicit_release,
45 releases_dir: PathBuf::from(data_dir).join("releases"),
46 active_release_dir: PathBuf::from(data_dir).join("active_release"),
47 }
48 }
49
50 fn _load(config_file: &str) -> Result<Self, io::Error> {
51 let file = File::open(config_file)?;
52 serde_yaml::from_reader(file).or_else(|err| {
53 let err_string = format!("{err:?}");
54 if err_string.contains(LEGACY_FMT_LOAD_ERR) {
55 Self::try_migrate_08(config_file)
58 .map_err(|_| io::Error::new(io::ErrorKind::Other, err_string))
59 } else {
60 Err(io::Error::new(io::ErrorKind::Other, err_string))
61 }
62 })
63 }
64
65 fn try_migrate_08(config_file: &str) -> Result<Self, io::Error> {
66 eprintln!("attempting to upgrade legacy config file");
67 let bak_filename = config_file.to_string() + ".bak";
68 std::fs::copy(config_file, &bak_filename)?;
69 let result = File::open(config_file).and_then(|file| {
70 serde_yaml_08::from_reader(file)
71 .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{err:?}")))
72 .and_then(|config_08: Self| {
73 let save = config_08._save(config_file).map(|_| config_08);
74 if save.is_ok() {
75 let _ = std::fs::remove_file(&bak_filename);
76 }
77 save
78 })
79 });
80 if result.is_err() {
81 eprintln!("config upgrade failed! restoring orignal");
82 let restored = std::fs::copy(&bak_filename, config_file)
83 .and_then(|_| std::fs::remove_file(&bak_filename));
84 if restored.is_err() {
85 eprintln!("restoration failed! original: `{bak_filename}`");
86 } else {
87 eprintln!("restoration succeeded!");
88 }
89 } else {
90 eprintln!("config upgrade succeeded!");
91 }
92 result
93 }
94
95 pub fn load(config_file: &str) -> Result<Self, String> {
96 Self::_load(config_file).map_err(|err| format!("Unable to load {config_file}: {err:?}"))
97 }
98
99 fn _save(&self, config_file: &str) -> Result<(), io::Error> {
100 let serialized = serde_yaml::to_string(self)
101 .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{err:?}")))?;
102
103 if let Some(outdir) = Path::new(&config_file).parent() {
104 create_dir_all(outdir)?;
105 }
106 let mut file = File::create(config_file)?;
107 file.write_all(b"---\n")?;
108 file.write_all(&serialized.into_bytes())?;
109
110 Ok(())
111 }
112
113 pub fn save(&self, config_file: &str) -> Result<(), String> {
114 self._save(config_file)
115 .map_err(|err| format!("Unable to save {config_file}: {err:?}"))
116 }
117
118 pub fn active_release_dir(&self) -> &PathBuf {
119 &self.active_release_dir
120 }
121
122 pub fn active_release_bin_dir(&self) -> PathBuf {
123 self.active_release_dir.join("bin")
124 }
125
126 pub fn release_dir(&self, release_id: &str) -> PathBuf {
127 self.releases_dir.join(release_id)
128 }
129}
130
131#[cfg(test)]
132mod test {
133 use {
134 super::*,
135 scopeguard::defer,
136 std::{
137 env,
138 fs::{read_to_string, remove_file},
139 },
140 };
141
142 #[test]
143 fn test_save() {
144 let root_dir = env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
145 let json_rpc_url = "https://api.mainnet-beta.solana.com";
146 let pubkey = Pubkey::default();
147 let config_name = "config.yaml";
148 let config_path = format!("{root_dir}/{config_name}");
149
150 let config = Config::new(&root_dir, json_rpc_url, &pubkey, None);
151
152 assert_eq!(config.save(config_name), Ok(()));
153 defer! {
154 remove_file(&config_path).unwrap();
155 }
156
157 assert_eq!(
158 read_to_string(&config_path).unwrap(),
159 format!(
160 "---
161json_rpc_url: https://api.mainnet-beta.solana.com
162update_manifest_pubkey:
163- 0
164- 0
165- 0
166- 0
167- 0
168- 0
169- 0
170- 0
171- 0
172- 0
173- 0
174- 0
175- 0
176- 0
177- 0
178- 0
179- 0
180- 0
181- 0
182- 0
183- 0
184- 0
185- 0
186- 0
187- 0
188- 0
189- 0
190- 0
191- 0
192- 0
193- 0
194- 0
195current_update_manifest: null
196update_poll_secs: 3600
197explicit_release: null
198releases_dir: {root_dir}/releases
199active_release_dir: {root_dir}/active_release
200"
201 ),
202 );
203 }
204
205 #[test]
206 fn test_load_serde_yaml_v_0_8_config() {
207 let file_name = "config.yml";
208 let mut file = File::create(file_name).unwrap();
209 defer! {
210 remove_file(file_name).unwrap();
211 }
212
213 let root_dir = "/home/sol/.local/share/solana/install";
214
215 writeln!(
216 file,
217 "---
218json_rpc_url: \"http://api.devnet.solana.com\"
219update_manifest_pubkey:
220 - 0
221 - 0
222 - 0
223 - 0
224 - 0
225 - 0
226 - 0
227 - 0
228 - 0
229 - 0
230 - 0
231 - 0
232 - 0
233 - 0
234 - 0
235 - 0
236 - 0
237 - 0
238 - 0
239 - 0
240 - 0
241 - 0
242 - 0
243 - 0
244 - 0
245 - 0
246 - 0
247 - 0
248 - 0
249 - 0
250 - 0
251 - 0
252current_update_manifest: ~
253update_poll_secs: 3600
254explicit_release:
255 Semver: 1.13.6
256releases_dir: {root_dir}/releases
257active_release_dir: {root_dir}/active_release
258"
259 )
260 .unwrap();
261 let config = Config::load(file_name).unwrap();
262 assert_eq!(
263 config,
264 Config {
265 json_rpc_url: String::from("http://api.devnet.solana.com"),
266 update_manifest_pubkey: Pubkey::default(),
267 current_update_manifest: None,
268 update_poll_secs: 3600,
269 explicit_release: Some(ExplicitRelease::Semver(String::from("1.13.6"))),
270 releases_dir: PathBuf::from(format!("{root_dir}/releases")),
271 active_release_dir: PathBuf::from(format!("{root_dir}/active_release")),
272 },
273 );
274 }
275}