]>
Commit | Line | Data |
---|---|---|
769f8c99 | 1 | use failure::*; |
47d47121 | 2 | use serde_json::{json, Value}; |
550e0d88 | 3 | use std::path::PathBuf; |
ea0b8b6e | 4 | |
769f8c99 DM |
5 | use proxmox::api::{api, cli::*}; |
6 | ||
550e0d88 | 7 | use proxmox_backup::configdir; |
769f8c99 DM |
8 | use proxmox_backup::tools; |
9 | use proxmox_backup::config; | |
10 | use proxmox_backup::api2::types::*; | |
11 | use proxmox_backup::client::*; | |
12 | use proxmox_backup::tools::ticket::*; | |
13 | use proxmox_backup::auth_helpers::*; | |
14 | ||
15 | ||
16 | async fn view_task_result( | |
17 | client: HttpClient, | |
18 | result: Value, | |
19 | output_format: &str, | |
20 | ) -> Result<(), Error> { | |
21 | let data = &result["data"]; | |
22 | if output_format == "text" { | |
23 | if let Some(upid) = data.as_str() { | |
24 | display_task_log(client, upid, true).await?; | |
25 | } | |
26 | } else { | |
27 | format_and_print_result(&data, &output_format); | |
28 | } | |
29 | ||
30 | Ok(()) | |
31 | } | |
211fabd7 | 32 | |
47d47121 DM |
33 | fn connect() -> Result<HttpClient, Error> { |
34 | ||
35 | let uid = nix::unistd::Uid::current(); | |
36 | ||
37 | let client = if uid.is_root() { | |
38 | let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?; | |
39 | HttpClient::new("localhost", "root@pam", Some(ticket))? | |
40 | } else { | |
41 | HttpClient::new("localhost", "root@pam", None)? | |
42 | }; | |
43 | ||
44 | Ok(client) | |
45 | } | |
46 | ||
9f6ab1fc | 47 | fn datastore_commands() -> CommandLineInterface { |
ea0b8b6e | 48 | |
576e3bf2 | 49 | use proxmox_backup::api2; |
bf7f1039 | 50 | |
6460764d | 51 | let cmd_def = CliCommandMap::new() |
48ef3c33 | 52 | .insert("list", CliCommand::new(&api2::config::datastore::GET)) |
6460764d | 53 | .insert("create", |
255f378a | 54 | CliCommand::new(&api2::config::datastore::POST) |
49fddd98 | 55 | .arg_param(&["name", "path"]) |
48ef3c33 | 56 | ) |
6460764d | 57 | .insert("remove", |
255f378a | 58 | CliCommand::new(&api2::config::datastore::DELETE) |
49fddd98 | 59 | .arg_param(&["name"]) |
07b4694a | 60 | .completion_cb("name", config::datastore::complete_datastore_name) |
48ef3c33 | 61 | ); |
211fabd7 | 62 | |
8f62336b | 63 | cmd_def.into() |
211fabd7 DM |
64 | } |
65 | ||
691c89a0 | 66 | |
769f8c99 DM |
67 | #[api( |
68 | input: { | |
69 | properties: { | |
70 | store: { | |
71 | schema: DATASTORE_SCHEMA, | |
72 | }, | |
73 | "output-format": { | |
74 | schema: OUTPUT_FORMAT, | |
75 | optional: true, | |
76 | }, | |
77 | } | |
78 | } | |
79 | )] | |
80 | /// Start garbage collection for a specific datastore. | |
81 | async fn start_garbage_collection(param: Value) -> Result<Value, Error> { | |
691c89a0 | 82 | |
769f8c99 | 83 | let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); |
691c89a0 | 84 | |
769f8c99 DM |
85 | let store = tools::required_string_param(¶m, "store")?; |
86 | ||
47d47121 | 87 | let mut client = connect()?; |
769f8c99 DM |
88 | |
89 | let path = format!("api2/json/admin/datastore/{}/gc", store); | |
90 | ||
91 | let result = client.post(&path, None).await?; | |
92 | ||
93 | view_task_result(client, result, &output_format).await?; | |
94 | ||
95 | Ok(Value::Null) | |
96 | } | |
97 | ||
98 | #[api( | |
99 | input: { | |
100 | properties: { | |
101 | store: { | |
102 | schema: DATASTORE_SCHEMA, | |
103 | }, | |
104 | "output-format": { | |
105 | schema: OUTPUT_FORMAT, | |
106 | optional: true, | |
107 | }, | |
108 | } | |
109 | } | |
110 | )] | |
111 | /// Show garbage collection status for a specific datastore. | |
112 | async fn garbage_collection_status(param: Value) -> Result<Value, Error> { | |
113 | ||
114 | let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | |
115 | ||
116 | let store = tools::required_string_param(¶m, "store")?; | |
117 | ||
47d47121 | 118 | let client = connect()?; |
769f8c99 DM |
119 | |
120 | let path = format!("api2/json/admin/datastore/{}/gc", store); | |
121 | ||
122 | let result = client.get(&path, None).await?; | |
123 | let data = &result["data"]; | |
124 | if output_format == "text" { | |
125 | format_and_print_result(&data, "json-pretty"); | |
126 | } else { | |
127 | format_and_print_result(&data, &output_format); | |
128 | } | |
129 | ||
130 | Ok(Value::Null) | |
131 | } | |
132 | ||
133 | fn garbage_collection_commands() -> CommandLineInterface { | |
691c89a0 DM |
134 | |
135 | let cmd_def = CliCommandMap::new() | |
136 | .insert("status", | |
769f8c99 | 137 | CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS) |
49fddd98 | 138 | .arg_param(&["store"]) |
9ac1045c | 139 | .completion_cb("store", config::datastore::complete_datastore_name) |
48ef3c33 | 140 | ) |
691c89a0 | 141 | .insert("start", |
769f8c99 | 142 | CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION) |
49fddd98 | 143 | .arg_param(&["store"]) |
9ac1045c | 144 | .completion_cb("store", config::datastore::complete_datastore_name) |
48ef3c33 | 145 | ); |
691c89a0 DM |
146 | |
147 | cmd_def.into() | |
148 | } | |
149 | ||
47d47121 DM |
150 | #[api( |
151 | input: { | |
152 | properties: { | |
153 | limit: { | |
154 | description: "The maximal number of tasks to list.", | |
155 | type: Integer, | |
156 | optional: true, | |
157 | minimum: 1, | |
158 | maximum: 1000, | |
159 | default: 50, | |
160 | }, | |
161 | "output-format": { | |
162 | schema: OUTPUT_FORMAT, | |
163 | optional: true, | |
164 | }, | |
165 | all: { | |
166 | type: Boolean, | |
167 | description: "Also list stopped tasks.", | |
168 | optional: true, | |
169 | } | |
170 | } | |
171 | } | |
172 | )] | |
173 | /// List running server tasks. | |
174 | async fn task_list(param: Value) -> Result<Value, Error> { | |
175 | ||
176 | let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | |
177 | ||
178 | let client = connect()?; | |
179 | ||
180 | let limit = param["limit"].as_u64().unwrap_or(50) as usize; | |
181 | let running = !param["all"].as_bool().unwrap_or(false); | |
182 | let args = json!({ | |
183 | "running": running, | |
184 | "start": 0, | |
185 | "limit": limit, | |
186 | }); | |
187 | let result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?; | |
188 | ||
189 | let data = &result["data"]; | |
190 | ||
191 | if output_format == "text" { | |
192 | for item in data.as_array().unwrap() { | |
193 | println!( | |
194 | "{} {}", | |
195 | item["upid"].as_str().unwrap(), | |
196 | item["status"].as_str().unwrap_or("running"), | |
197 | ); | |
198 | } | |
199 | } else { | |
200 | format_and_print_result(data, &output_format); | |
201 | } | |
202 | ||
203 | Ok(Value::Null) | |
204 | } | |
205 | ||
206 | #[api( | |
207 | input: { | |
208 | properties: { | |
209 | upid: { | |
210 | schema: UPID_SCHEMA, | |
211 | }, | |
212 | } | |
213 | } | |
214 | )] | |
215 | /// Display the task log. | |
216 | async fn task_log(param: Value) -> Result<Value, Error> { | |
217 | ||
218 | let upid = tools::required_string_param(¶m, "upid")?; | |
219 | ||
220 | let client = connect()?; | |
221 | ||
222 | display_task_log(client, upid, true).await?; | |
223 | ||
224 | Ok(Value::Null) | |
225 | } | |
226 | ||
227 | #[api( | |
228 | input: { | |
229 | properties: { | |
230 | upid: { | |
231 | schema: UPID_SCHEMA, | |
232 | }, | |
233 | } | |
234 | } | |
235 | )] | |
236 | /// Try to stop a specific task. | |
237 | async fn task_stop(param: Value) -> Result<Value, Error> { | |
238 | ||
239 | let upid_str = tools::required_string_param(¶m, "upid")?; | |
240 | ||
241 | let mut client = connect()?; | |
242 | ||
243 | let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str); | |
244 | let _ = client.delete(&path, None).await?; | |
245 | ||
246 | Ok(Value::Null) | |
247 | } | |
248 | ||
249 | fn task_mgmt_cli() -> CommandLineInterface { | |
250 | ||
251 | let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG) | |
252 | .arg_param(&["upid"]); | |
253 | ||
254 | let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP) | |
255 | .arg_param(&["upid"]); | |
256 | ||
257 | let cmd_def = CliCommandMap::new() | |
258 | .insert("list", CliCommand::new(&API_METHOD_TASK_LIST)) | |
259 | .insert("log", task_log_cmd_def) | |
260 | .insert("stop", task_stop_cmd_def); | |
261 | ||
262 | cmd_def.into() | |
263 | } | |
264 | ||
e739a8d8 DM |
265 | fn x509name_to_string(name: &openssl::x509::X509NameRef) -> Result<String, Error> { |
266 | let mut parts = Vec::new(); | |
267 | for entry in name.entries() { | |
268 | parts.push(format!("{} = {}", entry.object().nid().short_name()?, entry.data().as_utf8()?)); | |
269 | } | |
270 | Ok(parts.join(", ")) | |
271 | } | |
272 | ||
273 | #[api] | |
274 | /// Diplay node certificate information. | |
275 | fn cert_info() -> Result<(), Error> { | |
276 | ||
277 | let cert_path = PathBuf::from(configdir!("/proxy.pem")); | |
278 | ||
279 | let cert_pem = proxmox::tools::fs::file_get_contents(&cert_path)?; | |
280 | ||
281 | let cert = openssl::x509::X509::from_pem(&cert_pem)?; | |
282 | ||
283 | println!("Subject: {}", x509name_to_string(cert.subject_name())?); | |
284 | ||
285 | if let Some(san) = cert.subject_alt_names() { | |
286 | for name in san.iter() { | |
287 | if let Some(v) = name.dnsname() { | |
288 | println!(" DNS:{}", v); | |
289 | } else if let Some(v) = name.ipaddress() { | |
290 | println!(" IP:{:?}", v); | |
291 | } else if let Some(v) = name.email() { | |
292 | println!(" EMAIL:{}", v); | |
293 | } else if let Some(v) = name.uri() { | |
294 | println!(" URI:{}", v); | |
295 | } | |
296 | } | |
297 | } | |
298 | ||
299 | println!("Issuer: {}", x509name_to_string(cert.issuer_name())?); | |
300 | println!("Validity:"); | |
301 | println!(" Not Before: {}", cert.not_before()); | |
302 | println!(" Not After : {}", cert.not_after()); | |
303 | ||
304 | let fp = cert.digest(openssl::hash::MessageDigest::sha256())?; | |
305 | let fp_string = proxmox::tools::digest_to_hex(&fp); | |
306 | let fp_string = fp_string.as_bytes().chunks(2).map(|v| std::str::from_utf8(v).unwrap()) | |
307 | .collect::<Vec<&str>>().join(":"); | |
308 | ||
309 | println!("Fingerprint (sha256): {}", fp_string); | |
310 | ||
311 | let pubkey = cert.public_key()?; | |
312 | println!("Public key type: {}", openssl::nid::Nid::from_raw(pubkey.id().as_raw()).long_name()?); | |
313 | println!("Public key bits: {}", pubkey.bits()); | |
314 | ||
315 | Ok(()) | |
316 | } | |
317 | ||
550e0d88 DM |
318 | #[api( |
319 | input: { | |
320 | properties: { | |
321 | force: { | |
322 | description: "Force generation of new SSL certifate.", | |
323 | type: Boolean, | |
324 | optional:true, | |
325 | }, | |
326 | } | |
327 | }, | |
328 | )] | |
329 | /// Update node certificates and generate all needed files/directories. | |
330 | fn update_certs(force: Option<bool>) -> Result<(), Error> { | |
331 | ||
550e0d88 DM |
332 | config::create_configdir()?; |
333 | ||
334 | if let Err(err) = generate_auth_key() { | |
335 | bail!("unable to generate auth key - {}", err); | |
336 | } | |
337 | ||
338 | if let Err(err) = generate_csrf_key() { | |
339 | bail!("unable to generate csrf key - {}", err); | |
340 | } | |
341 | ||
f8fd5095 | 342 | config::update_self_signed_cert(force.unwrap_or(false))?; |
550e0d88 DM |
343 | |
344 | Ok(()) | |
345 | } | |
346 | ||
347 | fn cert_mgmt_cli() -> CommandLineInterface { | |
348 | ||
550e0d88 | 349 | let cmd_def = CliCommandMap::new() |
e739a8d8 DM |
350 | .insert("info", CliCommand::new(&API_METHOD_CERT_INFO)) |
351 | .insert("update", CliCommand::new(&API_METHOD_UPDATE_CERTS)); | |
550e0d88 DM |
352 | |
353 | cmd_def.into() | |
354 | } | |
355 | ||
211fabd7 DM |
356 | fn main() { |
357 | ||
6460764d | 358 | let cmd_def = CliCommandMap::new() |
48ef3c33 | 359 | .insert("datastore", datastore_commands()) |
47d47121 | 360 | .insert("garbage-collection", garbage_collection_commands()) |
550e0d88 | 361 | .insert("cert", cert_mgmt_cli()) |
47d47121 | 362 | .insert("task", task_mgmt_cli()); |
34d3ba52 | 363 | |
48ef3c33 | 364 | run_cli_command(cmd_def); |
ea0b8b6e | 365 | } |