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