]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox-backup-manager.rs
client/remote: add support to specify port number
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
1 use std::collections::HashMap;
2
3 use anyhow::{format_err, Error};
4 use serde_json::{json, Value};
5
6 use proxmox::api::{api, cli::*, RpcEnvironment};
7
8 use proxmox_backup::tools;
9 use proxmox_backup::config;
10 use proxmox_backup::api2::{self, types::* };
11 use proxmox_backup::client::*;
12 use proxmox_backup::tools::ticket::Ticket;
13 use proxmox_backup::auth_helpers::*;
14
15 mod proxmox_backup_manager;
16 use proxmox_backup_manager::*;
17
18 async fn view_task_result(
19 client: HttpClient,
20 result: Value,
21 output_format: &str,
22 ) -> Result<(), Error> {
23 let data = &result["data"];
24 if output_format == "text" {
25 if let Some(upid) = data.as_str() {
26 display_task_log(client, upid, true).await?;
27 }
28 } else {
29 format_and_print_result(&data, &output_format);
30 }
31
32 Ok(())
33 }
34
35 // Note: local workers should print logs to stdout, so there is no need
36 // to fetch/display logs. We just wait for the worker to finish.
37 pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
38
39 let upid: proxmox_backup::server::UPID = upid_str.parse()?;
40
41 let sleep_duration = core::time::Duration::new(0, 100_000_000);
42
43 loop {
44 if proxmox_backup::server::worker_is_active_local(&upid) {
45 tokio::time::delay_for(sleep_duration).await;
46 } else {
47 break;
48 }
49 }
50 Ok(())
51 }
52
53 fn connect() -> Result<HttpClient, Error> {
54
55 let uid = nix::unistd::Uid::current();
56
57 let mut options = HttpClientOptions::new()
58 .prefix(Some("proxmox-backup".to_string()))
59 .verify_cert(false); // not required for connection to localhost
60
61 let client = if uid.is_root() {
62 let ticket = Ticket::new("PBS", Userid::root_userid())?
63 .sign(private_auth_key(), None)?;
64 options = options.password(Some(ticket));
65 HttpClient::new("localhost", 8007, Userid::root_userid(), options)?
66 } else {
67 options = options.ticket_cache(true).interactive(true);
68 HttpClient::new("localhost", 8007, Userid::root_userid(), options)?
69 };
70
71 Ok(client)
72 }
73
74 #[api(
75 input: {
76 properties: {
77 store: {
78 schema: DATASTORE_SCHEMA,
79 },
80 "output-format": {
81 schema: OUTPUT_FORMAT,
82 optional: true,
83 },
84 }
85 }
86 )]
87 /// Start garbage collection for a specific datastore.
88 async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
89
90 let output_format = get_output_format(&param);
91
92 let store = tools::required_string_param(&param, "store")?;
93
94 let mut client = connect()?;
95
96 let path = format!("api2/json/admin/datastore/{}/gc", store);
97
98 let result = client.post(&path, None).await?;
99
100 view_task_result(client, result, &output_format).await?;
101
102 Ok(Value::Null)
103 }
104
105 #[api(
106 input: {
107 properties: {
108 store: {
109 schema: DATASTORE_SCHEMA,
110 },
111 "output-format": {
112 schema: OUTPUT_FORMAT,
113 optional: true,
114 },
115 }
116 }
117 )]
118 /// Show garbage collection status for a specific datastore.
119 async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
120
121 let output_format = get_output_format(&param);
122
123 let store = tools::required_string_param(&param, "store")?;
124
125 let client = connect()?;
126
127 let path = format!("api2/json/admin/datastore/{}/gc", store);
128
129 let mut result = client.get(&path, None).await?;
130 let mut data = result["data"].take();
131 let schema = &api2::admin::datastore::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS;
132
133 let options = default_table_format_options();
134
135 format_and_print_result_full(&mut data, schema, &output_format, &options);
136
137 Ok(Value::Null)
138 }
139
140 fn garbage_collection_commands() -> CommandLineInterface {
141
142 let cmd_def = CliCommandMap::new()
143 .insert("status",
144 CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS)
145 .arg_param(&["store"])
146 .completion_cb("store", config::datastore::complete_datastore_name)
147 )
148 .insert("start",
149 CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION)
150 .arg_param(&["store"])
151 .completion_cb("store", config::datastore::complete_datastore_name)
152 );
153
154 cmd_def.into()
155 }
156
157 #[api(
158 input: {
159 properties: {
160 limit: {
161 description: "The maximal number of tasks to list.",
162 type: Integer,
163 optional: true,
164 minimum: 1,
165 maximum: 1000,
166 default: 50,
167 },
168 "output-format": {
169 schema: OUTPUT_FORMAT,
170 optional: true,
171 },
172 all: {
173 type: Boolean,
174 description: "Also list stopped tasks.",
175 optional: true,
176 }
177 }
178 }
179 )]
180 /// List running server tasks.
181 async fn task_list(param: Value) -> Result<Value, Error> {
182
183 let output_format = get_output_format(&param);
184
185 let client = connect()?;
186
187 let limit = param["limit"].as_u64().unwrap_or(50) as usize;
188 let running = !param["all"].as_bool().unwrap_or(false);
189 let args = json!({
190 "running": running,
191 "start": 0,
192 "limit": limit,
193 });
194 let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
195
196 let mut data = result["data"].take();
197 let schema = &api2::node::tasks::API_RETURN_SCHEMA_LIST_TASKS;
198
199 let options = default_table_format_options()
200 .column(ColumnConfig::new("starttime").right_align(false).renderer(tools::format::render_epoch))
201 .column(ColumnConfig::new("endtime").right_align(false).renderer(tools::format::render_epoch))
202 .column(ColumnConfig::new("upid"))
203 .column(ColumnConfig::new("status").renderer(tools::format::render_task_status));
204
205 format_and_print_result_full(&mut data, schema, &output_format, &options);
206
207 Ok(Value::Null)
208 }
209
210 #[api(
211 input: {
212 properties: {
213 upid: {
214 schema: UPID_SCHEMA,
215 },
216 }
217 }
218 )]
219 /// Display the task log.
220 async fn task_log(param: Value) -> Result<Value, Error> {
221
222 let upid = tools::required_string_param(&param, "upid")?;
223
224 let client = connect()?;
225
226 display_task_log(client, upid, true).await?;
227
228 Ok(Value::Null)
229 }
230
231 #[api(
232 input: {
233 properties: {
234 upid: {
235 schema: UPID_SCHEMA,
236 },
237 }
238 }
239 )]
240 /// Try to stop a specific task.
241 async fn task_stop(param: Value) -> Result<Value, Error> {
242
243 let upid_str = tools::required_string_param(&param, "upid")?;
244
245 let mut client = connect()?;
246
247 let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str);
248 let _ = client.delete(&path, None).await?;
249
250 Ok(Value::Null)
251 }
252
253 fn task_mgmt_cli() -> CommandLineInterface {
254
255 let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG)
256 .arg_param(&["upid"]);
257
258 let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP)
259 .arg_param(&["upid"]);
260
261 let cmd_def = CliCommandMap::new()
262 .insert("list", CliCommand::new(&API_METHOD_TASK_LIST))
263 .insert("log", task_log_cmd_def)
264 .insert("stop", task_stop_cmd_def);
265
266 cmd_def.into()
267 }
268
269 // fixme: avoid API redefinition
270 #[api(
271 input: {
272 properties: {
273 "local-store": {
274 schema: DATASTORE_SCHEMA,
275 },
276 remote: {
277 schema: REMOTE_ID_SCHEMA,
278 },
279 "remote-store": {
280 schema: DATASTORE_SCHEMA,
281 },
282 "remove-vanished": {
283 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
284 optional: true,
285 },
286 "output-format": {
287 schema: OUTPUT_FORMAT,
288 optional: true,
289 },
290 }
291 }
292 )]
293 /// Sync datastore from another repository
294 async fn pull_datastore(
295 remote: String,
296 remote_store: String,
297 local_store: String,
298 remove_vanished: Option<bool>,
299 param: Value,
300 ) -> Result<Value, Error> {
301
302 let output_format = get_output_format(&param);
303
304 let mut client = connect()?;
305
306 let mut args = json!({
307 "store": local_store,
308 "remote": remote,
309 "remote-store": remote_store,
310 });
311
312 if let Some(remove_vanished) = remove_vanished {
313 args["remove-vanished"] = Value::from(remove_vanished);
314 }
315
316 let result = client.post("api2/json/pull", Some(args)).await?;
317
318 view_task_result(client, result, &output_format).await?;
319
320 Ok(Value::Null)
321 }
322
323 #[api(
324 input: {
325 properties: {
326 "store": {
327 schema: DATASTORE_SCHEMA,
328 },
329 "output-format": {
330 schema: OUTPUT_FORMAT,
331 optional: true,
332 },
333 }
334 }
335 )]
336 /// Verify backups
337 async fn verify(
338 store: String,
339 param: Value,
340 ) -> Result<Value, Error> {
341
342 let output_format = get_output_format(&param);
343
344 let mut client = connect()?;
345
346 let args = json!({});
347
348 let path = format!("api2/json/admin/datastore/{}/verify", store);
349
350 let result = client.post(&path, Some(args)).await?;
351
352 view_task_result(client, result, &output_format).await?;
353
354 Ok(Value::Null)
355 }
356
357 fn main() {
358
359 proxmox_backup::tools::setup_safe_path_env();
360
361 let cmd_def = CliCommandMap::new()
362 .insert("acl", acl_commands())
363 .insert("datastore", datastore_commands())
364 .insert("disk", disk_commands())
365 .insert("dns", dns_commands())
366 .insert("network", network_commands())
367 .insert("user", user_commands())
368 .insert("remote", remote_commands())
369 .insert("garbage-collection", garbage_collection_commands())
370 .insert("cert", cert_mgmt_cli())
371 .insert("sync-job", sync_job_commands())
372 .insert("task", task_mgmt_cli())
373 .insert(
374 "pull",
375 CliCommand::new(&API_METHOD_PULL_DATASTORE)
376 .arg_param(&["remote", "remote-store", "local-store"])
377 .completion_cb("local-store", config::datastore::complete_datastore_name)
378 .completion_cb("remote", config::remote::complete_remote_name)
379 .completion_cb("remote-store", complete_remote_datastore_name)
380 )
381 .insert(
382 "verify",
383 CliCommand::new(&API_METHOD_VERIFY)
384 .arg_param(&["store"])
385 .completion_cb("store", config::datastore::complete_datastore_name)
386 );
387
388
389
390 let mut rpcenv = CliEnvironment::new();
391 rpcenv.set_user(Some(String::from("root@pam")));
392
393 proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
394 }
395
396 // shell completion helper
397 pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
398
399 let mut list = Vec::new();
400
401 let _ = proxmox::try_block!({
402 let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
403 let (remote_config, _digest) = config::remote::config()?;
404
405 let remote: config::remote::Remote = remote_config.lookup("remote", &remote)?;
406
407 let options = HttpClientOptions::new()
408 .password(Some(remote.password.clone()))
409 .fingerprint(remote.fingerprint.clone());
410
411 let client = HttpClient::new(
412 &remote.host,
413 remote.port.unwrap_or(8007),
414 &remote.userid,
415 options,
416 )?;
417
418 let result = crate::tools::runtime::block_on(client.get("api2/json/admin/datastore", None))?;
419
420 if let Some(data) = result["data"].as_array() {
421 for item in data {
422 if let Some(store) = item["store"].as_str() {
423 list.push(store.to_owned());
424 }
425 }
426 }
427
428 Ok(())
429 }).map_err(|_err: Error| { /* ignore */ });
430
431 list
432 }