forked from proxmox-mirrors/proxmox-backup
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4be9fd98f2 | ||
|
|
abcfbac411 | ||
|
|
7016c96b28 | ||
|
|
f42b10bcfb | ||
|
|
3c27db8a53 | ||
|
|
b13f9efc5e | ||
|
|
d90d7ff230 | ||
|
|
7c7a05cf6b | ||
|
|
ca08eba0b3 | ||
|
|
c096925472 | ||
|
|
48c7a01008 | ||
|
|
0a9cb27e68 | ||
|
|
847d39c353 | ||
|
|
2a36f7016c | ||
|
|
e7beb64391 | ||
|
|
f37354cea2 | ||
|
|
aa829571cf | ||
|
|
d543ff8b27 | ||
|
|
d41a83268b | ||
|
|
5327a402a4 | ||
|
|
524e5c09a0 | ||
|
|
cc50ee772a | ||
|
|
cf8a658ea2 | ||
|
|
8ac8330818 | ||
|
|
9bcaa05731 | ||
|
|
c6d5c11639 | ||
|
|
cf9334627c | ||
|
|
f3d48c390b | ||
|
|
b6a34622ee | ||
|
|
6e5862770e | ||
|
|
48f936f1a8 | ||
|
|
d84d10125d | ||
|
|
566121ceb0 | ||
|
|
484f096250 | ||
|
|
241f4acb2a | ||
|
|
3122e40d94 | ||
|
|
c0368c11bf | ||
|
|
6b7ac16ebd |
12
Cargo.toml
12
Cargo.toml
@ -1,5 +1,5 @@
|
||||
[workspace.package]
|
||||
version = "3.4.2"
|
||||
version = "3.4.8"
|
||||
authors = [
|
||||
"Dietmar Maurer <dietmar@proxmox.com>",
|
||||
"Dominik Csapak <d.csapak@proxmox.com>",
|
||||
@ -53,10 +53,10 @@ path = "src/lib.rs"
|
||||
|
||||
[workspace.dependencies]
|
||||
# proxmox workspace
|
||||
proxmox-apt = { version = "0.11", features = [ "cache" ] }
|
||||
proxmox-apt-api-types = "1.0.1"
|
||||
proxmox-apt = { version = "0.11.9", features = [ "cache" ] }
|
||||
proxmox-apt-api-types = "1.0.4"
|
||||
proxmox-async = "0.4"
|
||||
proxmox-auth-api = "0.4"
|
||||
proxmox-auth-api = "0.4.9"
|
||||
proxmox-borrow = "1"
|
||||
proxmox-compression = "0.2"
|
||||
proxmox-config-digest = "0.1.0"
|
||||
@ -69,9 +69,10 @@ proxmox-lang = "1.1"
|
||||
proxmox-log = "0.2.6"
|
||||
proxmox-ldap = "0.2.1"
|
||||
proxmox-metrics = "0.3.1"
|
||||
proxmox-network-api = "0.1.6"
|
||||
proxmox-notify = "0.5.1"
|
||||
proxmox-openid = "0.10.0"
|
||||
proxmox-rest-server = { version = "0.8.9", features = [ "templates" ] }
|
||||
proxmox-rest-server = { version = "0.8.10", features = [ "templates" ] }
|
||||
# some use "cli", some use "cli" and "server", pbs-config uses nothing
|
||||
proxmox-router = { version = "3.0.0", default-features = false }
|
||||
proxmox-rrd = "0.4"
|
||||
@ -216,6 +217,7 @@ proxmox-lang.workspace = true
|
||||
proxmox-log.workspace = true
|
||||
proxmox-ldap.workspace = true
|
||||
proxmox-metrics.workspace = true
|
||||
proxmox-network-api.workspace = true
|
||||
proxmox-notify = { workspace = true, features = [ "pbs-context" ] }
|
||||
proxmox-openid.workspace = true
|
||||
proxmox-rest-server = { workspace = true, features = [ "rate-limited-stream" ] }
|
||||
|
||||
27
Makefile
27
Makefile
@ -38,19 +38,23 @@ SUBCRATES != cargo metadata --no-deps --format-version=1 \
|
||||
| grep "$$PWD/" \
|
||||
| sed -e "s!.*$$PWD/!!g" -e 's/\#.*$$//g' -e 's/)$$//g'
|
||||
|
||||
# sync with debian/rules!
|
||||
STATIC_TARGET_DIR := target/static-build
|
||||
|
||||
ifeq ($(BUILD_MODE), release)
|
||||
CARGO_BUILD_ARGS += --release --target $(DEB_HOST_RUST_TYPE)
|
||||
CARGO_STATIC_CONFIG ?= --config debian/cargo_home/config.static.toml
|
||||
CARGO_STATIC_BUILD_ARGS += $(CARGO_STATIC_CONFIG) --release --target $(DEB_HOST_RUST_TYPE) --target-dir $(STATIC_TARGET_DIR)
|
||||
COMPILEDIR := target/$(DEB_HOST_RUST_TYPE)/release
|
||||
STATIC_COMPILEDIR := $(STATIC_TARGET_DIR)/$(DEB_HOST_RUST_TYPE)/release
|
||||
else
|
||||
CARGO_BUILD_ARGS += --target $(DEB_HOST_RUST_TYPE)
|
||||
CARGO_STATIC_BUILD_ARGS += --target $(DEB_HOST_RUST_TYPE) --target-dir $(STATIC_TARGET_DIR)
|
||||
COMPILEDIR := target/$(DEB_HOST_RUST_TYPE)/debug
|
||||
STATIC_COMPILEDIR := $(STATIC_TARGET_DIR)/$(DEB_HOST_RUST_TYPE)/debug
|
||||
endif
|
||||
|
||||
STATIC_RUSTC_FLAGS := -C target-feature=+crt-static -L $(STATIC_COMPILEDIR)/deps-stubs/
|
||||
# end sync with debian/rules
|
||||
|
||||
ifeq ($(valgrind), yes)
|
||||
CARGO_BUILD_ARGS += --features valgrind
|
||||
@ -155,7 +159,6 @@ clean: clean-deb
|
||||
$(foreach i,$(SUBDIRS), \
|
||||
$(MAKE) -C $(i) clean ;)
|
||||
$(CARGO) clean
|
||||
rm -f .do-cargo-build .do-static-cargo-build
|
||||
|
||||
# allows one to avoid running cargo clean when one just wants to tidy up after a package build
|
||||
clean-deb:
|
||||
@ -171,11 +174,9 @@ docs: $(COMPILEDIR)/dump-catalog-shell-cli $(COMPILEDIR)/docgen
|
||||
|
||||
.PHONY: cargo-build
|
||||
cargo-build:
|
||||
rm -f .do-cargo-build
|
||||
$(MAKE) $(COMPILED_BINS)
|
||||
|
||||
$(COMPILED_BINS) $(COMPILEDIR)/dump-catalog-shell-cli $(COMPILEDIR)/docgen: .do-cargo-build
|
||||
.do-cargo-build:
|
||||
$(COMPILED_BINS) $(COMPILEDIR)/dump-catalog-shell-cli $(COMPILEDIR)/docgen &:
|
||||
$(CARGO) build $(CARGO_BUILD_ARGS) \
|
||||
--package proxmox-backup-banner \
|
||||
--bin proxmox-backup-banner \
|
||||
@ -195,6 +196,7 @@ $(COMPILED_BINS) $(COMPILEDIR)/dump-catalog-shell-cli $(COMPILEDIR)/docgen: .do-
|
||||
--package proxmox-backup \
|
||||
--bin docgen \
|
||||
--bin pbs2to3 \
|
||||
--bin pbs3to4 \
|
||||
--bin proxmox-backup-api \
|
||||
--bin proxmox-backup-manager \
|
||||
--bin proxmox-backup-proxy \
|
||||
@ -206,17 +208,17 @@ $(COMPILED_BINS) $(COMPILEDIR)/dump-catalog-shell-cli $(COMPILEDIR)/docgen: .do-
|
||||
|
||||
.PHONY: proxmox-backup-client-static
|
||||
proxmox-backup-client-static:
|
||||
rm -f .do-static-cargo-build
|
||||
$(MAKE) $(STATIC_BINS)
|
||||
|
||||
$(STATIC_BINS): .do-static-cargo-build
|
||||
.do-static-cargo-build:
|
||||
$(STATIC_BINS) &:
|
||||
mkdir -p $(STATIC_COMPILEDIR)/deps-stubs/ && \
|
||||
echo '!<arch>' > $(STATIC_COMPILEDIR)/deps-stubs/libsystemd.a # workaround for to greedy linkage and proxmox-systemd
|
||||
$(CARGO) rustc $(CARGO_BUILD_ARGS) --package pxar-bin --bin pxar \
|
||||
--target-dir $(STATIC_TARGET_DIR) -- $(STATIC_RUSTC_FLAGS)
|
||||
$(CARGO) rustc $(CARGO_BUILD_ARGS) --package proxmox-backup-client --bin proxmox-backup-client \
|
||||
--target-dir $(STATIC_TARGET_DIR) -- $(STATIC_RUSTC_FLAGS)
|
||||
OPENSSL_STATIC=1 \
|
||||
RUSTFLAGS="$(STATIC_RUSTC_FLAGS)" \
|
||||
$(CARGO) build $(CARGO_STATIC_BUILD_ARGS) --package pxar-bin --bin pxar
|
||||
OPENSSL_STATIC=1 \
|
||||
RUSTFLAGS="$(STATIC_RUSTC_FLAGS)" \
|
||||
$(CARGO) build $(CARGO_STATIC_BUILD_ARGS) --package proxmox-backup-client --bin proxmox-backup-client
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@ -233,6 +235,7 @@ install: $(COMPILED_BINS) $(STATIC_BINS)
|
||||
install -m755 $(COMPILEDIR)/$(i) $(DESTDIR)$(SBINDIR)/ ; \
|
||||
install -m644 zsh-completions/_$(i) $(DESTDIR)$(ZSH_COMPL_DEST)/ ;)
|
||||
install -m755 $(COMPILEDIR)/pbs2to3 $(DESTDIR)$(SBINDIR)/
|
||||
install -m755 $(COMPILEDIR)/pbs3to4 $(DESTDIR)$(SBINDIR)/
|
||||
install -dm755 $(DESTDIR)$(LIBEXECDIR)/proxmox-backup
|
||||
install -dm755 $(DESTDIR)$(LIBEXECDIR)/proxmox-backup/file-restore
|
||||
$(foreach i,$(RESTORE_BIN), \
|
||||
|
||||
80
debian/changelog
vendored
80
debian/changelog
vendored
@ -1,3 +1,83 @@
|
||||
rust-proxmox-backup (3.4.8-3) bookworm; urgency=medium
|
||||
|
||||
* update proxmox-rest-server which brings the following improvements:
|
||||
- unify returned error message for authentication failure.
|
||||
- only default to getting real client IP from RFC 7239 "Forwarded" header
|
||||
in non-public RPC environment.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 18 Feb 2026 15:54:33 +0100
|
||||
|
||||
rust-proxmox-backup (3.4.8-2) bookworm; urgency=medium
|
||||
|
||||
* update proxmox-apt and related api-types for more robust package name
|
||||
handling.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 22 Dec 2025 17:32:12 +0100
|
||||
|
||||
rust-proxmox-backup (3.4.8-1) bookworm; urgency=medium
|
||||
|
||||
* fix #6566: backup api: conditionally drop group and snapshot locks to
|
||||
avoid that an immediate subsequent backup of the client to the same group
|
||||
can fail. This manifests in, e.g., a push sync job failing.
|
||||
|
||||
* api: datastore: optimize `groups` api call to avoid reading all snapshots
|
||||
information during iterating over them while only needing that information
|
||||
from the most recent snapshot.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 27 Oct 2025 17:54:11 +0100
|
||||
|
||||
rust-proxmox-backup (3.4.7-1) bookworm; urgency=medium
|
||||
|
||||
* pbs3to4: fix logic error and typo in log message
|
||||
|
||||
* pbs3to4: use boolean variable for systemd-boot installation state
|
||||
|
||||
* pbs3to4: bootloader: only allow systemd-boot before upgrade when used
|
||||
|
||||
* update proxmox-auth-api to version 0.4.9 to avoid signaling that the
|
||||
HTTP-only auth flow is supported.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 01 Oct 2025 13:53:02 +0200
|
||||
|
||||
rust-proxmox-backup (3.4.6-1) bookworm; urgency=medium
|
||||
|
||||
* move from pbs-api-types to proxmox-network-api
|
||||
|
||||
* ui: webauthn view: make sure renderers are html encoded
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 13 Aug 2025 13:07:28 +0200
|
||||
|
||||
rust-proxmox-backup (3.4.5-1) bookworm; urgency=medium
|
||||
|
||||
* d/control: recommend proxmox-network-interface-pinning package
|
||||
|
||||
* pbs3to4: adapt boot-loader checks to trixie
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 11 Aug 2025 15:25:19 +0200
|
||||
|
||||
rust-proxmox-backup (3.4.4-1) bookworm; urgency=medium
|
||||
|
||||
* ui: do not show consent banner twice for OpenID Connect login.
|
||||
|
||||
* tape: increase timeout for moving medium in changer to 45 minutes.
|
||||
|
||||
* add pbs-network-config-commit systemd service that renames pending changes
|
||||
to network interfaces configuration on boot.
|
||||
|
||||
* pbs3to4: add check for spelling of pbs-test repo.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 06 Aug 2025 12:30:16 +0200
|
||||
|
||||
rust-proxmox-backup (3.4.3-1) bookworm; urgency=medium
|
||||
|
||||
* proxmox-backup-client: fix static build.
|
||||
|
||||
* bin: add pbs3to4 upgrade check-list script.
|
||||
|
||||
* docs: rephrase and extend rate limiting description for sync jobs.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 16 Jul 2025 01:02:16 +0200
|
||||
|
||||
rust-proxmox-backup (3.4.2-1) bookworm; urgency=medium
|
||||
|
||||
* datastore: various small perf optimizations
|
||||
|
||||
18
debian/control
vendored
18
debian/control
vendored
@ -52,12 +52,12 @@ Build-Depends: bash-completion,
|
||||
librust-pin-project-lite-0.2+default-dev,
|
||||
librust-proxmox-acme-0.5+default-dev (>= 0.5.3-~~),
|
||||
librust-proxmox-apt-0.11+cache-dev,
|
||||
librust-proxmox-apt-0.11+default-dev,
|
||||
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.1-~~),
|
||||
librust-proxmox-apt-0.11+default-dev (>= 0.11.9-~~),
|
||||
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.4-~~),
|
||||
librust-proxmox-async-0.4+default-dev,
|
||||
librust-proxmox-auth-api-0.4+api-dev,
|
||||
librust-proxmox-auth-api-0.4+default-dev,
|
||||
librust-proxmox-auth-api-0.4+pam-authenticator-dev,
|
||||
librust-proxmox-auth-api-0.4+api-dev (>= 0.4.9-~~),
|
||||
librust-proxmox-auth-api-0.4+default-dev (>= 0.4.9-~~),
|
||||
librust-proxmox-auth-api-0.4+pam-authenticator-dev (>= 0.4.9-~~),
|
||||
librust-proxmox-borrow-1+default-dev,
|
||||
librust-proxmox-compression-0.2+default-dev,
|
||||
librust-proxmox-config-digest-0.1+default-dev,
|
||||
@ -78,12 +78,13 @@ Build-Depends: bash-completion,
|
||||
librust-proxmox-ldap-0.2+default-dev (>= 0.2.1-~~),
|
||||
librust-proxmox-log-0.2+default-dev (>= 0.2.6-~~),
|
||||
librust-proxmox-metrics-0.3+default-dev (>= 0.3.1-~~),
|
||||
librust-proxmox-network-api-0.1-dev (>= 0.1.6-~~),
|
||||
librust-proxmox-notify-0.5+default-dev (>= 0.5.1-~~),
|
||||
librust-proxmox-notify-0.5+pbs-context-dev (>= 0.5.1-~~),
|
||||
librust-proxmox-openid-0.10+default-dev,
|
||||
librust-proxmox-rest-server-0.8+default-dev (>= 0.8.9-~~),
|
||||
librust-proxmox-rest-server-0.8+rate-limited-stream-dev (>= 0.8.9-~~),
|
||||
librust-proxmox-rest-server-0.8+templates-dev (>= 0.8.9-~~),
|
||||
librust-proxmox-rest-server-0.8+default-dev (>= 0.8.10-~~),
|
||||
librust-proxmox-rest-server-0.8+rate-limited-stream-dev (>= 0.8.10-~~),
|
||||
librust-proxmox-rest-server-0.8+templates-dev (>= 0.8.10-~~),
|
||||
librust-proxmox-router-3+cli-dev,
|
||||
librust-proxmox-router-3+server-dev,
|
||||
librust-proxmox-rrd-0.4+default-dev,
|
||||
@ -194,6 +195,7 @@ Depends: fonts-font-awesome,
|
||||
${shlibs:Depends},
|
||||
Recommends: ifupdown2,
|
||||
proxmox-mail-forward,
|
||||
proxmox-network-interface-pinning,
|
||||
proxmox-offline-mirror-helper,
|
||||
zfsutils-linux,
|
||||
Description: Proxmox Backup Server daemon with tools and GUI
|
||||
|
||||
1
debian/lintian-overrides
vendored
1
debian/lintian-overrides
vendored
@ -5,4 +5,5 @@ proxmox-backup-server: systemd-service-file-refers-to-unusual-wantedby-target ge
|
||||
proxmox-backup-server: uses-dpkg-database-directly [usr/lib/x86_64-linux-gnu/proxmox-backup/proxmox-backup-api]
|
||||
proxmox-backup-server: uses-dpkg-database-directly [usr/lib/x86_64-linux-gnu/proxmox-backup/proxmox-backup-proxy]
|
||||
proxmox-backup-server: uses-dpkg-database-directly [usr/sbin/pbs2to3]
|
||||
proxmox-backup-server: uses-dpkg-database-directly [usr/sbin/pbs3to4]
|
||||
proxmox-backup-server: uses-dpkg-database-directly [usr/sbin/proxmox-backup-debug]
|
||||
|
||||
3
debian/proxmox-backup-server.install
vendored
3
debian/proxmox-backup-server.install
vendored
@ -1,4 +1,5 @@
|
||||
etc/pbs-enterprise.list /etc/apt/sources.list.d/
|
||||
etc/pbs-network-config-commit.service /lib/systemd/system/
|
||||
etc/proxmox-backup-banner.service /lib/systemd/system/
|
||||
etc/proxmox-backup-daily-update.service /lib/systemd/system/
|
||||
etc/proxmox-backup-daily-update.timer /lib/systemd/system/
|
||||
@ -14,6 +15,7 @@ usr/lib/x86_64-linux-gnu/proxmox-backup/proxmox-backup-proxy
|
||||
usr/lib/x86_64-linux-gnu/proxmox-backup/proxmox-daily-update
|
||||
usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd
|
||||
usr/sbin/pbs2to3
|
||||
usr/sbin/pbs3to4
|
||||
usr/sbin/proxmox-backup-debug
|
||||
usr/sbin/proxmox-backup-manager
|
||||
usr/share/javascript/proxmox-backup/css/ext6-pbs.css
|
||||
@ -21,6 +23,7 @@ usr/share/javascript/proxmox-backup/images
|
||||
usr/share/javascript/proxmox-backup/index.hbs
|
||||
usr/share/javascript/proxmox-backup/js/proxmox-backup-gui.js
|
||||
usr/share/man/man1/pbs2to3.1
|
||||
usr/share/man/man1/pbs3to4.1
|
||||
usr/share/man/man1/pmt.1
|
||||
usr/share/man/man1/pmtx.1
|
||||
usr/share/man/man1/proxmox-backup-debug.1
|
||||
|
||||
13
debian/rules
vendored
13
debian/rules
vendored
@ -7,6 +7,16 @@ include /usr/share/dpkg/pkg-info.mk
|
||||
include /usr/share/rustc/architecture.mk
|
||||
|
||||
export BUILD_MODE=release
|
||||
export CARGO_STATIC_CONFIG=--config debian/cargo_home/config.static.toml
|
||||
|
||||
# sync with Makefile!
|
||||
STATIC_TARGET_DIR := target/static-build
|
||||
ifeq ($(BUILD_MODE), release)
|
||||
STATIC_COMPILEDIR := $(STATIC_TARGET_DIR)/$(DEB_HOST_RUST_TYPE)/release
|
||||
else
|
||||
STATIC_COMPILEDIR := $(STATIC_TARGET_DIR)/$(DEB_HOST_RUST_TYPE)/debug
|
||||
endif
|
||||
# end sync with Makefile!
|
||||
|
||||
export CARGO=/usr/share/cargo/bin/cargo
|
||||
|
||||
@ -28,6 +38,9 @@ override_dh_auto_configure:
|
||||
@perl -ne 'if (/^version\s*=\s*"(\d+(?:\.\d+)+)"/) { my $$v_cargo = $$1; my $$v_deb = "$(DEB_VERSION_UPSTREAM)"; \
|
||||
die "ERROR: d/changelog <-> Cargo.toml version mismatch: $$v_cargo != $$v_deb\n" if $$v_cargo ne $$v_deb; exit(0); }' Cargo.toml
|
||||
$(CARGO) prepare-debian $(CURDIR)/debian/cargo_registry --link-from-system
|
||||
# add a new config for static building, sync with Makefile!
|
||||
cp debian/cargo_home/config.toml debian/cargo_home/config.static.toml
|
||||
sed -ri -e 's!^(rustflags = .*)\]$$!\1, "-C", "target-feature=+crt-static", "-L", "$(STATIC_COMPILEDIR)/deps-stubs/"\]!' debian/cargo_home/config.static.toml
|
||||
# `cargo build` and `cargo install` have different config precedence, symlink
|
||||
# the wrapper config into a place where `build` picks it up as well..
|
||||
# https://doc.rust-lang.org/cargo/commands/cargo-install.html#configuration-discovery
|
||||
|
||||
@ -26,6 +26,7 @@ GENERATED_SYNOPSIS := \
|
||||
|
||||
MAN1_PAGES := \
|
||||
pbs2to3.1 \
|
||||
pbs3to4.1 \
|
||||
pmt.1 \
|
||||
pmtx.1 \
|
||||
proxmox-backup-client.1 \
|
||||
|
||||
@ -103,6 +103,7 @@ man_pages = [
|
||||
('pmt/man1', 'pmt', 'Control Linux Tape Devices', [author], 1),
|
||||
('pmtx/man1', 'pmtx', 'Control SCSI media changer devices (tape autoloaders)', [author], 1),
|
||||
('pbs2to3/man1', 'pbs2to3', 'Proxmox Backup Server upgrade checker script for 2.4+ to current 3.x major upgrades', [author], 1),
|
||||
('pbs3to4/man1', 'pbs3to4', 'Proxmox Backup Server upgrade checker script for 3.4+ to current 4.x major upgrades', [author], 1),
|
||||
# configs
|
||||
('config/acl/man5', 'acl.cfg', 'Access Control Configuration', [author], 5),
|
||||
('config/datastore/man5', 'datastore.cfg', 'Datastore Configuration', [author], 5),
|
||||
|
||||
@ -239,7 +239,9 @@ command-line tool:
|
||||
|
||||
# proxmox-backup-manager sync-job update ID --rate-in 20MiB
|
||||
|
||||
For sync jobs in push direction use the ``rate-out`` option instead.
|
||||
For sync jobs in push direction use the ``rate-out`` option instead. To allow
|
||||
for traffic bursts, you can set the size of the token bucket filter used for
|
||||
traffic limiting via ``burst-in`` or ``burst-out`` parameters.
|
||||
|
||||
Sync Direction Push
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
16
docs/pbs3to4/man1.rst
Normal file
16
docs/pbs3to4/man1.rst
Normal file
@ -0,0 +1,16 @@
|
||||
:orphan:
|
||||
|
||||
|
||||
=======
|
||||
pbs3to4
|
||||
=======
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
This tool will help you to detect common pitfalls and misconfiguration before,
|
||||
and during the upgrade of a Proxmox Backup Server system. Any failures or
|
||||
warnings must be addressed prior to the upgrade. If you suspect that a message
|
||||
is a false positive, you have to make carefully sure that it really is.
|
||||
|
||||
.. include:: ../pbs-copyright.rst
|
||||
@ -19,11 +19,12 @@ certain hosts.
|
||||
|
||||
You can manage the traffic controls either via the web-interface or using the
|
||||
``traffic-control`` commands of the ``proxmox-backup-manager`` command-line
|
||||
tool.
|
||||
tool. Traffic is limited by rate (``rate-in`` and ``rate-out``) and allows for
|
||||
short bursts by setting the token bucket size (``burst-in`` and ``burst-out``).
|
||||
|
||||
.. note:: Sync jobs on the server are not affected by the configured rate-in limits.
|
||||
If you want to limit the incoming traffic that a pull-based sync job
|
||||
generates, you need to setup a job-specific rate-in limit. See
|
||||
.. note:: Sync jobs on the server are not affected by the configured rate limits.
|
||||
If you want to limit the incoming traffic of pull-based or outgoing traffic
|
||||
of push-based sync job, you need to setup a job-specific rate-in limit. See
|
||||
:ref:`syncjobs`.
|
||||
|
||||
The following command adds a traffic control rule to limit all IPv4 clients
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
include ../defines.mk
|
||||
|
||||
UNITS := \
|
||||
pbs-network-config-commit.service \
|
||||
proxmox-backup-daily-update.timer \
|
||||
removable-device-attach@.service
|
||||
|
||||
|
||||
14
etc/pbs-network-config-commit.service
Normal file
14
etc/pbs-network-config-commit.service
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Commit any pending Proxmox Backup Server network changes
|
||||
DefaultDependencies=no
|
||||
After=local-fs.target pvenetcommit.service
|
||||
Before=sysinit.target
|
||||
|
||||
[Service]
|
||||
Environment="FN=/etc/network/interfaces"
|
||||
ExecStart=sh -c 'if [ -f ${FN}.new ]; then mv ${FN}.new ${FN}; fi'
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sysinit.target
|
||||
@ -18,6 +18,7 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
proxmox-notify.workspace = true
|
||||
proxmox-network-api.workspace = true
|
||||
proxmox-router = { workspace = true, default-features = false }
|
||||
proxmox-schema.workspace = true
|
||||
proxmox-section-config.workspace = true
|
||||
|
||||
@ -17,7 +17,7 @@ pub use lexer::*;
|
||||
mod parser;
|
||||
pub use parser::*;
|
||||
|
||||
use pbs_api_types::{
|
||||
use proxmox_network_api::{
|
||||
BondXmitHashPolicy, Interface, LinuxBondMode, NetworkConfigMethod, NetworkInterfaceType,
|
||||
};
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ use crate::{
|
||||
};
|
||||
|
||||
const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60 * 5; // 5 minutes
|
||||
const SCSI_CHANGER_MOVE_MEDIUM_TIMEOUT: usize = 60 * 45; // 45 minutes
|
||||
const SCSI_VOLUME_TAG_LEN: usize = 36;
|
||||
|
||||
/// Initialize element status (Inventory)
|
||||
@ -181,7 +182,7 @@ pub fn load_slot(file: &mut File, from_slot: u64, drivenum: u64) -> Result<(), E
|
||||
);
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, 64)?;
|
||||
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
|
||||
sg_raw.set_timeout(SCSI_CHANGER_MOVE_MEDIUM_TIMEOUT);
|
||||
|
||||
sg_raw
|
||||
.do_command(&cmd)
|
||||
@ -205,7 +206,7 @@ pub fn unload(file: &mut File, to_slot: u64, drivenum: u64) -> Result<(), Error>
|
||||
);
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, 64)?;
|
||||
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
|
||||
sg_raw.set_timeout(SCSI_CHANGER_MOVE_MEDIUM_TIMEOUT);
|
||||
|
||||
sg_raw
|
||||
.do_command(&cmd)
|
||||
@ -233,7 +234,7 @@ pub fn transfer_medium<F: AsRawFd>(
|
||||
);
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, 64)?;
|
||||
sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
|
||||
sg_raw.set_timeout(SCSI_CHANGER_MOVE_MEDIUM_TIMEOUT);
|
||||
|
||||
sg_raw.do_command(&cmd).map_err(|err| {
|
||||
format_err!(
|
||||
|
||||
@ -195,59 +195,68 @@ pub fn list_groups(
|
||||
.try_fold(Vec::new(), |mut group_info, group| {
|
||||
let group = group?;
|
||||
|
||||
let owner = match datastore.get_owner(&ns, group.as_ref()) {
|
||||
Ok(auth_id) => auth_id,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to get owner of group '{}' in {} - {}",
|
||||
group.group(),
|
||||
print_store_and_ns(&store, &ns),
|
||||
err
|
||||
);
|
||||
return Ok(group_info);
|
||||
}
|
||||
};
|
||||
if !list_all && check_backup_owner(&owner, &auth_id).is_err() {
|
||||
return Ok(group_info);
|
||||
let item =
|
||||
backup_group_to_group_list_item(datastore.clone(), group, &ns, &auth_id, list_all);
|
||||
|
||||
if let Some(item) = item {
|
||||
group_info.push(item);
|
||||
}
|
||||
|
||||
let snapshots = match group.list_backups() {
|
||||
Ok(snapshots) => snapshots,
|
||||
Err(_) => return Ok(group_info),
|
||||
};
|
||||
|
||||
let backup_count: u64 = snapshots.len() as u64;
|
||||
if backup_count == 0 {
|
||||
return Ok(group_info);
|
||||
}
|
||||
|
||||
let last_backup = snapshots
|
||||
.iter()
|
||||
.fold(&snapshots[0], |a, b| {
|
||||
if a.is_finished() && a.backup_dir.backup_time() > b.backup_dir.backup_time() {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
})
|
||||
.to_owned();
|
||||
|
||||
let notes_path = datastore.group_notes_path(&ns, group.as_ref());
|
||||
let comment = file_read_firstline(notes_path).ok();
|
||||
|
||||
group_info.push(GroupListItem {
|
||||
backup: group.into(),
|
||||
last_backup: last_backup.backup_dir.backup_time(),
|
||||
owner: Some(owner),
|
||||
backup_count,
|
||||
files: last_backup.files,
|
||||
comment,
|
||||
});
|
||||
|
||||
Ok(group_info)
|
||||
})
|
||||
}
|
||||
|
||||
fn backup_group_to_group_list_item(
|
||||
datastore: Arc<DataStore>,
|
||||
group: pbs_datastore::BackupGroup,
|
||||
ns: &BackupNamespace,
|
||||
auth_id: &Authid,
|
||||
list_all: bool,
|
||||
) -> Option<GroupListItem> {
|
||||
let owner = get_group_owner(datastore.name(), ns, &group)?;
|
||||
|
||||
if !list_all && check_backup_owner(&owner, auth_id).is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut snapshots: Vec<_> = match group.iter_snapshots() {
|
||||
Ok(snapshots) => snapshots.collect::<Result<Vec<_>, Error>>().ok()?,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let backup_count: u64 = snapshots.len() as u64;
|
||||
let last = if backup_count == 1 {
|
||||
// we may have only one unfinished snapshot
|
||||
snapshots.pop().and_then(|dir| BackupInfo::new(dir).ok())
|
||||
} else {
|
||||
// we either have no snapshots, or at least one finished one, since we cannot have
|
||||
// multiple unfinished ones
|
||||
snapshots.sort_by_key(|b| std::cmp::Reverse(b.backup_time()));
|
||||
snapshots
|
||||
.iter()
|
||||
.filter_map(|backup| BackupInfo::new(backup.clone()).ok())
|
||||
.find(|info| info.is_finished())
|
||||
};
|
||||
|
||||
let (last_backup, files) = last
|
||||
.map(|info| (info.backup_dir.backup_time(), info.files))
|
||||
.unwrap_or((0, Vec::new()));
|
||||
|
||||
let notes_path = datastore.group_notes_path(ns, group.as_ref());
|
||||
let comment = file_read_firstline(notes_path).ok();
|
||||
|
||||
let item = GroupListItem {
|
||||
backup: group.into(),
|
||||
last_backup,
|
||||
owner: Some(owner),
|
||||
backup_count,
|
||||
files,
|
||||
comment,
|
||||
};
|
||||
|
||||
Some(item)
|
||||
}
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
@ -475,6 +484,25 @@ pub async fn list_snapshots(
|
||||
.map_err(|err| format_err!("failed to await blocking task: {err}"))?
|
||||
}
|
||||
|
||||
fn get_group_owner(
|
||||
store: &str,
|
||||
ns: &BackupNamespace,
|
||||
group: &pbs_datastore::BackupGroup,
|
||||
) -> Option<Authid> {
|
||||
match group.get_owner() {
|
||||
Ok(auth_id) => Some(auth_id),
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to get owner of group '{}' in {} - {}",
|
||||
group.group(),
|
||||
print_store_and_ns(store, ns),
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This must not run in a main worker thread as it potentially does tons of I/O.
|
||||
unsafe fn list_snapshots_blocking(
|
||||
store: String,
|
||||
@ -588,17 +616,9 @@ unsafe fn list_snapshots_blocking(
|
||||
};
|
||||
|
||||
groups.iter().try_fold(Vec::new(), |mut snapshots, group| {
|
||||
let owner = match group.get_owner() {
|
||||
Ok(auth_id) => auth_id,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to get owner of group '{}' in {} - {}",
|
||||
group.group(),
|
||||
print_store_and_ns(&store, &ns),
|
||||
err
|
||||
);
|
||||
return Ok(snapshots);
|
||||
}
|
||||
let owner = match get_group_owner(&store, &ns, group) {
|
||||
Some(auth_id) => auth_id,
|
||||
None => return Ok(snapshots),
|
||||
};
|
||||
|
||||
if !list_all && check_backup_owner(&owner, &auth_id).is_err() {
|
||||
|
||||
@ -85,6 +85,27 @@ struct SharedBackupState {
|
||||
known_chunks: KnownChunksMap,
|
||||
backup_size: u64, // sums up size of all files
|
||||
backup_stat: UploadStatistic,
|
||||
backup_lock_guards: BackupLockGuards,
|
||||
}
|
||||
|
||||
pub struct BackupLockGuards {
|
||||
previous_snapshot: Option<BackupLockGuard>,
|
||||
group: Option<BackupLockGuard>,
|
||||
snapshot: Option<BackupLockGuard>,
|
||||
}
|
||||
|
||||
impl BackupLockGuards {
|
||||
pub(crate) fn new(
|
||||
previous_snapshot: Option<BackupLockGuard>,
|
||||
group: BackupLockGuard,
|
||||
snapshot: BackupLockGuard,
|
||||
) -> Self {
|
||||
Self {
|
||||
previous_snapshot,
|
||||
group: Some(group),
|
||||
snapshot: Some(snapshot),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedBackupState {
|
||||
@ -125,6 +146,7 @@ impl BackupEnvironment {
|
||||
worker: Arc<WorkerTask>,
|
||||
datastore: Arc<DataStore>,
|
||||
backup_dir: BackupDir,
|
||||
backup_lock_guards: BackupLockGuards,
|
||||
) -> Self {
|
||||
let state = SharedBackupState {
|
||||
finished: false,
|
||||
@ -135,6 +157,7 @@ impl BackupEnvironment {
|
||||
known_chunks: HashMap::new(),
|
||||
backup_size: 0,
|
||||
backup_stat: UploadStatistic::new(),
|
||||
backup_lock_guards,
|
||||
};
|
||||
|
||||
Self {
|
||||
@ -607,6 +630,9 @@ impl BackupEnvironment {
|
||||
bail!("backup does not contain valid files (file count == 0)");
|
||||
}
|
||||
|
||||
// drop previous snapshot lock
|
||||
state.backup_lock_guards.previous_snapshot.take();
|
||||
|
||||
// check for valid manifest and store stats
|
||||
let stats = serde_json::to_value(state.backup_stat)?;
|
||||
self.backup_dir
|
||||
@ -630,13 +656,17 @@ impl BackupEnvironment {
|
||||
// marks the backup as successful
|
||||
state.finished = true;
|
||||
|
||||
// drop snapshot and group lock only here so any error above will lead to
|
||||
// the locks still being held in the env for the backup cleanup.
|
||||
state.backup_lock_guards.snapshot.take();
|
||||
state.backup_lock_guards.group.take();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If verify-new is set on the datastore, this will run a new verify task
|
||||
/// for the backup. If not, this will return and also drop the passed lock
|
||||
/// immediately.
|
||||
pub fn verify_after_complete(&self, excl_snap_lock: BackupLockGuard) -> Result<(), Error> {
|
||||
/// for the backup. If not, this will return.
|
||||
pub fn verify_after_complete(&self) -> Result<(), Error> {
|
||||
self.ensure_finished()?;
|
||||
|
||||
if !self.datastore.verify_new() {
|
||||
@ -644,8 +674,7 @@ impl BackupEnvironment {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Downgrade to shared lock, the backup itself is finished
|
||||
drop(excl_snap_lock);
|
||||
// Get shared lock, the backup itself is finished
|
||||
let snap_lock = self.backup_dir.lock_shared().with_context(|| {
|
||||
format!(
|
||||
"while trying to verify snapshot '{:?}' after completion",
|
||||
|
||||
@ -140,7 +140,7 @@ fn upgrade_to_backup_protocol(
|
||||
};
|
||||
|
||||
// lock backup group to only allow one backup per group at a time
|
||||
let (owner, _group_guard) = datastore.create_locked_backup_group(
|
||||
let (owner, group_guard) = datastore.create_locked_backup_group(
|
||||
backup_group.backup_ns(),
|
||||
backup_group.as_ref(),
|
||||
&auth_id,
|
||||
@ -179,7 +179,7 @@ fn upgrade_to_backup_protocol(
|
||||
|
||||
let backup_dir = backup_group.backup_dir(backup_dir_arg.time)?;
|
||||
|
||||
let _last_guard = if let Some(last) = &last_backup {
|
||||
let last_guard = if let Some(last) = &last_backup {
|
||||
if backup_dir.backup_time() <= last.backup_dir.backup_time() {
|
||||
bail!("backup timestamp is older than last backup.");
|
||||
}
|
||||
@ -205,12 +205,19 @@ fn upgrade_to_backup_protocol(
|
||||
auth_id.to_string(),
|
||||
true,
|
||||
move |worker| {
|
||||
// Keep flock for the backup runtime by storing guards in backup env shared state.
|
||||
// Drop them on successful backup finish or when dropping the env after cleanup in
|
||||
// case of errors. The former is required for immediate subsequent backups (e.g.
|
||||
// during a push sync) to be able to lock the group and snapshots.
|
||||
let backup_lock_guards = BackupLockGuards::new(last_guard, group_guard, snap_guard);
|
||||
|
||||
let mut env = BackupEnvironment::new(
|
||||
env_type,
|
||||
auth_id,
|
||||
worker.clone(),
|
||||
datastore,
|
||||
backup_dir,
|
||||
backup_lock_guards,
|
||||
);
|
||||
|
||||
env.debug = debug;
|
||||
@ -264,11 +271,6 @@ fn upgrade_to_backup_protocol(
|
||||
let mut abort_future = abort_future.map(|_| Err(format_err!("task aborted")));
|
||||
|
||||
async move {
|
||||
// keep flock until task ends
|
||||
let _group_guard = _group_guard;
|
||||
let snap_guard = snap_guard;
|
||||
let _last_guard = _last_guard;
|
||||
|
||||
let res = select! {
|
||||
req = req_fut => req,
|
||||
abrt = abort_future => abrt,
|
||||
@ -280,7 +282,7 @@ fn upgrade_to_backup_protocol(
|
||||
}
|
||||
|
||||
let verify = |env: BackupEnvironment| {
|
||||
if let Err(err) = env.verify_after_complete(snap_guard) {
|
||||
if let Err(err) = env.verify_after_complete() {
|
||||
env.log(format!(
|
||||
"backup finished, but starting the requested verify task failed: {}",
|
||||
err
|
||||
|
||||
@ -7,11 +7,15 @@ use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
|
||||
use proxmox_schema::api;
|
||||
|
||||
use pbs_api_types::{
|
||||
Authid, BondXmitHashPolicy, Interface, LinuxBondMode, NetworkConfigMethod,
|
||||
NetworkInterfaceType, CIDR_V4_SCHEMA, CIDR_V6_SCHEMA, IP_V4_SCHEMA, IP_V6_SCHEMA,
|
||||
NETWORK_INTERFACE_ARRAY_SCHEMA, NETWORK_INTERFACE_LIST_SCHEMA, NETWORK_INTERFACE_NAME_SCHEMA,
|
||||
NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
|
||||
Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
|
||||
};
|
||||
|
||||
use proxmox_network_api::{
|
||||
BondXmitHashPolicy, Interface, LinuxBondMode, NetworkConfigMethod, NetworkInterfaceType,
|
||||
CIDR_V4_SCHEMA, CIDR_V6_SCHEMA, IP_V4_SCHEMA, IP_V6_SCHEMA, NETWORK_INTERFACE_ARRAY_SCHEMA,
|
||||
NETWORK_INTERFACE_LIST_SCHEMA, NETWORK_INTERFACE_NAME_SCHEMA,
|
||||
};
|
||||
|
||||
use pbs_config::network::{
|
||||
self, parse_vlan_id_from_name, parse_vlan_raw_device_from_name, NetworkConfig,
|
||||
};
|
||||
|
||||
771
src/bin/pbs3to4.rs
Normal file
771
src/bin/pbs3to4.rs
Normal file
@ -0,0 +1,771 @@
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{format_err, Error};
|
||||
use const_format::concatcp;
|
||||
use regex::Regex;
|
||||
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
||||
|
||||
use proxmox_apt::repositories;
|
||||
use proxmox_apt_api_types::{APTRepositoryFile, APTRepositoryPackageType};
|
||||
use proxmox_backup::api2::node::apt;
|
||||
|
||||
const OLD_SUITE: &str = "bookworm";
|
||||
const NEW_SUITE: &str = "trixie";
|
||||
const PROXMOX_BACKUP_META: &str = "proxmox-backup";
|
||||
const MIN_PBS_MAJOR: u8 = 3;
|
||||
const MIN_PBS_MINOR: u8 = 4;
|
||||
const MIN_PBS_PKGREL: u8 = 0;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let mut checker = Checker::new();
|
||||
checker.check_pbs_packages()?;
|
||||
checker.check_misc()?;
|
||||
checker.summary()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Checker {
|
||||
output: ConsoleOutput,
|
||||
upgraded: bool,
|
||||
}
|
||||
|
||||
impl Checker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
output: ConsoleOutput::new(),
|
||||
upgraded: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_pbs_packages(&mut self) -> Result<(), Error> {
|
||||
self.output
|
||||
.print_header("CHECKING VERSION INFORMATION FOR PBS PACKAGES")?;
|
||||
|
||||
self.check_upgradable_packages()?;
|
||||
let pkg_versions = apt::get_versions()?;
|
||||
self.check_meta_package_version(&pkg_versions)?;
|
||||
self.check_kernel_compat(&pkg_versions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_upgradable_packages(&mut self) -> Result<(), Error> {
|
||||
self.output.log_info("Checking for package updates..")?;
|
||||
|
||||
let result = apt::apt_update_available();
|
||||
match result {
|
||||
Err(err) => {
|
||||
self.output.log_warn(format!("{err}"))?;
|
||||
self.output
|
||||
.log_fail("unable to retrieve list of package updates!")?;
|
||||
}
|
||||
Ok(package_status) => {
|
||||
if package_status.is_empty() {
|
||||
self.output.log_pass("all packages up-to-date")?;
|
||||
} else {
|
||||
let pkgs = package_status
|
||||
.iter()
|
||||
.map(|pkg| pkg.package.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
self.output.log_warn(format!(
|
||||
"updates for the following packages are available:\n {pkgs}",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_meta_package_version(
|
||||
&mut self,
|
||||
pkg_versions: &[pbs_api_types::APTUpdateInfo],
|
||||
) -> Result<(), Error> {
|
||||
self.output
|
||||
.log_info("Checking proxmox backup server package version..")?;
|
||||
|
||||
let pbs_meta_pkg = pkg_versions
|
||||
.iter()
|
||||
.find(|pkg| pkg.package.as_str() == PROXMOX_BACKUP_META);
|
||||
|
||||
if let Some(pbs_meta_pkg) = pbs_meta_pkg {
|
||||
let pkg_version = Regex::new(r"^(\d+)\.(\d+)[.-](\d+)")?;
|
||||
let captures = pkg_version.captures(&pbs_meta_pkg.old_version);
|
||||
if let Some(captures) = captures {
|
||||
let maj = Self::extract_version_from_captures(1, &captures)?;
|
||||
let min = Self::extract_version_from_captures(2, &captures)?;
|
||||
let pkgrel = Self::extract_version_from_captures(3, &captures)?;
|
||||
|
||||
let min_version = format!("{MIN_PBS_MAJOR}.{MIN_PBS_MINOR}.{MIN_PBS_PKGREL}");
|
||||
|
||||
// MIN_PBS_PKGREL is currently zero, making the comparison further below
|
||||
// superfluous, but this might not always be the case
|
||||
#[allow(clippy::absurd_extreme_comparisons)]
|
||||
if maj > MIN_PBS_MAJOR {
|
||||
self.output
|
||||
.log_pass(format!("Already upgraded to Proxmox Backup Server {maj}"))?;
|
||||
self.upgraded = true;
|
||||
} else if maj >= MIN_PBS_MAJOR && min >= MIN_PBS_MINOR && pkgrel >= MIN_PBS_PKGREL {
|
||||
self.output.log_pass(format!(
|
||||
"'{PROXMOX_BACKUP_META}' has version >= {min_version}"
|
||||
))?;
|
||||
} else {
|
||||
self.output.log_fail(format!(
|
||||
"'{PROXMOX_BACKUP_META}' package is too old, please upgrade to >= {min_version}"
|
||||
))?;
|
||||
}
|
||||
} else {
|
||||
self.output.log_fail(format!(
|
||||
"could not match the '{PROXMOX_BACKUP_META}' package version, \
|
||||
is it installed?",
|
||||
))?;
|
||||
}
|
||||
} else {
|
||||
self.output
|
||||
.log_fail(format!("'{PROXMOX_BACKUP_META}' package not found!"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_kernel_version_compatible(&self, running_version: &str) -> bool {
|
||||
// TODO: rework this to parse out maj.min.patch and do numerical comparission and detect
|
||||
// those with a "bpo12" as backport kernels from the older release.
|
||||
const MINIMUM_RE: &str = r"6\.(?:14\.(?:[1-9]\d+|[6-9])|1[5-9])[^~]*";
|
||||
const ARBITRARY_RE: &str = r"(?:1[4-9]|2\d+)\.(?:[0-9]|\d{2,})[^~]*-pve";
|
||||
|
||||
let re = if self.upgraded {
|
||||
concatcp!(r"^(?:", MINIMUM_RE, r"|", ARBITRARY_RE, r")$")
|
||||
} else {
|
||||
r"^(?:6\.(?:2|5|8|11|14))"
|
||||
};
|
||||
let re = Regex::new(re).expect("failed to compile kernel compat regex");
|
||||
|
||||
re.is_match(running_version)
|
||||
}
|
||||
|
||||
fn check_kernel_compat(
|
||||
&mut self,
|
||||
pkg_versions: &[pbs_api_types::APTUpdateInfo],
|
||||
) -> Result<(), Error> {
|
||||
self.output.log_info("Check running kernel version..")?;
|
||||
|
||||
let kinstalled = if self.upgraded {
|
||||
"proxmox-kernel-6.14"
|
||||
} else {
|
||||
"proxmox-kernel-6.8"
|
||||
};
|
||||
|
||||
let output = std::process::Command::new("uname").arg("-r").output();
|
||||
match output {
|
||||
Err(_err) => self
|
||||
.output
|
||||
.log_fail("unable to determine running kernel version.")?,
|
||||
Ok(ret) => {
|
||||
let running_version = std::str::from_utf8(&ret.stdout[..ret.stdout.len() - 1])?;
|
||||
if self.is_kernel_version_compatible(running_version) {
|
||||
if self.upgraded {
|
||||
self.output.log_pass(format!(
|
||||
"running new kernel '{running_version}' after upgrade."
|
||||
))?;
|
||||
} else {
|
||||
self.output.log_pass(format!(
|
||||
"running kernel '{running_version}' is considered suitable for \
|
||||
upgrade."
|
||||
))?;
|
||||
}
|
||||
} else {
|
||||
let installed_kernel = pkg_versions
|
||||
.iter()
|
||||
.find(|pkg| pkg.package.as_str() == kinstalled);
|
||||
if installed_kernel.is_some() {
|
||||
self.output.log_warn(format!(
|
||||
"a suitable kernel '{kinstalled}' is installed, but an \
|
||||
unsuitable '{running_version}' is booted, missing reboot?!",
|
||||
))?;
|
||||
} else {
|
||||
self.output.log_warn(format!(
|
||||
"unexpected running and installed kernel '{running_version}'.",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_version_from_captures(
|
||||
index: usize,
|
||||
captures: ®ex::Captures,
|
||||
) -> Result<u8, Error> {
|
||||
if let Some(capture) = captures.get(index) {
|
||||
let val = capture.as_str().parse::<u8>()?;
|
||||
Ok(val)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_bootloader(&mut self) -> Result<(), Error> {
|
||||
self.output
|
||||
.log_info("Checking bootloader configuration...")?;
|
||||
|
||||
let sd_boot_installed =
|
||||
Path::new("/usr/share/doc/systemd-boot/changelog.Debian.gz").is_file();
|
||||
|
||||
if !Path::new("/sys/firmware/efi").is_dir() {
|
||||
if sd_boot_installed {
|
||||
self.output.log_warn(
|
||||
"systemd-boot package installed on legacy-boot system is not \
|
||||
necessary, consider removing it",
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
self.output
|
||||
.log_skip("System booted in legacy-mode - no need for additional packages.")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut boot_ok = true;
|
||||
if Path::new("/etc/kernel/proxmox-boot-uuids").is_file() {
|
||||
// PBS packages version check needs to be run before
|
||||
if !self.upgraded {
|
||||
let output = std::process::Command::new("proxmox-boot-tool")
|
||||
.arg("status")
|
||||
.output()
|
||||
.map_err(|err| {
|
||||
format_err!("failed to retrieve proxmox-boot-tool status - {err}")
|
||||
})?;
|
||||
let re = Regex::new(r"configured with:.* (uefi|systemd-boot) \(versions:")
|
||||
.expect("failed to proxmox-boot-tool status");
|
||||
if re.is_match(std::str::from_utf8(&output.stdout)?) {
|
||||
self.output
|
||||
.log_skip("not yet upgraded, systemd-boot still needed for bootctl")?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !Path::new("/usr/share/doc/grub-efi-amd64/changelog.Debian.gz").is_file() {
|
||||
self.output.log_warn(
|
||||
"System booted in uefi mode but grub-efi-amd64 meta-package not installed, \
|
||||
new grub versions will not be installed to /boot/efi!
|
||||
Install grub-efi-amd64.",
|
||||
)?;
|
||||
boot_ok = false;
|
||||
}
|
||||
if Path::new("/boot/efi/EFI/BOOT/BOOTX64.efi").is_file() {
|
||||
let output = std::process::Command::new("debconf-show")
|
||||
.arg("--db")
|
||||
.arg("configdb")
|
||||
.arg("grub-efi-amd64")
|
||||
.arg("grub-pc")
|
||||
.output()
|
||||
.map_err(|err| format_err!("failed to retrieve debconf settings - {err}"))?;
|
||||
let re = Regex::new(r"grub2/force_efi_extra_removable: +true(?:\n|$)")
|
||||
.expect("failed to compile dbconfig regex");
|
||||
if !re.is_match(std::str::from_utf8(&output.stdout)?) {
|
||||
self.output.log_warn(format!(
|
||||
"Removable bootloader found at '/boot/efi/EFI/BOOT/BOOTX64.efi', but GRUB packages \
|
||||
not set up to update it!\nRun the following command:\n\
|
||||
echo 'grub-efi-amd64 grub2/force_efi_extra_removable boolean true' | debconf-set-selections -v -u\n\
|
||||
Then reinstall GRUB with 'apt install --reinstall grub-efi-amd64'"
|
||||
))?;
|
||||
boot_ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if sd_boot_installed {
|
||||
self.output.log_fail(
|
||||
"systemd-boot meta-package installed. This will cause problems on upgrades of other \
|
||||
boot-related packages.\n\
|
||||
Remove the 'systemd-boot' package.\n\
|
||||
See https://pbs.proxmox.com/wiki/Upgrade_from_3_to_4#sd-boot-warning for more information."
|
||||
)?;
|
||||
boot_ok = false;
|
||||
}
|
||||
if boot_ok {
|
||||
self.output
|
||||
.log_pass("bootloader packages installed correctly")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_apt_repos(&mut self) -> Result<(), Error> {
|
||||
self.output
|
||||
.log_info("Checking for package repository suite mismatches..")?;
|
||||
|
||||
let mut strange_suite = false;
|
||||
let mut mismatches = Vec::new();
|
||||
let mut found_suite: Option<(String, String)> = None;
|
||||
let mut test_repos = Vec::new();
|
||||
|
||||
let (repo_files, _repo_errors, _digest) = repositories::repositories()?;
|
||||
for repo_file in repo_files {
|
||||
self.check_repo_file(
|
||||
&mut found_suite,
|
||||
&mut mismatches,
|
||||
&mut strange_suite,
|
||||
&mut test_repos,
|
||||
repo_file,
|
||||
)?;
|
||||
}
|
||||
|
||||
match (mismatches.is_empty(), strange_suite) {
|
||||
(true, false) => self.output.log_pass("found no suite mismatch")?,
|
||||
(true, true) => self
|
||||
.output
|
||||
.log_notice("found no suite mismatches, but found at least one strange suite")?,
|
||||
(false, _) => {
|
||||
let mut message = String::from(
|
||||
"Found mixed old and new packages repository suites, fix before upgrading!\
|
||||
\n Mismatches:",
|
||||
);
|
||||
for (suite, location) in mismatches.iter() {
|
||||
message.push_str(
|
||||
format!("\n found suite '{suite}' at '{location}'").as_str(),
|
||||
);
|
||||
}
|
||||
message.push('\n');
|
||||
self.output.log_fail(message)?
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove the check in PBS 5, one cannot really update to latest 4.4 with an old test
|
||||
// repo anyway
|
||||
for (component, suite, location) in &test_repos {
|
||||
self.output.log_info(format!(
|
||||
"Found test repo for Proxmox Backup Server at {location}, checking compatibility \
|
||||
with updated 'pve-test' spelling.",
|
||||
))?;
|
||||
match component.as_str() {
|
||||
"pbstest" => {
|
||||
let message = format!(
|
||||
"Found legacy spelling 'pbstest' of the pbs-test repo. Change the repo to \
|
||||
use 'pbs-test' when updating the repos to the '{NEW_SUITE}' suite for \
|
||||
Proxmox Backup Server 4!"
|
||||
);
|
||||
match suite.as_str() {
|
||||
NEW_SUITE => self.output.log_fail(message)?,
|
||||
OLD_SUITE => self.output.log_warn(message)?,
|
||||
_ => {} // unreachable, other suites return early in check_repo_file()
|
||||
}
|
||||
}
|
||||
"pbs-test" => match suite.as_str() {
|
||||
NEW_SUITE => self.output.log_pass(format!(
|
||||
"Found modern spelling 'pbs-test' of the pbs-test repo for new suite \
|
||||
'{NEW_SUITE}'.",
|
||||
))?,
|
||||
OLD_SUITE => self.output.log_fail(format!(
|
||||
"Found modern spelling 'pbs-test' but old suite '{OLD_SUITE}', did you \
|
||||
forget to update the suite?",
|
||||
))?,
|
||||
_ => {} // unreachable, other suites return early in check_repo_file()
|
||||
},
|
||||
_ => {} // unreachable, only test repositories are added
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_dkms_modules(&mut self) -> Result<(), Error> {
|
||||
let kver = std::process::Command::new("uname")
|
||||
.arg("-r")
|
||||
.output()
|
||||
.map_err(|err| format_err!("failed to retrieve running kernel version - {err}"))?;
|
||||
|
||||
let output = std::process::Command::new("dkms")
|
||||
.arg("status")
|
||||
.arg("-k")
|
||||
.arg(std::str::from_utf8(&kver.stdout)?)
|
||||
.output();
|
||||
match output {
|
||||
Err(_err) => self.output.log_skip("could not get dkms status")?,
|
||||
Ok(ret) => {
|
||||
let num_dkms_modules = std::str::from_utf8(&ret.stdout)?.lines().count();
|
||||
if num_dkms_modules == 0 {
|
||||
self.output.log_pass("no dkms modules found")?;
|
||||
} else {
|
||||
self.output
|
||||
.log_warn("dkms modules found, this might cause issues during upgrade.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_misc(&mut self) -> Result<(), Error> {
|
||||
self.output.print_header("MISCELLANEOUS CHECKS")?;
|
||||
self.check_pbs_services()?;
|
||||
self.check_time_sync()?;
|
||||
self.check_apt_repos()?;
|
||||
self.check_bootloader()?;
|
||||
self.check_dkms_modules()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn summary(&mut self) -> Result<(), Error> {
|
||||
self.output.print_summary()
|
||||
}
|
||||
|
||||
fn check_repo_file(
|
||||
&mut self,
|
||||
found_suite: &mut Option<(String, String)>,
|
||||
mismatches: &mut Vec<(String, String)>,
|
||||
strange_suite: &mut bool,
|
||||
test_repos: &mut Vec<(String, String, String)>,
|
||||
repo_file: APTRepositoryFile,
|
||||
) -> Result<(), Error> {
|
||||
for repo in repo_file.repositories {
|
||||
if !repo.enabled || repo.types == [APTRepositoryPackageType::DebSrc] {
|
||||
continue;
|
||||
}
|
||||
for suite in &repo.suites {
|
||||
let suite = match suite.find(&['-', '/'][..]) {
|
||||
Some(n) => &suite[0..n],
|
||||
None => suite,
|
||||
};
|
||||
|
||||
if suite != OLD_SUITE && suite != NEW_SUITE {
|
||||
let location = repo_file.path.clone().unwrap_or_default();
|
||||
self.output.log_notice(format!(
|
||||
"found unusual suite '{suite}', neither old '{OLD_SUITE}' nor new \
|
||||
'{NEW_SUITE}'..\n Affected file {location}\n Please \
|
||||
assure this is shipping compatible packages for the upgrade!"
|
||||
))?;
|
||||
*strange_suite = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((ref current_suite, ref current_location)) = found_suite {
|
||||
let location = repo_file.path.clone().unwrap_or_default();
|
||||
if suite != current_suite {
|
||||
if mismatches.is_empty() {
|
||||
mismatches.push((current_suite.clone(), current_location.clone()));
|
||||
mismatches.push((suite.to_string(), location));
|
||||
} else {
|
||||
mismatches.push((suite.to_string(), location));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let location = repo_file.path.clone().unwrap_or_default();
|
||||
*found_suite = Some((suite.to_string(), location));
|
||||
}
|
||||
|
||||
for component in &repo.components {
|
||||
if component == "pbstest" || component == "pbs-test" {
|
||||
let location = repo_file.path.clone().unwrap_or_default();
|
||||
test_repos.push((component.to_string(), suite.to_string(), location));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_systemd_unit_state(
|
||||
&mut self,
|
||||
unit: &str,
|
||||
) -> Result<(SystemdUnitState, SystemdUnitState), Error> {
|
||||
let output = std::process::Command::new("systemctl")
|
||||
.arg("is-enabled")
|
||||
.arg(unit)
|
||||
.output()
|
||||
.map_err(|err| format_err!("failed to execute - {err}"))?;
|
||||
|
||||
let enabled_state = match output.stdout.as_slice() {
|
||||
b"enabled\n" => SystemdUnitState::Enabled,
|
||||
b"disabled\n" => SystemdUnitState::Disabled,
|
||||
_ => SystemdUnitState::Unknown,
|
||||
};
|
||||
|
||||
let output = std::process::Command::new("systemctl")
|
||||
.arg("is-active")
|
||||
.arg(unit)
|
||||
.output()
|
||||
.map_err(|err| format_err!("failed to execute - {err}"))?;
|
||||
|
||||
let active_state = match output.stdout.as_slice() {
|
||||
b"active\n" => SystemdUnitState::Active,
|
||||
b"inactive\n" => SystemdUnitState::Inactive,
|
||||
b"failed\n" => SystemdUnitState::Failed,
|
||||
_ => SystemdUnitState::Unknown,
|
||||
};
|
||||
Ok((enabled_state, active_state))
|
||||
}
|
||||
|
||||
fn check_pbs_services(&mut self) -> Result<(), Error> {
|
||||
self.output.log_info("Checking PBS daemon services..")?;
|
||||
|
||||
for service in ["proxmox-backup.service", "proxmox-backup-proxy.service"] {
|
||||
match self.get_systemd_unit_state(service)? {
|
||||
(_, SystemdUnitState::Active) => {
|
||||
self.output
|
||||
.log_pass(format!("systemd unit '{service}' is in state 'active'"))?;
|
||||
}
|
||||
(_, SystemdUnitState::Inactive) => {
|
||||
self.output.log_fail(format!(
|
||||
"systemd unit '{service}' is in state 'inactive'\
|
||||
\n Please check the service for errors and start it.",
|
||||
))?;
|
||||
}
|
||||
(_, SystemdUnitState::Failed) => {
|
||||
self.output.log_fail(format!(
|
||||
"systemd unit '{service}' is in state 'failed'\
|
||||
\n Please check the service for errors and start it.",
|
||||
))?;
|
||||
}
|
||||
(_, _) => {
|
||||
self.output.log_fail(format!(
|
||||
"systemd unit '{service}' is not in state 'active'\
|
||||
\n Please check the service for errors and start it.",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_time_sync(&mut self) -> Result<(), Error> {
|
||||
self.output
|
||||
.log_info("Checking for supported & active NTP service..")?;
|
||||
if self.get_systemd_unit_state("systemd-timesyncd.service")?.1 == SystemdUnitState::Active {
|
||||
self.output.log_warn(
|
||||
"systemd-timesyncd is not the best choice for time-keeping on servers, due to only \
|
||||
applying updates on boot.\
|
||||
\n While not necessary for the upgrade it's recommended to use one of:\
|
||||
\n * chrony (Default in new Proxmox Backup Server installations)\
|
||||
\n * ntpsec\
|
||||
\n * openntpd"
|
||||
)?;
|
||||
} else if self.get_systemd_unit_state("ntp.service")?.1 == SystemdUnitState::Active {
|
||||
self.output.log_info(
|
||||
"Debian deprecated and removed the ntp package for Bookworm, but the system \
|
||||
will automatically migrate to the 'ntpsec' replacement package on upgrade.",
|
||||
)?;
|
||||
} else if self.get_systemd_unit_state("chrony.service")?.1 == SystemdUnitState::Active
|
||||
|| self.get_systemd_unit_state("openntpd.service")?.1 == SystemdUnitState::Active
|
||||
|| self.get_systemd_unit_state("ntpsec.service")?.1 == SystemdUnitState::Active
|
||||
{
|
||||
self.output
|
||||
.log_pass("Detected active time synchronisation unit")?;
|
||||
} else {
|
||||
self.output.log_warn(
|
||||
"No (active) time synchronisation daemon (NTP) detected, but synchronized systems \
|
||||
are important!",
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum SystemdUnitState {
|
||||
Active,
|
||||
Enabled,
|
||||
Disabled,
|
||||
Failed,
|
||||
Inactive,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Counters {
|
||||
pass: u64,
|
||||
skip: u64,
|
||||
notice: u64,
|
||||
warn: u64,
|
||||
fail: u64,
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
Pass,
|
||||
Info,
|
||||
Skip,
|
||||
Notice,
|
||||
Warn,
|
||||
Fail,
|
||||
}
|
||||
|
||||
struct ConsoleOutput {
|
||||
stream: StandardStream,
|
||||
first_header: bool,
|
||||
counters: Counters,
|
||||
}
|
||||
|
||||
impl ConsoleOutput {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stream: StandardStream::stdout(ColorChoice::Always),
|
||||
first_header: true,
|
||||
counters: Counters::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_header(&mut self, message: &str) -> Result<(), Error> {
|
||||
if !self.first_header {
|
||||
writeln!(&mut self.stream)?;
|
||||
}
|
||||
self.first_header = false;
|
||||
writeln!(&mut self.stream, "= {message} =\n")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, color: Color, bold: bool) -> Result<(), Error> {
|
||||
self.stream
|
||||
.set_color(ColorSpec::new().set_fg(Some(color)).set_bold(bold))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), std::io::Error> {
|
||||
self.stream.reset()
|
||||
}
|
||||
|
||||
pub fn log_line(&mut self, level: LogLevel, message: &str) -> Result<(), Error> {
|
||||
match level {
|
||||
LogLevel::Pass => {
|
||||
self.counters.pass += 1;
|
||||
self.set_color(Color::Green, false)?;
|
||||
writeln!(&mut self.stream, "PASS: {}", message)?;
|
||||
}
|
||||
LogLevel::Info => {
|
||||
writeln!(&mut self.stream, "INFO: {}", message)?;
|
||||
}
|
||||
LogLevel::Skip => {
|
||||
self.counters.skip += 1;
|
||||
writeln!(&mut self.stream, "SKIP: {}", message)?;
|
||||
}
|
||||
LogLevel::Notice => {
|
||||
self.counters.notice += 1;
|
||||
self.set_color(Color::White, true)?;
|
||||
writeln!(&mut self.stream, "NOTICE: {}", message)?;
|
||||
}
|
||||
LogLevel::Warn => {
|
||||
self.counters.warn += 1;
|
||||
self.set_color(Color::Yellow, false)?;
|
||||
writeln!(&mut self.stream, "WARN: {}", message)?;
|
||||
}
|
||||
LogLevel::Fail => {
|
||||
self.counters.fail += 1;
|
||||
self.set_color(Color::Red, true)?;
|
||||
writeln!(&mut self.stream, "FAIL: {}", message)?;
|
||||
}
|
||||
}
|
||||
self.reset()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log_pass<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
|
||||
self.log_line(LogLevel::Pass, message.as_ref())
|
||||
}
|
||||
|
||||
pub fn log_info<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
|
||||
self.log_line(LogLevel::Info, message.as_ref())
|
||||
}
|
||||
|
||||
pub fn log_skip<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
|
||||
self.log_line(LogLevel::Skip, message.as_ref())
|
||||
}
|
||||
|
||||
pub fn log_notice<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
|
||||
self.log_line(LogLevel::Notice, message.as_ref())
|
||||
}
|
||||
|
||||
pub fn log_warn<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
|
||||
self.log_line(LogLevel::Warn, message.as_ref())
|
||||
}
|
||||
|
||||
pub fn log_fail<T: AsRef<str>>(&mut self, message: T) -> Result<(), Error> {
|
||||
self.log_line(LogLevel::Fail, message.as_ref())
|
||||
}
|
||||
|
||||
pub fn print_summary(&mut self) -> Result<(), Error> {
|
||||
self.print_header("SUMMARY")?;
|
||||
|
||||
let total = self.counters.fail
|
||||
+ self.counters.pass
|
||||
+ self.counters.notice
|
||||
+ self.counters.skip
|
||||
+ self.counters.warn;
|
||||
|
||||
writeln!(&mut self.stream, "TOTAL: {total}")?;
|
||||
self.set_color(Color::Green, false)?;
|
||||
writeln!(&mut self.stream, "PASSED: {}", self.counters.pass)?;
|
||||
self.reset()?;
|
||||
writeln!(&mut self.stream, "SKIPPED: {}", self.counters.skip)?;
|
||||
writeln!(&mut self.stream, "NOTICE: {}", self.counters.notice)?;
|
||||
if self.counters.warn > 0 {
|
||||
self.set_color(Color::Yellow, false)?;
|
||||
writeln!(&mut self.stream, "WARNINGS: {}", self.counters.warn)?;
|
||||
}
|
||||
if self.counters.fail > 0 {
|
||||
self.set_color(Color::Red, true)?;
|
||||
writeln!(&mut self.stream, "FAILURES: {}", self.counters.fail)?;
|
||||
}
|
||||
if self.counters.warn > 0 || self.counters.fail > 0 {
|
||||
let (color, bold) = if self.counters.fail > 0 {
|
||||
(Color::Red, true)
|
||||
} else {
|
||||
(Color::Yellow, false)
|
||||
};
|
||||
|
||||
self.set_color(color, bold)?;
|
||||
writeln!(
|
||||
&mut self.stream,
|
||||
"\nATTENTION: Please check the output for detailed information!",
|
||||
)?;
|
||||
if self.counters.fail > 0 {
|
||||
writeln!(
|
||||
&mut self.stream,
|
||||
"Try to solve the problems one at a time and rerun this checklist tool again.",
|
||||
)?;
|
||||
}
|
||||
}
|
||||
self.reset()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn test_is_kernel_version_compatible(
|
||||
expected_versions: &[&str],
|
||||
unexpected_versions: &[&str],
|
||||
upgraded: bool,
|
||||
) {
|
||||
let checker = Checker {
|
||||
output: ConsoleOutput::new(),
|
||||
upgraded,
|
||||
};
|
||||
|
||||
for version in expected_versions {
|
||||
assert!(
|
||||
checker.is_kernel_version_compatible(version),
|
||||
"compatible kernel version '{version}' did not pass as expected!"
|
||||
);
|
||||
}
|
||||
for version in unexpected_versions {
|
||||
assert!(
|
||||
!checker.is_kernel_version_compatible(version),
|
||||
"incompatible kernel version '{version}' passed as expected!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_upgrade_kernel_version_compatibility() {
|
||||
let expected_versions = &["6.2.16-20-pve", "6.5.13-6-pve", "6.8.12-1-pve"];
|
||||
let unexpected_versions = &["6.1.10-1-pve", "5.19.17-2-pve"];
|
||||
|
||||
test_is_kernel_version_compatible(expected_versions, unexpected_versions, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_after_upgrade_kernel_version_compatibility() {
|
||||
let expected_versions = &["6.14.6-1-pve", "6.17.0-1-pve"];
|
||||
let unexpected_versions = &["6.12.1-1-pve", "6.2.1-1-pve"];
|
||||
|
||||
test_is_kernel_version_compatible(expected_versions, unexpected_versions, true);
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,9 @@ Ext.define('PBS.LoginView', {
|
||||
|
||||
init: async function () {
|
||||
if (Proxmox.consentText !== '') {
|
||||
if (Proxmox.Utils.getOpenIDRedirectionAuthorization() !== undefined) {
|
||||
return; // avoid showing the banner another time after OIDC login redirection.
|
||||
}
|
||||
Ext.create('Proxmox.window.ConsentModal', {
|
||||
autoShow: true,
|
||||
consent: Proxmox.Markdown.parse(
|
||||
|
||||
@ -3,10 +3,18 @@ const proxmoxOnlineHelpInfo = {
|
||||
"link": "/docs/index.html",
|
||||
"title": "Proxmox Backup Server Documentation Index"
|
||||
},
|
||||
"client-usage": {
|
||||
"link": "/docs/backup-client.html#client-usage",
|
||||
"title": "Backup Client Usage"
|
||||
},
|
||||
"client-repository": {
|
||||
"link": "/docs/backup-client.html#client-repository",
|
||||
"title": "Backup Repository Locations"
|
||||
},
|
||||
"statically-linked-client": {
|
||||
"link": "/docs/backup-client.html#statically-linked-client",
|
||||
"title": "Statically Linked Backup Client"
|
||||
},
|
||||
"environment-variables": {
|
||||
"link": "/docs/backup-client.html#environment-variables",
|
||||
"title": "Environment Variables"
|
||||
|
||||
@ -11,16 +11,19 @@ Ext.define('PBS.WebauthnConfigView', {
|
||||
header: gettext('Relying Party'),
|
||||
required: true,
|
||||
defaultValue: gettext('Not configured'),
|
||||
renderer: Ext.htmlEncode,
|
||||
},
|
||||
origin: {
|
||||
header: gettext('Origin'),
|
||||
required: true,
|
||||
defaultValue: gettext('Not configured'),
|
||||
renderer: Ext.htmlEncode,
|
||||
},
|
||||
id: {
|
||||
header: 'ID',
|
||||
required: true,
|
||||
defaultValue: gettext('Not configured'),
|
||||
renderer: Ext.htmlEncode,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user