]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-backup-manager.rs
ui: tell ESLint to be strict in check target
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
CommitLineData
331b869d 1use std::collections::HashMap;
941342f7 2use std::io::{self, Write};
ea0b8b6e 3
b29d046e 4use anyhow::{format_err, Error};
9894469e 5use serde_json::{json, Value};
9894469e 6
380bd7df 7use proxmox::api::{api, cli::*, RpcEnvironment};
769f8c99
DM
8
9use proxmox_backup::tools;
a220a456 10use proxmox_backup::config;
9894469e 11use proxmox_backup::api2::{self, types::* };
769f8c99 12use proxmox_backup::client::*;
72dc6832 13use proxmox_backup::tools::ticket::Ticket;
769f8c99
DM
14use proxmox_backup::auth_helpers::*;
15
a220a456
DM
16mod proxmox_backup_manager;
17use proxmox_backup_manager::*;
18
769f8c99
DM
19async fn view_task_result(
20 client: HttpClient,
21 result: Value,
22 output_format: &str,
23) -> Result<(), Error> {
24 let data = &result["data"];
25 if output_format == "text" {
26 if let Some(upid) = data.as_str() {
27 display_task_log(client, upid, true).await?;
28 }
29 } else {
30 format_and_print_result(&data, &output_format);
31 }
32
33 Ok(())
34}
211fabd7 35
dfb31de8 36// Note: local workers should print logs to stdout, so there is no need
707974fd
DM
37// to fetch/display logs. We just wait for the worker to finish.
38pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
39
40 let upid: proxmox_backup::server::UPID = upid_str.parse()?;
41
42 let sleep_duration = core::time::Duration::new(0, 100_000_000);
43
44 loop {
45 if proxmox_backup::server::worker_is_active_local(&upid) {
46 tokio::time::delay_for(sleep_duration).await;
47 } else {
48 break;
49 }
50 }
51 Ok(())
52}
53
47d47121
DM
54fn connect() -> Result<HttpClient, Error> {
55
56 let uid = nix::unistd::Uid::current();
57
d59dbeca 58 let mut options = HttpClientOptions::new()
5030b7ce 59 .prefix(Some("proxmox-backup".to_string()))
d59dbeca
DM
60 .verify_cert(false); // not required for connection to localhost
61
47d47121 62 let client = if uid.is_root() {
72dc6832
WB
63 let ticket = Ticket::new("PBS", Userid::root_userid())?
64 .sign(private_auth_key(), None)?;
d59dbeca 65 options = options.password(Some(ticket));
34aa8e13 66 HttpClient::new("localhost", 8007, Authid::root_auth_id(), options)?
47d47121 67 } else {
d59dbeca 68 options = options.ticket_cache(true).interactive(true);
34aa8e13 69 HttpClient::new("localhost", 8007, Authid::root_auth_id(), options)?
47d47121
DM
70 };
71
72 Ok(client)
73}
74
769f8c99
DM
75#[api(
76 input: {
77 properties: {
78 store: {
79 schema: DATASTORE_SCHEMA,
80 },
81 "output-format": {
82 schema: OUTPUT_FORMAT,
83 optional: true,
84 },
85 }
86 }
87)]
88/// Start garbage collection for a specific datastore.
89async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
691c89a0 90
ac3faaf5 91 let output_format = get_output_format(&param);
691c89a0 92
769f8c99
DM
93 let store = tools::required_string_param(&param, "store")?;
94
47d47121 95 let mut client = connect()?;
769f8c99
DM
96
97 let path = format!("api2/json/admin/datastore/{}/gc", store);
98
99 let result = client.post(&path, None).await?;
100
101 view_task_result(client, result, &output_format).await?;
102
103 Ok(Value::Null)
104}
105
106#[api(
107 input: {
108 properties: {
109 store: {
110 schema: DATASTORE_SCHEMA,
111 },
112 "output-format": {
113 schema: OUTPUT_FORMAT,
114 optional: true,
115 },
116 }
117 }
118)]
119/// Show garbage collection status for a specific datastore.
120async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
121
ac3faaf5 122 let output_format = get_output_format(&param);
769f8c99
DM
123
124 let store = tools::required_string_param(&param, "store")?;
125
47d47121 126 let client = connect()?;
769f8c99
DM
127
128 let path = format!("api2/json/admin/datastore/{}/gc", store);
129
9894469e
DM
130 let mut result = client.get(&path, None).await?;
131 let mut data = result["data"].take();
660a3489 132 let schema = &api2::admin::datastore::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS;
9894469e 133
ac3faaf5 134 let options = default_table_format_options();
9894469e
DM
135
136 format_and_print_result_full(&mut data, schema, &output_format, &options);
769f8c99
DM
137
138 Ok(Value::Null)
139}
140
141fn garbage_collection_commands() -> CommandLineInterface {
691c89a0
DM
142
143 let cmd_def = CliCommandMap::new()
144 .insert("status",
769f8c99 145 CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS)
49fddd98 146 .arg_param(&["store"])
9ac1045c 147 .completion_cb("store", config::datastore::complete_datastore_name)
48ef3c33 148 )
691c89a0 149 .insert("start",
769f8c99 150 CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION)
49fddd98 151 .arg_param(&["store"])
9ac1045c 152 .completion_cb("store", config::datastore::complete_datastore_name)
48ef3c33 153 );
691c89a0
DM
154
155 cmd_def.into()
156}
157
47d47121
DM
158#[api(
159 input: {
160 properties: {
161 limit: {
162 description: "The maximal number of tasks to list.",
163 type: Integer,
164 optional: true,
165 minimum: 1,
166 maximum: 1000,
167 default: 50,
168 },
169 "output-format": {
170 schema: OUTPUT_FORMAT,
171 optional: true,
172 },
173 all: {
174 type: Boolean,
175 description: "Also list stopped tasks.",
176 optional: true,
177 }
178 }
179 }
180)]
181/// List running server tasks.
182async fn task_list(param: Value) -> Result<Value, Error> {
183
ac3faaf5 184 let output_format = get_output_format(&param);
47d47121
DM
185
186 let client = connect()?;
187
188 let limit = param["limit"].as_u64().unwrap_or(50) as usize;
189 let running = !param["all"].as_bool().unwrap_or(false);
190 let args = json!({
191 "running": running,
192 "start": 0,
193 "limit": limit,
194 });
9894469e 195 let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
47d47121 196
9894469e 197 let mut data = result["data"].take();
660a3489 198 let schema = &api2::node::tasks::API_RETURN_SCHEMA_LIST_TASKS;
47d47121 199
ac3faaf5 200 let options = default_table_format_options()
4939255f
DM
201 .column(ColumnConfig::new("starttime").right_align(false).renderer(tools::format::render_epoch))
202 .column(ColumnConfig::new("endtime").right_align(false).renderer(tools::format::render_epoch))
93fbb4ef 203 .column(ColumnConfig::new("upid"))
4939255f 204 .column(ColumnConfig::new("status").renderer(tools::format::render_task_status));
9894469e
DM
205
206 format_and_print_result_full(&mut data, schema, &output_format, &options);
47d47121
DM
207
208 Ok(Value::Null)
209}
210
211#[api(
212 input: {
213 properties: {
214 upid: {
215 schema: UPID_SCHEMA,
216 },
217 }
218 }
219)]
220/// Display the task log.
221async fn task_log(param: Value) -> Result<Value, Error> {
222
223 let upid = tools::required_string_param(&param, "upid")?;
224
225 let client = connect()?;
226
227 display_task_log(client, upid, true).await?;
228
229 Ok(Value::Null)
230}
231
232#[api(
233 input: {
234 properties: {
235 upid: {
236 schema: UPID_SCHEMA,
237 },
238 }
239 }
240)]
241/// Try to stop a specific task.
242async fn task_stop(param: Value) -> Result<Value, Error> {
243
244 let upid_str = tools::required_string_param(&param, "upid")?;
245
246 let mut client = connect()?;
247
248 let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str);
249 let _ = client.delete(&path, None).await?;
250
251 Ok(Value::Null)
252}
253
254fn task_mgmt_cli() -> CommandLineInterface {
255
256 let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG)
257 .arg_param(&["upid"]);
258
259 let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP)
260 .arg_param(&["upid"]);
261
262 let cmd_def = CliCommandMap::new()
263 .insert("list", CliCommand::new(&API_METHOD_TASK_LIST))
264 .insert("log", task_log_cmd_def)
265 .insert("stop", task_stop_cmd_def);
266
267 cmd_def.into()
268}
269
4b4eba0b 270// fixme: avoid API redefinition
0eb0e024
DM
271#[api(
272 input: {
273 properties: {
eb506c83 274 "local-store": {
0eb0e024
DM
275 schema: DATASTORE_SCHEMA,
276 },
277 remote: {
167971ed 278 schema: REMOTE_ID_SCHEMA,
0eb0e024
DM
279 },
280 "remote-store": {
281 schema: DATASTORE_SCHEMA,
282 },
40dc1031
DC
283 "remove-vanished": {
284 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
4b4eba0b 285 optional: true,
4b4eba0b 286 },
0eb0e024
DM
287 "output-format": {
288 schema: OUTPUT_FORMAT,
289 optional: true,
290 },
291 }
292 }
293)]
eb506c83
DM
294/// Sync datastore from another repository
295async fn pull_datastore(
0eb0e024
DM
296 remote: String,
297 remote_store: String,
eb506c83 298 local_store: String,
40dc1031 299 remove_vanished: Option<bool>,
ac3faaf5 300 param: Value,
0eb0e024
DM
301) -> Result<Value, Error> {
302
ac3faaf5 303 let output_format = get_output_format(&param);
0eb0e024
DM
304
305 let mut client = connect()?;
306
8c877436 307 let mut args = json!({
eb506c83 308 "store": local_store,
94609e23 309 "remote": remote,
0eb0e024 310 "remote-store": remote_store,
0eb0e024
DM
311 });
312
8c877436
DC
313 if let Some(remove_vanished) = remove_vanished {
314 args["remove-vanished"] = Value::from(remove_vanished);
315 }
316
eb506c83 317 let result = client.post("api2/json/pull", Some(args)).await?;
0eb0e024
DM
318
319 view_task_result(client, result, &output_format).await?;
320
321 Ok(Value::Null)
322}
323
355c055e
DM
324#[api(
325 input: {
326 properties: {
327 "store": {
328 schema: DATASTORE_SCHEMA,
329 },
330 "output-format": {
331 schema: OUTPUT_FORMAT,
332 optional: true,
333 },
334 }
335 }
336)]
337/// Verify backups
338async fn verify(
339 store: String,
340 param: Value,
341) -> Result<Value, Error> {
342
343 let output_format = get_output_format(&param);
344
345 let mut client = connect()?;
346
347 let args = json!({});
348
349 let path = format!("api2/json/admin/datastore/{}/verify", store);
350
351 let result = client.post(&path, Some(args)).await?;
352
353 view_task_result(client, result, &output_format).await?;
354
355 Ok(Value::Null)
356}
357
9a556c8a
HL
358#[api()]
359/// System report
360async fn report() -> Result<Value, Error> {
941342f7
TL
361 let report = proxmox_backup::server::generate_report();
362 io::stdout().write_all(report.as_bytes())?;
9a556c8a
HL
363 Ok(Value::Null)
364}
365
c100fe91
ML
366#[api(
367 input: {
368 properties: {
369 verbose: {
370 type: Boolean,
371 optional: true,
372 default: false,
373 description: "Output verbose package information. It is ignored if output-format is specified.",
374 },
375 "output-format": {
376 schema: OUTPUT_FORMAT,
377 optional: true,
378 }
379 }
380 }
381)]
382/// List package versions for important Proxmox Backup Server packages.
383async fn get_versions(verbose: bool, param: Value) -> Result<Value, Error> {
294466ee 384 let output_format = get_output_format(&param);
c100fe91 385
294466ee
TL
386 let mut packages = if verbose {
387 crate::api2::node::apt::get_versions()?
c100fe91 388 } else {
294466ee
TL
389 // TODO: slice first element out in a nicer way?
390 let packages = crate::api2::node::apt::get_versions()?;
391 let packages = packages.as_array().unwrap();
392 Value::Array(vec![packages[0].to_owned()])
393 };
c100fe91 394
294466ee
TL
395 let options = default_table_format_options()
396 .disable_sort()
397 .noborder(true) // just not helpfull for version info which gets copy pasted often
398 .column(ColumnConfig::new("Package"))
399 .column(ColumnConfig::new("Version"))
400 .column(ColumnConfig::new("ExtraInfo").header("Extra Info"))
401 ;
402 let schema = &crate::api2::node::apt::API_RETURN_SCHEMA_GET_VERSIONS;
403
404 format_and_print_result_full(&mut packages, schema, &output_format, &options);
c100fe91
ML
405
406 Ok(Value::Null)
407}
408
211fabd7
DM
409fn main() {
410
ac7513e3
DM
411 proxmox_backup::tools::setup_safe_path_env();
412
6460764d 413 let cmd_def = CliCommandMap::new()
ed3e60ae 414 .insert("acl", acl_commands())
48ef3c33 415 .insert("datastore", datastore_commands())
8e40aa63 416 .insert("disk", disk_commands())
14627d67 417 .insert("dns", dns_commands())
ca0e5347 418 .insert("network", network_commands())
579728c6 419 .insert("user", user_commands())
f357390c 420 .insert("remote", remote_commands())
47d47121 421 .insert("garbage-collection", garbage_collection_commands())
550e0d88 422 .insert("cert", cert_mgmt_cli())
2762481c 423 .insert("subscription", subscription_commands())
a3016d65 424 .insert("sync-job", sync_job_commands())
0eb0e024
DM
425 .insert("task", task_mgmt_cli())
426 .insert(
eb506c83
DM
427 "pull",
428 CliCommand::new(&API_METHOD_PULL_DATASTORE)
429 .arg_param(&["remote", "remote-store", "local-store"])
430 .completion_cb("local-store", config::datastore::complete_datastore_name)
f357390c 431 .completion_cb("remote", config::remote::complete_remote_name)
331b869d 432 .completion_cb("remote-store", complete_remote_datastore_name)
355c055e
DM
433 )
434 .insert(
435 "verify",
436 CliCommand::new(&API_METHOD_VERIFY)
437 .arg_param(&["store"])
438 .completion_cb("store", config::datastore::complete_datastore_name)
9a556c8a
HL
439 )
440 .insert("report",
441 CliCommand::new(&API_METHOD_REPORT)
c100fe91
ML
442 )
443 .insert("versions",
444 CliCommand::new(&API_METHOD_GET_VERSIONS)
0eb0e024 445 );
34d3ba52 446
355c055e
DM
447
448
7b22acd0 449 let mut rpcenv = CliEnvironment::new();
e6dc35ac 450 rpcenv.set_auth_id(Some(String::from("root@pam")));
525008f7 451
7b22acd0 452 proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
ea0b8b6e 453}
331b869d
DM
454
455// shell completion helper
456pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
457
458 let mut list = Vec::new();
459
9ea4bce4 460 let _ = proxmox::try_block!({
331b869d 461 let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
331b869d 462
e0100d61
FG
463 let data = crate::tools::runtime::block_on(async move {
464 crate::api2::config::remote::scan_remote_datastores(remote.clone()).await
465 })?;
331b869d 466
e0100d61
FG
467 for item in data {
468 list.push(item.store);
331b869d
DM
469 }
470
471 Ok(())
472 }).map_err(|_err: Error| { /* ignore */ });
473
474 list
475}