]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox-backup-manager.rs
split out pbs-runtime module
[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 let options = default_table_format_options()
143 .column(ColumnConfig::new("starttime").right_align(false).renderer(tools::format::render_epoch))
144 .column(ColumnConfig::new("endtime").right_align(false).renderer(tools::format::render_epoch))
145 .column(ColumnConfig::new("upid"))
146 .column(ColumnConfig::new("status").renderer(tools::format::render_task_status));
147
148 format_and_print_result_full(&mut data, return_type, &output_format, &options);
149
150 Ok(Value::Null)
151 }
152
153 #[api(
154 input: {
155 properties: {
156 upid: {
157 schema: UPID_SCHEMA,
158 },
159 }
160 }
161 )]
162 /// Display the task log.
163 async fn task_log(param: Value) -> Result<Value, Error> {
164
165 let upid = tools::required_string_param(&param, "upid")?;
166
167 let mut client = connect_to_localhost()?;
168
169 display_task_log(&mut client, upid, true).await?;
170
171 Ok(Value::Null)
172 }
173
174 #[api(
175 input: {
176 properties: {
177 upid: {
178 schema: UPID_SCHEMA,
179 },
180 }
181 }
182 )]
183 /// Try to stop a specific task.
184 async fn task_stop(param: Value) -> Result<Value, Error> {
185
186 let upid_str = tools::required_string_param(&param, "upid")?;
187
188 let mut client = connect_to_localhost()?;
189
190 let path = format!("api2/json/nodes/localhost/tasks/{}", tools::percent_encode_component(upid_str));
191 let _ = client.delete(&path, None).await?;
192
193 Ok(Value::Null)
194 }
195
196 fn task_mgmt_cli() -> CommandLineInterface {
197
198 let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG)
199 .arg_param(&["upid"]);
200
201 let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP)
202 .arg_param(&["upid"]);
203
204 let cmd_def = CliCommandMap::new()
205 .insert("list", CliCommand::new(&API_METHOD_TASK_LIST))
206 .insert("log", task_log_cmd_def)
207 .insert("stop", task_stop_cmd_def);
208
209 cmd_def.into()
210 }
211
212 // fixme: avoid API redefinition
213 #[api(
214 input: {
215 properties: {
216 "local-store": {
217 schema: DATASTORE_SCHEMA,
218 },
219 remote: {
220 schema: REMOTE_ID_SCHEMA,
221 },
222 "remote-store": {
223 schema: DATASTORE_SCHEMA,
224 },
225 "remove-vanished": {
226 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
227 optional: true,
228 },
229 "output-format": {
230 schema: OUTPUT_FORMAT,
231 optional: true,
232 },
233 }
234 }
235 )]
236 /// Sync datastore from another repository
237 async fn pull_datastore(
238 remote: String,
239 remote_store: String,
240 local_store: String,
241 remove_vanished: Option<bool>,
242 param: Value,
243 ) -> Result<Value, Error> {
244
245 let output_format = get_output_format(&param);
246
247 let mut client = connect_to_localhost()?;
248
249 let mut args = json!({
250 "store": local_store,
251 "remote": remote,
252 "remote-store": remote_store,
253 });
254
255 if let Some(remove_vanished) = remove_vanished {
256 args["remove-vanished"] = Value::from(remove_vanished);
257 }
258
259 let result = client.post("api2/json/pull", Some(args)).await?;
260
261 view_task_result(&mut client, result, &output_format).await?;
262
263 Ok(Value::Null)
264 }
265
266 #[api(
267 input: {
268 properties: {
269 "store": {
270 schema: DATASTORE_SCHEMA,
271 },
272 "ignore-verified": {
273 schema: IGNORE_VERIFIED_BACKUPS_SCHEMA,
274 optional: true,
275 },
276 "outdated-after": {
277 schema: VERIFICATION_OUTDATED_AFTER_SCHEMA,
278 optional: true,
279 },
280 "output-format": {
281 schema: OUTPUT_FORMAT,
282 optional: true,
283 },
284 }
285 }
286 )]
287 /// Verify backups
288 async fn verify(
289 store: String,
290 mut param: Value,
291 ) -> Result<Value, Error> {
292
293 let output_format = extract_output_format(&mut param);
294
295 let mut client = connect_to_localhost()?;
296
297 let args = json!(param);
298
299 let path = format!("api2/json/admin/datastore/{}/verify", store);
300
301 let result = client.post(&path, Some(args)).await?;
302
303 view_task_result(&mut client, result, &output_format).await?;
304
305 Ok(Value::Null)
306 }
307
308 #[api()]
309 /// System report
310 async fn report() -> Result<Value, Error> {
311 let report = proxmox_backup::server::generate_report();
312 io::stdout().write_all(report.as_bytes())?;
313 Ok(Value::Null)
314 }
315
316 #[api(
317 input: {
318 properties: {
319 verbose: {
320 type: Boolean,
321 optional: true,
322 default: false,
323 description: "Output verbose package information. It is ignored if output-format is specified.",
324 },
325 "output-format": {
326 schema: OUTPUT_FORMAT,
327 optional: true,
328 }
329 }
330 }
331 )]
332 /// List package versions for important Proxmox Backup Server packages.
333 async fn get_versions(verbose: bool, param: Value) -> Result<Value, Error> {
334 let output_format = get_output_format(&param);
335
336 let packages = crate::api2::node::apt::get_versions()?;
337 let mut packages = json!(if verbose { &packages[..] } else { &packages[1..2] });
338
339 let options = default_table_format_options()
340 .disable_sort()
341 .noborder(true) // just not helpful for version info which gets copy pasted often
342 .column(ColumnConfig::new("Package"))
343 .column(ColumnConfig::new("Version"))
344 .column(ColumnConfig::new("ExtraInfo").header("Extra Info"))
345 ;
346 let return_type = &crate::api2::node::apt::API_METHOD_GET_VERSIONS.returns;
347
348 format_and_print_result_full(&mut packages, return_type, &output_format, &options);
349
350 Ok(Value::Null)
351 }
352
353 fn main() {
354
355 proxmox_backup::tools::setup_safe_path_env();
356
357 let cmd_def = CliCommandMap::new()
358 .insert("acl", acl_commands())
359 .insert("datastore", datastore_commands())
360 .insert("disk", disk_commands())
361 .insert("dns", dns_commands())
362 .insert("network", network_commands())
363 .insert("node", node_commands())
364 .insert("user", user_commands())
365 .insert("openid", openid_commands())
366 .insert("remote", remote_commands())
367 .insert("garbage-collection", garbage_collection_commands())
368 .insert("acme", acme_mgmt_cli())
369 .insert("cert", cert_mgmt_cli())
370 .insert("subscription", subscription_commands())
371 .insert("sync-job", sync_job_commands())
372 .insert("verify-job", verify_job_commands())
373 .insert("task", task_mgmt_cli())
374 .insert(
375 "pull",
376 CliCommand::new(&API_METHOD_PULL_DATASTORE)
377 .arg_param(&["remote", "remote-store", "local-store"])
378 .completion_cb("local-store", config::datastore::complete_datastore_name)
379 .completion_cb("remote", config::remote::complete_remote_name)
380 .completion_cb("remote-store", complete_remote_datastore_name)
381 )
382 .insert(
383 "verify",
384 CliCommand::new(&API_METHOD_VERIFY)
385 .arg_param(&["store"])
386 .completion_cb("store", config::datastore::complete_datastore_name)
387 )
388 .insert("report",
389 CliCommand::new(&API_METHOD_REPORT)
390 )
391 .insert("versions",
392 CliCommand::new(&API_METHOD_GET_VERSIONS)
393 );
394
395
396
397 let mut rpcenv = CliEnvironment::new();
398 rpcenv.set_auth_id(Some(String::from("root@pam")));
399
400 pbs_runtime::main(run_async_cli_command(cmd_def, rpcenv));
401 }
402
403 // shell completion helper
404 pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
405
406 let mut list = Vec::new();
407
408 let _ = proxmox::try_block!({
409 let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
410
411 let data = pbs_runtime::block_on(async move {
412 crate::api2::config::remote::scan_remote_datastores(remote.clone()).await
413 })?;
414
415 for item in data {
416 list.push(item.store);
417 }
418
419 Ok(())
420 }).map_err(|_err: Error| { /* ignore */ });
421
422 list
423 }