1 use std
::collections
::HashMap
;
2 use std
::io
::{self, Write}
;
4 use anyhow
::{format_err, Error}
;
5 use serde_json
::{json, Value}
;
7 use proxmox
::tools
::fs
::CreateOptions
;
8 use proxmox_router
::{cli::*, RpcEnvironment}
;
9 use proxmox_schema
::api
;
11 use pbs_client
::{display_task_log, view_task_result}
;
12 use pbs_tools
::percent_encoding
::percent_encode_component
;
13 use pbs_tools
::json
::required_string_param
;
16 DATASTORE_SCHEMA
, GROUP_FILTER_LIST_SCHEMA
, IGNORE_VERIFIED_BACKUPS_SCHEMA
, REMOTE_ID_SCHEMA
,
17 REMOVE_VANISHED_BACKUPS_SCHEMA
, UPID_SCHEMA
, VERIFICATION_OUTDATED_AFTER_SCHEMA
,
20 use proxmox_rest_server
::wait_for_local_worker
;
22 use proxmox_backup
::api2
;
23 use proxmox_backup
::client_helpers
::connect_to_localhost
;
24 use proxmox_backup
::config
;
26 mod proxmox_backup_manager
;
27 use proxmox_backup_manager
::*;
33 schema
: DATASTORE_SCHEMA
,
36 schema
: OUTPUT_FORMAT
,
42 /// Start garbage collection for a specific datastore.
43 async
fn start_garbage_collection(param
: Value
) -> Result
<Value
, Error
> {
45 let output_format
= get_output_format(¶m
);
47 let store
= required_string_param(¶m
, "store")?
;
49 let mut client
= connect_to_localhost()?
;
51 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
53 let result
= client
.post(&path
, None
).await?
;
55 view_task_result(&mut client
, result
, &output_format
).await?
;
64 schema
: DATASTORE_SCHEMA
,
67 schema
: OUTPUT_FORMAT
,
73 /// Show garbage collection status for a specific datastore.
74 async
fn garbage_collection_status(param
: Value
) -> Result
<Value
, Error
> {
76 let output_format
= get_output_format(¶m
);
78 let store
= required_string_param(¶m
, "store")?
;
80 let client
= connect_to_localhost()?
;
82 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
84 let mut result
= client
.get(&path
, None
).await?
;
85 let mut data
= result
["data"].take();
86 let return_type
= &api2
::admin
::datastore
::API_METHOD_GARBAGE_COLLECTION_STATUS
.returns
;
88 let options
= default_table_format_options();
90 format_and_print_result_full(&mut data
, return_type
, &output_format
, &options
);
95 fn garbage_collection_commands() -> CommandLineInterface
{
97 let cmd_def
= CliCommandMap
::new()
99 CliCommand
::new(&API_METHOD_GARBAGE_COLLECTION_STATUS
)
100 .arg_param(&["store"])
101 .completion_cb("store", pbs_config
::datastore
::complete_datastore_name
)
104 CliCommand
::new(&API_METHOD_START_GARBAGE_COLLECTION
)
105 .arg_param(&["store"])
106 .completion_cb("store", pbs_config
::datastore
::complete_datastore_name
)
116 description
: "The maximal number of tasks to list.",
124 schema
: OUTPUT_FORMAT
,
129 description
: "Also list stopped tasks.",
135 /// List running server tasks.
136 async
fn task_list(param
: Value
) -> Result
<Value
, Error
> {
138 let output_format
= get_output_format(¶m
);
140 let client
= connect_to_localhost()?
;
142 let limit
= param
["limit"].as_u64().unwrap_or(50) as usize;
143 let running
= !param
["all"].as_bool().unwrap_or(false);
149 let mut result
= client
.get("api2/json/nodes/localhost/tasks", Some(args
)).await?
;
151 let mut data
= result
["data"].take();
152 let return_type
= &api2
::node
::tasks
::API_METHOD_LIST_TASKS
.returns
;
154 use pbs_tools
::format
::{render_epoch, render_task_status}
;
155 let options
= default_table_format_options()
156 .column(ColumnConfig
::new("starttime").right_align(false).renderer(render_epoch
))
157 .column(ColumnConfig
::new("endtime").right_align(false).renderer(render_epoch
))
158 .column(ColumnConfig
::new("upid"))
159 .column(ColumnConfig
::new("status").renderer(render_task_status
));
161 format_and_print_result_full(&mut data
, return_type
, &output_format
, &options
);
175 /// Display the task log.
176 async
fn task_log(param
: Value
) -> Result
<Value
, Error
> {
178 let upid
= required_string_param(¶m
, "upid")?
;
180 let mut client
= connect_to_localhost()?
;
182 display_task_log(&mut client
, upid
, true).await?
;
196 /// Try to stop a specific task.
197 async
fn task_stop(param
: Value
) -> Result
<Value
, Error
> {
199 let upid_str
= required_string_param(¶m
, "upid")?
;
201 let mut client
= connect_to_localhost()?
;
203 let path
= format
!("api2/json/nodes/localhost/tasks/{}", percent_encode_component(upid_str
));
204 let _
= client
.delete(&path
, None
).await?
;
209 fn task_mgmt_cli() -> CommandLineInterface
{
211 let task_log_cmd_def
= CliCommand
::new(&API_METHOD_TASK_LOG
)
212 .arg_param(&["upid"]);
214 let task_stop_cmd_def
= CliCommand
::new(&API_METHOD_TASK_STOP
)
215 .arg_param(&["upid"]);
217 let cmd_def
= CliCommandMap
::new()
218 .insert("list", CliCommand
::new(&API_METHOD_TASK_LIST
))
219 .insert("log", task_log_cmd_def
)
220 .insert("stop", task_stop_cmd_def
);
225 // fixme: avoid API redefinition
230 schema
: DATASTORE_SCHEMA
,
233 schema
: REMOTE_ID_SCHEMA
,
236 schema
: DATASTORE_SCHEMA
,
239 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
243 schema
: GROUP_FILTER_LIST_SCHEMA
,
247 schema
: OUTPUT_FORMAT
,
253 /// Sync datastore from another repository
254 async
fn pull_datastore(
256 remote_store
: String
,
258 remove_vanished
: Option
<bool
>,
259 groups
: Option
<Vec
<GroupFilter
>>,
261 ) -> Result
<Value
, Error
> {
263 let output_format
= get_output_format(¶m
);
265 let mut client
= connect_to_localhost()?
;
267 let mut args
= json
!({
268 "store": local_store
,
270 "remote-store": remote_store
,
273 if groups
.is_some() {
274 args
["groups"] = json
!(groups
);
277 if let Some(remove_vanished
) = remove_vanished
{
278 args
["remove-vanished"] = Value
::from(remove_vanished
);
281 let result
= client
.post("api2/json/pull", Some(args
)).await?
;
283 view_task_result(&mut client
, result
, &output_format
).await?
;
292 schema
: DATASTORE_SCHEMA
,
295 schema
: IGNORE_VERIFIED_BACKUPS_SCHEMA
,
299 schema
: VERIFICATION_OUTDATED_AFTER_SCHEMA
,
303 schema
: OUTPUT_FORMAT
,
313 ) -> Result
<Value
, Error
> {
315 let output_format
= extract_output_format(&mut param
);
317 let mut client
= connect_to_localhost()?
;
319 let args
= json
!(param
);
321 let path
= format
!("api2/json/admin/datastore/{}/verify", store
);
323 let result
= client
.post(&path
, Some(args
)).await?
;
325 view_task_result(&mut client
, result
, &output_format
).await?
;
332 async
fn report() -> Result
<Value
, Error
> {
333 let report
= proxmox_backup
::server
::generate_report();
334 io
::stdout().write_all(report
.as_bytes())?
;
345 description
: "Output verbose package information. It is ignored if output-format is specified.",
348 schema
: OUTPUT_FORMAT
,
354 /// List package versions for important Proxmox Backup Server packages.
355 async
fn get_versions(verbose
: bool
, param
: Value
) -> Result
<Value
, Error
> {
356 let output_format
= get_output_format(¶m
);
358 let packages
= crate::api2
::node
::apt
::get_versions()?
;
359 let mut packages
= json
!(if verbose { &packages[..] }
else { &packages[1..2] }
);
361 let options
= default_table_format_options()
363 .noborder(true) // just not helpful for version info which gets copy pasted often
364 .column(ColumnConfig
::new("Package"))
365 .column(ColumnConfig
::new("Version"))
366 .column(ColumnConfig
::new("ExtraInfo").header("Extra Info"))
368 let return_type
= &crate::api2
::node
::apt
::API_METHOD_GET_VERSIONS
.returns
;
370 format_and_print_result_full(&mut packages
, return_type
, &output_format
, &options
);
375 async
fn run() -> Result
<(), Error
> {
377 let cmd_def
= CliCommandMap
::new()
378 .insert("acl", acl_commands())
379 .insert("datastore", datastore_commands())
380 .insert("disk", disk_commands())
381 .insert("dns", dns_commands())
382 .insert("network", network_commands())
383 .insert("node", node_commands())
384 .insert("user", user_commands())
385 .insert("openid", openid_commands())
386 .insert("remote", remote_commands())
387 .insert("traffic-control", traffic_control_commands())
388 .insert("garbage-collection", garbage_collection_commands())
389 .insert("acme", acme_mgmt_cli())
390 .insert("cert", cert_mgmt_cli())
391 .insert("subscription", subscription_commands())
392 .insert("sync-job", sync_job_commands())
393 .insert("verify-job", verify_job_commands())
394 .insert("task", task_mgmt_cli())
397 CliCommand
::new(&API_METHOD_PULL_DATASTORE
)
398 .arg_param(&["remote", "remote-store", "local-store"])
399 .completion_cb("local-store", pbs_config
::datastore
::complete_datastore_name
)
400 .completion_cb("remote", pbs_config
::remote
::complete_remote_name
)
401 .completion_cb("remote-store", complete_remote_datastore_name
)
405 CliCommand
::new(&API_METHOD_VERIFY
)
406 .arg_param(&["store"])
407 .completion_cb("store", pbs_config
::datastore
::complete_datastore_name
)
410 CliCommand
::new(&API_METHOD_REPORT
)
413 CliCommand
::new(&API_METHOD_GET_VERSIONS
)
416 let args
: Vec
<String
> = std
::env
::args().take(2).collect();
417 let avoid_init
= args
.len() >= 2 && (args
[1] == "bashcomplete" || args
[1] == "printdoc");
420 let backup_user
= pbs_config
::backup_user()?
;
421 let file_opts
= CreateOptions
::new().owner(backup_user
.uid
).group(backup_user
.gid
);
422 proxmox_rest_server
::init_worker_tasks(pbs_buildcfg
::PROXMOX_BACKUP_LOG_DIR_M
!().into(), file_opts
.clone())?
;
424 let mut commando_sock
= proxmox_rest_server
::CommandSocket
::new(proxmox_rest_server
::our_ctrl_sock(), backup_user
.gid
);
425 proxmox_rest_server
::register_task_control_commands(&mut commando_sock
)?
;
426 commando_sock
.spawn()?
;
429 let mut rpcenv
= CliEnvironment
::new();
430 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
432 run_async_cli_command(cmd_def
, rpcenv
).await
; // this call exit(-1) on error
437 fn main() -> Result
<(), Error
> {
439 proxmox_backup
::tools
::setup_safe_path_env();
441 pbs_runtime
::main(run())
444 // shell completion helper
445 pub fn complete_remote_datastore_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
447 let mut list
= Vec
::new();
449 let _
= proxmox_lang
::try_block
!({
450 let remote
= param
.get("remote").ok_or_else(|| format_err
!("no remote"))?
;
452 let data
= pbs_runtime
::block_on(async
move {
453 crate::api2
::config
::remote
::scan_remote_datastores(remote
.clone()).await
457 list
.push(item
.store
);
461 }).map_err(|_err
: Error
| { /* ignore */ }
);