Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
97cd0a2a6d | |||
49a92084a9 | |||
9bdeecaee4 | |||
843880f008 | |||
a6ed5e1273 | |||
74f94d0678 | |||
946c3e8a81 | |||
7b212c1f79 | |||
3b2046d263 | |||
1ffe030123 | |||
5255e641fa | |||
c86b6f40d7 | |||
5a718dce17 | |||
1b32750644 | |||
5aa103c3c3 | |||
fd3f690104 | |||
24b638bd9f | |||
9624c5eecb | |||
503dd339a8 | |||
36ea5df444 | |||
dce9dd6f70 | |||
88e28e15e4 |
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "proxmox-backup"
|
name = "proxmox-backup"
|
||||||
version = "0.8.12"
|
version = "0.8.13"
|
||||||
authors = ["Dietmar Maurer <dietmar@proxmox.com>"]
|
authors = ["Dietmar Maurer <dietmar@proxmox.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "AGPL-3"
|
license = "AGPL-3"
|
||||||
@ -43,7 +43,7 @@ proxmox = { version = "0.3.3", features = [ "sortable-macro", "api-macro", "webs
|
|||||||
#proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] }
|
#proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] }
|
||||||
#proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro", "websocket" ] }
|
#proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro", "websocket" ] }
|
||||||
proxmox-fuse = "0.1.0"
|
proxmox-fuse = "0.1.0"
|
||||||
pxar = { version = "0.4.0", features = [ "tokio-io", "futures-io" ] }
|
pxar = { version = "0.6.0", features = [ "tokio-io", "futures-io" ] }
|
||||||
#pxar = { path = "../pxar", features = [ "tokio-io", "futures-io" ] }
|
#pxar = { path = "../pxar", features = [ "tokio-io", "futures-io" ] }
|
||||||
regex = "1.2"
|
regex = "1.2"
|
||||||
rustyline = "6"
|
rustyline = "6"
|
||||||
|
10
debian/changelog
vendored
10
debian/changelog
vendored
@ -1,3 +1,13 @@
|
|||||||
|
rust-proxmox-backup (0.8.13-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* improve and add to documentation
|
||||||
|
|
||||||
|
* save last verify result in snapshot manifest and show it in the GUI
|
||||||
|
|
||||||
|
* gc: use human readable units for summary in task log
|
||||||
|
|
||||||
|
-- Proxmox Support Team <support@proxmox.com> Thu, 27 Aug 2020 16:12:07 +0200
|
||||||
|
|
||||||
rust-proxmox-backup (0.8.12-1) unstable; urgency=medium
|
rust-proxmox-backup (0.8.12-1) unstable; urgency=medium
|
||||||
|
|
||||||
* verify: speedup - only verify chunks once
|
* verify: speedup - only verify chunks once
|
||||||
|
6
debian/control
vendored
6
debian/control
vendored
@ -39,9 +39,9 @@ Build-Depends: debhelper (>= 11),
|
|||||||
librust-proxmox-0.3+sortable-macro-dev (>= 0.3.3-~~),
|
librust-proxmox-0.3+sortable-macro-dev (>= 0.3.3-~~),
|
||||||
librust-proxmox-0.3+websocket-dev (>= 0.3.3-~~),
|
librust-proxmox-0.3+websocket-dev (>= 0.3.3-~~),
|
||||||
librust-proxmox-fuse-0.1+default-dev,
|
librust-proxmox-fuse-0.1+default-dev,
|
||||||
librust-pxar-0.3+default-dev,
|
librust-pxar-0.6+default-dev,
|
||||||
librust-pxar-0.3+futures-io-dev,
|
librust-pxar-0.6+futures-io-dev,
|
||||||
librust-pxar-0.3+tokio-io-dev,
|
librust-pxar-0.6+tokio-io-dev,
|
||||||
librust-regex-1+default-dev (>= 1.2-~~),
|
librust-regex-1+default-dev (>= 1.2-~~),
|
||||||
librust-rustyline-6+default-dev,
|
librust-rustyline-6+default-dev,
|
||||||
librust-serde-1+default-dev,
|
librust-serde-1+default-dev,
|
||||||
|
@ -146,6 +146,74 @@ when setting up the backup server.
|
|||||||
filesystem configuration from being supported for a datastore. For example,
|
filesystem configuration from being supported for a datastore. For example,
|
||||||
``ext3`` as a whole or ``ext4`` with the ``dir_nlink`` feature manually disabled.
|
``ext3`` as a whole or ``ext4`` with the ``dir_nlink`` feature manually disabled.
|
||||||
|
|
||||||
|
Disk Management
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
Proxmox Backup Server comes with a set of disk utilities, which are
|
||||||
|
accessed using the ``disk`` subcommand. This subcommand allows you to initialize
|
||||||
|
disks, create various filesystems, and get information about the disks.
|
||||||
|
|
||||||
|
To view the disks connected to the system, use the ``list`` subcommand of
|
||||||
|
``disk``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager disk list
|
||||||
|
┌──────┬────────┬─────┬───────────┬─────────────┬───────────────┬─────────┬────────┐
|
||||||
|
│ name │ used │ gpt │ disk-type │ size │ model │ wearout │ status │
|
||||||
|
╞══════╪════════╪═════╪═══════════╪═════════════╪═══════════════╪═════════╪════════╡
|
||||||
|
│ sda │ lvm │ 1 │ hdd │ 34359738368 │ QEMU_HARDDISK │ - │ passed │
|
||||||
|
├──────┼────────┼─────┼───────────┼─────────────┼───────────────┼─────────┼────────┤
|
||||||
|
│ sdb │ unused │ 1 │ hdd │ 68719476736 │ QEMU_HARDDISK │ - │ passed │
|
||||||
|
├──────┼────────┼─────┼───────────┼─────────────┼───────────────┼─────────┼────────┤
|
||||||
|
│ sdc │ unused │ 1 │ hdd │ 68719476736 │ QEMU_HARDDISK │ - │ passed │
|
||||||
|
└──────┴────────┴─────┴───────────┴─────────────┴───────────────┴─────────┴────────┘
|
||||||
|
|
||||||
|
To initialize a disk with a new GPT, use the ``initialize`` subcommand:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager disk initialize sdX
|
||||||
|
|
||||||
|
You can create an ``ext4`` or ``xfs`` filesystem on a disk, using ``fs
|
||||||
|
create``. The following command creates an ``ext4`` filesystem and passes the
|
||||||
|
``--add-datastore`` parameter, in order to automatically create a datastore on
|
||||||
|
the disk (in this case ``sdd``). This will create a datastore at the location
|
||||||
|
``/mnt/datastore/store1``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager disk fs create store1 --disk sdd --filesystem ext4 --add-datastore true
|
||||||
|
create datastore 'store1' on disk sdd
|
||||||
|
Percentage done: 1
|
||||||
|
...
|
||||||
|
Percentage done: 99
|
||||||
|
TASK OK
|
||||||
|
|
||||||
|
You can also create a ``zpool`` with various raid levels. The command below
|
||||||
|
creates a mirrored ``zpool`` using two disks (``sdb`` & ``sdc``) and mounts it
|
||||||
|
on the root directory (default):
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager disk zpool create zpool1 --devices sdb,sdc --raidlevel mirror
|
||||||
|
create Mirror zpool 'zpool1' on devices 'sdb,sdc'
|
||||||
|
# "zpool" "create" "-o" "ashift=12" "zpool1" "mirror" "sdb" "sdc"
|
||||||
|
|
||||||
|
TASK OK
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You can also pass the ``--add-datastore`` parameter here, to automatically
|
||||||
|
create a datastore from the disk.
|
||||||
|
|
||||||
|
You can use ``disk fs list`` and ``disk zpool list`` to keep track of your
|
||||||
|
filesystems and zpools respectively.
|
||||||
|
|
||||||
|
If a disk supports S.M.A.R.T. capability, and you have this enabled, you can
|
||||||
|
display S.M.A.R.T. attributes using the command:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager disk smart-attributes sdX
|
||||||
|
|
||||||
Datastore Configuration
|
Datastore Configuration
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -404,6 +472,72 @@ A single user can be assigned multiple permission sets for different data stores
|
|||||||
remote (see `Remote` below) and ``{storename}`` is the name of the data store on
|
remote (see `Remote` below) and ``{storename}`` is the name of the data store on
|
||||||
the remote.
|
the remote.
|
||||||
|
|
||||||
|
Network Management
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
Proxmox Backup Server provides an interface for network configuration, through the
|
||||||
|
``network`` subcommand. This allows you to carry out some basic network
|
||||||
|
management tasks such as adding, configuring and removing network interfaces.
|
||||||
|
|
||||||
|
To get a list of available interfaces, use the following command:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network list
|
||||||
|
┌───────┬────────┬───────────┬────────┬─────────┬───────────────────┬──────────────┬──────────────┐
|
||||||
|
│ name │ type │ autostart │ method │ method6 │ address │ gateway │ ports/slaves │
|
||||||
|
╞═══════╪════════╪═══════════╪════════╪═════════╪═══════════════════╪══════════════╪══════════════╡
|
||||||
|
│ bond0 │ bond │ 1 │ manual │ │ │ │ ens18 ens19 │
|
||||||
|
├───────┼────────┼───────────┼────────┼─────────┼───────────────────┼──────────────┼──────────────┤
|
||||||
|
│ ens18 │ eth │ 1 │ manual │ │ │ │ │
|
||||||
|
├───────┼────────┼───────────┼────────┼─────────┼───────────────────┼──────────────┼──────────────┤
|
||||||
|
│ ens19 │ eth │ 1 │ manual │ │ │ │ │
|
||||||
|
├───────┼────────┼───────────┼────────┼─────────┼───────────────────┼──────────────┼──────────────┤
|
||||||
|
│ vmbr0 │ bridge │ 1 │ static │ │ x.x.x.x/x │ x.x.x.x │ bond0 │
|
||||||
|
└───────┴────────┴───────────┴────────┴─────────┴───────────────────┴──────────────┴──────────────┘
|
||||||
|
|
||||||
|
To add a new network interface, use the ``create`` subcommand with the relevant
|
||||||
|
parameters. The following command shows a template for creating a new bridge:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network create vmbr1 --autostart true --cidr x.x.x.x/x --gateway x.x.x.x --bridge_ports iface_name --type bridge
|
||||||
|
|
||||||
|
You can make changes to the configuration of a network interface with the
|
||||||
|
``update`` subcommand:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network update vmbr1 --cidr y.y.y.y/y
|
||||||
|
|
||||||
|
You can also remove a network interface:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network remove vmbr1
|
||||||
|
|
||||||
|
To view the changes made to the network configuration file, before committing
|
||||||
|
them, use the command:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network changes
|
||||||
|
|
||||||
|
If you would like to cancel all changes at this point, you can do this using:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network revert
|
||||||
|
|
||||||
|
If you are happy with the changes and would like to write them into the
|
||||||
|
configuration file, the command is:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
# proxmox-backup-manager network reload
|
||||||
|
|
||||||
|
You can also configure DNS settings using the ``dns`` subcommand of
|
||||||
|
``proxmox-backup-manager``.
|
||||||
|
|
||||||
:term:`Remote`
|
:term:`Remote`
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -461,6 +595,14 @@ provide it with a :term:`schedule` to run regularly. The
|
|||||||
└────────────┴───────┴────────┴──────────────┴───────────┴─────────┘
|
└────────────┴───────┴────────┴──────────────┴───────────┴─────────┘
|
||||||
# proxmox-backup-manager sync-job remove pbs2-local
|
# proxmox-backup-manager sync-job remove pbs2-local
|
||||||
|
|
||||||
|
Garbage Collection
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
You can monitor and run :ref:`garbage collection <garbage-collection>` on the
|
||||||
|
Proxmox Backup Server using the ``garbage-collection`` subcommand of
|
||||||
|
``proxmox-backup-manager``. You can use the ``start`` subcommand to manually start garbage
|
||||||
|
collection on an entire data store and the ``status`` subcommand to see
|
||||||
|
attributes relating to the :ref:`garbage collection <garbage-collection>`.
|
||||||
|
|
||||||
|
|
||||||
Backup Client usage
|
Backup Client usage
|
||||||
-------------------
|
-------------------
|
||||||
@ -1143,6 +1285,10 @@ benchmark using the ``benchmark`` subcommand of ``proxmox-backup-client``:
|
|||||||
│ AES256 GCM encryption speed │ 3974.03 MB/s (104%) │
|
│ AES256 GCM encryption speed │ 3974.03 MB/s (104%) │
|
||||||
└───────────────────────────────────┴─────────────────────┘
|
└───────────────────────────────────┴─────────────────────┘
|
||||||
|
|
||||||
|
.. note:: The percentages given in the output table correspond to a
|
||||||
|
comparison against a Ryzen 7 2700X. The TLS test connects to the
|
||||||
|
local host, so there is no network involved.
|
||||||
|
|
||||||
You can also pass the ``--output-format`` parameter to output stats in ``json``,
|
You can also pass the ``--output-format`` parameter to output stats in ``json``,
|
||||||
rather than the default table format.
|
rather than the default table format.
|
||||||
|
|
||||||
|
@ -361,7 +361,7 @@ pub fn list_snapshots (
|
|||||||
|
|
||||||
let mut size = None;
|
let mut size = None;
|
||||||
|
|
||||||
let (comment, files) = match get_all_snapshot_files(&datastore, &info) {
|
let (comment, verification, files) = match get_all_snapshot_files(&datastore, &info) {
|
||||||
Ok((manifest, files)) => {
|
Ok((manifest, files)) => {
|
||||||
size = Some(files.iter().map(|x| x.size.unwrap_or(0)).sum());
|
size = Some(files.iter().map(|x| x.size.unwrap_or(0)).sum());
|
||||||
// extract the first line from notes
|
// extract the first line from notes
|
||||||
@ -370,11 +370,21 @@ pub fn list_snapshots (
|
|||||||
.and_then(|notes| notes.lines().next())
|
.and_then(|notes| notes.lines().next())
|
||||||
.map(String::from);
|
.map(String::from);
|
||||||
|
|
||||||
(comment, files)
|
let verify = manifest.unprotected["verify_state"].clone();
|
||||||
|
let verify: Option<SnapshotVerifyState> = match serde_json::from_value(verify) {
|
||||||
|
Ok(verify) => verify,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("error parsing verification state : '{}'", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(comment, verify, files)
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("error during snapshot file listing: '{}'", err);
|
eprintln!("error during snapshot file listing: '{}'", err);
|
||||||
(
|
(
|
||||||
|
None,
|
||||||
None,
|
None,
|
||||||
info
|
info
|
||||||
.files
|
.files
|
||||||
@ -394,6 +404,7 @@ pub fn list_snapshots (
|
|||||||
backup_id: group.backup_id().to_string(),
|
backup_id: group.backup_id().to_string(),
|
||||||
backup_time: info.backup_dir.backup_time().timestamp(),
|
backup_time: info.backup_dir.backup_time().timestamp(),
|
||||||
comment,
|
comment,
|
||||||
|
verification,
|
||||||
files,
|
files,
|
||||||
size,
|
size,
|
||||||
owner: Some(owner),
|
owner: Some(owner),
|
||||||
@ -489,7 +500,7 @@ pub fn verify(
|
|||||||
(None, None, None) => {
|
(None, None, None) => {
|
||||||
worker_id = store.clone();
|
worker_id = store.clone();
|
||||||
}
|
}
|
||||||
_ => bail!("parameters do not spefify a backup group or snapshot"),
|
_ => bail!("parameters do not specify a backup group or snapshot"),
|
||||||
}
|
}
|
||||||
|
|
||||||
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||||
@ -519,7 +530,7 @@ pub fn verify(
|
|||||||
for dir in failed_dirs {
|
for dir in failed_dirs {
|
||||||
worker.log(format!("\t{}", dir));
|
worker.log(format!("\t{}", dir));
|
||||||
}
|
}
|
||||||
bail!("verfication failed - please check the log for details");
|
bail!("verification failed - please check the log for details");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
@ -1218,7 +1229,7 @@ fn catalog(
|
|||||||
pub const API_METHOD_PXAR_FILE_DOWNLOAD: ApiMethod = ApiMethod::new(
|
pub const API_METHOD_PXAR_FILE_DOWNLOAD: ApiMethod = ApiMethod::new(
|
||||||
&ApiHandler::AsyncHttp(&pxar_file_download),
|
&ApiHandler::AsyncHttp(&pxar_file_download),
|
||||||
&ObjectSchema::new(
|
&ObjectSchema::new(
|
||||||
"Download single file from pxar file of a bacup snapshot. Only works if it's not encrypted.",
|
"Download single file from pxar file of a backup snapshot. Only works if it's not encrypted.",
|
||||||
&sorted!([
|
&sorted!([
|
||||||
("store", false, &DATASTORE_SCHEMA),
|
("store", false, &DATASTORE_SCHEMA),
|
||||||
("backup-type", false, &BACKUP_TYPE_SCHEMA),
|
("backup-type", false, &BACKUP_TYPE_SCHEMA),
|
||||||
|
@ -6,6 +6,7 @@ use proxmox::const_regex;
|
|||||||
use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
|
use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
|
||||||
|
|
||||||
use crate::backup::CryptMode;
|
use crate::backup::CryptMode;
|
||||||
|
use crate::server::UPID;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
@ -379,6 +380,25 @@ pub struct GroupListItem {
|
|||||||
pub owner: Option<Userid>,
|
pub owner: Option<Userid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
upid: {
|
||||||
|
schema: UPID_SCHEMA
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
/// Task properties.
|
||||||
|
pub struct SnapshotVerifyState {
|
||||||
|
/// UPID of the verify task
|
||||||
|
pub upid: UPID,
|
||||||
|
/// State of the verification. "failed" or "ok"
|
||||||
|
pub state: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
properties: {
|
properties: {
|
||||||
"backup-type": {
|
"backup-type": {
|
||||||
@ -390,6 +410,14 @@ pub struct GroupListItem {
|
|||||||
"backup-time": {
|
"backup-time": {
|
||||||
schema: BACKUP_TIME_SCHEMA,
|
schema: BACKUP_TIME_SCHEMA,
|
||||||
},
|
},
|
||||||
|
comment: {
|
||||||
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
verification: {
|
||||||
|
type: SnapshotVerifyState,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
items: {
|
items: {
|
||||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||||
@ -411,6 +439,9 @@ pub struct SnapshotListItem {
|
|||||||
/// The first line from manifest "notes"
|
/// The first line from manifest "notes"
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
|
/// The result of the last run verify task
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub verification: Option<SnapshotVerifyState>,
|
||||||
/// List of contained archive files.
|
/// List of contained archive files.
|
||||||
pub files: Vec<BackupContent>,
|
pub files: Vec<BackupContent>,
|
||||||
/// Overall snapshot size (sum of all archive sizes).
|
/// Overall snapshot size (sum of all archive sizes).
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
//! with `String`, meaning you can only make references to it.
|
//! with `String`, meaning you can only make references to it.
|
||||||
//! * [`Realm`]: an owned realm (`String` equivalent).
|
//! * [`Realm`]: an owned realm (`String` equivalent).
|
||||||
//! * [`RealmRef`]: a borrowed realm (`str` equivalent).
|
//! * [`RealmRef`]: a borrowed realm (`str` equivalent).
|
||||||
//! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separte
|
//! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separate
|
||||||
//! borrowed type.
|
//! borrowed type.
|
||||||
//!
|
//!
|
||||||
//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
|
//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
|
||||||
|
@ -120,6 +120,8 @@ macro_rules! PROXMOX_BACKUP_READER_PROTOCOL_ID_V1 {
|
|||||||
|
|
||||||
/// Unix system user used by proxmox-backup-proxy
|
/// Unix system user used by proxmox-backup-proxy
|
||||||
pub const BACKUP_USER_NAME: &str = "backup";
|
pub const BACKUP_USER_NAME: &str = "backup";
|
||||||
|
/// Unix system group used by proxmox-backup-proxy
|
||||||
|
pub const BACKUP_GROUP_NAME: &str = "backup";
|
||||||
|
|
||||||
/// Return User info for the 'backup' user (``getpwnam_r(3)``)
|
/// Return User info for the 'backup' user (``getpwnam_r(3)``)
|
||||||
pub fn backup_user() -> Result<nix::unistd::User, Error> {
|
pub fn backup_user() -> Result<nix::unistd::User, Error> {
|
||||||
@ -129,6 +131,14 @@ pub fn backup_user() -> Result<nix::unistd::User, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return Group info for the 'backup' group (``getgrnam(3)``)
|
||||||
|
pub fn backup_group() -> Result<nix::unistd::Group, Error> {
|
||||||
|
match nix::unistd::Group::from_name(BACKUP_GROUP_NAME)? {
|
||||||
|
Some(group) => Ok(group),
|
||||||
|
None => bail!("Unable to lookup backup user."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod file_formats;
|
mod file_formats;
|
||||||
pub use file_formats::*;
|
pub use file_formats::*;
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ impl ChunkStore {
|
|||||||
}
|
}
|
||||||
let percentage = (i*100)/(64*1024);
|
let percentage = (i*100)/(64*1024);
|
||||||
if percentage != last_percentage {
|
if percentage != last_percentage {
|
||||||
eprintln!("Percentage done: {}", percentage);
|
eprintln!("{}%", percentage);
|
||||||
last_percentage = percentage;
|
last_percentage = percentage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,7 +295,7 @@ impl ChunkStore {
|
|||||||
for (entry, percentage) in self.get_chunk_iterator()? {
|
for (entry, percentage) in self.get_chunk_iterator()? {
|
||||||
if last_percentage != percentage {
|
if last_percentage != percentage {
|
||||||
last_percentage = percentage;
|
last_percentage = percentage;
|
||||||
worker.log(format!("percentage done: {}, chunk count: {}", percentage, chunk_count));
|
worker.log(format!("{}%, processed {} chunks", percentage, chunk_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.fail_on_abort()?;
|
worker.fail_on_abort()?;
|
||||||
|
@ -21,6 +21,7 @@ use super::{DataBlob, ArchiveType, archive_type};
|
|||||||
use crate::config::datastore;
|
use crate::config::datastore;
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
|
use crate::tools::format::HumanByte;
|
||||||
use crate::tools::fs::{lock_dir_noblock, DirLockGuard};
|
use crate::tools::fs::{lock_dir_noblock, DirLockGuard};
|
||||||
use crate::api2::types::{GarbageCollectionStatus, Userid};
|
use crate::api2::types::{GarbageCollectionStatus, Userid};
|
||||||
|
|
||||||
@ -299,7 +300,7 @@ impl DataStore {
|
|||||||
/// And set the owner to 'userid'. If the group already exists, it returns the
|
/// And set the owner to 'userid'. If the group already exists, it returns the
|
||||||
/// current owner (instead of setting the owner).
|
/// current owner (instead of setting the owner).
|
||||||
///
|
///
|
||||||
/// This also aquires an exclusive lock on the directory and returns the lock guard.
|
/// This also acquires an exclusive lock on the directory and returns the lock guard.
|
||||||
pub fn create_locked_backup_group(
|
pub fn create_locked_backup_group(
|
||||||
&self,
|
&self,
|
||||||
backup_group: &BackupGroup,
|
backup_group: &BackupGroup,
|
||||||
@ -462,9 +463,8 @@ impl DataStore {
|
|||||||
|
|
||||||
let _exclusive_lock = self.chunk_store.try_exclusive_lock()?;
|
let _exclusive_lock = self.chunk_store.try_exclusive_lock()?;
|
||||||
|
|
||||||
let now = unsafe { libc::time(std::ptr::null_mut()) };
|
let phase1_start_time = unsafe { libc::time(std::ptr::null_mut()) };
|
||||||
|
let oldest_writer = self.chunk_store.oldest_writer().unwrap_or(phase1_start_time);
|
||||||
let oldest_writer = self.chunk_store.oldest_writer().unwrap_or(now);
|
|
||||||
|
|
||||||
let mut gc_status = GarbageCollectionStatus::default();
|
let mut gc_status = GarbageCollectionStatus::default();
|
||||||
gc_status.upid = Some(worker.to_string());
|
gc_status.upid = Some(worker.to_string());
|
||||||
@ -474,26 +474,26 @@ impl DataStore {
|
|||||||
self.mark_used_chunks(&mut gc_status, &worker)?;
|
self.mark_used_chunks(&mut gc_status, &worker)?;
|
||||||
|
|
||||||
worker.log("Start GC phase2 (sweep unused chunks)");
|
worker.log("Start GC phase2 (sweep unused chunks)");
|
||||||
self.chunk_store.sweep_unused_chunks(oldest_writer, now, &mut gc_status, &worker)?;
|
self.chunk_store.sweep_unused_chunks(oldest_writer, phase1_start_time, &mut gc_status, &worker)?;
|
||||||
|
|
||||||
worker.log(&format!("Removed bytes: {}", gc_status.removed_bytes));
|
worker.log(&format!("Removed garbage: {}", HumanByte::from(gc_status.removed_bytes)));
|
||||||
worker.log(&format!("Removed chunks: {}", gc_status.removed_chunks));
|
worker.log(&format!("Removed chunks: {}", gc_status.removed_chunks));
|
||||||
if gc_status.pending_bytes > 0 {
|
if gc_status.pending_bytes > 0 {
|
||||||
worker.log(&format!("Pending removals: {} bytes ({} chunks)", gc_status.pending_bytes, gc_status.pending_chunks));
|
worker.log(&format!("Pending removals: {} (in {} chunks)", HumanByte::from(gc_status.pending_bytes), gc_status.pending_chunks));
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.log(&format!("Original data bytes: {}", gc_status.index_data_bytes));
|
worker.log(&format!("Original data usage: {}", HumanByte::from(gc_status.index_data_bytes)));
|
||||||
|
|
||||||
if gc_status.index_data_bytes > 0 {
|
if gc_status.index_data_bytes > 0 {
|
||||||
let comp_per = (gc_status.disk_bytes*100)/gc_status.index_data_bytes;
|
let comp_per = (gc_status.disk_bytes as f64 * 100.)/gc_status.index_data_bytes as f64;
|
||||||
worker.log(&format!("Disk bytes: {} ({} %)", gc_status.disk_bytes, comp_per));
|
worker.log(&format!("On-Disk usage: {} ({:.2}%)", HumanByte::from(gc_status.disk_bytes), comp_per));
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.log(&format!("Disk chunks: {}", gc_status.disk_chunks));
|
worker.log(&format!("On-Disk chunks: {}", gc_status.disk_chunks));
|
||||||
|
|
||||||
if gc_status.disk_chunks > 0 {
|
if gc_status.disk_chunks > 0 {
|
||||||
let avg_chunk = gc_status.disk_bytes/(gc_status.disk_chunks as u64);
|
let avg_chunk = gc_status.disk_bytes/(gc_status.disk_chunks as u64);
|
||||||
worker.log(&format!("Average chunk size: {}", avg_chunk));
|
worker.log(&format!("Average chunk size: {}", HumanByte::from(avg_chunk)));
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.last_gc_status.lock().unwrap() = gc_status;
|
*self.last_gc_status.lock().unwrap() = gc_status;
|
||||||
|
@ -145,7 +145,7 @@ impl BackupManifest {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate cannonical json
|
// Generate canonical json
|
||||||
fn to_canonical_json(value: &Value) -> Result<Vec<u8>, Error> {
|
fn to_canonical_json(value: &Value) -> Result<Vec<u8>, Error> {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
Self::write_canonical_json(value, &mut data)?;
|
Self::write_canonical_json(value, &mut data)?;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
|
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
|
use crate::api2::types::*;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DataStore, BackupGroup, BackupDir, BackupInfo, IndexFile,
|
DataStore, BackupGroup, BackupDir, BackupInfo, IndexFile,
|
||||||
@ -178,7 +179,7 @@ pub fn verify_backup_dir(
|
|||||||
worker: &WorkerTask
|
worker: &WorkerTask
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
|
|
||||||
let manifest = match datastore.load_manifest(&backup_dir) {
|
let mut manifest = match datastore.load_manifest(&backup_dir) {
|
||||||
Ok((manifest, _)) => manifest,
|
Ok((manifest, _)) => manifest,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
worker.log(format!("verify {}:{} - manifest load error: {}", datastore.name(), backup_dir, err));
|
worker.log(format!("verify {}:{} - manifest load error: {}", datastore.name(), backup_dir, err));
|
||||||
@ -190,6 +191,7 @@ pub fn verify_backup_dir(
|
|||||||
|
|
||||||
let mut error_count = 0;
|
let mut error_count = 0;
|
||||||
|
|
||||||
|
let mut verify_result = "ok";
|
||||||
for info in manifest.files() {
|
for info in manifest.files() {
|
||||||
let result = proxmox::try_block!({
|
let result = proxmox::try_block!({
|
||||||
worker.log(format!(" check {}", info.filename));
|
worker.log(format!(" check {}", info.filename));
|
||||||
@ -221,9 +223,20 @@ pub fn verify_backup_dir(
|
|||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
worker.log(format!("verify {}:{}/{} failed: {}", datastore.name(), backup_dir, info.filename, err));
|
worker.log(format!("verify {}:{}/{} failed: {}", datastore.name(), backup_dir, info.filename, err));
|
||||||
error_count += 1;
|
error_count += 1;
|
||||||
|
verify_result = "failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let verify_state = SnapshotVerifyState {
|
||||||
|
state: verify_result.to_string(),
|
||||||
|
upid: worker.upid().clone(),
|
||||||
|
};
|
||||||
|
manifest.unprotected["verify_state"] = serde_json::to_value(verify_state)?;
|
||||||
|
datastore.store_manifest(&backup_dir, serde_json::to_value(manifest)?)
|
||||||
|
.map_err(|err| format_err!("unable to store manifest blob - {}", err))?;
|
||||||
|
|
||||||
|
|
||||||
Ok(error_count == 0)
|
Ok(error_count == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,13 +20,19 @@ use proxmox_backup::tools::disks::{ DiskManage, zfs_pool_stats };
|
|||||||
|
|
||||||
use proxmox_backup::api2::pull::do_sync_job;
|
use proxmox_backup::api2::pull::do_sync_job;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), Error> {
|
||||||
proxmox_backup::tools::setup_safe_path_env();
|
proxmox_backup::tools::setup_safe_path_env();
|
||||||
|
|
||||||
if let Err(err) = proxmox_backup::tools::runtime::main(run()) {
|
let backup_uid = proxmox_backup::backup::backup_user()?.uid;
|
||||||
eprintln!("Error: {}", err);
|
let backup_gid = proxmox_backup::backup::backup_group()?.gid;
|
||||||
std::process::exit(-1);
|
let running_uid = nix::unistd::Uid::effective();
|
||||||
|
let running_gid = nix::unistd::Gid::effective();
|
||||||
|
|
||||||
|
if running_uid != backup_uid || running_gid != backup_gid {
|
||||||
|
bail!("proxy not running as backup user or group (got uid {} gid {})", running_uid, running_gid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxmox_backup::tools::runtime::main(run())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run() -> Result<(), Error> {
|
async fn run() -> Result<(), Error> {
|
||||||
@ -43,11 +49,6 @@ async fn run() -> Result<(), Error> {
|
|||||||
let mut config = ApiConfig::new(
|
let mut config = ApiConfig::new(
|
||||||
buildcfg::JS_DIR, &proxmox_backup::api2::ROUTER, RpcEnvironmentType::PUBLIC)?;
|
buildcfg::JS_DIR, &proxmox_backup::api2::ROUTER, RpcEnvironmentType::PUBLIC)?;
|
||||||
|
|
||||||
// add default dirs which includes jquery and bootstrap
|
|
||||||
// my $base = '/usr/share/libpve-http-server-perl';
|
|
||||||
// add_dirs($self->{dirs}, '/css/' => "$base/css/");
|
|
||||||
// add_dirs($self->{dirs}, '/js/' => "$base/js/");
|
|
||||||
// add_dirs($self->{dirs}, '/fonts/' => "$base/fonts/");
|
|
||||||
config.add_alias("novnc", "/usr/share/novnc-pve");
|
config.add_alias("novnc", "/usr/share/novnc-pve");
|
||||||
config.add_alias("extjs", "/usr/share/javascript/extjs");
|
config.add_alias("extjs", "/usr/share/javascript/extjs");
|
||||||
config.add_alias("fontawesome", "/usr/share/fonts-font-awesome");
|
config.add_alias("fontawesome", "/usr/share/fonts-font-awesome");
|
||||||
|
@ -239,7 +239,7 @@ pub fn zpool_commands() -> CommandLineInterface {
|
|||||||
.insert("create",
|
.insert("create",
|
||||||
CliCommand::new(&API_METHOD_CREATE_ZPOOL)
|
CliCommand::new(&API_METHOD_CREATE_ZPOOL)
|
||||||
.arg_param(&["name"])
|
.arg_param(&["name"])
|
||||||
.completion_cb("devices", complete_disk_name) // fixme: comlete the list
|
.completion_cb("devices", complete_disk_name) // fixme: complete the list
|
||||||
);
|
);
|
||||||
|
|
||||||
cmd_def.into()
|
cmd_def.into()
|
||||||
|
@ -629,7 +629,7 @@ impl BackupWriter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload speed test - prints result ot stderr
|
/// Upload speed test - prints result to stderr
|
||||||
pub async fn upload_speedtest(&self, verbose: bool) -> Result<f64, Error> {
|
pub async fn upload_speedtest(&self, verbose: bool) -> Result<f64, Error> {
|
||||||
|
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
|
@ -133,7 +133,7 @@ impl DiskManage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about file system type and unsed device for a path
|
/// Information about file system type and used device for a path
|
||||||
///
|
///
|
||||||
/// Returns tuple (fs_type, device, mount_source)
|
/// Returns tuple (fs_type, device, mount_source)
|
||||||
pub fn find_mounted_device(
|
pub fn find_mounted_device(
|
||||||
|
@ -111,7 +111,7 @@ fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
|||||||
Ok((i, stat))
|
Ok((i, stat))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse zpool list outout
|
/// Parse zpool list output
|
||||||
///
|
///
|
||||||
/// Note: This does not reveal any details on how the pool uses the devices, because
|
/// Note: This does not reveal any details on how the pool uses the devices, because
|
||||||
/// the zpool list output format is not really defined...
|
/// the zpool list output format is not really defined...
|
||||||
|
@ -53,7 +53,7 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
|||||||
|
|
||||||
let (i, vdev_name) = notspace1(i)?;
|
let (i, vdev_name) = notspace1(i)?;
|
||||||
|
|
||||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // sepecial device
|
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // special device
|
||||||
let vdev = ZFSPoolVDevState {
|
let vdev = ZFSPoolVDevState {
|
||||||
name: vdev_name.to_string(),
|
name: vdev_name.to_string(),
|
||||||
lvl: indent_level,
|
lvl: indent_level,
|
||||||
|
@ -80,6 +80,11 @@ impl From<usize> for HumanByte {
|
|||||||
HumanByte { b: v }
|
HumanByte { b: v }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<u64> for HumanByte {
|
||||||
|
fn from(v: u64) -> Self {
|
||||||
|
HumanByte { b: v as usize }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn correct_byte_convert() {
|
fn correct_byte_convert() {
|
||||||
|
@ -10,6 +10,7 @@ Ext.define('pbs-data-store-snapshots', {
|
|||||||
},
|
},
|
||||||
'files',
|
'files',
|
||||||
'owner',
|
'owner',
|
||||||
|
'verification',
|
||||||
{ name: 'size', type: 'int', allowNull: true, },
|
{ name: 'size', type: 'int', allowNull: true, },
|
||||||
{
|
{
|
||||||
name: 'crypt-mode',
|
name: 'crypt-mode',
|
||||||
@ -209,6 +210,10 @@ Ext.define('PBS.DataStoreContent', {
|
|||||||
group.size = item.size;
|
group.size = item.size;
|
||||||
group.owner = item.owner;
|
group.owner = item.owner;
|
||||||
}
|
}
|
||||||
|
if (item.verification &&
|
||||||
|
(!group.verification || group.verification.state !== 'failed')) {
|
||||||
|
group.verification = item.verification;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
group.count = group.children.length;
|
group.count = group.children.length;
|
||||||
@ -563,6 +568,41 @@ Ext.define('PBS.DataStoreContent', {
|
|||||||
return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText
|
return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: gettext('Verify State'),
|
||||||
|
sortable: true,
|
||||||
|
dataIndex: 'verification',
|
||||||
|
renderer: (v, meta, record) => {
|
||||||
|
if (v === undefined || v === null || !v.state) {
|
||||||
|
//meta.tdCls = "x-grid-row-loading";
|
||||||
|
return record.data.leaf ? '' : gettext('None');
|
||||||
|
}
|
||||||
|
let task = Proxmox.Utils.parse_task_upid(v.upid);
|
||||||
|
let verify_time = Proxmox.Utils.render_timestamp(task.starttime);
|
||||||
|
let iconCls = v.state === 'ok' ? 'check good' : 'times critical';
|
||||||
|
let tip = `Verify task started on ${verify_time}`;
|
||||||
|
if (record.parentNode.id === 'root') {
|
||||||
|
tip = v.state === 'ok'
|
||||||
|
? 'All verification OK in backup group'
|
||||||
|
: 'At least one failed verification in backup group!';
|
||||||
|
}
|
||||||
|
return `<span data-qtip="${tip}">
|
||||||
|
<i class="fa fa-fw fa-${iconCls}"></i> ${v.state}
|
||||||
|
</span>`;
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
dblclick: function(view, el, row, col, ev, rec) {
|
||||||
|
let data = rec.data || {};
|
||||||
|
let verify = data.verification;
|
||||||
|
if (verify && verify.upid && rec.parentNode.id !== 'root') {
|
||||||
|
let win = Ext.create('Proxmox.window.TaskViewer', {
|
||||||
|
upid: verify.upid,
|
||||||
|
});
|
||||||
|
win.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
tbar: [
|
tbar: [
|
||||||
|
Reference in New Issue
Block a user