1 use std
::collections
::HashMap
;
2 use std
::io
::{self, Write}
;
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}
;
13 use pbs_tools
::percent_encoding
::percent_encode_component
;
14 use pbs_tools
::json
::required_string_param
;
16 GroupFilter
, SyncJobConfig
,
17 DATASTORE_SCHEMA
, GROUP_FILTER_LIST_SCHEMA
, IGNORE_VERIFIED_BACKUPS_SCHEMA
, REMOTE_ID_SCHEMA
,
18 REMOVE_VANISHED_BACKUPS_SCHEMA
, UPID_SCHEMA
, VERIFICATION_OUTDATED_AFTER_SCHEMA
,
21 use proxmox_rest_server
::wait_for_local_worker
;
23 use proxmox_backup
::api2
;
24 use proxmox_backup
::client_helpers
::connect_to_localhost
;
25 use proxmox_backup
::config
;
27 mod proxmox_backup_manager
;
28 use proxmox_backup_manager
::*;
34 schema
: DATASTORE_SCHEMA
,
37 schema
: OUTPUT_FORMAT
,
43 /// Start garbage collection for a specific datastore.
44 async
fn start_garbage_collection(param
: Value
) -> Result
<Value
, Error
> {
46 let output_format
= get_output_format(¶m
);
48 let store
= required_string_param(¶m
, "store")?
;
50 let mut client
= connect_to_localhost()?
;
52 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
54 let result
= client
.post(&path
, None
).await?
;
56 view_task_result(&mut client
, result
, &output_format
).await?
;
65 schema
: DATASTORE_SCHEMA
,
68 schema
: OUTPUT_FORMAT
,
74 /// Show garbage collection status for a specific datastore.
75 async
fn garbage_collection_status(param
: Value
) -> Result
<Value
, Error
> {
77 let output_format
= get_output_format(¶m
);
79 let store
= required_string_param(¶m
, "store")?
;
81 let client
= connect_to_localhost()?
;
83 let path
= format
!("api2/json/admin/datastore/{}/gc", store
);
85 let mut result
= client
.get(&path
, None
).await?
;
86 let mut data
= result
["data"].take();
87 let return_type
= &api2
::admin
::datastore
::API_METHOD_GARBAGE_COLLECTION_STATUS
.returns
;
89 let options
= default_table_format_options();
91 format_and_print_result_full(&mut data
, return_type
, &output_format
, &options
);
96 fn garbage_collection_commands() -> CommandLineInterface
{
98 let cmd_def
= CliCommandMap
::new()
100 CliCommand
::new(&API_METHOD_GARBAGE_COLLECTION_STATUS
)
101 .arg_param(&["store"])
102 .completion_cb("store", pbs_config
::datastore
::complete_datastore_name
)
105 CliCommand
::new(&API_METHOD_START_GARBAGE_COLLECTION
)
106 .arg_param(&["store"])
107 .completion_cb("store", pbs_config
::datastore
::complete_datastore_name
)
117 description
: "The maximal number of tasks to list.",
125 schema
: OUTPUT_FORMAT
,
130 description
: "Also list stopped tasks.",
136 /// List running server tasks.
137 async
fn task_list(param
: Value
) -> Result
<Value
, Error
> {
139 let output_format
= get_output_format(¶m
);
141 let client
= connect_to_localhost()?
;
143 let limit
= param
["limit"].as_u64().unwrap_or(50) as usize;
144 let running
= !param
["all"].as_bool().unwrap_or(false);
150 let mut result
= client
.get("api2/json/nodes/localhost/tasks", Some(args
)).await?
;
152 let mut data
= result
["data"].take();
153 let return_type
= &api2
::node
::tasks
::API_METHOD_LIST_TASKS
.returns
;
155 use pbs_tools
::format
::{render_epoch, render_task_status}
;
156 let options
= default_table_format_options()
157 .column(ColumnConfig
::new("starttime").right_align(false).renderer(render_epoch
))
158 .column(ColumnConfig
::new("endtime").right_align(false).renderer(render_epoch
))
159 .column(ColumnConfig
::new("upid"))
160 .column(ColumnConfig
::new("status").renderer(render_task_status
));
162 format_and_print_result_full(&mut data
, return_type
, &output_format
, &options
);
176 /// Display the task log.
177 async
fn task_log(param
: Value
) -> Result
<Value
, Error
> {
179 let upid
= required_string_param(¶m
, "upid")?
;
181 let mut client
= connect_to_localhost()?
;
183 display_task_log(&mut client
, upid
, true).await?
;
197 /// Try to stop a specific task.
198 async
fn task_stop(param
: Value
) -> Result
<Value
, Error
> {
200 let upid_str
= required_string_param(¶m
, "upid")?
;
202 let mut client
= connect_to_localhost()?
;
204 let path
= format
!("api2/json/nodes/localhost/tasks/{}", percent_encode_component(upid_str
));
205 let _
= client
.delete(&path
, None
).await?
;
210 fn task_mgmt_cli() -> CommandLineInterface
{
212 let task_log_cmd_def
= CliCommand
::new(&API_METHOD_TASK_LOG
)
213 .arg_param(&["upid"]);
215 let task_stop_cmd_def
= CliCommand
::new(&API_METHOD_TASK_STOP
)
216 .arg_param(&["upid"]);
218 let cmd_def
= CliCommandMap
::new()
219 .insert("list", CliCommand
::new(&API_METHOD_TASK_LIST
))
220 .insert("log", task_log_cmd_def
)
221 .insert("stop", task_stop_cmd_def
);
226 // fixme: avoid API redefinition
231 schema
: DATASTORE_SCHEMA
,
234 schema
: REMOTE_ID_SCHEMA
,
237 schema
: DATASTORE_SCHEMA
,
240 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
244 schema
: GROUP_FILTER_LIST_SCHEMA
,
248 schema
: OUTPUT_FORMAT
,
254 /// Sync datastore from another repository
255 async
fn pull_datastore(
257 remote_store
: String
,
259 remove_vanished
: Option
<bool
>,
260 group_filter
: Option
<Vec
<GroupFilter
>>,
262 ) -> Result
<Value
, Error
> {
264 let output_format
= get_output_format(¶m
);
266 let mut client
= connect_to_localhost()?
;
268 let mut args
= json
!({
269 "store": local_store
,
271 "remote-store": remote_store
,
274 if group_filter
.is_some() {
275 args
["group-filter"] = json
!(group_filter
);
278 if let Some(remove_vanished
) = remove_vanished
{
279 args
["remove-vanished"] = Value
::from(remove_vanished
);
282 let result
= client
.post("api2/json/pull", Some(args
)).await?
;
284 view_task_result(&mut client
, result
, &output_format
).await?
;
293 schema
: DATASTORE_SCHEMA
,
296 schema
: IGNORE_VERIFIED_BACKUPS_SCHEMA
,
300 schema
: VERIFICATION_OUTDATED_AFTER_SCHEMA
,
304 schema
: OUTPUT_FORMAT
,
314 ) -> Result
<Value
, Error
> {
316 let output_format
= extract_output_format(&mut param
);
318 let mut client
= connect_to_localhost()?
;
320 let args
= json
!(param
);
322 let path
= format
!("api2/json/admin/datastore/{}/verify", store
);
324 let result
= client
.post(&path
, Some(args
)).await?
;
326 view_task_result(&mut client
, result
, &output_format
).await?
;
333 async
fn report() -> Result
<Value
, Error
> {
334 let report
= proxmox_backup
::server
::generate_report();
335 io
::stdout().write_all(report
.as_bytes())?
;
346 description
: "Output verbose package information. It is ignored if output-format is specified.",
349 schema
: OUTPUT_FORMAT
,
355 /// List package versions for important Proxmox Backup Server packages.
356 async
fn get_versions(verbose
: bool
, param
: Value
) -> Result
<Value
, Error
> {
357 let output_format
= get_output_format(¶m
);
359 let packages
= crate::api2
::node
::apt
::get_versions()?
;
360 let mut packages
= json
!(if verbose { &packages[..] }
else { &packages[1..2] }
);
362 let options
= default_table_format_options()
364 .noborder(true) // just not helpful for version info which gets copy pasted often
365 .column(ColumnConfig
::new("Package"))
366 .column(ColumnConfig
::new("Version"))
367 .column(ColumnConfig
::new("ExtraInfo").header("Extra Info"))
369 let return_type
= &crate::api2
::node
::apt
::API_METHOD_GET_VERSIONS
.returns
;
371 format_and_print_result_full(&mut packages
, return_type
, &output_format
, &options
);
376 async
fn run() -> Result
<(), Error
> {
378 let cmd_def
= CliCommandMap
::new()
379 .insert("acl", acl_commands())
380 .insert("datastore", datastore_commands())
381 .insert("disk", disk_commands())
382 .insert("dns", dns_commands())
383 .insert("network", network_commands())
384 .insert("node", node_commands())
385 .insert("user", user_commands())
386 .insert("openid", openid_commands())
387 .insert("remote", remote_commands())
388 .insert("traffic-control", traffic_control_commands())
389 .insert("garbage-collection", garbage_collection_commands())
390 .insert("acme", acme_mgmt_cli())
391 .insert("cert", cert_mgmt_cli())
392 .insert("subscription", subscription_commands())
393 .insert("sync-job", sync_job_commands())
394 .insert("verify-job", verify_job_commands())
395 .insert("task", task_mgmt_cli())
398 CliCommand
::new(&API_METHOD_PULL_DATASTORE
)
399 .arg_param(&["remote", "remote-store", "local-store"])
400 .completion_cb("local-store", pbs_config
::datastore
::complete_datastore_name
)
401 .completion_cb("remote", pbs_config
::remote
::complete_remote_name
)
402 .completion_cb("remote-store", complete_remote_datastore_name
)
403 .completion_cb("group_filter", complete_remote_datastore_group_filter
)
407 CliCommand
::new(&API_METHOD_VERIFY
)
408 .arg_param(&["store"])
409 .completion_cb("store", pbs_config
::datastore
::complete_datastore_name
)
412 CliCommand
::new(&API_METHOD_REPORT
)
415 CliCommand
::new(&API_METHOD_GET_VERSIONS
)
418 let args
: Vec
<String
> = std
::env
::args().take(2).collect();
419 let avoid_init
= args
.len() >= 2 && (args
[1] == "bashcomplete" || args
[1] == "printdoc");
422 let backup_user
= pbs_config
::backup_user()?
;
423 let file_opts
= CreateOptions
::new().owner(backup_user
.uid
).group(backup_user
.gid
);
424 proxmox_rest_server
::init_worker_tasks(pbs_buildcfg
::PROXMOX_BACKUP_LOG_DIR_M
!().into(), file_opts
.clone())?
;
426 let mut commando_sock
= proxmox_rest_server
::CommandSocket
::new(proxmox_rest_server
::our_ctrl_sock(), backup_user
.gid
);
427 proxmox_rest_server
::register_task_control_commands(&mut commando_sock
)?
;
428 commando_sock
.spawn()?
;
431 let mut rpcenv
= CliEnvironment
::new();
432 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
434 run_async_cli_command(cmd_def
, rpcenv
).await
; // this call exit(-1) on error
439 fn main() -> Result
<(), Error
> {
441 proxmox_backup
::tools
::setup_safe_path_env();
443 pbs_runtime
::main(run())
446 fn get_sync_job(id
: &String
) -> Result
<SyncJobConfig
, Error
> {
447 let (config
, _digest
) = sync
::config()?
;
449 config
.lookup("sync", id
)
452 fn get_remote(param
: &HashMap
<String
, String
>) -> Option
<String
> {
455 .map(|r
| r
.to_owned())
457 if let Some(id
) = param
.get("id") {
458 if let Ok(job
) = get_sync_job(id
) {
459 return Some(job
.remote
.clone());
466 fn get_remote_store(param
: &HashMap
<String
, String
>) -> Option
<(String
, String
)> {
467 let mut job
: Option
<SyncJobConfig
> = None
;
471 .map(|r
| r
.to_owned())
473 if let Some(id
) = param
.get("id") {
474 job
= get_sync_job(id
).ok();
475 if let Some(ref job
) = job
{
476 return Some(job
.remote
.clone());
482 if let Some(remote
) = remote
{
485 .map(|r
| r
.to_owned())
486 .or_else(|| job
.map(|job
| job
.remote_store
.clone()));
488 if let Some(store
) = store
{
489 return Some((remote
, store
))
496 // shell completion helper
497 pub fn complete_remote_datastore_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
499 let mut list
= Vec
::new();
501 if let Some(remote
) = get_remote(param
) {
502 if let Ok(data
) = pbs_runtime
::block_on(async
move {
503 crate::api2
::config
::remote
::scan_remote_datastores(remote
).await
507 list
.push(item
.store
);
515 // shell completion helper
516 pub fn complete_remote_datastore_group(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
518 let mut list
= Vec
::new();
520 if let Some((remote
, remote_store
)) = get_remote_store(param
) {
521 if let Ok(data
) = pbs_runtime
::block_on(async
move {
522 crate::api2
::config
::remote
::scan_remote_groups(remote
.clone(), remote_store
.clone()).await
526 list
.push(format
!("{}/{}", item
.backup_type
, item
.backup_id
));
534 // shell completion helper
535 pub fn complete_remote_datastore_group_filter(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
537 let mut list
= Vec
::new();
539 list
.push("regex:".to_string());
540 list
.push("type:ct".to_string());
541 list
.push("type:host".to_string());
542 list
.push("type:vm".to_string());
544 list
.extend(complete_remote_datastore_group(_arg
, param
).iter().map(|group
| format
!("group:{}", group
)));