# Schedules and source groups Two related but separable ideas: - A **source group** is a named bundle of "what to back up": include paths, exclude patterns, retention policy, retry configuration, optional pre/post hooks. The group's name is used as the restic snapshot tag, so retention can target it with `restic forget --tag `. - A **schedule** is a cron expression that, when it fires, triggers a backup of one or more source groups on a host. Decoupling them means you can have one schedule covering several groups (e.g. `0 1 * * *` running both `system` and `data`), and each group has its own retention without duplicating policy across schedules. ## Source group anatomy ```yaml name: data includes: - /var/lib/postgresql - /home excludes: - /home/*/.cache - /home/*/Downloads retention: keep_last: 7 keep_daily: 14 keep_weekly: 4 keep_monthly: 6 retry_max: 3 retry_backoff_seconds: 600 pre_hook: | pg_dump -U postgres -F c -f /var/lib/postgresql/dumps/all.dump post_hook: | rm -f /var/lib/postgresql/dumps/all.dump ``` ### Conflict detection If your retention policy says `keep_hourly: 24` but no schedule points at this group sub-daily, the UI surfaces a **conflict-dimension banner** ("`hourly` won't be honoured — no schedule fires more often than once a day"). The flag is stored on the source group (`conflict_dimension`) and refreshed whenever a schedule or group changes. ### Hooks `pre_hook` and `post_hook` run on the agent host inside `/bin/sh -c` (`cmd.exe /C` on Windows). Output is streamed back to the live job log as `hook(): …` lines. - A non-zero `pre_hook` exit aborts the backup. - `post_hook` always runs, with `RM_JOB_STATUS=succeeded|failed` in the environment. Use this for cleanup that must happen whether the backup worked or not. - Hooks only run for `kind=backup` jobs. They do not run for `forget`, `prune`, `check`, etc. - AEAD-encrypted at rest at the HTTP layer; the agent receives plaintext over the WS channel. A "host default" pair of hooks lives on the host itself; a source group's own hooks override them when set. ## Schedule anatomy ```yaml cron: "0 2 * * *" enabled: true source_group_ids: - - ``` Slim by design: a schedule says **when** and **which groups**. Everything else (paths, retention, hooks) lives on the groups. The agent's local cron fires the schedule. If the WebSocket is down at fire time, the server queues the firing into `pending_runs` and drains it on the next agent reconnect — a short network blip won't lose the backup. ### Last / next run The schedules tab shows "next" (computed by parsing the cron expression with `robfig/cron/v3`) and "last" (the latest `actor_kind=schedule` job in the `jobs` table) for every schedule. The dashboard host row also surfaces `next 12h ago/from now` when a single covering schedule is the run-now candidate. ## Bandwidth limits Two places set restic's `--limit-upload` / `--limit-download`: 1. **Host-wide caps** on the host row (`bandwidth_up_kbps`, `bandwidth_down_kbps`). Pushed to the agent on hello and after `PUT /api/hosts/{id}/bandwidth`. Apply to every restic invocation on the host. 2. **Per-job overrides** on the per-source-group Run-now form. Win over host caps for the lifetime of that one job. If neither is set, restic runs unthrottled.