Configuration

Copy the template and replace its CHANGE_ME values:

cp config.template.yaml config.yaml
uv run restic-backups --config config.yaml check-config

There is no default configuration path. Pass --config before the command, or set it once for the current shell:

export RESTIC_BACKUPS_CONFIG="$PWD/config.yaml"
uv run restic-backups check-config

To use an encrypted configuration, encrypt it with SOPS and enable decryption with either --sops or RESTIC_BACKUPS_SOPS=1:

cp config.template.yaml config.sops.yaml
sops config.sops.yaml
uv run restic-backups --config config.sops.yaml --sops check-config

export RESTIC_BACKUPS_CONFIG="$PWD/config.sops.yaml"
export RESTIC_BACKUPS_SOPS=1

See SOPS for age key setup, selective encryption, .sops.yaml, environment variables, and safe editing.

The values of id fields are user-defined names. They only need to be unique within their section; references in other sections must match them exactly. Every item may also have an optional description for user notes. Descriptions do not affect backup behavior.

Logging

restic-backups uses the standard Python logging framework and writes logs to stderr. Each entry includes a local ISO 8601 timestamp, level, logger name, and message:

2026-08-03T11:44:44+0900 INFO     restic_backups.github_repository.workflow: github-org-example: discovered 18 GitHub repositories

The default level for application loggers is INFO. Pass the global --verbose option before the subcommand, or set RESTIC_BACKUPS_VERBOSE=1, to include DEBUG entries such as the Restic command being executed:

uv run restic-backups --verbose job run documents

export RESTIC_BACKUPS_VERBOSE=1
uv run restic-backups job run documents

User-facing Restic stdout and stderr are routed through the same logger, so Restic output also receives timestamps and levels. Machine-readable internal calls remain quiet unless they fail. Credentials and repository passwords are not included in application log messages.

Prometheus

Job metrics are disabled by default. To enable them, deploy a Prometheus Pushgateway and set its URL in the process that runs restic-backups:

export RESTIC_BACKUPS_PROMETHEUS_PUSHGATEWAY_URL=http://pushgateway:9091
uv run restic-backups job run documents

This environment variable is sufficient; Prometheus settings do not belong in config.yaml or config.sops.yaml. Every completed job run pushes these gauges:

Metric Meaning
restic_backups_job_last_run_timestamp_seconds Completion time
restic_backups_job_last_run_duration_seconds Run duration
restic_backups_job_last_run_success Overall result (1 or 0)
restic_backups_job_repository_success Result for each destination (1 or 0)

Metrics are grouped by hostname and job ID. They also carry job_type and dry_run labels; the repository result additionally carries repository_id. Each push replaces the previous values for the same hostname and job ID. A Pushgateway connection failure is logged after a five-second timeout and does not change the backup result.

Prometheus must scrape the Pushgateway separately. For example:

scrape_configs:
  - job_name: pushgateway
    honor_labels: true
    static_configs:
      - targets:
          - pushgateway:9091

storage

storage describes the underlying place where repository data lives. Supported types are s3 for AWS S3 and S3-compatible services, and local for an existing local or mounted filesystem.

An S3 storage entry owns its service credentials:

storage:
  - id: backblaze-b2
    description: Backblaze B2 S3-compatible storage.
    enabled: true
    type: s3
    endpoint: https://s3.us-west-004.backblazeb2.com
    region: us-west-004
    credentials:
      access-key-id: "..."
      secret-access-key: "..."

Backblaze application key IDs and application keys use the same fields as AWS access keys. Several restic repositories may use one storage entry.

enabled defaults to true. Set it to false to keep a storage entry and all of its repositories visible while making those repositories unavailable for selection. This is useful for an unmounted disk or a temporarily unused cloud account. A repository is selectable only when both its own enabled field and its storage entry are enabled. Disabled storage may retain credential placeholders because it is not contacted.

A local storage entry gives an absolute filesystem root:

storage:
  - id: sandisk-2tb
    description: Mounted external backup disk.
    enabled: true
    type: local
    path: /Volumes/SanDisk-2TB

Commands that access a local repository fail before running restic when this path does not exist or is not a directory. This prevents an absent external disk from being silently replaced by a relative directory elsewhere.

restic-repositories

restic-repositories defines the encrypted repositories inside storage. Every entry links to one backend through storage-id and owns its restic password and optional cache directory.

An S3 repository selects a bucket and object-key prefix:

restic-repositories:
  - id: personal-b2
    description: Personal restic repository on Backblaze B2.
    storage-id: backblaze-b2
    enabled: true
    bucket: example-bucket
    key_prefix: voice-memos
    cache-dir: .restic-cache/personal-b2
    password: "..."

For S3, the storage endpoint identifies the service, region configures request signing, and the repository’s bucket plus key_prefix locate its data.

A local repository selects a relative directory inside the storage path:

restic-repositories:
  - id: personal-local
    description: Personal restic repository on the external disk.
    storage-id: sandisk-2tb
    enabled: true
    path: restic/personal
    cache-dir: .restic-cache/personal-local
    password: "..."

Local repository paths must be relative and cannot contain ... Restic creates the repository directory during initialization, but the storage root itself must already exist. Every restic repository needs its own encryption password, including repositories on local disks. Losing it makes the snapshots irrecoverable.

Disabled repositories, and repositories whose storage is disabled, are ignored by commands and may retain CHANGE_ME placeholders. check-config checks every available repository and reports any remaining placeholders. Interactive repository pickers make an inexpensive initialization check before displaying their choices. Uninitialized repositories remain visible as disabled and cannot be selected; the initialization picker is the exception. The check looks only for Restic’s config file, using an S3 metadata request for remote storage, and does not retrieve backup data.

cache-dir optionally selects restic’s local cache for this repository. ~ is expanded, and a relative path is resolved beside the selected config file. If omitted, restic uses its operating-system default. A repository-specific value takes precedence over RESTIC_CACHE_DIR from the process environment.

Every repository appears in the generic repository list table. generic repository init can initialize a selected available repository even when no backup references it; use --all to initialize every available repository. A job entry is required before using that repository with job run or generic restic run.

jobs

jobs defines every backup shown by job list and the Jobs TUI. Each job has a type, a type-specific source, and one or more destinations under restic-repository-ids. Every destination must match a restic-repositories.id value.

jobs:
  - job-id: documents
    type: files
    description: Back up local documents.
    restic-repository-ids:
      - personal-b2
      - personal-local
    source:
      paths:
        - ~/Documents
        - ~/Desktop
    # Optional; defaults to the job ID.
    # tag: documents

job-id is the stable name used to select the job in commands and menus. For a files job, source.paths is the non-empty list passed to restic backup; ~ is expanded to the current user’s home directory. Relative paths remain relative to the directory where the command is run.

When a job has one available repository, it starts selected in the TUI. When it has several, all start deselected and one or more must be selected with Space. Repositories on disabled storage remain visible with a storage disabled label but cannot be selected. Uninitialized repositories behave the same way with a not initialized label until they are initialized. Non-interactive commands must repeat --repository for every destination. Each repository remains independent and receives its own restic snapshot.

tag is optional restic metadata applied automatically to every snapshot created through that job. It defaults to job-id, so it normally does not need to be configured. Restic generates a unique snapshot ID after each successful backup; a snapshot ID is not a configuration value.

Run the configured backup with:

uv run restic-backups job run documents

Restic scans the configured paths and creates a new snapshot. It stores only new or changed file data, while the snapshot still represents the complete state of those paths at that time.

Several backups may share a restic repository. Their tags distinguish their snapshots inside it.

The supported types are files, github-repository, github-owner, and voice-memos. They all use the same job list, destination picker, run command, status table, snapshot tools, and audit result. The source mapping is the only type-specific part. See Jobs for all forms.

Voice Memos credentials

Speaker diarization uses a Hugging Face model token. Keep this job-specific credential with the external source that consumes it:

jobs:
  - job-id: voice-memos
    type: voice-memos
    restic-repository-ids:
      - personal-b2
    source:
      authentication:
        hugging-face:
          token: CHANGE_ME

Encrypt the token value with SOPS; the recommended .sops.yaml rule on the SOPS page includes this field. Alternatively, use {env: HF_TOKEN} or {file: /var/run/secrets/hugging-face/token}. Omit authentication to keep using HF_TOKEN or HUGGINGFACE_TOKEN directly from the process environment. The credential is resolved only by diarize and diarize-parallel; ordinary backups and transcription do not require it.

Credentials remain attached to what owns them: S3 credentials belong to storage, Restic passwords belong to restic-repositories, and external job-source credentials belong to jobs[].source.authentication. A global credential registry is deliberately unnecessary.

GitHub jobs

A GitHub repository job uses a GitHub source mapping:

jobs:
  - job-id: example-github-repository
    type: github-repository
    description: Back up Git history and selected GitHub data.
    restic-repository-ids:
      - personal-b2
      - personal-local
    source:
      repository-urls:
        - git@github.com:example/example-repository.git
        - git@github.com:example/another-repository.git
      authentication:
        api:
          token:
            env: GITHUB_MIGRATION_TOKEN
      components:
        git: true
        lfs: false
        wiki: false
        metadata: true
        release-assets: true
      migration-timeout-seconds: 3600

All component flags are required and at least one must be enabled. lfs requires git. On a laptop, omit authentication to use gh auth login and the normal Git/SSH configuration. Configure explicit Git or API credential sources only when those local credentials are unavailable, such as in a container or Kubernetes CronJob. Secret sources may be SOPS-encrypted strings or contain exactly one env or file key. repository-urls must be a non-empty list identifying distinct GitHub repositories. See GitHub Authentication for SSH, HTTPS, API tokens, and migration permissions; Run GitHub Backups for commands and workspace layout; and Restore GitHub Backups for recovery.

To discover every repository owned by one organization or user on each run, use type: github-owner and replace repository-urls with:

source:
  owner-url: https://github.com/example
  clone-protocol: ssh
  components:
    git: true
    lfs: false
    wiki: false
    metadata: false
    release-assets: false
  migration-timeout-seconds: 3600

clone-protocol defaults to ssh. Owner discovery uses the current gh auth login credentials when authentication.api.token is omitted; an unattended environment can supply that token through an env or file secret source. Owner-job dry runs contact GitHub read-only to report the repositories they would process, but never run Git or contact restic.

Optional archive policy

Restic repositories on AWS S3 storage may add an archive policy. storage-class controls how new objects are stored, and restore defines retrieval behavior for cold storage:

storage:
  - id: aws-s3
    type: s3
    endpoint: https://s3.us-east-1.amazonaws.com
    region: us-east-1
    credentials:
      access-key-id: "..."
      secret-access-key: "..."

restic-repositories:
  - id: aws-archive
    description: AWS S3 restic repository.
    storage-id: aws-s3
    enabled: false
    bucket: example-bucket
    key_prefix: backups
    password: "..."
    archive:
      storage-class: GLACIER_IR
      restore: null

Omit archive to use the storage provider’s default class.

S3 Glacier Instant Retrieval (GLACIER_IR)

S3 Glacier Instant Retrieval is for rarely accessed data that must remain readable within milliseconds. Restic can access it normally, so it must not define a restore policy:

archive:
  storage-class: GLACIER_IR
  restore: null
  • 90-day minimum: Each uploaded object is billed for at least 90 days. If restic deletes or replaces it after 30 days, AWS still charges the remaining 60 days.
  • 128 KB minimum: A 10 KB object is billed as 128 KB. This matters when a repository contains many small objects.
  • Retrieval charges: Data is available immediately, but AWS charges when restic reads it, in addition to request and possible internet-transfer fees.

S3 Glacier Flexible Retrieval (GLACIER)

S3 Glacier Flexible Retrieval is for backups read roughly once or twice per year. Objects require an archive restore before restic can read them. Expedited retrieval usually takes 1–5 minutes, Standard takes 3–5 hours, and Bulk takes 5–12 hours. Objects have a 90-day minimum storage duration and 40 KB of billable metadata overhead each.

Configure the retrieval tier, the number of days to retain the temporary restored copy, and the maximum wait time:

archive:
  storage-class: GLACIER
  restore:
    tier: Bulk
    days: 7
    timeout: 24h

tier accepts Standard, Bulk, or Expedited.

S3 Glacier Deep Archive (DEEP_ARCHIVE)

S3 Glacier Deep Archive is the lowest-cost option for data read less than once per year. It has a 180-day minimum storage duration and 40 KB of billable metadata overhead per object. Standard retrieval typically completes within 12 hours; Bulk can take up to 48 hours.

archive:
  storage-class: DEEP_ARCHIVE
  restore:
    tier: Bulk
    days: 7
    timeout: 48h

tier accepts Standard or Bulk.

Flexible Retrieval and Deep Archive commands that read repository data require an acknowledgement:

ALLOW_ARCHIVE_RETRIEVAL=1 uv run restic-backups generic restic run \
  --backup <job-id> restore latest --target <dir>

Changing a storage class affects new objects only. Use a new key_prefix rather than mixing storage policies in one repository.

Validation rules

check-config also rejects duplicate IDs, unknown storage or repository references, unsupported storage types, missing S3 fields, unsafe local paths, empty or invalid paths, unsafe GitHub URLs, invalid GitHub component or credential combinations, and invalid Glacier retrieval tiers or durations.