[pbs-devel] [RFC PATCH proxmox-backup 1/2] client/remote: add support to specify port number

Dominik Csapak d.csapak at proxmox.com
Tue Sep 29 16:18:58 CEST 2020


this adds the ability to add port numbers in the backup repo spec
as well as remotes, so that user that are behind a
NAT/Firewall/Reverse proxy can still use it

also adds some explanation and examples to the docs to make it clearer
for h2 client i left the localhost:8007 part, since it is not
configurable where we bind to

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
i sent it in one big patch, since most of it is simply adding a new
'port' parameter to various functions

 docs/administration-guide.rst              | 17 +++++++++++-
 src/api2/config/remote.rs                  | 16 ++++++++++++
 src/api2/pull.rs                           |  4 +--
 src/api2/types/mod.rs                      |  2 +-
 src/bin/proxmox-backup-client.rs           | 30 +++++++++++-----------
 src/bin/proxmox-backup-manager.rs          |  5 ++--
 src/bin/proxmox_backup_client/benchmark.rs |  2 +-
 src/bin/proxmox_backup_client/catalog.rs   |  4 +--
 src/bin/proxmox_backup_client/mount.rs     |  2 +-
 src/bin/proxmox_backup_client/task.rs      |  6 ++---
 src/client/backup_reader.rs                |  2 +-
 src/client/backup_repo.rs                  | 29 +++++++++++++--------
 src/client/backup_writer.rs                |  2 +-
 src/client/http_client.rs                  | 29 +++++++++++++--------
 src/client/pull.rs                         |  2 +-
 src/config/remote.rs                       |  7 +++++
 16 files changed, 108 insertions(+), 51 deletions(-)

diff --git a/docs/administration-guide.rst b/docs/administration-guide.rst
index c270bdb1..9769f3a5 100644
--- a/docs/administration-guide.rst
+++ b/docs/administration-guide.rst
@@ -732,11 +732,14 @@ Repository Locations
 The client uses the following notation to specify a datastore repository
 on the backup server.
 
-  [[username@]server:]datastore
+  [[username@]server[:port]:]datastore
 
 The default value for ``username`` ist ``root at pam``.  If no server is specified,
 the default is the local host (``localhost``).
 
+You can specify a port if your backup server is only reachable on a different
+port (e.g. with NAT and port forwarding).
+
 Note that if the server is an IPv6 address, you have to write it with
 square brackets (e.g. [fe80::01]).
 
@@ -744,6 +747,18 @@ You can pass the repository with the ``--repository`` command
 line option, or by setting the ``PBS_REPOSITORY`` environment
 variable.
 
+Here some examples of valid repositories and the real values
+
+================================ ============ ================== ===========
+Example                          User         Host:Port          Datastore
+================================ ============ ================== ===========
+mydatastore                      ``root at pam`` localhost:8007     mydatastore
+myhostname:mydatastore           ``root at pam`` myhostname:8007    mydatastore
+user at pbs@myhostname:mydatastore  ``user at pbs`` myhostname:8007    mydatastore
+192.168.55.55:1234:mydatastore   ``root at pam`` 192.168.55.55:1234 mydatastore
+[ff80::51]:mydatastore           ``root at pam`` [ff80::51]:8007    mydatastore
+[ff80::51]:1234:mydatastore      ``root at pam`` [ff80::51]:1234    mydatastore
+================================ ============ ================== ===========
 
 Environment Variables
 ~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs
index faef51d6..65dedd7e 100644
--- a/src/api2/config/remote.rs
+++ b/src/api2/config/remote.rs
@@ -60,6 +60,12 @@ pub fn list_remotes(
             host: {
                 schema: DNS_NAME_OR_IP_SCHEMA,
             },
+            port: {
+                description: "The (optional) port.",
+                type: u16,
+                optional: true,
+                default: 8007,
+            },
             userid: {
                 type: Userid,
             },
@@ -136,6 +142,8 @@ pub enum DeletableProperty {
     comment,
     /// Delete the fingerprint property.
     fingerprint,
+    /// Delete the port property.
+    port,
 }
 
 #[api(
@@ -153,6 +161,11 @@ pub enum DeletableProperty {
                 optional: true,
                 schema: DNS_NAME_OR_IP_SCHEMA,
             },
+            port: {
+                description: "The (optional) port.",
+                type: u16,
+                optional: true,
+            },
             userid: {
                 optional: true,
                 type: Userid,
@@ -188,6 +201,7 @@ pub fn update_remote(
     name: String,
     comment: Option<String>,
     host: Option<String>,
+    port: Option<u16>,
     userid: Option<Userid>,
     password: Option<String>,
     fingerprint: Option<String>,
@@ -211,6 +225,7 @@ pub fn update_remote(
             match delete_prop {
                 DeletableProperty::comment => { data.comment = None; },
                 DeletableProperty::fingerprint => { data.fingerprint = None; },
+                DeletableProperty::port => { data.port = None; },
             }
         }
     }
@@ -224,6 +239,7 @@ pub fn update_remote(
         }
     }
     if let Some(host) = host { data.host = host; }
+    if port.is_some() { data.port = port; }
     if let Some(userid) = userid { data.userid = userid; }
     if let Some(password) = password { data.password = password; }
 
diff --git a/src/api2/pull.rs b/src/api2/pull.rs
index 09e27a17..3edbdf2f 100644
--- a/src/api2/pull.rs
+++ b/src/api2/pull.rs
@@ -55,12 +55,12 @@ pub async fn get_pull_parameters(
         .password(Some(remote.password.clone()))
         .fingerprint(remote.fingerprint.clone());
 
-    let client = HttpClient::new(&remote.host, &remote.userid, options)?;
+    let client = HttpClient::new(&remote.host, remote.port.unwrap_or(8007), &remote.userid, options)?;
     let _auth_info = client.login() // make sure we can auth
         .await
         .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?;
 
-    let src_repo = BackupRepository::new(Some(remote.userid), Some(remote.host), remote_store.to_string());
+    let src_repo = BackupRepository::new(Some(remote.userid), Some(remote.host), remote.port, remote_store.to_string());
 
     Ok((client, src_repo, tgt_store))
 }
diff --git a/src/api2/types/mod.rs b/src/api2/types/mod.rs
index 6ad67607..a5c8e3e9 100644
--- a/src/api2/types/mod.rs
+++ b/src/api2/types/mod.rs
@@ -65,7 +65,7 @@ const_regex!{
 
     pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|",  IPRE!(), r"$");
 
-    pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|",  IPRE_BRACKET!() ,"):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
+    pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|",  IPRE_BRACKET!() ,"):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
 
     pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
 
diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs
index c0cd86a6..2403bea6 100644
--- a/src/bin/proxmox-backup-client.rs
+++ b/src/bin/proxmox-backup-client.rs
@@ -192,7 +192,7 @@ pub fn complete_repository(_arg: &str, _param: &HashMap<String, String>) -> Vec<
     result
 }
 
-fn connect(server: &str, userid: &Userid) -> Result<HttpClient, Error> {
+fn connect(server: &str, port: u16, userid: &Userid) -> Result<HttpClient, Error> {
 
     let fingerprint = std::env::var(ENV_VAR_PBS_FINGERPRINT).ok();
 
@@ -211,7 +211,7 @@ fn connect(server: &str, userid: &Userid) -> Result<HttpClient, Error> {
         .fingerprint_cache(true)
         .ticket_cache(true);
 
-    HttpClient::new(server, userid, options)
+    HttpClient::new(server, port, userid, options)
 }
 
 async fn view_task_result(
@@ -365,7 +365,7 @@ async fn list_backup_groups(param: Value) -> Result<Value, Error> {
 
     let repo = extract_repository_from_value(&param)?;
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/admin/datastore/{}/groups", repo.store());
 
@@ -438,7 +438,7 @@ async fn list_snapshots(param: Value) -> Result<Value, Error> {
 
     let output_format = get_output_format(&param);
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     let group: Option<BackupGroup> = if let Some(path) = param["group"].as_str() {
         Some(path.parse()?)
@@ -503,7 +503,7 @@ async fn forget_snapshots(param: Value) -> Result<Value, Error> {
     let path = tools::required_string_param(&param, "snapshot")?;
     let snapshot: BackupDir = path.parse()?;
 
-    let mut client = connect(repo.host(), repo.user())?;
+    let mut client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store());
 
@@ -533,7 +533,7 @@ async fn api_login(param: Value) -> Result<Value, Error> {
 
     let repo = extract_repository_from_value(&param)?;
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
     client.login().await?;
 
     record_repository(&repo);
@@ -590,7 +590,7 @@ async fn api_version(param: Value) -> Result<(), Error> {
 
     let repo = extract_repository_from_value(&param);
     if let Ok(repo) = repo {
-        let client = connect(repo.host(), repo.user())?;
+        let client = connect(repo.host(), repo.port(), repo.user())?;
 
         match client.get("api2/json/version", None).await {
             Ok(mut result) => version_info["server"] = result["data"].take(),
@@ -640,7 +640,7 @@ async fn list_snapshot_files(param: Value) -> Result<Value, Error> {
 
     let output_format = get_output_format(&param);
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/admin/datastore/{}/files", repo.store());
 
@@ -684,7 +684,7 @@ async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
 
     let output_format = get_output_format(&param);
 
-    let mut client = connect(repo.host(), repo.user())?;
+    let mut client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/admin/datastore/{}/gc", repo.store());
 
@@ -996,7 +996,7 @@ async fn create_backup(
 
     let backup_time = backup_time_opt.unwrap_or_else(|| epoch_i64());
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
     record_repository(&repo);
 
     println!("Starting backup: {}/{}/{}", backup_type, backup_id, BackupDir::backup_time_to_string(backup_time)?);
@@ -1299,7 +1299,7 @@ async fn restore(param: Value) -> Result<Value, Error> {
 
     let archive_name = tools::required_string_param(&param, "archive-name")?;
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     record_repository(&repo);
 
@@ -1472,7 +1472,7 @@ async fn upload_log(param: Value) -> Result<Value, Error> {
     let snapshot = tools::required_string_param(&param, "snapshot")?;
     let snapshot: BackupDir = snapshot.parse()?;
 
-    let mut client = connect(repo.host(), repo.user())?;
+    let mut client = connect(repo.host(), repo.port(), repo.user())?;
 
     let (keydata, crypt_mode) = keyfile_parameters(&param)?;
 
@@ -1543,7 +1543,7 @@ fn prune<'a>(
 async fn prune_async(mut param: Value) -> Result<Value, Error> {
     let repo = extract_repository_from_value(&param)?;
 
-    let mut client = connect(repo.host(), repo.user())?;
+    let mut client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/admin/datastore/{}/prune", repo.store());
 
@@ -1626,7 +1626,7 @@ async fn status(param: Value) -> Result<Value, Error> {
 
     let output_format = get_output_format(&param);
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/admin/datastore/{}/status", repo.store());
 
@@ -1671,7 +1671,7 @@ async fn try_get(repo: &BackupRepository, url: &str) -> Value {
         .fingerprint_cache(true)
         .ticket_cache(true);
 
-    let client = match HttpClient::new(repo.host(), repo.user(), options) {
+    let client = match HttpClient::new(repo.host(), repo.port(), repo.user(), options) {
         Ok(v) => v,
         _ => return Value::Null,
     };
diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs
index 16b8d702..f0dbea93 100644
--- a/src/bin/proxmox-backup-manager.rs
+++ b/src/bin/proxmox-backup-manager.rs
@@ -62,10 +62,10 @@ fn connect() -> Result<HttpClient, Error> {
         let ticket = Ticket::new("PBS", Userid::root_userid())?
             .sign(private_auth_key(), None)?;
         options = options.password(Some(ticket));
-        HttpClient::new("localhost", Userid::root_userid(), options)?
+        HttpClient::new("localhost", 8007, Userid::root_userid(), options)?
     } else {
         options = options.ticket_cache(true).interactive(true);
-        HttpClient::new("localhost", Userid::root_userid(), options)?
+        HttpClient::new("localhost", 8007, Userid::root_userid(), options)?
     };
 
     Ok(client)
@@ -410,6 +410,7 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String
 
         let client = HttpClient::new(
             &remote.host,
+            remote.port.unwrap_or(8007),
             &remote.userid,
             options,
         )?;
diff --git a/src/bin/proxmox_backup_client/benchmark.rs b/src/bin/proxmox_backup_client/benchmark.rs
index 68726d97..716aaf0d 100644
--- a/src/bin/proxmox_backup_client/benchmark.rs
+++ b/src/bin/proxmox_backup_client/benchmark.rs
@@ -225,7 +225,7 @@ async fn test_upload_speed(
 
     let backup_time = proxmox::tools::time::epoch_i64();
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
     record_repository(&repo);
 
     if verbose { eprintln!("Connecting to backup server"); }
diff --git a/src/bin/proxmox_backup_client/catalog.rs b/src/bin/proxmox_backup_client/catalog.rs
index b419728e..e35692f2 100644
--- a/src/bin/proxmox_backup_client/catalog.rs
+++ b/src/bin/proxmox_backup_client/catalog.rs
@@ -79,7 +79,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
         }
     };
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     let client = BackupReader::start(
         client,
@@ -153,7 +153,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
 /// Shell to interactively inspect and restore snapshots.
 async fn catalog_shell(param: Value) -> Result<(), Error> {
     let repo = extract_repository_from_value(&param)?;
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
     let path = tools::required_string_param(&param, "snapshot")?;
     let archive_name = tools::required_string_param(&param, "archive-name")?;
 
diff --git a/src/bin/proxmox_backup_client/mount.rs b/src/bin/proxmox_backup_client/mount.rs
index 24224499..4f362dd2 100644
--- a/src/bin/proxmox_backup_client/mount.rs
+++ b/src/bin/proxmox_backup_client/mount.rs
@@ -101,7 +101,7 @@ async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> {
     let repo = extract_repository_from_value(&param)?;
     let archive_name = tools::required_string_param(&param, "archive-name")?;
     let target = tools::required_string_param(&param, "target")?;
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     record_repository(&repo);
 
diff --git a/src/bin/proxmox_backup_client/task.rs b/src/bin/proxmox_backup_client/task.rs
index 96a28be9..72d8095c 100644
--- a/src/bin/proxmox_backup_client/task.rs
+++ b/src/bin/proxmox_backup_client/task.rs
@@ -48,7 +48,7 @@ async fn task_list(param: Value) -> Result<Value, Error> {
     let output_format = get_output_format(&param);
 
     let repo = extract_repository_from_value(&param)?;
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     let limit = param["limit"].as_u64().unwrap_or(50) as usize;
     let running = !param["all"].as_bool().unwrap_or(false);
@@ -96,7 +96,7 @@ async fn task_log(param: Value) -> Result<Value, Error> {
     let repo = extract_repository_from_value(&param)?;
     let upid =  tools::required_string_param(&param, "upid")?;
 
-    let client = connect(repo.host(), repo.user())?;
+    let client = connect(repo.host(), repo.port(), repo.user())?;
 
     display_task_log(client, upid, true).await?;
 
@@ -122,7 +122,7 @@ async fn task_stop(param: Value) -> Result<Value, Error> {
     let repo = extract_repository_from_value(&param)?;
     let upid_str =  tools::required_string_param(&param, "upid")?;
 
-    let mut client = connect(repo.host(), repo.user())?;
+    let mut client = connect(repo.host(), repo.port(), repo.user())?;
 
     let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str);
     let _ = client.delete(&path, None).await?;
diff --git a/src/client/backup_reader.rs b/src/client/backup_reader.rs
index 8afaa4aa..92200f9e 100644
--- a/src/client/backup_reader.rs
+++ b/src/client/backup_reader.rs
@@ -54,7 +54,7 @@ impl BackupReader {
             "store": datastore,
             "debug": debug,
         });
-        let req = HttpClient::request_builder(client.server(), "GET", "/api2/json/reader", Some(param)).unwrap();
+        let req = HttpClient::request_builder(client.server(), client.port(), "GET", "/api2/json/reader", Some(param)).unwrap();
 
         let (h2, abort) = client.start_h2_connection(req, String::from(PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!())).await?;
 
diff --git a/src/client/backup_repo.rs b/src/client/backup_repo.rs
index ae40ad2d..862e93af 100644
--- a/src/client/backup_repo.rs
+++ b/src/client/backup_repo.rs
@@ -19,14 +19,16 @@ pub struct BackupRepository {
     user: Option<Userid>,
     /// The host name or IP address
     host: Option<String>,
+    /// The port
+    port: Option<u16>,
     /// The name of the datastore
     store: String,
 }
 
 impl BackupRepository {
 
-    pub fn new(user: Option<Userid>, host: Option<String>, store: String) -> Self {
-        Self { user, host, store }
+    pub fn new(user: Option<Userid>, host: Option<String>, port: Option<u16>, store: String) -> Self {
+        Self { user, host, port, store }
     }
 
     pub fn user(&self) -> &Userid {
@@ -43,6 +45,13 @@ impl BackupRepository {
         "localhost"
     }
 
+    pub fn port(&self) -> u16 {
+        if let Some(port) = self.port {
+            return port;
+        }
+        8007
+    }
+
     pub fn store(&self) -> &str {
         &self.store
     }
@@ -50,13 +59,12 @@ impl BackupRepository {
 
 impl fmt::Display for BackupRepository {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-       if let Some(ref user) = self.user {
-           write!(f, "{}@{}:{}", user, self.host(), self.store)
-       } else if let Some(ref host) = self.host {
-           write!(f, "{}:{}", host, self.store)
-       } else {
-           write!(f, "{}", self.store)
-       }
+        match (&self.user, &self.host, self.port) {
+            (Some(user), _, _) => write!(f, "{}@{}:{}:{}", user, self.host(), self.port(), self.store),
+            (None, Some(host), None) => write!(f, "{}:{}", host, self.store),
+            (None, _, Some(port)) => write!(f, "{}:{}:{}", self.host(), port, self.store),
+            (None, None, None) => write!(f, "{}", self.store),
+        }
     }
 }
 
@@ -76,7 +84,8 @@ impl std::str::FromStr for BackupRepository {
         Ok(Self {
             user: cap.get(1).map(|m| Userid::try_from(m.as_str().to_owned())).transpose()?,
             host: cap.get(2).map(|m| m.as_str().to_owned()),
-            store: cap[3].to_owned(),
+            port: cap.get(3).map(|m| m.as_str().parse::<u16>()).transpose()?,
+            store: cap[4].to_owned(),
         })
     }
 }
diff --git a/src/client/backup_writer.rs b/src/client/backup_writer.rs
index e0719115..b3fd3703 100644
--- a/src/client/backup_writer.rs
+++ b/src/client/backup_writer.rs
@@ -65,7 +65,7 @@ impl BackupWriter {
         });
 
         let req = HttpClient::request_builder(
-            client.server(), "GET", "/api2/json/backup", Some(param)).unwrap();
+            client.server(), client.port(), "GET", "/api2/json/backup", Some(param)).unwrap();
 
         let (h2, abort) = client.start_h2_connection(req, String::from(PROXMOX_BACKUP_PROTOCOL_ID_V1!())).await?;
 
diff --git a/src/client/http_client.rs b/src/client/http_client.rs
index 692b2c3e..66c7e11f 100644
--- a/src/client/http_client.rs
+++ b/src/client/http_client.rs
@@ -99,6 +99,7 @@ impl HttpClientOptions {
 pub struct HttpClient {
     client: Client<HttpsConnector>,
     server: String,
+    port: u16,
     fingerprint: Arc<Mutex<Option<String>>>,
     first_auth: BroadcastFuture<()>,
     auth: Arc<RwLock<AuthInfo>>,
@@ -250,6 +251,7 @@ fn load_ticket_info(prefix: &str, server: &str, userid: &Userid) -> Option<(Stri
 impl HttpClient {
     pub fn new(
         server: &str,
+        port: u16,
         userid: &Userid,
         mut options: HttpClientOptions,
     ) -> Result<Self, Error> {
@@ -338,7 +340,7 @@ impl HttpClient {
                     let authinfo = auth2.read().unwrap().clone();
                     (authinfo.userid, authinfo.ticket)
                 };
-                match Self::credentials(client2.clone(), server2.clone(), userid, ticket).await {
+                match Self::credentials(client2.clone(), server2.clone(), port, userid, ticket).await {
                     Ok(auth) => {
                         if use_ticket_cache & &prefix2.is_some() {
                             let _ = store_ticket_info(prefix2.as_ref().unwrap(), &server2, &auth.userid.to_string(), &auth.ticket, &auth.token);
@@ -358,6 +360,7 @@ impl HttpClient {
         let login_future = Self::credentials(
             client.clone(),
             server.to_owned(),
+            port,
             userid.to_owned(),
             password.to_owned(),
         ).map_ok({
@@ -377,6 +380,7 @@ impl HttpClient {
         Ok(Self {
             client,
             server: String::from(server),
+            port,
             fingerprint: verified_fingerprint,
             auth,
             ticket_abort,
@@ -486,7 +490,7 @@ impl HttpClient {
         path: &str,
         data: Option<Value>,
     ) -> Result<Value, Error> {
-        let req = Self::request_builder(&self.server, "GET", path, data).unwrap();
+        let req = Self::request_builder(&self.server, self.port, "GET", path, data)?;
         self.request(req).await
     }
 
@@ -495,7 +499,7 @@ impl HttpClient {
         path: &str,
         data: Option<Value>,
     ) -> Result<Value, Error> {
-        let req = Self::request_builder(&self.server, "DELETE", path, data).unwrap();
+        let req = Self::request_builder(&self.server, self.port, "DELETE", path, data)?;
         self.request(req).await
     }
 
@@ -504,7 +508,7 @@ impl HttpClient {
         path: &str,
         data: Option<Value>,
     ) -> Result<Value, Error> {
-        let req = Self::request_builder(&self.server, "POST", path, data).unwrap();
+        let req = Self::request_builder(&self.server, self.port, "POST", path, data)?;
         self.request(req).await
     }
 
@@ -513,7 +517,7 @@ impl HttpClient {
         path: &str,
         output: &mut (dyn Write + Send),
     ) -> Result<(), Error> {
-        let mut req = Self::request_builder(&self.server, "GET", path, None).unwrap();
+        let mut req = Self::request_builder(&self.server, self.port, "GET", path, None)?;
 
         let client = self.client.clone();
 
@@ -549,7 +553,7 @@ impl HttpClient {
     ) -> Result<Value, Error> {
 
         let path = path.trim_matches('/');
-        let mut url = format!("https://{}:8007/{}", &self.server, path);
+        let mut url = format!("https://{}:{}/{}", &self.server, self.port, path);
 
         if let Some(data) = data {
             let query = tools::json_object_to_query(data).unwrap();
@@ -624,11 +628,12 @@ impl HttpClient {
     async fn credentials(
         client: Client<HttpsConnector>,
         server: String,
+        port: u16,
         username: Userid,
         password: String,
     ) -> Result<AuthInfo, Error> {
         let data = json!({ "username": username, "password": password });
-        let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap();
+        let req = Self::request_builder(&server, port, "POST", "/api2/json/access/ticket", Some(data))?;
         let cred = Self::api_request(client, req).await?;
         let auth = AuthInfo {
             userid: cred["data"]["username"].as_str().unwrap().parse()?,
@@ -672,9 +677,13 @@ impl HttpClient {
         &self.server
     }
 
-    pub fn request_builder(server: &str, method: &str, path: &str, data: Option<Value>) -> Result<Request<Body>, Error> {
+    pub fn port(&self) -> u16 {
+        self.port
+    }
+
+    pub fn request_builder(server: &str, port: u16, method: &str, path: &str, data: Option<Value>) -> Result<Request<Body>, Error> {
         let path = path.trim_matches('/');
-        let url: Uri = format!("https://{}:8007/{}", server, path).parse()?;
+        let url: Uri = format!("https://{}:{}/{}", server, port, path).parse()?;
 
         if let Some(data) = data {
             if method == "POST" {
@@ -687,7 +696,7 @@ impl HttpClient {
                 return Ok(request);
             } else {
                 let query = tools::json_object_to_query(data)?;
-                let url: Uri = format!("https://{}:8007/{}?{}", server, path, query).parse()?;
+                let url: Uri = format!("https://{}:{}/{}?{}", server, port, path, query).parse()?;
                 let request = Request::builder()
                     .method(method)
                     .uri(url)
diff --git a/src/client/pull.rs b/src/client/pull.rs
index 94143d61..86843b99 100644
--- a/src/client/pull.rs
+++ b/src/client/pull.rs
@@ -439,7 +439,7 @@ pub async fn pull_group(
             .password(Some(auth_info.ticket.clone()))
             .fingerprint(fingerprint.clone());
 
-        let new_client = HttpClient::new(src_repo.host(), src_repo.user(), options)?;
+        let new_client = HttpClient::new(src_repo.host(), src_repo.port(), src_repo.user(), options)?;
 
         let reader = BackupReader::start(
             new_client,
diff --git a/src/config/remote.rs b/src/config/remote.rs
index ae2d8957..9e597342 100644
--- a/src/config/remote.rs
+++ b/src/config/remote.rs
@@ -39,6 +39,11 @@ pub const REMOTE_PASSWORD_SCHEMA: Schema = StringSchema::new("Password or auth t
         host: {
             schema: DNS_NAME_OR_IP_SCHEMA,
         },
+        port: {
+            optional: true,
+            description: "The (optional) port",
+            type: u16,
+        },
         userid: {
             type: Userid,
         },
@@ -58,6 +63,8 @@ pub struct Remote {
     #[serde(skip_serializing_if="Option::is_none")]
     pub comment: Option<String>,
     pub host: String,
+    #[serde(skip_serializing_if="Option::is_none")]
+    pub port: Option<u16>,
     pub userid: Userid,
     #[serde(skip_serializing_if="String::is_empty")]
     #[serde(with = "proxmox::tools::serde::string_as_base64")]
-- 
2.20.1






More information about the pbs-devel mailing list