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