Backup and restore
What a complete backup is
Section titled “What a complete backup is”A complete backup has two parts:
- A dump of the whole database.
- The
TIMOTHY_MASTER_KEYvalue from your.envfile.
The database holds your provider keys and account credentials only in encrypted form. The master key decrypts them. If you lose the key, or restore with a different one, every stored credential is lost. You then have to enter every provider key and reconnect every account.
Back up the key once, and keep it apart from the dumps. Never create a new master key for an existing install. A new key cannot read the credentials sealed with the old one.
Back up a source build
Section titled “Back up a source build”The database has no port on your machine, so the backup runs through the container. The repository has a script for it. From the repository folder:
scripts/backup-db.shIt writes a compressed dump to backups/timothy-<UTC timestamp>.sql.gz. To write somewhere else, such as a disk outside the host, give a folder:
scripts/backup-db.sh /mnt/nas/timothyTwo settings change how it works:
| Variable | Default | Meaning |
|---|---|---|
BACKUP_DIR |
the backups folder in the repository |
Where dumps go. A folder you pass on the command line wins. |
BACKUP_KEEP |
14 |
How many dumps to keep. Older ones are deleted. |
The script asks no questions, prints no secrets and fails loudly on any error. You can run it from cron, for example every night at 03:15:
15 3 * * * /path/to/timothy/scripts/backup-db.sh >> /var/log/timothy-backup.log 2>&1Each run dumps the whole database. Never limit a dump to some tables. A dump without the secrets table restores an install with every credential missing. The script refuses to write a dump that has no secrets table.
Back up a quick start install
Section titled “Back up a quick start install”The quick start install does not include the backup script. Run the same dump command the script uses, from the install folder:
cd ~/timothydocker compose exec -T postgres \ sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump -U timothy -d timothy --no-owner --no-privileges' \ | gzip -c > timothy-$(date -u +%Y%m%dT%H%M%SZ).sql.gzCheck that the dump contains the secrets table. The count must be 1 or more:
gzip -dc timothy-<timestamp>.sql.gz | grep -c 'CREATE TABLE public.secrets'Restore onto a fresh host
Section titled “Restore onto a fresh host”These steps use a source build. For a quick start install, run the docker compose commands from the install folder and leave out -f deploy/docker-compose.yml.
-
Put the repository and
deploy/.envin place. The.envfile must have the sameTIMOTHY_MASTER_KEYas the install the dump came from.POSTGRES_PASSWORDcan be new. -
Start only the database, so nothing writes to it during the restore:
Terminal window docker compose -f deploy/docker-compose.yml up -d postgres -
Load the dump.
ON_ERROR_STOP=1makes a partial restore fail instead of leaving a half-filled database:Terminal window gunzip -c backups/timothy-<timestamp>.sql.gz \| docker compose -f deploy/docker-compose.yml exec -T postgres \sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U timothy -d timothy -v ON_ERROR_STOP=1'The dump creates the schema itself, so restore into an empty database. If this database already ran Timothy once, drop it and create it again first:
Terminal window docker compose -f deploy/docker-compose.yml exec -T postgres \sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U timothy -d postgres -c "DROP DATABASE timothy" -c "CREATE DATABASE timothy"' -
Check that the data is there, including the secrets table:
Terminal window docker compose -f deploy/docker-compose.yml exec -T postgres \sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U timothy -d timothy -c "select count(*) from secrets" -c "select count(*) from session_events"' -
Start the rest of the stack:
Terminal window make upOn a quick start install, run
docker compose up -dinstead. -
Check that the credentials decrypt. This proves the master key matches. In the web interface, open “Settings”, then “Providers”, and press “Test” on a provider.
A passing test means the key is right. If the test fails with a decrypt error, or the gateway or brain logs show one, the master key in .env is not the one that sealed these rows. Put the correct key back. Do not enter the credentials again, or every older secret stays unreadable.