]>
Commit | Line | Data |
---|---|---|
331b869d | 1 | use std::collections::HashMap; |
941342f7 | 2 | use std::io::{self, Write}; |
ea0b8b6e | 3 | |
b29d046e | 4 | use anyhow::{format_err, Error}; |
9894469e | 5 | use serde_json::{json, Value}; |
9894469e | 6 | |
380bd7df | 7 | use proxmox::api::{api, cli::*, RpcEnvironment}; |
ae18c436 | 8 | use proxmox::tools::fs::CreateOptions; |
769f8c99 | 9 | |
01a08021 | 10 | use pbs_client::{display_task_log, view_task_result}; |
4805edc4 | 11 | use pbs_tools::percent_encoding::percent_encode_component; |
3c8c2827 | 12 | use pbs_tools::json::required_string_param; |
6227654a DM |
13 | use pbs_api_types::{ |
14 | DATASTORE_SCHEMA, UPID_SCHEMA, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, | |
15 | IGNORE_VERIFIED_BACKUPS_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA, | |
16 | }; | |
4805edc4 | 17 | |
b9700a9f DM |
18 | use proxmox_rest_server::wait_for_local_worker; |
19 | ||
6227654a | 20 | use proxmox_backup::api2; |
01a08021 WB |
21 | use proxmox_backup::client_helpers::connect_to_localhost; |
22 | use proxmox_backup::config; | |
769f8c99 | 23 | |
a220a456 DM |
24 | mod proxmox_backup_manager; |
25 | use proxmox_backup_manager::*; | |
26 | ||
769f8c99 DM |
27 | #[api( |
28 | input: { | |
29 | properties: { | |
30 | store: { | |
31 | schema: DATASTORE_SCHEMA, | |
32 | }, | |
33 | "output-format": { | |
34 | schema: OUTPUT_FORMAT, | |
35 | optional: true, | |
36 | }, | |
37 | } | |
38 | } | |
39 | )] | |
40 | /// Start garbage collection for a specific datastore. | |
41 | async fn start_garbage_collection(param: Value) -> Result<Value, Error> { | |
691c89a0 | 42 | |
ac3faaf5 | 43 | let output_format = get_output_format(¶m); |
691c89a0 | 44 | |
3c8c2827 | 45 | let store = required_string_param(¶m, "store")?; |
769f8c99 | 46 | |
6b68e5d5 | 47 | let mut client = connect_to_localhost()?; |
769f8c99 DM |
48 | |
49 | let path = format!("api2/json/admin/datastore/{}/gc", store); | |
50 | ||
51 | let result = client.post(&path, None).await?; | |
52 | ||
e68269fc | 53 | view_task_result(&mut client, result, &output_format).await?; |
769f8c99 DM |
54 | |
55 | Ok(Value::Null) | |
56 | } | |
57 | ||
58 | #[api( | |
59 | input: { | |
60 | properties: { | |
61 | store: { | |
62 | schema: DATASTORE_SCHEMA, | |
63 | }, | |
64 | "output-format": { | |
65 | schema: OUTPUT_FORMAT, | |
66 | optional: true, | |
67 | }, | |
68 | } | |
69 | } | |
70 | )] | |
71 | /// Show garbage collection status for a specific datastore. | |
72 | async fn garbage_collection_status(param: Value) -> Result<Value, Error> { | |
73 | ||
ac3faaf5 | 74 | let output_format = get_output_format(¶m); |
769f8c99 | 75 | |
3c8c2827 | 76 | let store = required_string_param(¶m, "store")?; |
769f8c99 | 77 | |
6b68e5d5 | 78 | let client = connect_to_localhost()?; |
769f8c99 DM |
79 | |
80 | let path = format!("api2/json/admin/datastore/{}/gc", store); | |
81 | ||
9894469e DM |
82 | let mut result = client.get(&path, None).await?; |
83 | let mut data = result["data"].take(); | |
b2362a12 | 84 | let return_type = &api2::admin::datastore::API_METHOD_GARBAGE_COLLECTION_STATUS.returns; |
9894469e | 85 | |
ac3faaf5 | 86 | let options = default_table_format_options(); |
9894469e | 87 | |
b2362a12 | 88 | format_and_print_result_full(&mut data, return_type, &output_format, &options); |
769f8c99 DM |
89 | |
90 | Ok(Value::Null) | |
91 | } | |
92 | ||
93 | fn garbage_collection_commands() -> CommandLineInterface { | |
691c89a0 DM |
94 | |
95 | let cmd_def = CliCommandMap::new() | |
96 | .insert("status", | |
769f8c99 | 97 | CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS) |
49fddd98 | 98 | .arg_param(&["store"]) |
e7d4be9d | 99 | .completion_cb("store", pbs_config::datastore::complete_datastore_name) |
48ef3c33 | 100 | ) |
691c89a0 | 101 | .insert("start", |
769f8c99 | 102 | CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION) |
49fddd98 | 103 | .arg_param(&["store"]) |
e7d4be9d | 104 | .completion_cb("store", pbs_config::datastore::complete_datastore_name) |
48ef3c33 | 105 | ); |
691c89a0 DM |
106 | |
107 | cmd_def.into() | |
108 | } | |
109 | ||
47d47121 DM |
110 | #[api( |
111 | input: { | |
112 | properties: { | |
113 | limit: { | |
114 | description: "The maximal number of tasks to list.", | |
115 | type: Integer, | |
116 | optional: true, | |
117 | minimum: 1, | |
118 | maximum: 1000, | |
119 | default: 50, | |
120 | }, | |
121 | "output-format": { | |
122 | schema: OUTPUT_FORMAT, | |
123 | optional: true, | |
124 | }, | |
125 | all: { | |
126 | type: Boolean, | |
127 | description: "Also list stopped tasks.", | |
128 | optional: true, | |
129 | } | |
130 | } | |
131 | } | |
132 | )] | |
133 | /// List running server tasks. | |
134 | async fn task_list(param: Value) -> Result<Value, Error> { | |
135 | ||
ac3faaf5 | 136 | let output_format = get_output_format(¶m); |
47d47121 | 137 | |
6b68e5d5 | 138 | let client = connect_to_localhost()?; |
47d47121 DM |
139 | |
140 | let limit = param["limit"].as_u64().unwrap_or(50) as usize; | |
141 | let running = !param["all"].as_bool().unwrap_or(false); | |
142 | let args = json!({ | |
143 | "running": running, | |
144 | "start": 0, | |
145 | "limit": limit, | |
146 | }); | |
9894469e | 147 | let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?; |
47d47121 | 148 | |
9894469e | 149 | let mut data = result["data"].take(); |
b2362a12 | 150 | let return_type = &api2::node::tasks::API_METHOD_LIST_TASKS.returns; |
47d47121 | 151 | |
770a36e5 | 152 | use pbs_tools::format::{render_epoch, render_task_status}; |
ac3faaf5 | 153 | let options = default_table_format_options() |
770a36e5 WB |
154 | .column(ColumnConfig::new("starttime").right_align(false).renderer(render_epoch)) |
155 | .column(ColumnConfig::new("endtime").right_align(false).renderer(render_epoch)) | |
93fbb4ef | 156 | .column(ColumnConfig::new("upid")) |
770a36e5 | 157 | .column(ColumnConfig::new("status").renderer(render_task_status)); |
9894469e | 158 | |
b2362a12 | 159 | format_and_print_result_full(&mut data, return_type, &output_format, &options); |
47d47121 DM |
160 | |
161 | Ok(Value::Null) | |
162 | } | |
163 | ||
164 | #[api( | |
165 | input: { | |
166 | properties: { | |
167 | upid: { | |
168 | schema: UPID_SCHEMA, | |
169 | }, | |
170 | } | |
171 | } | |
172 | )] | |
173 | /// Display the task log. | |
174 | async fn task_log(param: Value) -> Result<Value, Error> { | |
175 | ||
3c8c2827 | 176 | let upid = required_string_param(¶m, "upid")?; |
47d47121 | 177 | |
e68269fc | 178 | let mut client = connect_to_localhost()?; |
47d47121 | 179 | |
e68269fc | 180 | display_task_log(&mut client, upid, true).await?; |
47d47121 DM |
181 | |
182 | Ok(Value::Null) | |
183 | } | |
184 | ||
185 | #[api( | |
186 | input: { | |
187 | properties: { | |
188 | upid: { | |
189 | schema: UPID_SCHEMA, | |
190 | }, | |
191 | } | |
192 | } | |
193 | )] | |
194 | /// Try to stop a specific task. | |
195 | async fn task_stop(param: Value) -> Result<Value, Error> { | |
196 | ||
3c8c2827 | 197 | let upid_str = required_string_param(¶m, "upid")?; |
47d47121 | 198 | |
6b68e5d5 | 199 | let mut client = connect_to_localhost()?; |
47d47121 | 200 | |
4805edc4 | 201 | let path = format!("api2/json/nodes/localhost/tasks/{}", percent_encode_component(upid_str)); |
47d47121 DM |
202 | let _ = client.delete(&path, None).await?; |
203 | ||
204 | Ok(Value::Null) | |
205 | } | |
206 | ||
207 | fn task_mgmt_cli() -> CommandLineInterface { | |
208 | ||
209 | let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG) | |
210 | .arg_param(&["upid"]); | |
211 | ||
212 | let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP) | |
213 | .arg_param(&["upid"]); | |
214 | ||
215 | let cmd_def = CliCommandMap::new() | |
216 | .insert("list", CliCommand::new(&API_METHOD_TASK_LIST)) | |
217 | .insert("log", task_log_cmd_def) | |
218 | .insert("stop", task_stop_cmd_def); | |
219 | ||
220 | cmd_def.into() | |
221 | } | |
222 | ||
4b4eba0b | 223 | // fixme: avoid API redefinition |
0eb0e024 DM |
224 | #[api( |
225 | input: { | |
226 | properties: { | |
eb506c83 | 227 | "local-store": { |
0eb0e024 DM |
228 | schema: DATASTORE_SCHEMA, |
229 | }, | |
230 | remote: { | |
167971ed | 231 | schema: REMOTE_ID_SCHEMA, |
0eb0e024 DM |
232 | }, |
233 | "remote-store": { | |
234 | schema: DATASTORE_SCHEMA, | |
235 | }, | |
40dc1031 DC |
236 | "remove-vanished": { |
237 | schema: REMOVE_VANISHED_BACKUPS_SCHEMA, | |
4b4eba0b | 238 | optional: true, |
4b4eba0b | 239 | }, |
0eb0e024 DM |
240 | "output-format": { |
241 | schema: OUTPUT_FORMAT, | |
242 | optional: true, | |
243 | }, | |
244 | } | |
245 | } | |
246 | )] | |
eb506c83 DM |
247 | /// Sync datastore from another repository |
248 | async fn pull_datastore( | |
0eb0e024 DM |
249 | remote: String, |
250 | remote_store: String, | |
eb506c83 | 251 | local_store: String, |
40dc1031 | 252 | remove_vanished: Option<bool>, |
ac3faaf5 | 253 | param: Value, |
0eb0e024 DM |
254 | ) -> Result<Value, Error> { |
255 | ||
ac3faaf5 | 256 | let output_format = get_output_format(¶m); |
0eb0e024 | 257 | |
6b68e5d5 | 258 | let mut client = connect_to_localhost()?; |
0eb0e024 | 259 | |
8c877436 | 260 | let mut args = json!({ |
eb506c83 | 261 | "store": local_store, |
94609e23 | 262 | "remote": remote, |
0eb0e024 | 263 | "remote-store": remote_store, |
0eb0e024 DM |
264 | }); |
265 | ||
8c877436 DC |
266 | if let Some(remove_vanished) = remove_vanished { |
267 | args["remove-vanished"] = Value::from(remove_vanished); | |
268 | } | |
269 | ||
eb506c83 | 270 | let result = client.post("api2/json/pull", Some(args)).await?; |
0eb0e024 | 271 | |
e68269fc | 272 | view_task_result(&mut client, result, &output_format).await?; |
0eb0e024 DM |
273 | |
274 | Ok(Value::Null) | |
275 | } | |
276 | ||
355c055e DM |
277 | #[api( |
278 | input: { | |
279 | properties: { | |
280 | "store": { | |
281 | schema: DATASTORE_SCHEMA, | |
282 | }, | |
60abf03f HL |
283 | "ignore-verified": { |
284 | schema: IGNORE_VERIFIED_BACKUPS_SCHEMA, | |
285 | optional: true, | |
286 | }, | |
287 | "outdated-after": { | |
288 | schema: VERIFICATION_OUTDATED_AFTER_SCHEMA, | |
289 | optional: true, | |
290 | }, | |
355c055e DM |
291 | "output-format": { |
292 | schema: OUTPUT_FORMAT, | |
293 | optional: true, | |
294 | }, | |
295 | } | |
296 | } | |
297 | )] | |
298 | /// Verify backups | |
299 | async fn verify( | |
300 | store: String, | |
60abf03f | 301 | mut param: Value, |
355c055e DM |
302 | ) -> Result<Value, Error> { |
303 | ||
60abf03f | 304 | let output_format = extract_output_format(&mut param); |
355c055e | 305 | |
6b68e5d5 | 306 | let mut client = connect_to_localhost()?; |
355c055e | 307 | |
60abf03f | 308 | let args = json!(param); |
355c055e DM |
309 | |
310 | let path = format!("api2/json/admin/datastore/{}/verify", store); | |
311 | ||
312 | let result = client.post(&path, Some(args)).await?; | |
313 | ||
e68269fc | 314 | view_task_result(&mut client, result, &output_format).await?; |
355c055e DM |
315 | |
316 | Ok(Value::Null) | |
317 | } | |
318 | ||
9a556c8a HL |
319 | #[api()] |
320 | /// System report | |
321 | async fn report() -> Result<Value, Error> { | |
941342f7 TL |
322 | let report = proxmox_backup::server::generate_report(); |
323 | io::stdout().write_all(report.as_bytes())?; | |
9a556c8a HL |
324 | Ok(Value::Null) |
325 | } | |
326 | ||
c100fe91 ML |
327 | #[api( |
328 | input: { | |
329 | properties: { | |
330 | verbose: { | |
331 | type: Boolean, | |
332 | optional: true, | |
333 | default: false, | |
334 | description: "Output verbose package information. It is ignored if output-format is specified.", | |
335 | }, | |
336 | "output-format": { | |
337 | schema: OUTPUT_FORMAT, | |
338 | optional: true, | |
339 | } | |
340 | } | |
341 | } | |
342 | )] | |
343 | /// List package versions for important Proxmox Backup Server packages. | |
344 | async fn get_versions(verbose: bool, param: Value) -> Result<Value, Error> { | |
294466ee | 345 | let output_format = get_output_format(¶m); |
c100fe91 | 346 | |
5e293f13 | 347 | let packages = crate::api2::node::apt::get_versions()?; |
fc5a0120 | 348 | let mut packages = json!(if verbose { &packages[..] } else { &packages[1..2] }); |
c100fe91 | 349 | |
294466ee TL |
350 | let options = default_table_format_options() |
351 | .disable_sort() | |
d1d74c43 | 352 | .noborder(true) // just not helpful for version info which gets copy pasted often |
294466ee TL |
353 | .column(ColumnConfig::new("Package")) |
354 | .column(ColumnConfig::new("Version")) | |
355 | .column(ColumnConfig::new("ExtraInfo").header("Extra Info")) | |
356 | ; | |
b2362a12 | 357 | let return_type = &crate::api2::node::apt::API_METHOD_GET_VERSIONS.returns; |
294466ee | 358 | |
b2362a12 | 359 | format_and_print_result_full(&mut packages, return_type, &output_format, &options); |
c100fe91 ML |
360 | |
361 | Ok(Value::Null) | |
362 | } | |
363 | ||
ae18c436 | 364 | async fn run() -> Result<(), Error> { |
ac7513e3 | 365 | |
6460764d | 366 | let cmd_def = CliCommandMap::new() |
ed3e60ae | 367 | .insert("acl", acl_commands()) |
48ef3c33 | 368 | .insert("datastore", datastore_commands()) |
8e40aa63 | 369 | .insert("disk", disk_commands()) |
14627d67 | 370 | .insert("dns", dns_commands()) |
ca0e5347 | 371 | .insert("network", network_commands()) |
72e311c6 | 372 | .insert("node", node_commands()) |
579728c6 | 373 | .insert("user", user_commands()) |
0decd11e | 374 | .insert("openid", openid_commands()) |
f357390c | 375 | .insert("remote", remote_commands()) |
47d47121 | 376 | .insert("garbage-collection", garbage_collection_commands()) |
72bd8293 | 377 | .insert("acme", acme_mgmt_cli()) |
550e0d88 | 378 | .insert("cert", cert_mgmt_cli()) |
2762481c | 379 | .insert("subscription", subscription_commands()) |
a3016d65 | 380 | .insert("sync-job", sync_job_commands()) |
4a874665 | 381 | .insert("verify-job", verify_job_commands()) |
0eb0e024 DM |
382 | .insert("task", task_mgmt_cli()) |
383 | .insert( | |
eb506c83 DM |
384 | "pull", |
385 | CliCommand::new(&API_METHOD_PULL_DATASTORE) | |
386 | .arg_param(&["remote", "remote-store", "local-store"]) | |
e7d4be9d | 387 | .completion_cb("local-store", pbs_config::datastore::complete_datastore_name) |
6afdda88 | 388 | .completion_cb("remote", pbs_config::remote::complete_remote_name) |
331b869d | 389 | .completion_cb("remote-store", complete_remote_datastore_name) |
355c055e DM |
390 | ) |
391 | .insert( | |
392 | "verify", | |
393 | CliCommand::new(&API_METHOD_VERIFY) | |
394 | .arg_param(&["store"]) | |
e7d4be9d | 395 | .completion_cb("store", pbs_config::datastore::complete_datastore_name) |
9a556c8a HL |
396 | ) |
397 | .insert("report", | |
398 | CliCommand::new(&API_METHOD_REPORT) | |
c100fe91 ML |
399 | ) |
400 | .insert("versions", | |
401 | CliCommand::new(&API_METHOD_GET_VERSIONS) | |
0eb0e024 | 402 | ); |
34d3ba52 | 403 | |
bbd57396 DM |
404 | let args: Vec<String> = std::env::args().take(2).collect(); |
405 | let avoid_init = args.len() >= 2 && (args[1] == "bashcomplete" || args[1] == "printdoc"); | |
355c055e | 406 | |
bbd57396 DM |
407 | if !avoid_init { |
408 | let backup_user = pbs_config::backup_user()?; | |
409 | let file_opts = CreateOptions::new().owner(backup_user.uid).group(backup_user.gid); | |
410 | proxmox_rest_server::init_worker_tasks(pbs_buildcfg::PROXMOX_BACKUP_LOG_DIR_M!().into(), file_opts.clone())?; | |
411 | ||
412 | let mut commando_sock = proxmox_rest_server::CommandoSocket::new(proxmox_rest_server::our_ctrl_sock(), backup_user.gid); | |
413 | proxmox_rest_server::register_task_control_commands(&mut commando_sock)?; | |
414 | commando_sock.spawn()?; | |
415 | } | |
355c055e | 416 | |
7b22acd0 | 417 | let mut rpcenv = CliEnvironment::new(); |
e6dc35ac | 418 | rpcenv.set_auth_id(Some(String::from("root@pam"))); |
525008f7 | 419 | |
ae18c436 DM |
420 | run_async_cli_command(cmd_def, rpcenv).await; // this call exit(-1) on error |
421 | ||
422 | Ok(()) | |
423 | } | |
424 | ||
425 | fn main() -> Result<(), Error> { | |
426 | ||
427 | proxmox_backup::tools::setup_safe_path_env(); | |
428 | ||
429 | pbs_runtime::main(run()) | |
ea0b8b6e | 430 | } |
331b869d DM |
431 | |
432 | // shell completion helper | |
433 | pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> { | |
434 | ||
435 | let mut list = Vec::new(); | |
436 | ||
9ea4bce4 | 437 | let _ = proxmox::try_block!({ |
331b869d | 438 | let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?; |
331b869d | 439 | |
d420962f | 440 | let data = pbs_runtime::block_on(async move { |
e0100d61 FG |
441 | crate::api2::config::remote::scan_remote_datastores(remote.clone()).await |
442 | })?; | |
331b869d | 443 | |
e0100d61 FG |
444 | for item in data { |
445 | list.push(item.store); | |
331b869d DM |
446 | } |
447 | ||
448 | Ok(()) | |
449 | }).map_err(|_err: Error| { /* ignore */ }); | |
450 | ||
451 | list | |
452 | } |