From: Thomas Lamprecht <t.lamprecht@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH 7/7] docs: client: document scheduled backup jobs
Date: Wed, 14 Oct 2026 12:51:36 +0200 [thread overview]
Message-ID: <20261014105208.2747208-8-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20261014105208.2747208-1-t.lamprecht@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
docs/backup-client.rst | 274 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 274 insertions(+)
diff --git a/docs/backup-client.rst b/docs/backup-client.rst
index 39b1f9be0..c224cc19c 100644
--- a/docs/backup-client.rst
+++ b/docs/backup-client.rst
@@ -210,6 +210,280 @@ Additionally, system credentials (e.g. passed down from the hypervisor to a
virtual machine via SMBIOS type 11) can be loaded on a service via
`LoadCredential=` as described in the manual page ``systemd.exec(5)``.
+.. _client_scheduled_backups:
+
+Scheduled Backups
+-----------------
+
+The backup client has a built-in scheduler that runs backup jobs at regular
+intervals, without :manpage:`cron(8)` or a systemd timer for each job. A job is
+a named configuration holding everything a backup run needs: the target
+repository, the backup sources, the encryption key, and optionally a script
+that decides whether a run should proceed.
+
+Jobs are configured per user and stored in that user's XDG directories. Manage
+the jobs as the user that should run them, for example as ``root`` for a backup
+that has to read every file on the system.
+
+``$XDG_CONFIG_HOME/proxmox-backup/jobs.cfg``
+ The job definitions, in section configuration format. The format has no
+ comment lines and cannot store values with leading or trailing spaces, so the
+ ``job`` subcommands reject such values. If you edit the file by hand, do not
+ change jobs with the ``job`` subcommands at the same time, as they rewrite
+ the whole file.
+
+ If the file does not parse, the ``job`` subcommands report the error and
+ the scheduler refuses to start, until you fix the file.
+
+``$XDG_STATE_HOME/proxmox-backup/jobstates/backup-job-<id>.json``
+ The outcome of the job's most recent run, see `Job State and Concurrency`_.
+
+``$XDG_STATE_HOME/proxmox-backup/scheduler.pid``
+ The process ID of the running scheduler.
+
+Without these environment variables, the paths default to
+``~/.config/proxmox-backup/`` and ``~/.local/state/proxmox-backup/``. The
+configuration and state files are created with mode ``0600``, as they record
+where the secrets of a job come from.
+
+Managing Jobs
+~~~~~~~~~~~~~
+
+The ``proxmox-backup-client job`` subcommands create, list, show, update,
+remove, and run jobs. For example, the following command creates a job:
+
+.. code-block:: console
+
+ # proxmox-backup-client job create nightly \
+ --schedule '02:30' \
+ --server pbs.example.com --datastore store1 --auth-id 'backup@pbs!nightly' \
+ --backupspec root.pxar:/ --backupspec home.pxar:/home \
+ --exclude /var/cache --exclude /var/tmp \
+ --keyfile /root/.config/proxmox-backup/key.json \
+ --password-file /run/secrets/pbs-token \
+ --pre-run-hook /usr/local/bin/pbs-check-battery.sh \
+ --comment 'nightly laptop backup'
+
+As with ``proxmox-backup-client backup``, you can name the target repository
+with the separate ``--server``, ``--port``, ``--datastore``, and ``--auth-id``
+options, as above, or as a single URL with ``--repository``.
+
+All paths in a job must be absolute: the backup sources, the key file, the
+files holding secrets, the ``--include-dev`` mount points, and the pre-run
+hook. The scheduler does not share the working directory and ``$PATH`` of your
+shell, so a relative path could resolve to a different file. The patterns of
+``--exclude`` are the exception, as they are matched relative to the backup
+source.
+
+The other subcommands inspect and change existing jobs:
+
+.. code-block:: console
+
+ # proxmox-backup-client job list
+ # proxmox-backup-client job show nightly
+ # proxmox-backup-client job update nightly --schedule 'mon..fri 02:30'
+ # proxmox-backup-client job update nightly --delete comment --delete pre-run-hook
+ # proxmox-backup-client job disable nightly
+ # proxmox-backup-client job enable nightly
+ # proxmox-backup-client job run nightly
+ # proxmox-backup-client job run nightly --dry-run
+ # proxmox-backup-client job remove nightly
+
+A dry run shows what the job would back up without uploading anything. It
+still runs the pre-run hook and connects to the server, but it records no run,
+so the last result and the next run of the job stay as they were.
+
+``update`` accepts the same options as ``create`` and changes only the options
+you give. ``--delete <property>`` clears an optional setting. Setting
+``--repository`` on a job that uses the separate options clears them, and the
+other way around, so you can switch between both forms in one call.
+
+A disabled job keeps its configuration, but the scheduler does not start it.
+You can still run it with ``job run``.
+
+The ``--rate`` and ``--burst`` options limit the bandwidth of a job, and take
+the same values as for ``proxmox-backup-client backup``:
+
+.. code-block:: console
+
+ # proxmox-backup-client job update nightly --rate 10MB --burst 20MB
+
+Unattended Authentication
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A scheduled run cannot ask for a password, so a job names the source of each
+secret it needs with the following options. A secret configured in the job
+takes precedence over the corresponding ``PBS_*`` environment variables.
+
+``--password-file <path>`` / ``--password-cmd <command>``
+ The source of the repository password or API token secret: the first line of
+ the file, or the first line that the command prints. Use only one of them.
+ The command runs without a shell, so give it as an absolute path, and use
+ ``/bin/sh -c '...'`` for shell syntax. If the command fails, the run fails
+ with its error. An empty password is not caught early, and shows up as an
+ authentication failure. For example, read a file that systemd decrypted with
+ ``--password-file /run/secrets/pbs-token``, or ask a password manager with
+ ``--password-cmd '/usr/bin/pass show pbs/nightly'``.
+
+ The client waits for the command without a time limit, and a hanging command
+ blocks one of the two backups that the scheduler runs at a time. If the
+ command can hang, give it a timeout of its own, for example
+ ``--password-cmd '/usr/bin/timeout 30 /usr/bin/pass show pbs/nightly'``.
+
+``--encryption-password-file <path>`` / ``--encryption-password-cmd <command>``
+ The same for the passphrase of the key file, which the client otherwise reads
+ from :envvar:`PBS_ENCRYPTION_PASSWORD`. A job needs one of them if its key
+ file is protected by a passphrase.
+
+``--fingerprint <sha256>``
+ Pins the fingerprint of the server certificate, in the colon-separated
+ hexadecimal form that the web interface shows. Use it if the system does not
+ trust the certificate of the server, for example a self-signed one. A
+ scheduled run cannot ask you to confirm an unknown fingerprint, so without
+ this option it fails, unless you already accepted the fingerprint in an
+ interactive run as the same user, for example with ``job run``.
+
+Schedules
+~~~~~~~~~
+
+The ``--schedule`` option accepts the same calendar events as the jobs of the
+server, see :ref:`calendar-event-scheduling` for the syntax. Some common
+schedules:
+
+======================= ==============================================
+Schedule Meaning
+======================= ==============================================
+``hourly`` At the top of every hour.
+``daily`` At 00:00 every day.
+``02:30`` Every day at 02:30.
+``mon..fri 02:30`` Weekdays at 02:30.
+``*/15`` Every 15 minutes.
+``sat *-*-1..7 03:00`` First Saturday of each month at 03:00.
+======================= ==============================================
+
+The next run is computed from the end of the previous one. A run missed while
+the machine was switched off therefore starts shortly after the scheduler comes
+back up, rather than at the next occurrence. The scheduler checks the schedules
+at least once a minute, so a run may start up to a minute late.
+
+A run that fails or is interrupted, for example because the machine was
+switched off, is not retried. The job runs again at its next scheduled time,
+or right away with ``job run``.
+
+``create`` and ``update`` reject a schedule without future events, such as a
+date in the past.
+
+The Scheduler Daemon
+~~~~~~~~~~~~~~~~~~~~
+
+Jobs only run while a scheduler is running, and creating a job does not start
+one. The recommended way to run it is the systemd user service that the client
+package ships. The service is not enabled by default, as a backup daemon should
+only run for users that configured a job:
+
+.. code-block:: console
+
+ # systemctl --user enable --now proxmox-backup-client-scheduler
+
+The scheduler rereads ``jobs.cfg`` at least once a minute. To apply a change at
+once, run ``systemctl --user reload proxmox-backup-client-scheduler``. If no
+scheduler is running when you create or enable a job, the subcommand prints how
+to start one.
+
+If ``jobs.cfg`` stops parsing while the scheduler runs, the scheduler keeps
+running but starts no new runs until you fix the file. Runs already in progress
+finish.
+
+A run started by the scheduler first checks that the job is still due, and does
+nothing if the job was disabled, removed, or has already run in the meantime. A
+manual ``job run`` always runs the job, regardless of its schedule.
+
+On ``SIGTERM``, the scheduler starts no new runs and waits for the running ones
+to finish. A second ``SIGTERM`` makes it exit without waiting. ``systemctl
+--user stop`` therefore lets a running backup finish, up to the
+``TimeoutStopSec`` of the service.
+
+Each run is a separate ``proxmox-backup-client job run <id>`` process with its
+own environment, so that the password of one job cannot leak into another. The
+runs log to the journal of the user service. At most two backups run at the
+same time. Jobs that have to wait start as soon as another run finishes,
+longest waiting first.
+
+Only one scheduler can run per user. A second one refuses to start.
+
+.. Note:: A systemd user service only runs while its user is logged in, unless
+ `lingering <https://www.freedesktop.org/software/systemd/man/loginctl.html>`_
+ is enabled for that user with ``loginctl enable-linger <username>``.
+
+Job State and Concurrency
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Each run records its outcome in the state file of the job. ``job list`` and
+``job show`` display it in the ``last-run`` and ``last-result`` columns, and the
+time the job is due next in the ``next-run`` column. In ``job list``,
+``next-run`` reads ``disabled`` for a disabled job and ``never`` for a schedule
+without future events. ``last-result`` is one of the following:
+
+``created``
+ The job has not run yet.
+
+``running``
+ A run is in progress.
+
+``ok``
+ The last run succeeded.
+
+``failed``
+ The last run failed. ``job show`` prints the error.
+
+``skipped``
+ The pre-run hook skipped the last run, so no backup was made.
+
+``interrupted``
+ The last run never finished, for example because the machine was switched
+ off or the process was killed.
+
+Without a readable state file, ``last-run`` and ``last-result`` stay empty, and
+the job is due right away.
+
+A job never runs twice at the same time. Starting ``job run`` for a job that is
+already running fails after a few seconds, and ``job remove`` refuses to remove
+a running job.
+
+Pre-Run Hooks
+~~~~~~~~~~~~~
+
+The ``--pre-run-hook <path>`` option names an executable, by its absolute path,
+that decides by its exit status whether a run proceeds:
+
+- ``0``: The backup proceeds.
+- ``1``: This run is skipped and recorded as ``skipped``. The schedule continues
+ as usual.
+- Any other status, or a hook killed by a signal, fails the run, so that a
+ broken hook is not mistaken for a deliberate skip.
+
+The hook gets the job ID in the ``PBS_JOB_ID`` environment variable, runs in
+``/`` without standard input, and does not see the ``PBS_PASSWORD*``,
+``PBS_ENCRYPTION_PASSWORD*``, and ``CREDENTIALS_DIRECTORY`` variables, as it
+does not need the secrets of the backup. Its output goes to the log of the run.
+
+Hooks typically skip a run under conditions that a schedule cannot express, for
+example:
+
+- while the machine runs on battery, or the battery is low
+- while the backup server is not reachable
+- during working hours
+
+A hook may take up to one minute. After that, the client stops it along with
+any processes it started, and the run fails.
+
+The client packages ship an example hook that skips a backup when the battery
+is low. It is ``examples/pbs-check-battery.sh`` in the documentation directory
+of the installed package, ``/usr/share/doc/proxmox-backup-client/`` or
+``/usr/share/doc/proxmox-backup-client-static/``. Copy it to a stable path such
+as ``/usr/local/bin``, adjust the thresholds, and point ``--pre-run-hook`` at
+the copy.
+
Output Format
-------------
--
2.47.3
next prev parent reply other threads:[~2026-10-21 20:56 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 10:51 [PATCH 0/7] client: add scheduled backup jobs Thomas Lamprecht
2026-10-14 10:51 ` [PATCH 1/7] client: backup specification: widen BackupDetectionMode derives Thomas Lamprecht
2026-10-14 10:51 ` [PATCH 2/7] client: job: add backup job config and state types Thomas Lamprecht
2026-10-14 10:51 ` [PATCH 3/7] client: job: add job runner and scheduled backup daemon Thomas Lamprecht
2026-10-14 10:51 ` [PATCH 4/7] client: job: add job management CLI subcommands Thomas Lamprecht
2026-10-14 10:51 ` [PATCH 5/7] client: job: ship systemd user unit for the scheduler Thomas Lamprecht
2026-10-14 10:51 ` [PATCH 6/7] client: job: add battery-check example pre-run hook Thomas Lamprecht
2026-10-14 10:51 ` Thomas Lamprecht [this message]
2026-10-22 12:58 ` [PATCH 0/7] client: add scheduled backup jobs Christian Ebner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261014105208.2747208-8-t.lamprecht@proxmox.com \
--to=t.lamprecht@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.