├── .coveragerc ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE └── workflows │ ├── black.yaml │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── .mailmap ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── AUTHORS ├── Brewfile ├── CHANGES.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── SECURITY.md ├── Vagrantfile ├── docs ├── 3rd_party │ └── README ├── Makefile ├── _static │ ├── Makefile │ ├── favicon.ico │ ├── logo.pdf │ ├── logo.png │ ├── logo.svg │ └── logo_font.txt ├── _templates │ ├── globaltoc.html │ ├── layout.html │ └── logo-text.html ├── authors.rst ├── binaries │ └── 00_README.txt ├── book.rst ├── borg_theme │ └── css │ │ └── borg.css ├── changes.rst ├── changes_0.x.rst ├── changes_1.x.rst ├── conf.py ├── deployment.rst ├── deployment │ ├── automated-local.rst │ ├── central-backup-server.rst │ ├── hosting-repositories.rst │ ├── image-backup.rst │ ├── non-root-user.rst │ └── pull-backup.rst ├── development.rst ├── faq.rst ├── global.rst.inc ├── index.rst ├── installation.rst ├── internals.rst ├── internals │ ├── data-structures.rst │ ├── encryption-aead.odg │ ├── encryption-aead.png │ ├── frontends.rst │ ├── object-graph.odg │ ├── object-graph.png │ ├── security.rst │ ├── structure.odg │ └── structure.png ├── introduction.rst ├── man │ ├── borg-analyze.1 │ ├── borg-benchmark-cpu.1 │ ├── borg-benchmark-crud.1 │ ├── borg-benchmark.1 │ ├── borg-break-lock.1 │ ├── borg-check.1 │ ├── borg-common.1 │ ├── borg-compact.1 │ ├── borg-compression.1 │ ├── borg-config.1 │ ├── borg-create.1 │ ├── borg-delete.1 │ ├── borg-diff.1 │ ├── borg-export-tar.1 │ ├── borg-extract.1 │ ├── borg-import-tar.1 │ ├── borg-info.1 │ ├── borg-key-change-algorithm.1 │ ├── borg-key-change-location.1 │ ├── borg-key-change-passphrase.1 │ ├── borg-key-export.1 │ ├── borg-key-import.1 │ ├── borg-key-migrate-to-repokey.1 │ ├── borg-key.1 │ ├── borg-list.1 │ ├── borg-match-archives.1 │ ├── borg-mount.1 │ ├── borg-patterns.1 │ ├── borg-placeholders.1 │ ├── borg-prune.1 │ ├── borg-recreate.1 │ ├── borg-rename.1 │ ├── borg-repo-compress.1 │ ├── borg-repo-create.1 │ ├── borg-repo-delete.1 │ ├── borg-repo-info.1 │ ├── borg-repo-list.1 │ ├── borg-repo-space.1 │ ├── borg-serve.1 │ ├── borg-tag.1 │ ├── borg-transfer.1 │ ├── borg-umount.1 │ ├── borg-undelete.1 │ ├── borg-upgrade.1 │ ├── borg-version.1 │ ├── borg-with-lock.1 │ ├── borg.1 │ └── borgfs.1 ├── man_intro.rst ├── misc │ ├── asciinema │ │ ├── README │ │ ├── Vagrantfile │ │ ├── advanced.json │ │ ├── advanced.tcl │ │ ├── basic.json │ │ ├── basic.tcl │ │ ├── install.json │ │ ├── install.tcl │ │ └── sample-wallpapers.txt │ ├── benchmark-crud.txt │ ├── borg-data-flow.odg │ ├── borg-data-flow.png │ ├── create_chunker-params.txt │ ├── internals-picture.txt │ ├── logging.conf │ └── prune-example.txt ├── quickstart.rst ├── quickstart_example.rst.inc ├── support.rst ├── usage.rst ├── usage │ ├── analyze.rst │ ├── analyze.rst.inc │ ├── benchmark.rst │ ├── benchmark_cpu.rst.inc │ ├── benchmark_crud.rst.inc │ ├── borgfs.rst │ ├── borgfs.rst.inc │ ├── break-lock.rst.inc │ ├── check.rst │ ├── check.rst.inc │ ├── common-options.rst.inc │ ├── compact.rst │ ├── compact.rst.inc │ ├── create.rst │ ├── create.rst.inc │ ├── debug.rst │ ├── delete.rst │ ├── delete.rst.inc │ ├── diff.rst │ ├── diff.rst.inc │ ├── export-tar.rst.inc │ ├── extract.rst │ ├── extract.rst.inc │ ├── general.rst │ ├── general │ │ ├── date-time.rst.inc │ │ ├── environment.rst.inc │ │ ├── file-metadata.rst.inc │ │ ├── file-systems.rst.inc │ │ ├── logging.rst.inc │ │ ├── positional-arguments.rst.inc │ │ ├── repository-locations.rst.inc │ │ ├── repository-urls.rst.inc │ │ ├── resources.rst.inc │ │ ├── return-codes.rst.inc │ │ └── units.rst.inc │ ├── help.rst │ ├── help.rst.inc │ ├── import-tar.rst.inc │ ├── info.rst │ ├── info.rst.inc │ ├── key.rst │ ├── key_change-location.rst.inc │ ├── key_change-passphrase.rst.inc │ ├── key_export.rst.inc │ ├── key_import.rst.inc │ ├── list.rst │ ├── list.rst.inc │ ├── lock.rst │ ├── mount.rst │ ├── mount.rst.inc │ ├── notes.rst │ ├── prune.rst │ ├── prune.rst.inc │ ├── recreate.rst │ ├── recreate.rst.inc │ ├── rename.rst │ ├── rename.rst.inc │ ├── repo-compress.rst │ ├── repo-compress.rst.inc │ ├── repo-create.rst │ ├── repo-create.rst.inc │ ├── repo-delete.rst │ ├── repo-delete.rst.inc │ ├── repo-info.rst │ ├── repo-info.rst.inc │ ├── repo-list.rst │ ├── repo-list.rst.inc │ ├── repo-space.rst │ ├── repo-space.rst.inc │ ├── serve.rst │ ├── serve.rst.inc │ ├── tag.rst │ ├── tag.rst.inc │ ├── tar.rst │ ├── transfer.rst │ ├── transfer.rst.inc │ ├── umount.rst.inc │ ├── undelete.rst │ ├── undelete.rst.inc │ ├── usage_general.rst.inc │ ├── version.rst │ ├── version.rst.inc │ └── with-lock.rst.inc └── usage_general.rst.inc ├── pyproject.toml ├── requirements.d ├── codestyle.txt ├── development.lock.txt ├── development.txt └── docs.txt ├── scripts ├── borg.exe.spec ├── errorlist.py ├── fetch-binaries ├── glibc_check.py ├── make-testdata │ └── test_transfer_upgrade.sh ├── make.py ├── msys2-install-deps ├── sdist-sign ├── shell_completions │ ├── bash │ │ └── borg │ ├── fish │ │ └── borg.fish │ └── zsh │ │ └── _borg ├── sign-binaries └── upload-pypi ├── setup.py └── src └── borg ├── __init__.py ├── __main__.py ├── _item.c ├── archive.py ├── archiver ├── __init__.py ├── _common.py ├── analyze_cmd.py ├── benchmark_cmd.py ├── check_cmd.py ├── compact_cmd.py ├── create_cmd.py ├── debug_cmd.py ├── delete_cmd.py ├── diff_cmd.py ├── extract_cmd.py ├── help_cmd.py ├── info_cmd.py ├── key_cmds.py ├── list_cmd.py ├── lock_cmds.py ├── mount_cmds.py ├── prune_cmd.py ├── recreate_cmd.py ├── rename_cmd.py ├── repo_compress_cmd.py ├── repo_create_cmd.py ├── repo_delete_cmd.py ├── repo_info_cmd.py ├── repo_list_cmd.py ├── repo_space_cmd.py ├── serve_cmd.py ├── tag_cmd.py ├── tar_cmds.py ├── transfer_cmd.py ├── undelete_cmd.py └── version_cmd.py ├── cache.py ├── checksums.pyi ├── checksums.pyx ├── chunker.pyi ├── chunker.pyx ├── compress.pyi ├── compress.pyx ├── conftest.py ├── constants.py ├── crypto ├── __init__.py ├── file_integrity.py ├── key.py ├── keymanager.py └── low_level.pyx ├── fslocking.py ├── fuse.py ├── fuse_impl.py ├── hashindex.pyi ├── hashindex.pyx ├── helpers ├── __init__.py ├── checks.py ├── datastruct.py ├── errors.py ├── fs.py ├── lrucache.py ├── misc.py ├── msgpack.py ├── nanorst.py ├── parseformat.py ├── passphrase.py ├── process.py ├── progress.py ├── shellpattern.py ├── time.py └── yes_no.py ├── item.pyi ├── item.pyx ├── legacyremote.py ├── legacyrepository.py ├── logger.py ├── manifest.py ├── paperkey.html ├── patterns.py ├── platform ├── __init__.py ├── base.py ├── darwin.pyx ├── freebsd.pyx ├── linux.pyx ├── posix.pyx ├── syncfilerange.pyx ├── windows.pyx └── xattr.py ├── platformflags.py ├── remote.py ├── repoobj.py ├── repository.py ├── selftest.py ├── storelocking.py ├── testsuite ├── __init__.py ├── archive_test.py ├── archiver │ ├── __init__.py │ ├── analyze_cmd_test.py │ ├── argparsing_test.py │ ├── benchmark_cmd_test.py │ ├── check_cmd_test.py │ ├── checks_test.py │ ├── compact_cmd_test.py │ ├── corruption_test.py │ ├── create_cmd_test.py │ ├── debug_cmds_test.py │ ├── delete_cmd_test.py │ ├── diff_cmd_test.py │ ├── disk_full_test.py │ ├── dotdot_path.tar │ ├── extract_cmd_test.py │ ├── help_cmd_test.py │ ├── info_cmd_test.py │ ├── key_cmds_test.py │ ├── list_cmd_test.py │ ├── lock_cmds_test.py │ ├── mount_cmds_test.py │ ├── patterns_test.py │ ├── prune_cmd_test.py │ ├── recreate_cmd_test.py │ ├── rename_cmd_test.py │ ├── repo12.tar.gz │ ├── repo_compress_cmd_test.py │ ├── repo_create_cmd_test.py │ ├── repo_delete_cmd_test.py │ ├── repo_info_cmd_test.py │ ├── repo_list_cmd_test.py │ ├── repo_space_cmd_test.py │ ├── restricted_permissions_test.py │ ├── return_codes_test.py │ ├── serve_cmd_test.py │ ├── tag_cmd_test.py │ ├── tar_cmds_test.py │ ├── transfer_cmd_test.py │ ├── undelete_cmd_test.py │ └── unusual_paths.tar ├── benchmark_test.py ├── cache_test.py ├── checksums_test.py ├── chunker_pytest_test.py ├── chunker_slow_test.py ├── chunker_test.py ├── compress_test.py ├── crypto_test.py ├── efficient_collection_queue_test.py ├── file_integrity_test.py ├── fslocking_test.py ├── hashindex_test.py ├── helpers │ ├── __init__.py │ ├── __init__test.py │ ├── datastruct_test.py │ ├── fs_test.py │ ├── misc_test.py │ ├── msgpack_test.py │ ├── parseformat_test.py │ ├── passphrase_test.py │ ├── process_test.py │ ├── progress_test.py │ ├── shellpattern_test.py │ ├── time_test.py │ └── yes_no_test.py ├── item_test.py ├── key_test.py ├── legacyrepository_test.py ├── logger_test.py ├── lrucache_test.py ├── nanorst_test.py ├── patterns_test.py ├── platform_darwin_test.py ├── platform_freebsd_test.py ├── platform_linux_test.py ├── platform_posix_test.py ├── platform_test.py ├── remote_test.py ├── repoobj_test.py ├── repository_test.py ├── shell_completions_test.py ├── storelocking_test.py ├── version_test.py └── xattr_test.py ├── upgrade.py ├── version.py └── xattr.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | disable_warnings = module-not-measured 4 | source = src/borg 5 | omit = 6 | */borg/__init__.py 7 | */borg/__main__.py 8 | */borg/_version.py 9 | */borg/fuse.py 10 | */borg/support/* 11 | */borg/testsuite/* 12 | */borg/hash_sizes.py 13 | 14 | [report] 15 | exclude_lines = 16 | pragma: no cover 17 | pragma: freebsd only 18 | pragma: unknown platform only 19 | def __repr__ 20 | raise AssertionError 21 | raise NotImplementedError 22 | if 0: 23 | if __name__ == .__main__.: 24 | ignore_errors = True 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Migrate code style to Black 2 | 7957af562d5ce8266b177039783be4dc8bdd7898 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | borg/_version.py export-subst 2 | 3 | *.py diff=python 4 | docs/usage/*.rst.inc merge=ours 5 | docs/man/* merge=ours 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: borgbackup 4 | liberapay: borgbackup 5 | open_collective: borgbackup 6 | custom: ['https://www.borgbackup.org/support/fund.html'] 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | ## Have you checked borgbackup docs, FAQ, and open GitHub issues? 16 | 17 | No 18 | 19 | ## Is this a BUG / ISSUE report or a QUESTION? 20 | 21 | Invalid 22 | 23 | ## System information. For client/server mode post info for both machines. 24 | 25 | #### Your borg version (borg -V). 26 | 27 | #### Operating system (distribution) and version. 28 | 29 | #### Hardware / network configuration, and filesystems used. 30 | 31 | #### How much data is handled by borg? 32 | 33 | #### Full borg commandline that lead to the problem (leave away excludes and passwords) 34 | 35 | 36 | ## Describe the problem you're observing. 37 | 38 | #### Can you reproduce the problem? If so, describe how. If not, describe troubleshooting steps you took before opening the issue. 39 | 40 | #### Include any warning/errors/backtraces from the system logs 41 | 42 | 57 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | Thank you for contributing code to Borg, your help is appreciated! 2 | 3 | Please, before you submit a pull request, make sure it complies with the 4 | guidelines given in our documentation: 5 | 6 | https://borgbackup.readthedocs.io/en/latest/development.html#contributions 7 | 8 | **Please remove all above text before submitting your pull request.** 9 | -------------------------------------------------------------------------------- /.github/workflows/black.yaml: -------------------------------------------------------------------------------- 1 | # https://black.readthedocs.io/en/stable/integrations/github_actions.html#usage 2 | # see also what we use locally, requirements.d/codestyle.txt - should be the same version here. 3 | 4 | name: Lint 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: psf/black@stable 14 | with: 15 | version: "~= 24.0" 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # CodeQL semantic code analysis engine 2 | 3 | name: "CodeQL" 4 | 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | # The branches below must be a subset of the branches above 10 | branches: [ master ] 11 | schedule: 12 | - cron: '39 2 * * 5' 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze 17 | runs-on: ubuntu-22.04 18 | permissions: 19 | actions: read 20 | contents: read 21 | security-events: write 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | language: [ 'cpp', 'python' ] 27 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 28 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v4 33 | with: 34 | # just fetching 1 commit is not enough for setuptools-scm, so we fetch all 35 | fetch-depth: 0 36 | - name: Set up Python 37 | uses: actions/setup-python@v5 38 | with: 39 | python-version: 3.11 40 | - name: Cache pip 41 | uses: actions/cache@v4 42 | with: 43 | path: ~/.cache/pip 44 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.d/development.txt') }} 45 | restore-keys: | 46 | ${{ runner.os }}-pip- 47 | ${{ runner.os }}- 48 | - name: Install requirements 49 | run: | 50 | sudo apt-get update 51 | sudo apt-get install -y pkg-config build-essential 52 | sudo apt-get install -y libssl-dev libacl1-dev libxxhash-dev liblz4-dev libzstd-dev 53 | # Initializes the CodeQL tools for scanning. 54 | - name: Initialize CodeQL 55 | uses: github/codeql-action/init@v3 56 | with: 57 | languages: ${{ matrix.language }} 58 | # If you wish to specify custom queries, you can do so here or in a config file. 59 | # By default, queries listed here will override any specified in a config file. 60 | # Prefix the list here with "+" to use these queries and those in the config file. 61 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 62 | - name: Build and install Borg 63 | run: | 64 | python3 -m venv ../borg-env 65 | source ../borg-env/bin/activate 66 | pip3 install -r requirements.d/development.txt 67 | pip3 install -ve . 68 | - name: Perform CodeQL Analysis 69 | uses: github/codeql-action/analyze@v3 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | docs/_build 3 | build 4 | dist 5 | external 6 | borg-env 7 | .tox 8 | src/borg/compress.c 9 | src/borg/crypto/low_level.c 10 | src/borg/hashindex.c 11 | src/borg/item.c 12 | src/borg/chunker.c 13 | src/borg/checksums.c 14 | src/borg/platform/darwin.c 15 | src/borg/platform/freebsd.c 16 | src/borg/platform/linux.c 17 | src/borg/platform/syncfilerange.c 18 | src/borg/platform/posix.c 19 | src/borg/platform/windows.c 20 | src/borg/_version.py 21 | *.egg-info 22 | *.pyc 23 | *.pyd 24 | *.so 25 | .idea/ 26 | .cache/ 27 | .vscode/ 28 | borg.build/ 29 | borg.dist/ 30 | borg.exe 31 | .coverage 32 | .coverage.* 33 | .vagrant 34 | .eggs 35 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Abdel-Rahman 2 | Brian Johnson 3 | Carlo Teubner 4 | Mark Edgington 5 | Leo Famulari 6 | Marian Beermann 7 | Thomas Waldmann 8 | Dan Christensen 9 | Antoine Beaupré 10 | Hartmut Goebel 11 | Michael Gajda 12 | Milkey Mouse 13 | Ronny Pfannschmidt 14 | Stefan Tatschner 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 24.8.0 4 | hooks: 5 | - id: black 6 | - repo: https://github.com/astral-sh/ruff-pre-commit 7 | rev: v0.0.287 8 | hooks: 9 | - id: ruff 10 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml - Read the Docs configuration file. 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details. 3 | 4 | version: 2 5 | 6 | build: 7 | os: ubuntu-22.04 8 | tools: 9 | python: "3.11" 10 | jobs: 11 | post_checkout: 12 | - git fetch --unshallow 13 | apt_packages: 14 | - build-essential 15 | - pkg-config 16 | - libacl1-dev 17 | - libssl-dev 18 | - liblz4-dev 19 | - libzstd-dev 20 | - libxxhash-dev 21 | 22 | python: 23 | install: 24 | - requirements: requirements.d/development.lock.txt 25 | - requirements: requirements.d/docs.txt 26 | - method: pip 27 | path: . 28 | 29 | sphinx: 30 | configuration: docs/conf.py 31 | 32 | formats: 33 | - htmlzip 34 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | E-mail addresses listed here are not intended for support, please see 2 | the `support section`_ instead. 3 | 4 | .. _support section: https://borgbackup.readthedocs.io/en/stable/support.html 5 | 6 | Borg authors ("The Borg Collective") 7 | ------------------------------------ 8 | 9 | - Thomas Waldmann 10 | - Radek Podgorny 11 | - Yuri D'Elia 12 | - Michael Hanselmann 13 | - Teemu Toivanen 14 | - Marian Beermann 15 | - Martin Hostettler 16 | - Daniel Reichelt 17 | - Lauri Niskanen 18 | - Abdel-Rahman A. (Abogical) 19 | - Gu1nness 20 | - Andrey Andreyevich Bienkowski 21 | 22 | Retired 23 | ``````` 24 | 25 | - Antoine Beaupré 26 | 27 | Borg is a fork of Attic. 28 | 29 | Attic authors 30 | ------------- 31 | 32 | Attic is written and maintained by Jonas Borgström and various contributors: 33 | 34 | Attic Development Lead 35 | `````````````````````` 36 | - Jonas Borgström 37 | 38 | Attic Patches and Suggestions 39 | ````````````````````````````` 40 | - Brian Johnson 41 | - Cyril Roussillon 42 | - Dan Christensen 43 | - Jeremy Maitin-Shepard 44 | - Johann Klähn 45 | - Petros Moisiadis 46 | - Thomas Waldmann 47 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew 'pkgconf' 2 | brew 'zstd' 3 | brew 'lz4' 4 | brew 'xxhash' 5 | brew 'openssl@3.0' 6 | 7 | # osxfuse (aka macFUSE) is only required for "borg mount", 8 | # but won't work on github actions' workers. 9 | # it requires installing a kernel extension, so some users 10 | # may want it and some won't. 11 | 12 | #cask 'osxfuse' 13 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | docs/changes.rst -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2025 The Borg Collective (see AUTHORS file) 2 | Copyright (C) 2010-2014 Jonas Borgström 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in 13 | the documentation and/or other materials provided with the 14 | distribution. 15 | 3. The name of the author may not be used to endorse or promote 16 | products derived from this software without specific prior 17 | written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # stuff we need to include into the sdist is handled automatically by 2 | # setuptools_scm - it includes all git-committed files. 3 | # but we want to exclude some committed files/dirs not needed in the sdist: 4 | exclude .editorconfig .gitattributes .gitignore .mailmap Vagrantfile 5 | prune .github 6 | include src/borg/platform/darwin.c src/borg/platform/freebsd.c src/borg/platform/linux.c src/borg/platform/posix.c 7 | include src/borg/platform/syncfilerange.c 8 | include src/borg/platform/windows.c 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | These borg releases are currently supported with security updates. 6 | 7 | | Version | Supported | 8 | |---------|--------------------| 9 | | 2.0.x | :x: (not released) | 10 | | 1.4.x | :white_check_mark: | 11 | | 1.2.x | :white_check_mark: | 12 | | 1.1.x | :x: | 13 | | < 1.1 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | See there: 18 | 19 | https://borgbackup.readthedocs.io/en/latest/support.html#security-contact 20 | -------------------------------------------------------------------------------- /docs/3rd_party/README: -------------------------------------------------------------------------------- 1 | Here we store 3rd party documentation, licenses, etc. 2 | 3 | Please note that all files inside the "borg" package directory (except the 4 | stuff excluded in setup.py) will be INSTALLED, so don't keep docs or licenses 5 | there. 6 | -------------------------------------------------------------------------------- /docs/_static/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: logo.pdf logo.png 3 | 4 | logo.pdf: logo.svg 5 | inkscape logo.svg --export-pdf=logo.pdf 6 | 7 | logo.png: logo.svg 8 | inkscape logo.svg --export-png=logo.png --export-dpi=72,72 9 | 10 | clean: 11 | rm -f logo.pdf logo.png 12 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/_static/logo.pdf -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/_static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/_static/logo_font.txt: -------------------------------------------------------------------------------- 1 | Black Ops One 2 | James Grieshaber 3 | SIL Open Font License, 1.1 4 | 5 | https://www.google.com/fonts/specimen/Black+Ops+One 6 | -------------------------------------------------------------------------------- /docs/_templates/globaltoc.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /docs/_templates/logo-text.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: global.rst.inc 2 | 3 | Authors 4 | ======= 5 | 6 | .. include:: ../AUTHORS 7 | 8 | License 9 | ======= 10 | 11 | .. _license: 12 | 13 | .. include:: ../LICENSE 14 | :literal: 15 | -------------------------------------------------------------------------------- /docs/book.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. include:: global.rst.inc 4 | 5 | Borg documentation 6 | ================== 7 | 8 | .. when you add an element here, do not forget to add it to index.rst 9 | .. Note: Some things are in appendices (see latex_appendices in conf.py) 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | introduction 15 | installation 16 | quickstart 17 | usage 18 | deployment 19 | faq 20 | support 21 | changes 22 | internals 23 | development 24 | authors 25 | -------------------------------------------------------------------------------- /docs/deployment.rst: -------------------------------------------------------------------------------- 1 | .. include:: global.rst.inc 2 | .. highlight:: none 3 | 4 | Deployment 5 | ========== 6 | 7 | This chapter details deployment strategies for the following scenarios. 8 | 9 | .. toctree:: 10 | :titlesonly: 11 | 12 | deployment/central-backup-server 13 | deployment/hosting-repositories 14 | deployment/automated-local 15 | deployment/image-backup 16 | deployment/pull-backup 17 | deployment/non-root-user 18 | -------------------------------------------------------------------------------- /docs/deployment/hosting-repositories.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../global.rst.inc 2 | .. highlight:: none 3 | .. _hosting_repositories: 4 | 5 | Hosting repositories 6 | ==================== 7 | 8 | This sections shows how to provide repository storage securely for users. 9 | 10 | Repositories are accessed through SSH. Each user of the service should 11 | have her own login which is only able to access the user's files. 12 | Technically it would be possible to have multiple users share one login, 13 | however, separating them is better. Separate logins increase isolation 14 | and are thus an additional layer of security and safety for both the 15 | provider and the users. 16 | 17 | For example, if a user manages to breach ``borg serve`` then she can 18 | only damage her own data (assuming that the system does not have further 19 | vulnerabilities). 20 | 21 | Use the standard directory structure of the operating system. Each user 22 | is assigned a home directory and repositories of the user reside in her 23 | home directory. 24 | 25 | The following ``~user/.ssh/authorized_keys`` file is the most important 26 | piece for a correct deployment. It allows the user to log in via 27 | their public key (which must be provided by the user), and restricts 28 | SSH access to safe operations only. 29 | 30 | :: 31 | 32 | command="borg serve --restrict-to-repository /home//repository",restrict 33 | 34 | 35 | .. note:: The text shown above needs to be written on a **single** line! 36 | 37 | .. warning:: 38 | 39 | If this file should be automatically updated (e.g. by a web console), 40 | pay **utmost attention** to sanitizing user input. Strip all whitespace 41 | around the user-supplied key, ensure that it **only** contains ASCII 42 | with no control characters and that it consists of three parts separated 43 | by a single space. Ensure that no newlines are contained within the key. 44 | 45 | The ``restrict`` keyword enables all restrictions, i.e. disables port, agent 46 | and X11 forwarding, as well as disabling PTY allocation and execution of ~/.ssh/rc. 47 | If any future restriction capabilities are added to authorized_keys 48 | files they will be included in this set. 49 | 50 | The ``command`` keyword forces execution of the specified command line 51 | upon login. This must be ``borg serve``. The ``--restrict-to-repository`` 52 | option permits access to exactly **one** repository. It can be given 53 | multiple times to permit access to more than one repository. 54 | 55 | The repository may not exist yet; it can be initialized by the user, 56 | which allows for encryption. 57 | 58 | Refer to the `sshd(8) `_ 59 | man page for more details on SSH options. 60 | See also :ref:`borg_serve` 61 | -------------------------------------------------------------------------------- /docs/deployment/non-root-user.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../global.rst.inc 2 | .. highlight:: none 3 | .. _non_root_user: 4 | 5 | ================================ 6 | Backing up using a non-root user 7 | ================================ 8 | 9 | This section describes how to run borg as a non-root user and still be able to 10 | backup every file on the system. 11 | 12 | Normally borg is run as the root user to bypass all filesystem permissions and 13 | be able to read all files. But in theory this also allows borg to modify or 14 | delete files on your system, in case of a bug for example. 15 | 16 | To eliminate this possibility, we can run borg as a non-root user and give it read-only 17 | permissions to all files on the system. 18 | 19 | 20 | Using Linux capabilities inside a systemd service 21 | ================================================= 22 | 23 | One way to do so, is to use linux `capabilities 24 | `_ within a systemd 25 | service. 26 | 27 | Linux capabilities allow us to give parts of the privileges the root user has to 28 | a non-root user. This works on a per-thread level and does not give the permission 29 | to the non-root user as a whole. 30 | 31 | For this we need to run our backup script from a systemd service and use the `AmbientCapabilities 32 | `_ 33 | option added in systemd 229. 34 | 35 | A very basic unit file would look like this: 36 | 37 | :: 38 | 39 | [Unit] 40 | Description=Borg Backup 41 | 42 | [Service] 43 | Type=oneshot 44 | User=borg 45 | ExecStart=/usr/local/sbin/backup.sh 46 | 47 | AmbientCapabilities=CAP_DAC_READ_SEARCH 48 | 49 | The ``CAP_DAC_READ_SEARCH`` capability gives borg read-only access to all files and directories on the system. 50 | 51 | This service can then be started manually using ``systemctl start``, a systemd timer or other methods. 52 | 53 | Restore considerations 54 | ====================== 55 | 56 | When restoring files, the root user should be used. When using the non-root user, borg extract will 57 | change all files to be owned by the non-root user. Using borg mount will not allow the non-root user 58 | to access files that it would not have access to on the system itself. 59 | 60 | Other than that, the same restore process, that would be used when running the backup as root, can be used. 61 | 62 | .. warning:: 63 | 64 | When using a local repo and running borg commands as root, make sure to only use commands that do not 65 | modify the repo itself, like extract or mount. Modifying the repo using the root user will break 66 | the repo for the non-root user, since some files inside the repo will now be owned by root. 67 | -------------------------------------------------------------------------------- /docs/global.rst.inc: -------------------------------------------------------------------------------- 1 | .. highlight:: bash 2 | .. |package_dirname| replace:: borgbackup-|version| 3 | .. |package_filename| replace:: |package_dirname|.tar.gz 4 | .. |package_url| replace:: https://pypi.python.org/packages/source/b/borgbackup/|package_filename| 5 | .. |git_url| replace:: https://github.com/borgbackup/borg.git 6 | .. _github: https://github.com/borgbackup/borg 7 | .. _issue tracker: https://github.com/borgbackup/borg/issues 8 | .. _deduplication: https://en.wikipedia.org/wiki/Data_deduplication 9 | .. _AES: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard 10 | .. _HMAC-SHA256: https://en.wikipedia.org/wiki/HMAC 11 | .. _SHA256: https://en.wikipedia.org/wiki/SHA-256 12 | .. _PBKDF2: https://en.wikipedia.org/wiki/PBKDF2 13 | .. _argon2: https://en.wikipedia.org/wiki/Argon2 14 | .. _ACL: https://en.wikipedia.org/wiki/Access_control_list 15 | .. _libacl: https://savannah.nongnu.org/projects/acl/ 16 | .. _libattr: https://savannah.nongnu.org/projects/attr/ 17 | .. _libxxhash: https://github.com/Cyan4973/xxHash 18 | .. _liblz4: https://github.com/Cyan4973/lz4 19 | .. _libzstd: https://github.com/facebook/zstd 20 | .. _OpenSSL: https://www.openssl.org/ 21 | .. _`Python 3`: https://www.python.org/ 22 | .. _Buzhash: https://en.wikipedia.org/wiki/Buzhash 23 | .. _msgpack: https://msgpack.org/ 24 | .. _`msgpack-python`: https://pypi.python.org/pypi/msgpack-python/ 25 | .. _llfuse: https://pypi.python.org/pypi/llfuse/ 26 | .. _pyfuse3: https://pypi.python.org/pypi/pyfuse3/ 27 | .. _userspace filesystems: https://en.wikipedia.org/wiki/Filesystem_in_Userspace 28 | .. _Cython: http://cython.org/ 29 | .. _virtualenv: https://pypi.python.org/pypi/virtualenv/ 30 | .. _mailing list discussion about internals: http://librelist.com/browser/attic/2014/5/6/questions-and-suggestions-about-inner-working-of-attic> 31 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: global.rst.inc 2 | .. highlight:: none 3 | 4 | Borg Documentation 5 | ================== 6 | 7 | .. include:: ../README.rst 8 | 9 | .. when you add an element here, do not forget to add it to book.rst 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | installation 15 | quickstart 16 | usage 17 | deployment 18 | faq 19 | support 20 | changes 21 | changes_1.x 22 | changes_0.x 23 | internals 24 | development 25 | authors 26 | -------------------------------------------------------------------------------- /docs/internals.rst: -------------------------------------------------------------------------------- 1 | .. include:: global.rst.inc 2 | .. _internals: 3 | 4 | Internals 5 | ========= 6 | 7 | The internals chapter describes and analyses most of the inner workings 8 | of Borg. 9 | 10 | Borg uses a low-level, key-value store, the :ref:`repository`, and 11 | implements a more complex data structure on top of it, which is made 12 | up of the :ref:`manifest `, :ref:`archives `, 13 | :ref:`items ` and data :ref:`chunks`. 14 | 15 | Each repository can hold multiple :ref:`archives `, which 16 | represent individual backups that contain a full archive of the files 17 | specified when the backup was performed. 18 | 19 | Deduplication is performed globally across all data in the repository 20 | (multiple backups and even multiple hosts), both on data and file 21 | metadata, using :ref:`chunks` created by the chunker using the 22 | Buzhash_ algorithm ("buzhash" chunker) or a simpler fixed blocksize 23 | algorithm ("fixed" chunker). 24 | 25 | To perform the repository-wide deduplication, a hash of each 26 | chunk is checked against the :ref:`chunks cache `, which is a 27 | hash-table of all chunks that already exist. 28 | 29 | .. figure:: internals/structure.png 30 | :figwidth: 100% 31 | :width: 100% 32 | 33 | Layers in Borg. On the very top commands are implemented, using 34 | a data access layer provided by the Archive and Item classes. 35 | The "key" object provides both compression and authenticated 36 | encryption used by the data access layer. The "key" object represents 37 | the sole trust boundary in Borg. 38 | The lowest layer is the repository, either accessed directly 39 | (Repository) or remotely (RemoteRepository). 40 | 41 | .. toctree:: 42 | :caption: Internals contents 43 | 44 | internals/security 45 | internals/data-structures 46 | internals/frontends 47 | -------------------------------------------------------------------------------- /docs/internals/encryption-aead.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/internals/encryption-aead.odg -------------------------------------------------------------------------------- /docs/internals/encryption-aead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/internals/encryption-aead.png -------------------------------------------------------------------------------- /docs/internals/object-graph.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/internals/object-graph.odg -------------------------------------------------------------------------------- /docs/internals/object-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/internals/object-graph.png -------------------------------------------------------------------------------- /docs/internals/structure.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/internals/structure.odg -------------------------------------------------------------------------------- /docs/internals/structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/internals/structure.png -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | .. this shim is here to fix the structure in the PDF 5 | rendering. without this stub, the elements in the toctree of 6 | index.rst show up a level below the README file included 7 | 8 | .. include:: ../README.rst 9 | -------------------------------------------------------------------------------- /docs/man/borg-benchmark-cpu.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-BENCHMARK-CPU" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-benchmark-cpu \- Benchmark CPU bound operations. 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] benchmark cpu [options] 36 | .SH DESCRIPTION 37 | .sp 38 | This command benchmarks misc. CPU bound borg operations. 39 | .sp 40 | It creates input data in memory, runs the operation and then displays throughput. 41 | To reduce outside influence on the timings, please make sure to run this with: 42 | .INDENT 0.0 43 | .IP \(bu 2 44 | an otherwise as idle as possible machine 45 | .IP \(bu 2 46 | enough free memory so there will be no slow down due to paging activity 47 | .UNINDENT 48 | .SH OPTIONS 49 | .sp 50 | See \fIborg\-common(1)\fP for common options of Borg commands. 51 | .SH SEE ALSO 52 | .sp 53 | \fIborg\-common(1)\fP 54 | .SH AUTHOR 55 | The Borg Collective 56 | .\" Generated by docutils manpage writer. 57 | . 58 | -------------------------------------------------------------------------------- /docs/man/borg-benchmark.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-BENCHMARK" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-benchmark \- benchmark command 33 | .SH SYNOPSIS 34 | .nf 35 | borg [common options] benchmark crud ... 36 | borg [common options] benchmark cpu ... 37 | .fi 38 | .sp 39 | .SH DESCRIPTION 40 | .sp 41 | These commands do various benchmarks. 42 | .SH SEE ALSO 43 | .sp 44 | \fIborg\-common(1)\fP, \fIborg\-benchmark\-crud(1)\fP, \fIborg\-benchmark\-cpu(1)\fP 45 | .SH AUTHOR 46 | The Borg Collective 47 | .\" Generated by docutils manpage writer. 48 | . 49 | -------------------------------------------------------------------------------- /docs/man/borg-break-lock.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-BREAK-LOCK" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-break-lock \- Break the repository lock (e.g. in case it was left by a dead borg. 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] break\-lock [options] 36 | .SH DESCRIPTION 37 | .sp 38 | This command breaks the repository and cache locks. 39 | Please use carefully and only while no borg process (on any machine) is 40 | trying to access the Cache or the Repository. 41 | .SH OPTIONS 42 | .sp 43 | See \fIborg\-common(1)\fP for common options of Borg commands. 44 | .SH SEE ALSO 45 | .sp 46 | \fIborg\-common(1)\fP 47 | .SH AUTHOR 48 | The Borg Collective 49 | .\" Generated by docutils manpage writer. 50 | . 51 | -------------------------------------------------------------------------------- /docs/man/borg-key-change-location.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-KEY-CHANGE-LOCATION" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-key-change-location \- Change repository key location 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] key change\-location [options] KEY_LOCATION 36 | .SH DESCRIPTION 37 | .sp 38 | Change the location of a borg key. The key can be stored at different locations: 39 | .INDENT 0.0 40 | .IP \(bu 2 41 | keyfile: locally, usually in the home directory 42 | .IP \(bu 2 43 | repokey: inside the repo (in the repo config) 44 | .UNINDENT 45 | .sp 46 | Please note: 47 | .sp 48 | This command does NOT change the crypto algorithms, just the key location, 49 | thus you must ONLY give the key location (keyfile or repokey). 50 | .SH OPTIONS 51 | .sp 52 | See \fIborg\-common(1)\fP for common options of Borg commands. 53 | .SS arguments 54 | .INDENT 0.0 55 | .TP 56 | .B KEY_LOCATION 57 | select key location 58 | .UNINDENT 59 | .SS options 60 | .INDENT 0.0 61 | .TP 62 | .B \-\-keep 63 | keep the key also at the current location (default: remove it) 64 | .UNINDENT 65 | .SH SEE ALSO 66 | .sp 67 | \fIborg\-common(1)\fP 68 | .SH AUTHOR 69 | The Borg Collective 70 | .\" Generated by docutils manpage writer. 71 | . 72 | -------------------------------------------------------------------------------- /docs/man/borg-key-import.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-KEY-IMPORT" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-key-import \- Import the repository key from backup 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] key import [options] [PATH] 36 | .SH DESCRIPTION 37 | .sp 38 | This command restores a key previously backed up with the export command. 39 | .sp 40 | If the \fB\-\-paper\fP option is given, the import will be an interactive 41 | process in which each line is checked for plausibility before 42 | proceeding to the next line. For this format PATH must not be given. 43 | .sp 44 | For repositories using keyfile encryption, the key file which \fBborg key 45 | import\fP writes to depends on several factors. If the \fBBORG_KEY_FILE\fP 46 | environment variable is set and non\-empty, \fBborg key import\fP creates 47 | or overwrites that file named by \fB$BORG_KEY_FILE\fP\&. Otherwise, \fBborg 48 | key import\fP searches in the \fB$BORG_KEYS_DIR\fP directory for a key file 49 | associated with the repository. If a key file is found in 50 | \fB$BORG_KEYS_DIR\fP, \fBborg key import\fP overwrites it; otherwise, \fBborg 51 | key import\fP creates a new key file in \fB$BORG_KEYS_DIR\fP\&. 52 | .SH OPTIONS 53 | .sp 54 | See \fIborg\-common(1)\fP for common options of Borg commands. 55 | .SS arguments 56 | .INDENT 0.0 57 | .TP 58 | .B PATH 59 | path to the backup (\(aq\-\(aq to read from stdin) 60 | .UNINDENT 61 | .SS options 62 | .INDENT 0.0 63 | .TP 64 | .B \-\-paper 65 | interactively import from a backup done with \fB\-\-paper\fP 66 | .UNINDENT 67 | .SH SEE ALSO 68 | .sp 69 | \fIborg\-common(1)\fP, \fIborg\-key\-export(1)\fP 70 | .SH AUTHOR 71 | The Borg Collective 72 | .\" Generated by docutils manpage writer. 73 | . 74 | -------------------------------------------------------------------------------- /docs/man/borg-key-migrate-to-repokey.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-KEY-MIGRATE-TO-REPOKEY" 1 "2022-02-19" "" "borg backup tool" 31 | .SH NAME 32 | borg-key-migrate-to-repokey \- Migrate passphrase -> repokey 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] key migrate\-to\-repokey [options] [REPOSITORY] 36 | .SH DESCRIPTION 37 | .sp 38 | This command migrates a repository from passphrase mode (removed in Borg 1.0) 39 | to repokey mode. 40 | .sp 41 | You will be first asked for the repository passphrase (to open it in passphrase 42 | mode). This is the same passphrase as you used to use for this repo before 1.0. 43 | .sp 44 | It will then derive the different secrets from this passphrase. 45 | .sp 46 | Then you will be asked for a new passphrase (twice, for safety). This 47 | passphrase will be used to protect the repokey (which contains these same 48 | secrets in encrypted form). You may use the same passphrase as you used to 49 | use, but you may also use a different one. 50 | .sp 51 | After migrating to repokey mode, you can change the passphrase at any time. 52 | But please note: the secrets will always stay the same and they could always 53 | be derived from your (old) passphrase\-mode passphrase. 54 | .SH OPTIONS 55 | .sp 56 | See \fIborg\-common(1)\fP for common options of Borg commands. 57 | .SS arguments 58 | .sp 59 | REPOSITORY 60 | .SH SEE ALSO 61 | .sp 62 | \fIborg\-common(1)\fP 63 | .SH AUTHOR 64 | The Borg Collective 65 | .\" Generated by docutils manpage writer. 66 | . 67 | -------------------------------------------------------------------------------- /docs/man/borg-key.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-KEY" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-key \- Manage a keyfile or repokey of a repository 33 | .SH SYNOPSIS 34 | .nf 35 | borg [common options] key export ... 36 | borg [common options] key import ... 37 | borg [common options] key change\-passphrase ... 38 | borg [common options] key change\-location ... 39 | .fi 40 | .sp 41 | .SH SEE ALSO 42 | .sp 43 | \fIborg\-common(1)\fP, \fIborg\-key\-export(1)\fP, \fIborg\-key\-import(1)\fP, \fIborg\-key\-change\-passphrase(1)\fP, \fIborg\-key\-change\-location(1)\fP 44 | .SH AUTHOR 45 | The Borg Collective 46 | .\" Generated by docutils manpage writer. 47 | . 48 | -------------------------------------------------------------------------------- /docs/man/borg-rename.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-RENAME" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-rename \- Rename an existing archive 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] rename [options] OLDNAME NEWNAME 36 | .SH DESCRIPTION 37 | .sp 38 | This command renames an archive in the repository. 39 | .sp 40 | This results in a different archive ID. 41 | .SH OPTIONS 42 | .sp 43 | See \fIborg\-common(1)\fP for common options of Borg commands. 44 | .SS arguments 45 | .INDENT 0.0 46 | .TP 47 | .B OLDNAME 48 | specify the archive name 49 | .TP 50 | .B NEWNAME 51 | specify the new archive name 52 | .UNINDENT 53 | .SH EXAMPLES 54 | .INDENT 0.0 55 | .INDENT 3.5 56 | .sp 57 | .EX 58 | $ borg create archivename ~ 59 | $ borg repo\-list 60 | archivename Mon, 2016\-02\-15 19:50:19 61 | 62 | $ borg rename archivename newname 63 | $ borg repo\-list 64 | newname Mon, 2016\-02\-15 19:50:19 65 | .EE 66 | .UNINDENT 67 | .UNINDENT 68 | .SH SEE ALSO 69 | .sp 70 | \fIborg\-common(1)\fP 71 | .SH AUTHOR 72 | The Borg Collective 73 | .\" Generated by docutils manpage writer. 74 | . 75 | -------------------------------------------------------------------------------- /docs/man/borg-repo-delete.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-REPO-DELETE" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-repo-delete \- Delete a repository 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] repo\-delete [options] 36 | .SH DESCRIPTION 37 | .sp 38 | This command deletes the complete repository. 39 | .sp 40 | When you delete a complete repository, the security info and local cache for it 41 | (if any) are also deleted. Alternatively, you can delete just the local cache 42 | with the \fB\-\-cache\-only\fP option, or keep the security info with the 43 | \fB\-\-keep\-security\-info\fP option. 44 | .sp 45 | Always first use \fB\-\-dry\-run \-\-list\fP to see what would be deleted. 46 | .SH OPTIONS 47 | .sp 48 | See \fIborg\-common(1)\fP for common options of Borg commands. 49 | .SS options 50 | .INDENT 0.0 51 | .TP 52 | .B \-n\fP,\fB \-\-dry\-run 53 | do not change repository 54 | .TP 55 | .B \-\-list 56 | output verbose list of archives 57 | .TP 58 | .B \-\-force 59 | force deletion of corrupted archives, use \fB\-\-force \-\-force\fP in case \fB\-\-force\fP does not work. 60 | .TP 61 | .B \-\-cache\-only 62 | delete only the local cache for the given repository 63 | .TP 64 | .B \-\-keep\-security\-info 65 | keep the local security info when deleting a repository 66 | .UNINDENT 67 | .SH EXAMPLES 68 | .INDENT 0.0 69 | .INDENT 3.5 70 | .sp 71 | .EX 72 | # delete the whole repository and the related local cache: 73 | $ borg repo\-delete 74 | You requested to DELETE the repository completely *including* all archives it contains: 75 | repo Mon, 2016\-02\-15 19:26:54 76 | root\-2016\-02\-15 Mon, 2016\-02\-15 19:36:29 77 | newname Mon, 2016\-02\-15 19:50:19 78 | Type \(aqYES\(aq if you understand this and want to continue: YES 79 | .EE 80 | .UNINDENT 81 | .UNINDENT 82 | .SH SEE ALSO 83 | .sp 84 | \fIborg\-common(1)\fP 85 | .SH AUTHOR 86 | The Borg Collective 87 | .\" Generated by docutils manpage writer. 88 | . 89 | -------------------------------------------------------------------------------- /docs/man/borg-repo-info.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-REPO-INFO" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-repo-info \- Show repository infos 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] repo\-info [options] 36 | .SH DESCRIPTION 37 | .sp 38 | This command displays detailed information about the repository. 39 | .SH OPTIONS 40 | .sp 41 | See \fIborg\-common(1)\fP for common options of Borg commands. 42 | .SS options 43 | .INDENT 0.0 44 | .TP 45 | .B \-\-json 46 | format output as JSON 47 | .UNINDENT 48 | .SH EXAMPLES 49 | .INDENT 0.0 50 | .INDENT 3.5 51 | .sp 52 | .EX 53 | $ borg repo\-info 54 | Repository ID: 0e85a7811022326c067acb2a7181d5b526b7d2f61b34470fb8670c440a67f1a9 55 | Location: /Users/tw/w/borg/path/to/repo 56 | Encrypted: Yes (repokey AES\-OCB) 57 | Cache: /Users/tw/.cache/borg/0e85a7811022326c067acb2a7181d5b526b7d2f61b34470fb8670c440a67f1a9 58 | Security dir: /Users/tw/.config/borg/security/0e85a7811022326c067acb2a7181d5b526b7d2f61b34470fb8670c440a67f1a9 59 | Original size: 152.14 MB 60 | Deduplicated size: 30.38 MB 61 | Unique chunks: 654 62 | Total chunks: 3302 63 | .EE 64 | .UNINDENT 65 | .UNINDENT 66 | .SH SEE ALSO 67 | .sp 68 | \fIborg\-common(1)\fP 69 | .SH AUTHOR 70 | The Borg Collective 71 | .\" Generated by docutils manpage writer. 72 | . 73 | -------------------------------------------------------------------------------- /docs/man/borg-version.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-VERSION" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-version \- Display the borg client / borg server version 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] version [options] 36 | .SH DESCRIPTION 37 | .sp 38 | This command displays the borg client version / borg server version. 39 | .sp 40 | If a local repo is given, the client code directly accesses the repository, 41 | thus we show the client version also as the server version. 42 | .sp 43 | If a remote repo is given (e.g. ssh:), the remote borg is queried and 44 | its version is displayed as the server version. 45 | .sp 46 | Examples: 47 | .INDENT 0.0 48 | .INDENT 3.5 49 | .sp 50 | .EX 51 | # local repo (client uses 1.4.0 alpha version) 52 | $ borg version /mnt/backup 53 | 1.4.0a / 1.4.0a 54 | 55 | # remote repo (client uses 1.4.0 alpha, server uses 1.2.7 release) 56 | $ borg version ssh://borg@borgbackup:repo 57 | 1.4.0a / 1.2.7 58 | .EE 59 | .UNINDENT 60 | .UNINDENT 61 | .sp 62 | Due to the version tuple format used in borg client/server negotiation, only 63 | a simplified version is displayed (as provided by borg.version.format_version). 64 | .sp 65 | There is also borg \-\-version to display a potentially more precise client version. 66 | .SH OPTIONS 67 | .sp 68 | See \fIborg\-common(1)\fP for common options of Borg commands. 69 | .SH SEE ALSO 70 | .sp 71 | \fIborg\-common(1)\fP 72 | .SH AUTHOR 73 | The Borg Collective 74 | .\" Generated by docutils manpage writer. 75 | . 76 | -------------------------------------------------------------------------------- /docs/man/borg-with-lock.1: -------------------------------------------------------------------------------- 1 | .\" Man page generated from reStructuredText. 2 | . 3 | . 4 | .nr rst2man-indent-level 0 5 | . 6 | .de1 rstReportMargin 7 | \\$1 \\n[an-margin] 8 | level \\n[rst2man-indent-level] 9 | level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] 10 | - 11 | \\n[rst2man-indent0] 12 | \\n[rst2man-indent1] 13 | \\n[rst2man-indent2] 14 | .. 15 | .de1 INDENT 16 | .\" .rstReportMargin pre: 17 | . RS \\$1 18 | . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] 19 | . nr rst2man-indent-level +1 20 | .\" .rstReportMargin post: 21 | .. 22 | .de UNINDENT 23 | . RE 24 | .\" indent \\n[an-margin] 25 | .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] 26 | .nr rst2man-indent-level -1 27 | .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] 28 | .in \\n[rst2man-indent\\n[rst2man-indent-level]]u 29 | .. 30 | .TH "BORG-WITH-LOCK" "1" "2025-05-22" "" "borg backup tool" 31 | .SH NAME 32 | borg-with-lock \- run a user specified command with the repository lock held 33 | .SH SYNOPSIS 34 | .sp 35 | borg [common options] with\-lock [options] COMMAND [ARGS...] 36 | .SH DESCRIPTION 37 | .sp 38 | This command runs a user\-specified command while locking the repository. For example: 39 | .INDENT 0.0 40 | .INDENT 3.5 41 | .sp 42 | .EX 43 | $ BORG_REPO=/mnt/borgrepo borg with\-lock rsync \-av /mnt/borgrepo /somewhere/else/borgrepo 44 | .EE 45 | .UNINDENT 46 | .UNINDENT 47 | .sp 48 | It will first try to acquire the lock (make sure that no other operation is 49 | running in the repo), then execute the given command as a subprocess and wait 50 | for its termination, release the lock and return the user command\(aqs return 51 | code as borg\(aqs return code. 52 | .sp 53 | \fBNOTE:\fP 54 | .INDENT 0.0 55 | .INDENT 3.5 56 | If you copy a repository with the lock held, the lock will be present in 57 | the copy. Thus, before using borg on the copy from a different host, 58 | you need to use \(dqborg break\-lock\(dq on the copied repository, because 59 | Borg is cautious and does not automatically remove stale locks made by a different host. 60 | .UNINDENT 61 | .UNINDENT 62 | .SH OPTIONS 63 | .sp 64 | See \fIborg\-common(1)\fP for common options of Borg commands. 65 | .SS arguments 66 | .INDENT 0.0 67 | .TP 68 | .B COMMAND 69 | command to run 70 | .TP 71 | .B ARGS 72 | command arguments 73 | .UNINDENT 74 | .SH SEE ALSO 75 | .sp 76 | \fIborg\-common(1)\fP 77 | .SH AUTHOR 78 | The Borg Collective 79 | .\" Generated by docutils manpage writer. 80 | . 81 | -------------------------------------------------------------------------------- /docs/man_intro.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | SYNOPSIS 4 | -------- 5 | 6 | borg [common options] [options] [arguments] 7 | 8 | DESCRIPTION 9 | ----------- 10 | 11 | .. we don't include the README.rst here since we want to keep this terse. 12 | 13 | BorgBackup (short: Borg) is a deduplicating backup program. 14 | Optionally, it supports compression and authenticated encryption. 15 | 16 | The main goal of Borg is to provide an efficient and secure way to back data up. 17 | The data deduplication technique used makes Borg suitable for daily backups 18 | since only changes are stored. 19 | The authenticated encryption technique makes it suitable for backups to targets not 20 | fully trusted. 21 | 22 | Borg stores a set of files in an *archive*. A *repository* is a collection 23 | of *archives*. The format of repositories is Borg-specific. Borg does not 24 | distinguish archives from each other in any way other than their name, 25 | it does not matter when or where archives were created (e.g. different hosts). 26 | 27 | EXAMPLES 28 | -------- 29 | 30 | A step-by-step example 31 | ~~~~~~~~~~~~~~~~~~~~~~ 32 | 33 | .. include:: quickstart_example.rst.inc 34 | 35 | NOTES 36 | ----- 37 | 38 | .. include:: usage_general.rst.inc 39 | 40 | SEE ALSO 41 | -------- 42 | 43 | `borg-common(1)` for common command line options 44 | 45 | `borg-repo-create(1)`, `borg-repo-delete(1)`, `borg-repo-list(1)`, `borg-repo-info(1)`, 46 | `borg-create(1)`, `borg-mount(1)`, `borg-extract(1)`, 47 | `borg-list(1)`, `borg-info(1)`, 48 | `borg-delete(1)`, `borg-prune(1)`, `borg-compact(1)`, 49 | `borg-recreate(1)` 50 | 51 | `borg-compression(1)`, `borg-patterns(1)`, `borg-placeholders(1)` 52 | 53 | * Main web site https://www.borgbackup.org/ 54 | * Releases https://github.com/borgbackup/borg/releases 55 | * Changelog https://github.com/borgbackup/borg/blob/master/docs/changes.rst 56 | * GitHub https://github.com/borgbackup/borg 57 | * Security contact https://borgbackup.readthedocs.io/en/latest/support.html#security-contact 58 | -------------------------------------------------------------------------------- /docs/misc/asciinema/README: -------------------------------------------------------------------------------- 1 | Do NOT run the examples without isolation (e.g Vagrant) or 2 | this code may make undesirable changes to your host. 3 | 4 | Running `vagrant up` in this directory will update the screencasts. 5 | -------------------------------------------------------------------------------- /docs/misc/asciinema/install.tcl: -------------------------------------------------------------------------------- 1 | # Configuration for send -h 2 | # Tries to emulate a human typing 3 | # Tweak this if typing is too fast or too slow 4 | set send_human {.05 .1 1 .01 .2} 5 | 6 | set script [string trim { 7 | # This asciinema will show you the installation of borg as a standalone binary. Usually you only need this if you want to have an up-to-date version of borg or no package is available for your distro/OS. 8 | 9 | # First, we need to download the version, we'd like to install… 10 | wget -q --show-progress https://github.com/borgbackup/borg/releases/download/1.2.1/borg-linux64 11 | # and do not forget the GPG signature…! 12 | wget -q --show-progress https://github.com/borgbackup/borg/releases/download/1.2.1/borg-linux64.asc 13 | 14 | # In this case, we have already imported the public key of a borg developer. So we only need to verify it: 15 | gpg --verify borg-linux64.asc 16 | # Okay, the binary is valid! 17 | 18 | # Now install it: 19 | sudo cp borg-linux64 /usr/local/bin/borg 20 | sudo chown root:root /usr/local/bin/borg 21 | # and make it executable… 22 | sudo chmod 755 /usr/local/bin/borg 23 | 24 | # Now check it: (possibly needs a terminal restart) 25 | borg -V 26 | 27 | # That's it! Now check out the other screencasts to see how to use borgbackup. 28 | }] 29 | 30 | # wget may be slow 31 | set timeout -1 32 | 33 | foreach line [split $script \n] { 34 | send_user "$ " 35 | send_user -h $line\n 36 | spawn -noecho /bin/sh -c $line 37 | expect eof 38 | } 39 | -------------------------------------------------------------------------------- /docs/misc/borg-data-flow.odg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/misc/borg-data-flow.odg -------------------------------------------------------------------------------- /docs/misc/borg-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/docs/misc/borg-data-flow.png -------------------------------------------------------------------------------- /docs/misc/internals-picture.txt: -------------------------------------------------------------------------------- 1 | BorgBackup from 10.000m 2 | ======================= 3 | 4 | +--------+ +--------+ +--------+ 5 | |archive0| |archive1| ... |archiveN| 6 | +--------+ +--------+ +--+-----+ 7 | | | | 8 | | | | 9 | | +---+ | 10 | | | | 11 | | | | 12 | +------+-------+ | 13 | | | | | 14 | /chunk\/chunk\/chunk\... /maybe different chunks lists\ 15 | +-----------------------------------------------------------------+ 16 | |item list | 17 | +-----------------------------------------------------------------+ 18 | | 19 | +-------------------------------------+--------------+ 20 | | | | 21 | | | | 22 | +-------------+ +-------------+ | 23 | |item0 | |item1 | | 24 | | - owner | | - owner | | 25 | | - size | | - size | ... 26 | | - ... | | - ... | 27 | | - chunks | | - chunks | 28 | +----+--------+ +-----+-------+ 29 | | | 30 | | +-----+----------------------------+-----------------+ 31 | | | | | 32 | +-o-----o------------+ | 33 | | | | | | 34 | /chunk0\/chunk1\ ... /chunkN\ /chunk0\/chunk1\ ... /chunkN'\ 35 | +-----------------------------+ +------------------------------+ 36 | |file0 | |file0' | 37 | +-----------------------------+ +------------------------------+ 38 | 39 | 40 | Thanks to anarcat for drawing the picture! 41 | 42 | -------------------------------------------------------------------------------- /docs/misc/logging.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [handlers] 5 | keys=logfile 6 | 7 | [formatters] 8 | keys=logfile 9 | 10 | [logger_root] 11 | level=NOTSET 12 | handlers=logfile 13 | 14 | [handler_logfile] 15 | class=FileHandler 16 | level=INFO 17 | formatter=logfile 18 | args=('borg.log', 'w') 19 | 20 | [formatter_logfile] 21 | format=%(asctime)s %(levelname)s %(message)s 22 | datefmt= 23 | class=logging.Formatter 24 | -------------------------------------------------------------------------------- /docs/quickstart_example.rst.inc: -------------------------------------------------------------------------------- 1 | 1. Before a backup can be made, a repository has to be initialized:: 2 | 3 | $ borg -r /path/to/repo repo-create --encryption=repokey-aes-ocb 4 | 5 | 2. Back up the ``~/src`` and ``~/Documents`` directories into an archive called 6 | *docs*:: 7 | 8 | $ borg -r /path/to/repo create docs ~/src ~/Documents 9 | 10 | 3. The next day create a new archive using the same archive name:: 11 | 12 | $ borg -r /path/to/repo create --stats docs ~/src ~/Documents 13 | 14 | This backup will be a lot quicker and a lot smaller since only new, never 15 | before seen data is stored. The ``--stats`` option causes Borg to 16 | output statistics about the newly created archive such as the deduplicated 17 | size (the amount of unique data not shared with other archives):: 18 | 19 | Repository: /path/to/repo 20 | Archive name: docs 21 | Archive fingerprint: bcd1b53f9b4991b7afc2b339f851b7ffe3c6d030688936fe4552eccc1877718d 22 | Time (start): Sat, 2022-06-25 20:21:43 23 | Time (end): Sat, 2022-06-25 20:21:43 24 | Duration: 0.07 seconds 25 | Utilization of max. archive size: 0% 26 | Number of files: 699 27 | Original size: 31.14 MB 28 | Deduplicated size: 502 B 29 | 30 | 4. List all archives in the repository:: 31 | 32 | $ borg -r /path/to/repo repo-list 33 | docs Sat, 2022-06-25 20:21:14 [b80e24d2...b179f298] 34 | docs Sat, 2022-06-25 20:21:43 [bcd1b53f...1877718d] 35 | 36 | 5. List the contents of the first archive:: 37 | 38 | $ borg -r /path/to/repo list aid:b80e24d2 39 | drwxr-xr-x user group 0 Mon, 2016-02-15 18:22:30 home/user/Documents 40 | -rw-r--r-- user group 7961 Mon, 2016-02-15 18:22:30 home/user/Documents/Important.doc 41 | ... 42 | 43 | 6. Restore the first archive by extracting the files relative to the current directory:: 44 | 45 | $ borg -r /path/to/repo extract aid:b80e24d2 46 | 47 | 7. Delete the first archive (please note that this does **not** free repo disk space):: 48 | 49 | $ borg -r /path/to/repo delete aid:b80e24d2 50 | 51 | Be careful if you use an archive NAME (and not an archive ID), that might match multiple archives! 52 | Always first use with ``--dry-run`` and ``--list``! 53 | 54 | 8. Recover disk space by compacting the segment files in the repo:: 55 | 56 | $ borg -r /path/to/repo compact -v 57 | 58 | .. Note:: 59 | Borg is quiet by default (it defaults to WARNING log level). 60 | You can use options like ``--progress`` or ``--list`` to get specific 61 | reports during command execution. You can also add the ``-v`` (or 62 | ``--verbose`` or ``--info``) option to adjust the log level to INFO to 63 | get other informational messages. 64 | -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | .. _support: 2 | 3 | Support 4 | ======= 5 | 6 | Support and Services 7 | -------------------- 8 | 9 | Please see https://www.borgbackup.org/ for free and paid support and service options. 10 | 11 | 12 | .. _security-contact: 13 | 14 | Security 15 | -------- 16 | 17 | In case you discover a security issue, please use this contact for reporting it 18 | privately and please, if possible, use encrypted E-Mail: 19 | 20 | Thomas Waldmann 21 | 22 | GPG Key Fingerprint: 6D5B EF9A DD20 7580 5747 B70F 9F88 FB52 FAF7 B393 23 | 24 | The public key can be fetched from any GPG keyserver, but be careful: you must 25 | use the **full fingerprint** to check that you got the correct key. 26 | 27 | Verifying signed releases 28 | ------------------------- 29 | 30 | `Releases `_ are signed with the 31 | same GPG key and a .asc file is provided for each binary. 32 | 33 | To verify a signature, the public key needs to be known to GPG. It can be 34 | imported into the local keystore from a keyserver with the fingerprint:: 35 | 36 | gpg --recv-keys "6D5B EF9A DD20 7580 5747 B70F 9F88 FB52 FAF7 B393" 37 | 38 | If GPG successfully imported the key, the output should include (among other things):: 39 | 40 | ... 41 | gpg: Total number processed: 1 42 | ... 43 | 44 | To verify for example the signature of the borg-linux64 binary:: 45 | 46 | gpg --verify borg-linux64.asc 47 | 48 | GPG outputs if it finds a good signature. The output should look similar to this:: 49 | 50 | gpg: Signature made Sat 30 Dec 2017 01:07:36 PM CET using RSA key ID 51F78E01 51 | gpg: Good signature from "Thomas Waldmann " 52 | gpg: aka "Thomas Waldmann " 53 | gpg: aka "Thomas Waldmann " 54 | gpg: aka "Thomas Waldmann " 55 | gpg: WARNING: This key is not certified with a trusted signature! 56 | gpg: There is no indication that the signature belongs to the owner. 57 | Primary key fingerprint: 6D5B EF9A DD20 7580 5747 B70F 9F88 FB52 FAF7 B393 58 | Subkey fingerprint: 2F81 AFFB AB04 E11F E8EE 65D4 243A CFA9 51F7 8E01 59 | 60 | If you want to make absolutely sure that you have the right key, you need to 61 | verify it via another channel and assign a trust-level to it. 62 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | .. include:: global.rst.inc 2 | .. highlight:: none 3 | .. _detailed_usage: 4 | 5 | Usage 6 | ===== 7 | 8 | .. raw:: html 9 | Redirecting... 10 | 11 | 35 | 36 | .. toctree:: 37 | usage/general 38 | 39 | usage/repo-create 40 | usage/repo-space 41 | usage/repo-list 42 | usage/repo-info 43 | usage/repo-compress 44 | usage/repo-delete 45 | usage/serve 46 | usage/version 47 | usage/compact 48 | usage/lock 49 | usage/key 50 | 51 | usage/create 52 | usage/extract 53 | usage/check 54 | usage/list 55 | usage/tag 56 | usage/rename 57 | usage/diff 58 | usage/delete 59 | usage/prune 60 | usage/undelete 61 | usage/info 62 | usage/analyze 63 | usage/mount 64 | usage/recreate 65 | usage/tar 66 | 67 | usage/transfer 68 | usage/benchmark 69 | 70 | usage/help 71 | usage/debug 72 | usage/notes 73 | -------------------------------------------------------------------------------- /docs/usage/analyze.rst: -------------------------------------------------------------------------------- 1 | .. include:: analyze.rst.inc 2 | -------------------------------------------------------------------------------- /docs/usage/benchmark.rst: -------------------------------------------------------------------------------- 1 | .. include:: benchmark_crud.rst.inc 2 | 3 | .. include:: benchmark_cpu.rst.inc 4 | -------------------------------------------------------------------------------- /docs/usage/benchmark_cpu.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_benchmark_cpu: 4 | 5 | borg benchmark cpu 6 | ------------------ 7 | .. code-block:: none 8 | 9 | borg [common options] benchmark cpu [options] 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+ 16 | | .. class:: borg-common-opt-ref | 17 | | | 18 | | :ref:`common_options` | 19 | +-------------------------------------------------------+ 20 | 21 | .. raw:: html 22 | 23 | 28 | 29 | .. only:: latex 30 | 31 | 32 | 33 | :ref:`common_options` 34 | | 35 | 36 | Description 37 | ~~~~~~~~~~~ 38 | 39 | This command benchmarks misc. CPU bound borg operations. 40 | 41 | It creates input data in memory, runs the operation and then displays throughput. 42 | To reduce outside influence on the timings, please make sure to run this with: 43 | 44 | - an otherwise as idle as possible machine 45 | - enough free memory so there will be no slow down due to paging activity -------------------------------------------------------------------------------- /docs/usage/borgfs.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. include:: borgfs.rst.inc 4 | -------------------------------------------------------------------------------- /docs/usage/break-lock.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_break-lock: 4 | 5 | borg break-lock 6 | --------------- 7 | .. code-block:: none 8 | 9 | borg [common options] break-lock [options] 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+ 16 | | .. class:: borg-common-opt-ref | 17 | | | 18 | | :ref:`common_options` | 19 | +-------------------------------------------------------+ 20 | 21 | .. raw:: html 22 | 23 | 28 | 29 | .. only:: latex 30 | 31 | 32 | 33 | :ref:`common_options` 34 | | 35 | 36 | Description 37 | ~~~~~~~~~~~ 38 | 39 | This command breaks the repository and cache locks. 40 | Please use carefully and only while no borg process (on any machine) is 41 | trying to access the Cache or the Repository. -------------------------------------------------------------------------------- /docs/usage/check.rst: -------------------------------------------------------------------------------- 1 | .. include:: check.rst.inc 2 | -------------------------------------------------------------------------------- /docs/usage/common-options.rst.inc: -------------------------------------------------------------------------------- 1 | -h, --help show this help message and exit 2 | --critical work on log level CRITICAL 3 | --error work on log level ERROR 4 | --warning work on log level WARNING (default) 5 | --info, -v, --verbose work on log level INFO 6 | --debug enable debug output, work on log level DEBUG 7 | --debug-topic TOPIC enable TOPIC debugging (can be specified multiple times). The logger path is borg.debug. if TOPIC is not fully qualified. 8 | -p, --progress show progress information 9 | --iec format using IEC units (1KiB = 1024B) 10 | --log-json Output one JSON object per log line instead of formatted text. 11 | --lock-wait SECONDS wait at most SECONDS for acquiring a repository/cache lock (default: 10). 12 | --show-version show/log the borg version 13 | --show-rc show/log the return code (rc) 14 | --umask M set umask to M (local only, default: 0077) 15 | --remote-path PATH use PATH as borg executable on the remote (default: "borg") 16 | --upload-ratelimit RATE set network upload rate limit in kiByte/s (default: 0=unlimited) 17 | --upload-buffer UPLOAD_BUFFER set network upload buffer size in MiB. (default: 0=no buffer) 18 | --debug-profile FILE Write execution profile in Borg format into FILE. For local use a Python-compatible file can be generated by suffixing FILE with ".pyprof". 19 | --rsh RSH Use this command to connect to the 'borg serve' process (default: 'ssh') 20 | --socket PATH Use UNIX DOMAIN (IPC) socket at PATH for client/server communication with socket: protocol. 21 | -r REPO, --repo REPO repository to use 22 | -------------------------------------------------------------------------------- /docs/usage/compact.rst: -------------------------------------------------------------------------------- 1 | .. include:: compact.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | # compact segments and free repo disk space 8 | $ borg compact 9 | 10 | -------------------------------------------------------------------------------- /docs/usage/debug.rst: -------------------------------------------------------------------------------- 1 | Debugging Facilities 2 | -------------------- 3 | 4 | There is a ``borg debug`` command that has some subcommands which are all 5 | **not intended for normal use** and **potentially very dangerous** if used incorrectly. 6 | 7 | For example, ``borg debug put-obj`` and ``borg debug delete-obj`` will only do 8 | what their name suggests: put objects into repo / delete objects from repo. 9 | 10 | Please note: 11 | 12 | - they will not update the chunks cache (chunks index) about the object 13 | - they will not update the manifest (so no automatic chunks index resync is triggered) 14 | - they will not check whether the object is in use (e.g. before delete-obj) 15 | - they will not update any metadata which may point to the object 16 | 17 | They exist to improve debugging capabilities without direct system access, e.g. 18 | in case you ever run into some severe malfunction. Use them only if you know 19 | what you are doing or if a trusted Borg developer tells you what to do. 20 | 21 | Borg has a ``--debug-topic TOPIC`` option to enable specific debugging messages. Topics 22 | are generally not documented. 23 | 24 | A ``--debug-profile FILE`` option exists which writes a profile of the main program's 25 | execution to a file. The format of these files is not directly compatible with the 26 | Python profiling tools, since these use the "marshal" format, which is not intended 27 | to be secure (quoting the Python docs: "Never unmarshal data received from an untrusted 28 | or unauthenticated source."). 29 | 30 | The ``borg debug profile-convert`` command can be used to take a Borg profile and convert 31 | it to a profile file that is compatible with the Python tools. 32 | 33 | Additionally, if the filename specified for ``--debug-profile`` ends with ".pyprof" a 34 | Python compatible profile is generated. This is only intended for local use by developers. 35 | -------------------------------------------------------------------------------- /docs/usage/delete.rst: -------------------------------------------------------------------------------- 1 | .. include:: delete.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | # delete all backup archives named "kenny-files": 8 | $ borg delete -a kenny-files 9 | # actually free disk space: 10 | $ borg compact 11 | 12 | # delete a specific backup archive using its unique archive ID prefix 13 | $ borg delete aid:d34db33f 14 | 15 | # delete all archives whose names begin with the machine's hostname followed by "-" 16 | $ borg delete -a 'sh:{hostname}-*' 17 | 18 | # delete all archives whose names contain "-2012-" 19 | $ borg delete -a 'sh:*-2012-*' 20 | 21 | # see what would be deleted if delete was run without --dry-run 22 | $ borg delete --list --dry-run -a 'sh:*-May-*' 23 | 24 | -------------------------------------------------------------------------------- /docs/usage/diff.rst: -------------------------------------------------------------------------------- 1 | .. include:: diff.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | $ borg diff archive1 archive2 8 | +17 B -5 B [-rw-r--r-- -> -rwxr-xr-x] file1 9 | +135 B -252 B file2 10 | added 0 B file4 11 | removed 0 B file3 12 | 13 | $ borg diff archive1 archive2 14 | {"path": "file1", "changes": [{"type": "modified", "added": 17, "removed": 5}, {"type": "mode", "old_mode": "-rw-r--r--", "new_mode": "-rwxr-xr-x"}]} 15 | {"path": "file2", "changes": [{"type": "modified", "added": 135, "removed": 252}]} 16 | {"path": "file4", "changes": [{"type": "added", "size": 0}]} 17 | {"path": "file3", "changes": [{"type": "removed", "size": 0}]} 18 | 19 | -------------------------------------------------------------------------------- /docs/usage/extract.rst: -------------------------------------------------------------------------------- 1 | .. include:: extract.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | # Extract entire archive 8 | $ borg extract my-files 9 | 10 | # Extract entire archive and list files while processing 11 | $ borg extract --list my-files 12 | 13 | # Verify whether an archive could be successfully extracted, but do not write files to disk 14 | $ borg extract --dry-run my-files 15 | 16 | # Extract the "src" directory 17 | $ borg extract my-files home/USERNAME/src 18 | 19 | # Extract the "src" directory but exclude object files 20 | $ borg extract my-files home/USERNAME/src --exclude '*.o' 21 | 22 | # Restore a raw device (must not be active/in use/mounted at that time) 23 | $ borg extract --stdout my-sdx | dd of=/dev/sdx bs=10M 24 | 25 | -------------------------------------------------------------------------------- /docs/usage/general.rst: -------------------------------------------------------------------------------- 1 | General 2 | ------- 3 | 4 | Borg consists of a number of commands. Each command accepts 5 | a number of arguments and options and interprets various environment variables. 6 | The following sections will describe each command in detail. 7 | 8 | Commands, options, parameters, paths and such are ``set in fixed-width``. 9 | Option values are `underlined`. Borg has few options accepting a fixed set 10 | of values (e.g. ``--encryption`` of :ref:`borg_repo-create`). 11 | 12 | .. container:: experimental 13 | 14 | Experimental features are marked with red stripes on the sides, like this paragraph. 15 | 16 | Experimental features are not stable, which means that they may be changed in incompatible 17 | ways or even removed entirely without prior notice in following releases. 18 | 19 | .. include:: usage_general.rst.inc 20 | 21 | In case you are interested in more details (like formulas), please see 22 | :ref:`internals`. For details on the available JSON output, refer to 23 | :ref:`json_output`. 24 | 25 | .. _common_options: 26 | 27 | Common options 28 | ~~~~~~~~~~~~~~ 29 | 30 | All Borg commands share these options: 31 | 32 | .. include:: common-options.rst.inc 33 | 34 | Option ``--help`` when used as a command works as expected on subcommands (e.g., ``borg help compact``). 35 | But it does not work when the help command is being used on sub-sub-commands (e.g., ``borg help key export``). 36 | The workaround for this to use the help command as a flag (e.g., ``borg key export --help``). 37 | 38 | Examples 39 | ~~~~~~~~ 40 | :: 41 | 42 | # Create an archive and log: borg version, files list, return code 43 | $ borg -r /path/to/repo create --show-version --list --show-rc my-files files 44 | 45 | -------------------------------------------------------------------------------- /docs/usage/general/date-time.rst.inc: -------------------------------------------------------------------------------- 1 | Date and Time 2 | ~~~~~~~~~~~~~ 3 | 4 | We format date and time conforming to ISO-8601, that is: YYYY-MM-DD and 5 | HH:MM:SS (24h clock). 6 | 7 | For more information about that, see: https://xkcd.com/1179/ 8 | 9 | Unless otherwise noted, we display local date and time. 10 | Internally, we store and process date and time as UTC. 11 | 12 | 13 | .. rubric:: TIMESPAN 14 | 15 | Some options accept a TIMESPAN parameter, which can be given as a number of 16 | years (e.g. ``2y``), months (e.g. ``12m``), weeks (e.g. ``2w``), 17 | days (e.g. ``7d``), hours (e.g. ``8H``), minutes (e.g. ``30M``), 18 | or seconds (e.g. ``150S``). 19 | -------------------------------------------------------------------------------- /docs/usage/general/file-systems.rst.inc: -------------------------------------------------------------------------------- 1 | File systems 2 | ~~~~~~~~~~~~ 3 | 4 | We recommend using a reliable, scalable journaling filesystem for the 5 | repository, e.g. zfs, btrfs, ext4, apfs. 6 | 7 | Borg now uses the ``borgstore`` package to implement the key/value store it 8 | uses for the repository. 9 | 10 | It currently uses the ``file:`` Store (posixfs backend) either with a local 11 | directory or via ssh and a remote ``borg serve`` agent using borgstore on the 12 | remote side. 13 | 14 | This means that it will store each chunk into a separate filesystem file 15 | (for more details, see the ``borgstore`` project). 16 | 17 | This has some pros and cons (compared to legacy borg 1.x's segment files): 18 | 19 | Pros: 20 | 21 | - Simplicity and better maintainability of the borg code. 22 | - Sometimes faster, less I/O, better scalability: e.g. borg compact can just 23 | remove unused chunks by deleting a single file and does not need to read 24 | and re-write segment files to free space. 25 | - In future, easier to adapt to other kinds of storage: 26 | borgstore's backends are quite simple to implement. 27 | ``sftp:`` and ``rclone:`` backends already exist, others might be easy to add. 28 | - Parallel repository access with less locking is easier to implement. 29 | 30 | Cons: 31 | 32 | - The repository filesystem will have to deal with a big amount of files (there 33 | are provisions in borgstore against having too many files in a single directory 34 | by using a nested directory structure). 35 | - Bigger fs space usage overhead (will depend on allocation block size - modern 36 | filesystems like zfs are rather clever here using a variable block size). 37 | - Sometimes slower, due to less sequential / more random access operations. 38 | -------------------------------------------------------------------------------- /docs/usage/general/logging.rst.inc: -------------------------------------------------------------------------------- 1 | Logging 2 | ~~~~~~~ 3 | 4 | Borg writes all log output to stderr by default. But please note that something 5 | showing up on stderr does *not* indicate an error condition just because it is 6 | on stderr. Please check the log levels of the messages and the return code of 7 | borg for determining error, warning or success conditions. 8 | 9 | If you want to capture the log output to a file, just redirect it: 10 | 11 | :: 12 | 13 | borg create --repo repo archive myfiles 2>> logfile 14 | 15 | 16 | Custom logging configurations can be implemented via BORG_LOGGING_CONF. 17 | 18 | The log level of the builtin logging configuration defaults to WARNING. 19 | This is because we want Borg to be mostly silent and only output 20 | warnings, errors and critical messages, unless output has been requested 21 | by supplying an option that implies output (e.g. ``--list`` or ``--progress``). 22 | 23 | Log levels: DEBUG < INFO < WARNING < ERROR < CRITICAL 24 | 25 | Use ``--debug`` to set DEBUG log level - 26 | to get debug, info, warning, error and critical level output. 27 | 28 | Use ``--info`` (or ``-v`` or ``--verbose``) to set INFO log level - 29 | to get info, warning, error and critical level output. 30 | 31 | Use ``--warning`` (default) to set WARNING log level - 32 | to get warning, error and critical level output. 33 | 34 | Use ``--error`` to set ERROR log level - 35 | to get error and critical level output. 36 | 37 | Use ``--critical`` to set CRITICAL log level - 38 | to get critical level output. 39 | 40 | While you can set misc. log levels, do not expect that every command will 41 | give different output on different log levels - it's just a possibility. 42 | 43 | .. warning:: Options ``--critical`` and ``--error`` are provided for completeness, 44 | their usage is not recommended as you might miss important information. 45 | -------------------------------------------------------------------------------- /docs/usage/general/positional-arguments.rst.inc: -------------------------------------------------------------------------------- 1 | Positional Arguments and Options: Order matters 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Borg only supports taking options (``-s`` and ``--progress`` in the example) 5 | to the left or right of all positional arguments (``repo::archive`` and ``path`` 6 | in the example), but not in between them: 7 | 8 | :: 9 | 10 | borg create -s --progress archive path # good and preferred 11 | borg create archive path -s --progress # also works 12 | borg create -s archive path --progress # works, but ugly 13 | borg create archive -s --progress path # BAD 14 | 15 | This is due to a problem in the argparse module: https://bugs.python.org/issue15112 16 | -------------------------------------------------------------------------------- /docs/usage/general/repository-locations.rst.inc: -------------------------------------------------------------------------------- 1 | Repository Locations / Archive names 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Many commands need to know the repository location, give it via ``-r`` / ``--repo`` 5 | or use the ``BORG_REPO`` environment variable. 6 | 7 | Commands needing one or two archive names usually get them as positional argument. 8 | 9 | Commands working with an arbitrary amount of archives, usually take ``-a ARCH_GLOB``. 10 | 11 | Archive names must not contain the ``/`` (slash) character. For simplicity, 12 | maybe also avoid blanks or other characters that have special meaning on the 13 | shell or in a filesystem (borg mount will use the archive name as directory 14 | name). 15 | -------------------------------------------------------------------------------- /docs/usage/general/repository-urls.rst.inc: -------------------------------------------------------------------------------- 1 | Repository URLs 2 | ~~~~~~~~~~~~~~~ 3 | 4 | **Local filesystem** (or locally mounted network filesystem): 5 | 6 | ``/path/to/repo`` - filesystem path to repo directory, absolute path 7 | 8 | ``path/to/repo`` - filesystem path to repo directory, relative path 9 | 10 | Also, stuff like ``~/path/to/repo`` or ``~other/path/to/repo`` works (this is 11 | expanded by your shell). 12 | 13 | Note: you may also prepend a ``file://`` to a filesystem path to get URL style. 14 | 15 | **Remote repositories** accessed via ssh user@host: 16 | 17 | ``ssh://user@host:port//abs/path/to/repo`` - absolute path 18 | 19 | ``ssh://user@host:port/rel/path/to/repo`` - path relative to current directory 20 | 21 | **Remote repositories** accessed via sftp: 22 | 23 | ``sftp://user@host:port//abs/path/to/repo`` - absolute path 24 | 25 | ``sftp://user@host:port/rel/path/to/repo`` - path relative to current directory 26 | 27 | For ssh and sftp URLs, the ``user@`` and ``:port`` parts are optional. 28 | 29 | **Remote repositories** accessed via rclone: 30 | 31 | ``rclone:remote:path`` - see the rclone docs for more details about remote:path. 32 | 33 | **Remote repositories** accessed via S3: 34 | 35 | ``(s3|b2):[profile|(access_key_id:access_key_secret)@][schema://hostname[:port]]/bucket/path`` - see the boto3 docs for more details about the credentials. 36 | 37 | If you're connecting to AWS S3, ``[schema://hostname[:port]]`` is optional, but ``bucket`` and ``path`` are always required. 38 | 39 | Note: There is a known issue with some S3-compatible services, e.g., Backblaze B2. If you encounter problems, try using ``b2:`` instead of ``s3:`` in the url. 40 | 41 | 42 | If you frequently need the same repo URL, it is a good idea to set the 43 | ``BORG_REPO`` environment variable to set a default for the repo URL: 44 | 45 | :: 46 | 47 | export BORG_REPO='ssh://user@host:port/rel/path/to/repo' 48 | 49 | Then just leave away the ``--repo`` option if you want 50 | to use the default - it will be read from BORG_REPO then. 51 | -------------------------------------------------------------------------------- /docs/usage/general/return-codes.rst.inc: -------------------------------------------------------------------------------- 1 | Return codes 2 | ~~~~~~~~~~~~ 3 | 4 | Borg can exit with the following return codes (rc): 5 | 6 | =========== ======= 7 | Return code Meaning 8 | =========== ======= 9 | 0 success (logged as INFO) 10 | 1 generic warning (operation reached its normal end, but there were warnings -- 11 | you should check the log, logged as WARNING) 12 | 2 generic error (like a fatal error, a local or remote exception, the operation 13 | did not reach its normal end, logged as ERROR) 14 | 3..99 specific error (enabled by BORG_EXIT_CODES=modern) 15 | 100..127 specific warning (enabled by BORG_EXIT_CODES=modern) 16 | 128+N killed by signal N (e.g. 137 == kill -9) 17 | =========== ======= 18 | 19 | If you use ``--show-rc``, the return code is also logged at the indicated 20 | level as the last log entry. 21 | 22 | The modern exit codes (return codes, "rc") are documented there: :ref:`msgid` 23 | -------------------------------------------------------------------------------- /docs/usage/general/units.rst.inc: -------------------------------------------------------------------------------- 1 | Units 2 | ~~~~~ 3 | 4 | To display quantities, Borg takes care of respecting the 5 | usual conventions of scale. Disk sizes are displayed in `decimal 6 | `_, using powers of ten (so 7 | ``kB`` means 1000 bytes). For memory usage, `binary prefixes 8 | `_ are used, and are 9 | indicated using the `IEC binary prefixes 10 | `_, 11 | using powers of two (so ``KiB`` means 1024 bytes). 12 | -------------------------------------------------------------------------------- /docs/usage/help.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous Help 2 | ------------------ 3 | 4 | .. include:: help.rst.inc 5 | -------------------------------------------------------------------------------- /docs/usage/info.rst: -------------------------------------------------------------------------------- 1 | .. include:: info.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | $ borg info aid:f7dea078 8 | Archive name: source-backup 9 | Archive fingerprint: f7dea0788dfc026cc2be1c0f5b94beb4e4084eb3402fc40c38d8719b1bf2d943 10 | Comment: 11 | Hostname: mba2020 12 | Username: tw 13 | Time (start): Sat, 2022-06-25 20:51:40 14 | Time (end): Sat, 2022-06-25 20:51:40 15 | Duration: 0.03 seconds 16 | Command line: /usr/bin/borg -r path/to/repo create source-backup src 17 | Utilization of maximum supported archive size: 0% 18 | Number of files: 244 19 | Original size: 13.80 MB 20 | Deduplicated size: 531 B 21 | 22 | -------------------------------------------------------------------------------- /docs/usage/key.rst: -------------------------------------------------------------------------------- 1 | .. include:: key_change-location.rst.inc 2 | 3 | .. _borg-change-passphrase: 4 | 5 | .. include:: key_change-passphrase.rst.inc 6 | 7 | Examples 8 | ~~~~~~~~ 9 | :: 10 | 11 | # Create a key file protected repository 12 | $ borg repo-create --encryption=keyfile-aes-ocb -v 13 | Initializing repository at "/path/to/repo" 14 | Enter new passphrase: 15 | Enter same passphrase again: 16 | Remember your passphrase. Your data will be inaccessible without it. 17 | Key in "/root/.config/borg/keys/mnt_backup" created. 18 | Keep this key safe. Your data will be inaccessible without it. 19 | Synchronizing chunks cache... 20 | Archives: 0, w/ cached Idx: 0, w/ outdated Idx: 0, w/o cached Idx: 0. 21 | Done. 22 | 23 | # Change key file passphrase 24 | $ borg key change-passphrase -v 25 | Enter passphrase for key /root/.config/borg/keys/mnt_backup: 26 | Enter new passphrase: 27 | Enter same passphrase again: 28 | Remember your passphrase. Your data will be inaccessible without it. 29 | Key updated 30 | 31 | # Import a previously-exported key into the specified 32 | # key file (creating or overwriting the output key) 33 | # (keyfile repositories only) 34 | $ BORG_KEY_FILE=/path/to/output-key borg key import /path/to/exported 35 | 36 | Fully automated using environment variables: 37 | 38 | :: 39 | 40 | $ BORG_NEW_PASSPHRASE=old borg repo-create --encryption=repokey-aes-ocb 41 | # now "old" is the current passphrase. 42 | $ BORG_PASSPHRASE=old BORG_NEW_PASSPHRASE=new borg key change-passphrase 43 | # now "new" is the current passphrase. 44 | 45 | 46 | .. include:: key_export.rst.inc 47 | 48 | Examples 49 | ~~~~~~~~ 50 | :: 51 | 52 | borg key export > encrypted-key-backup 53 | borg key export --paper > encrypted-key-backup.txt 54 | borg key export --qr-html > encrypted-key-backup.html 55 | # Or pass the output file as an argument instead of redirecting stdout: 56 | borg key export encrypted-key-backup 57 | borg key export --paper encrypted-key-backup.txt 58 | borg key export --qr-html encrypted-key-backup.html 59 | 60 | .. include:: key_import.rst.inc 61 | -------------------------------------------------------------------------------- /docs/usage/key_change-passphrase.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_key_change-passphrase: 4 | 5 | borg key change-passphrase 6 | -------------------------- 7 | .. code-block:: none 8 | 9 | borg [common options] key change-passphrase [options] 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+ 16 | | .. class:: borg-common-opt-ref | 17 | | | 18 | | :ref:`common_options` | 19 | +-------------------------------------------------------+ 20 | 21 | .. raw:: html 22 | 23 | 28 | 29 | .. only:: latex 30 | 31 | 32 | 33 | :ref:`common_options` 34 | | 35 | 36 | Description 37 | ~~~~~~~~~~~ 38 | 39 | The key files used for repository encryption are optionally passphrase 40 | protected. This command can be used to change this passphrase. 41 | 42 | Please note that this command only changes the passphrase, but not any 43 | secret protected by it (like e.g. encryption/MAC keys or chunker seed). 44 | Thus, changing the passphrase after passphrase and borg key got compromised 45 | does not protect future (nor past) backups to the same repository. -------------------------------------------------------------------------------- /docs/usage/list.rst: -------------------------------------------------------------------------------- 1 | .. include:: list.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | $ borg list root-2016-02-15 8 | drwxr-xr-x root root 0 Mon, 2016-02-15 17:44:27 . 9 | drwxrwxr-x root root 0 Mon, 2016-02-15 19:04:49 bin 10 | -rwxr-xr-x root root 1029624 Thu, 2014-11-13 00:08:51 bin/bash 11 | lrwxrwxrwx root root 0 Fri, 2015-03-27 20:24:26 bin/bzcmp -> bzdiff 12 | -rwxr-xr-x root root 2140 Fri, 2015-03-27 20:24:22 bin/bzdiff 13 | ... 14 | 15 | $ borg list root-2016-02-15 --pattern "- bin/ba*" 16 | drwxr-xr-x root root 0 Mon, 2016-02-15 17:44:27 . 17 | drwxrwxr-x root root 0 Mon, 2016-02-15 19:04:49 bin 18 | lrwxrwxrwx root root 0 Fri, 2015-03-27 20:24:26 bin/bzcmp -> bzdiff 19 | -rwxr-xr-x root root 2140 Fri, 2015-03-27 20:24:22 bin/bzdiff 20 | ... 21 | 22 | $ borg list archiveA --format="{mode} {user:6} {group:6} {size:8d} {isomtime} {path}{extra}{NEWLINE}" 23 | drwxrwxr-x user user 0 Sun, 2015-02-01 11:00:00 . 24 | drwxrwxr-x user user 0 Sun, 2015-02-01 11:00:00 code 25 | drwxrwxr-x user user 0 Sun, 2015-02-01 11:00:00 code/myproject 26 | -rw-rw-r-- user user 1416192 Sun, 2015-02-01 11:00:00 code/myproject/file.ext 27 | -rw-rw-r-- user user 1416192 Sun, 2015-02-01 11:00:00 code/myproject/file.text 28 | ... 29 | 30 | $ borg list archiveA --pattern '+ re:\.ext$' --pattern '- re:^.*$' 31 | -rw-rw-r-- user user 1416192 Sun, 2015-02-01 11:00:00 code/myproject/file.ext 32 | ... 33 | 34 | $ borg list archiveA --pattern '+ re:.ext$' --pattern '- re:^.*$' 35 | -rw-rw-r-- user user 1416192 Sun, 2015-02-01 11:00:00 code/myproject/file.ext 36 | -rw-rw-r-- user user 1416192 Sun, 2015-02-01 11:00:00 code/myproject/file.text 37 | ... 38 | 39 | -------------------------------------------------------------------------------- /docs/usage/lock.rst: -------------------------------------------------------------------------------- 1 | .. include:: with-lock.rst.inc 2 | 3 | .. include:: break-lock.rst.inc 4 | -------------------------------------------------------------------------------- /docs/usage/mount.rst: -------------------------------------------------------------------------------- 1 | .. include:: mount.rst.inc 2 | 3 | .. include:: umount.rst.inc 4 | 5 | Examples 6 | ~~~~~~~~ 7 | 8 | :: 9 | 10 | # Mounting the repository shows all archives. 11 | # Archives are loaded lazily, expect some delay when navigating to an archive 12 | # for the first time. 13 | $ borg mount /tmp/mymountpoint 14 | $ ls /tmp/mymountpoint 15 | root-2016-02-14 root-2016-02-15 16 | $ borg umount /tmp/mymountpoint 17 | 18 | # The "versions view" merges all archives in the repository 19 | # and provides a versioned view on files. 20 | $ borg mount -o versions /tmp/mymountpoint 21 | $ ls -l /tmp/mymountpoint/home/user/doc.txt/ 22 | total 24 23 | -rw-rw-r-- 1 user group 12357 Aug 26 21:19 doc.cda00bc9.txt 24 | -rw-rw-r-- 1 user group 12204 Aug 26 21:04 doc.fa760f28.txt 25 | $ borg umount /tmp/mymountpoint 26 | 27 | # Archive filters are supported. 28 | # These are especially handy for the "versions view", 29 | # which does not support lazy processing of archives. 30 | $ borg mount -o versions --match-archives 'sh:*-my-home' --last 10 /tmp/mymountpoint 31 | 32 | # Exclusion options are supported. 33 | # These can speed up mounting and lower memory needs significantly. 34 | $ borg mount /path/to/repo /tmp/mymountpoint only/that/path 35 | $ borg mount --exclude '...' /tmp/mymountpoint 36 | 37 | 38 | borgfs 39 | ++++++ 40 | 41 | :: 42 | 43 | $ echo '/mnt/backup /tmp/myrepo fuse.borgfs defaults,noauto 0 0' >> /etc/fstab 44 | $ mount /tmp/myrepo 45 | $ ls /tmp/myrepo 46 | root-2016-02-01 root-2016-02-2015 47 | 48 | .. Note:: 49 | 50 | ``borgfs`` will be automatically provided if you used a distribution 51 | package or ``pip`` to install Borg. Users of the standalone binary will have 52 | to manually create a symlink (see :ref:`pyinstaller-binary`). 53 | -------------------------------------------------------------------------------- /docs/usage/prune.rst: -------------------------------------------------------------------------------- 1 | .. include:: prune.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | 6 | Be careful, prune is a potentially dangerous command, it will remove backup 7 | archives. 8 | 9 | The default of prune is to apply to **all archives in the repository** unless 10 | you restrict its operation to a subset of the archives. 11 | 12 | The recommended way to name archives (with ``borg create``) is to use the 13 | identical archive name within a series of archives. Then you can simply give 14 | that name to prune also, so it operates just on that series of archives. 15 | 16 | Alternatively, you can use ``-a`` / ``--match-archives`` to do a match on the 17 | archive names to select some of them. 18 | When using ``-a``, be careful to choose a good pattern - e.g. do not use a 19 | prefix "foo" if you do not also want to match "foobar". 20 | 21 | It is strongly recommended to always run ``prune -v --list --dry-run ...`` 22 | first so you will see what it would do without it actually doing anything. 23 | 24 | Don't forget to run ``borg compact -v`` after prune to actually free disk space. 25 | 26 | :: 27 | 28 | # Keep 7 end of day and 4 additional end of week archives. 29 | # Do a dry-run without actually deleting anything. 30 | $ borg prune -v --list --dry-run --keep-daily=7 --keep-weekly=4 31 | 32 | # Similar as above but only apply to the archive series named '{hostname}': 33 | $ borg prune -v --list --keep-daily=7 --keep-weekly=4 '{hostname}' 34 | 35 | # Similar as above but apply to archive names starting with the hostname 36 | # of the machine followed by a "-" character: 37 | $ borg prune -v --list --keep-daily=7 --keep-weekly=4 -a 'sh:{hostname}-*' 38 | 39 | # Keep 7 end of day, 4 additional end of week archives, 40 | # and an end of month archive for every month: 41 | $ borg prune -v --list --keep-daily=7 --keep-weekly=4 --keep-monthly=-1 42 | 43 | # Keep all backups in the last 10 days, 4 additional end of week archives, 44 | # and an end of month archive for every month: 45 | $ borg prune -v --list --keep-within=10d --keep-weekly=4 --keep-monthly=-1 46 | 47 | There is also a visualized prune example in ``docs/misc/prune-example.txt``: 48 | 49 | .. highlight:: none 50 | .. include:: ../misc/prune-example.txt 51 | :literal: 52 | -------------------------------------------------------------------------------- /docs/usage/recreate.rst: -------------------------------------------------------------------------------- 1 | .. include:: recreate.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | # Create a backup with little but fast compression 8 | $ borg create archive /some/files --compression lz4 9 | # Then compress it - this might take longer, but the backup has already completed, 10 | # so no inconsistencies from a long-running backup job. 11 | $ borg recreate -a archive --recompress --compression zlib,9 12 | 13 | # Remove unwanted files from all archives in a repository. 14 | # Note the relative path for the --exclude option - archives only contain relative paths. 15 | $ borg recreate --exclude home/icke/Pictures/drunk_photos 16 | 17 | # Change archive comment 18 | $ borg create --comment "This is a comment" archivename ~ 19 | $ borg info -a archivename 20 | Name: archivename 21 | Fingerprint: ... 22 | Comment: This is a comment 23 | ... 24 | $ borg recreate --comment "This is a better comment" -a archivename 25 | $ borg info -a archivename 26 | Name: archivename 27 | Fingerprint: ... 28 | Comment: This is a better comment 29 | ... 30 | 31 | -------------------------------------------------------------------------------- /docs/usage/rename.rst: -------------------------------------------------------------------------------- 1 | .. include:: rename.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | $ borg create archivename ~ 8 | $ borg repo-list 9 | archivename Mon, 2016-02-15 19:50:19 10 | 11 | $ borg rename archivename newname 12 | $ borg repo-list 13 | newname Mon, 2016-02-15 19:50:19 14 | 15 | -------------------------------------------------------------------------------- /docs/usage/rename.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_rename: 4 | 5 | borg rename 6 | ----------- 7 | .. code-block:: none 8 | 9 | borg [common options] rename [options] OLDNAME NEWNAME 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+-------------+------------------------------+ 16 | | **positional arguments** | 17 | +-------------------------------------------------------+-------------+------------------------------+ 18 | | | ``OLDNAME`` | specify the archive name | 19 | +-------------------------------------------------------+-------------+------------------------------+ 20 | | | ``NEWNAME`` | specify the new archive name | 21 | +-------------------------------------------------------+-------------+------------------------------+ 22 | | .. class:: borg-common-opt-ref | 23 | | | 24 | | :ref:`common_options` | 25 | +-------------------------------------------------------+-------------+------------------------------+ 26 | 27 | .. raw:: html 28 | 29 | 34 | 35 | .. only:: latex 36 | 37 | OLDNAME 38 | specify the archive name 39 | NEWNAME 40 | specify the new archive name 41 | 42 | 43 | :ref:`common_options` 44 | | 45 | 46 | Description 47 | ~~~~~~~~~~~ 48 | 49 | This command renames an archive in the repository. 50 | 51 | This results in a different archive ID. -------------------------------------------------------------------------------- /docs/usage/repo-compress.rst: -------------------------------------------------------------------------------- 1 | .. include:: repo-compress.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | 6 | :: 7 | 8 | # recompress repo contents 9 | $ borg repo-compress --progress --compression=zstd,3 10 | 11 | # recompress and obfuscate repo contents 12 | $ borg repo-compress --progress --compression=obfuscate,1,zstd,3 13 | -------------------------------------------------------------------------------- /docs/usage/repo-create.rst: -------------------------------------------------------------------------------- 1 | .. _borg_repo_create: 2 | 3 | .. include:: repo-create.rst.inc 4 | 5 | Examples 6 | ~~~~~~~~ 7 | :: 8 | 9 | # Local repository 10 | $ export BORG_REPO=/path/to/repo 11 | # recommended repokey AEAD crypto modes 12 | $ borg repo-create --encryption=repokey-aes-ocb 13 | $ borg repo-create --encryption=repokey-chacha20-poly1305 14 | $ borg repo-create --encryption=repokey-blake2-aes-ocb 15 | $ borg repo-create --encryption=repokey-blake2-chacha20-poly1305 16 | # no encryption, not recommended 17 | $ borg repo-create --encryption=authenticated 18 | $ borg repo-create --encryption=authenticated-blake2 19 | $ borg repo-create --encryption=none 20 | 21 | # Remote repository (accesses a remote borg via ssh) 22 | $ export BORG_REPO=ssh://user@hostname/~/backup 23 | # repokey: stores the (encrypted) key into /config 24 | $ borg repo-create --encryption=repokey-aes-ocb 25 | # keyfile: stores the (encrypted) key into ~/.config/borg/keys/ 26 | $ borg repo-create --encryption=keyfile-aes-ocb 27 | 28 | -------------------------------------------------------------------------------- /docs/usage/repo-delete.rst: -------------------------------------------------------------------------------- 1 | .. include:: repo-delete.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | # delete the whole repository and the related local cache: 8 | $ borg repo-delete 9 | You requested to DELETE the repository completely *including* all archives it contains: 10 | repo Mon, 2016-02-15 19:26:54 11 | root-2016-02-15 Mon, 2016-02-15 19:36:29 12 | newname Mon, 2016-02-15 19:50:19 13 | Type 'YES' if you understand this and want to continue: YES 14 | 15 | -------------------------------------------------------------------------------- /docs/usage/repo-info.rst: -------------------------------------------------------------------------------- 1 | .. include:: repo-info.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | $ borg repo-info 8 | Repository ID: 0e85a7811022326c067acb2a7181d5b526b7d2f61b34470fb8670c440a67f1a9 9 | Location: /Users/tw/w/borg/path/to/repo 10 | Encrypted: Yes (repokey AES-OCB) 11 | Cache: /Users/tw/.cache/borg/0e85a7811022326c067acb2a7181d5b526b7d2f61b34470fb8670c440a67f1a9 12 | Security dir: /Users/tw/.config/borg/security/0e85a7811022326c067acb2a7181d5b526b7d2f61b34470fb8670c440a67f1a9 13 | Original size: 152.14 MB 14 | Deduplicated size: 30.38 MB 15 | Unique chunks: 654 16 | Total chunks: 3302 17 | 18 | -------------------------------------------------------------------------------- /docs/usage/repo-info.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_repo-info: 4 | 5 | borg repo-info 6 | -------------- 7 | .. code-block:: none 8 | 9 | borg [common options] repo-info [options] 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+------------+-----------------------+ 16 | | **options** | 17 | +-------------------------------------------------------+------------+-----------------------+ 18 | | | ``--json`` | format output as JSON | 19 | +-------------------------------------------------------+------------+-----------------------+ 20 | | .. class:: borg-common-opt-ref | 21 | | | 22 | | :ref:`common_options` | 23 | +-------------------------------------------------------+------------+-----------------------+ 24 | 25 | .. raw:: html 26 | 27 | 32 | 33 | .. only:: latex 34 | 35 | 36 | 37 | options 38 | --json format output as JSON 39 | 40 | 41 | :ref:`common_options` 42 | | 43 | 44 | Description 45 | ~~~~~~~~~~~ 46 | 47 | This command displays detailed information about the repository. -------------------------------------------------------------------------------- /docs/usage/repo-list.rst: -------------------------------------------------------------------------------- 1 | .. include:: repo-list.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | $ borg repo-list 8 | 151b1a57 Mon, 2024-09-23 22:57:11 +0200 docs tw MacBook-Pro this is a comment 9 | 3387a079 Thu, 2024-09-26 09:07:07 +0200 scripts tw MacBook-Pro 10 | ca774425 Thu, 2024-09-26 10:05:23 +0200 scripts tw MacBook-Pro 11 | ba56c4a5 Thu, 2024-09-26 10:12:45 +0200 src tw MacBook-Pro 12 | 7567b79a Thu, 2024-09-26 10:15:07 +0200 scripts tw MacBook-Pro 13 | 21ab3600 Thu, 2024-09-26 10:15:17 +0200 docs tw MacBook-Pro 14 | ... 15 | 16 | -------------------------------------------------------------------------------- /docs/usage/repo-space.rst: -------------------------------------------------------------------------------- 1 | .. include:: repo-space.rst.inc 2 | -------------------------------------------------------------------------------- /docs/usage/tag.rst: -------------------------------------------------------------------------------- 1 | .. include:: tag.rst.inc 2 | -------------------------------------------------------------------------------- /docs/usage/tar.rst: -------------------------------------------------------------------------------- 1 | .. include:: export-tar.rst.inc 2 | 3 | .. include:: import-tar.rst.inc 4 | 5 | Examples 6 | ~~~~~~~~ 7 | :: 8 | 9 | # export as uncompressed tar 10 | $ borg export-tar Monday Monday.tar 11 | 12 | # import an uncompressed tar 13 | $ borg import-tar Monday Monday.tar 14 | 15 | # exclude some file types, compress using gzip 16 | $ borg export-tar Monday Monday.tar.gz --exclude '*.so' 17 | 18 | # use higher compression level with gzip 19 | $ borg export-tar --tar-filter="gzip -9" Monday Monday.tar.gz 20 | 21 | # copy an archive from repoA to repoB 22 | $ borg -r repoA export-tar --tar-format=BORG archive - | borg -r repoB import-tar archive - 23 | 24 | # export a tar, but instead of storing it on disk, upload it to remote site using curl 25 | $ borg export-tar Monday - | curl --data-binary @- https://somewhere/to/POST 26 | 27 | # remote extraction via "tarpipe" 28 | $ borg export-tar Monday - | ssh somewhere "cd extracted; tar x" 29 | 30 | Archives transfer script 31 | ~~~~~~~~~~~~~~~~~~~~~~~~ 32 | 33 | Outputs a script that copies all archives from repo1 to repo2: 34 | 35 | :: 36 | 37 | for N I T in `borg list --format='{archive} {id} {time:%Y-%m-%dT%H:%M:%S}{NL}'` 38 | do 39 | echo "borg -r repo1 export-tar --tar-format=BORG aid:$I - | borg -r repo2 import-tar --timestamp=$T $N -" 40 | done 41 | 42 | Kept: 43 | 44 | - archive name, archive timestamp 45 | - archive contents (all items with metadata and data) 46 | 47 | Lost: 48 | 49 | - some archive metadata (like the original commandline, execution time, etc.) 50 | 51 | Please note: 52 | 53 | - all data goes over that pipe, again and again for every archive 54 | - the pipe is dumb, there is no data or transfer time reduction there due to deduplication 55 | - maybe add compression 56 | - pipe over ssh for remote transfer 57 | - no special sparse file support 58 | -------------------------------------------------------------------------------- /docs/usage/transfer.rst: -------------------------------------------------------------------------------- 1 | .. include:: transfer.rst.inc 2 | 3 | Examples 4 | ~~~~~~~~ 5 | :: 6 | 7 | # 0. Have borg 2.0 installed on client AND server, have a b12 repo copy for testing. 8 | 9 | # 1. Create a new "related" repository: 10 | # here, the existing borg 1.2 repo used repokey-blake2 (and aes-ctr mode), 11 | # thus we use repokey-blake2-aes-ocb for the new borg 2.0 repo. 12 | # staying with the same chunk id algorithm (blake2) and with the same 13 | # key material (via --other-repo ) will make deduplication work 14 | # between old archives (copied with borg transfer) and future ones. 15 | # the AEAD cipher does not matter (everything must be re-encrypted and 16 | # re-authenticated anyway), you could also choose repokey-blake2-chacha20-poly1305. 17 | # in case your old borg repo did not use blake2, just remove the "-blake2". 18 | $ borg --repo ssh://borg2@borgbackup/./tests/b20 repo-create \ 19 | --other-repo ssh://borg2@borgbackup/./tests/b12 -e repokey-blake2-aes-ocb 20 | 21 | # 2. Check what and how much it would transfer: 22 | $ borg --repo ssh://borg2@borgbackup/./tests/b20 transfer --upgrader=From12To20 \ 23 | --other-repo ssh://borg2@borgbackup/./tests/b12 --dry-run 24 | 25 | # 3. Transfer (copy) archives from old repo into new repo (takes time and space!): 26 | $ borg --repo ssh://borg2@borgbackup/./tests/b20 transfer --upgrader=From12To20 \ 27 | --other-repo ssh://borg2@borgbackup/./tests/b12 28 | 29 | # 4. Check if we have everything (same as 2.): 30 | $ borg --repo ssh://borg2@borgbackup/./tests/b20 transfer --upgrader=From12To20 \ 31 | --other-repo ssh://borg2@borgbackup/./tests/b12 --dry-run 32 | 33 | -------------------------------------------------------------------------------- /docs/usage/umount.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_umount: 4 | 5 | borg umount 6 | ----------- 7 | .. code-block:: none 8 | 9 | borg [common options] umount [options] MOUNTPOINT 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+----------------+----------------------------------------+ 16 | | **positional arguments** | 17 | +-------------------------------------------------------+----------------+----------------------------------------+ 18 | | | ``MOUNTPOINT`` | mountpoint of the filesystem to umount | 19 | +-------------------------------------------------------+----------------+----------------------------------------+ 20 | | .. class:: borg-common-opt-ref | 21 | | | 22 | | :ref:`common_options` | 23 | +-------------------------------------------------------+----------------+----------------------------------------+ 24 | 25 | .. raw:: html 26 | 27 | 32 | 33 | .. only:: latex 34 | 35 | MOUNTPOINT 36 | mountpoint of the filesystem to umount 37 | 38 | 39 | :ref:`common_options` 40 | | 41 | 42 | Description 43 | ~~~~~~~~~~~ 44 | 45 | This command un-mounts a FUSE filesystem that was mounted with ``borg mount``. 46 | 47 | This is a convenience wrapper that just calls the platform-specific shell 48 | command - usually this is either umount or fusermount -u. -------------------------------------------------------------------------------- /docs/usage/undelete.rst: -------------------------------------------------------------------------------- 1 | .. include:: undelete.rst.inc 2 | -------------------------------------------------------------------------------- /docs/usage/usage_general.rst.inc: -------------------------------------------------------------------------------- 1 | .. include:: general/positional-arguments.rst.inc 2 | 3 | .. include:: general/repository-urls.rst.inc 4 | 5 | .. include:: general/repository-locations.rst.inc 6 | 7 | .. include:: general/logging.rst.inc 8 | 9 | .. include:: general/return-codes.rst.inc 10 | 11 | .. _env_vars: 12 | 13 | .. include:: general/environment.rst.inc 14 | 15 | .. _file-systems: 16 | 17 | .. include:: general/file-systems.rst.inc 18 | 19 | .. include:: general/units.rst.inc 20 | 21 | .. include:: general/date-time.rst.inc 22 | 23 | .. include:: general/resources.rst.inc 24 | 25 | .. _platforms: 26 | 27 | .. include:: general/file-metadata.rst.inc 28 | -------------------------------------------------------------------------------- /docs/usage/version.rst: -------------------------------------------------------------------------------- 1 | .. include:: version.rst.inc 2 | -------------------------------------------------------------------------------- /docs/usage/version.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_version: 4 | 5 | borg version 6 | ------------ 7 | .. code-block:: none 8 | 9 | borg [common options] version [options] 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+ 16 | | .. class:: borg-common-opt-ref | 17 | | | 18 | | :ref:`common_options` | 19 | +-------------------------------------------------------+ 20 | 21 | .. raw:: html 22 | 23 | 28 | 29 | .. only:: latex 30 | 31 | 32 | 33 | :ref:`common_options` 34 | | 35 | 36 | Description 37 | ~~~~~~~~~~~ 38 | 39 | This command displays the borg client version / borg server version. 40 | 41 | If a local repo is given, the client code directly accesses the repository, 42 | thus we show the client version also as the server version. 43 | 44 | If a remote repo is given (e.g. ssh:), the remote borg is queried and 45 | its version is displayed as the server version. 46 | 47 | Examples:: 48 | 49 | # local repo (client uses 1.4.0 alpha version) 50 | $ borg version /mnt/backup 51 | 1.4.0a / 1.4.0a 52 | 53 | # remote repo (client uses 1.4.0 alpha, server uses 1.2.7 release) 54 | $ borg version ssh://borg@borgbackup:repo 55 | 1.4.0a / 1.2.7 56 | 57 | Due to the version tuple format used in borg client/server negotiation, only 58 | a simplified version is displayed (as provided by borg.version.format_version). 59 | 60 | There is also borg --version to display a potentially more precise client version. -------------------------------------------------------------------------------- /docs/usage/with-lock.rst.inc: -------------------------------------------------------------------------------- 1 | .. IMPORTANT: this file is auto-generated from borg's built-in help, do not edit! 2 | 3 | .. _borg_with-lock: 4 | 5 | borg with-lock 6 | -------------- 7 | .. code-block:: none 8 | 9 | borg [common options] with-lock [options] COMMAND [ARGS...] 10 | 11 | .. only:: html 12 | 13 | .. class:: borg-options-table 14 | 15 | +-------------------------------------------------------+-------------+-------------------+ 16 | | **positional arguments** | 17 | +-------------------------------------------------------+-------------+-------------------+ 18 | | | ``COMMAND`` | command to run | 19 | +-------------------------------------------------------+-------------+-------------------+ 20 | | | ``ARGS`` | command arguments | 21 | +-------------------------------------------------------+-------------+-------------------+ 22 | | .. class:: borg-common-opt-ref | 23 | | | 24 | | :ref:`common_options` | 25 | +-------------------------------------------------------+-------------+-------------------+ 26 | 27 | .. raw:: html 28 | 29 | 34 | 35 | .. only:: latex 36 | 37 | COMMAND 38 | command to run 39 | ARGS 40 | command arguments 41 | 42 | 43 | :ref:`common_options` 44 | | 45 | 46 | Description 47 | ~~~~~~~~~~~ 48 | 49 | This command runs a user-specified command while locking the repository. For example: 50 | 51 | :: 52 | 53 | $ BORG_REPO=/mnt/borgrepo borg with-lock rsync -av /mnt/borgrepo /somewhere/else/borgrepo 54 | 55 | It will first try to acquire the lock (make sure that no other operation is 56 | running in the repo), then execute the given command as a subprocess and wait 57 | for its termination, release the lock and return the user command's return 58 | code as borg's return code. 59 | 60 | .. note:: 61 | 62 | If you copy a repository with the lock held, the lock will be present in 63 | the copy. Thus, before using borg on the copy from a different host, 64 | you need to use "borg break-lock" on the copied repository, because 65 | Borg is cautious and does not automatically remove stale locks made by a different host. -------------------------------------------------------------------------------- /docs/usage_general.rst.inc: -------------------------------------------------------------------------------- 1 | .. include:: usage/general/positional-arguments.rst.inc 2 | 3 | .. include:: usage/general/repository-urls.rst.inc 4 | 5 | .. include:: usage/general/repository-locations.rst.inc 6 | 7 | .. include:: usage/general/logging.rst.inc 8 | 9 | .. include:: usage/general/return-codes.rst.inc 10 | 11 | .. include:: usage/general/environment.rst.inc 12 | 13 | .. include:: usage/general/file-systems.rst.inc 14 | 15 | .. include:: usage/general/units.rst.inc 16 | 17 | .. include:: usage/general/date-time.rst.inc 18 | 19 | .. include:: usage/general/resources.rst.inc 20 | 21 | .. include:: usage/general/file-metadata.rst.inc 22 | -------------------------------------------------------------------------------- /requirements.d/codestyle.txt: -------------------------------------------------------------------------------- 1 | black >=24.0, <25 2 | -------------------------------------------------------------------------------- /requirements.d/development.lock.txt: -------------------------------------------------------------------------------- 1 | setuptools==78.1.1 2 | setuptools-scm==8.2.0 3 | pip==25.0.1 4 | wheel==0.45.1 5 | virtualenv==20.30.0 6 | build==1.2.2 7 | pkgconfig==1.5.5 8 | tox==4.24.2 9 | pytest==8.3.5 10 | pytest-xdist==3.6.1 11 | pytest-cov==6.0.0 12 | pytest-benchmark==5.1.0 13 | Cython==3.0.12 14 | pre-commit==4.2.0 15 | -------------------------------------------------------------------------------- /requirements.d/development.txt: -------------------------------------------------------------------------------- 1 | setuptools >=77.0.0 2 | setuptools_scm 3 | pip !=24.2 4 | wheel 5 | virtualenv 6 | build 7 | pkgconfig 8 | tox 9 | pytest 10 | pytest-xdist 11 | pytest-cov 12 | pytest-benchmark 13 | Cython 14 | pre-commit 15 | -------------------------------------------------------------------------------- /requirements.d/docs.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinxcontrib-jquery 3 | guzzle_sphinx_theme 4 | -------------------------------------------------------------------------------- /scripts/borg.exe.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | # this pyinstaller spec file is used to build borg binaries on posix platforms and Windows 3 | 4 | import os, sys 5 | 6 | is_win32 = sys.platform.startswith('win32') 7 | 8 | # Note: SPEC contains the spec file argument given to pyinstaller 9 | here = os.path.dirname(os.path.abspath(SPEC)) 10 | basepath = os.path.abspath(os.path.join(here, '..')) 11 | 12 | if is_win32: 13 | hiddenimports = ['borghash'] 14 | else: 15 | hiddenimports = ['borg.platform.posix', 'pkg_resources.py2_warn', 'borghash'] 16 | 17 | block_cipher = None 18 | 19 | a = Analysis([os.path.join(basepath, 'src', 'borg', '__main__.py'), ], 20 | pathex=[basepath, ], 21 | binaries=[], 22 | datas=[ 23 | (os.path.join(basepath, 'src', 'borg', 'paperkey.html'), 'borg'), 24 | ], 25 | hiddenimports=hiddenimports, 26 | hookspath=[], 27 | runtime_hooks=[], 28 | excludes=[ 29 | '_ssl', 'ssl', 30 | ], 31 | win_no_prefer_redirects=False, 32 | win_private_assemblies=False, 33 | cipher=block_cipher) 34 | 35 | if sys.platform == 'darwin': 36 | # do not bundle the osxfuse libraries, so we do not get a version 37 | # mismatch to the installed kernel driver of osxfuse. 38 | a.binaries = [b for b in a.binaries if 'libosxfuse' not in b[0]] 39 | 40 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 41 | 42 | exe = EXE(pyz, 43 | a.scripts, 44 | a.binaries, 45 | a.zipfiles, 46 | a.datas, 47 | name='borg.exe', 48 | debug=False, 49 | strip=False, 50 | upx=True, 51 | console=True, 52 | icon='NONE') 53 | 54 | # Build a directory-based binary in addition to a packed 55 | # single file. This allows one to look at all included 56 | # files easily (e.g. without having to strace or halt the built binary 57 | # and introspect /tmp). Also avoids unpacking all libs when 58 | # running the app, which is better for app signing on various OS. 59 | slim_exe = EXE(pyz, 60 | a.scripts, 61 | exclude_binaries=True, 62 | name='borg.exe', 63 | debug=False, 64 | strip=False, 65 | upx=False, 66 | console=True) 67 | 68 | coll = COLLECT(slim_exe, 69 | a.binaries, 70 | a.zipfiles, 71 | a.datas, 72 | strip=False, 73 | upx=False, 74 | name='borg-dir') 75 | -------------------------------------------------------------------------------- /scripts/errorlist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # this script automatically generates the error list for the docs by 3 | # looking at the "Error" class and its subclasses. 4 | 5 | from textwrap import indent 6 | 7 | import borg.archiver # noqa: F401 - need import to get Error subclasses. 8 | from borg.constants import * # NOQA 9 | from borg.helpers import Error, BackupError, BorgWarning 10 | 11 | 12 | def subclasses(cls): 13 | direct_subclasses = cls.__subclasses__() 14 | return set(direct_subclasses) | {s for c in direct_subclasses for s in subclasses(c)} 15 | 16 | 17 | # 0, 1, 2 are used for success, generic warning, generic error 18 | # 3..99 are available for specific errors 19 | # 100..127 are available for specific warnings 20 | # 128+ are reserved for signals 21 | free_error_rcs = set(range(EXIT_ERROR_BASE, EXIT_WARNING_BASE)) # 3 .. 99 22 | free_warning_rcs = set(range(EXIT_WARNING_BASE, EXIT_SIGNAL_BASE)) # 100 .. 127 23 | 24 | # these classes map to rc 2 25 | generic_error_rc_classes = set() 26 | generic_warning_rc_classes = set() 27 | 28 | error_classes = {Error} | subclasses(Error) 29 | 30 | for cls in sorted(error_classes, key=lambda cls: (cls.__module__, cls.__qualname__)): 31 | traceback = "yes" if cls.traceback else "no" 32 | rc = cls.exit_mcode 33 | print(" ", cls.__qualname__, "rc:", rc, "traceback:", traceback) 34 | print(indent(cls.__doc__, " " * 8)) 35 | if rc in free_error_rcs: 36 | free_error_rcs.remove(rc) 37 | elif rc == 2: 38 | generic_error_rc_classes.add(cls.__qualname__) 39 | else: # rc != 2 40 | # if we did not intentionally map this to the generic error rc, this might be an issue: 41 | print(f"ERROR: {rc} is not a free/available RC, but either duplicate or invalid") 42 | 43 | print() 44 | print("free error RCs:", sorted(free_error_rcs)) 45 | print("generic errors:", sorted(generic_error_rc_classes)) 46 | 47 | warning_classes = {BorgWarning} | subclasses(BorgWarning) | {BackupError} | subclasses(BackupError) 48 | 49 | for cls in sorted(warning_classes, key=lambda cls: (cls.__module__, cls.__qualname__)): 50 | rc = cls.exit_mcode 51 | print(" ", cls.__qualname__, "rc:", rc) 52 | print(indent(cls.__doc__, " " * 8)) 53 | if rc in free_warning_rcs: 54 | free_warning_rcs.remove(rc) 55 | elif rc == 1: 56 | generic_warning_rc_classes.add(cls.__qualname__) 57 | else: # rc != 1 58 | # if we did not intentionally map this to the generic warning rc, this might be an issue: 59 | print(f"ERROR: {rc} is not a free/available RC, but either duplicate or invalid") 60 | 61 | print("\n") 62 | print("free warning RCs:", sorted(free_warning_rcs)) 63 | print("generic warnings:", sorted(generic_warning_rc_classes)) 64 | -------------------------------------------------------------------------------- /scripts/fetch-binaries: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p dist/ 4 | 5 | check_and_copy () { 6 | echo "--- EXE $2 -----------------------------------------------" 7 | vagrant ssh $1 -c "/vagrant/borg/borg.exe -V" 8 | vagrant scp $1:/vagrant/borg/borg.exe dist/$2 9 | echo "--- DIR $2 -----------------------------------------------" 10 | vagrant ssh $1 -c "/vagrant/borg/borg-dir/borg.exe -V" 11 | vagrant scp $1:/vagrant/borg/borg.tgz dist/$2.tgz 12 | echo "" 13 | } 14 | 15 | check_and_copy buster borg-linux-glibc228 16 | check_and_copy bullseye borg-linux-glibc231 17 | check_and_copy bookworm borg-linux-glibc236 18 | 19 | check_and_copy freebsd13 borg-freebsd13 20 | check_and_copy freebsd14 borg-freebsd14 21 | 22 | check_and_copy macos1012 borg-macos1012 23 | -------------------------------------------------------------------------------- /scripts/glibc_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Check if all given binaries work with the given glibc version. 4 | 5 | glibc_check.py 2.11 bin [bin ...] 6 | 7 | rc = 0 means "yes", rc = 1 means "no". 8 | """ 9 | 10 | import re 11 | import subprocess 12 | import sys 13 | 14 | verbose = True 15 | glibc_re = re.compile(r"GLIBC_([0-9]\.[0-9]+)") 16 | 17 | 18 | def parse_version(v): 19 | major, minor = v.split(".") 20 | return int(major), int(minor) 21 | 22 | 23 | def format_version(version): 24 | return "%d.%d" % version 25 | 26 | 27 | def main(): 28 | given = parse_version(sys.argv[1]) 29 | filenames = sys.argv[2:] 30 | 31 | overall_versions = set() 32 | for filename in filenames: 33 | try: 34 | output = subprocess.check_output(["objdump", "-T", filename], stderr=subprocess.STDOUT) 35 | output = output.decode() 36 | versions = {parse_version(match.group(1)) for match in glibc_re.finditer(output)} 37 | requires_glibc = max(versions) 38 | overall_versions.add(requires_glibc) 39 | if verbose: 40 | print(f"{filename} {format_version(requires_glibc)}") 41 | except subprocess.CalledProcessError: 42 | if verbose: 43 | print("%s errored." % filename) 44 | 45 | wanted = max(overall_versions) 46 | ok = given >= wanted 47 | 48 | if verbose: 49 | if ok: 50 | print("The binaries work with the given glibc %s." % format_version(given)) 51 | else: 52 | print( 53 | "The binaries do not work with the given glibc %s. " 54 | "Minimum is: %s" % (format_version(given), format_version(wanted)) 55 | ) 56 | return ok 57 | 58 | 59 | if __name__ == "__main__": 60 | ok = main() 61 | sys.exit(0 if ok else 1) 62 | -------------------------------------------------------------------------------- /scripts/make-testdata/test_transfer_upgrade.sh: -------------------------------------------------------------------------------- 1 | # this scripts uses borg 1.2 to generate test data for "borg transfer --upgrader=From12To20" 2 | BORG=./borg-1.2.2 3 | # on macOS, gnu tar is available as gtar 4 | TAR=gtar 5 | SRC=/tmp/borgtest 6 | ARCHIVE=`pwd`/src/borg/testsuite/archiver/repo12.tar.gz 7 | 8 | export BORG_REPO=/tmp/repo12 9 | META=$BORG_REPO/test_meta 10 | export BORG_PASSPHRASE="waytooeasyonlyfortests" 11 | export BORG_DELETE_I_KNOW_WHAT_I_AM_DOING=YES 12 | 13 | $BORG init -e repokey 2> /dev/null 14 | mkdir $META 15 | 16 | # archive1 17 | mkdir $SRC 18 | 19 | pushd $SRC >/dev/null 20 | 21 | mkdir directory 22 | 23 | echo "content" > directory/no_hardlink 24 | 25 | echo "hardlink content" > hardlink1 26 | ln hardlink1 hardlink2 27 | 28 | echo "symlinked content" > target 29 | ln -s target symlink 30 | 31 | ln -s doesnotexist broken_symlink 32 | 33 | mkfifo fifo 34 | 35 | touch without_xattrs 36 | touch with_xattrs 37 | xattr -w key1 value with_xattrs 38 | xattr -w key2 "" with_xattrs 39 | 40 | touch without_flags 41 | touch with_flags 42 | chflags nodump with_flags 43 | 44 | popd >/dev/null 45 | 46 | $BORG create ::archive1 $SRC 47 | $BORG list ::archive1 --json-lines > $META/archive1_list.json 48 | rm -rf $SRC 49 | 50 | # archive2 51 | mkdir $SRC 52 | 53 | pushd $SRC >/dev/null 54 | 55 | sudo mkdir root_stuff 56 | sudo mknod root_stuff/bdev_12_34 b 12 34 57 | sudo mknod root_stuff/cdev_34_56 c 34 56 58 | sudo touch root_stuff/strange_uid_gid # no user name, no group name for this uid/gid! 59 | sudo chown 54321:54321 root_stuff/strange_uid_gid 60 | 61 | popd >/dev/null 62 | 63 | $BORG create ::archive2 $SRC 64 | $BORG list ::archive2 --json-lines > $META/archive2_list.json 65 | sudo rm -rf $SRC/root_stuff 66 | rm -rf $SRC 67 | 68 | 69 | $BORG --version > $META/borg_version.txt 70 | $BORG list :: --json > $META/repo_list.json 71 | 72 | pushd $BORG_REPO >/dev/null 73 | $TAR czf $ARCHIVE . 74 | popd >/dev/null 75 | 76 | $BORG delete :: 2> /dev/null 77 | -------------------------------------------------------------------------------- /scripts/msys2-install-deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,zstd,lz4,xxhash,openssl,rclone,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko} 4 | 5 | if [ "$1" = "development" ]; then 6 | pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-xdist} 7 | fi 8 | -------------------------------------------------------------------------------- /scripts/sdist-sign: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | R=$1 4 | 5 | if [ "$R" = "" ]; then 6 | echo "Usage: sdist-sign 1.2.3" 7 | exit 8 | fi 9 | 10 | if [ "$QUBES_GPG_DOMAIN" = "" ]; then 11 | GPG=gpg 12 | else 13 | GPG=qubes-gpg-client-wrapper 14 | fi 15 | 16 | python -m build 17 | 18 | D=dist/borgbackup-$R.tar.gz 19 | 20 | $GPG --detach-sign --local-user "Thomas Waldmann" --armor --output "$D.asc" "$D" 21 | -------------------------------------------------------------------------------- /scripts/sign-binaries: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | D=$1 4 | 5 | if [ "$D" = "" ]; then 6 | echo "Usage: sign-binaries 201912312359" 7 | exit 8 | fi 9 | 10 | if [ "$QUBES_GPG_DOMAIN" = "" ]; then 11 | GPG=gpg 12 | else 13 | GPG=qubes-gpg-client-wrapper 14 | fi 15 | 16 | for file in dist/borg-*; do 17 | $GPG --local-user "Thomas Waldmann" --armor --detach-sign --output "$file.asc" "$file" 18 | done 19 | 20 | touch -t "$D" dist/* 21 | -------------------------------------------------------------------------------- /scripts/upload-pypi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | R=$1 4 | 5 | if [ "$R" = "" ]; then 6 | echo "Usage: upload-pypi 1.2.3 [test]" 7 | exit 8 | fi 9 | 10 | if [ "$2" = "test" ]; then 11 | export TWINE_REPOSITORY=borgbackuptest 12 | else 13 | export TWINE_REPOSITORY=borgbackup 14 | fi 15 | 16 | D=dist/borgbackup-$R.tar.gz 17 | 18 | twine upload $D 19 | -------------------------------------------------------------------------------- /src/borg/__init__.py: -------------------------------------------------------------------------------- 1 | from packaging.version import parse as parse_version 2 | 3 | from ._version import version as __version__ 4 | 5 | 6 | _v = parse_version(__version__) 7 | __version_tuple__ = _v._version.release # type: ignore 8 | 9 | # assert that all semver components are integers 10 | # this is mainly to show errors when people repackage poorly 11 | # and setuptools_scm determines a 0.1.dev... version 12 | assert all(isinstance(v, int) for v in __version_tuple__), ( 13 | """\ 14 | broken borgbackup version metadata: %r 15 | 16 | version metadata is obtained dynamically on installation via setuptools_scm, 17 | please ensure your git repo has the correct tags or you provide the version 18 | using SETUPTOOLS_SCM_PRETEND_VERSION in your build script. 19 | """ 20 | % __version__ 21 | ) 22 | -------------------------------------------------------------------------------- /src/borg/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | # On windows loading the bundled libcrypto dll fails if the folder 5 | # containing the dll is not in the search path. The dll is shipped 6 | # with python in the "DLLs" folder, so let's add this folder 7 | # to the path. The folder is always in sys.path, get it from there. 8 | if sys.platform.startswith("win32"): 9 | # Keep it an iterable to support multiple folder which contain "DLLs". 10 | dll_path = (p for p in sys.path if "DLLs" in os.path.normpath(p).split(os.path.sep)) 11 | os.environ["PATH"] = os.pathsep.join(dll_path) + os.pathsep + os.environ["PATH"] 12 | 13 | 14 | # note: absolute import from "borg", it seems pyinstaller binaries do not work without this. 15 | from borg.archiver import main 16 | 17 | main() 18 | -------------------------------------------------------------------------------- /src/borg/_item.c: -------------------------------------------------------------------------------- 1 | #include "Python.h" 2 | 3 | /* 4 | * This is not quite as dark magic as it looks. We just convert the address of (pointer to) 5 | * a PyObject into a bytes object in _wrap_object, and convert these bytes back to the 6 | * pointer to the original object. 7 | * 8 | * This mainly looks a bit confusing due to our mental special-casing of "char*" from other 9 | * pointers. 10 | * 11 | * The big upside to this is that this neither does *any* serialization (beyond creating tiny 12 | * bytes objects as "stand-ins"), nor has to copy the entire object that's passed around. 13 | */ 14 | 15 | static PyObject * 16 | _object_to_optr(PyObject *obj) 17 | { 18 | /* 19 | * Create a temporary reference to the object being passed around so it does not vanish. 20 | * Note that we never decref this one in _unwrap_object, since we just transfer that reference 21 | * there, i.e. there is an elided "Py_INCREF(x); Py_DECREF(x)". 22 | * Since the reference is transferred, calls to _wrap_object and _unwrap_object must be symmetric. 23 | */ 24 | Py_INCREF(obj); 25 | return PyBytes_FromStringAndSize((const char*) &obj, sizeof(void*)); 26 | } 27 | 28 | static PyObject * 29 | _optr_to_object(PyObject *bytes) 30 | { 31 | if(!PyBytes_Check(bytes)) { 32 | PyErr_SetString(PyExc_TypeError, "Cannot unwrap non-bytes object"); 33 | return NULL; 34 | } 35 | if(PyBytes_Size(bytes) != sizeof(void*)) { 36 | PyErr_SetString(PyExc_TypeError, "Invalid length of bytes object"); 37 | return NULL; 38 | } 39 | PyObject *object = * (PyObject **) PyBytes_AsString(bytes); 40 | return object; 41 | } 42 | -------------------------------------------------------------------------------- /src/borg/archiver/rename_cmd.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from ._common import with_repository, with_archive 4 | from ..constants import * # NOQA 5 | from ..helpers import archivename_validator 6 | from ..manifest import Manifest 7 | 8 | from ..logger import create_logger 9 | 10 | logger = create_logger() 11 | 12 | 13 | class RenameMixIn: 14 | @with_repository(cache=True, compatibility=(Manifest.Operation.CHECK,)) 15 | @with_archive 16 | def do_rename(self, args, repository, manifest, cache, archive): 17 | """Rename an existing archive""" 18 | archive.rename(args.newname) 19 | manifest.write() 20 | 21 | def build_parser_rename(self, subparsers, common_parser, mid_common_parser): 22 | from ._common import process_epilog 23 | 24 | rename_epilog = process_epilog( 25 | """ 26 | This command renames an archive in the repository. 27 | 28 | This results in a different archive ID. 29 | """ 30 | ) 31 | subparser = subparsers.add_parser( 32 | "rename", 33 | parents=[common_parser], 34 | add_help=False, 35 | description=self.do_rename.__doc__, 36 | epilog=rename_epilog, 37 | formatter_class=argparse.RawDescriptionHelpFormatter, 38 | help="rename archive", 39 | ) 40 | subparser.set_defaults(func=self.do_rename) 41 | subparser.add_argument("name", metavar="OLDNAME", type=archivename_validator, help="specify the archive name") 42 | subparser.add_argument( 43 | "newname", metavar="NEWNAME", type=archivename_validator, help="specify the new archive name" 44 | ) 45 | -------------------------------------------------------------------------------- /src/borg/archiver/version_cmd.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from .. import __version__ 4 | from ..constants import * # NOQA 5 | from ..remote import RemoteRepository 6 | 7 | from ..logger import create_logger 8 | 9 | logger = create_logger() 10 | 11 | 12 | class VersionMixIn: 13 | def do_version(self, args): 14 | """Display the borg client / borg server version""" 15 | from borg.version import parse_version, format_version 16 | 17 | client_version = parse_version(__version__) 18 | if args.location.proto in ("ssh", "socket"): 19 | with RemoteRepository(args.location, lock=False, args=args) as repository: 20 | server_version = repository.server_version 21 | else: 22 | server_version = client_version 23 | print(f"{format_version(client_version)} / {format_version(server_version)}") 24 | 25 | def build_parser_version(self, subparsers, common_parser, mid_common_parser): 26 | from ._common import process_epilog 27 | 28 | version_epilog = process_epilog( 29 | """ 30 | This command displays the borg client version / borg server version. 31 | 32 | If a local repo is given, the client code directly accesses the repository, 33 | thus we show the client version also as the server version. 34 | 35 | If a remote repo is given (e.g. ssh:), the remote borg is queried and 36 | its version is displayed as the server version. 37 | 38 | Examples:: 39 | 40 | # local repo (client uses 1.4.0 alpha version) 41 | $ borg version /mnt/backup 42 | 1.4.0a / 1.4.0a 43 | 44 | # remote repo (client uses 1.4.0 alpha, server uses 1.2.7 release) 45 | $ borg version ssh://borg@borgbackup:repo 46 | 1.4.0a / 1.2.7 47 | 48 | Due to the version tuple format used in borg client/server negotiation, only 49 | a simplified version is displayed (as provided by borg.version.format_version). 50 | 51 | There is also borg --version to display a potentially more precise client version. 52 | """ 53 | ) 54 | subparser = subparsers.add_parser( 55 | "version", 56 | parents=[common_parser], 57 | add_help=False, 58 | description=self.do_version.__doc__, 59 | epilog=version_epilog, 60 | formatter_class=argparse.RawDescriptionHelpFormatter, 61 | help="display borg client version / borg server version", 62 | ) 63 | subparser.set_defaults(func=self.do_version) 64 | -------------------------------------------------------------------------------- /src/borg/checksums.pyi: -------------------------------------------------------------------------------- 1 | def crc32(data: bytes, value: int = 0) -> int: ... 2 | def xxh64(data: bytes, seed: int = 0) -> bytes: ... 3 | 4 | class StreamingXXH64: 5 | def __init__(self, seed: int = 0) -> None: ... 6 | def update(self, data: bytes) -> None: ... 7 | def digest(self) -> bytes: ... 8 | def hexdigest(self) -> str: ... 9 | -------------------------------------------------------------------------------- /src/borg/chunker.pyi: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, Tuple, List, Dict, Any, Type, Iterator, BinaryIO 2 | 3 | API_VERSION: str 4 | 5 | has_seek_hole: bool 6 | 7 | class _Chunk(NamedTuple): 8 | data: bytes 9 | meta: Dict[str, Any] 10 | 11 | def Chunk(data: bytes, **meta) -> Type[_Chunk]: ... 12 | def buzhash(data: bytes, seed: int) -> int: ... 13 | def buzhash_update(sum: int, remove: int, add: int, len: int, seed: int) -> int: ... 14 | def get_chunker(algo: str, *params, **kw) -> Any: ... 15 | 16 | fmap_entry = Tuple[int, int, bool] 17 | 18 | def sparsemap(fd: BinaryIO = None, fh: int = -1) -> List[fmap_entry]: ... 19 | 20 | class ChunkerFailing: 21 | def __init__(self, block_size: int, map: str) -> None: ... 22 | def chunkify(self, fd: BinaryIO = None, fh: int = -1) -> Iterator: ... 23 | 24 | class ChunkerFixed: 25 | def __init__(self, block_size: int, header_size: int = 0, sparse: bool = False) -> None: ... 26 | def chunkify(self, fd: BinaryIO = None, fh: int = -1, fmap: List[fmap_entry] = None) -> Iterator: ... 27 | 28 | class Chunker: 29 | def __init__( 30 | self, seed: int, chunk_min_exp: int, chunk_max_exp: int, hash_mask_bits: int, hash_window_size: int 31 | ) -> None: ... 32 | def chunkify(self, fd: BinaryIO = None, fh: int = -1) -> Iterator: ... 33 | -------------------------------------------------------------------------------- /src/borg/compress.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Type, Dict, Tuple 2 | 3 | API_VERSION: str 4 | 5 | def get_compressor(name: str, **kwargs) -> Any: ... 6 | 7 | class CompressionSpec: 8 | def __init__(self, spec: str) -> None: ... 9 | @property 10 | def compressor(self) -> Any: ... 11 | inner: CompressionSpec 12 | 13 | class Compressor: 14 | def __init__(self, name: Any = ..., **kwargs) -> None: ... 15 | def compress(self, meta: Dict, data: bytes) -> Tuple[Dict, bytes]: ... 16 | def decompress(self, meta: Dict, data: bytes) -> Tuple[Dict, bytes]: ... 17 | @staticmethod 18 | def detect(data: bytes) -> Any: ... 19 | 20 | class CompressorBase: 21 | ID: bytes = ... 22 | name: str = ... 23 | @classmethod 24 | def detect(self, data: bytes) -> bool: ... 25 | def __init__(self, level: int = ..., **kwargs) -> None: ... 26 | def decide(self, data: bytes) -> Any: ... 27 | def compress(self, data: bytes) -> bytes: ... 28 | def decompress(self, data: bytes) -> bytes: ... 29 | 30 | class Auto(CompressorBase): 31 | def __init__(self, compressor: Any) -> None: ... 32 | 33 | class DecidingCompressor(CompressorBase): 34 | def __init__(self, level: int = ..., **kwargs) -> None: ... 35 | def decide_compress(self, data: bytes) -> Any: ... 36 | 37 | class CNONE(CompressorBase): 38 | def __init__(self, level: int = ..., **kwargs) -> None: ... 39 | 40 | class ObfuscateSize(CompressorBase): 41 | def __init__(self, level: int = ..., compressor: Any = ...) -> None: ... 42 | 43 | class ZLIB_legacy(CompressorBase): 44 | def __init__(self, level: int = ..., **kwargs) -> None: ... 45 | level: int 46 | 47 | class ZLIB(CompressorBase): 48 | def __init__(self, level: int = ..., **kwargs) -> None: ... 49 | level: int 50 | 51 | class LZ4(DecidingCompressor): 52 | def __init__(self, level: int = ..., **kwargs) -> None: ... 53 | 54 | class LZMA(DecidingCompressor): 55 | def __init__(self, level: int = ..., **kwargs) -> None: ... 56 | level: int 57 | 58 | class ZSTD(DecidingCompressor): 59 | def __init__(self, level: int = ..., **kwargs) -> None: ... 60 | level: int 61 | 62 | LZ4_COMPRESSOR: Type[LZ4] 63 | NONE_COMPRESSOR: Type[CNONE] 64 | 65 | COMPRESSOR_TABLE: dict 66 | -------------------------------------------------------------------------------- /src/borg/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/src/borg/crypto/__init__.py -------------------------------------------------------------------------------- /src/borg/fuse_impl.py: -------------------------------------------------------------------------------- 1 | """ 2 | load library for lowlevel FUSE implementation 3 | """ 4 | 5 | import os 6 | 7 | BORG_FUSE_IMPL = os.environ.get("BORG_FUSE_IMPL", "pyfuse3,llfuse") 8 | 9 | for FUSE_IMPL in BORG_FUSE_IMPL.split(","): 10 | FUSE_IMPL = FUSE_IMPL.strip() 11 | if FUSE_IMPL == "pyfuse3": 12 | try: 13 | import pyfuse3 as llfuse 14 | except ImportError: 15 | pass 16 | else: 17 | has_llfuse = False 18 | has_pyfuse3 = True 19 | break 20 | elif FUSE_IMPL == "llfuse": 21 | try: 22 | import llfuse 23 | except ImportError: 24 | pass 25 | else: 26 | has_llfuse = True 27 | has_pyfuse3 = False 28 | break 29 | elif FUSE_IMPL == "none": 30 | pass 31 | else: 32 | raise RuntimeError("unknown fuse implementation in BORG_FUSE_IMPL: '%s'" % BORG_FUSE_IMPL) 33 | else: 34 | llfuse = None # noqa 35 | has_llfuse = False 36 | has_pyfuse3 = False 37 | -------------------------------------------------------------------------------- /src/borg/hashindex.pyi: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, Tuple, Type, Union, IO, Iterator, Any 2 | 3 | API_VERSION: str 4 | 5 | PATH_OR_FILE = Union[str, IO] 6 | 7 | class ChunkIndexEntry(NamedTuple): 8 | flags: int 9 | size: int 10 | 11 | CIE = Union[Tuple[int, int], Type[ChunkIndexEntry]] 12 | 13 | class ChunkIndex: 14 | F_NONE: int 15 | F_USED: int 16 | F_COMPRESS: int 17 | F_NEW: int 18 | M_USER: int 19 | M_SYSTEM: int 20 | def add(self, key: bytes, size: int) -> None: ... 21 | def iteritems(self, *, only_new: bool = ...) -> Iterator: ... 22 | def clear_new(self) -> None: ... 23 | def __contains__(self, key: bytes) -> bool: ... 24 | def __getitem__(self, key: bytes) -> Type[ChunkIndexEntry]: ... 25 | def __setitem__(self, key: bytes, value: CIE) -> None: ... 26 | 27 | class NSIndex1Entry(NamedTuple): 28 | segment: int 29 | offset: int 30 | 31 | class NSIndex1: # legacy 32 | def iteritems(self, *args, **kwargs) -> Iterator: ... 33 | def __contains__(self, key: bytes) -> bool: ... 34 | def __getitem__(self, key: bytes) -> Any: ... 35 | def __setitem__(self, key: bytes, value: Any) -> None: ... 36 | 37 | class FuseVersionsIndexEntry(NamedTuple): 38 | version: int 39 | hash: bytes 40 | 41 | class FuseVersionsIndex: 42 | def __contains__(self, key: bytes) -> bool: ... 43 | def __getitem__(self, key: bytes) -> Any: ... 44 | def __setitem__(self, key: bytes, value: Any) -> None: ... 45 | -------------------------------------------------------------------------------- /src/borg/helpers/checks.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .errors import RTError 4 | from ..platformflags import is_win32 5 | 6 | 7 | def check_python(): 8 | if is_win32: 9 | required_funcs = {os.stat} 10 | else: 11 | required_funcs = {os.stat, os.utime, os.chown} 12 | if not os.supports_follow_symlinks.issuperset(required_funcs): 13 | raise RTError("""FATAL: this Python was compiled for a too old (g)libc and misses required functionality.""") 14 | 15 | 16 | def check_extension_modules(): 17 | from .. import platform, compress, crypto, item, chunker, hashindex 18 | 19 | msg = """The Borg binary extension modules do not seem to be properly installed.""" 20 | if hashindex.API_VERSION != "1.2_01": 21 | raise RTError(msg) 22 | if chunker.API_VERSION != "1.2_01": 23 | raise RTError(msg) 24 | if compress.API_VERSION != "1.2_02": 25 | raise RTError(msg) 26 | if crypto.low_level.API_VERSION != "1.3_01": 27 | raise RTError(msg) 28 | if item.API_VERSION != "1.2_01": 29 | raise RTError(msg) 30 | if platform.API_VERSION != platform.OS_API_VERSION or platform.API_VERSION != "1.2_05": 31 | raise RTError(msg) 32 | -------------------------------------------------------------------------------- /src/borg/helpers/lrucache.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from collections.abc import Callable, ItemsView, Iterator, KeysView, MutableMapping, ValuesView 3 | from typing import TypeVar 4 | 5 | K = TypeVar("K") 6 | V = TypeVar("V") 7 | 8 | 9 | class LRUCache(MutableMapping[K, V]): 10 | """ 11 | Mapping which maintains a maximum size by removing the least recently used value. 12 | Items are passed to dispose before being removed and setting an item which is 13 | already in the cache has to be done using the replace method. 14 | """ 15 | 16 | _cache: OrderedDict[K, V] 17 | 18 | _capacity: int 19 | 20 | _dispose: Callable[[V], None] 21 | 22 | def __init__(self, capacity: int, dispose: Callable[[V], None] = lambda _: None): 23 | self._cache = OrderedDict() 24 | self._capacity = capacity 25 | self._dispose = dispose 26 | 27 | def __setitem__(self, key: K, value: V) -> None: 28 | assert key not in self._cache, ( 29 | "Unexpected attempt to replace a cached item," " without first deleting the old item." 30 | ) 31 | while len(self._cache) >= self._capacity: 32 | self._dispose(self._cache.popitem(last=False)[1]) 33 | self._cache[key] = value 34 | self._cache.move_to_end(key) 35 | 36 | def __getitem__(self, key: K) -> V: 37 | self._cache.move_to_end(key) # raise KeyError if not found 38 | return self._cache[key] 39 | 40 | def __delitem__(self, key: K) -> None: 41 | self._dispose(self._cache.pop(key)) 42 | 43 | def __contains__(self, key: object) -> bool: 44 | return key in self._cache 45 | 46 | def __len__(self) -> int: 47 | return len(self._cache) 48 | 49 | def replace(self, key: K, value: V) -> None: 50 | """Replace an item which is already present, not disposing it in the process""" 51 | # this method complements __setitem__ which should be used for the normal use case. 52 | assert key in self._cache, "Unexpected attempt to update a non-existing item." 53 | self._cache[key] = value 54 | 55 | def clear(self) -> None: 56 | for value in self._cache.values(): 57 | self._dispose(value) 58 | self._cache.clear() 59 | 60 | def __iter__(self) -> Iterator[K]: 61 | return iter(self._cache) 62 | 63 | def keys(self) -> KeysView[K]: 64 | return self._cache.keys() 65 | 66 | def values(self) -> ValuesView[V]: 67 | return self._cache.values() 68 | 69 | def items(self) -> ItemsView[K, V]: 70 | return self._cache.items() 71 | -------------------------------------------------------------------------------- /src/borg/platform/syncfilerange.pyx: -------------------------------------------------------------------------------- 1 | from libc.stdint cimport int64_t 2 | 3 | 4 | # Some Linux systems (like Termux on Android 7 or earlier) do not have access 5 | # to sync_file_range. By isolating the access to sync_file_range in this 6 | # separate extension, it can be imported dynamically from linux.pyx only when 7 | # available and systems without support can otherwise use the rest of 8 | # linux.pyx. 9 | cdef extern from "fcntl.h": 10 | int sync_file_range(int fd, int64_t offset, int64_t nbytes, unsigned int flags) 11 | unsigned int SYNC_FILE_RANGE_WRITE 12 | unsigned int SYNC_FILE_RANGE_WAIT_BEFORE 13 | unsigned int SYNC_FILE_RANGE_WAIT_AFTER 14 | -------------------------------------------------------------------------------- /src/borg/platform/windows.pyx: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | from functools import lru_cache 4 | 5 | 6 | cdef extern from 'windows.h': 7 | ctypedef void* HANDLE 8 | ctypedef int BOOL 9 | ctypedef unsigned long DWORD 10 | 11 | BOOL CloseHandle(HANDLE hObject) 12 | HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dbProcessId) 13 | 14 | cdef extern int PROCESS_QUERY_INFORMATION 15 | 16 | 17 | @lru_cache(maxsize=None) 18 | def uid2user(uid, default=None): 19 | return "root" 20 | 21 | 22 | @lru_cache(maxsize=None) 23 | def user2uid(user, default=None): 24 | if not user: 25 | # user is either None or the empty string 26 | return default 27 | return 0 28 | 29 | 30 | @lru_cache(maxsize=None) 31 | def gid2group(gid, default=None): 32 | return "root" 33 | 34 | 35 | @lru_cache(maxsize=None) 36 | def group2gid(group, default=None): 37 | if not group: 38 | # group is either None or the empty string 39 | return default 40 | return 0 41 | 42 | 43 | def getosusername(): 44 | """Return the os user name.""" 45 | return os.getlogin() 46 | 47 | 48 | def process_alive(host, pid, thread): 49 | """ 50 | Check whether the (host, pid, thread_id) combination corresponds to a process potentially alive. 51 | """ 52 | if host.split('@')[0].lower() != platform.node().lower(): 53 | # Not running on the same node, assume running. 54 | return True 55 | 56 | # If the process can be opened, the process is alive. 57 | handle = OpenProcess(PROCESS_QUERY_INFORMATION, False, pid) 58 | if handle != NULL: 59 | CloseHandle(handle) 60 | return True 61 | return False 62 | 63 | 64 | def local_pid_alive(pid): 65 | """Return whether *pid* is alive.""" 66 | raise NotImplementedError 67 | -------------------------------------------------------------------------------- /src/borg/platformflags.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flags for Platform-specific APIs. 3 | 4 | Use these Flags instead of sys.platform.startswith('') or try/except. 5 | """ 6 | 7 | import sys 8 | 9 | is_win32 = sys.platform.startswith("win32") 10 | is_cygwin = sys.platform.startswith("cygwin") 11 | 12 | is_linux = sys.platform.startswith("linux") 13 | is_freebsd = sys.platform.startswith("freebsd") 14 | is_darwin = sys.platform.startswith("darwin") 15 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/analyze_cmd_test.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | from ...constants import * # NOQA 4 | from . import cmd, generate_archiver_tests, RK_ENCRYPTION 5 | 6 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local") # NOQA 7 | 8 | 9 | def test_analyze(archivers, request): 10 | def create_archive(): 11 | cmd(archiver, "create", "archive", archiver.input_path) 12 | 13 | def analyze_archives(): 14 | return cmd(archiver, "analyze", "-a", "archive") 15 | 16 | archiver = request.getfixturevalue(archivers) 17 | 18 | cmd(archiver, "repo-create", RK_ENCRYPTION) 19 | input_path = pathlib.Path(archiver.input_path) 20 | 21 | # 1st archive 22 | (input_path / "file1").write_text("1") 23 | create_archive() 24 | 25 | # 2nd archive 26 | (input_path / "file2").write_text("22") 27 | create_archive() 28 | 29 | assert "/input: 2" in analyze_archives() # 2nd archive added 1 chunk for input path 30 | 31 | # 3rd archive 32 | (input_path / "file3").write_text("333") 33 | create_archive() 34 | 35 | assert "/input: 5" in analyze_archives() # 2nd/3rd archives added 2 chunks for input path 36 | 37 | # 4th archive 38 | (input_path / "file2").unlink() 39 | create_archive() 40 | 41 | assert "/input: 7" in analyze_archives() # 2nd/3rd archives added 2, 4th archive removed 1 42 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/benchmark_cmd_test.py: -------------------------------------------------------------------------------- 1 | from ...constants import * # NOQA 2 | from . import cmd, RK_ENCRYPTION 3 | 4 | 5 | def test_benchmark_crud(archiver, monkeypatch): 6 | cmd(archiver, "repo-create", RK_ENCRYPTION) 7 | monkeypatch.setenv("_BORG_BENCHMARK_CRUD_TEST", "YES") 8 | cmd(archiver, "benchmark", "crud", archiver.input_path) 9 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/corruption_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from configparser import ConfigParser 4 | 5 | import pytest 6 | 7 | from ...constants import * # NOQA 8 | from ...helpers import bin_to_hex 9 | from . import cmd, create_test_files, RK_ENCRYPTION 10 | 11 | 12 | def corrupt_archiver(archiver): 13 | create_test_files(archiver.input_path) 14 | cmd(archiver, "repo-create", RK_ENCRYPTION) 15 | archiver.cache_path = json.loads(cmd(archiver, "repo-info", "--json"))["cache"].get("path") 16 | 17 | 18 | def test_old_version_interfered(archiver): 19 | corrupt_archiver(archiver) 20 | if archiver.cache_path is None: 21 | pytest.skip("no cache path for this kind of Cache implementation") 22 | 23 | # Modify the main manifest ID without touching the manifest ID in the integrity section. 24 | # This happens if a version without integrity checking modifies the cache. 25 | config_path = os.path.join(archiver.cache_path, "config") 26 | config = ConfigParser(interpolation=None) 27 | config.read(config_path) 28 | config.set("cache", "manifest", bin_to_hex(bytes(32))) 29 | with open(config_path, "w") as fd: 30 | config.write(fd) 31 | out = cmd(archiver, "repo-info") 32 | assert "Cache integrity data not available: old Borg version modified the cache." in out 33 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/delete_cmd_test.py: -------------------------------------------------------------------------------- 1 | from ...constants import * # NOQA 2 | from . import cmd, create_regular_file, generate_archiver_tests, RK_ENCRYPTION 3 | 4 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 5 | 6 | 7 | def test_delete_options(archivers, request): 8 | archiver = request.getfixturevalue(archivers) 9 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 10 | create_regular_file(archiver.input_path, "dir2/file2", size=1024 * 80) 11 | cmd(archiver, "repo-create", RK_ENCRYPTION) 12 | cmd(archiver, "create", "test", "input") 13 | cmd(archiver, "create", "test.2", "input") 14 | cmd(archiver, "create", "test.3", "input") 15 | cmd(archiver, "create", "another_test.1", "input") 16 | cmd(archiver, "create", "another_test.2", "input") 17 | cmd(archiver, "delete", "--match-archives", "sh:another_*") 18 | cmd(archiver, "delete", "--last", "1") # test.3 19 | cmd(archiver, "delete", "-a", "test") 20 | cmd(archiver, "extract", "test.2", "--dry-run") # still there? 21 | cmd(archiver, "delete", "-a", "test.2") 22 | output = cmd(archiver, "repo-list") 23 | assert output == "" # no archives left! 24 | 25 | 26 | def test_delete_multiple(archivers, request): 27 | archiver = request.getfixturevalue(archivers) 28 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 29 | cmd(archiver, "repo-create", RK_ENCRYPTION) 30 | cmd(archiver, "create", "test1", "input") 31 | cmd(archiver, "create", "test2", "input") 32 | cmd(archiver, "delete", "-a", "test1") 33 | cmd(archiver, "delete", "-a", "test2") 34 | assert not cmd(archiver, "repo-list") 35 | 36 | 37 | def test_delete_ignore_protected(archivers, request): 38 | archiver = request.getfixturevalue(archivers) 39 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 40 | cmd(archiver, "repo-create", RK_ENCRYPTION) 41 | cmd(archiver, "create", "test1", "input") 42 | cmd(archiver, "tag", "--add=@PROT", "test1") 43 | cmd(archiver, "create", "test2", "input") 44 | cmd(archiver, "delete", "-a", "test1") 45 | cmd(archiver, "delete", "-a", "test2") 46 | cmd(archiver, "delete", "-a", "sh:test*") 47 | output = cmd(archiver, "repo-list") 48 | assert "@PROT" in output 49 | assert "test1" in output 50 | assert "test2" not in output 51 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/help_cmd_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ...constants import * # NOQA 4 | from ...helpers.nanorst import RstToTextLazy, rst_to_terminal 5 | from . import Archiver, cmd 6 | 7 | 8 | def get_all_parsers(): 9 | # Return dict mapping command to parser. 10 | parser = Archiver(prog="borg").build_parser() 11 | borgfs_parser = Archiver(prog="borgfs").build_parser() 12 | parsers = {} 13 | 14 | def discover_level(prefix, parser, Archiver, extra_choices=None): 15 | choices = {} 16 | for action in parser._actions: 17 | if action.choices is not None and "SubParsersAction" in str(action.__class__): 18 | for command, parser in action.choices.items(): 19 | choices[prefix + command] = parser 20 | if extra_choices is not None: 21 | choices.update(extra_choices) 22 | if prefix and not choices: 23 | return 24 | 25 | for command, parser in sorted(choices.items()): 26 | discover_level(command + " ", parser, Archiver) 27 | parsers[command] = parser 28 | 29 | discover_level("", parser, Archiver, {"borgfs": borgfs_parser}) 30 | return parsers 31 | 32 | 33 | def test_usage(archiver): 34 | cmd(archiver) 35 | cmd(archiver, "-h") 36 | 37 | 38 | def test_help(archiver): 39 | assert "Borg" in cmd(archiver, "help") 40 | assert "patterns" in cmd(archiver, "help", "patterns") 41 | assert "creates a new, empty repository" in cmd(archiver, "help", "repo-create") 42 | assert "positional arguments" not in cmd(archiver, "help", "repo-create", "--epilog-only") 43 | assert "creates a new, empty repository" not in cmd(archiver, "help", "repo-create", "--usage-only") 44 | 45 | 46 | @pytest.mark.parametrize("command, parser", list(get_all_parsers().items())) 47 | def test_help_formatting(command, parser): 48 | if isinstance(parser.epilog, RstToTextLazy): 49 | assert parser.epilog.rst 50 | 51 | 52 | @pytest.mark.parametrize("topic", list(Archiver.helptext.keys())) 53 | def test_help_formatting_helptexts(topic): 54 | helptext = Archiver.helptext[topic] 55 | assert str(rst_to_terminal(helptext)) 56 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/info_cmd_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from ...constants import * # NOQA 5 | from . import cmd, checkts, create_regular_file, generate_archiver_tests, RK_ENCRYPTION 6 | 7 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 8 | 9 | 10 | def test_info(archivers, request): 11 | archiver = request.getfixturevalue(archivers) 12 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 13 | cmd(archiver, "repo-create", RK_ENCRYPTION) 14 | cmd(archiver, "create", "test", "input") 15 | info_archive = cmd(archiver, "info", "-a", "test") 16 | assert "Archive name: test" + os.linesep in info_archive 17 | info_archive = cmd(archiver, "info", "--first", "1") 18 | assert "Archive name: test" + os.linesep in info_archive 19 | 20 | 21 | def test_info_json(archivers, request): 22 | archiver = request.getfixturevalue(archivers) 23 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 24 | cmd(archiver, "repo-create", RK_ENCRYPTION) 25 | cmd(archiver, "create", "test", "input") 26 | 27 | info_archive = json.loads(cmd(archiver, "info", "-a", "test", "--json")) 28 | archives = info_archive["archives"] 29 | assert len(archives) == 1 30 | archive = archives[0] 31 | assert archive["name"] == "test" 32 | assert isinstance(archive["command_line"], str) 33 | assert isinstance(archive["duration"], float) 34 | assert len(archive["id"]) == 64 35 | assert archive["tags"] == [] 36 | assert "stats" in archive 37 | checkts(archive["start"]) 38 | checkts(archive["end"]) 39 | 40 | 41 | def test_info_json_of_empty_archive(archivers, request): 42 | """See https://github.com/borgbackup/borg/issues/6120""" 43 | archiver = request.getfixturevalue(archivers) 44 | cmd(archiver, "repo-create", RK_ENCRYPTION) 45 | info_repo = json.loads(cmd(archiver, "info", "--json", "--first=1")) 46 | assert info_repo["archives"] == [] 47 | info_repo = json.loads(cmd(archiver, "info", "--json", "--last=1")) 48 | assert info_repo["archives"] == [] 49 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/lock_cmds_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import time 4 | 5 | from ...constants import * # NOQA 6 | from . import cmd, generate_archiver_tests, RK_ENCRYPTION 7 | from ...helpers import CommandError 8 | 9 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 10 | 11 | 12 | def test_break_lock(archivers, request): 13 | archiver = request.getfixturevalue(archivers) 14 | cmd(archiver, "repo-create", RK_ENCRYPTION) 15 | cmd(archiver, "break-lock") 16 | 17 | 18 | def test_with_lock(tmp_path): 19 | repo_path = tmp_path / "repo" 20 | env = os.environ.copy() 21 | env["BORG_REPO"] = "file://" + str(repo_path) 22 | command0 = "python3", "-m", "borg", "repo-create", "--encryption=none" 23 | # timings must be adjusted so that command1 keeps running while command2 tries to get the lock, 24 | # so that lock acquisition for command2 fails as the test expects it. 25 | lock_wait, execution_time, startup_wait = 2, 4, 1 26 | assert lock_wait < execution_time - startup_wait 27 | command1 = "python3", "-c", f'import time; print("first command - acquires the lock"); time.sleep({execution_time})' 28 | command2 = "python3", "-c", 'print("second command - should never get executed")' 29 | borgwl = "python3", "-m", "borg", "with-lock", f"--lock-wait={lock_wait}" 30 | popen_options = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env) 31 | subprocess.run(command0, env=env, check=True, text=True, capture_output=True) 32 | assert repo_path.exists() 33 | with subprocess.Popen([*borgwl, *command1], **popen_options) as p1: 34 | time.sleep(startup_wait) # wait until p1 is running 35 | # now try to get another lock on the same repository: 36 | with subprocess.Popen([*borgwl, *command2], **popen_options) as p2: 37 | out, err_out = p2.communicate() 38 | assert "second command" not in out # command2 is "locked out" 39 | assert "Failed to create/acquire the lock" in err_out 40 | assert p2.returncode == 73 # LockTimeout: could not acquire the lock, p1 already has it 41 | out, err_out = p1.communicate() 42 | assert "first command" in out # command1 was executed and had the lock 43 | assert not err_out 44 | assert p1.returncode == 0 45 | 46 | 47 | def test_with_lock_non_existent_command(archivers, request): 48 | archiver = request.getfixturevalue(archivers) 49 | cmd(archiver, "repo-create", RK_ENCRYPTION) 50 | command = ["non_existent_command"] 51 | expected_ec = CommandError().exit_code 52 | cmd(archiver, "with-lock", *command, fork=True, exit_code=expected_ec) 53 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/patterns_test.py: -------------------------------------------------------------------------------- 1 | from ...archiver._common import build_filter 2 | from ...constants import * # NOQA 3 | from ...patterns import IECommand, PatternMatcher, parse_pattern 4 | from ...item import Item 5 | 6 | 7 | def test_basic(): 8 | matcher = PatternMatcher() 9 | matcher.add([parse_pattern("included")], IECommand.Include) 10 | filter = build_filter(matcher, 0) 11 | assert filter(Item(path="included")) 12 | assert filter(Item(path="included/file")) 13 | assert not filter(Item(path="something else")) 14 | 15 | 16 | def test_empty(): 17 | matcher = PatternMatcher(fallback=True) 18 | filter = build_filter(matcher, 0) 19 | assert filter(Item(path="anything")) 20 | 21 | 22 | def test_strip_components(): 23 | matcher = PatternMatcher(fallback=True) 24 | filter = build_filter(matcher, strip_components=1) 25 | assert not filter(Item(path="shallow")) 26 | assert filter(Item(path="deep enough/file")) 27 | assert filter(Item(path="something/dir/file")) 28 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/rename_cmd_test.py: -------------------------------------------------------------------------------- 1 | from ...constants import * # NOQA 2 | from ...manifest import Manifest 3 | from ...repository import Repository 4 | from . import cmd, create_regular_file, generate_archiver_tests, RK_ENCRYPTION 5 | 6 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 7 | 8 | 9 | def test_rename(archivers, request): 10 | archiver = request.getfixturevalue(archivers) 11 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 12 | create_regular_file(archiver.input_path, "dir2/file2", size=1024 * 80) 13 | cmd(archiver, "repo-create", RK_ENCRYPTION) 14 | cmd(archiver, "create", "test", "input") 15 | cmd(archiver, "create", "test.2", "input") 16 | cmd(archiver, "extract", "test", "--dry-run") 17 | cmd(archiver, "extract", "test.2", "--dry-run") 18 | cmd(archiver, "rename", "test", "test.3") 19 | cmd(archiver, "extract", "test.2", "--dry-run") 20 | cmd(archiver, "rename", "test.2", "test.4") 21 | cmd(archiver, "extract", "test.3", "--dry-run") 22 | cmd(archiver, "extract", "test.4", "--dry-run") 23 | # Make sure both archives have been renamed 24 | with Repository(archiver.repository_path) as repository: 25 | manifest = Manifest.load(repository, Manifest.NO_OPERATION_CHECK) 26 | assert manifest.archives.count() == 2 27 | assert manifest.archives.exists("test.3") 28 | assert manifest.archives.exists("test.4") 29 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/repo12.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/src/borg/testsuite/archiver/repo12.tar.gz -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/repo_create_cmd_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest.mock import patch 3 | 4 | import pytest 5 | 6 | from ...helpers.errors import Error, CancelledByUser 7 | from ...constants import * # NOQA 8 | from ...crypto.key import FlexiKey 9 | from . import cmd, generate_archiver_tests, RK_ENCRYPTION, KF_ENCRYPTION 10 | 11 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 12 | 13 | 14 | def test_repo_create_interrupt(archivers, request): 15 | archiver = request.getfixturevalue(archivers) 16 | if archiver.EXE: 17 | pytest.skip("patches object") 18 | 19 | def raise_eof(*args, **kwargs): 20 | raise EOFError 21 | 22 | with patch.object(FlexiKey, "create", raise_eof): 23 | if archiver.FORK_DEFAULT: 24 | cmd(archiver, "repo-create", RK_ENCRYPTION, exit_code=2) 25 | else: 26 | with pytest.raises(CancelledByUser): 27 | cmd(archiver, "repo-create", RK_ENCRYPTION) 28 | 29 | assert not os.path.exists(archiver.repository_location) 30 | 31 | 32 | def test_repo_create_requires_encryption_option(archivers, request): 33 | archiver = request.getfixturevalue(archivers) 34 | cmd(archiver, "repo-create", exit_code=2) 35 | 36 | 37 | def test_repo_create_refuse_to_overwrite_keyfile(archivers, request, monkeypatch): 38 | # BORG_KEY_FILE=something borg repo-create should quit if "something" already exists. 39 | # See: https://github.com/borgbackup/borg/pull/6046 40 | archiver = request.getfixturevalue(archivers) 41 | keyfile = os.path.join(archiver.tmpdir, "keyfile") 42 | monkeypatch.setenv("BORG_KEY_FILE", keyfile) 43 | original_location = archiver.repository_location 44 | archiver.repository_location = original_location + "0" 45 | cmd(archiver, "repo-create", KF_ENCRYPTION) 46 | with open(keyfile) as file: 47 | before = file.read() 48 | archiver.repository_location = original_location + "1" 49 | arg = ("repo-create", KF_ENCRYPTION) 50 | if archiver.FORK_DEFAULT: 51 | cmd(archiver, *arg, exit_code=2) 52 | else: 53 | with pytest.raises(Error): 54 | cmd(archiver, *arg) 55 | with open(keyfile) as file: 56 | after = file.read() 57 | assert before == after 58 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/repo_delete_cmd_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from ...constants import * # NOQA 6 | from ...helpers import CancelledByUser 7 | from . import create_regular_file, cmd, generate_archiver_tests, RK_ENCRYPTION 8 | 9 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 10 | 11 | 12 | def test_delete_repo(archivers, request): 13 | archiver = request.getfixturevalue(archivers) 14 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 15 | create_regular_file(archiver.input_path, "dir2/file2", size=1024 * 80) 16 | cmd(archiver, "repo-create", RK_ENCRYPTION) 17 | cmd(archiver, "create", "test", "input") 18 | cmd(archiver, "create", "test.2", "input") 19 | os.environ["BORG_DELETE_I_KNOW_WHAT_I_AM_DOING"] = "no" 20 | if archiver.FORK_DEFAULT: 21 | expected_ec = CancelledByUser().exit_code 22 | cmd(archiver, "repo-delete", exit_code=expected_ec) 23 | else: 24 | with pytest.raises(CancelledByUser): 25 | cmd(archiver, "repo-delete") 26 | assert os.path.exists(archiver.repository_path) 27 | os.environ["BORG_DELETE_I_KNOW_WHAT_I_AM_DOING"] = "YES" 28 | cmd(archiver, "repo-delete") 29 | # Make sure the repo is gone 30 | assert not os.path.exists(archiver.repository_path) 31 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/repo_info_cmd_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from ...constants import * # NOQA 4 | from . import checkts, cmd, create_regular_file, generate_archiver_tests, RK_ENCRYPTION 5 | 6 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 7 | 8 | 9 | def test_info(archivers, request): 10 | archiver = request.getfixturevalue(archivers) 11 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 12 | cmd(archiver, "repo-create", RK_ENCRYPTION) 13 | cmd(archiver, "create", "test", "input") 14 | info_repo = cmd(archiver, "repo-info") 15 | assert "Repository ID:" in info_repo 16 | 17 | 18 | def test_info_json(archivers, request): 19 | archiver = request.getfixturevalue(archivers) 20 | create_regular_file(archiver.input_path, "file1", size=1024 * 80) 21 | cmd(archiver, "repo-create", RK_ENCRYPTION) 22 | cmd(archiver, "create", "test", "input") 23 | 24 | info_repo = json.loads(cmd(archiver, "repo-info", "--json")) 25 | repository = info_repo["repository"] 26 | assert len(repository["id"]) == 64 27 | assert "last_modified" in repository 28 | 29 | checkts(repository["last_modified"]) 30 | assert info_repo["encryption"]["mode"] == RK_ENCRYPTION[13:] 31 | assert "keyfile" not in info_repo["encryption"] 32 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/return_codes_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ...constants import * # NOQA 4 | from ...helpers import IncludePatternNeverMatchedWarning 5 | from ...repository import Repository 6 | from . import cmd, changedir, generate_archiver_tests # NOQA 7 | 8 | pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA 9 | 10 | 11 | def test_return_codes(archivers, request): 12 | archiver = request.getfixturevalue(archivers) 13 | cmd(archiver, "repo-create", "--encryption=none") 14 | cmd(archiver, "create", "archive", "input") 15 | with changedir("output"): 16 | cmd(archiver, "extract", "archive") 17 | cmd( 18 | archiver, 19 | "extract", 20 | "archive", 21 | "does/not/match", 22 | fork=True, 23 | exit_code=IncludePatternNeverMatchedWarning().exit_code, 24 | ) 25 | 26 | 27 | def test_exit_codes(archivers, request, monkeypatch): 28 | archiver = request.getfixturevalue(archivers) 29 | # we create the repo path, but do NOT initialize the borg repo, 30 | # so the borg create commands are expected to fail with DoesNotExist (was: InvalidRepository in borg 1.4). 31 | os.makedirs(archiver.repository_path) 32 | monkeypatch.setenv("BORG_EXIT_CODES", "classic") 33 | cmd(archiver, "create", "archive", "input", fork=True, exit_code=EXIT_ERROR) 34 | monkeypatch.setenv("BORG_EXIT_CODES", "modern") 35 | cmd(archiver, "create", "archive", "input", fork=True, exit_code=Repository.DoesNotExist.exit_mcode) 36 | -------------------------------------------------------------------------------- /src/borg/testsuite/archiver/serve_cmd_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import tempfile 4 | import time 5 | 6 | import pytest 7 | import platformdirs 8 | 9 | from . import exec_cmd 10 | from ...platformflags import is_win32 11 | from ...helpers import get_runtime_dir 12 | 13 | 14 | def have_a_short_runtime_dir(mp): 15 | # under pytest, we use BORG_BASE_DIR to keep stuff away from the user's normal borg dirs. 16 | # this leads to a very long get_runtime_dir() path - too long for a socket file! 17 | # thus, we override that again via BORG_RUNTIME_DIR to get a shorter path. 18 | mp.setenv("BORG_RUNTIME_DIR", os.path.join(platformdirs.user_runtime_dir(), "pytest")) 19 | 20 | 21 | @pytest.fixture 22 | def serve_socket(monkeypatch): 23 | have_a_short_runtime_dir(monkeypatch) 24 | # use a random unique socket filename, so tests can run in parallel. 25 | socket_file = tempfile.mktemp(suffix=".sock", prefix="borg-", dir=get_runtime_dir()) 26 | with subprocess.Popen(["borg", "serve", f"--socket={socket_file}"]) as p: 27 | while not os.path.exists(socket_file): 28 | time.sleep(0.01) # wait until socket server has started 29 | yield socket_file 30 | p.terminate() 31 | 32 | 33 | @pytest.mark.skipif(is_win32, reason="hangs on win32") 34 | def test_with_socket(serve_socket, tmpdir, monkeypatch): 35 | have_a_short_runtime_dir(monkeypatch) 36 | repo_path = str(tmpdir.join("repo")) 37 | 38 | ret, output = exec_cmd( 39 | f"--socket={serve_socket}", f"--repo=socket://{repo_path}", "repo-create", "--encryption=none" 40 | ) 41 | assert ret == 0 42 | 43 | ret, output = exec_cmd(f"--socket={serve_socket}", f"--repo=socket://{repo_path}", "repo-info") 44 | assert ret == 0 45 | assert "Repository ID: " in output 46 | 47 | monkeypatch.setenv("BORG_DELETE_I_KNOW_WHAT_I_AM_DOING", "YES") 48 | ret, output = exec_cmd(f"--socket={serve_socket}", f"--repo=socket://{repo_path}", "repo-delete") 49 | assert ret == 0 50 | 51 | 52 | @pytest.mark.skipif(is_win32, reason="hangs on win32") 53 | def test_socket_permissions(serve_socket): 54 | st = os.stat(serve_socket) 55 | assert st.st_mode & 0o0777 == 0o0770 # user and group are permitted to use the socket 56 | -------------------------------------------------------------------------------- /src/borg/testsuite/cache_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from .hashindex_test import H 6 | from .key_test import TestKey 7 | from ..archive import Statistics 8 | from ..cache import AdHocWithFilesCache 9 | from ..crypto.key import AESOCBRepoKey 10 | from ..manifest import Manifest 11 | from ..repository import Repository 12 | 13 | 14 | class TestAdHocWithFilesCache: 15 | @pytest.fixture 16 | def repository(self, tmpdir): 17 | self.repository_location = os.path.join(str(tmpdir), "repository") 18 | with Repository(self.repository_location, exclusive=True, create=True) as repository: 19 | repository.put(H(1), b"1234") 20 | yield repository 21 | 22 | @pytest.fixture 23 | def key(self, repository, monkeypatch): 24 | monkeypatch.setenv("BORG_PASSPHRASE", "test") 25 | key = AESOCBRepoKey.create(repository, TestKey.MockArgs()) 26 | return key 27 | 28 | @pytest.fixture 29 | def manifest(self, repository, key): 30 | Manifest(key, repository).write() 31 | return Manifest.load(repository, key=key, operations=Manifest.NO_OPERATION_CHECK) 32 | 33 | @pytest.fixture 34 | def cache(self, repository, key, manifest): 35 | return AdHocWithFilesCache(manifest) 36 | 37 | def test_does_not_contain_manifest(self, cache): 38 | assert not cache.seen_chunk(Manifest.MANIFEST_ID) 39 | 40 | def test_seen_chunk_add_chunk_size(self, cache): 41 | assert cache.add_chunk(H(1), {}, b"5678", stats=Statistics()) == (H(1), 4) 42 | 43 | def test_reuse_after_add_chunk(self, cache): 44 | assert cache.add_chunk(H(3), {}, b"5678", stats=Statistics()) == (H(3), 4) 45 | assert cache.reuse_chunk(H(3), 4, Statistics()) == (H(3), 4) 46 | 47 | def test_existing_reuse_after_add_chunk(self, cache): 48 | assert cache.add_chunk(H(1), {}, b"5678", stats=Statistics()) == (H(1), 4) 49 | assert cache.reuse_chunk(H(1), 4, Statistics()) == (H(1), 4) 50 | 51 | def test_files_cache(self, cache): 52 | st = os.stat(".") 53 | assert cache.file_known_and_unchanged(b"foo", bytes(32), st) == (False, None) 54 | assert cache.cache_mode == "d" 55 | assert cache.files == {} 56 | -------------------------------------------------------------------------------- /src/borg/testsuite/checksums_test.py: -------------------------------------------------------------------------------- 1 | from .. import checksums 2 | from ..helpers import bin_to_hex, hex_to_bin 3 | 4 | 5 | def test_xxh64(): 6 | assert bin_to_hex(checksums.xxh64(b"test", 123)) == "2b81b9401bef86cf" 7 | assert bin_to_hex(checksums.xxh64(b"test")) == "4fdcca5ddb678139" 8 | assert ( 9 | bin_to_hex( 10 | checksums.xxh64( 11 | hex_to_bin( 12 | "6f663f01c118abdea553373d5eae44e7dac3b6829b46b9bbeff202b6c592c22d724" 13 | "fb3d25a347cca6c5b8f20d567e4bb04b9cfa85d17f691590f9a9d32e8ccc9102e9d" 14 | "cf8a7e6716280cd642ce48d03fdf114c9f57c20d9472bb0f81c147645e6fa3d331" 15 | ) 16 | ) 17 | ) 18 | == "35d5d2f545d9511a" 19 | ) 20 | 21 | 22 | def test_streaming_xxh64(): 23 | hasher = checksums.StreamingXXH64(123) 24 | hasher.update(b"te") 25 | hasher.update(b"st") 26 | assert bin_to_hex(hasher.digest()) == hasher.hexdigest() == "2b81b9401bef86cf" 27 | -------------------------------------------------------------------------------- /src/borg/testsuite/chunker_slow_test.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha256 2 | from io import BytesIO 3 | 4 | from .chunker_test import cf 5 | from ..chunker import Chunker 6 | from ..constants import * # NOQA 7 | from ..helpers import hex_to_bin 8 | 9 | 10 | def H(data): 11 | return sha256(data).digest() 12 | 13 | 14 | def test_chunkpoints_unchanged(): 15 | def twist(size): 16 | x = 1 17 | a = bytearray(size) 18 | for i in range(size): 19 | x = (x * 1103515245 + 12345) & 0x7FFFFFFF 20 | a[i] = x & 0xFF 21 | return a 22 | 23 | data = twist(100000) 24 | 25 | runs = [] 26 | for winsize in (65, 129, HASH_WINDOW_SIZE, 7351): 27 | for minexp in (4, 6, 7, 11, 12): 28 | for maxexp in (15, 17): 29 | if minexp >= maxexp: 30 | continue 31 | for maskbits in (4, 7, 10, 12): 32 | for seed in (1849058162, 1234567653): 33 | fh = BytesIO(data) 34 | chunker = Chunker(seed, minexp, maxexp, maskbits, winsize) 35 | chunks = [H(c) for c in cf(chunker.chunkify(fh, -1))] 36 | runs.append(H(b"".join(chunks))) 37 | 38 | # The "correct" hash below matches the existing chunker behavior. 39 | # Future chunker optimisations must not change this, or existing repos will bloat. 40 | overall_hash = H(b"".join(runs)) 41 | assert overall_hash == hex_to_bin("a43d0ecb3ae24f38852fcc433a83dacd28fe0748d09cc73fc11b69cf3f1a7299") 42 | -------------------------------------------------------------------------------- /src/borg/testsuite/efficient_collection_queue_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ..helpers.datastruct import EfficientCollectionQueue 4 | 5 | 6 | class TestEfficientQueue: 7 | def test_base_usage(self): 8 | queue = EfficientCollectionQueue(100, bytes) 9 | assert queue.peek_front() == b"" 10 | queue.push_back(b"1234") 11 | assert queue.peek_front() == b"1234" 12 | assert len(queue) == 4 13 | assert queue 14 | queue.pop_front(4) 15 | assert queue.peek_front() == b"" 16 | assert len(queue) == 0 17 | assert not queue 18 | 19 | def test_usage_with_arrays(self): 20 | queue = EfficientCollectionQueue(100, list) 21 | assert queue.peek_front() == [] 22 | queue.push_back([1, 2, 3, 4]) 23 | assert queue.peek_front() == [1, 2, 3, 4] 24 | assert len(queue) == 4 25 | assert queue 26 | queue.pop_front(4) 27 | assert queue.peek_front() == [] 28 | assert len(queue) == 0 29 | assert not queue 30 | 31 | def test_chunking(self): 32 | queue = EfficientCollectionQueue(2, bytes) 33 | queue.push_back(b"1") 34 | queue.push_back(b"23") 35 | queue.push_back(b"4567") 36 | assert len(queue) == 7 37 | assert queue.peek_front() == b"12" 38 | queue.pop_front(3) 39 | assert queue.peek_front() == b"4" 40 | queue.pop_front(1) 41 | assert queue.peek_front() == b"56" 42 | queue.pop_front(2) 43 | assert len(queue) == 1 44 | assert queue 45 | with pytest.raises(EfficientCollectionQueue.SizeUnderflow): 46 | queue.pop_front(2) 47 | assert queue.peek_front() == b"7" 48 | queue.pop_front(1) 49 | assert queue.peek_front() == b"" 50 | assert len(queue) == 0 51 | assert not queue 52 | -------------------------------------------------------------------------------- /src/borg/testsuite/hashindex_test.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import struct 3 | 4 | import pytest 5 | 6 | from ..hashindex import ChunkIndex, ChunkIndexEntry 7 | 8 | 9 | def H(x): 10 | # make some 32byte long thing that depends on x 11 | return bytes("%-0.32d" % x, "ascii") 12 | 13 | 14 | def H2(x): 15 | # like H(x), but with pseudo-random distribution of the output value 16 | return hashlib.sha256(H(x)).digest() 17 | 18 | 19 | def test_chunkindex_add(): 20 | chunks = ChunkIndex() 21 | x = H2(1) 22 | chunks.add(x, 0) 23 | assert chunks[x] == ChunkIndexEntry(flags=ChunkIndex.F_USED, size=0) 24 | chunks.add(x, 2) # updating size (we do not have a size yet) 25 | assert chunks[x] == ChunkIndexEntry(flags=ChunkIndex.F_USED, size=2) 26 | chunks.add(x, 2) 27 | assert chunks[x] == ChunkIndexEntry(flags=ChunkIndex.F_USED, size=2) 28 | with pytest.raises(AssertionError): 29 | chunks.add(x, 3) # inconsistent size (we already have a different size) 30 | 31 | 32 | def test_keyerror(): 33 | chunks = ChunkIndex() 34 | x = H2(1) 35 | with pytest.raises(KeyError): 36 | chunks[x] 37 | with pytest.raises(struct.error): 38 | chunks[x] = ChunkIndexEntry(flags=ChunkIndex.F_NONE, size=2**33) 39 | 40 | 41 | def test_new(): 42 | def new_chunks(): 43 | return list(chunks.iteritems(only_new=True)) 44 | 45 | chunks = ChunkIndex() 46 | key1, value1a = H2(1), ChunkIndexEntry(flags=ChunkIndex.F_USED, size=23) 47 | key2, value2a = H2(2), ChunkIndexEntry(flags=ChunkIndex.F_USED, size=42) 48 | # tracking of new entries 49 | assert new_chunks() == [] 50 | chunks[key1] = value1a 51 | assert new_chunks() == [(key1, value1a)] 52 | chunks.clear_new() 53 | assert new_chunks() == [] 54 | chunks[key2] = value2a 55 | assert new_chunks() == [(key2, value2a)] 56 | chunks.clear_new() 57 | assert new_chunks() == [] 58 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borgbackup/borg/dda9c445e12bf52ec62302bff19495c690c98a97/src/borg/testsuite/helpers/__init__.py -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/__init__test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ...constants import * # NOQA 4 | from ...helpers import classify_ec, max_ec 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "ec_range,ec_class", 9 | ( 10 | # inclusive range start, exclusive range end 11 | ((0, 1), "success"), 12 | ((1, 2), "warning"), 13 | ((2, 3), "error"), 14 | ((EXIT_ERROR_BASE, EXIT_WARNING_BASE), "error"), 15 | ((EXIT_WARNING_BASE, EXIT_SIGNAL_BASE), "warning"), 16 | ((EXIT_SIGNAL_BASE, 256), "signal"), 17 | ), 18 | ) 19 | def test_classify_ec(ec_range, ec_class): 20 | for ec in range(*ec_range): 21 | classify_ec(ec) == ec_class 22 | 23 | 24 | def test_ec_invalid(): 25 | with pytest.raises(ValueError): 26 | classify_ec(666) 27 | with pytest.raises(ValueError): 28 | classify_ec(-1) 29 | with pytest.raises(TypeError): 30 | classify_ec(None) 31 | 32 | 33 | @pytest.mark.parametrize( 34 | "ec1,ec2,ec_max", 35 | ( 36 | # same for modern / legacy 37 | (EXIT_SUCCESS, EXIT_SUCCESS, EXIT_SUCCESS), 38 | (EXIT_SUCCESS, EXIT_SIGNAL_BASE, EXIT_SIGNAL_BASE), 39 | # legacy exit codes 40 | (EXIT_SUCCESS, EXIT_WARNING, EXIT_WARNING), 41 | (EXIT_SUCCESS, EXIT_ERROR, EXIT_ERROR), 42 | (EXIT_WARNING, EXIT_SUCCESS, EXIT_WARNING), 43 | (EXIT_WARNING, EXIT_WARNING, EXIT_WARNING), 44 | (EXIT_WARNING, EXIT_ERROR, EXIT_ERROR), 45 | (EXIT_WARNING, EXIT_SIGNAL_BASE, EXIT_SIGNAL_BASE), 46 | (EXIT_ERROR, EXIT_SUCCESS, EXIT_ERROR), 47 | (EXIT_ERROR, EXIT_WARNING, EXIT_ERROR), 48 | (EXIT_ERROR, EXIT_ERROR, EXIT_ERROR), 49 | (EXIT_ERROR, EXIT_SIGNAL_BASE, EXIT_SIGNAL_BASE), 50 | # some modern codes 51 | (EXIT_SUCCESS, EXIT_WARNING_BASE, EXIT_WARNING_BASE), 52 | (EXIT_SUCCESS, EXIT_ERROR_BASE, EXIT_ERROR_BASE), 53 | (EXIT_WARNING_BASE, EXIT_SUCCESS, EXIT_WARNING_BASE), 54 | (EXIT_WARNING_BASE + 1, EXIT_WARNING_BASE + 2, EXIT_WARNING_BASE + 1), 55 | (EXIT_WARNING_BASE, EXIT_ERROR_BASE, EXIT_ERROR_BASE), 56 | (EXIT_WARNING_BASE, EXIT_SIGNAL_BASE, EXIT_SIGNAL_BASE), 57 | (EXIT_ERROR_BASE, EXIT_SUCCESS, EXIT_ERROR_BASE), 58 | (EXIT_ERROR_BASE, EXIT_WARNING_BASE, EXIT_ERROR_BASE), 59 | (EXIT_ERROR_BASE + 1, EXIT_ERROR_BASE + 2, EXIT_ERROR_BASE + 1), 60 | (EXIT_ERROR_BASE, EXIT_SIGNAL_BASE, EXIT_SIGNAL_BASE), 61 | ), 62 | ) 63 | def test_max_ec(ec1, ec2, ec_max): 64 | assert max_ec(ec1, ec2) == ec_max 65 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/datastruct_test.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import pytest 3 | 4 | from ...helpers.datastruct import StableDict, Buffer 5 | from ...helpers import msgpack 6 | 7 | 8 | def test_stable_dict(): 9 | d = StableDict(foo=1, bar=2, boo=3, baz=4) 10 | assert list(d.items()) == [("bar", 2), ("baz", 4), ("boo", 3), ("foo", 1)] 11 | assert hashlib.md5(msgpack.packb(d)).hexdigest() == "fc78df42cd60691b3ac3dd2a2b39903f" 12 | 13 | 14 | class TestBuffer: 15 | def test_type(self): 16 | buffer = Buffer(bytearray) 17 | assert isinstance(buffer.get(), bytearray) 18 | buffer = Buffer(bytes) # don't do that in practice 19 | assert isinstance(buffer.get(), bytes) 20 | 21 | def test_len(self): 22 | buffer = Buffer(bytearray, size=0) 23 | b = buffer.get() 24 | assert len(buffer) == len(b) == 0 25 | buffer = Buffer(bytearray, size=1234) 26 | b = buffer.get() 27 | assert len(buffer) == len(b) == 1234 28 | 29 | def test_resize(self): 30 | buffer = Buffer(bytearray, size=100) 31 | assert len(buffer) == 100 32 | b1 = buffer.get() 33 | buffer.resize(200) 34 | assert len(buffer) == 200 35 | b2 = buffer.get() 36 | assert b2 is not b1 # new, bigger buffer 37 | buffer.resize(100) 38 | assert len(buffer) >= 100 39 | b3 = buffer.get() 40 | assert b3 is b2 # still same buffer (200) 41 | buffer.resize(100, init=True) 42 | assert len(buffer) == 100 # except on init 43 | b4 = buffer.get() 44 | assert b4 is not b3 # new, smaller buffer 45 | 46 | def test_limit(self): 47 | buffer = Buffer(bytearray, size=100, limit=200) 48 | buffer.resize(200) 49 | assert len(buffer) == 200 50 | with pytest.raises(Buffer.MemoryLimitExceeded): 51 | buffer.resize(201) 52 | assert len(buffer) == 200 53 | 54 | def test_get(self): 55 | buffer = Buffer(bytearray, size=100, limit=200) 56 | b1 = buffer.get(50) 57 | assert len(b1) >= 50 # == 100 58 | b2 = buffer.get(100) 59 | assert len(b2) >= 100 # == 100 60 | assert b2 is b1 # did not need resizing yet 61 | b3 = buffer.get(200) 62 | assert len(b3) == 200 63 | assert b3 is not b2 # new, resized buffer 64 | with pytest.raises(Buffer.MemoryLimitExceeded): 65 | buffer.get(201) # beyond limit 66 | assert len(buffer) == 200 67 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/misc_test.py: -------------------------------------------------------------------------------- 1 | from io import StringIO, BytesIO 2 | 3 | import pytest 4 | 5 | from ...helpers.misc import ChunkIteratorFileWrapper, chunkit, iter_separated 6 | 7 | 8 | def test_chunk_file_wrapper(): 9 | cfw = ChunkIteratorFileWrapper(iter([b"abc", b"def"])) 10 | assert cfw.read(2) == b"ab" 11 | assert cfw.read(50) == b"cdef" 12 | assert cfw.exhausted 13 | 14 | cfw = ChunkIteratorFileWrapper(iter([])) 15 | assert cfw.read(2) == b"" 16 | assert cfw.exhausted 17 | 18 | 19 | def test_chunkit(): 20 | it = chunkit("abcdefg", 3) 21 | assert next(it) == ["a", "b", "c"] 22 | assert next(it) == ["d", "e", "f"] 23 | assert next(it) == ["g"] 24 | with pytest.raises(StopIteration): 25 | next(it) 26 | with pytest.raises(StopIteration): 27 | next(it) 28 | 29 | it = chunkit("ab", 3) 30 | assert list(it) == [["a", "b"]] 31 | 32 | it = chunkit("", 3) 33 | assert list(it) == [] 34 | 35 | 36 | def test_iter_separated(): 37 | # newline and utf-8 38 | sep, items = "\n", ["foo", "bar/baz", "αáčő"] 39 | fd = StringIO(sep.join(items)) 40 | assert list(iter_separated(fd)) == items 41 | # null and bogus ending 42 | sep, items = "\0", ["foo/bar", "baz", "spam"] 43 | fd = StringIO(sep.join(items) + "\0") 44 | assert list(iter_separated(fd, sep=sep)) == ["foo/bar", "baz", "spam"] 45 | # multichar 46 | sep, items = "SEP", ["foo/bar", "baz", "spam"] 47 | fd = StringIO(sep.join(items)) 48 | assert list(iter_separated(fd, sep=sep)) == items 49 | # bytes 50 | sep, items = b"\n", [b"foo", b"blop\t", b"gr\xe4ezi"] 51 | fd = BytesIO(sep.join(items)) 52 | assert list(iter_separated(fd)) == items 53 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/msgpack_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | 4 | from ...helpers.msgpack import is_slow_msgpack 5 | from ...platform import is_cygwin 6 | 7 | 8 | def expected_py_mp_slow_combination(): 9 | """do we expect msgpack to be slow in this environment?""" 10 | # we need to import upstream msgpack package here, not helpers.msgpack: 11 | import msgpack 12 | 13 | # msgpack is slow on cygwin 14 | if is_cygwin: 15 | return True 16 | # msgpack < 1.0.6 did not have py312 wheels 17 | if sys.version_info[:2] == (3, 12) and msgpack.version < (1, 0, 6): 18 | return True 19 | # otherwise we expect msgpack to be fast! 20 | return False 21 | 22 | 23 | @pytest.mark.skipif(expected_py_mp_slow_combination(), reason="ignore expected slow msgpack") 24 | def test_is_slow_msgpack(): 25 | # we need to import upstream msgpack package here, not helpers.msgpack: 26 | import msgpack 27 | import msgpack.fallback 28 | 29 | saved_packer = msgpack.Packer 30 | try: 31 | msgpack.Packer = msgpack.fallback.Packer 32 | assert is_slow_msgpack() 33 | finally: 34 | msgpack.Packer = saved_packer 35 | # this tests that we have fast msgpack on test platform: 36 | assert not is_slow_msgpack() 37 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/process_test.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import pytest 3 | 4 | from ...helpers.process import popen_with_error_handling 5 | 6 | 7 | class TestPopenWithErrorHandling: 8 | @pytest.mark.skipif(not shutil.which("test"), reason='"test" binary is needed') 9 | def test_simple(self): 10 | proc = popen_with_error_handling("test 1") 11 | assert proc.wait() == 0 12 | 13 | @pytest.mark.skipif( 14 | shutil.which("borg-foobar-test-notexist"), reason='"borg-foobar-test-notexist" binary exists (somehow?)' 15 | ) 16 | def test_not_found(self): 17 | proc = popen_with_error_handling("borg-foobar-test-notexist 1234") 18 | assert proc is None 19 | 20 | @pytest.mark.parametrize("cmd", ('mismatched "quote', 'foo --bar="baz', "")) 21 | def test_bad_syntax(self, cmd): 22 | proc = popen_with_error_handling(cmd) 23 | assert proc is None 24 | 25 | def test_shell(self): 26 | with pytest.raises(AssertionError): 27 | popen_with_error_handling("", shell=True) 28 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/progress_test.py: -------------------------------------------------------------------------------- 1 | from ...helpers.progress import ProgressIndicatorPercent 2 | 3 | 4 | def test_progress_percentage(capfd): 5 | pi = ProgressIndicatorPercent(1000, step=5, start=0, msg="%3.0f%%") 6 | pi.logger.setLevel("INFO") 7 | pi.show(0) 8 | out, err = capfd.readouterr() 9 | assert err == " 0%\n" 10 | pi.show(420) 11 | pi.show(680) 12 | out, err = capfd.readouterr() 13 | assert err == " 42%\n 68%\n" 14 | pi.show(1000) 15 | out, err = capfd.readouterr() 16 | assert err == "100%\n" 17 | pi.finish() 18 | out, err = capfd.readouterr() 19 | assert err == "\n" 20 | 21 | 22 | def test_progress_percentage_step(capfd): 23 | pi = ProgressIndicatorPercent(100, step=2, start=0, msg="%3.0f%%") 24 | pi.logger.setLevel("INFO") 25 | pi.show() 26 | out, err = capfd.readouterr() 27 | assert err == " 0%\n" 28 | pi.show() 29 | out, err = capfd.readouterr() 30 | assert err == "" # no output at 1% as we have step == 2 31 | pi.show() 32 | out, err = capfd.readouterr() 33 | assert err == " 2%\n" 34 | 35 | 36 | def test_progress_percentage_quiet(capfd): 37 | pi = ProgressIndicatorPercent(1000, step=5, start=0, msg="%3.0f%%") 38 | pi.logger.setLevel("WARN") 39 | pi.show(0) 40 | out, err = capfd.readouterr() 41 | assert err == "" 42 | pi.show(1000) 43 | out, err = capfd.readouterr() 44 | assert err == "" 45 | pi.finish() 46 | out, err = capfd.readouterr() 47 | assert err == "" 48 | -------------------------------------------------------------------------------- /src/borg/testsuite/helpers/time_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from datetime import datetime, timezone 3 | 4 | from ...helpers.time import safe_ns, safe_s, SUPPORT_32BIT_PLATFORMS 5 | 6 | 7 | def utcfromtimestamp(timestamp): 8 | """Returns a naive datetime instance representing the timestamp in the UTC timezone""" 9 | return datetime.fromtimestamp(timestamp, timezone.utc).replace(tzinfo=None) 10 | 11 | 12 | def test_safe_timestamps(): 13 | if SUPPORT_32BIT_PLATFORMS: 14 | # ns fit into int64 15 | assert safe_ns(2**64) <= 2**63 - 1 16 | assert safe_ns(-1) == 0 17 | # s fit into int32 18 | assert safe_s(2**64) <= 2**31 - 1 19 | assert safe_s(-1) == 0 20 | # datetime won't fall over its y10k problem 21 | beyond_y10k = 2**100 22 | with pytest.raises(OverflowError): 23 | utcfromtimestamp(beyond_y10k) 24 | assert utcfromtimestamp(safe_s(beyond_y10k)) > datetime(2038, 1, 1) 25 | assert utcfromtimestamp(safe_ns(beyond_y10k) / 1000000000) > datetime(2038, 1, 1) 26 | else: 27 | # ns fit into int64 28 | assert safe_ns(2**64) <= 2**63 - 1 29 | assert safe_ns(-1) == 0 30 | # s are so that their ns conversion fits into int64 31 | assert safe_s(2**64) * 1000000000 <= 2**63 - 1 32 | assert safe_s(-1) == 0 33 | # datetime won't fall over its y10k problem 34 | beyond_y10k = 2**100 35 | with pytest.raises(OverflowError): 36 | utcfromtimestamp(beyond_y10k) 37 | assert utcfromtimestamp(safe_s(beyond_y10k)) > datetime(2262, 1, 1) 38 | assert utcfromtimestamp(safe_ns(beyond_y10k) / 1000000000) > datetime(2262, 1, 1) 39 | -------------------------------------------------------------------------------- /src/borg/testsuite/logger_test.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from io import StringIO 3 | 4 | import pytest 5 | 6 | from ..logger import find_parent_module, create_logger, setup_logging 7 | 8 | logger = create_logger() 9 | 10 | 11 | @pytest.fixture() 12 | def io_logger(): 13 | io = StringIO() 14 | handler = setup_logging(stream=io, env_var=None) 15 | handler.setFormatter(logging.Formatter("%(name)s: %(message)s")) 16 | logger.setLevel(logging.DEBUG) 17 | return io 18 | 19 | 20 | def test_setup_logging(io_logger): 21 | logger.info("hello world") 22 | assert io_logger.getvalue() == "borg.testsuite.logger_test: hello world\n" 23 | 24 | 25 | def test_multiple_loggers(io_logger): 26 | logger = logging.getLogger(__name__) 27 | logger.info("hello world 1") 28 | assert io_logger.getvalue() == "borg.testsuite.logger_test: hello world 1\n" 29 | logger = logging.getLogger("borg.testsuite.logger_test") 30 | logger.info("hello world 2") 31 | assert ( 32 | io_logger.getvalue() == "borg.testsuite.logger_test: hello world 1\nborg.testsuite.logger_test: hello world 2\n" 33 | ) 34 | io_logger.truncate(0) 35 | io_logger.seek(0) 36 | logger = logging.getLogger("borg.testsuite.logger_test") 37 | logger.info("hello world 2") 38 | assert io_logger.getvalue() == "borg.testsuite.logger_test: hello world 2\n" 39 | 40 | 41 | def test_parent_module(): 42 | assert find_parent_module() == __name__ 43 | 44 | 45 | def test_lazy_logger(): 46 | # just calling all the methods of the proxy 47 | logger.setLevel(logging.DEBUG) 48 | logger.debug("debug") 49 | logger.info("info") 50 | logger.warning("warning") 51 | logger.error("error") 52 | logger.critical("critical") 53 | logger.log(logging.INFO, "info") 54 | try: 55 | raise Exception 56 | except Exception: 57 | logger.exception("exception") 58 | -------------------------------------------------------------------------------- /src/borg/testsuite/lrucache_test.py: -------------------------------------------------------------------------------- 1 | from tempfile import TemporaryFile 2 | 3 | import pytest 4 | 5 | from ..helpers.lrucache import LRUCache 6 | 7 | 8 | class TestLRUCache: 9 | def test_lrucache(self): 10 | c = LRUCache(2) 11 | assert len(c) == 0 12 | assert c.items() == set() 13 | for i, x in enumerate("abc"): 14 | c[x] = i 15 | assert len(c) == 2 16 | assert c.items() == {("b", 1), ("c", 2)} 17 | assert "a" not in c 18 | assert "b" in c 19 | with pytest.raises(KeyError): 20 | c["a"] 21 | assert c.get("a") is None 22 | assert c.get("a", "foo") == "foo" 23 | assert c["b"] == 1 24 | assert c.get("b") == 1 25 | assert c["c"] == 2 26 | c["d"] = 3 27 | assert len(c) == 2 28 | assert c["c"] == 2 29 | assert c["d"] == 3 30 | del c["c"] 31 | assert len(c) == 1 32 | with pytest.raises(KeyError): 33 | c["c"] 34 | assert c["d"] == 3 35 | c.clear() 36 | assert c.items() == set() 37 | 38 | def test_dispose(self): 39 | c = LRUCache(2, dispose=lambda f: f.close()) 40 | f1 = TemporaryFile() 41 | f2 = TemporaryFile() 42 | f3 = TemporaryFile() 43 | c[1] = f1 44 | c[2] = f2 45 | assert not f2.closed 46 | c[3] = f3 47 | assert 1 not in c 48 | assert f1.closed 49 | assert 2 in c 50 | assert not f2.closed 51 | del c[2] 52 | assert 2 not in c 53 | assert f2.closed 54 | c.clear() 55 | assert c.items() == set() 56 | assert f3.closed 57 | -------------------------------------------------------------------------------- /src/borg/testsuite/nanorst_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ..helpers.nanorst import rst_to_text 4 | 5 | 6 | def test_inline(): 7 | assert rst_to_text("*foo* and ``bar``.") == "foo and bar." 8 | 9 | 10 | def test_inline_spread(): 11 | assert rst_to_text("*foo and bar, thusly\nfoobar*.") == "foo and bar, thusly\nfoobar." 12 | 13 | 14 | def test_comment_inline(): 15 | assert rst_to_text("Foo and Bar\n.. foo\nbar") == "Foo and Bar\n.. foo\nbar" 16 | 17 | 18 | def test_inline_escape(): 19 | assert rst_to_text('Such as "\\*" characters.') == 'Such as "*" characters.' 20 | 21 | 22 | def test_comment(): 23 | assert rst_to_text("Foo and Bar\n\n.. foo\nbar") == "Foo and Bar\n\nbar" 24 | 25 | 26 | def test_directive_note(): 27 | assert rst_to_text(".. note::\n Note this and that") == "Note:\n Note this and that" 28 | 29 | 30 | def test_ref(): 31 | references = {"foo": "baz"} 32 | assert rst_to_text("See :ref:`fo\no`.", references=references) == "See baz." 33 | 34 | 35 | def test_undefined_ref(): 36 | with pytest.raises(ValueError) as exc_info: 37 | rst_to_text("See :ref:`foo`.") 38 | assert "Undefined reference" in str(exc_info.value) 39 | -------------------------------------------------------------------------------- /src/borg/testsuite/platform_darwin_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | from ..platform import acl_get, acl_set 5 | from .platform_test import skipif_not_darwin, skipif_fakeroot_detected, skipif_acls_not_working 6 | 7 | # set module-level skips 8 | pytestmark = [skipif_not_darwin, skipif_fakeroot_detected] 9 | 10 | 11 | def get_acl(path, numeric_ids=False): 12 | item = {} 13 | acl_get(path, item, os.stat(path), numeric_ids=numeric_ids) 14 | return item 15 | 16 | 17 | def set_acl(path, acl, numeric_ids=False): 18 | item = {"acl_extended": acl} 19 | acl_set(path, item, numeric_ids=numeric_ids) 20 | 21 | 22 | @skipif_acls_not_working 23 | def test_extended_acl(): 24 | file = tempfile.NamedTemporaryFile() 25 | assert get_acl(file.name) == {} 26 | set_acl( 27 | file.name, 28 | b"!#acl 1\n" 29 | b"group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000000:staff:0:allow:read\n" 30 | b"user:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000:root:0:allow:read\n", 31 | numeric_ids=False, 32 | ) 33 | assert b"group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000014:staff:20:allow:read" in get_acl(file.name)["acl_extended"] 34 | assert b"user:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000:root:0:allow:read" in get_acl(file.name)["acl_extended"] 35 | 36 | file2 = tempfile.NamedTemporaryFile() 37 | set_acl( 38 | file2.name, 39 | b"!#acl 1\n" 40 | b"group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000000:staff:0:allow:read\n" 41 | b"user:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000:root:0:allow:read\n", 42 | numeric_ids=True, 43 | ) 44 | assert b"group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000000:wheel:0:allow:read" in get_acl(file2.name)["acl_extended"] 45 | assert ( 46 | b"group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000000::0:allow:read" 47 | in get_acl(file2.name, numeric_ids=True)["acl_extended"] 48 | ) 49 | -------------------------------------------------------------------------------- /src/borg/testsuite/platform_posix_test.py: -------------------------------------------------------------------------------- 1 | from ..platform import swidth 2 | from .platform_test import skipif_not_posix 3 | 4 | 5 | # set module-level skips 6 | pytestmark = skipif_not_posix 7 | 8 | 9 | def test_posix_swidth_ascii(): 10 | assert swidth("borg") == 4 11 | 12 | 13 | def test_posix_swidth_cjk(): 14 | assert swidth("バックアップ") == 6 * 2 15 | 16 | 17 | def test_posix_swidth_mixed(): 18 | assert swidth("borgバックアップ") == 4 + 6 * 2 19 | -------------------------------------------------------------------------------- /src/borg/testsuite/shell_completions_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import pytest 4 | 5 | SHELL_COMPLETIONS_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", "scripts", "shell_completions") 6 | 7 | 8 | def test_bash_completion_is_valid(): 9 | """Test that the bash completion file is valid bash syntax.""" 10 | bash_completion_file = os.path.join(SHELL_COMPLETIONS_DIR, "bash", "borg") 11 | assert os.path.isfile(bash_completion_file) 12 | 13 | # Check if bash is available 14 | try: 15 | subprocess.run(["bash", "--version"], capture_output=True, check=True) 16 | except (subprocess.SubprocessError, FileNotFoundError): 17 | pytest.skip("bash not available") 18 | 19 | # Test if the bash completion file can be sourced without errors 20 | result = subprocess.run(["bash", "-n", bash_completion_file], capture_output=True) 21 | assert result.returncode == 0, f"Bash completion file has syntax errors: {result.stderr.decode()}" 22 | 23 | 24 | def test_fish_completion_is_valid(): 25 | """Test that the fish completion file is valid fish syntax.""" 26 | fish_completion_file = os.path.join(SHELL_COMPLETIONS_DIR, "fish", "borg.fish") 27 | assert os.path.isfile(fish_completion_file) 28 | 29 | # Check if fish is available 30 | try: 31 | subprocess.run(["fish", "--version"], capture_output=True, check=True) 32 | except (subprocess.SubprocessError, FileNotFoundError): 33 | pytest.skip("fish not available") 34 | 35 | # Test if the fish completion file can be sourced without errors 36 | result = subprocess.run(["fish", "-c", f"source {fish_completion_file}"], capture_output=True) 37 | assert result.returncode == 0, f"Fish completion file has syntax errors: {result.stderr.decode()}" 38 | 39 | 40 | def test_zsh_completion_is_valid(): 41 | """Test that the zsh completion file is valid zsh syntax.""" 42 | zsh_completion_file = os.path.join(SHELL_COMPLETIONS_DIR, "zsh", "_borg") 43 | assert os.path.isfile(zsh_completion_file) 44 | 45 | # Check if zsh is available 46 | try: 47 | subprocess.run(["zsh", "--version"], capture_output=True, check=True) 48 | except (subprocess.SubprocessError, FileNotFoundError): 49 | pytest.skip("zsh not available") 50 | 51 | # Test if the zsh completion file can be sourced without errors 52 | result = subprocess.run(["zsh", "-n", zsh_completion_file], capture_output=True) 53 | assert result.returncode == 0, f"Zsh completion file has syntax errors: {result.stderr.decode()}" 54 | -------------------------------------------------------------------------------- /src/borg/testsuite/version_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ..version import parse_version, format_version 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "version_str, version_tuple", 8 | [ 9 | # setuptools < 8.0 uses "-" 10 | ("1.0.0a1.dev204-g8866961.d20170606", (1, 0, 0, -4, 1)), 11 | ("1.0.0a1.dev204-g8866961", (1, 0, 0, -4, 1)), 12 | ("1.0.0-d20170606", (1, 0, 0, -1)), 13 | # setuptools >= 8.0 uses "+" 14 | ("1.0.0a1.dev204+g8866961.d20170606", (1, 0, 0, -4, 1)), 15 | ("1.0.0a1.dev204+g8866961", (1, 0, 0, -4, 1)), 16 | ("1.0.0+d20170606", (1, 0, 0, -1)), 17 | # pre-release versions: 18 | ("1.0.0a1", (1, 0, 0, -4, 1)), 19 | ("1.0.0a2", (1, 0, 0, -4, 2)), 20 | ("1.0.0b3", (1, 0, 0, -3, 3)), 21 | ("1.0.0rc4", (1, 0, 0, -2, 4)), 22 | # release versions: 23 | ("0.0.0", (0, 0, 0, -1)), 24 | ("0.0.11", (0, 0, 11, -1)), 25 | ("0.11.0", (0, 11, 0, -1)), 26 | ("11.0.0", (11, 0, 0, -1)), 27 | ], 28 | ) 29 | def test_parse_version(version_str, version_tuple): 30 | assert parse_version(version_str) == version_tuple 31 | 32 | 33 | @pytest.mark.parametrize("invalid_version", ["", "1", "1.2", "crap"]) 34 | def test_parse_version_invalid(invalid_version): 35 | with pytest.raises(ValueError): 36 | assert parse_version(invalid_version) # we require x.y.z versions 37 | 38 | 39 | @pytest.mark.parametrize( 40 | "version_str, version_tuple", 41 | [ 42 | ("1.0.0a1", (1, 0, 0, -4, 1)), 43 | ("1.0.0", (1, 0, 0, -1)), 44 | ("1.0.0a2", (1, 0, 0, -4, 2)), 45 | ("1.0.0b3", (1, 0, 0, -3, 3)), 46 | ("1.0.0rc4", (1, 0, 0, -2, 4)), 47 | ("0.0.0", (0, 0, 0, -1)), 48 | ("0.0.11", (0, 0, 11, -1)), 49 | ("0.11.0", (0, 11, 0, -1)), 50 | ("11.0.0", (11, 0, 0, -1)), 51 | ], 52 | ) 53 | def test_format_version(version_str, version_tuple): 54 | assert format_version(version_tuple) == version_str 55 | -------------------------------------------------------------------------------- /src/borg/version.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def parse_version(version): 5 | """ 6 | Simplistic parser for setuptools_scm versions. 7 | 8 | Supports final versions and alpha ('a'), beta ('b') and release candidate ('rc') versions. 9 | It does not try to parse anything else than that, even if there is more in the version string. 10 | 11 | Output is a version tuple containing integers. It ends with one or two elements that ensure that relational 12 | operators yield correct relations for alpha, beta and rc versions, too. 13 | For final versions the last element is a -1. 14 | For prerelease versions the last two elements are a smaller negative number and the number of e.g. the beta. 15 | 16 | This version format is part of the remote protocol, don‘t change in breaking ways. 17 | """ 18 | version_re = r""" 19 | (?P\d+)\.(?P\d+)\.(?P\d+) # version, e.g. 1.2.33 20 | (?P(?Pa|b|rc)(?P\d+))? # optional prerelease, e.g. a1 or b2 or rc33 21 | """ 22 | m = re.match(version_re, version, re.VERBOSE) 23 | if m is None: 24 | raise ValueError("Invalid version string %s" % version) 25 | gd = m.groupdict() 26 | version = [int(gd["major"]), int(gd["minor"]), int(gd["patch"])] 27 | if m.lastgroup == "prerelease": 28 | p_type = {"a": -4, "b": -3, "rc": -2}[gd["ptype"]] 29 | p_num = int(gd["pnum"]) 30 | version += [p_type, p_num] 31 | else: 32 | version += [-1] 33 | return tuple(version) 34 | 35 | 36 | def format_version(version): 37 | """a reverse for parse_version (obviously without the dropped information)""" 38 | f = [] 39 | it = iter(version) 40 | while True: 41 | part = next(it) 42 | if part >= 0: 43 | f.append(str(part)) 44 | elif part == -1: 45 | break 46 | else: 47 | f[-1] = f[-1] + {-2: "rc", -3: "b", -4: "a"}[part] + str(next(it)) 48 | break 49 | return ".".join(f) 50 | --------------------------------------------------------------------------------