Compare commits

...

38 Commits

Author SHA1 Message Date
Thomas Lamprecht
4be9fd98f2 bump version to 3.4.8-3
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2026-02-18 15:55:15 +01:00
Thomas Lamprecht
abcfbac411 update proxmox-rest-server to 0.8.10
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2026-02-18 15:55:11 +01:00
Thomas Lamprecht
7016c96b28 bump version to 3.4.8-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-12-22 17:32:18 +01:00
Thomas Lamprecht
f42b10bcfb update proxmox-apt and related api-types
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-12-22 17:31:44 +01:00
Thomas Lamprecht
3c27db8a53 bump version to 3.4.8-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-10-27 18:07:27 +01:00
Thomas Lamprecht
b13f9efc5e d/changelog: improve historic entry
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-10-27 18:07:27 +01:00
Dominik Csapak
d90d7ff230 api: admin: datastore: optimize groups api call
Currently we always touch all files for each snapshot in a group when
listing them, even though we don't need all that info.

We're only interested in getting either the last finished snapshot
information, or the last unfinished one (which must the only one in
normal use, we can't have multiple unfinished snapshots usually)

Instead of getting all the information upfront, use the snapshot
iterator of the group to get only the id, sort them by time, and
use the first we're interested in, getting the snapshot specific info
only for those we want to check.

In my (admittedly extreme) setup with ~600 groups with ~1000 snapshots
each, this changes the time this api call needs from ~40s to <1s on a
relatively fast disk.

While at it, lift the restriction of only returning groups with
snapshots in them, now returning also empty ones.

To keep api compatibility, use a timestamp of 0 for those, as no valid
backup could have been made at that time anyway.

This API call is currently used by the web UI to add the group notes
to the backup snapshot tree in the datastore content view, so while it
will help in a few cases, it will not make the listing of all
snapshots itself faster.

(cherry picked from commit e59d33f4ce)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20251008134344.3512958-7-d.csapak@proxmox.com
 [TL: add context for where the UI uses this API call]
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20251027103123.101013-4-c.ebner@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-10-27 17:33:30 +01:00
Dominik Csapak
7c7a05cf6b api: admin: datastore: factor out 'get_group_owner'
and change the `eprintln` to a `log::warn`

(cherry picked from commit 9af3b391b6)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20251008134344.3512958-6-d.csapak@proxmox.com
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20251027103123.101013-3-c.ebner@proxmox.com
2025-10-27 17:33:30 +01:00
Dominik Csapak
ca08eba0b3 api: admin: datastore: refactor BackupGroup to GroupListItem conversion
We will reuse this later.

No functionial change intended.

(cherry picked from commit 45693bfa7a)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20251008134344.3512958-5-d.csapak@proxmox.com
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20251027103123.101013-2-c.ebner@proxmox.com
2025-10-27 17:33:30 +01:00
Christian Ebner
c096925472 fix #6566: backup: api: conditionally drop group and snapshot locks
To guarantee consistency by possible concurrent operations, the
backup protocol locks the backup group, the previous backup
snapshot (if any) and holds a lock for the newly created backup
snapshot. All of these are currently stored in the backup worker
task, only released on its destruction.

The backup API however signals a successful backup via the return
status of the `finish` call, while still holding the locks.
Therefore, an immediate subsequent backup of the client to the same
group can fail because the locks cannot be acquired until the previous
backup task is completely destroyed, which can however outlive the
`finish` return for some time. This manifests in e.g. a push sync job
failing.

To fix this, store the lock guards inside the RPC environments shared
state instead, allowing to selectively drop the locks on successful
backup finish. On error, hold the locks until the cleanup was
successful.

Immediate verification of new snapshots already downgraded the lock
by dropping the exclusive lock and getting a shared lock. Since the
dropping is now already handled by the finish call, only gathering
the shared lock is required. While there is now a larger time window
for concurrent prunes, the underlying possible race between
verification and prune remains in place.

Backported from https://git.proxmox.com/?p=proxmox-backup.git;a=commit;h=b1ece6c70c7785191321525576ed6f53e9c4bc18

Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=6566
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/all/20251001112251.3788-1-c.ebner@proxmox.com
2025-10-02 10:00:19 +02:00
Fabian Grünbichler
48c7a01008 bump version to 3.4.7-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-10-01 13:53:36 +02:00
Fabian Grünbichler
0a9cb27e68 update to proxmox-auth-api 0.4.9
it includes fixes for the HTTP-only auth flow.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-10-01 13:52:32 +02:00
Stoiko Ivanov
847d39c353 pbs3to4: bootloader: only allow systemd-boot before upgrade when used
This carries over the changes from pve-manager:
7e168453 ("pve8to9: only allow systemd-boot when it is actually used before upgrade")

additionally it pushes the check for systemd-boot being present to the
bottom (there is an early return for the single case which is not
problematic (used by p-b-t on a system still on bookworm) - and logs a
failure with a link to the upgrade guide in all cases.
The previous suggestion of installing systemd-boot-tools and
systemd-boot-efi explicitly is not fitting when systemd-boot is not
really used for booting (mostly: secure-boot enabled p-b-t setups)

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Reviewed-by: Shannon Sterz <s.sterz@proxmox.com>
Tested-by: Shannon Sterz <s.sterz@proxmox.com>
Link: https://lore.proxmox.com/20250821141719.4130062-4-s.ivanov@proxmox.com
2025-08-22 15:41:33 +02:00
Stoiko Ivanov
2a36f7016c pbs3to4: use boolean variable for systemd-boot installation state
while it's not saving too much run-time wise (each branch stats the
file only once), it's a bit easier to read.

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Reviewed-by: Shannon Sterz <s.sterz@proxmox.com>
Tested-by: Shannon Sterz <s.sterz@proxmox.com>
Link: https://lore.proxmox.com/20250821141719.4130062-3-s.ivanov@proxmox.com
2025-08-22 15:41:33 +02:00
Stoiko Ivanov
e7beb64391 pbs3to4: fix logic error and typo in log message
/usr/share/doc/systemd-boot/changelog.Debian.gz existing indicates
that system-boot is installed (the `!` was in error).

additionally fix a typo in the log message.

Fixes: 94cc9903 ("bin: pbs3to4: adapt boot-loader checks to trixie")
Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Reviewed-by: Shannon Sterz <s.sterz@proxmox.com>
Tested-by: Shannon Sterz <s.sterz@proxmox.com>
Link: https://lore.proxmox.com/20250821141719.4130062-2-s.ivanov@proxmox.com
2025-08-22 15:41:33 +02:00
Fabian Grünbichler
f37354cea2 bump version to 3.4.6-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-08-13 13:11:58 +02:00
Dominik Csapak
aa829571cf ui: webauthn view: make sure renderers are html encoded
to avoid interpreting html elements in these options

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2025-08-13 13:07:19 +02:00
Fabian Grünbichler
d543ff8b27 bump network-api dep, add to d/control
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-08-13 11:10:14 +02:00
Dominik Csapak
d41a83268b fixup type move from pbs-api-types to proxmox-network-api
some types moved crate, so adapt here to the correct location, otherwise
it'll not build with newer pbs-api-types on bookworm

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Stefan Hanreich <s.hanreich@proxmox.com>
Link: https://lore.proxmox.com/all/20250813081959.1112692-1-d.csapak@proxmox.com
2025-08-13 11:04:00 +02:00
Fabian Grünbichler
5327a402a4 bump version to 3.4.5-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-08-11 15:26:05 +02:00
Fabian Grünbichler
524e5c09a0 pbs3to4: slightly reflow systemd-boot messages
these are rather long otherwise..

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
(cherry picked from commit e48de65ce1)
2025-08-11 12:54:04 +02:00
Fabian Grünbichler
cc50ee772a pbs3to4: reformat long messages
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
(cherry picked from commit 50a65d681d)
2025-08-11 12:54:04 +02:00
Stoiko Ivanov
cf8a658ea2 bin: pbs3to4: adapt boot-loader checks to trixie
basically carry-over the checks from pve-manger for pve8to9 [0]:
* 65ffcdd0 ("cli: pve8to9: rework boot-loader suggestions for trixie")
* 7cc36772 ("8 to 9 checks: do not ask bootctl if systemd-boot is used.")
* 2d79b567 ("8 to 9 checks: check for removable grub-install")

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Link: https://lore.proxmox.com/all/20250811091135.127299-1-s.ivanov@proxmox.com
(cherry picked from commit 94cc99033b)
2025-08-11 12:54:04 +02:00
Thomas Lamprecht
8ac8330818 d/control: recommend proxmox-network-interface-pinning package for PBS
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-08-06 13:03:22 +02:00
Thomas Lamprecht
9bcaa05731 bump version to 3.4.4-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-08-06 12:30:32 +02:00
Fiona Ebner
c6d5c11639 pbs3to4: clippy: allow always true comparision against minimal value
MIN_PBS_PKGREL is a constant that is zero, so the check is currently
superfluous, but that might not always be the case.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Link: https://lore.proxmox.com/20250806100941.59272-3-f.ebner@proxmox.com
2025-08-06 12:22:44 +02:00
Fiona Ebner
cf9334627c pbs3to4: add check for spelling of pbs-test repo
Logic copied and messages adapted from the pve8to9 checker script in
Proxmox VE.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Link: https://lore.proxmox.com/20250806100941.59272-2-f.ebner@proxmox.com
2025-08-06 12:22:44 +02:00
Thomas Lamprecht
f3d48c390b add pbs-network-config-commit systemd service
This mirrors the 'pvenetcommit' service from Proxmox VE, but is even a
bit simpler as we do not care about OVS.

Order the service after the 'pvenetcommit' service so that we ensure
that it can nicely work in a PVE + PBS co-installed environment, even
if one service (probably PVE) would get more complex in the future.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-29 20:08:48 +02:00
Dominik Csapak
b6a34622ee tape: increase timeout for moving medium in changer to 45 minutes
Increase the time out from the current 5 minutes to 45 minutes.
According to documentation from vendors (e.g., for HP see [0]) moving
a medium between slots or to/from a drive can take up a much longer
time than 5 minutes. (up to 38 minutes in a HP library) so increase
the timeout we use here to something that exceeds these
recommendations.

[0]: https://support.hpe.com/hpesc/public/docDisplay?docId=sd00001714en_us&page=GUID-D7147C7F-2016-0901-065E-00000000072C.html

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20250725113657.3815270-1-d.csapak@proxmox.com
 [TL: commit message fix-ups]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
(cherry picked from commit ac5bcc36a1)
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-25 15:58:43 +02:00
Thomas Lamprecht
6e5862770e ui: do not show consent banner twice for OIDC login
We unconditionally showed the consent banner when constructing the
login view, but for an OIDC based authentication flow the user might
visit that view twice, once when first loading the UI and the second
one when getting redirected back by their OIDC provider.

Checking if there was such an OIDC redirect and skip showing the
banner in that cases avoids this issue.

Fix is similar in principle to what we do for pve-manager when closing
issue #6311 but replaces the if guard with a reverse early-return.

Report: https://bugzilla.proxmox.com/show_bug.cgi?id=6311
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-22 07:45:11 +02:00
Thomas Lamprecht
48f936f1a8 ui: update online help info
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-22 07:43:34 +02:00
Thomas Lamprecht
d84d10125d bump version to 3.4.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-16 01:02:31 +02:00
Christian Ebner
566121ceb0 docs: rephrase and extend rate limiting description for sync jobs
Since commit 37a85cf6 ("fix: ui: sync job: edit rate limit based on
sync direction") rate limits for sync jobs can be correctly applied
for both directions. State this in the documentation and explicitley
mention the directions to reduce confusion.

Further, also mention the burst parameters, as they are not mentioned
at all.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20250623124543.590388-1-c.ebner@proxmox.com
2025-07-16 00:17:34 +02:00
Christian Ebner
484f096250 build: Adapt from pbs2to3 to pbs3to4
Build and package the new version of the upgrade check binary,
alongside the previous one, which is still good to have for
post-installation checks for those slow to upgrade from PBS 3.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20250714100841.25268-2-c.ebner@proxmox.com
 [TL: do not replace old checker tool on stable branch]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-15 22:26:21 +02:00
Thomas Lamprecht
241f4acb2a bin: add pbs3to4 upgrade check-list script
Copied over pbs2to3 as base and did minimal adaptions to expected code
names and package and kernel versions, might need more work though.

Link: https://lore.proxmox.com/20250714100841.25268-1-c.ebner@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-07-15 22:21:31 +02:00
Fabian Grünbichler
3122e40d94 fix static build
`cargo rustc` only passes the flags (like `target-feature` in this case) for
the final invocation, not for any dependency compilation.

unfortunately, switching to `cargo build` is not straight-forward:
- during a package build, $CARGO is the cargo wrapper which only honors
  RUSTFLAGS in its `prepare-debian` invocation
- rustflags in cargo's config.toml are global/per target
- the unstable override that would allow setting them per profile is broken
- and it would only work for the final invocation anyway, just like `cargo rustc`

as a stop-gap measure, let's duplicate and adapt the generated config.toml, and
select it explicitly when doing the static compilation as part of the package
build. manual `make proxmox-backup-client-static` can still just pass RUSTFLAGS
via the environment..

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
[WB: separate -i and -e in sed invocation, add -r, drop backslashes]
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-06-30 15:41:26 +02:00
Wolfgang Bumiller
c0368c11bf build: set OPENSSL_STATIC=1 when building static binaries
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
(cherry picked from commit 63ece39a17)
2025-06-30 15:21:51 +02:00
Wolfgang Bumiller
6b7ac16ebd build: replace .do-* helpers with grouped targets
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
(cherry picked from commit f2a4b46379)
2025-06-30 15:21:49 +02:00
25 changed files with 1091 additions and 109 deletions

View File

@ -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" ] }

View File

@ -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
View File

@ -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
View File

@ -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

View File

@ -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]

View File

@ -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
View File

@ -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

View File

@ -26,6 +26,7 @@ GENERATED_SYNOPSIS := \
MAN1_PAGES := \
pbs2to3.1 \
pbs3to4.1 \
pmt.1 \
pmtx.1 \
proxmox-backup-client.1 \

View File

@ -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),

View File

@ -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
View 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

View File

@ -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

View File

@ -1,6 +1,7 @@
include ../defines.mk
UNITS := \
pbs-network-config-commit.service \
proxmox-backup-daily-update.timer \
removable-device-attach@.service

View 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

View File

@ -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

View File

@ -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,
};

View File

@ -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!(

View File

@ -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() {

View File

@ -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",

View File

@ -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

View File

@ -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
View 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: &regex::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);
}
}

View File

@ -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(

View File

@ -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"

View File

@ -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,
},
},