├── .gitmodules ├── logrotate ├── tardis ├── tardisd └── tardisremote ├── tools ├── tcd.sh ├── README ├── cdfile.py ├── bulkInsert.py ├── checksum.py ├── fixSizes.py ├── setToken.py ├── decryptName.py ├── setSRP.py ├── adjustVirtualDevices.py ├── addDigest.py ├── mkKeyBackup.py ├── setDirHashes.py ├── checkDB.py ├── tcd.py ├── encryptFile.py ├── shtardis.py └── decryptFile.py ├── .gitignore ├── lstardis ├── sonic ├── tardiff ├── tardis ├── tardisd ├── tardisd-profile ├── tardisfs ├── regenerate ├── tardisremote ├── init ├── debian │ ├── tardisd.service │ └── tardisremote.service ├── other │ ├── tardisd.service │ └── tardisremote.service ├── tardisd └── tardisremote ├── tardisremote.cfg-template ├── tardisd.cfg-template ├── tardis-profile ├── docs └── source │ ├── configuration.rst │ ├── installation.rst │ ├── index.rst │ ├── components.rst │ ├── sonic.rst │ ├── tardiff.rst │ ├── regenerate.rst │ ├── encryption.rst │ └── lstardis.rst ├── .pre-commit-config.yaml ├── install_service ├── logwatch └── conf │ ├── services │ └── tardisd.conf │ └── logfiles │ └── tardisd.conf ├── TODO ├── requirements.txt ├── types.ignore ├── LICENSE ├── Copyright ├── src └── Tardis │ ├── Converters │ ├── __init__.py │ ├── convertutils.py │ ├── convert9to10.py │ ├── convert14to15.py │ ├── convert6to7.py │ ├── convert19to20.py │ ├── convert20to21.py │ ├── convert17to18.py │ ├── convert5to6.py │ ├── convert10to11.py │ ├── convert15to16.py │ ├── convert3to4.py │ ├── convert11to12.py │ ├── convert12to13.py │ ├── convert8to9.py │ ├── convert16to17.py │ ├── convert18to19.py │ ├── convert7to8.py │ ├── convert13to14.py │ ├── convert4to5.py │ ├── convert21to22.py │ ├── convert2to3.py │ └── convert1to2.py │ ├── ConnIdLogAdapter.py │ ├── log.py │ ├── MultiFormatter.py │ ├── Protocol.py │ ├── ThreadedScheduler.py │ ├── __init__.py │ ├── Rotator.py │ ├── Defaults.py │ ├── Cache.py │ └── Config.py ├── hooks └── debug-statements-thirdparty-hook.py ├── protocol └── setup.py /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logrotate/tardis: -------------------------------------------------------------------------------- 1 | /var/log/tardis/tardis.log { 2 | rotate 7 3 | missingok 4 | notifempty 5 | maxsize 512k 6 | weekly 7 | compress 8 | } 9 | -------------------------------------------------------------------------------- /tools/tcd.sh: -------------------------------------------------------------------------------- 1 | tcd() { 2 | x=`~/dev/stage/Tardis/tools/tcd.py $1` 3 | if [ $? -eq 0 ] 4 | then 5 | cd $x; 6 | fi 7 | echo -e "\e[1m`pwd`" 8 | } 9 | -------------------------------------------------------------------------------- /logrotate/tardisd: -------------------------------------------------------------------------------- 1 | /var/log/tardisd/tardisd.log { 2 | su tardis tardis 3 | rotate 7 4 | missingok 5 | notifempty 6 | maxsize 512k 7 | weekly 8 | compress 9 | } 10 | -------------------------------------------------------------------------------- /tools/README: -------------------------------------------------------------------------------- 1 | WARNING: 2 | This contains various tools to help with either debugging, or doing task related to database version changes which 3 | need to be done manually. These may not be maintained. 4 | -------------------------------------------------------------------------------- /logrotate/tardisremote: -------------------------------------------------------------------------------- 1 | /var/log/tardisd/tardisremote.log { 2 | su tardis tardis 3 | rotate 7 4 | missingok 5 | notifempty 6 | maxsize 512k 7 | weekly 8 | compress 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | cache/ 3 | *.log 4 | *.out 5 | *.bak 6 | *.db 7 | .tardis-excludes 8 | test.cfg 9 | test 10 | mnt 11 | nohup.out* 12 | Tardis.egg-info/ 13 | Tardis_Backup.egg-info/ 14 | build/ 15 | dist/ 16 | cache/ 17 | cache2/ 18 | tmp/ 19 | -------------------------------------------------------------------------------- /lstardis: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import List 8 | 9 | sys.exit(List.main()) 10 | -------------------------------------------------------------------------------- /sonic: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | 8 | from Tardis import Sonic 9 | sys.exit(Sonic.main()) 10 | -------------------------------------------------------------------------------- /tardiff: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import Diff 8 | 9 | sys.exit(Diff.main()) 10 | -------------------------------------------------------------------------------- /tardis: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import Client 8 | 9 | sys.exit(Client.main()) 10 | -------------------------------------------------------------------------------- /tardisd: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import Daemon 8 | 9 | sys.exit(Daemon.main()) 10 | -------------------------------------------------------------------------------- /tardisd-profile: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from Tardis import Daemon 5 | import sys 6 | import cProfile 7 | import time 8 | 9 | name = 'tardisd.profile.' + str(int(time.time())) 10 | 11 | cProfile.run('Daemon.main()', name) 12 | -------------------------------------------------------------------------------- /tardisfs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import TardisFS 8 | 9 | sys.exit(TardisFS.main()) 10 | -------------------------------------------------------------------------------- /regenerate: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import Regenerate 8 | 9 | sys.exit(Regenerate.main()) 10 | -------------------------------------------------------------------------------- /tardisremote: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 6 | sys.path.insert(0, src) 7 | from Tardis import HttpInterface 8 | 9 | sys.exit(HttpInterface.tornado()) 10 | -------------------------------------------------------------------------------- /init/debian/tardisd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Tardis Backup Server 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/tardisd --daemon --config /etc/tardis/tardisd.cfg 7 | ExecStop=/usr/bin/killall tardisd 8 | Type=forking 9 | PIDFile=/var/run/tardisd.pid 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /init/other/tardisd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Tardis Backup Server 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/usr/local/bin/tardisd --daemon --config /etc/tardis/tardisd.cfg 7 | ExecStop=/usr/bin/killall tardisd 8 | Type=forking 9 | PIDFile=/var/run/tardisd.pid 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /tardisremote.cfg-template: -------------------------------------------------------------------------------- 1 | [Tardis] 2 | Port=7430 3 | Database=/mnt/usb/Tardis 4 | LogFile=/var/log/tardisd/tardisremote.log 5 | LogExceptions=True 6 | Verbose=1 7 | Daemon=False 8 | User=tardis 9 | Group=tardis 10 | SSL=False 11 | CertFile=./cert 12 | KeyFile=./key 13 | PidFile=/var/run/tardisremote.pid 14 | Compress=True 15 | AllowSchemaUpgrades=True 16 | -------------------------------------------------------------------------------- /init/debian/tardisremote.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Tardis HTTP File Server 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/tardisremote --daemon --config /etc/tardis/tardisremote.cfg 7 | ExecStop=/usr/bin/killall tardisremote 8 | Type=forking 9 | PIDFile=/var/run/tardisremote.pid 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /init/other/tardisremote.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Tardis HTTP File Server 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/usr/local/bin/tardisremote --daemon --config /etc/tardis/tardisremote.cfg 7 | ExecStop=/usr/bin/killall tardisremote 8 | Type=forking 9 | PIDFile=/var/run/tardisremote.pid 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /tardisd.cfg-template: -------------------------------------------------------------------------------- 1 | [Tardis] 2 | Port=7420 3 | BaseDir=/media/Backup/Tardis 4 | MaxDeltaChain=4 5 | MaxChangePercent=80 6 | AllowNewHosts=True 7 | RequirePassword=False 8 | LogFile=/var/log/tardisd/tardisd.log 9 | Verbose=1 10 | SaveFull=False 11 | SSL=False 12 | CertFile=./cert 13 | KeyFile=./key 14 | Daemon=True 15 | User=tardis 16 | Group=tardis 17 | AllowSchemaUpgrades=True 18 | -------------------------------------------------------------------------------- /tardis-profile: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os.path 5 | import cProfile 6 | import time 7 | 8 | src = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src') 9 | sys.path.insert(0, src) 10 | 11 | from Tardis import Client 12 | 13 | name = 'tardis.profile.' + str(int(time.time())) 14 | 15 | #sys.exit(Client.main()) 16 | cProfile.run('Client.main()', name) 17 | -------------------------------------------------------------------------------- /docs/source/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | Tardis provides multiple levels of configuration: 5 | * Built in defaults 6 | Sensible defaults are built into all of tardis's various programs 7 | * System Defaults 8 | A system level file, usually /etc/tardis/system.defaults, contains default values that will be applied to all users on the system. 9 | * Environment Variables 10 | Users can specify environment variables that override the system defaults 11 | * Configuration files 12 | Configuration files for some tools can override the above defaults 13 | * Command line arguments 14 | 15 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | Installing up the server is relatively straightforward. 4 | * Install librsync, python fuse, and python development 5 | * Fedora: ``{yum|dnf} install librsync libacl-devel libffi-devel python-devel python-fuse python-setuptools gmp snappy-devel openssl-devel`` 6 | * Ubuntu/Debian: ``apt-get install librsync1 libacl1-dev libffi-dev python-dev python-fuse libcurl4-openssl-dev python-setuptools libgmp3-dev libsnappy-dev`` 7 | * Run the python setup: 8 | * ``python setup.py install`` 9 | 10 | This will install the client, tardisd (the backup daemon), tardisremote (the remote access recovery server), and all the command line tools. It will install 11 | initialization scripts for tardisd, and tardisremote, but they won't be enabled. 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - id: check-ast 12 | - id: check-case-conflict 13 | - id: debug-statements 14 | - repo: https://github.com/pycqa/isort 15 | rev: 5.13.2 16 | hooks: 17 | - id: isort 18 | name: isort (python) 19 | - repo: local 20 | hooks: 21 | - id: debug-statements-thirdparty 22 | name: Third Party Debug 23 | language: system 24 | entry: ./hooks/debug-statements-thirdparty-hook.py 25 | types: [python] 26 | -------------------------------------------------------------------------------- /install_service: -------------------------------------------------------------------------------- 1 | #! /usr/bin/bash 2 | 3 | if [ "$EUID" -ne 0 ]; 4 | then echo "Please run as root"; 5 | exit 1 6 | fi 7 | 8 | # Config Files 9 | install -D -t /etc/tardis tardisd.cfg-template types.ignore tardisremote.cfg-template 10 | 11 | # System startup files 12 | #install -t /etc/init.d init/tardisd init/tardisremote 13 | install -t /usr/lib/systemd/system init/tardisd.service init/tardisremote.service 14 | 15 | # Log rotate files 16 | install -t /etc/logrotate.d logrotate/tardisd logrotate/tardisremote 17 | 18 | #logwatch files 19 | install -t /etc/logwatch/conf/services logwatch/conf/services/tardisd.conf 20 | install -t /etc/logwatch/conf/logfiles logwatch/conf/logfiles/tardisd.conf 21 | install -t /etc/logwatch/scripts/services logwatch/scripts/services/tardisd 22 | -------------------------------------------------------------------------------- /logwatch/conf/services/tardisd.conf: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | # $Id: tardisd.conf,v 1.1 2006/05/30 19:04:26 bjorn Exp $ 3 | ########################################################################### 4 | # $Log: tardisd.conf,v $ 5 | # 6 | ########################################################################### 7 | 8 | # You can put comments anywhere you want to. They are effective for the 9 | # rest of the line. 10 | 11 | # this is in the format of = . Whitespace at the beginning 12 | # and end of the lines is removed. Whitespace before and after the = sign 13 | # is removed. Everything is case *insensitive*. 14 | 15 | # Yes = True = On = 1 16 | # No = False = Off = 0 17 | 18 | Title = Tardis Backup 19 | 20 | Detail = High 21 | 22 | LogFile = tardisd 23 | 24 | *ApplyEuroDate 25 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1: Improve communications protocol. Remove name from autoname sockets. Make early messages compliant 2 | with other messages. Route all traffic after initial config through Messages. 3 | 2: Break server code into two components, communications and sockets, and backend server proper. 4 | Drive the server, as much as possible, from the communications side, rather than vice versa. 5 | 3: Directly connect the server to the client via null messages, allowing local integration. 6 | 4: Create SSH/stdin/stdout communications to allow "serverless" remote connections. 7 | 5: Update metadata format to allow better tracking of which files use this data. Improve handling of ranges. 8 | Write emergency recovery code. 9 | 6: ASYNCIO client 10 | 7: Out-of-band send and receive, to different locations/URL's. 11 | 8: End directory chunking. Allow NULL for open ended files. 12 | -------------------------------------------------------------------------------- /logwatch/conf/logfiles/tardisd.conf: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | # $Id: tardisd.conf,v 1.1 2006/05/30 19:04:26 bjorn Exp $ 3 | ########################################################################### 4 | # $Log: tardisd.conf,v $ 5 | # 6 | ########################################################################### 7 | 8 | # You can put comments anywhere you want to. They are effective for the 9 | # rest of the line. 10 | 11 | # this is in the format of = . Whitespace at the beginning 12 | # and end of the lines is removed. Whitespace before and after the = sign 13 | # is removed. Everything is case *insensitive*. 14 | 15 | # Yes = True = On = 1 16 | # No = False = Off = 0 17 | 18 | # Which logfile group... 19 | LogFile = tardisd/tardisd.log 20 | 21 | Archive = tardisd/tardisd.log.* 22 | Archive = tardisd/tardisd.log.*.gz 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==25.3.0 2 | cattrs==25.1.1 3 | certifi==2025.8.3 4 | chardet==5.2.0 5 | charset-normalizer==3.4.3 6 | colorlog==6.9.0 7 | daemonize==2.5.0 8 | Flask==3.1.2 9 | fusepy==3.0.1 10 | idna==3.10 11 | itsdangerous==2.2.0 12 | Jinja2==3.1.6 13 | MarkupSafe==3.0.2 14 | msgpack==1.1.1 15 | parsedatetime==2.6 16 | pid==3.0.4 17 | Pillow==11.3.0 18 | platformdirs==4.3.8 19 | pycparser==2.22 20 | pycryptodomex==3.23.0 21 | pylibacl==0.7.3 22 | pypng==0.20220715.0 23 | python-librsync @ git+https://github.com/koldinger/python-librsync.git 24 | python-magic==0.4.27 25 | python-snappy==0.7.3 26 | python-utils==3.9.1 27 | qrcode==8.2 28 | reportlab==4.4.3 29 | requests==2.32.5 30 | requests-cache==1.2.1 31 | rich @ git+https://github.com/koldinger/rich 32 | srp==1.0.22 33 | StrEnum==0.4.15 34 | termcolor==3.1.0 35 | tornado==6.5.2 36 | typing_extensions==4.14.0 37 | url-normalize==2.2.1 38 | Werkzeug==3.1.4 39 | xattr==1.2.0 40 | zstandard==0.24.0 41 | -------------------------------------------------------------------------------- /types.ignore: -------------------------------------------------------------------------------- 1 | # File formats that can't be compressed easily 2 | # Compressed Video formats 3 | video/mp4 4 | video/x-matroska 5 | video/x-msvideo 6 | video/3gpp 7 | video/x-m4v 8 | 9 | # Compressed Image formats 10 | image/jpeg 11 | image/gif 12 | image/png 13 | 14 | # Compressed Audio 15 | audio/mpeg3 16 | audio/x-flac 17 | audio/mp4 18 | 19 | # Compressed file and archive formats 20 | application/x-bzip2 21 | application/x-gzip 22 | application/x-lzip 23 | application/x-lzma 24 | application/x-lzop 25 | application/x-xz 26 | application/x-zstd 27 | application/x-rpm 28 | application/x-compress 29 | application/x-7z-compressed 30 | application/x-ace-compressed 31 | application/x-rar-compressed 32 | application/x-alz-compressed 33 | application/x-cfs-compressed 34 | application/vnd.android.package-archive 35 | application/x-astrotite-afa 36 | application/x-arj 37 | application/x-b1 38 | application/vnd.ms-cab-compressed 39 | application/x-dar 40 | application/x-stuffit 41 | application/x-stuffitx 42 | application/zip 43 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Tardis documentation master file, created by 2 | sphinx-quickstart on Thu Mar 24 21:09:32 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Tardis's documentation! 7 | ================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | installation 15 | quickstart 16 | components 17 | tardis 18 | regenerate 19 | lstardis 20 | tardiff 21 | sonic 22 | tardisfs 23 | tardisd 24 | tardisremote 25 | configuration 26 | encryption 27 | 28 | Overview 29 | ======== 30 | Tardis is a backup system, similar in concept to Apple's TimeMachine. 31 | 32 | Tardis supports incremental backups, with optional encryption, compression, and file deduplication. Tardis supports multiple methods of recovery, 33 | including a filesystem based system, and command line utility. 34 | 35 | Indices and tables 36 | ================== 37 | 38 | * :ref:`genindex` 39 | * :ref:`modindex` 40 | * :ref:`search` 41 | 42 | -------------------------------------------------------------------------------- /docs/source/components.rst: -------------------------------------------------------------------------------- 1 | Components 2 | ========== 3 | 4 | User Tools 5 | ++++++++++ 6 | 7 | :any:`tardis` 8 | ------------- 9 | The client application which performs the backups 10 | 11 | :any:`regenerate` 12 | ----------------- 13 | A tool for recovering backed up files 14 | 15 | :any:`lstardis` 16 | --------------- 17 | A program to list which versions of files are available 18 | 19 | :any:`tardiff` 20 | ------------- 21 | A program to show differences between different backed up versions of files, and optionally the current version. 22 | 23 | :any:`sonic` 24 | ------------ 25 | An administrative program 26 | 27 | Tardis provides an additional method of regenerating files, namely through a file-system interface: 28 | * :any:`tardisfs` 29 | A FUSE (File System in User Space) filesystem which provides access to all functions. 30 | 31 | In addition, the following two services run on servers which support Tardis. Users will not typically interact with these. 32 | * :any:`tardisd` 33 | A server side daemon to receive the backup data from tardis. 34 | * :any:`tardisremote` 35 | An optional http server to serve backedup datab. 36 | 37 | -------------------------------------------------------------------------------- /docs/source/sonic.rst: -------------------------------------------------------------------------------- 1 | Sonic 2 | ===== 3 | 4 | Sonic is the swiss army knife tool to perform many configuration options on a database. 5 | 6 | create 7 | ------ 8 | 9 | Creates a new backup client in the database, and optionally sets a password. 10 | MUST be run on the backup server. 11 | 12 | setpass 13 | ------- 14 | Adds a password to the backup client. Only works with backup clients that do not have a password currently. 15 | Note, you should not run this on any backup clients for which data is already saved. It can leave the database in an awkward state. 16 | 17 | chpass 18 | ------ 19 | Change the password on a given database. 20 | 21 | keys 22 | ---- 23 | 24 | Manipulate the encryption keys, either extracting them from, or insterting them into the backup server. 25 | 26 | 27 | list 28 | ---- 29 | 30 | Lists the backupsets that exist in the database. 31 | 32 | 33 | info 34 | ---- 35 | [Deprecated] 36 | Prints info about each set in the database. Can be very slow. 37 | 38 | purge 39 | ----- 40 | 41 | Purges out old backup sets, according to a specified schedule. 42 | 43 | orphans 44 | ------- 45 | 46 | Removes any "orphaned" files, ie those that are not currently in use anywhere in any backup sets. 47 | 48 | getconfig 49 | --------- 50 | Prints the contents of configuration variables stored in the database. 51 | 52 | setconfig 53 | --------- 54 | Allows setting configuration values in the database. 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Tardis: A Backup System 2 | Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 3 | kolding@washington.edu 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of the copyright holder nor the 14 | names of its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Copyright: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/Tardis/Converters/__init__.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | # Dummy file to make this show up as a module. 32 | # Nothing to see here. Move along, move along. 33 | -------------------------------------------------------------------------------- /src/Tardis/ConnIdLogAdapter.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | 33 | 34 | class ConnIdLogAdapter(logging.LoggerAdapter): 35 | def process(self, msg, kwargs): 36 | if self.extra and "connid" in self.extra: 37 | msg = f"[{self.extra['connid']}]: {msg}" 38 | return (msg, kwargs) 39 | -------------------------------------------------------------------------------- /src/Tardis/log.py: -------------------------------------------------------------------------------- 1 | 2 | # vi: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import logging 33 | 34 | STATS = logging.INFO + 1 35 | DIRS = logging.INFO - 1 36 | FILES = logging.INFO - 2 37 | MSGS = logging.DEBUG - 1 38 | logging.addLevelName(STATS, "STAT") 39 | logging.addLevelName(FILES, "FILE") 40 | logging.addLevelName(DIRS, "DIR") 41 | logging.addLevelName(MSGS, "MSG") 42 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convertutils.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | def checkVersion(conn, version, log): 33 | s = conn.execute('SELECT Value FROM Config WHERE Key = "SchemaVersion"') 34 | t = s.fetchone() 35 | if int(t[0]) != version: 36 | log.error(f"Invalid database schema version: {t[0]}") 37 | raise Exception(f"Invalid database schema version: {t[0]}") 38 | 39 | def updateVersion(conn, version, log): 40 | # Ugh, make sure the last element is a tuple, otherwise the string will get broken into multiple characters 41 | conn.execute('INSERT OR REPLACE INTO Config (Key, Value) VALUES ("SchemaVersion", ?)', (str(version + 1),) ) 42 | log.info(f"Upgrade to schema version {version + 1} complete") 43 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert9to10.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 9 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN ServerSession TEXT") 43 | 44 | convertutils.updateVersion(conn, version, logger) 45 | conn.commit() 46 | 47 | if __name__ == "__main__": 48 | logging.basicConfig(level=logging.DEBUG) 49 | logger = logging.getLogger('') 50 | 51 | if len(sys.argv) > 1: 52 | db = sys.argv[1] 53 | else: 54 | db = "tardis.db" 55 | 56 | conn = sqlite3.connect(db) 57 | upgrade(conn, logger) 58 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert14to15.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 14 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN CmdLineId INTEGER") 43 | 44 | convertutils.updateVersion(conn, version, logger) 45 | conn.commit() 46 | 47 | if __name__ == "__main__": 48 | logging.basicConfig(level=logging.DEBUG) 49 | logger = logging.getLogger('') 50 | 51 | if len(sys.argv) > 1: 52 | db = sys.argv[1] 53 | else: 54 | db = "tardis.db" 55 | 56 | conn = sqlite3.connect(db) 57 | upgrade(conn, logger) 58 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert6to7.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 6 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN Full INTEGER DEFAULT 0") 43 | 44 | convertutils.updateVersion(conn, version, logger) 45 | conn.commit() 46 | 47 | if __name__ == "__main__": 48 | logging.basicConfig(level=logging.DEBUG) 49 | logger = logging.getLogger('') 50 | 51 | if len(sys.argv) > 1: 52 | db = sys.argv[1] 53 | else: 54 | db = "tardis.db" 55 | 56 | conn = sqlite3.connect(db) 57 | upgrade(conn, logger) 58 | 59 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert19to20.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | import logging 31 | import sqlite3 32 | import sys 33 | 34 | from . import convertutils 35 | 36 | version = 19 37 | 38 | def upgrade(conn, logger): 39 | convertutils.checkVersion(conn, version, logger) 40 | 41 | conn.execute(""" 42 | ALTER TABLE Backups ADD COLUMN Locked INTEGER DEFAULT 0 43 | """) 44 | 45 | convertutils.updateVersion(conn, version, logger) 46 | conn.commit() 47 | 48 | if __name__ == "__main__": 49 | logging.basicConfig(level=logging.DEBUG) 50 | logger = logging.getLogger('') 51 | 52 | if len(sys.argv) > 1: 53 | db = sys.argv[1] 54 | else: 55 | db = "tardis.db" 56 | 57 | conn = sqlite3.connect(db) 58 | upgrade(conn, logger) 59 | -------------------------------------------------------------------------------- /hooks/debug-statements-thirdparty-hook.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import ast 7 | import traceback 8 | from collections.abc import Sequence 9 | from typing import NamedTuple 10 | 11 | DEBUG_STATEMENTS = { 12 | 'icecream', 13 | 'ictruck', 14 | } 15 | 16 | 17 | class Debug(NamedTuple): 18 | line: int 19 | col: int 20 | name: str 21 | reason: str 22 | 23 | 24 | class DebugStatementParser(ast.NodeVisitor): 25 | def __init__(self) -> None: 26 | self.breakpoints: list[Debug] = [] 27 | 28 | def visit_Import(self, node: ast.Import) -> None: 29 | for name in node.names: 30 | if name.name in DEBUG_STATEMENTS: 31 | st = Debug(node.lineno, node.col_offset, name.name, 'imported') 32 | self.breakpoints.append(st) 33 | 34 | def visit_ImportFrom(self, node: ast.ImportFrom) -> None: 35 | if node.module in DEBUG_STATEMENTS: 36 | st = Debug(node.lineno, node.col_offset, node.module, 'imported') 37 | self.breakpoints.append(st) 38 | 39 | def visit_Call(self, node: ast.Call) -> None: 40 | """python3.7+ breakpoint()""" 41 | if isinstance(node.func, ast.Name) and node.func.id == 'breakpoint': 42 | st = Debug(node.lineno, node.col_offset, node.func.id, 'called') 43 | self.breakpoints.append(st) 44 | self.generic_visit(node) 45 | 46 | 47 | def check_file(filename: str) -> int: 48 | try: 49 | with open(filename, 'rb') as f: 50 | ast_obj = ast.parse(f.read(), filename=filename) 51 | except SyntaxError: 52 | print(f'{filename} - Could not parse ast') 53 | print() 54 | print('\t' + traceback.format_exc().replace('\n', '\n\t')) 55 | print() 56 | return 1 57 | 58 | visitor = DebugStatementParser() 59 | visitor.visit(ast_obj) 60 | 61 | for bp in visitor.breakpoints: 62 | print(f'{filename}:{bp.line}:{bp.col}: {bp.name} {bp.reason}') 63 | 64 | return int(bool(visitor.breakpoints)) 65 | 66 | 67 | def main(argv: Sequence[str] | None = None) -> int: 68 | parser = argparse.ArgumentParser() 69 | parser.add_argument('filenames', nargs='*', help='Filenames to run') 70 | args = parser.parse_args(argv) 71 | 72 | retv = 0 73 | for filename in args.filenames: 74 | retv |= check_file(filename) 75 | return retv 76 | 77 | 78 | if __name__ == '__main__': 79 | raise SystemExit(main()) 80 | -------------------------------------------------------------------------------- /protocol: -------------------------------------------------------------------------------- 1 | #### This is hopelessly out of date at this point. 2 | 3 | C->S open 4 | S->C "TARDIS 1.1/SSL" 5 | C->S { "message": "BACKUP", "host": client, "encoding": encoding, "name": name, "priority": priority, "force": force, "version": version, "compress": compress, "protocol": protocol 6 | "autoname": auto, "clienttime": time, ["token": token] } 7 | 8 | S->C "OK uuid" 9 | "FAIL reasonCode string" 10 | 11 | C->S DIR 12 | { message : "DIR", name : "path", inode : number, files : []} 13 | file objects 14 | "name": name in directory, 15 | "nlinks": number of links 16 | "gid": Group ID 17 | "uid": UID 18 | "mode": mode 19 | "inode": inode number 20 | "dir": false, 21 | "size": 7 in bytes 22 | "mtime": 1381188762.094946, 23 | { {"name": "123123", "nlinks": 1, "gid": 1000, "mode": 33204, "mtime": 1381188762.094946, "uid": 1000, "inode": 58589484, "dir": false, "size": 7} 24 | 25 | S->C ACD 26 | {message : "ACKDIR", inode : "inode number", status : "OK|FAIL", 27 | done : [ inodes ] 28 | cksum : [ inodes ] 29 | content : [ inodes ] 30 | delta : [ inodes ] 31 | deleted : [ [name, inode], ...] 32 | } 33 | 34 | C->S CKS 35 | {message : "CKSUM", files : [ { inode : number, md5 : checksum }, ... ] } 36 | CON 37 | { message : "CONTENT", inode : inode number, size : size, encoding : BASE64|BIN } 38 | followed by content 39 | SGR 40 | { message : "SIGREQ", inode : inode number } 41 | 42 | S->C 43 | SIG 44 | { message : "SIGNATURE", inode : inode number, size : size, encoding : BASE64|BIN } 45 | followed by signature 46 | 47 | S->C ACS 48 | { message : "ACKCKSUM", 49 | done : [ inodes ], 50 | delta : [ inodes ], 51 | content : [ inodes ], 52 | } 53 | C->S DEL 54 | { message : "DELTA", inode : inode number, size : size, encoding : BASE64|BIN } 55 | S->C ACKDELTA 56 | 57 | C->S COMPLETE name 58 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert20to21.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | import logging 31 | import sqlite3 32 | import sys 33 | 34 | from . import convertutils 35 | 36 | version = 20 37 | 38 | def upgrade(conn, logger): 39 | convertutils.checkVersion(conn, version, logger) 40 | 41 | conn.execute(""" 42 | CREATE INDEX IF NOT EXISTS FileChksumIndex ON Files(ChecksumID ASC); 43 | """) 44 | 45 | convertutils.updateVersion(conn, version, logger) 46 | conn.commit() 47 | 48 | if __name__ == "__main__": 49 | logging.basicConfig(level=logging.DEBUG) 50 | logger = logging.getLogger('') 51 | 52 | if len(sys.argv) > 1: 53 | db = sys.argv[1] 54 | else: 55 | db = "tardis.db" 56 | 57 | conn = sqlite3.connect(db) 58 | upgrade(conn, logger) 59 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert17to18.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 17 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN Exception TEXT") 43 | conn.execute("ALTER TABLE Backups ADD COLUMN ErrorMsg TEXT") 44 | 45 | convertutils.updateVersion(conn, version, logger) 46 | conn.commit() 47 | 48 | if __name__ == "__main__": 49 | logging.basicConfig(level=logging.DEBUG) 50 | logger = logging.getLogger('') 51 | 52 | if len(sys.argv) > 1: 53 | db = sys.argv[1] 54 | else: 55 | db = "tardis.db" 56 | 57 | conn = sqlite3.connect(db) 58 | upgrade(conn, logger) 59 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert5to6.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 5 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | conn.execute("CREATE INDEX IF NOT EXISTS InodeIndex ON Files(Inode ASC, Device ASC, Parent ASC, ParentDev ASC, FirstSet ASC, LastSet ASC)") 42 | 43 | convertutils.updateVersion(conn, version, logger) 44 | conn.commit() 45 | 46 | if __name__ == "__main__": 47 | logging.basicConfig(level=logging.DEBUG) 48 | logger = logging.getLogger('') 49 | 50 | if len(sys.argv) > 1: 51 | db = sys.argv[1] 52 | else: 53 | db = "tardis.db" 54 | 55 | conn = sqlite3.connect(db) 56 | upgrade(conn, logger) 57 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert10to11.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 10 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute('UPDATE Checksums SET Compressed = "zlib" WHERE Compressed IS 1') 43 | conn.execute('UPDATE Checksums SET Compressed = "none" WHERE Compressed IS 0') 44 | 45 | convertutils.updateVersion(conn, version, logger) 46 | conn.commit() 47 | 48 | if __name__ == "__main__": 49 | logging.basicConfig(level=logging.DEBUG) 50 | logger = logging.getLogger('') 51 | 52 | if len(sys.argv) > 1: 53 | db = sys.argv[1] 54 | else: 55 | db = "tardis.db" 56 | 57 | conn = sqlite3.connect(db) 58 | upgrade(conn, logger) 59 | -------------------------------------------------------------------------------- /docs/source/tardiff.rst: -------------------------------------------------------------------------------- 1 | tardiff 2 | ======= 3 | tardiff can show the differences between different versions of a file which exist in the database, and the current version. 4 | 5 | .. code-block:: 6 | 7 | usage: tardiff [-h] [--database DATABASE] [--dbname DBNAME] [--client CLIENT] 8 | [--backup BACKUP [BACKUP ...]] [--password [PASSWORD] | 9 | --password-file PASSWORDFILE | --password-prog PASSWORDPROG] 10 | [--crypt] [--keys KEYS] [--color] [--unified [UNIFIED] | 11 | --context [CONTEXT] | --ndiff] [--reduce-path [N]] [--recurse] 12 | [--list] [--verbose] [--version] 13 | files [files ...] 14 | 15 | Diff files in Tardis 16 | 17 | positional arguments: 18 | files File to diff 19 | 20 | optional arguments: 21 | -h, --help show this help message and exit 22 | --database DATABASE, -D DATABASE 23 | Path to database directory (Default: /nfs/blueberrypi) 24 | --dbname DBNAME, -N DBNAME 25 | Name of the database file (Default: tardis.db) 26 | --client CLIENT, -C CLIENT 27 | Client to process for (Default: linux.koldware.com) 28 | --backup BACKUP [BACKUP ...], -b BACKUP [BACKUP ...] 29 | Backup set(s) to use (Default: ['Current']) 30 | --password [PASSWORD], -P [PASSWORD] 31 | Encrypt files with this password 32 | --password-file PASSWORDFILE, -F PASSWORDFILE 33 | Read password from file. Can be a URL (HTTP/HTTPS or 34 | FTP) 35 | --password-prog PASSWORDPROG 36 | Use the specified command to generate the password on 37 | stdout 38 | --[no]crypt Are files encrypted, if password is specified. 39 | Default: True 40 | --keys KEYS Load keys from file. 41 | --[no]color Use colors 42 | --unified [UNIFIED], -u [UNIFIED] 43 | Generate unified diff 44 | --context [CONTEXT], -c [CONTEXT] 45 | Generate context diff 46 | --ndiff, -n Generate NDiff style diff 47 | --reduce-path [N], -R [N] 48 | Reduce path by N directories. No value for "smart" 49 | reduction 50 | --[no]recurse Recurse into directories. Default: False 51 | --[no]list Only list files that differ. Do not show diffs. 52 | Default: False 53 | --verbose, -v Increase the verbosity 54 | --version Show the version 55 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert15to16.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 15 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN PurgeTime TEXT") 43 | conn.execute("ALTER TABLE Backups ADD COLUMN ClientEndTime TEXT") 44 | conn.execute("ALTER TABLE Backups ADD COLUMN Vacuumed INTEGER DEFAULT 0") 45 | 46 | convertutils.updateVersion(conn, version, logger) 47 | conn.commit() 48 | 49 | if __name__ == "__main__": 50 | logging.basicConfig(level=logging.DEBUG) 51 | logger = logging.getLogger('') 52 | 53 | if len(sys.argv) > 1: 54 | db = sys.argv[1] 55 | else: 56 | db = "tardis.db" 57 | 58 | conn = sqlite3.connect(db) 59 | upgrade(conn, logger) 60 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert3to4.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 3 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN ClientVersion CHARACTER") 43 | conn.execute("ALTER TABLE Backups ADD COLUMN ServerVersion CHARACTER") 44 | conn.execute("ALTER TABLE Backups ADD COLUMN ClientIP CHARACTER") 45 | 46 | convertutils.updateVersion(conn, version, logger) 47 | conn.commit() 48 | 49 | if __name__ == "__main__": 50 | logging.basicConfig(level=logging.DEBUG) 51 | logger = logging.getLogger('') 52 | 53 | if len(sys.argv) > 1: 54 | db = sys.argv[1] 55 | else: 56 | db = "tardis.db" 57 | 58 | conn = sqlite3.connect(db) 59 | upgrade(conn, logger) 60 | -------------------------------------------------------------------------------- /tools/cdfile.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import logging 34 | 35 | from pathlib import Path 36 | 37 | from Tardis import CacheDir, Util 38 | 39 | logging.basicConfig() 40 | 41 | 42 | parser = argparse.ArgumentParser(description="Generate file paths in a cache dir directory", add_help=True) 43 | parser.add_argument('--base', '-b', dest='base', default='.', type=Path, help='Base CacheDir directory') 44 | parser.add_argument('--dir', '-d', dest='dir', default=False, action='store_true', help='Directory portion only') 45 | parser.add_argument('files', nargs='*', help='List of files to print') 46 | 47 | Util.addGenCompletions(parser) 48 | 49 | args = parser.parse_args() 50 | 51 | c = CacheDir.CacheDir(args.base, create=False) 52 | 53 | for i in args.files: 54 | if args.dir: 55 | print(c.dirPath(i)) 56 | else: 57 | print(c.path(i)) 58 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert11to12.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 11 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | ### 42 | # Do this all 'manually', because a SQL only version seems to throw SQLite3 into an infinite loop. 43 | # Would be much cleaner if UPDATE supported an AS keyword, like SELECT does. 44 | 45 | conn.execute("PRAGMA journal_mode=truncate") 46 | 47 | convertutils.updateVersion(conn, version, logger) 48 | conn.commit() 49 | 50 | if __name__ == "__main__": 51 | logging.basicConfig(level=logging.DEBUG) 52 | logger = logging.getLogger('') 53 | 54 | if len(sys.argv) > 1: 55 | db = sys.argv[1] 56 | else: 57 | db = "tardis.db" 58 | 59 | conn = sqlite3.connect(db) 60 | upgrade(conn, logger) 61 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert12to13.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 12 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | ### 42 | # Do this all 'manually', because a SQL only version seems to throw SQLite3 into an infinite loop. 43 | # Would be much cleaner if UPDATE supported an AS keyword, like SELECT does. 44 | 45 | conn.execute("ALTER TABLE Backups ADD COLUMN SchemaVersion INTEGER") 46 | 47 | convertutils.updateVersion(conn, version, logger) 48 | conn.commit() 49 | 50 | if __name__ == "__main__": 51 | logging.basicConfig(level=logging.DEBUG) 52 | logger = logging.getLogger('') 53 | 54 | if len(sys.argv) > 1: 55 | db = sys.argv[1] 56 | else: 57 | db = "tardis.db" 58 | 59 | conn = sqlite3.connect(db) 60 | upgrade(conn, logger) 61 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert8to9.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 8 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN ClientConfigId INTEGER") 43 | conn.execute( 44 | """ 45 | CREATE TABLE IF NOT EXISTS ClientConfig ( 46 | ClientConfigID INTEGER PRIMARY KEY AUTOINCREMENT, 47 | ClientConfig TEXT 48 | ) 49 | """ 50 | ) 51 | 52 | convertutils.updateVersion(conn, version, logger) 53 | conn.commit() 54 | 55 | 56 | if __name__ == "__main__": 57 | logging.basicConfig(level=logging.DEBUG) 58 | logger = logging.getLogger('') 59 | 60 | if len(sys.argv) > 1: 61 | db = sys.argv[1] 62 | else: 63 | db = "tardis.db" 64 | 65 | conn = sqlite3.connect(db) 66 | upgrade(conn, logger) 67 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert16to17.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 16 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | rs = conn.execute('SELECT Value FROM Config WHERE Key = "ContentKey"') 43 | if rs.fetchone(): 44 | conn.execute('INSERT OR REPLACE INTO Config (Key, Value) VALUES ("CryptoScheme", 1)') 45 | else: 46 | conn.execute('INSERT OR REPLACE INTO Config (Key, Value) VALUES ("CryptoScheme", 0)') 47 | 48 | convertutils.updateVersion(conn, version, logger) 49 | conn.commit() 50 | 51 | if __name__ == "__main__": 52 | logging.basicConfig(level=logging.DEBUG) 53 | logger = logging.getLogger('') 54 | 55 | if len(sys.argv) > 1: 56 | db = sys.argv[1] 57 | else: 58 | db = "tardis.db" 59 | 60 | conn = sqlite3.connect(db) 61 | upgrade(conn, logger) 62 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert18to19.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 18 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute(""" 43 | CREATE TABLE IF NOT EXISTS Tags ( 44 | TagId INTEGER PRIMARY KEY AUTOINCREMENT, 45 | BackupSet INTEGER NOT NULL, 46 | NameId INTEGER UNIQUE NOT NULL, 47 | FOREIGN KEY(BackupSet) REFERENCES Backups(BackupSet), 48 | FOREIGN KEY(NameId) REFERENCES Names(NameId) 49 | ); 50 | """) 51 | 52 | convertutils.updateVersion(conn, version, logger) 53 | conn.commit() 54 | 55 | if __name__ == "__main__": 56 | logging.basicConfig(level=logging.DEBUG) 57 | logger = logging.getLogger('') 58 | 59 | if len(sys.argv) > 1: 60 | db = sys.argv[1] 61 | else: 62 | db = "tardis.db" 63 | 64 | conn = sqlite3.connect(db) 65 | upgrade(conn, logger) 66 | -------------------------------------------------------------------------------- /src/Tardis/MultiFormatter.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | 33 | 34 | class MultiFormatter(logging.Formatter): 35 | """ 36 | A class to allow different logging levels to have different logging formats 37 | """ 38 | def __init__(self, default_fmt = "%(levelname)s: %(message)s", formats=None, baseclass=logging.Formatter, **kwargs): 39 | """ 40 | default_fmt: A string containing the default format to use 41 | formats: A dict containing loggingLevel -> format string for that level 42 | baseclass: Base formatter for logging 43 | """ 44 | super().__init__(default_fmt) 45 | self.formatters = {} 46 | if formats is None: 47 | formats = {} 48 | for i in formats: 49 | self.formatters[i] = baseclass(formats[i], **kwargs) 50 | self.defaultFormatter = baseclass(default_fmt, **kwargs) 51 | 52 | def format(self, record): 53 | return self.formatters.setdefault(record.levelno, self.defaultFormatter).format(record) 54 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert7to8.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 7 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Backups ADD COLUMN FilesFull INTEGER") 43 | conn.execute("ALTER TABLE Backups ADD COLUMN FilesDelta INTEGER") 44 | conn.execute("ALTER TABLE Backups ADD COLUMN BytesReceived INTEGER") 45 | 46 | conn.execute("ALTER TABLE CheckSums ADD COLUMN Encrypted INTEGER") 47 | 48 | conn.execute("UPDATE CheckSums SET Encrypted = 1 WHERE InitVector IS NOT NULL") 49 | conn.execute("UPDATE CheckSums SET Encrypted = 0 WHERE InitVector IS NULL") 50 | 51 | convertutils.updateVersion(conn, version, logger) 52 | conn.commit() 53 | 54 | if __name__ == "__main__": 55 | logging.basicConfig(level=logging.DEBUG) 56 | logger = logging.getLogger('') 57 | 58 | if len(sys.argv) > 1: 59 | db = sys.argv[1] 60 | else: 61 | db = "tardis.db" 62 | 63 | conn = sqlite3.connect(db) 64 | upgrade(conn, logger) 65 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert13to14.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 13 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Config RENAME TO _Config_Orig") 43 | conn.execute(""" 44 | CREATE TABLE IF NOT EXISTS Config ( 45 | Key TEXT PRIMARY KEY, 46 | Value TEXT NOT NULL, 47 | Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP 48 | ); 49 | """) 50 | conn.execute("INSERT INTO Config (Key, Value, Timestamp) " + 51 | " SELECT Key, Value, NULL FROM _Config_Orig") 52 | 53 | conn.execute("DROP TABLE _Config_Orig") 54 | 55 | convertutils.updateVersion(conn, version, logger) 56 | conn.commit() 57 | 58 | if __name__ == "__main__": 59 | logging.basicConfig(level=logging.DEBUG) 60 | logger = logging.getLogger('') 61 | 62 | if len(sys.argv) > 1: 63 | db = sys.argv[1] 64 | else: 65 | db = "tardis.db" 66 | 67 | conn = sqlite3.connect(db) 68 | upgrade(conn, logger) 69 | -------------------------------------------------------------------------------- /src/Tardis/Protocol.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | from enum import auto 32 | 33 | from strenum import UppercaseStrEnum 34 | 35 | 36 | class Commands(UppercaseStrEnum): 37 | BACKUP = auto() 38 | DIR = auto() 39 | DHSH = auto() 40 | SGR = auto() 41 | SGS = auto() 42 | SIG = auto() 43 | DEL = auto() 44 | CON = auto() 45 | CKS = auto() 46 | CLN = auto() 47 | # BATCH = auto() 48 | PRG = auto() 49 | CLICONFIG = auto() 50 | COMMANDLINE = auto() 51 | META = auto() 52 | METADATA = auto() 53 | SETKEYS = auto() 54 | AUTH1 = auto() 55 | AUTH2 = auto() 56 | DONE = auto() 57 | 58 | class Responses(UppercaseStrEnum): 59 | ACKDIR = auto() 60 | ACKCLN = auto() 61 | ACKPRG = auto() 62 | ACKSUM = auto() 63 | ACKMETA = auto() 64 | ACKMETADATA = auto() 65 | ACKDHSH = auto() 66 | ACKCLICONFIG = auto() 67 | ACKCMDLN = auto() 68 | ACKDONE = auto() 69 | # ACKBTCH = auto() 70 | ACKBACKUP = "INIT" 71 | ACKSETKEYS = auto() 72 | ACKCON = auto() 73 | ACKDEL = auto() 74 | ACKSIG = auto() 75 | ACKSGR = "SIG" 76 | NEEDKEYS = auto() 77 | AUTH = auto() 78 | -------------------------------------------------------------------------------- /src/Tardis/ThreadedScheduler.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import sched 32 | import threading 33 | import time 34 | 35 | 36 | class ThreadedScheduler(sched.scheduler): 37 | def __init__(self, timefunc=time.monotonic, delayfunc=time.sleep): 38 | super().__init__(timefunc, delayfunc) 39 | 40 | def start(self, name="EventScheduler"): 41 | self.thread = threading.Thread(name=name, target=self.run) 42 | self.thread.setDaemon(True) 43 | self.thread.start() 44 | 45 | def shutdown(self): 46 | while not self.empty(): 47 | try: 48 | self.cancel(self.queue[0]) 49 | except ValueError: 50 | pass 51 | 52 | if __name__ == "__main__": 53 | def print_time(a="default"): 54 | print("From print_time", time.time(), a) 55 | 56 | x = ThreadedScheduler() 57 | print("Starting") 58 | x.enter(10, 1, print_time, (10,)) 59 | x.start() 60 | x.enter(5, 2, print_time, argument=("positional",)) 61 | x.enter(5, 1, print_time, (5,)) 62 | x.enter(15, 3, print_time, (15,)) 63 | x.enter(25, 3, print_time, (25,)) 64 | x.enter(30, 3, print_time, (30,)) 65 | time.sleep(20) 66 | -------------------------------------------------------------------------------- /src/Tardis/__init__.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import os 32 | import subprocess 33 | import sys 34 | 35 | __version__ = "1.9.5" 36 | v = sys.version_info 37 | 38 | __buildversion__ = "" 39 | __pythonversion__ = f" Python {v.major}.{v.minor}.{v.micro}" 40 | __versionstring__ = f"{__version__} ({__pythonversion__})" 41 | 42 | try: 43 | parentDir = os.path.dirname(os.path.realpath(__file__)) 44 | versionFile = os.path.join(parentDir, "tardisversion") 45 | __buildversion__ = str(open(versionFile).readline()).strip() 46 | except Exception: 47 | try: 48 | __buildversion__ = str(subprocess.check_output(["git", "describe", "--dirty", "--tags", "--always"], stderr=subprocess.STDOUT).strip(), "utf-8") 49 | except subprocess.CalledProcessError: 50 | pass 51 | 52 | if __buildversion__: 53 | __versionstring__ = __version__ + " (" + str(__buildversion__) + __pythonversion__ + ")" 54 | 55 | def check_features(): 56 | xattr_pkg = "xattr" 57 | acl_pkg = "pylibacl" 58 | os_info = os.uname() 59 | if os_info[0] == "Linux": 60 | return [xattr_pkg, acl_pkg] 61 | if os_info[0] == "Darwin": 62 | return [xattr_pkg] 63 | return [] 64 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert4to5.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import sqlite3 33 | import sys 34 | 35 | from . import convertutils 36 | 37 | version = 4 38 | 39 | def upgrade(conn, logger): 40 | convertutils.checkVersion(conn, version, logger) 41 | 42 | conn.execute("ALTER TABLE Checksums ADD COLUMN Added INTEGER") 43 | conn.execute("ALTER TABLE Checksums ADD COLUMN IsFile INTEGER") # Old version only uses checksums for files. 44 | 45 | conn.execute("UPDATE Checksums SET IsFile = 1") 46 | 47 | # This can be really slow. Only enable it if you really want it. 48 | # conn.execute("UPDATE Checksums SET Added = (SELECT MIN(FirstSet) FROM Files WHERE Files.ChecksumID = Checksums.ChecksumID OR Files.XattrID = Checksums.ChecksumID OR Files.AclID = Checksums.ChecksumId)") 49 | 50 | convertutils.updateVersion(conn, version, logger) 51 | conn.commit() 52 | 53 | if __name__ == "__main__": 54 | logging.basicConfig(level=logging.DEBUG) 55 | logger = logging.getLogger('') 56 | 57 | if len(sys.argv) > 1: 58 | db = sys.argv[1] 59 | else: 60 | db = "tardis.db" 61 | 62 | conn = sqlite3.connect(db) 63 | upgrade(conn, logger) 64 | -------------------------------------------------------------------------------- /tools/bulkInsert.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import logging 34 | import os 35 | import os.path 36 | 37 | from Tardis import CacheDir, Util 38 | 39 | logging.basicConfig() 40 | 41 | 42 | parser = argparse.ArgumentParser(description="Insert files into a cache dir", add_help=True) 43 | parser.add_argument('--base', '-b', dest='base', default='.', help='Base CacheDir directory') 44 | parser.add_argument('--link', '-l', dest='link', default=False, action='store_true', help='link instead of move') 45 | parser.add_argument('--delete', '-d', dest='delete', default=False, action='store_true', help='Delete files instead of adding') 46 | parser.add_argument('files', nargs='*', help='List of files to print') 47 | 48 | Util.addGenCompletions(parser) 49 | 50 | args = parser.parse_args() 51 | 52 | c = CacheDir.CacheDir(args.base, create=False) 53 | 54 | for i in args.files: 55 | name = os.path.basename(i) 56 | try: 57 | if args.delete: 58 | print(f"Removing {name}") 59 | c.remove(name) 60 | else: 61 | print(f"{i} => {c.path(name)}") 62 | c.insert(i, name, link=args.link) 63 | except Exception as e: 64 | print(e) 65 | -------------------------------------------------------------------------------- /tools/checksum.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import logging 34 | 35 | from Tardis import Util, Config 36 | 37 | def computeChecksum(name, crypto): 38 | with open(name, "rb") as file: 39 | data = file.read() 40 | hsh = crypto.getHash() 41 | hsh.update(data) 42 | return hsh.hexdigest() 43 | 44 | def process_args(): 45 | parser = argparse.ArgumentParser(description="Generate checksums from files for a specific job", add_help=True) 46 | 47 | (_, remaining) = Config.parseConfigOptions(parser) 48 | 49 | Config.addCommonOptions(parser) 50 | Config.addPasswordOptions(parser) 51 | parser.add_argument('files', nargs='*', help='List of files to print') 52 | 53 | Util.addGenCompletions(parser) 54 | 55 | return parser.parse_args(remaining) 56 | 57 | def main(): 58 | args = process_args() 59 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog, f"Password for {args.client}") 60 | logging.basicConfig() 61 | 62 | _, _, crypto, _ = Util.setupDataConnection(args.repo, password, args.keys) 63 | 64 | maxlen = max(map(len, args.files)) 65 | 66 | for i in args.files: 67 | hsh = computeChecksum(i, crypto) 68 | print(f"{i:{maxlen}} :\t{hsh}") 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /init/tardisd: -------------------------------------------------------------------------------- 1 | ### BEGIN INIT INFO 2 | # Provides: tardisd 3 | # Required-Start: networking 4 | # Required-Stop: networking 5 | # Default-Start: 2 3 4 5 6 | # Default-Stop: 0 1 6 7 | # Short-Description: Tardis backup daemon 8 | # Description: This fires up the tardis backup daemon 9 | ### END INIT INFO 10 | 11 | # Using the lsb functions to perform the operations. 12 | . /lib/lsb/init-functions 13 | 14 | # Process name ( For display ) 15 | NAME=tardisd 16 | 17 | # Daemon name, where is the actual executable 18 | DAEMON=/usr/local/bin/tardisd 19 | 20 | # pid file for the daemon 21 | PIDFILE=/var/run/tardisd.pid 22 | 23 | # If the daemon is not there, then exit. 24 | test -x $DAEMON || exit 5 25 | 26 | case $1 in 27 | start) 28 | # Checked the PID file exists and check the actual status of process 29 | if [ -e $PIDFILE ]; then 30 | status_of_proc -p $PIDFILE $DAEMON "$NAME process" && status="0" || status="$?" 31 | # If the status is SUCCESS then don't need to start again. 32 | if [ $status = "0" ]; then 33 | exit # Exit 34 | fi 35 | fi 36 | # Start the daemon. 37 | log_daemon_msg "Starting the process" "$NAME" 38 | # Start the daemon with the help of start-stop-daemon 39 | # Log the message appropriately 40 | if start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON ; then 41 | log_end_msg 0 42 | else 43 | log_end_msg 1 44 | fi 45 | ;; 46 | stop) 47 | # Stop the daemon. 48 | if [ -e $PIDFILE ]; then 49 | status_of_proc -p $PIDFILE $DAEMON "Stoppping the $NAME process" && status="0" || status="$?" 50 | if [ "$status" = 0 ]; then 51 | start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE 52 | /bin/rm -rf $PIDFILE 53 | fi 54 | else 55 | log_daemon_msg "$NAME process is not running" 56 | log_end_msg 0 57 | fi 58 | ;; 59 | restart) 60 | # Restart the daemon. 61 | $0 stop && sleep 2 && $0 start 62 | ;; 63 | status) 64 | # Check the status of the process. 65 | if [ -e $PIDFILE ]; then 66 | status_of_proc -p $PIDFILE $DAEMON "$NAME process" && exit 0 || exit $? 67 | else 68 | log_daemon_msg "$NAME Process is not running" 69 | log_end_msg 0 70 | fi 71 | ;; 72 | reload) 73 | # Reload the process. Basically sending some signal to a daemon to reload 74 | # it configurations. 75 | if [ -e $PIDFILE ]; then 76 | start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $NAME 77 | log_success_msg "$NAME process reloaded successfully" 78 | else 79 | log_failure_msg "$PIDFILE does not exists" 80 | fi 81 | ;; 82 | *) 83 | # For invalid arguments, print the usage message. 84 | echo "Usage: $0 {start|stop|restart|reload|status}" 85 | exit 2 86 | ;; 87 | esac 88 | -------------------------------------------------------------------------------- /init/tardisremote: -------------------------------------------------------------------------------- 1 | ### BEGIN INIT INFO 2 | # Provides: tardisremote 3 | # Required-Start: networking 4 | # Required-Stop: networking 5 | # Default-Start: 2 3 4 5 6 | # Default-Stop: 0 1 6 7 | # Short-Description: Tardis HTTP File Server 8 | # Description: This fires up the tardis HTTP File Server 9 | ### END INIT INFO 10 | 11 | # Using the lsb functions to perform the operations. 12 | . /lib/lsb/init-functions 13 | 14 | # Process name ( For display ) 15 | NAME=tardisremote 16 | 17 | # Daemon name, where is the actual executable 18 | DAEMON=/usr/local/bin/tardisremote 19 | 20 | # pid file for the daemon 21 | PIDFILE=/var/run/tardisremote.pid 22 | 23 | # If the daemon is not there, then exit. 24 | test -x $DAEMON || exit 5 25 | 26 | case $1 in 27 | start) 28 | # Checked the PID file exists and check the actual status of process 29 | if [ -e $PIDFILE ]; then 30 | status_of_proc -p $PIDFILE $DAEMON "$NAME process" && status="0" || status="$?" 31 | # If the status is SUCCESS then don't need to start again. 32 | if [ $status = "0" ]; then 33 | exit # Exit 34 | fi 35 | fi 36 | # Start the daemon. 37 | log_daemon_msg "Starting the process" "$NAME" 38 | # Start the daemon with the help of start-stop-daemon 39 | # Log the message appropriately 40 | if start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON ; then 41 | log_end_msg 0 42 | else 43 | log_end_msg 1 44 | fi 45 | ;; 46 | stop) 47 | # Stop the daemon. 48 | if [ -e $PIDFILE ]; then 49 | status_of_proc -p $PIDFILE $DAEMON "Stoppping the $NAME process" && status="0" || status="$?" 50 | if [ "$status" = 0 ]; then 51 | start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE 52 | /bin/rm -rf $PIDFILE 53 | fi 54 | else 55 | log_daemon_msg "$NAME process is not running" 56 | log_end_msg 0 57 | fi 58 | ;; 59 | restart) 60 | # Restart the daemon. 61 | $0 stop && sleep 2 && $0 start 62 | ;; 63 | status) 64 | # Check the status of the process. 65 | if [ -e $PIDFILE ]; then 66 | status_of_proc -p $PIDFILE $DAEMON "$NAME process" && exit 0 || exit $? 67 | else 68 | log_daemon_msg "$NAME Process is not running" 69 | log_end_msg 0 70 | fi 71 | ;; 72 | reload) 73 | # Reload the process. Basically sending some signal to a daemon to reload 74 | # it configurations. 75 | if [ -e $PIDFILE ]; then 76 | start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $NAME 77 | log_success_msg "$NAME process reloaded successfully" 78 | else 79 | log_failure_msg "$PIDFILE does not exists" 80 | fi 81 | ;; 82 | *) 83 | # For invalid arguments, print the usage message. 84 | echo "Usage: $0 {start|stop|restart|reload|status}" 85 | exit 2 86 | ;; 87 | esac 88 | -------------------------------------------------------------------------------- /src/Tardis/Rotator.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import gzip 32 | import logging 33 | import shutil 34 | import time 35 | from pathlib import Path 36 | 37 | 38 | class Rotator: 39 | def __init__(self, rotations=5, compress=32 * 1024): 40 | self.logger = logging.getLogger("Rotator") 41 | self.rotations = rotations 42 | self.compress = compress 43 | 44 | def backup(self, name: Path): 45 | if name.exists(): 46 | with name.open("rb") as infile: 47 | newname = Path(f"{name.name}.{time.strftime('%Y%m%d-%H%M%S')}") 48 | stat = name.stat() 49 | if self.compress and stat.st_size >= self.compress: 50 | newname = Path(f"{newname}.gz") 51 | self.logger.debug("Compressing %s to %s", name, newname) 52 | outfile = gzip.open(newname, "wb") 53 | else: 54 | self.logger.debug("Copying %s to %s", name, newname) 55 | outfile = newname.open("wb") 56 | try: 57 | shutil.copyfileobj(infile, outfile) 58 | finally: 59 | outfile.close() 60 | 61 | def rotate(self, path: Path): 62 | path = path.absolute() 63 | d = path.parent 64 | f = path.name 65 | prefix = f + "." 66 | files = [i for i in d.iterdir() if i.name.startswith(prefix)] 67 | self.logger.debug("Rotating %d files: %s", len(files), str(files)) 68 | files = sorted(files, reverse=True) 69 | toDelete = files[self.rotations:] 70 | for i in toDelete: 71 | name = d / i 72 | self.logger.debug("Deleting %s", name) 73 | name.unlink() 74 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert21to22.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | import logging 31 | import sqlite3 32 | import sys 33 | 34 | from . import convertutils 35 | 36 | version = 21 37 | 38 | def upgrade(conn, logger): 39 | convertutils.checkVersion(conn, version, logger) 40 | 41 | conn.execute( 42 | """ 43 | CREATE TABLE IF NOT EXISTS Users ( 44 | UserId INTEGER PRIMARY KEY AUTOINCREMENT, 45 | NameId INTEGER REFERENCES Names(NameId) 46 | ) 47 | """ 48 | ) 49 | 50 | conn.execute( 51 | """ 52 | CREATE TABLE IF NOT EXISTS Groups ( 53 | GroupId INTEGER PRIMARY KEY AUTOINCREMENT, 54 | NameId INTEGER REFERENCES Names(NameId) 55 | ) 56 | """ 57 | ) 58 | 59 | conn.execute("ALTER TABLE Files ADD COLUMN UserID INTEGER;") 60 | conn.execute("ALTER TABLE Files ADD COLUMN GroupID INTEGER;") 61 | 62 | # Here we put the name insertion but it really doesn't work, because we really want to insert 63 | # encrypted names. 64 | 65 | conn.execute("INSERT INTO Users (NameId) SELECT DISTINCT Uid FROM Files;") 66 | conn.execute("INSERT INTO Groups (NameId) SELECT DISTINCT Gid FROM Files;") 67 | 68 | conn.execute("UPDATE Files SET UserID = (SELECT UserID FROM Users WHERE Users.NameId = Files.UID);") 69 | conn.execute("UPDATE Files SET GroupID = (SELECT GroupID FROM Groups WHERE Groups.NameId = Files.GID);") 70 | 71 | convertutils.updateVersion(conn, version, logger) 72 | conn.commit() 73 | 74 | if __name__ == "__main__": 75 | logging.basicConfig(level=logging.DEBUG) 76 | logger = logging.getLogger('') 77 | 78 | if len(sys.argv) > 1: 79 | db = sys.argv[1] 80 | else: 81 | db = "tardis.db" 82 | 83 | conn = sqlite3.connect(db) 84 | upgrade(conn, logger) 85 | -------------------------------------------------------------------------------- /tools/fixSizes.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import sys 33 | import argparse 34 | import json 35 | 36 | import Tardis 37 | from Tardis import Util 38 | from Tardis import Config 39 | 40 | args: argparse.Namespace 41 | 42 | def processArgs(): 43 | parser = argparse.ArgumentParser(description='Check contents of the DB against the file system', fromfile_prefix_chars='@', formatter_class=Util.HelpFormatter, add_help=False) 44 | 45 | (_, remaining) = Config.parseConfigOptions(parser) 46 | 47 | Config.addCommonOptions(parser) 48 | Config.addPasswordOptions(parser) 49 | 50 | parser.add_argument('--input', '-i', default=None, dest='input', required=True, type=argparse.FileType('r'), help='Input file') 51 | 52 | #parser.add_argument('--verbose', '-v', action='count', default=0, dest='verbose', help='Increase the verbosity') 53 | parser.add_argument('--version', action='version', version='%(prog)s ' + Tardis.__versionstring__, help='Show the version') 54 | parser.add_argument('--help', '-h', action='help') 55 | 56 | return parser.parse_args(remaining) 57 | 58 | def main(): 59 | global args 60 | args = processArgs() 61 | 62 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog, prompt="Password for %s: " % (args.client)) 63 | tardis, _, _, _ = Util.setupDataConnection(args.repo, password, args.keys) 64 | 65 | allData = json.load(args.input) 66 | sizes = allData['size'] 67 | for i in sizes: 68 | checksum = i[0] 69 | actualSize = i[2] 70 | tardis.conn.execute("UPDATE Checksums SET DiskSize = :size WHERE Checksum = :cksum", {'size': actualSize, 'cksum': checksum }) 71 | 72 | tardis.conn.commit() 73 | 74 | return 0 75 | 76 | if __name__ == "__main__": 77 | sys.exit(main()) 78 | -------------------------------------------------------------------------------- /docs/source/regenerate.rst: -------------------------------------------------------------------------------- 1 | Regenerate 2 | ========== 3 | The regenerate application recovers versions of files from the backups. 4 | 5 | Note, you can also use the :any:`tardisfs` file system to make data avaible in a file system like view. 6 | 7 |
 8 | usage: regenerate [-h] [--output OUTPUT] [--checksum] [--database DATABASE]
 9 |                   [--dbname DBNAME] [--client CLIENT]
10 |                   [--backup BACKUP | --date DATE | --last]
11 |                   [--password [PASSWORD] | --password-file PASSWORDFILE |
12 |                   --password-prog PASSWORDPROG] [--crypt] [--keys KEYS]
13 |                   [--recurse] [--authenticate]
14 |                   [--authfail-action {keep,rename,delete}] [--reduce-path [N]]
15 |                   [--set-times] [--set-perms] [--set-attrs] [--set-acl]
16 |                   [--overwrite [{always,newer,older,never}]] [--hardlinks]
17 |                   [--verbose] [--version]
18 |                   files [files ...]
19 | 
20 | Regenerate a Tardis backed file
21 | 
22 | positional arguments:
23 |   files                 List of files to regenerate
24 | 
25 | optional arguments:
26 |   -h, --help            show this help message and exit
27 |   --output OUTPUT, -o OUTPUT
28 |                         Output file
29 |   --checksum, -c        Use checksum instead of filename
30 |   --database DATABASE, -D DATABASE
31 |                         Path to database directory (Default: /nfs/blueberrypi)
32 |   --dbname DBNAME, -N DBNAME
33 |                         Name of the database file (Default: tardis.db)
34 |   --client CLIENT, -C CLIENT
35 |                         Client to process for (Default: linux.koldware.com)
36 |   --backup BACKUP, -b BACKUP
37 |                         Backup set to use
38 |   --date DATE, -d DATE  Regenerate as of date
39 |   --last, -l            Regenerate the most recent version of the file
40 |   --password [PASSWORD], -P [PASSWORD]
41 |                         Encrypt files with this password
42 |   --password-file PASSWORDFILE, -F PASSWORDFILE
43 |                         Read password from file. Can be a URL (HTTP/HTTPS or
44 |                         FTP)
45 |   --password-prog PASSWORDPROG
46 |                         Use the specified command to generate the password on
47 |                         stdout
48 |   --[no]crypt           Are files encyrpted, if password is specified.
49 |                         Default: True
50 |   --keys KEYS           Load keys from file.
51 |   --[no]recurse         Recurse directory trees. Default: True
52 |   --[no]authenticate    Authenticate files while regenerating them. Default:
53 |                         True
54 |   --authfail-action {keep,rename,delete}
55 |                         Action to take for files that do not authenticate.
56 |                         Default: rename
57 |   --reduce-path [N], -R [N]
58 |                         Reduce path by N directories. No value for "smart"
59 |                         reduction
60 |   --[no]set-times       Set file times to match original file. Default: True
61 |   --[no]set-perms       Set file owner and permisions to match original file.
62 |                         Default: True
63 |   --[no]set-attrs       Set file extended attributes to match original file.
64 |                         May only set attributes in user space. Default: True
65 |   --[no]set-acl         Set file access control lists to match the original
66 |                         file. Default: True
67 |   --overwrite [{always,newer,older,never}], -O [{always,newer,older,never}]
68 |                         Mode for handling existing files. Default: never
69 |   --[no]hardlinks       Create hardlinks of multiple copies of same inode
70 |                         created. Default: True
71 |   --verbose, -v         Increase the verbosity
72 |   --version             Show the version
73 | 
74 | -------------------------------------------------------------------------------- /src/Tardis/Defaults.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import configparser 32 | import os 33 | import os.path 34 | 35 | SECTION = "Tardis" 36 | 37 | _defaults = { 38 | "TARDIS_BASEDIR" : "/srv/tardis", 39 | "TARDIS_REPO" : "", 40 | "TARDIS_CONFIG" : "", 41 | "TARDIS_JOB" : SECTION, 42 | "TARDIS_EXCLUDES" : ".tardis-excludes", 43 | "TARDIS_LOCAL_EXCLUDES" : ".tardis-local-excludes", 44 | "TARDIS_GLOBAL_EXCLUDES": "/etc/tardis/excludes", 45 | "TARDIS_SKIP" : ".tardis-skip", 46 | "TARDIS_TIMEOUT" : "300", 47 | "TARDIS_DAEMON_CONFIG" : "", 48 | "TARDIS_LOCAL_CONFIG" : "", 49 | "TARDIS_PWTIMEOUT" : "120", 50 | "TARDIS_PIDFILE" : "/var/run/tardisd.pid", 51 | "TARDIS_JOURNAL" : "tardis.journal.gz", 52 | "TARDIS_SEND_CONFIG" : "true", 53 | "TARDIS_PORT" : "7420", 54 | "TARDIS_REMOTE_PORT" : "7430", 55 | "TARDIS_REMOTE_CONFIG" : "/etc/tardis/tardisremote.cfg", 56 | "TARDIS_REMOTE_PIDFILE" : "/var/run/tardisremote.pid", 57 | "TARDIS_LS_COLORS" : "gone=yellow:changed=cyan:full=cyan,,bold:moved=blue:header=green:name=yellow:details=white:error=red,,bold:default=white", 58 | "TARDIS_NOCOMPRESS" : "", 59 | "TARDIS_RECENT_SET" : "Current", 60 | "TARDIS_DEFAULTS" : "/etc/tardis/system.defaults", 61 | "TARDIS_PWFILE" : "", 62 | "TARDIS_KEYFILE" : "", 63 | "TARDIS_VALIDATE_CERTS" : "false", # TODO: Fix this, once we get it working 64 | } 65 | 66 | try: 67 | _default_file = os.environ["TARDIS_DEFAULTS"] 68 | except KeyError: 69 | _default_file = _defaults["TARDIS_DEFAULTS"] 70 | 71 | _parser = configparser.ConfigParser(_defaults) 72 | _parser.add_section(SECTION) # Keep it happy later. 73 | _parser.read(_default_file) 74 | 75 | def getDefault(var): 76 | if var in os.environ: 77 | return os.environ[var] 78 | 79 | try: 80 | return _parser.get(SECTION, var) 81 | except configparser.Error: 82 | return None 83 | 84 | if __name__ == "__main__": 85 | print(_default_file) 86 | for i in _defaults: 87 | print(f"{i:-24s}: {getDefault(i)}") 88 | -------------------------------------------------------------------------------- /docs/source/encryption.rst: -------------------------------------------------------------------------------- 1 | Encryption 2 | ========== 3 | 4 | Backup sets can be optionally encrypted. Tardis uses AES CBC with 256-bit keys. 5 | 6 | All file names are also encrypted in the database, using the less secure AES EBC mode, also with 256-bit keys. 7 | 8 | Separate, unrelated, keys are used for the filenames and the file data. 9 | 10 | Keys 11 | ---- 12 | Keys are generated randomly when the database is created. 13 | Keys are always stored encrypted, whether they are stored in the database, or in a 14 | The keys are encrypted with a key derived from the current password. 15 | 16 | Keys can be either stored in the database directly, or in a user controlled "key database" file. 17 | 18 | Key Flow 19 | -------- 20 | 21 | When you attempt to access the database, the password will be hashed using a "Password Based Key Deviration Function" (PBKDF), namely 22 | `PBKDF2 `_. 23 | This is used to generate two keys, one of which is used to generate the authentication token used to login, and the other of which generates the 24 | encrypts the two keys. 25 | 26 | Once the keys are generated, the token key is used to encrypt a string and pass it into the database for authentication. When the server returns 27 | the keys, the key key (the second of the two keys) is used to decrypt the two main keys, namely the filename key, and the contents key. 28 | 29 | All key manipulation, and encryption/decryption is performed in the client apps. At no time is any information sufficient to decrypt anything sent to the 30 | server. 31 | 32 | Filename Encryption 33 | ------------------- 34 | 35 | File names are encrypted via AES 256 in ECB mode. In general, ECB mode is less secure than the CBC mode used to encrypt the contents of the files. 36 | ECB mode was used as it does not rely on a Nonce or Initialization vector. This means that a name can be encrypted multiple times without having to somehow 37 | know the initialization vector. 38 | 39 | It may be possible to attack the file names. However, attacking the file names does give access to the contents of the files, as the contents are encrypted with a separate, 40 | unrelated key. Both keys (content and filenmae) are generated randomly and encrypted. 41 | 42 | Authentication 43 | -------------- 44 | 45 | Encrypted files can be authenticated in two separate manners. First, all files are named within the database by a hash of the data in the file. If the backup is not encrypted, this is simply 46 | the `MD5 `_ hash of the data. If the backup is encrypted, the `HMAC-MD5 `_ of the same data is used. 47 | 48 | When the backup is encrypted, the data format also includes a HMAC-SHA512 authentication code in each data file. 49 | 50 | Unencrypted files can be mildly authenticated by checking that the MD5 hash of the file matches what's stored in the backup database. This is open to attack, but is sufficient to determine if data is 51 | corrupted in transmission. 52 | Encrypted files can be authenticated via both the overall HMAC-MD5 of the entire file, and the HMAC-SHA512 of each component file used to generate the complete file. 53 | 54 | Data Format 55 | ----------- 56 | 57 | Data files are encrypted and saved in the following format: 58 | * Bytes 0-15 contain the Initialization Vector used for this file. 59 | * Bytes 16-(length - 64) contain the data, padded per `PKCS7 `_ and encrypted with AES-CBC 256. 60 | * The last 64 bytes of the file contain the HMAC-SHA512 of the encrypted data. 61 | 62 | Notes 63 | ----- 64 | 65 | Encryption is handled via the `pycryptodome `_ library. pycryptodome supports the AES-NI instructions on processors which have them. This can 66 | result in a significant speedup. 67 | 68 | Hashing is done via the standard hashlib and hmac libraries from Python. 69 | -------------------------------------------------------------------------------- /tools/setToken.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | from Tardis import Defaults, Util, TardisDB, TardisCrypto 33 | import os.path 34 | import logging 35 | import argparse 36 | import base64 37 | 38 | from Cryptodome.Cipher import AES 39 | 40 | def processArgs(): 41 | parser = argparse.ArgumentParser(description='Set a token/password') 42 | parser.add_argument('--database', '-D', dest='database', default=Defaults.getDefault('TARDIS_DB'), help="Database to use. Default: %(default)s") 43 | parser.add_argument('--client', '-C', dest='client', default=Defaults.getDefault('TARDIS_CLIENT'), help="Client to list on. Default: %(default)s") 44 | parser.add_argument('--dbname', dest='dbname', default=Defaults.getDefault('TARDIS_DBNAME'), help="Name of the database file. Default: %(default)s") 45 | 46 | passgroup= parser.add_argument_group("Password/Encryption specification options") 47 | pwgroup = passgroup.add_mutually_exclusive_group(required=True) 48 | pwgroup.add_argument('--password', '-p',dest='password', default=None, nargs='?', const=True, help='Encrypt files with this password') 49 | pwgroup.add_argument('--password-file', dest='passwordfile', default=None, help='Read password from file') 50 | pwgroup.add_argument('--password-url', dest='passwordurl', default=None, help='Retrieve password from the specified URL') 51 | pwgroup.add_argument('--password-prog', dest='passwordprog', default=None, help='Use the specified command to generate the password on stdout') 52 | 53 | return parser.parse_args() 54 | 55 | def createToken(crypto, client): 56 | cipher = AES.new(crypto._tokenKey, AES.MODE_ECB) 57 | token = base64.b64encode(cipher.encrypt(crypto.padzero(client)), crypto._altchars) 58 | return token 59 | 60 | def main(): 61 | logging.basicConfig(level=logging.DEBUG) 62 | args = processArgs() 63 | password = Util.getPassword(args.password, args.passwordfile, args.passwordurl, args.passwordprog) 64 | 65 | crypto = TardisCrypto.TardisCrypto(password, args.client) 66 | token = createToken(crypto, args.client) 67 | 68 | path = os.path.join(args.repo, 'tardis.db') 69 | db = TardisDB.TardisDB(path, backup=False) 70 | db.setToken(token) 71 | 72 | if __name__ == "__main__": 73 | main() 74 | -------------------------------------------------------------------------------- /tools/decryptName.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import logging 34 | import os.path 35 | import os 36 | import sys 37 | 38 | from Tardis import Util, Config 39 | 40 | logger = None 41 | 42 | def reader(quiet): 43 | prompt = '' if quiet else '--> ' 44 | try: 45 | while True: 46 | yield input(prompt) 47 | except EOFError: 48 | pass 49 | 50 | def processArgs(): 51 | parser = argparse.ArgumentParser(description='encrypt or decrypt filenames', fromfile_prefix_chars='@', add_help=False) 52 | 53 | (_, remaining) = Config.parseConfigOptions(parser) 54 | Config.addCommonOptions(parser) 55 | Config.addPasswordOptions(parser) 56 | 57 | parser.add_argument('--encrypt', '-e', dest='encrypt', default=False, action='store_true', help='Encrypt names instead of decrypting') 58 | parser.add_argument('--quiet', '-q', dest='quiet', default=False, action='store_true', help="Only print the translation, not the input strings") 59 | 60 | parser.add_argument('--help', '-h', action='help') 61 | parser.add_argument('names', nargs='*', help="List of pathnames to decrypt") 62 | 63 | Util.addGenCompletions(parser) 64 | 65 | args = parser.parse_args(remaining) 66 | 67 | return args 68 | 69 | def main(): 70 | global logger 71 | logging.basicConfig(level=logging.INFO) 72 | logger = logging.getLogger('') 73 | args = processArgs() 74 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog) 75 | 76 | _, _, crypto, _ = Util.setupDataConnection(args.repo, password, args.keys) 77 | 78 | data = args.names 79 | if not data: 80 | tty = os.isatty(0) 81 | if not tty: 82 | data = list(map(str.strip, sys.stdin.readlines())) 83 | else: 84 | data = reader(args.quiet) 85 | 86 | for i in data: 87 | if i: 88 | if not args.quiet: 89 | print(i, " \t => \t", end=' ') 90 | try: 91 | if args.encrypt: 92 | print(crypto.encryptPath(i)) 93 | else: 94 | print(crypto.decryptPath(i)) 95 | except Exception as e: 96 | print("Caught exception: " + str(e)) 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /docs/source/lstardis.rst: -------------------------------------------------------------------------------- 1 | lstardis 2 | ======== 3 | The contents of a Tardis backup set can be read via the lstardis application. lstardis is similar in many ways to a standard ls, except that in addition to listing 4 | any files, it lists backupsets where the files exist. 5 | 6 |
 7 | usage: lstardis [-h] [--database DATABASE] [--client CLIENT] [--long]
 8 |                 [--hidden] [--reverse] [--annotate] [--size] [--human]
 9 |                 [--dirinfo] [--checksums] [--chainlen] [--inode] [--versions]
10 |                 [--all] [--deletions] [--times] [--headers] [--colors]
11 |                 [--columns COLUMNS] [--dbname DBNAME] [--recurse]
12 |                 [--maxdepth MAXDEPTH] [--recent] [--glob] [--reduce [REDUCE]]
13 |                 [--realpath] [--range RANGE | --dates DATERANGE]
14 |                 [--password [PASSWORD] | --password-file PASSWORDFILE |
15 |                 --password-prog PASSWORDPROG] [--crypt] [--keys KEYS]
16 |                 [--version]
17 |                 [directories [directories ...]]
18 | 
19 | List Tardis File Versions
20 | 
21 | positional arguments:
22 |   directories           List of directories/files to list
23 | 
24 | optional arguments:
25 |   -h, --help            show this help message and exit
26 |   --database DATABASE, -D DATABASE
27 |                         Database to use. Default: /nfs/blueberrypi
28 |   --client CLIENT, -C CLIENT
29 |                         Client to list on. Default: linux.koldware.com
30 |   --long, -l            Use long listing format.
31 |   --hidden, -a          Show hidden files.
32 |   --reverse, -r         Reverse the sort order
33 |   --annotate, -f        Annotate files based on type.
34 |   --size, -s            Show file sizes
35 |   --human, -H           Format sizes for easy reading
36 |   --dirinfo, -d         Maxdepth to recurse directories. 0 for none
37 |   --checksums, -c       Print checksums.
38 |   --chainlen, -L        Print chainlengths.
39 |   --inode, -i           Print inode numbers
40 |   --[no]versions        Display versions of files. Default: True
41 |   --all                 Show all versions of a file. Default: False
42 |   --[no]deletions       Show deletions. Default: True
43 |   --[no]times           Use file time changes when determining diffs. Default:
44 |                         False
45 |   --[no]headers         Show headers. Default: True
46 |   --[no]colors          Use colors. Default: False
47 |   --columns COLUMNS     Number of columns to display
48 |   --dbname DBNAME       Name of the database file. Default: tardis.db
49 |   --recurse, -R         List Directories Recurively
50 |   --maxdepth MAXDEPTH   Maximum depth to recurse directories
51 |   --[no]recent          Show only the most recent version of a file. Default:
52 |                         False
53 |   --[no]glob            Glob filenames
54 |   --reduce [REDUCE]     Reduce paths by N directories. No value for smart
55 |                         reduction
56 |   --[no]realpath        Use the full path, expanding symlinks to their actual
57 |                         path components
58 |   --range RANGE         Use a range of backupsets. Format: 'Start:End' Start
59 |                         and End can be names or backupset numbers. Either
60 |                         value can be left off to indicate the first or last
61 |                         set respectively
62 |   --dates DATERANGE     Use a range of dates for the backupsets. Format:
63 |                         'Start:End'. Start and End are names which can be
64 |                         intepreted liberally. Either can be left off to
65 |                         indicate the first or last set respectively
66 |   --version             Show the version
67 | 
68 | Password/Encryption specification options:
69 |   --password [PASSWORD], -P [PASSWORD]
70 |                         Encrypt files with this password
71 |   --password-file PASSWORDFILE, -F PASSWORDFILE
72 |                         Read password from file. Can be a URL (HTTP/HTTPS or
73 |                         FTP)
74 |   --password-prog PASSWORDPROG
75 |                         Use the specified command to generate the password on
76 |                         stdout
77 |   --[no]crypt           Encrypt data. Only valid if password is set
78 |   --keys KEYS           Load keys from file.
79 | 
80 | -------------------------------------------------------------------------------- /src/Tardis/Cache.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import collections 32 | import logging 33 | import time 34 | 35 | 36 | class Cache: 37 | def __init__(self, size, timeout, name="Cache"): 38 | self.size = size 39 | self.timeout = timeout 40 | self.cache = collections.OrderedDict() 41 | self.logger = logging.getLogger(name) 42 | 43 | def insert(self, key, value, now=None, timeout=None): 44 | # Use the regular timeout if it's specified 45 | if timeout is None: 46 | timeout = self.timeout 47 | 48 | # If there is a timeout, set the timeout time 49 | if timeout: 50 | if now is None: 51 | now = time.time() 52 | timeout += now 53 | 54 | self.cache[key] = (value, timeout) 55 | self.logger.debug("Inserting key %s", key) 56 | if self.size != 0 and len(self.cache) > self.size: 57 | self.flush() 58 | if len(self.cache) > self.size: 59 | self.cache.popitem(False) 60 | 61 | def retrieve(self, key): 62 | if key not in self.cache: 63 | self.logger.debug("Retrieving key %s failed", key) 64 | return None 65 | (value, timeout) = self.cache[key] 66 | if timeout and timeout < time.time(): 67 | self.logger.debug("Removing timedout key %s", key) 68 | del self.cache[key] 69 | self.flush() 70 | return None 71 | self.logger.debug("Retrieving key %s", key) 72 | return value 73 | 74 | def delete(self, key): 75 | if key in self.cache: 76 | del self.cache[key] 77 | 78 | def flush(self): 79 | now = time.time() 80 | i = iter(self.cache.items()) 81 | z = next(i) 82 | try: 83 | while z: 84 | (_, item) = z 85 | (_, timeout) = item 86 | if timeout > now: 87 | return 88 | self.cache.popitem(False) 89 | z = next(i) 90 | except Exception: 91 | # If something goes wrong, just punt. 92 | pass 93 | 94 | def purge(self): 95 | self.cache = collections.OrderedDict() 96 | 97 | if __name__ == "__main__": 98 | c = Cache(5, 2) 99 | for i in range(0, 5): 100 | c.insert(i, i * 100) 101 | for i in range(0, 10): 102 | print(i, " :: ", c.retrieve(i)) 103 | print("----") 104 | for i in range(5, 10): 105 | c.insert(i, i * 100) 106 | for i in range(0, 10): 107 | print(i, " :: ", c.retrieve(i)) 108 | time.sleep(2) 109 | for i in range(0, 10): 110 | print(i, " :: ", c.retrieve(i)) 111 | -------------------------------------------------------------------------------- /tools/setSRP.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import os.path 33 | import logging 34 | import argparse 35 | import hashlib 36 | import base64 37 | 38 | import srp 39 | from Cryptodome.Cipher import AES 40 | 41 | from Tardis import Defaults, Util, TardisDB, TardisCrypto 42 | 43 | def getToken(db): 44 | return db._getConfigValue('Token') 45 | 46 | def checkToken(db, token): 47 | dbToken = getToken(db) 48 | if dbToken is None: 49 | print("No token in DB. Password is not set.") 50 | return False 51 | s = hashlib.sha1() 52 | s.update(token) 53 | tokenhash = s.hexdigest() 54 | return dbToken == tokenhash 55 | 56 | def createToken(crypto, client): 57 | cipher = AES.new(crypto._tokenKey, AES.MODE_ECB) 58 | token = base64.b64encode(cipher.encrypt(crypto.padzero(client)), crypto._altchars) 59 | return token 60 | 61 | def processArgs(): 62 | parser = argparse.ArgumentParser(description='Set directory hashes.') 63 | parser.add_argument('--repo', '-R', dest='repo', default=Defaults.getDefault('TARDIS_REPO'), help="Repository to use. Default: %(default)s") 64 | 65 | passgroup= parser.add_argument_group("Password/Encryption specification options") 66 | pwgroup = passgroup.add_mutually_exclusive_group(required=True) 67 | pwgroup.add_argument('--password', '-p',dest='password', default=None, nargs='?', const=True, help='Encrypt files with this password') 68 | pwgroup.add_argument('--password-file', dest='passwordfile', default=None, help='Read password from file') 69 | pwgroup.add_argument('--password-url', dest='passwordurl', default=None, help='Retrieve password from the specified URL') 70 | pwgroup.add_argument('--password-prog', dest='passwordprog', default=None, help='Use the specified command to generate the password on stdout') 71 | 72 | return parser.parse_args() 73 | 74 | def main(): 75 | logging.basicConfig(level=logging.INFO) 76 | logger = logging.getLogger() 77 | crypto = None 78 | args = processArgs() 79 | password = Util.getPassword(args.password, args.passwordfile, args.passwordurl, args.passwordprog) 80 | 81 | if password: 82 | crypto = TardisCrypto.TardisCrypto(password, args.client) 83 | 84 | path = os.path.join(args.repo, 'tardis.db') 85 | db = TardisDB.TardisDB(path, backup=False) 86 | 87 | token = createToken(crypto, args.client) 88 | if not checkToken(db, token): 89 | logger.error("Password does not match") 90 | raise Exception() 91 | 92 | salt, vkey = srp.create_salted_verification_key(args.client, password) 93 | db.setSrpValues(salt, vkey) 94 | db._setConfigValue('Token', None) 95 | 96 | if __name__ == "__main__": 97 | main() 98 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert2to3.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import logging 32 | import os.path 33 | import sqlite3 34 | import sys 35 | 36 | from Tardis import CacheDir 37 | 38 | from . import convertutils 39 | 40 | version = 2 41 | 42 | def upgrade(conn, logger, db): 43 | convertutils.checkVersion(conn, version, logger) 44 | 45 | conn.execute("ALTER TABLE Files ADD COLUMN XattrId INTEGER") 46 | conn.execute("ALTER TABLE Files ADD COLUMN AclId INTEGER") 47 | conn.execute("ALTER TABLE CheckSums ADD COLUMN DiskSize INTEGER") 48 | conn.execute("ALTER TABLE CheckSums ADD COLUMN ChainLength INTEGER") 49 | 50 | conn.execute('INSERT OR REPLACE INTO Config (Key, Value) VALUES ("SchemaVersion", "3")') 51 | 52 | 53 | print("Setting chain lengths") 54 | conn.execute("UPDATE Checksums SET ChainLength = 0 WHERE Basis IS NULL") 55 | 56 | rnd = 0 57 | 58 | while True: 59 | c = conn.execute("SELECT COUNT(*) FROM Checksums WHERE ChainLength IS NULL") 60 | r = c.fetchone() 61 | print("Round %d: Remaining empty chainlengths: %d" % (rnd, r[0])) 62 | rnd += 1 63 | if r[0] == 0: 64 | break 65 | conn.execute("UPDATE Checksums " 66 | "SET ChainLength = 1 + (SELECT ChainLength FROM Checksums C WHERE C.Checksum == CheckSums.Basis) " 67 | "WHERE (Basis IS NOT NULL) AND (ChainLength IS NULL) AND " 68 | "Basis IN (SELECT Checksum FROM Checksums WHERE Chainlength IS NOT NULL)") 69 | 70 | 71 | print("Setting data sizes") 72 | cache = CacheDir.CacheDir(os.path.dirname(db)) 73 | 74 | c = conn.execute("SELECT COUNT(*) FROM Checksums WHERE DiskSize IS NULL") 75 | r = c.fetchone() 76 | numrows = r[0] 77 | print(numrows) 78 | 79 | # Get all non-sized files. Order by checksum so that we can get locality in the directories we read 80 | c = conn.execute("SELECT Checksum FROM Checksums WHERE DiskSize IS NULL ORDER BY Checksum") 81 | checksums = c.fetchall() 82 | # Build a progress bar, if we have that module. Just for grins. 83 | 84 | 85 | c2 = conn.cursor() 86 | x = 0 87 | for i in checksums: 88 | checksum = i[0] 89 | size = os.path.getsize(cache.path(checksum)) 90 | c2.execute("UPDATE Checksums SET DiskSize = ? WHERE Checksum = ?", (size, checksum)) 91 | x += 1 92 | 93 | convertutils.updateVersion(conn, version, logger) 94 | conn.commit() 95 | 96 | if __name__ == "__main__": 97 | logging.basicConfig(level=logging.DEBUG) 98 | logger = logging.getLogger('') 99 | 100 | if len(sys.argv) > 1: 101 | db = sys.argv[1] 102 | else: 103 | db = "tardis.db" 104 | 105 | conn = sqlite3.connect(db) 106 | upgrade(conn, logger, db) 107 | -------------------------------------------------------------------------------- /tools/adjustVirtualDevices.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import os.path 34 | import logging 35 | import pprint 36 | 37 | from Tardis import Util, librsync, Regenerator, Config 38 | 39 | logger: logging.Logger 40 | 41 | def processArgs(): 42 | parser = argparse.ArgumentParser(description='Encrypt the database', add_help = False) 43 | 44 | (_, remaining) = Config.parseConfigOptions(parser) 45 | Config.addCommonOptions(parser) 46 | Config.addPasswordOptions(parser) 47 | 48 | Util.addGenCompletions(parser) 49 | 50 | args = parser.parse_args(remaining) 51 | return args 52 | 53 | def getPath(db, crypt, bset, inode): 54 | fInfo = db.getFileInfoByInode(inode, bset) 55 | if fInfo: 56 | parent = (fInfo['parent'], fInfo['parentdev']) 57 | prefix = getPath(db, crypt, bset, parent) 58 | 59 | name = crypt.decryptName(fInfo['name']) 60 | path = os.path.join(prefix, name) 61 | return path 62 | return '/' 63 | 64 | def main(): 65 | global logger 66 | logging.basicConfig(level=logging.INFO) 67 | logger = logging.getLogger('') 68 | args = processArgs() 69 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog, allowNone=True) 70 | 71 | mounts = {} 72 | 73 | (db, _, crypto) = Util.setupDataConnection(args.database, args.client, password, args.keys, args.dbname, args.dbdir, allow_upgrade=True) 74 | 75 | conn = db.conn 76 | cursor = conn.execute("SELECT DISTINCT Inode, DeviceId, VirtualId, FirstSet, LastSet FROM Files JOIN Devices ON Files.Device = Devices.DeviceID WHERE Device != ParentDev") 77 | data = cursor.fetchall() 78 | 79 | for i in data: 80 | #print(i) 81 | #f = db.getFileInfoByInode((i['Inode'], i['VirtualId']), i['FirstSet']) 82 | p = getPath(db, crypto, i['FirstSet'], (i['Inode'], i['VirtualId'])) 83 | hp = Util.hashPath(p) 84 | mounts[i['VirtualId']] = (p, hp) 85 | 86 | pprint.pprint(mounts) 87 | 88 | rc = 0 89 | for i, j in mounts.items(): 90 | if i == j[1]: 91 | print(f"{i} already adjusted") 92 | continue 93 | oldDev = db._getDeviceId(i) 94 | newDev = db._getDeviceId(j[1]) 95 | print(f"{i} {j[1]} ::: {oldDev} -> {newDev}") 96 | c = conn.execute("UPDATE Files SET Device = :newDev WHERE Device = :oldDev", {"newDev": newDev, "oldDev": oldDev}) 97 | print(f"Rows Changed Device: {c.rowcount}") 98 | rc += c.rowcount 99 | conn.execute("UPDATE Files SET ParentDev = :newDev WHERE ParentDev = :oldDev", {"newDev": newDev, "oldDev": oldDev}) 100 | print(f"Rows Changed Parent: {c.rowcount}") 101 | rc += c.rowcount 102 | print(rc) 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /tools/addDigest.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | 33 | import argparse 34 | import sys 35 | from binascii import hexlify 36 | 37 | from Tardis import Regenerator, TardisDB, CacheDir, TardisCrypto, Config, Util 38 | #from Tardis.Regenerator import RegenerateException 39 | 40 | from icecream import ic 41 | 42 | def parseArgs(): 43 | parser = argparse.ArgumentParser(description='Check backup files for integrity', fromfile_prefix_chars='@', add_help=False) 44 | (_, remaining) = Config.parseConfigOptions(parser) 45 | 46 | Config.addCommonOptions(parser) 47 | Config.addPasswordOptions(parser) 48 | 49 | parser.add_argument("--verbose", "-v", dest='verbosity', action='count', default=0, help="Increase verbosity") 50 | parser.add_argument("--help", "-h", action='help') 51 | parser.add_argument(dest='checksums', nargs='+', help="List of checksums to validate. Blank = all") 52 | 53 | args = parser.parse_args(remaining) 54 | return args 55 | 56 | def doWrite(out, data): 57 | #ic(hexlify(data)) 58 | out.write(data) 59 | return(len(data)) 60 | 61 | def main(): 62 | args = parseArgs() 63 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog) 64 | tardis, cache, crypto = Util.setupDataConnection(args.database, args.client, password, args.keys, args.dbname, args.dbdir) 65 | logger = Util.setupLogging(args.verbosity) 66 | 67 | 68 | ivSize = crypto.ivLength 69 | 70 | for file in args.checksums: 71 | try: 72 | info = tardis.getChecksumInfo(file) 73 | #ic(info) 74 | 75 | logger.info("Adding digest to %s", file) 76 | old = file + ".bak" 77 | cache.move(file, old) 78 | infile = cache.open(old, "rb") 79 | outfile = cache.open(file, "wb") 80 | 81 | data = infile.read() 82 | iv = data[0:ivSize] 83 | #ic(hexlify(data)) 84 | 85 | decrypt = crypto.getContentCipher(iv) 86 | encrypt = crypto.getContentEncryptor(iv) 87 | plain = decrypt.decrypt(data[ivSize:]) 88 | 89 | numBytes = 0 90 | 91 | numBytes += doWrite(outfile, iv) 92 | ct = encrypt.encrypt(plain) 93 | numBytes += doWrite(outfile, ct) 94 | numBytes += doWrite(outfile, encrypt.finish()) 95 | dig = encrypt.digest() 96 | numBytes += doWrite(outfile, dig) 97 | #ic(numBytes) 98 | 99 | tardis.updateChecksumFile(file, True, info['size'], None, None, info['compressed'], numBytes, 0) 100 | 101 | infile.close() 102 | outfile.close() 103 | except Exception as e: 104 | logger.error("Got exception processing %s: %s", file, str(e)) 105 | 106 | 107 | 108 | 109 | if __name__ == "__main__": 110 | sys.exit(main()) 111 | -------------------------------------------------------------------------------- /tools/mkKeyBackup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import time 33 | import sys 34 | import argparse 35 | import tempfile 36 | 37 | import qrcode 38 | 39 | from reportlab.lib.enums import TA_JUSTIFY 40 | from reportlab.lib.pagesizes import letter 41 | from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image 42 | from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle 43 | from reportlab.lib.units import inch 44 | 45 | import Tardis 46 | from Tardis import Util 47 | from Tardis import Config 48 | 49 | def makePdf(output, qrCode, data, client): 50 | doc = SimpleDocTemplate(output, pagesize=letter, 51 | rightMargin=72,leftMargin=72, 52 | topMargin=72,bottomMargin=18) 53 | Story=[] 54 | 55 | im = Image(qrCode, 2*inch, 2*inch) 56 | Story.append(im) 57 | 58 | Story.append(Spacer(1, 12)) 59 | 60 | 61 | styles=getSampleStyleSheet() 62 | styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY)) 63 | 64 | title = time.strftime("Keys for client %%s as of %a, %m/%d/%Y, %H:%M") % client 65 | 66 | Story.append(Paragraph(title, styles["Normal"])) 67 | Story.append(Spacer(1, 12)) 68 | 69 | for line in data.split('\n'): 70 | Story.append(Paragraph(line, styles["Normal"])) 71 | 72 | doc.build(Story) 73 | 74 | def mkQrFile(data): 75 | qrfile = tempfile.NamedTemporaryFile() 76 | qrimage = qrcode.make(data) 77 | qrimage.save(qrfile) 78 | qrfile.flush() 79 | return qrfile 80 | 81 | def processArgs(): 82 | parser = argparse.ArgumentParser(description='Generate a key backup', fromfile_prefix_chars='@', formatter_class=Util.HelpFormatter, add_help=False) 83 | 84 | (_, remaining) = Config.parseConfigOptions(parser) 85 | 86 | Config.addCommonOptions(parser) 87 | Config.addPasswordOptions(parser) 88 | 89 | parser.add_argument('--output', '-o', default=None, dest='output', required=True, help='Output file') 90 | 91 | parser.add_argument('--verbose', '-v', action='count', default=0, dest='verbose', help='Increase the verbosity') 92 | parser.add_argument('--version', action='version', version='%(prog)s ' + Tardis.__versionstring__, help='Show the version') 93 | parser.add_argument('--help', '-h', action='help') 94 | 95 | Util.addGenCompletions(parser) 96 | 97 | return parser.parse_args(remaining) 98 | 99 | def main(): 100 | args = processArgs() 101 | 102 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog) 103 | tardis, _, crypt, client = Util.setupDataConnection(args.repo, password, args.keys) 104 | 105 | (f, c) = crypt.getKeys() 106 | client = tardis.getConfigValue('ClientID') 107 | 108 | data = Util.mkKeyString(client, f, c) 109 | 110 | qrfile = mkQrFile(data) 111 | makePdf(args.output, qrfile.name, data, client) 112 | return 0 113 | 114 | if __name__ == "__main__": 115 | sys.exit(main()) 116 | -------------------------------------------------------------------------------- /tools/setDirHashes.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | #import sys 33 | #sys.path.insert(0, '.') 34 | import os.path 35 | import logging 36 | import argparse 37 | 38 | from Tardis import Defaults, Util, TardisDB, TardisCrypto 39 | 40 | def processArgs(): 41 | parser = argparse.ArgumentParser(description='Set a token/password') 42 | parser.add_argument('--repo', '-R', dest='repo', default=Defaults.getDefault('TARDIS_REPO'), help="Repository to use. Default: %(default)s") 43 | 44 | passgroup= parser.add_argument_group("Password/Encryption specification options") 45 | pwgroup = passgroup.add_mutually_exclusive_group() 46 | pwgroup.add_argument('--password', '-p',dest='password', default=None, nargs='?', const=True, help='Encrypt files with this password') 47 | pwgroup.add_argument('--password-file', dest='passwordfile', default=None, help='Read password from file') 48 | pwgroup.add_argument('--password-url', dest='passwordurl', default=None, help='Retrieve password from the specified URL') 49 | pwgroup.add_argument('--password-prog', dest='passwordprog', default=None, help='Use the specified command to generate the password on stdout') 50 | 51 | return parser.parse_args() 52 | 53 | def main(): 54 | logging.basicConfig(level=logging.INFO) 55 | crypto = None 56 | token = None 57 | args = processArgs() 58 | password = Util.getPassword(args.password, args.passwordfile, args.passwordurl, args.passwordprog) 59 | 60 | if password: 61 | crypto = TardisCrypto.TardisCrypto(password, args.client) 62 | 63 | path = os.path.join(args.repo, 'tardis.db') 64 | db = TardisDB.TardisDB(path, token=token, backup=False) 65 | 66 | if crypto: 67 | (a, b) = db.getKeys() 68 | crypto.setKeys(a, b) 69 | 70 | conn = db.conn 71 | dirs = conn.execute("SELECT Name as name, Inode AS inode, Device AS device, FirstSet as firstset, LastSet AS lastset FROM Files JOIN Names ON Files.NameId = Names.NameId WHERE Dir = 1") 72 | while True: 73 | batch = dirs.fetchmany(1000) 74 | if not batch: 75 | break 76 | 77 | for d in batch: 78 | name = d['name'] 79 | inode = d['inode'] 80 | device = d['device'] 81 | firstset = d['firstset'] 82 | lastset = d['lastset'] 83 | 84 | files = db.readDirectory((inode, device), current=lastset) 85 | (checksum, nfiles) = Util.hashDir(crypto, files, True) 86 | 87 | print(("%-20s (%d, %d) [%d %d] -- %s %d") % (name, inode, device, firstset, lastset, checksum, nfiles)) 88 | ckinfo = db.getChecksumInfo(checksum) 89 | if ckinfo: 90 | cksid = ckinfo['checksumid'] 91 | else: 92 | cksid = db.insertChecksumFile(checksum, size=nfiles, isFile=False) 93 | 94 | db.updateDirChecksum((inode, device), cksid, current=lastset) 95 | conn.commit() 96 | 97 | if __name__ == "__main__": 98 | main() 99 | -------------------------------------------------------------------------------- /tools/checkDB.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import os, os.path 33 | import sys 34 | import sqlite3 35 | import glob 36 | 37 | from Tardis import CacheDir 38 | 39 | def hexcount(lower, upper, digits): 40 | fmt = "%0" + str(digits) + "x" 41 | for i in range(lower, upper): 42 | out = fmt % (i) 43 | yield out 44 | 45 | def getdbfiles(conn, prefix): 46 | prefix += "%" 47 | ret = set() 48 | cur = conn.execute('SELECT Checksum FROM Checksums WHERE Checksum LIKE :prefix AND IsFile = 1', {"prefix": prefix}) 49 | while True: 50 | batch = cur.fetchmany() 51 | if not batch: 52 | break 53 | ret.update([i[0] for i in batch]) 54 | return ret 55 | 56 | def hasExt(x): 57 | (_, e) = os.path.splitext(x) 58 | return (e is not '' and e is not None) 59 | 60 | def main(): 61 | d = sys.argv[1] 62 | cd = CacheDir.CacheDir(d, create=False) 63 | 64 | 65 | db = os.path.join(d, "tardis.db") 66 | print("Opening DB: " + db) 67 | conn = sqlite3.connect(db) 68 | print("Connected") 69 | 70 | missingData = set() 71 | unreferenced = set() 72 | 73 | for i in hexcount(0, 16 ** int(cd.partsize), int(cd.partsize)): 74 | print(f"Starting: {i} ", end='') 75 | # Get all the files which start with i 76 | dbfiles = getdbfiles(conn, i) 77 | alldatafiles = set() 78 | # Grab each subdirectory, 79 | 80 | path = os.path.join(d, i) 81 | try: 82 | if os.path.isdir(path): 83 | pattern = ('?' * int(cd.partsize) + os.sep) * (int(cd.parts)-1) + "*" 84 | pattern = os.path.join(d, i, pattern) 85 | #print(pattern) 86 | 87 | # contents = os.listdir(path) 88 | contents = list(map(lambda x: os.path.basename(x), glob.glob(pattern, recursive=True))) 89 | # print(contents) 90 | print(f"{len(contents)} files") 91 | metafiles = set(filter(hasExt, contents)) 92 | datafiles = set([x for x in contents if not hasExt(x)]) 93 | 94 | alldatafiles.update(datafiles) 95 | 96 | #print path, " :: ", len(contents), len(metafiles), len(datafiles), " :: ", len(dbfiles) 97 | # Process the signature files 98 | for f in metafiles: 99 | (data, _) = os.path.splitext(f) 100 | if not data in datafiles: 101 | print("{} without matching data file".format(f)) 102 | else: 103 | print() 104 | except Exception as e: 105 | print("Caught exception proecssing directory {}: {}".format(path), e) 106 | 107 | # Find missing data files 108 | missing = dbfiles.difference(alldatafiles) 109 | missingData.update(missing) 110 | for i in missing: 111 | print("Missing data file {}".format(i)) 112 | 113 | # Find files which aren't in the DB 114 | unref = alldatafiles.difference(dbfiles) 115 | unreferenced.update(unref) 116 | for i in unref: 117 | print("Unreferenced data file: {}".format(i)) 118 | 119 | conn.close() 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /src/Tardis/Config.py: -------------------------------------------------------------------------------- 1 | # vi: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import configparser 32 | import sys 33 | 34 | from . import Defaults, TardisCrypto 35 | 36 | configDefaults = { 37 | "Repository": Defaults.getDefault("TARDIS_REPO"), 38 | "Password": None, 39 | "PasswordFile": Defaults.getDefault("TARDIS_PWFILE"), 40 | "PasswordProg": None, 41 | "PasswordTimeout": Defaults.getDefault("TARDIS_PWTIMEOUT"), 42 | "Crypt": str(True), 43 | "KeyFile": Defaults.getDefault("TARDIS_KEYFILE"), 44 | "LogFiles": None, 45 | "Verbosity": str(0), 46 | } 47 | 48 | config = configparser.ConfigParser(configDefaults, allow_no_value=True, interpolation=configparser.ExtendedInterpolation()) 49 | job = None 50 | 51 | def parseConfigOptions(parser, exit_on_error=True): 52 | global job 53 | configGroup = parser.add_argument_group("Configuration File Options") 54 | configGroup.add_argument("--config", dest="config", default=Defaults.getDefault("TARDIS_CONFIG"), const=None, help="Location of the configuration file. Default: %(default)s") 55 | configGroup.add_argument("--job", dest="job", default=Defaults.getDefault("TARDIS_JOB"), help="Job Name within the configuration file. Default: %(default)s") 56 | 57 | (args, remaining) = parser.parse_known_args() 58 | 59 | job = args.job 60 | if args.config: 61 | config.read(args.config) 62 | if not config.has_section(job): 63 | sys.stderr.write(f"WARNING: No Job named {job} available. Using defaults. Jobs available: {str(config.sections()).strip('[]')}\n") 64 | config.add_section(job) # Make it safe for reading other values from. 65 | if exit_on_error: 66 | sys.exit(1) 67 | raise KeyError(f"No such job: {job}") 68 | else: 69 | config.add_section(job) # Make it safe for reading other values from. 70 | 71 | return args, remaining 72 | 73 | def addCommonOptions(parser): 74 | dbGroup = parser.add_argument_group("Database specification options") 75 | dbGroup.add_argument("--repository", "-R", dest="repo", default=config.get(job, "Repository"), help="Database to use. Default: %(default)s") 76 | 77 | def addPasswordOptions(parser, addscheme=False): 78 | passgroup = parser.add_argument_group("Password/Encryption specification options") 79 | pwgroup = passgroup.add_mutually_exclusive_group() 80 | pwgroup.add_argument("--password", "-P",dest="password", default=config.get(job, "Password"), nargs="?", const=True, help="Encrypt files with this password") 81 | pwgroup.add_argument("--password-file", "-F", dest="passwordfile", default=config.get(job, "PasswordFile"), help="Read password from file. Can be a URL (HTTP/HTTPS or FTP)") 82 | pwgroup.add_argument("--password-prog", dest="passwordprog", default=config.get(job, "PasswordProg"), help="Use the specified command to generate the password on stdout") 83 | 84 | if addscheme: 85 | passgroup.add_argument("--crypt", dest="scheme", type=int, choices=range(TardisCrypto.MAX_CRYPTO_SCHEME + 1), default=TardisCrypto.DEF_CRYPTO_SCHEME, 86 | help="Use cryptography scheme\n" + TardisCrypto.getCryptoNames()) 87 | passgroup.add_argument("--keys", dest="keys", default=config.get(job, "KeyFile"), help="Load keys from file.") 88 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #td /usr/bin/python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2019, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import os, sys 33 | import subprocess 34 | 35 | from setuptools import setup, find_packages 36 | 37 | sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'src')) 38 | 39 | import Tardis 40 | 41 | longdesc = ''' 42 | This is a system for performing backups, supporting incremental, delta backups, with option encryption, and 43 | recovery of data via either a filesystem based interface, or via explicit tools. Please pardon any Dr. Who 44 | jokes. 45 | ''' 46 | 47 | buildVersion = subprocess.check_output(['git', 'describe', '--dirty', '--tags', '--always']).strip() 48 | 49 | versionfile = "src/Tardis/tardisversion" 50 | print(buildVersion.decode('utf8'), file=open(versionfile, "w")) 51 | 52 | version = Tardis.__version__ 53 | add_pkgs = Tardis.check_features() 54 | 55 | setup( name = 'Tardis-Backup', 56 | version = version, 57 | description = "Tardis Backup System", 58 | long_description = longdesc, 59 | python_requires = ">=3.13", 60 | packages = find_packages('src', exclude=['ez_setup', 'examples', 'tests']), 61 | author = "Eric Koldinger", 62 | author_email = "kolding@washington.edu", 63 | url = "https://github.com/koldinger/Tardis", 64 | license = "BSD", 65 | platforms = "Posix; MacOS X", 66 | include_package_data = True, 67 | zip_safe = False, 68 | install_requires = ['msgpack>=1.0', 'daemonize', 'parsedatetime', 'pycryptodomex', 69 | 'termcolor', 'python-snappy', 'zstandard', 'srp', 70 | 'pid', 'python-magic', 'colorlog', 'reportlab', 71 | 'qrcode', 'fusepy', 'requests_cache','requests', 72 | 'flask', 'tornado', 'zstandard', 'lz4', 73 | 'StrEnum'] 74 | + add_pkgs, 75 | package_dir = {'': 'src'}, 76 | package_data = { 77 | 'Tardis': [ 'tardisversion', 'schema/tardis.sql' ], 78 | }, 79 | entry_points = { 80 | 'console_scripts' : [ 81 | 'tardis = Tardis.Client:main', 82 | 'tardisd = Tardis.Daemon:main', 83 | 'tardisfs = Tardis.TardisFS:main', 84 | 'regenerate = Tardis.Regenerate:main', 85 | 'lstardis = Tardis.List:main', 86 | 'sonic = Tardis.Sonic:main', 87 | 'tardiff = Tardis.Diff:main', 88 | 'tardisremote = Tardis.HttpInterface:tornado', 89 | ], 90 | }, 91 | classifiers = [ 92 | 'License :: OSI Approved :: BSD License', 93 | 'Development Status :: 4 - Beta', 94 | 'Intended Audience :: Developers', 95 | 'Intended Audience :: System Administrators', 96 | 'Topic :: System :: Archiving :: Backup', 97 | 'Programming Language :: Python', 98 | 'Programming Language :: Python :: 3', 99 | 'Operating System :: MacOS :: MacOS X', 100 | 'Operating System :: POSIX', 101 | 'Operating System :: POSIX :: Linux', 102 | 'System::Archiving::Backup', 103 | ] 104 | ) 105 | 106 | os.remove(versionfile) 107 | -------------------------------------------------------------------------------- /tools/tcd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import os 33 | import os.path 34 | import sys 35 | import parsedatetime 36 | import datetime 37 | import re 38 | 39 | usage = \ 40 | """'target' can be a name of a backup set, a date or relative day, or a relative number of sets (+3, -1) 41 | Examples: 42 | tcd -1 - Change to the previous backup set 43 | tcd +3 - Change 3 backup sets forward 44 | tcd "last week" - Change to a backup set from a week ago. 45 | tcd Current - Change to the current backup set. 46 | tcd Monthly-2023-01 - Change to the backup set namely "Monthly-2023-01" """ 47 | 48 | def findMount(path): 49 | origpath = os.path.realpath(path) 50 | path = origpath 51 | while path != '/': 52 | path, f = os.path.split(path) 53 | if os.path.ismount(path): 54 | return path, f 55 | #if os.path.basename(path) == 'TardisFS': 56 | # return path, f 57 | raise Exception(f"No mountpoint found in {origpath}") 58 | 59 | def findByName(name, bSets): 60 | x = filter(lambda x: x.name == name, bSets) 61 | return next(x, None) 62 | 63 | def findByTime(theTime, bSets): 64 | x = filter(lambda y: int(y.stat().st_mtime) < theTime, bSets) 65 | x = sorted(x, key=lambda y: y.stat().st_mtime, reverse=True) 66 | if x: 67 | return x[0] 68 | else: 69 | raise Exception("No best time found") 70 | 71 | def findRelative(expr, current, bSets): 72 | names = [x.name for x in bSets if not x.is_symlink()] 73 | pos = names.index(current) 74 | newPos = eval(f"{pos} {expr}") 75 | if newPos < 0 or newPos >= len(names): 76 | raise Exception(f"{newPos} ({expr}) is out of range") 77 | return findByName(names[newPos], bSets) 78 | 79 | def main(): 80 | # Make sure the usage makes sense 81 | if len(sys.argv) != 2: 82 | print(f"Usage: {sys.argv[0]} target", file=sys.stderr) 83 | print(usage, file=sys.stderr) 84 | sys.exit(0) 85 | 86 | # Get the target 87 | target = sys.argv[1] 88 | 89 | # Find the root of the system 90 | root, current = findMount('.') 91 | 92 | # And where we are in the system 93 | me = os.path.relpath('.', start=os.path.join(root, current)) 94 | 95 | #allSets = list(os.scandir(root)) 96 | allSets = sorted(os.scandir(root), key=lambda x: x.stat().st_mtime) 97 | 98 | # Check if we're of the form +/-Number 99 | relative = re.match(r'[-+]\d+$', target) 100 | if relative: 101 | x = findRelative(target, current, allSets) 102 | print(os.path.join(x.path, me)) 103 | sys.exit(0) 104 | 105 | # Else, see if this name exists 106 | x = findByName(target, allSets) 107 | if x: 108 | #print(f"Found target {target} -- {x}") 109 | print(os.path.realpath(os.path.join(x.path, me))) 110 | sys.exit(0) 111 | 112 | # Or perhaps we're a date 113 | cal = parsedatetime.Calendar() 114 | val, success = cal.parse(target) 115 | if success: 116 | timestamp = datetime.datetime(*val[:7]).timestamp() 117 | x = findByTime(timestamp, allSets) 118 | if x: 119 | #print(f"Found time {timestamp} -- {x} {x.stat().st_mtime}") 120 | print(os.path.join(x.path, me)) 121 | sys.exit(0) 122 | else: 123 | raise Exception(f"Can't parse date: {target}") 124 | 125 | sys.exit(1) 126 | 127 | 128 | if __name__ == "__main__": 129 | try: 130 | main() 131 | except Exception as e: 132 | sys.exit(1) 133 | -------------------------------------------------------------------------------- /tools/encryptFile.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse, logging 33 | import os.path 34 | import os 35 | import uuid 36 | import json 37 | import shutil 38 | import traceback 39 | 40 | import progressbar 41 | 42 | from Tardis import Util, Config, CompressedBuffer 43 | 44 | logger = None 45 | 46 | class FileSender: 47 | def __init__(self, output): 48 | if isinstance(output, str): 49 | self.output = open(output, "wb") 50 | else: 51 | self.output = output 52 | 53 | def sendMessage(self, message): 54 | if not isinstance(message, dict): 55 | self.output.write(message) 56 | 57 | def processArgs(): 58 | parser = argparse.ArgumentParser(description='Encrypt files for a backup database', add_help = False) 59 | 60 | (_, remaining) = Config.parseConfigOptions(parser) 61 | Config.addCommonOptions(parser) 62 | Config.addPasswordOptions(parser) 63 | 64 | parser.add_argument('--output', '-o', dest='output', required=True, help="Output directory") 65 | parser.add_argument('--json', '-j', default=None, dest='input', help='JSON input file') 66 | parser.add_argument('--signature', '-s', default=False, action='store_true', dest='signature', help='Generate signature file') 67 | parser.add_argument('--compress-data', '-Z', dest='compress', const='zlib', default=None, nargs='?', choices=CompressedBuffer.getCompressors(), 68 | help='Compress files') 69 | parser.add_argument('names', nargs='*', help="List of pathnames to decrypt") 70 | parser.add_argument('--help', '-h', action='help') 71 | 72 | Util.addGenCompletions(parser) 73 | 74 | args = parser.parse_args(remaining) 75 | 76 | return args 77 | 78 | def processFile(outdir, crypto, name, compress='none', signature=False): 79 | outname = str(uuid.uuid1()) 80 | outpath = os.path.join(outdir, outname) 81 | with open(outpath, "wb") as outfile: 82 | s = FileSender(outfile) 83 | with open(name, "rb") as infile: 84 | size, ck, sig = Util.sendData(s, infile, crypto.getContentEncryptor(), hasher=crypto.getHash(), signature=True, compress=compress) 85 | newpath = os.path.join(outdir, ck) 86 | print(name, size, ck, outpath) 87 | os.rename(outpath, newpath) 88 | 89 | if signature: 90 | outpath = os.path.join(outdir, ck + ".sig") 91 | with open(outpath, "wb") as sigfile: 92 | shutil.copyfileobj(sig, sigfile) 93 | 94 | def main(): 95 | global logger 96 | progressbar.streams.wrap_stderr() 97 | logging.basicConfig(level=logging.INFO) 98 | logger = logging.getLogger('') 99 | args = processArgs() 100 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog, allowNone=False) 101 | 102 | _, _, crypto, _ = Util.setupDataConnection(args.repo, password, args.keys) 103 | 104 | if args.input: 105 | files = json.load(open(args.input, 'r', encoding='utf8')) 106 | for x in files: 107 | try: 108 | processFile(args.output, crypto, x, args.compress, args.signature) 109 | except Exception as e: 110 | print(f"----> {str(e)} Processing {files[x]}") 111 | traceback.print_exc() 112 | 113 | for name in args.names: 114 | try: 115 | processFile(args.output, crypto, name, args.compress, args.signature) 116 | except Exception as e: 117 | print(f"----> {str(e)} Processing {name}") 118 | traceback.print_exc() 119 | 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /tools/shtardis.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | from Tardis import Util, TardisDB, Config 32 | import argparse, logging 33 | import os.path 34 | import os 35 | import sys 36 | import pwd 37 | import subprocess 38 | 39 | logger = None 40 | 41 | def getShell(): 42 | if 'SHELL' in os.environ: 43 | return os.environ['SHELL'] 44 | try: 45 | pwdEntry = pwd.getpwuid(os.getuid()) 46 | return pwdEntry[6] 47 | except KeyError: 48 | pass 49 | return '/bin/bash' 50 | 51 | def validateShell(shell): 52 | shells = list(map(str.strip, open('/etc/shells', 'r').readlines())) 53 | if not shell in shells: 54 | logger.error('%s is not a valid shell', shell) 55 | return (shell in shells) 56 | 57 | def processArgs(): 58 | parser = argparse.ArgumentParser(description='Encrypt the database', add_help = False) 59 | 60 | (_, remaining) = Config.parseConfigOptions(parser) 61 | Config.addCommonOptions(parser) 62 | Config.addPasswordOptions(parser) 63 | 64 | parser.add_argument('--shell', dest='shell', default=getShell(), help='Shell to use. Default: %(default)s') 65 | 66 | parser.add_argument('--verbose', '-v', action='count', default=0, dest='verbose', help='Increase the verbosity') 67 | 68 | parser.add_argument('--help', '-h', action='help'); 69 | 70 | Util.addGenCompletions(parser) 71 | 72 | args = parser.parse_args(remaining) 73 | 74 | return args 75 | 76 | def main(): 77 | global logger 78 | crypto = None 79 | 80 | args = processArgs() 81 | 82 | Util.setupLogging(args.verbose, levels=[logging.WARNING, logging.DEBUG]) 83 | logger = logging.getLogger('') 84 | 85 | if not validateShell(args.shell): 86 | sys.exit(1) 87 | 88 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog) 89 | 90 | try: 91 | Util.setupDataConnection(args.repo, password, args.keys) 92 | except TardisDB.AuthenticationFailed as e: 93 | logger.error("Authentication failed") 94 | sys.exit(1) 95 | 96 | try: 97 | logger.debug("Setting environment variables") 98 | if password: 99 | pwFileName = ".tardis-" + str(os.getpid()) 100 | pwFilePath = os.path.join(os.path.expanduser("~"), pwFileName) 101 | logger.debug("Storing password in %s", pwFilePath) 102 | with os.fdopen(os.open(pwFilePath, os.O_WRONLY | os.O_CREAT, 0o400), 'w') as handle: 103 | handle.write(password) 104 | os.environ['TARDIS_PWFILE'] = pwFilePath 105 | os.environ['TARDIS_CLIENT'] = args.client 106 | os.environ['TARDIS_DB'] = args.repo 107 | if args.keys: 108 | os.environ['TARDIS_KEYFILE'] = os.path.abspath(args.keys) 109 | 110 | prompt = os.environ.get('PS1') 111 | if prompt: 112 | os.environ['PS1'] = f'TARDIS: {args.client}: {PS1}' 113 | 114 | logger.warning("Spawning interactive shell with security preauthenticated.") 115 | 116 | # Run the shell, and wait 117 | status = subprocess.run(args.shell) 118 | 119 | # Check the return code. 120 | if status.returncode != 0: 121 | logger.warning("Child exited with status %d", status.returncode) 122 | 123 | logger.warning("Returned to unauthenticated environment") 124 | finally: 125 | if password: 126 | try: 127 | os.unlink(pwFilePath) 128 | except Exception as e: 129 | logger.critical("Unable to delete password file: %s :: %s", pwFilePath, str(e)) 130 | 131 | sys.exit(status.returncode) 132 | 133 | if __name__ == "__main__": 134 | main() 135 | -------------------------------------------------------------------------------- /tools/decryptFile.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 3 | # 4 | # Tardis: A Backup System 5 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 6 | # kolding@washington.edu 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # * Neither the name of the copyright holder nor the 17 | # names of its contributors may be used to endorse or promote products 18 | # derived from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import logging 34 | import os.path 35 | import os 36 | import sys 37 | import binascii 38 | 39 | from Tardis import Util, Config 40 | 41 | logger: logging.Logger 42 | 43 | def reader(quiet): 44 | prompt = '' if quiet else '--> ' 45 | try: 46 | while True: 47 | yield input(prompt) 48 | except EOFError: 49 | pass 50 | 51 | def decryptFile(infile, outfile, size, crypt, authenticate=True): 52 | # Get the IV, if it's not specified. 53 | infile.seek(0, os.SEEK_SET) 54 | iv = infile.read(crypt.ivLength) 55 | 56 | logger.debug("Got IV: %d %s", len(iv), binascii.hexlify(iv)) 57 | 58 | # Create the cipher 59 | encryptor = crypt.getContentEncryptor(iv) 60 | 61 | contentSize = size - crypt.ivLength - encryptor.getDigestSize() 62 | #self.logger.info("Computed Size: %d. Specified size: %d. Diff: %d", ctSize, size, (ctSize - size)) 63 | 64 | rem = contentSize 65 | blocksize = 64 * 1024 66 | last = False 67 | while rem > 0: 68 | readsize = blocksize if rem > blocksize else rem 69 | if rem <= blocksize: 70 | last = True 71 | ct = infile.read(readsize) 72 | pt = encryptor.decrypt(ct, last) 73 | if last: 74 | # ie, we're the last block 75 | digest = infile.read(encryptor.getDigestSize()) 76 | logger.debug("Got HMAC Digest: %d %s", len(digest), binascii.hexlify(digest)) 77 | readsize += len(digest) 78 | if authenticate: 79 | try: 80 | encryptor.verify(digest) 81 | except Exception: 82 | logger.debug("HMAC's: File: %-128s Computed: %-128s", binascii.hexlify(digest), binascii.hexlify(encryptor.digest())) 83 | raise Exception("HMAC did not authenticate.") 84 | outfile.write(pt) 85 | rem -= readsize 86 | 87 | def processArgs(): 88 | parser = argparse.ArgumentParser(description='Decrypt a File', fromfile_prefix_chars='@', add_help=False) 89 | 90 | (_, remaining) = Config.parseConfigOptions(parser) 91 | Config.addCommonOptions(parser) 92 | Config.addPasswordOptions(parser) 93 | 94 | parser.add_argument('--output', '-o', type=argparse.FileType('wb'), default=sys.stdout.buffer, help='output file (default: stdout)') 95 | parser.add_argument('--from_cache', '-c', default=False, action='store_true', help='Read a cached file') 96 | parser.add_argument('--noauth', '-n', default=False, action='store_true', help='Do not authenticate file info') 97 | parser.add_argument('--help', '-h', action='help') 98 | parser.add_argument('name', nargs=1, help="Pathnames to decrypt") 99 | 100 | Util.addGenCompletions(parser) 101 | 102 | args = parser.parse_args(remaining) 103 | 104 | return args 105 | 106 | def main(): 107 | global logger 108 | logging.basicConfig(level=logging.INFO) 109 | logger = logging.getLogger('') 110 | args = processArgs() 111 | password = Util.getPassword(args.password, args.passwordfile, args.passwordprog) 112 | 113 | _, cache, crypto, _ = Util.setupDataConnection(args.repo, password, args.keys) 114 | 115 | name = args.name[0] 116 | 117 | if args.from_cache: 118 | sz = cache.size(name) 119 | infile = cache.open(name, "rb") 120 | else: 121 | st = os.stat(name) 122 | sz = st.st_size 123 | infile = open(name, "rb") 124 | decryptFile(infile, args.output, sz, crypto, authenticate=not args.noauth) 125 | 126 | if __name__ == "__main__": 127 | main() 128 | -------------------------------------------------------------------------------- /src/Tardis/Converters/convert1to2.py: -------------------------------------------------------------------------------- 1 | # vim: set et sw=4 sts=4 fileencoding=utf-8: 2 | # 3 | # Tardis: A Backup System 4 | # Copyright 2013-2025, Eric Koldinger, All Rights Reserved. 5 | # kolding@washington.edu 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # * Neither the name of the copyright holder nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | # POSSIBILITY OF SUCH DAMAGE. 30 | 31 | import os.path 32 | import sqlite3 33 | import sys 34 | 35 | 36 | def processDir(path, bset, parent, device): 37 | s = conn.execute("UPDATE Files SET ParentDev = :device WHERE Parent = :parent AND :bset BETWEEN FirstSet AND LastSet", 38 | {"parent": parent, "device": device, "bset": bset}) 39 | s = conn.execute("UPDATE Files SET Device = :device WHERE Parent = :parent AND Device IS NULL AND :bset BETWEEN FirstSet AND LastSet", 40 | {"parent": parent, "device": device, "bset": bset}) 41 | s = conn.execute("SELECT Name, INode, Device, ParentDev FROM Files JOIN Names ON Files.Nameid = Names.Nameid WHERE Parent = :parent AND ParentDev != Device AND ParentDev != 0 AND :bset BETWEEN FirstSet AND LastSet", 42 | {"parent": parent, "device": device, "bset": bset}) 43 | for row in s.fetchall(): 44 | name = row[0] 45 | inode = row[1] 46 | device = row[2] 47 | parentdev = row[3] 48 | sub = os.path.join(path, name) 49 | print(f" {sub} ({inode}) has different device from parent: {device} {parentdev}") 50 | s = conn.execute("SELECT Name, inode, device FROM Files JOIN Names ON Files.Nameid = Names.Nameid WHERE Parent = :parent AND dir = 1 AND :bset BETWEEN FirstSet AND LastSet", 51 | {"parent": parent, "bset": bset}) 52 | for row in s.fetchall(): 53 | name = row[0] 54 | inode = row[1] 55 | device = row[2] 56 | sub = os.path.join(path, name) 57 | processDir(sub, bset, inode, device) 58 | 59 | schemaFile = "schema/tardis.sql" 60 | ## Update this list of files to express all top level mount points. 61 | ## At this point, interior mounted files are not updated. 62 | topfiles = [ 63 | ("etc", 2082), 64 | ("GITROOT", 2305), 65 | ("CVSROOT", 2305), 66 | ("home", 2305), 67 | ("music", 2305), 68 | ("pictures", 2305), 69 | ("videos", 2097), 70 | ] 71 | 72 | if len(sys.argv) > 1: 73 | db = sys.argv[1] 74 | else: 75 | db = "tardis.db" 76 | 77 | conn = sqlite3.connect(db) 78 | 79 | s = conn.execute('SELECT Value FROM Config WHERE Key = "SchemaVersion"') 80 | t = s.fetchone() 81 | if t[0] != 1: 82 | print(f"Invalid database schema version: {t[0]}") 83 | sys.exit(1) 84 | 85 | conn.execute("ALTER TABLE Files ADD COLUMN Device INTEGER") 86 | conn.execute("ALTER TABLE Files ADD COLUMN ParentDev INTEGER") 87 | conn.execute("UPDATE Files SET ParentDev = 0 WHERE Parent = 0") 88 | 89 | for i in topfiles: 90 | (name, device) = i 91 | print(f"Updating {name} to device {device}") 92 | s = conn.execute("UPDATE Files SET Device = :device " 93 | "WHERE Inode = (SELECT Inode FROM Files JOIN Names ON Files.nameid = Names.nameid AND ParentDev = 0 and Names.name = :name)", 94 | {"name": name, "device": device}) 95 | 96 | s = conn.execute("SELECT BackupSet, Name FROM Backups ORDER BY BackupSet ASC") 97 | for row in s.fetchall(): 98 | bset = row[0] 99 | name = row[1] 100 | print(f"Processing set {name} ({bset})") 101 | processDir("/", bset, 0, 0) 102 | 103 | print("Done updating. Rearranging tables to meet new schema") 104 | 105 | # Rename the orginal table and delete the vfiles 106 | conn.execute("ALTER TABLE Files RENAME TO Temp") 107 | conn.execute("DROP VIEW VFiles") 108 | 109 | with open(schemaFile, "r") as f: 110 | script = f.read() 111 | conn.executescript(script) 112 | 113 | conn.execute("INSERT INTO Files(NameId, FirstSet, LastSet, Inode, Device, Parent, ParentDev, ChecksumId, Dir, Link, MTime, CTime, ATime, Mode, UID, GID, NLinks)" 114 | "SELECT NameId, FirstSet, LastSet, Inode, Device, Parent, ParentDev, ChecksumId, Dir, Link, MTime, CTime, ATime, Mode, UID, GID, NLinks FROM Temp") 115 | conn.execute("DROP TABLE Temp") 116 | conn.execute('INSERT OR REPLACE INTO Config (Key, Value) VALUES ("SchemaVersion", "2")') 117 | --------------------------------------------------------------------------------