1use std::{net::IpAddr, path::PathBuf, str::FromStr as _};
2
3use ini::Ini;
4use url::Url;
5
6use crate::{
7 ckb_config::AppConfig as CkbConfig,
8 error::{Error, Result},
9};
10
11const NORMAL_CONFIG_FILE: &str = "/etc/ckbdev.conf";
12const SECRET_CONFIG_FILE: &str = "/etc/ckbdev.secret.conf";
13
14pub struct Config {
15 pub(crate) normal: NormalConfig,
16 pub(crate) secret: SecretConfig,
17}
18
19pub(crate) struct NormalConfig {
20 pub(crate) host: HostSection,
21 pub(crate) ckb: CkbSection,
22}
23
24pub(crate) struct SecretConfig {
25 pub(crate) qiniu: QiniuSection,
26}
27
28pub(crate) struct HostSection {
29 #[allow(dead_code)]
30 pub(crate) ip: IpAddr,
31 pub(crate) name: String,
32}
33
34pub(crate) struct CkbSection {
35 pub(crate) service_name: String,
36 pub(crate) bin_path: PathBuf,
37 pub(crate) root_dir: PathBuf,
38 pub(crate) data_dir: PathBuf,
39 pub(crate) rpc_url: Url,
40}
41
42pub(crate) struct QiniuSection {
43 pub(crate) access_key: String,
44 pub(crate) secret_key: String,
45 pub(crate) bucket: String,
46 pub(crate) domain: Url,
47 pub(crate) path_prefix: String,
48}
49
50impl Config {
51 pub fn load_from_files() -> Result<Self> {
52 let normal = NormalConfig::load_from_file(NORMAL_CONFIG_FILE)?;
53 let secret = SecretConfig::load_from_file(SECRET_CONFIG_FILE)?;
54 Ok(Self { normal, secret })
55 }
56}
57
58impl NormalConfig {
59 pub(crate) fn load_from_file(path: &str) -> Result<Self> {
60 let ini = Ini::load_from_file(path)
61 .map_err(|err| Error::Cfg(format!("failed to load \"{}\" since {}", path, err)))?;
62 let host = {
63 let prop = ini
64 .section(Some("host"))
65 .ok_or_else(|| Error::config_not_found("host"))?;
66 let ip = prop
67 .get("ip")
68 .ok_or_else(|| Error::config_not_found("host.ip"))
69 .and_then(|s| {
70 IpAddr::from_str(s).map_err(|err| {
71 Error::Cfg(format!("failed to parse [host.ip] since {}", err))
72 })
73 })?;
74 let name = prop
75 .get("name")
76 .ok_or_else(|| Error::config_not_found("host.name"))?
77 .to_owned();
78 HostSection { ip, name }
79 };
80 let ckb = {
81 let prop = ini
82 .section(Some("ckb"))
83 .ok_or_else(|| Error::config_not_found("ckb"))?;
84 let service_name = prop
85 .get("service_name")
86 .ok_or_else(|| Error::config_not_found("ckb.service_name"))?
87 .to_owned();
88 let bin_path = prop
89 .get("bin_path")
90 .ok_or_else(|| Error::config_not_found("ckb.bin_path"))
91 .map(PathBuf::from)?;
92 let root_dir = prop
93 .get("root_dir")
94 .ok_or_else(|| Error::config_not_found("ckb.root_dir"))
95 .map(PathBuf::from)?;
96 let ckb_cfg = CkbConfig::load_from_workdir(&root_dir)?;
97 let data_dir = root_dir.join(&ckb_cfg.data_dir);
98 let rpc_url = {
99 let url = format!("http://{}", ckb_cfg.rpc.listen_address);
100 Url::parse(&url).map_err(|err| {
101 Error::Cfg(format!(
102 "failed to parse CKB RPC URL [{}] since {}",
103 url, err
104 ))
105 })
106 }?;
107 CkbSection {
108 service_name,
109 bin_path,
110 root_dir,
111 data_dir,
112 rpc_url,
113 }
114 };
115 Ok(Self { host, ckb })
116 }
117}
118
119impl SecretConfig {
120 pub(crate) fn load_from_file(path: &str) -> Result<Self> {
121 let ini = Ini::load_from_file(path)
122 .map_err(|err| Error::Cfg(format!("failed to load \"{}\" since {}", path, err)))?;
123 let qiniu = {
124 let prop = ini
125 .section(Some("qiniu"))
126 .ok_or_else(|| Error::config_not_found("qiniu"))?;
127 let access_key = prop
128 .get("access_key")
129 .ok_or_else(|| Error::config_not_found("qiniu.access_key"))?
130 .to_owned();
131 let secret_key = prop
132 .get("secret_key")
133 .ok_or_else(|| Error::config_not_found("qiniu.secret_key"))?
134 .to_owned();
135 let bucket = prop
136 .get("bucket")
137 .ok_or_else(|| Error::config_not_found("qiniu.bucket"))?
138 .to_owned();
139 let domain = prop
140 .get("domain")
141 .ok_or_else(|| Error::config_not_found("qiniu.domain"))
142 .and_then(|s| {
143 let u = Url::parse(s).map_err(|err| {
144 Error::Cfg(format!("failed to parse [qiniu.domain] since {}", err))
145 })?;
146 if u.scheme() != "http" && u.scheme() != "https" {
147 let msg = "invalid [qiniu.domain], scheme should be \"http\" or \"https\"";
148 let err = Error::Cfg(msg.to_owned());
149 Err(err)
150 } else {
151 Ok(u)
152 }
153 })?;
154 let path_prefix = prop
155 .get("path_prefix")
156 .ok_or_else(|| Error::config_not_found("qiniu.path_prefix"))?
157 .to_owned();
158 QiniuSection {
159 access_key,
160 secret_key,
161 bucket,
162 domain,
163 path_prefix,
164 }
165 };
166 Ok(Self { qiniu })
167 }
168}