From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 2754E1FF136 for ; Mon, 23 Mar 2026 22:14:31 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C2BA735612; Mon, 23 Mar 2026 22:14:49 +0100 (CET) From: Thomas Lamprecht To: pbs-devel@lists.proxmox.com Subject: [PATCH] client: support individual repository component parameters Date: Mon, 23 Mar 2026 22:11:56 +0100 Message-ID: <20260323211400.2661765-1-t.lamprecht@proxmox.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1774300403041 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.011 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: BWPYDY6CVIFHOXBAHTWEGJC3YX3YX3X4 X-Message-ID-Hash: BWPYDY6CVIFHOXBAHTWEGJC3YX3YX3X4 X-MailFrom: t.lamprecht@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The compact repository URL format ([[auth-id@]server[:port]:]datastore) can be cumbersome to remember the exact syntax and work with. It also makes it awkward to change a single aspect of the connection, like switching to a different datastore, without rewriting the whole string. So add the "expanded" --server, --port, --datastore, and --auth-id as separate CLI parameters, mutually exclusive with --repository. Both forms resolve to the same BackupRepository internally. Add a BackupRepositoryArgs that holds both the atoms and the combined URL and replace the existing "repository" parameter in all client tools, i.e. proxmox-backup-client, proxmox-file-restore, and proxmox-backup-debug. This struct gets flattened into the cli (api) schemas such that it's fully backward compatible. Also add recognition for corresponding environment variables (PBS_SERVER, PBS_PORT, PBS_DATASTORE, PBS_AUTH_ID) serve as fallback when neither --repository/PBS_REPOSITORY nor CLI component options are given. Signed-off-by: Thomas Lamprecht --- Disclaimer: only lightly tested! but as this is somewhat of a pain point I run myself into when adding a new Debian host to backup to a PBS via the client, I finally wanted to have this improved and polished the POC I have lying around for some time now already. docs/backup-client.rst | 60 ++++++++++ pbs-client/src/backup_repo.rs | 117 +++++++++++++++++- pbs-client/src/tools/mod.rs | 159 +++++++++++++++++++------ proxmox-backup-client/src/benchmark.rs | 8 +- proxmox-backup-client/src/catalog.rs | 16 +-- proxmox-backup-client/src/group.rs | 8 +- proxmox-backup-client/src/main.rs | 68 +++++------ proxmox-backup-client/src/mount.rs | 13 +- proxmox-backup-client/src/namespace.rs | 20 ++-- proxmox-backup-client/src/snapshot.rs | 52 ++++---- proxmox-backup-client/src/task.rs | 20 ++-- proxmox-file-restore/src/main.rs | 15 ++- src/bin/proxmox_backup_debug/diff.rs | 9 +- 13 files changed, 418 insertions(+), 147 deletions(-) diff --git a/docs/backup-client.rst b/docs/backup-client.rst index 40962f0e2..4d0467eb1 100644 --- a/docs/backup-client.rst +++ b/docs/backup-client.rst @@ -28,6 +28,50 @@ brackets (for example, `[fe80::01]`). You can pass the repository with the ``--repository`` command-line option, or by setting the ``PBS_REPOSITORY`` environment variable. +Alternatively, you can specify the repository components as separate +command-line options: + +``--server `` + Backup server address (hostname or IP address). Defaults to ``localhost``. + +``--port `` + Backup server port. Defaults to ``8007``. + +``--datastore `` + Name of the target datastore. Required when using component options instead + of ``--repository``. + +``--auth-id `` + Authentication identity, either a user (``user@realm``) or an API token + (``user@realm!tokenname``). Defaults to ``root@pam``. + +These options are mutually exclusive with ``--repository``. Both forms +resolve to the same internal representation, so cached login tickets and +other session state are shared between them. For example, logging in with +``--repository`` and then running a backup with ``--server``/``--datastore`` +(or vice versa) reuses the same ticket, as long as the server address and +user match. + +The component options make it easy to change individual parts of the +connection, for example switching to a different datastore or server without +having to rewrite the entire repository string. They also simplify usage +with API tokens, which require escaping the ``@`` separator in the compact +form: + +.. code-block:: console + + # Using component options + # proxmox-backup-client backup root.pxar:/ \ + --auth-id 'user@pbs!backup' --server pbs.example.com --datastore store1 + + # Equivalent compact form (the \@ disambiguates the user@realm + # separator from the user-to-host separator in the URL format) + # proxmox-backup-client backup root.pxar:/ \ + --repository 'user\@pbs!backup@pbs.example.com:store1' + +.. Note:: Remember to quote API token identifiers on the shell, since the + exclamation mark (``!``) is a special character in most shells. + The web interface provides copyable repository text in the datastore summary with the `Show Connection Information` button. @@ -70,6 +114,22 @@ Environment Variables ``PBS_REPOSITORY`` The default backup repository. +``PBS_SERVER`` + Default backup server address. Used as a fallback when neither + ``--repository`` / ``PBS_REPOSITORY`` nor ``--server`` is given. + Requires ``PBS_DATASTORE`` to be set as well. + +``PBS_PORT`` + Default backup server port. Defaults to ``8007`` if unset. + +``PBS_DATASTORE`` + Default datastore name. Used as a fallback when neither ``--repository`` / + ``PBS_REPOSITORY`` nor ``--datastore`` is given. + +``PBS_AUTH_ID`` + Default authentication identity (``user@realm`` or + ``user@realm!tokenname``). Defaults to ``root@pam`` if unset. + ``PBS_PASSWORD`` When set, this value is used as the password for the backup server. You can also set this to an API token secret. diff --git a/pbs-client/src/backup_repo.rs b/pbs-client/src/backup_repo.rs index 458f89dc2..8d2d356f4 100644 --- a/pbs-client/src/backup_repo.rs +++ b/pbs-client/src/backup_repo.rs @@ -1,8 +1,121 @@ use std::fmt; -use anyhow::{format_err, Error}; +use anyhow::{bail, format_err, Error}; +use serde::{Deserialize, Serialize}; -use pbs_api_types::{Authid, Userid, BACKUP_REPO_URL_REGEX, IP_V6_REGEX}; +use proxmox_schema::api; +use proxmox_schema::*; + +use pbs_api_types::{ + Authid, Userid, BACKUP_REPO_URL, BACKUP_REPO_URL_REGEX, DATASTORE_SCHEMA, IP_V6_REGEX, +}; + +pub const REPO_URL_SCHEMA: Schema = + StringSchema::new("Repository URL: [[auth-id@]server[:port]:]datastore") + .format(&BACKUP_REPO_URL) + .max_length(256) + .schema(); + +pub const BACKUP_REPO_SERVER_SCHEMA: Schema = + StringSchema::new("Backup server address (hostname or IP). Default: localhost") + .format(&api_types::DNS_NAME_OR_IP_FORMAT) + .max_length(256) + .schema(); + +pub const BACKUP_REPO_PORT_SCHEMA: Schema = IntegerSchema::new("Backup server port. Default: 8007") + .minimum(1) + .maximum(65535) + .default(8007) + .schema(); + +#[api( + properties: { + repository: { + schema: REPO_URL_SCHEMA, + optional: true, + }, + server: { + schema: BACKUP_REPO_SERVER_SCHEMA, + optional: true, + }, + port: { + schema: BACKUP_REPO_PORT_SCHEMA, + optional: true, + }, + datastore: { + schema: DATASTORE_SCHEMA, + optional: true, + }, + "auth-id": { + type: Authid, + optional: true, + }, + }, +)] +#[derive(Default, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +/// Backup repository location, specified either as a repository URL or as individual components +/// (server, port, datastore, auth-id). +pub struct BackupRepositoryArgs { + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub server: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub port: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub datastore: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_id: Option, +} + +impl BackupRepositoryArgs { + /// Returns `true` if any atom parameter (server, port, datastore, or auth-id) is set. + pub fn has_atoms(&self) -> bool { + self.server.is_some() + || self.port.is_some() + || self.datastore.is_some() + || self.auth_id.is_some() + } +} + +impl TryFrom for BackupRepository { + type Error = anyhow::Error; + + /// Convert explicit CLI arguments into a [`BackupRepository`]. + /// + /// * If `repository` and any atom are both set, returns an error. + /// * If atoms are present, builds the repository from them (requires `datastore`). + /// * If only `repository` is set, parses the repo URL. + /// * If nothing is set, returns an error - callers must fall back to environment variables / + /// credentials themselves. + fn try_from(args: BackupRepositoryArgs) -> Result { + let has_url = args.repository.is_some(); + let has_atoms = args.has_atoms(); + + if has_url && has_atoms { + bail!("--repository and --server/--port/--datastore/--auth-id are mutually exclusive"); + } + + if has_atoms { + let store = args.datastore.ok_or_else(|| { + format_err!("--datastore is required when not using --repository") + })?; + return Ok(BackupRepository::new( + args.auth_id, + args.server, + args.port, + store, + )); + } + + if let Some(url) = args.repository { + return url.parse(); + } + + anyhow::bail!("no repository specified") + } +} /// Reference remote backup locations /// diff --git a/pbs-client/src/tools/mod.rs b/pbs-client/src/tools/mod.rs index 7a496d14c..ad83d8cab 100644 --- a/pbs-client/src/tools/mod.rs +++ b/pbs-client/src/tools/mod.rs @@ -17,12 +17,14 @@ use proxmox_router::cli::{complete_file_name, shellword_split}; use proxmox_schema::*; use proxmox_sys::fs::file_get_json; -use pbs_api_types::{ - Authid, BackupArchiveName, BackupNamespace, RateLimitConfig, UserWithTokens, BACKUP_REPO_URL, -}; +use pbs_api_types::{Authid, BackupArchiveName, BackupNamespace, RateLimitConfig, UserWithTokens}; use pbs_datastore::BackupManifest; -use crate::{BackupRepository, HttpClient, HttpClientOptions}; +use crate::{BackupRepository, BackupRepositoryArgs, HttpClient, HttpClientOptions}; + +// Re-export for backward compatibility; the canonical definition is now in +// backup_repo.rs alongside BackupRepositoryArgs. +pub use crate::REPO_URL_SCHEMA; pub mod key_source; @@ -30,6 +32,10 @@ const ENV_VAR_PBS_FINGERPRINT: &str = "PBS_FINGERPRINT"; const ENV_VAR_PBS_PASSWORD: &str = "PBS_PASSWORD"; const ENV_VAR_PBS_ENCRYPTION_PASSWORD: &str = "PBS_ENCRYPTION_PASSWORD"; const ENV_VAR_PBS_REPOSITORY: &str = "PBS_REPOSITORY"; +const ENV_VAR_PBS_SERVER: &str = "PBS_SERVER"; +const ENV_VAR_PBS_PORT: &str = "PBS_PORT"; +const ENV_VAR_PBS_DATASTORE: &str = "PBS_DATASTORE"; +const ENV_VAR_PBS_AUTH_ID: &str = "PBS_AUTH_ID"; /// Directory with system [credential]s. See systemd-creds(1). /// @@ -44,11 +50,6 @@ const CRED_PBS_REPOSITORY: &str = "proxmox-backup-client.repository"; /// Credential name of the the fingerprint. const CRED_PBS_FINGERPRINT: &str = "proxmox-backup-client.fingerprint"; -pub const REPO_URL_SCHEMA: Schema = StringSchema::new("Repository URL.") - .format(&BACKUP_REPO_URL) - .max_length(256) - .schema(); - pub const CHUNK_SIZE_SCHEMA: Schema = IntegerSchema::new("Chunk size in KB. Must be a power of 2.") .minimum(64) .maximum(4096) @@ -233,41 +234,131 @@ pub fn get_fingerprint() -> Option { .unwrap_or_default() } +/// Build [`BackupRepositoryArgs`] from the fields in a JSON Value. +fn args_from_value(param: &Value) -> BackupRepositoryArgs { + BackupRepositoryArgs { + repository: param["repository"].as_str().map(String::from), + server: param["server"].as_str().map(String::from), + port: param["port"].as_u64().map(|p| p as u16), + datastore: param["datastore"].as_str().map(String::from), + auth_id: param["auth-id"] + .as_str() + .and_then(|s| s.parse::().ok()), + } +} + +/// Build [`BackupRepositoryArgs`] from `PBS_*` environment variables. +fn args_from_env() -> BackupRepositoryArgs { + BackupRepositoryArgs { + repository: None, + server: std::env::var(ENV_VAR_PBS_SERVER).ok(), + port: std::env::var(ENV_VAR_PBS_PORT) + .ok() + .and_then(|p| p.parse::().ok()), + datastore: std::env::var(ENV_VAR_PBS_DATASTORE).ok(), + auth_id: std::env::var(ENV_VAR_PBS_AUTH_ID) + .ok() + .and_then(|s| s.parse::().ok()), + } +} + +/// Remove repository-related keys from a JSON Value and return the parsed [`BackupRepository`]. +/// +/// This is used by commands that forward the remaining parameters to the server API after stripping +/// the repository fields. pub fn remove_repository_from_value(param: &mut Value) -> Result { - if let Some(url) = param + let map = param .as_object_mut() - .ok_or_else(|| format_err!("unable to get repository (parameter is not an object)"))? - .remove("repository") - { - return url - .as_str() - .ok_or_else(|| format_err!("invalid repository value (must be a string)"))? - .parse(); + .ok_or_else(|| format_err!("unable to get repository (parameter is not an object)"))?; + + let to_string = |v: Value| v.as_str().map(String::from); + + let args = BackupRepositoryArgs { + repository: map.remove("repository").and_then(to_string), + server: map.remove("server").and_then(to_string), + port: map + .remove("port") + .and_then(|v| v.as_u64()) + .map(|p| p as u16), + datastore: map.remove("datastore").and_then(to_string), + auth_id: map + .remove("auth-id") + .and_then(to_string) + .map(|s| s.parse::()) + .transpose()?, + }; + + match BackupRepository::try_from(args) { + Ok(repo) => Ok(repo), + Err(_) => { + // Fall back to environment. + if let Some(url) = get_default_repository() { + return url.parse(); + } + let env_args = args_from_env(); + if env_args.has_atoms() { + return BackupRepository::try_from(env_args); + } + bail!("repository not passed via CLI options and unable to get (default) repository from environment"); + } } - - get_default_repository() - .ok_or_else(|| format_err!("unable to get default repository"))? - .parse() } +/// Extract a [`BackupRepository`] from CLI parameters. +/// +/// Resolution precedence: +/// 1. CLI `--repository` + CLI atoms → error (mutually exclusive) +/// 2. CLI atoms (at least `--datastore`) +/// 3. CLI `--repository` +/// 4. `PBS_REPOSITORY` environment variable / systemd credential +/// 5. `PBS_SERVER`/`PBS_PORT`/`PBS_DATASTORE`/`PBS_AUTH_ID` environment variables pub fn extract_repository_from_value(param: &Value) -> Result { - let repo_url = param["repository"] - .as_str() - .map(String::from) - .or_else(get_default_repository) - .ok_or_else(|| format_err!("unable to get (default) repository"))?; + let args = args_from_value(param); - let repo: BackupRepository = repo_url.parse()?; - - Ok(repo) + match BackupRepository::try_from(args) { + Ok(repo) => Ok(repo), + Err(_) => { + // Fall back to environment. + if let Some(url) = get_default_repository() { + return url.parse(); + } + let env_args = args_from_env(); + if env_args.has_atoms() { + return BackupRepository::try_from(env_args); + } + bail!("unable to get (default) repository"); + } + } } +/// Extract a [`BackupRepository`] from a parameter map (used for shell +/// completion callbacks). pub fn extract_repository_from_map(param: &HashMap) -> Option { - param - .get("repository") - .map(String::from) - .or_else(get_default_repository) - .and_then(|repo_url| repo_url.parse::().ok()) + let args = BackupRepositoryArgs { + repository: param.get("repository").cloned(), + server: param.get("server").cloned(), + port: param.get("port").and_then(|p| p.parse().ok()), + datastore: param.get("datastore").cloned(), + auth_id: param.get("auth-id").and_then(|s| s.parse().ok()), + }; + + if let Ok(repo) = BackupRepository::try_from(args) { + return Some(repo); + } + + // Fall back to environment: compound URL, then atoms. + if let Some(url) = get_default_repository() { + if let Ok(repo) = url.parse() { + return Some(repo); + } + } + + let env_args = args_from_env(); + if env_args.has_atoms() { + return BackupRepository::try_from(env_args).ok(); + } + + None } pub fn connect(repo: &BackupRepository) -> Result { diff --git a/proxmox-backup-client/src/benchmark.rs b/proxmox-backup-client/src/benchmark.rs index ad8c60ed9..c113e2ca2 100644 --- a/proxmox-backup-client/src/benchmark.rs +++ b/proxmox-backup-client/src/benchmark.rs @@ -22,7 +22,7 @@ use pbs_key_config::{load_and_decrypt_key, KeyDerivationConfig}; use pbs_tools::crypt_config::CryptConfig; use crate::{ - connect, extract_repository_from_value, record_repository, KEYFILE_SCHEMA, REPO_URL_SCHEMA, + connect, extract_repository_from_value, record_repository, BackupRepositoryArgs, KEYFILE_SCHEMA, }; #[api()] @@ -105,9 +105,9 @@ static BENCHMARK_RESULT_2020_TOP: BenchmarkResult = BenchmarkResult { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, keyfile: { schema: KEYFILE_SCHEMA, diff --git a/proxmox-backup-client/src/catalog.rs b/proxmox-backup-client/src/catalog.rs index b1b22ff24..50d8e997a 100644 --- a/proxmox-backup-client/src/catalog.rs +++ b/proxmox-backup-client/src/catalog.rs @@ -20,16 +20,16 @@ use crate::{ complete_backup_snapshot, complete_group_or_snapshot, complete_namespace, complete_pxar_archive_name, complete_repository, connect, crypto_parameters, decrypt_key, dir_or_last_from_group, extract_repository_from_value, format_key_source, optional_ns_param, - record_repository, BackupDir, BufferedDynamicReader, CatalogReader, DynamicIndexReader, - IndexFile, Shell, KEYFD_SCHEMA, REPO_URL_SCHEMA, + record_repository, BackupDir, BackupRepositoryArgs, BufferedDynamicReader, CatalogReader, + DynamicIndexReader, IndexFile, Shell, KEYFD_SCHEMA, }; #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -170,9 +170,9 @@ async fn dump_catalog(param: Value) -> Result { "archive-name": { type: BackupArchiveName, }, - "repository": { - optional: true, - schema: REPO_URL_SCHEMA, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "keyfile": { optional: true, diff --git a/proxmox-backup-client/src/group.rs b/proxmox-backup-client/src/group.rs index 2c12db2ee..5a3786f07 100644 --- a/proxmox-backup-client/src/group.rs +++ b/proxmox-backup-client/src/group.rs @@ -6,7 +6,7 @@ use proxmox_schema::api; use crate::{ complete_backup_group, complete_namespace, complete_repository, merge_group_into, - REPO_URL_SCHEMA, + BackupRepositoryArgs, }; use pbs_api_types::{BackupGroup, BackupNamespace}; use pbs_client::tools::{connect, remove_repository_from_value}; @@ -29,9 +29,9 @@ pub fn group_mgmt_cli() -> CliCommandMap { type: String, description: "Backup group", }, - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, diff --git a/proxmox-backup-client/src/main.rs b/proxmox-backup-client/src/main.rs index 2ada87bdd..55fef0cca 100644 --- a/proxmox-backup-client/src/main.rs +++ b/proxmox-backup-client/src/main.rs @@ -41,13 +41,13 @@ use pbs_client::tools::{ crypto_parameters, format_key_source, get_encryption_key_password, KEYFD_SCHEMA, KEYFILE_SCHEMA, MASTER_PUBKEY_FD_SCHEMA, MASTER_PUBKEY_FILE_SCHEMA, }, - raise_nofile_limit, CHUNK_SIZE_SCHEMA, REPO_URL_SCHEMA, + raise_nofile_limit, CHUNK_SIZE_SCHEMA, }; use pbs_client::{ delete_ticket_info, parse_backup_specification, view_task_result, BackupDetectionMode, - BackupReader, BackupRepository, BackupSpecificationType, BackupStats, BackupWriter, - BackupWriterOptions, ChunkStream, FixedChunkStream, HttpClient, IndexType, InjectionData, - PxarBackupStream, RemoteChunkReader, UploadOptions, BACKUP_SOURCE_SCHEMA, + BackupReader, BackupRepository, BackupRepositoryArgs, BackupSpecificationType, BackupStats, + BackupWriter, BackupWriterOptions, ChunkStream, FixedChunkStream, HttpClient, IndexType, + InjectionData, PxarBackupStream, RemoteChunkReader, UploadOptions, BACKUP_SOURCE_SCHEMA, }; use pbs_datastore::catalog::{BackupCatalogWriter, CatalogReader, CatalogWriter}; use pbs_datastore::chunk_store::verify_chunk_size; @@ -340,9 +340,9 @@ pub fn optional_ns_param(param: &Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "ns": { type: BackupNamespace, @@ -433,9 +433,9 @@ fn merge_group_into(to: &mut serde_json::Map, group: BackupGroup) #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, group: { type: String, @@ -478,9 +478,9 @@ async fn change_backup_owner(group: String, mut param: Value) -> Result<(), Erro #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, } } @@ -500,9 +500,9 @@ async fn api_login(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, } } @@ -519,9 +519,9 @@ fn api_logout(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "output-format": { schema: OUTPUT_FORMAT, @@ -572,9 +572,9 @@ async fn api_version(param: Value) -> Result<(), Error> { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "output-format": { schema: OUTPUT_FORMAT, @@ -662,9 +662,9 @@ fn spawn_catalog_upload( schema: BACKUP_SOURCE_SCHEMA, } }, - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "include-dev": { description: @@ -1439,9 +1439,9 @@ async fn dump_image( #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -1837,9 +1837,9 @@ async fn restore( default: false, description: "Minimal output - only show removals.", }, - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, }, }, @@ -1929,9 +1929,9 @@ async fn prune( #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "output-format": { schema: OUTPUT_FORMAT, diff --git a/proxmox-backup-client/src/mount.rs b/proxmox-backup-client/src/mount.rs index e815c8a9c..bc666a18f 100644 --- a/proxmox-backup-client/src/mount.rs +++ b/proxmox-backup-client/src/mount.rs @@ -17,11 +17,12 @@ use proxmox_router::{cli::*, ApiHandler, ApiMethod, RpcEnvironment}; use proxmox_schema::*; use proxmox_sortable_macro::sortable; -use pbs_api_types::{ArchiveType, BackupArchiveName, BackupNamespace}; +use pbs_api_types::{ArchiveType, Authid, BackupArchiveName, BackupNamespace, DATASTORE_SCHEMA}; use pbs_client::tools::key_source::{ crypto_parameters, format_key_source, get_encryption_key_password, }; use pbs_client::{BackupReader, RemoteChunkReader}; +use pbs_client::{BACKUP_REPO_PORT_SCHEMA, BACKUP_REPO_SERVER_SCHEMA, REPO_URL_SCHEMA}; use pbs_datastore::cached_chunk_reader::CachedChunkReader; use pbs_datastore::index::IndexFile; use pbs_key_config::decrypt_key; @@ -32,7 +33,7 @@ use crate::helper; use crate::{ complete_group_or_snapshot, complete_img_archive_name, complete_namespace, complete_pxar_archive_name, complete_repository, connect, dir_or_last_from_group, - extract_repository_from_value, optional_ns_param, record_repository, REPO_URL_SCHEMA, + extract_repository_from_value, optional_ns_param, record_repository, }; #[sortable] @@ -54,6 +55,10 @@ const API_METHOD_MOUNT: ApiMethod = ApiMethod::new( &StringSchema::new("Target directory path.").schema() ), ("repository", true, &REPO_URL_SCHEMA), + ("server", true, &BACKUP_REPO_SERVER_SCHEMA), + ("port", true, &BACKUP_REPO_PORT_SCHEMA), + ("datastore", true, &DATASTORE_SCHEMA), + ("auth-id", true, &Authid::API_SCHEMA), ( "keyfile", true, @@ -85,6 +90,10 @@ WARNING: Only do this with *trusted* backups!", ), ("archive-name", false, &BackupArchiveName::API_SCHEMA), ("repository", true, &REPO_URL_SCHEMA), + ("server", true, &BACKUP_REPO_SERVER_SCHEMA), + ("port", true, &BACKUP_REPO_PORT_SCHEMA), + ("datastore", true, &DATASTORE_SCHEMA), + ("auth-id", true, &Authid::API_SCHEMA), ( "keyfile", true, diff --git a/proxmox-backup-client/src/namespace.rs b/proxmox-backup-client/src/namespace.rs index 2929e394b..53d4c31af 100644 --- a/proxmox-backup-client/src/namespace.rs +++ b/proxmox-backup-client/src/namespace.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Error}; use serde_json::{json, Value}; use pbs_api_types::BackupNamespace; -use pbs_client::tools::REPO_URL_SCHEMA; +use pbs_client::BackupRepositoryArgs; use proxmox_router::cli::{ format_and_print_result, get_output_format, CliCommand, CliCommandMap, OUTPUT_FORMAT, @@ -17,9 +17,9 @@ use crate::{ #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -84,9 +84,9 @@ async fn list_namespaces(param: Value, max_depth: Option) -> Result<(), E #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -124,9 +124,9 @@ async fn create_namespace(param: Value) -> Result<(), Error> { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, diff --git a/proxmox-backup-client/src/snapshot.rs b/proxmox-backup-client/src/snapshot.rs index 500fd2bb8..7c7217891 100644 --- a/proxmox-backup-client/src/snapshot.rs +++ b/proxmox-backup-client/src/snapshot.rs @@ -17,8 +17,8 @@ use pbs_tools::json::required_string_param; use crate::{ api_datastore_list_snapshots, complete_backup_group, complete_backup_snapshot, complete_namespace, complete_repository, connect, crypto_parameters, - extract_repository_from_value, optional_ns_param, record_repository, BackupDir, KEYFD_SCHEMA, - KEYFILE_SCHEMA, REPO_URL_SCHEMA, + extract_repository_from_value, optional_ns_param, record_repository, BackupDir, + BackupRepositoryArgs, KEYFD_SCHEMA, KEYFILE_SCHEMA, }; fn snapshot_args(ns: &BackupNamespace, snapshot: &BackupDir) -> Result { @@ -32,9 +32,9 @@ fn snapshot_args(ns: &BackupNamespace, snapshot: &BackupDir) -> Result Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -161,9 +161,9 @@ async fn list_snapshot_files(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -200,9 +200,9 @@ async fn forget_snapshots(param: Value) -> Result<(), Error> { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -281,9 +281,9 @@ async fn upload_log(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -338,9 +338,9 @@ async fn show_notes(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -380,9 +380,9 @@ async fn update_notes(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -437,9 +437,9 @@ async fn show_protection(param: Value) -> Result<(), Error> { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, diff --git a/proxmox-backup-client/src/task.rs b/proxmox-backup-client/src/task.rs index 472db0860..dba50cc85 100644 --- a/proxmox-backup-client/src/task.rs +++ b/proxmox-backup-client/src/task.rs @@ -10,14 +10,14 @@ use pbs_tools::json::required_string_param; use pbs_api_types::UPID; -use crate::{complete_repository, connect, extract_repository_from_value, REPO_URL_SCHEMA}; +use crate::{complete_repository, connect, extract_repository_from_value, BackupRepositoryArgs}; #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, limit: { description: "The maximal number of tasks to list.", @@ -87,9 +87,9 @@ async fn task_list(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, upid: { type: UPID, @@ -112,9 +112,9 @@ async fn task_log(param: Value) -> Result { #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, upid: { type: UPID, diff --git a/proxmox-file-restore/src/main.rs b/proxmox-file-restore/src/main.rs index bf6cf9aed..99685bafc 100644 --- a/proxmox-file-restore/src/main.rs +++ b/proxmox-file-restore/src/main.rs @@ -31,9 +31,8 @@ use pbs_client::tools::{ crypto_parameters_keep_fd, format_key_source, get_encryption_key_password, KEYFD_SCHEMA, KEYFILE_SCHEMA, }, - REPO_URL_SCHEMA, }; -use pbs_client::{BackupReader, BackupRepository, RemoteChunkReader}; +use pbs_client::{BackupReader, BackupRepository, BackupRepositoryArgs, RemoteChunkReader}; use pbs_datastore::catalog::{ArchiveEntry, CatalogReader, DirEntryAttribute}; use pbs_datastore::dynamic_index::BufferedDynamicReader; use pbs_datastore::index::IndexFile; @@ -212,9 +211,9 @@ async fn list_files( #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, @@ -361,9 +360,9 @@ async fn list( #[api( input: { properties: { - repository: { - schema: REPO_URL_SCHEMA, - optional: true, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, ns: { type: BackupNamespace, diff --git a/src/bin/proxmox_backup_debug/diff.rs b/src/bin/proxmox_backup_debug/diff.rs index 116216e51..f13cc6ab5 100644 --- a/src/bin/proxmox_backup_debug/diff.rs +++ b/src/bin/proxmox_backup_debug/diff.rs @@ -19,9 +19,8 @@ use pbs_client::tools::key_source::{ }; use pbs_client::tools::{ complete_archive_name, complete_group_or_snapshot, connect, extract_repository_from_value, - REPO_URL_SCHEMA, }; -use pbs_client::{BackupReader, BackupRepository, RemoteChunkReader}; +use pbs_client::{BackupReader, BackupRepository, BackupRepositoryArgs, RemoteChunkReader}; use pbs_datastore::dynamic_index::{BufferedDynamicReader, DynamicIndexReader, LocalDynamicReadAt}; use pbs_datastore::index::IndexFile; use pbs_key_config::decrypt_key; @@ -72,9 +71,9 @@ pub fn diff_commands() -> CommandLineInterface { "archive-name": { type: BackupArchiveName, }, - "repository": { - optional: true, - schema: REPO_URL_SCHEMA, + repo: { + type: BackupRepositoryArgs, + flatten: true, }, "keyfile": { optional: true, -- 2.47.3