Read all five documents against the code and fix what the sixth review found
check / check (push) Successful in 12m5s
check / check (push) Successful in 12m5s
Fixes the four findings of the sixth review: `snapshot remove` with `--local-only`, the two comma-chained permission lists, six undefined terms, and the sentence about an unfinished snapshot in the local index. Then reads every sentence of `README.md`, `ARCHITECTURE.md`, `docs/DATAMODEL.md`, `docs/REPOSTRUCTURE.md` and the comments in `config.example.yml` against the code for four faults: a command's needs or behaviour stated without regard to a flag, a false absolute, three or more things chained with commas, and a technical word not defined on first use. Each sentence changed is listed on the pull request. The help string of `snapshot remove --dry-run` now says it needs no permission to delete. Model: fable-5-1
This commit is contained in:
1 parent
a133b8838e
commit
46b2a9fc78
6 files changed
+341
-193
No files matched your search
+36
-26
@@ -63,13 +63,13 @@ source files
|
|||||||
[`docs/DATAMODEL.md`](docs/DATAMODEL.md#code) says which type serves which
|
[`docs/DATAMODEL.md`](docs/DATAMODEL.md#code) says which type serves which
|
||||||
table.
|
table.
|
||||||
- `internal/storage`: the `Storer` interface and its three implementations, for
|
- `internal/storage`: the `Storer` interface and its three implementations, for
|
||||||
S3, a local directory and rclone.
|
S3, a local directory and [rclone](https://rclone.org/).
|
||||||
- `internal/s3`: the S3 client that the S3 `Storer` uses.
|
- `internal/s3`: the S3 client that the S3 `Storer` uses.
|
||||||
- `internal/config`: loads and checks the config file.
|
- `internal/config`: loads and checks the config file.
|
||||||
- `internal/ui`: formats the status lines, warnings and errors a user sees.
|
- `internal/ui`: formats the status lines, warnings and errors a user sees.
|
||||||
- `internal/log`: the logger. It writes to stderr.
|
- `internal/log`: the logger. It writes to stderr.
|
||||||
- `internal/pidlock`: the lock that keeps two data-changing commands from
|
- `internal/pidlock`: the lock that `RunWithApp` takes for a command that
|
||||||
running at once.
|
changes data, so that only one runs at a time.
|
||||||
- `internal/globals`: the version, commit and commit date, set when the binary
|
- `internal/globals`: the version, commit and commit date, set when the binary
|
||||||
is built.
|
is built.
|
||||||
- `internal/types`: distinct types for IDs, hashes and paths.
|
- `internal/types`: distinct types for IDs, hashes and paths.
|
||||||
@@ -114,8 +114,12 @@ A command runs through `cli.RunOperation`. It works like this:
|
|||||||
waits for the function to return, so that its temporary files are removed.
|
waits for the function to return, so that its temporary files are removed.
|
||||||
- The wait ends after 30 seconds at most.
|
- The wait ends after 30 seconds at most.
|
||||||
|
|
||||||
The `config` commands, `database delete`, `completion` and `version` do not
|
These commands do not build the application:
|
||||||
build the application.
|
|
||||||
|
- the `config` commands
|
||||||
|
- `database delete`
|
||||||
|
- `completion`
|
||||||
|
- `version`
|
||||||
|
|
||||||
## Backup
|
## Backup
|
||||||
|
|
||||||
@@ -163,7 +167,8 @@ A new `Scanner` is created for each snapshot, from `ScannerFactory`. It owns a
|
|||||||
- `repairInterruptedBlobs` deletes the records of blobs whose upload never
|
- `repairInterruptedBlobs` deletes the records of blobs whose upload never
|
||||||
finished. Their data is then stored again.
|
finished. Their data is then stored again.
|
||||||
- `loadDatabaseState` loads the known files and chunks into memory.
|
- `loadDatabaseState` loads the known files and chunks into memory.
|
||||||
- `scanPhase` walks the path. It sorts each entry into changed or unchanged.
|
- `scanPhase` walks the path. It sorts each regular file and symlink into
|
||||||
|
changed or unchanged.
|
||||||
- `detectDeletedFiles` counts the files that the previous snapshot of this name
|
- `detectDeletedFiles` counts the files that the previous snapshot of this name
|
||||||
had and that are now gone.
|
had and that are now gone.
|
||||||
- `batchAddFilesToSnapshot` records the unchanged files in the new snapshot.
|
- `batchAddFilesToSnapshot` records the unchanged files in the new snapshot.
|
||||||
@@ -184,8 +189,8 @@ Data is stored once at two levels.
|
|||||||
|
|
||||||
- **Files.** A file is not read again when its record in the index still matches
|
- **Files.** A file is not read again when its record in the index still matches
|
||||||
it.
|
it.
|
||||||
- **Chunks.** A chunk is named by the SHA-256 hash of its content. A chunk whose
|
- **Chunks.** A chunk is named by the SHA-256 hash of its content. A chunk that
|
||||||
hash is already in the index is not stored again.
|
an uploaded blob already holds, according to the index, is not stored again.
|
||||||
|
|
||||||
A file's record matches when all of these are unchanged:
|
A file's record matches when all of these are unchanged:
|
||||||
|
|
||||||
@@ -208,8 +213,8 @@ on the content, so an insertion early in a file moves only nearby chunks.
|
|||||||
this size. Only a file's last chunk can be smaller than a quarter of it. With
|
this size. Only a file's last chunk can be smaller than a quarter of it. With
|
||||||
the default of 10 MB, those limits are 40 MB and 2.5 MB.
|
the default of 10 MB, those limits are 40 MB and 2.5 MB.
|
||||||
|
|
||||||
`ChunkReaderStreaming` passes each chunk to a callback as it is found. The whole
|
`ChunkReaderStreaming` passes each chunk to a callback as it is found. The
|
||||||
file is never held in memory.
|
chunker buffers at most twice the largest chunk size.
|
||||||
|
|
||||||
### Blobs
|
### Blobs
|
||||||
|
|
||||||
@@ -217,8 +222,9 @@ file is never held in memory.
|
|||||||
|
|
||||||
- It gives the blob a UUID and creates a temporary file.
|
- It gives the blob a UUID and creates a temporary file.
|
||||||
- It adds each chunk's bytes to a running SHA-256 hash.
|
- It adds each chunk's bytes to a running SHA-256 hash.
|
||||||
- In the same pass it compresses the bytes with zstd and encrypts them with
|
- In the same pass it compresses the bytes with
|
||||||
age. The result goes to the temporary file.
|
[zstd](https://github.com/facebook/zstd) and encrypts them with
|
||||||
|
[age](https://age-encryption.org/). The result goes to the temporary file.
|
||||||
- It records each chunk's offset and length within the blob.
|
- It records each chunk's offset and length within the blob.
|
||||||
- It closes the blob when the next chunk would pass `blob_size_limit`, and at
|
- It closes the blob when the next chunk would pass `blob_size_limit`, and at
|
||||||
the end of each path.
|
the end of each path.
|
||||||
@@ -250,8 +256,8 @@ chunks is uploaded.
|
|||||||
- `MarkSnapshotComplete` marks the snapshot complete.
|
- `MarkSnapshotComplete` marks the snapshot complete.
|
||||||
|
|
||||||
Completion is recorded last on purpose. A backup that stops during the upload
|
Completion is recorded last on purpose. A backup that stops during the upload
|
||||||
leaves the snapshot unfinished, and the next backup removes it and stores its
|
leaves the snapshot unfinished, and the next backup removes it and backs up its
|
||||||
data again.
|
files again.
|
||||||
|
|
||||||
`ExportSnapshotMetadata` works on a copy of the index, never on the index
|
`ExportSnapshotMetadata` works on a copy of the index, never on the index
|
||||||
itself:
|
itself:
|
||||||
@@ -282,11 +288,13 @@ therefore marks a backup that did not finish.
|
|||||||
temporary directory.
|
temporary directory.
|
||||||
- `getFilesToRestore` selects the files, from the paths given.
|
- `getFilesToRestore` selects the files, from the paths given.
|
||||||
- `restoreAllFiles` downloads blobs and writes the files.
|
- `restoreAllFiles` downloads blobs and writes the files.
|
||||||
- With `--verify`, `verifyRestoredFiles` reads each restored file and checks its
|
- With `--verify`, `verifyRestoredFiles` reads each restored regular file and
|
||||||
chunk hashes.
|
checks its chunk hashes.
|
||||||
|
|
||||||
The restore does not read the local index or the manifest. It finds each file's
|
Given a snapshot ID, the restore does not read the local index. Given a remote
|
||||||
chunks and blobs through the downloaded database.
|
key, it reads the local index only to check the destination marker. It does not
|
||||||
|
read the manifest. It finds each file's chunks and blobs through the downloaded
|
||||||
|
database.
|
||||||
|
|
||||||
### Checks on the downloaded database
|
### Checks on the downloaded database
|
||||||
|
|
||||||
@@ -313,12 +321,13 @@ database is honest. The restore therefore checks it:
|
|||||||
- `restoreSweeper` deletes a cached blob once no remaining file needs it.
|
- `restoreSweeper` deletes a cached blob once no remaining file needs it.
|
||||||
|
|
||||||
A regular file is created with mode `0600` and gets its stored mode after its
|
A regular file is created with mode `0600` and gets its stored mode after its
|
||||||
content is written. A directory gets its stored mode, owner and modification
|
content is written. A directory gets its stored mode and modification time after
|
||||||
time after the whole restore.
|
the whole restore, and its owner when the command runs as root.
|
||||||
|
|
||||||
## Verify
|
## Verify
|
||||||
|
|
||||||
`Vaultik.VerifySnapshotWithOptions` checks a snapshot without the private key:
|
Without `--deep`, `Vaultik.VerifySnapshotWithOptions` checks a snapshot without
|
||||||
|
the private key:
|
||||||
|
|
||||||
- It downloads the manifest.
|
- It downloads the manifest.
|
||||||
- It checks that the snapshot's encrypted database is present.
|
- It checks that the snapshot's encrypted database is present.
|
||||||
@@ -337,7 +346,7 @@ time after the whole restore.
|
|||||||
|
|
||||||
## Prune
|
## Prune
|
||||||
|
|
||||||
`Vaultik.Prune` does three steps:
|
`Vaultik.Prune` calls `EnsureStorageBinding` and then does three steps:
|
||||||
|
|
||||||
- `CleanupLocalSnapshots` drops from the local index each snapshot whose
|
- `CleanupLocalSnapshots` drops from the local index each snapshot whose
|
||||||
manifest is gone from the destination.
|
manifest is gone from the destination.
|
||||||
@@ -382,17 +391,18 @@ With no `storage_url`, it builds the S3 implementation from the `s3` settings.
|
|||||||
Two checks keep the local index and the destination in step.
|
Two checks keep the local index and the destination in step.
|
||||||
|
|
||||||
- `checkDestinationMarker` looks for `vaultik-destination.txt`. It fails when
|
- `checkDestinationMarker` looks for `vaultik-destination.txt`. It fails when
|
||||||
the file is reported missing while the local index holds snapshots.
|
the file is reported missing while the local index holds snapshots. It skips
|
||||||
|
the check when the local index belongs to another destination.
|
||||||
- `EnsureStorageBinding` records the configured destination in the local index
|
- `EnsureStorageBinding` records the configured destination in the local index
|
||||||
on first use. Later it fails when the config names a different destination.
|
on first use. Later it fails when the config names a different destination.
|
||||||
|
|
||||||
Every command that changes data calls `EnsureStorageBinding` first.
|
Every command that takes the lock calls `EnsureStorageBinding` first.
|
||||||
|
|
||||||
## Concurrency
|
## Concurrency
|
||||||
|
|
||||||
- Each open database uses one connection, so its queries run one at a time.
|
- Each open database uses one connection, so its queries run one at a time.
|
||||||
- The index runs in SQLite's WAL mode. A second process can therefore read it
|
- The index runs in SQLite's write-ahead log (WAL) mode. A second process can
|
||||||
while a backup writes to it.
|
therefore read it while a backup writes to it.
|
||||||
- `Packer` guards its state with a mutex.
|
- `Packer` guards its state with a mutex.
|
||||||
- `Scanner` holds `packerMu` while a chunk is added to the packer and while a
|
- `Scanner` holds `packerMu` while a chunk is added to the packer and while a
|
||||||
blob is closed.
|
blob is closed.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
`vaultik` is an MIT-licensed command-line tool for incremental, encrypted
|
`vaultik` is an MIT-licensed command-line tool for incremental, encrypted
|
||||||
backups, written in Go by [@sneak](https://sneak.berlin). It encrypts your files
|
backups, written in Go by [@sneak](https://sneak.berlin). It encrypts your files
|
||||||
with a public key and uploads them to an S3-compatible object store, a local
|
with a public key and uploads them to an S3-compatible object store, a local
|
||||||
directory or an rclone remote.
|
directory or any storage service that [rclone](https://rclone.org/) supports.
|
||||||
|
|
||||||
The machine being backed up holds only the public key. It cannot decrypt its own
|
The machine being backed up holds only the public key. It cannot decrypt its own
|
||||||
backups. You keep the private key somewhere else. A backup never needs it.
|
backups. You keep the private key somewhere else. A backup never needs it.
|
||||||
@@ -25,13 +25,14 @@ It was written to meet these requirements together:
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Encryption with [age](https://age-encryption.org/) (X25519 and
|
- Encryption with [age](https://age-encryption.org/), to one or more public
|
||||||
ChaCha20-Poly1305), to one or more public keys.
|
keys. age uses X25519 keys and the ChaCha20-Poly1305 cipher.
|
||||||
- Incremental backups. A file is read again only when it has changed, or when
|
- Incremental backups. A file is read again only when it has changed, or when
|
||||||
part of its content has to be stored again.
|
part of its content has to be stored again.
|
||||||
- Deduplication. Identical content is stored once, across files and across
|
- Deduplication. Identical content is stored once, across files and across
|
||||||
snapshots.
|
snapshots.
|
||||||
- zstd compression at a level you choose.
|
- Compression with [zstd](https://github.com/facebook/zstd), at a level you
|
||||||
|
choose.
|
||||||
- Small files are packed together into large objects. This keeps the number of
|
- Small files are packed together into large objects. This keeps the number of
|
||||||
S3 requests low.
|
S3 requests low.
|
||||||
- File contents, names, paths and metadata are encrypted at the destination.
|
- File contents, names, paths and metadata are encrypted at the destination.
|
||||||
@@ -91,12 +92,13 @@ vaultik snapshot list
|
|||||||
|
|
||||||
The default config is written for macOS. It backs up your home directory and
|
The default config is written for macOS. It backs up your home directory and
|
||||||
`/Applications`, with a list of excludes. A configured path that does not exist
|
`/Applications`, with a list of excludes. A configured path that does not exist
|
||||||
stops the backup. On another system, run `vaultik config edit` first and change
|
stops the backup, unless you give `--skip-errors`. On another system, run
|
||||||
the `snapshots` section.
|
`vaultik config edit` first and change the `snapshots` section.
|
||||||
|
|
||||||
On macOS, grant your terminal app Full Disk Access before the first backup. The
|
On macOS, grant your terminal app Full Disk Access before the first backup. The
|
||||||
setting is under System Settings, Privacy & Security, Full Disk Access. Without
|
setting is under System Settings, Privacy & Security, Full Disk Access. Without
|
||||||
it the backup stops with a permission error on a protected directory.
|
it the backup stops with a permission error on a protected directory, unless you
|
||||||
|
give `--skip-errors`.
|
||||||
|
|
||||||
## Everyday use
|
## Everyday use
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ Use that remote key in place of the snapshot ID:
|
|||||||
```sh
|
```sh
|
||||||
export VAULTIK_AGE_SECRET_KEY="$(cat vaultik_backup_private_key.txt)"
|
export VAULTIK_AGE_SECRET_KEY="$(cat vaultik_backup_private_key.txt)"
|
||||||
|
|
||||||
# Restore everything, then check the chunk hashes of every restored file.
|
# Restore everything, then check the chunk hashes of the restored files.
|
||||||
vaultik snapshot restore --verify <remote-key> ~/vaultik-restore
|
vaultik snapshot restore --verify <remote-key> ~/vaultik-restore
|
||||||
|
|
||||||
# Optionally, also check every blob of the snapshot at the destination.
|
# Optionally, also check every blob of the snapshot at the destination.
|
||||||
@@ -194,10 +196,14 @@ vaultik version
|
|||||||
|
|
||||||
### What every command needs
|
### What every command needs
|
||||||
|
|
||||||
`config init`, `completion` and `version` need nothing.
|
`completion` and `version` need nothing. `config init` needs write access to the
|
||||||
|
config directory.
|
||||||
|
|
||||||
`config edit`, `config get` and `config set` need a config file that exists. It
|
These commands need a config file that exists, even an incomplete one:
|
||||||
does not have to be complete.
|
|
||||||
|
- `config edit`
|
||||||
|
- `config get`
|
||||||
|
- `config set`
|
||||||
|
|
||||||
Every other command needs a config file that loads. See
|
Every other command needs a config file that loads. See
|
||||||
[Configuration](#configuration) for the checks.
|
[Configuration](#configuration) for the checks.
|
||||||
@@ -219,24 +225,34 @@ sets file owners only when it runs as root.
|
|||||||
[Where the config file is](#where-the-config-file-is).
|
[Where the config file is](#where-the-config-file-is).
|
||||||
- `--verbose`, `-v`: write informational log records to stderr.
|
- `--verbose`, `-v`: write informational log records to stderr.
|
||||||
- `--debug`: write debug log records to stderr.
|
- `--debug`: write debug log records to stderr.
|
||||||
- `--quiet`, `-q`: print only warnings, errors and the output a script reads.
|
- `--quiet`, `-q`: leave out the banner, the status lines, the tables and the
|
||||||
See [Output](#output).
|
reports. See [Output](#output).
|
||||||
- `--skip-errors`: skip a file that cannot be read during a backup, or cannot be
|
- `--skip-errors`: skip a file that cannot be read during a backup, or cannot be
|
||||||
restored during a restore. Without it the command stops at the first such
|
restored during a restore. Without it the command stops at the first such
|
||||||
file.
|
file.
|
||||||
|
|
||||||
|
`--quiet` and `--cron` override `--verbose` and `--debug`.
|
||||||
|
|
||||||
`--skip-errors` applies to `snapshot create` and `snapshot restore` only. A
|
`--skip-errors` applies to `snapshot create` and `snapshot restore` only. A
|
||||||
command that skipped anything still finishes, and then exits non-zero.
|
command that skipped anything still finishes, and then exits non-zero.
|
||||||
|
|
||||||
### Durations
|
### Durations
|
||||||
|
|
||||||
`--keep-newer-than`, `--older-than` and `prune_grace_period` take a duration.
|
These take a duration:
|
||||||
|
|
||||||
|
- `--keep-newer-than`
|
||||||
|
- `--older-than`
|
||||||
|
- `prune_grace_period`
|
||||||
|
|
||||||
|
These units are accepted:
|
||||||
|
|
||||||
- `d`: days.
|
- `d`: days.
|
||||||
- `w`: weeks.
|
- `w`: weeks.
|
||||||
- `mo`: months of 30 days.
|
- `mo`: months of 30 days.
|
||||||
- `y`: years of 365 days.
|
- `y`: years of 365 days.
|
||||||
- `h`, `m` and `s`: hours, minutes and seconds, as in `36h` or `90m`.
|
- `h`: hours, as in `36h`.
|
||||||
|
- `m`: minutes, as in `90m`.
|
||||||
|
- `s`: seconds.
|
||||||
|
|
||||||
`m` means minutes and `mo` means months. Days, weeks, months and years take
|
`m` means minutes and `mo` means months. Days, weeks, months and years take
|
||||||
whole numbers and can be combined, as in `2w3d`.
|
whole numbers and can be combined, as in `2w3d`.
|
||||||
@@ -249,9 +265,9 @@ Writes a default config file, with a comment that explains each setting it
|
|||||||
lists.
|
lists.
|
||||||
|
|
||||||
- It writes to the path from `--config`, or else from `VAULTIK_CONFIG`.
|
- It writes to the path from `--config`, or else from `VAULTIK_CONFIG`.
|
||||||
- Otherwise it writes to `config.yml` in the platform config directory. That is
|
- Otherwise it writes to `config.yml` in the platform config directory. By
|
||||||
`~/.config/vaultik/` on Linux and `~/Library/Application Support/vaultik/` on
|
default that is `~/.config/vaultik/` on Linux and
|
||||||
macOS.
|
`~/Library/Application Support/vaultik/` on macOS.
|
||||||
- As root it writes to `/etc/vaultik/config.yml` instead.
|
- As root it writes to `/etc/vaultik/config.yml` instead.
|
||||||
- It creates the file with mode `0600`, because the file can hold credentials.
|
- It creates the file with mode `0600`, because the file can hold credentials.
|
||||||
- It refuses to overwrite a file that exists.
|
- It refuses to overwrite a file that exists.
|
||||||
@@ -275,7 +291,8 @@ Sets one value in the config file. The key is a dotted path, as in
|
|||||||
- A key that does not exist yet is created. A missing parent becomes a list when
|
- A key that does not exist yet is created. A missing parent becomes a list when
|
||||||
the next part of the path is a number, and a map otherwise.
|
the next part of the path is a number, and a map otherwise.
|
||||||
- In a list, the index one past the end appends an entry.
|
- In a list, the index one past the end appends an entry.
|
||||||
- The command prints the key it set, and never the value.
|
- The command prints the key it set, and not the value. A refusal for a value
|
||||||
|
of the wrong type quotes the beginning of that value.
|
||||||
- It sets the file's mode to `0600` when group or others could read it.
|
- It sets the file's mode to `0600` when group or others could read it.
|
||||||
|
|
||||||
The command refuses a change that would stop the config from loading. An example
|
The command refuses a change that would stop the config from loading. An example
|
||||||
@@ -301,7 +318,8 @@ It needs:
|
|||||||
|
|
||||||
It does not need the private key.
|
It does not need the private key.
|
||||||
|
|
||||||
A file is read again when any of these holds:
|
A file is read when the local index does not know it, or when any of these
|
||||||
|
holds:
|
||||||
|
|
||||||
- Its size has changed since the last backup.
|
- Its size has changed since the last backup.
|
||||||
- Its modification time has changed.
|
- Its modification time has changed.
|
||||||
@@ -383,9 +401,9 @@ config.
|
|||||||
|
|
||||||
### `snapshot list`
|
### `snapshot list`
|
||||||
|
|
||||||
Lists the snapshots in the local index and the snapshots at the destination, as
|
Lists the finished snapshots in the local index and the snapshots at the
|
||||||
one table. Before the table, the command prints the snapshot names and paths in
|
destination, as one table. Before the table, the command prints the snapshot
|
||||||
the config.
|
names and paths in the config.
|
||||||
|
|
||||||
It needs permission to list and read at the destination. It needs no private
|
It needs permission to list and read at the destination. It needs no private
|
||||||
key.
|
key.
|
||||||
@@ -402,7 +420,8 @@ The table has these columns:
|
|||||||
A snapshot at the destination that is not in the local index is shown as
|
A snapshot at the destination that is not in the local index is shown as
|
||||||
`<remote only:...>`. The dots stand for the first 12 characters of its remote
|
`<remote only:...>`. The dots stand for the first 12 characters of its remote
|
||||||
key. Its last two columns read `<remote only>`, because those sizes come from
|
key. Its last two columns read `<remote only>`, because those sizes come from
|
||||||
the local index. Its timestamp is the time its backup finished.
|
the local index. Its timestamp is the time its manifest was written, at the end
|
||||||
|
of its backup.
|
||||||
|
|
||||||
The command reports these below the table:
|
The command reports these below the table:
|
||||||
|
|
||||||
@@ -424,7 +443,8 @@ marker file although the local index holds snapshots.
|
|||||||
- `remote_key`: the full 64-character remote key.
|
- `remote_key`: the full 64-character remote key.
|
||||||
- `timestamp`
|
- `timestamp`
|
||||||
- `compressed_size`
|
- `compressed_size`
|
||||||
- `uncompressed_size` and `new_chunk_size`: left out for a remote-only snapshot.
|
- `uncompressed_size` and `new_chunk_size`: left out when zero, and for a
|
||||||
|
remote-only snapshot.
|
||||||
- `locally_tracked`: whether the snapshot is in the local index.
|
- `locally_tracked`: whether the snapshot is in the local index.
|
||||||
- `remote_present`: whether the snapshot is at the destination. It is `null`
|
- `remote_present`: whether the snapshot is at the destination. It is `null`
|
||||||
when the destination could not be listed.
|
when the destination could not be listed.
|
||||||
@@ -469,7 +489,8 @@ variable wins when both are set. See [The private key](#the-private-key).
|
|||||||
- It downloads and decrypts every blob. It checks the hash of each chunk, and
|
- It downloads and decrypts every blob. It checks the hash of each chunk, and
|
||||||
the hash that names the blob.
|
the hash that names the blob.
|
||||||
|
|
||||||
It stops at the first blob that fails.
|
The presence check counts every missing or wrong-size blob. The download check
|
||||||
|
stops at the first blob that fails.
|
||||||
|
|
||||||
`--deep` proves that the stored data is intact and decrypts with your key. It
|
`--deep` proves that the stored data is intact and decrypts with your key. It
|
||||||
does not prove who wrote the data.
|
does not prove who wrote the data.
|
||||||
@@ -510,20 +531,22 @@ cannot be listed or has no marker file.
|
|||||||
Removes one snapshot. The argument must be a snapshot ID. A remote key is not
|
Removes one snapshot. The argument must be a snapshot ID. A remote key is not
|
||||||
accepted.
|
accepted.
|
||||||
|
|
||||||
It needs the [lock](#locking), and permission to list and delete at the
|
It needs the [lock](#locking). It needs permission to list and delete at the
|
||||||
destination. It needs no private key.
|
destination, unless you give `--local-only`. It needs no private key.
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|
||||||
- `--force`, `-f`: do not ask for confirmation.
|
- `--force`, `-f`: do not ask for confirmation.
|
||||||
- `--dry-run`: say what would be removed, and remove nothing.
|
- `--dry-run`: say what would be removed, and remove nothing. It needs no
|
||||||
|
permission to delete.
|
||||||
- `--local-only`: remove the snapshot from the local index only. The destination
|
- `--local-only`: remove the snapshot from the local index only. The destination
|
||||||
is not changed.
|
is not changed.
|
||||||
- `--json`: print the result as JSON. This also skips the confirmation.
|
- `--json`: print the result as JSON. This also skips the confirmation.
|
||||||
|
|
||||||
The command removes the snapshot from the local index. It also deletes the
|
The command removes the snapshot from the local index. Without `--local-only`,
|
||||||
snapshot's directory under `metadata/` at the destination. Its blobs stay. Run
|
it also deletes the snapshot's directory under `metadata/` at the destination.
|
||||||
`vaultik prune` afterwards to delete the blobs that no snapshot uses any more.
|
Its blobs stay. Run `vaultik prune` afterwards to delete the blobs that no
|
||||||
|
snapshot uses any more.
|
||||||
|
|
||||||
An ID that is in neither the local index nor the destination is an error. With
|
An ID that is in neither the local index nor the destination is an error. With
|
||||||
`--local-only`, the snapshot must be in the local index.
|
`--local-only`, the snapshot must be in the local index.
|
||||||
@@ -586,8 +609,8 @@ Restore into a new directory that only you can write, not a shared one such as
|
|||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|
||||||
- `--verify`: after the restore, read every restored file and check its chunk
|
- `--verify`: after the restore, read every restored regular file and check its
|
||||||
hashes.
|
chunk hashes.
|
||||||
|
|
||||||
With `--skip-errors`, a file that cannot be restored is reported and skipped,
|
With `--skip-errors`, a file that cannot be restored is reported and skipped,
|
||||||
and the command exits non-zero. `--verify` does not check a skipped file.
|
and the command exits non-zero. `--verify` does not check a skipped file.
|
||||||
@@ -666,12 +689,13 @@ Reports what the destination holds:
|
|||||||
It needs permission to list and read at the destination. It needs no private
|
It needs permission to list and read at the destination. It needs no private
|
||||||
key.
|
key.
|
||||||
|
|
||||||
When a manifest cannot be read, the figures for blobs that no manifest lists are
|
|
||||||
reported as unknown. That snapshot's own blob count and size are unknown too.
|
|
||||||
|
|
||||||
The command skips a name under `metadata/` that is not a remote key, with a
|
The command skips a name under `metadata/` that is not a remote key, with a
|
||||||
warning. It does not count a file under `blobs/` whose name is not a blob hash.
|
warning. It does not count a file under `blobs/` whose name is not a blob hash.
|
||||||
|
|
||||||
|
When a manifest cannot be read, that snapshot's blob count and size are unknown.
|
||||||
|
The figures for blobs that no manifest lists are unknown when a manifest cannot
|
||||||
|
be read or sits under a skipped name.
|
||||||
|
|
||||||
`--json` prints the report as JSON. Unknown figures are `null`.
|
`--json` prints the report as JSON. Unknown figures are `null`.
|
||||||
`unreadable_manifests` lists the remote key of each manifest that could not be
|
`unreadable_manifests` lists the remote key of each manifest that could not be
|
||||||
read. `skipped_manifest_count` counts the manifests under skipped names.
|
read. `skipped_manifest_count` counts the manifests under skipped names.
|
||||||
@@ -715,7 +739,12 @@ Use this command after you point the config at a different destination. See
|
|||||||
|
|
||||||
### `completion <shell>`
|
### `completion <shell>`
|
||||||
|
|
||||||
Prints a completion script for `bash`, `zsh`, `fish` or `powershell`.
|
Prints a completion script for one of these shells:
|
||||||
|
|
||||||
|
- `bash`
|
||||||
|
- `zsh`
|
||||||
|
- `fish`
|
||||||
|
- `powershell`
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# zsh, for the current session
|
# zsh, for the current session
|
||||||
@@ -736,31 +765,51 @@ vaultik completion fish > ~/.config/fish/completions/vaultik.fish
|
|||||||
|
|
||||||
### `version`
|
### `version`
|
||||||
|
|
||||||
Prints the version, the commit, the commit date and the platform. It labels the
|
Prints these:
|
||||||
commit date `build date`. It also says when the binary is a development build,
|
|
||||||
not a release.
|
- the version
|
||||||
|
- the commit
|
||||||
|
- the commit date, labelled `build date`
|
||||||
|
- the Go version
|
||||||
|
- the platform
|
||||||
|
- the author
|
||||||
|
- the homepage
|
||||||
|
- the license
|
||||||
|
|
||||||
|
It also says when the binary is a development build, not a release.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The config file is YAML. `vaultik config init` writes one to start from, and
|
The config file is YAML. `vaultik config init` writes one to start from, and
|
||||||
[`config.example.yml`](config.example.yml) is a longer example.
|
[`config.example.yml`](config.example.yml) is a longer example.
|
||||||
|
|
||||||
A command refuses to start when the config does not load. The `config` commands,
|
A command refuses to start when the config does not load. These commands are
|
||||||
`completion` and `version` are the exceptions. The config loads when all of
|
the exceptions:
|
||||||
these hold:
|
|
||||||
|
- the `config` commands
|
||||||
|
- `completion`
|
||||||
|
- `version`
|
||||||
|
|
||||||
|
The config loads when it is valid YAML and all of these hold:
|
||||||
|
|
||||||
- Every entry in `age_recipients` is an age X25519 public key.
|
- Every entry in `age_recipients` is an age X25519 public key.
|
||||||
- `snapshots` has at least one name, and every name has at least one path.
|
- `snapshots` has at least one name, and every name has at least one path.
|
||||||
- A destination is set, with credentials when it is S3.
|
- A destination is set. Its `storage_url` has one of the three schemes under
|
||||||
|
[Destinations](#destinations). An S3 destination has credentials.
|
||||||
|
- Every `${ENV:NAME}` names an environment variable with a non-empty value.
|
||||||
- Every other setting below is within its limits.
|
- Every other setting below is within its limits.
|
||||||
|
|
||||||
`vaultik` warns when group or others can read the config file.
|
Every command that needs the local index warns when group or others can read the
|
||||||
|
config file.
|
||||||
|
|
||||||
A value written as `${ENV:NAME}` is replaced by the environment variable `NAME`.
|
A value written as `${ENV:NAME}` is replaced by the environment variable `NAME`.
|
||||||
This keeps a credential out of the file.
|
This keeps a credential out of the file.
|
||||||
|
|
||||||
A leading `~` is expanded to your home directory in snapshot paths, in
|
A leading `~` is expanded to your home directory in these places:
|
||||||
`index_path` and in a `file://~/...` destination.
|
|
||||||
|
- snapshot paths
|
||||||
|
- `index_path`
|
||||||
|
- a `file://~/...` destination
|
||||||
|
|
||||||
### Where the config file is
|
### Where the config file is
|
||||||
|
|
||||||
@@ -768,11 +817,13 @@ A leading `~` is expanded to your home directory in snapshot paths, in
|
|||||||
|
|
||||||
- the path given with `--config`
|
- the path given with `--config`
|
||||||
- the path in the `VAULTIK_CONFIG` environment variable
|
- the path in the `VAULTIK_CONFIG` environment variable
|
||||||
- `config.yml` in the platform config directory, which is `~/.config/vaultik/`
|
- `config.yml` in the platform config directory, which by default is
|
||||||
on Linux and `~/Library/Application Support/vaultik/` on macOS
|
`~/.config/vaultik/` on Linux and `~/Library/Application Support/vaultik/` on
|
||||||
|
macOS
|
||||||
- `/etc/vaultik/config.yml`
|
- `/etc/vaultik/config.yml`
|
||||||
|
|
||||||
A path from `--config` or `VAULTIK_CONFIG` must exist.
|
A command that reads the config fails when the path from `--config` or
|
||||||
|
`VAULTIK_CONFIG` does not exist. `config init` creates the file there.
|
||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
@@ -806,7 +857,8 @@ A path from `--config` or `VAULTIK_CONFIG` must exist.
|
|||||||
- `s3.region`
|
- `s3.region`
|
||||||
- The S3 region. Default: `us-east-1`.
|
- The S3 region. Default: `us-east-1`.
|
||||||
- `s3.part_size`
|
- `s3.part_size`
|
||||||
- The size of each part of a multipart upload. Default: `5MiB`.
|
- A large blob is uploaded to S3 in parts. This is the size of each part.
|
||||||
|
Default: `5MiB`.
|
||||||
- It must be between `5MiB` and `5GiB`.
|
- It must be between `5MiB` and `5GiB`.
|
||||||
- A blob too large for 10,000 parts of this size is uploaded in larger
|
- A blob too large for 10,000 parts of this size is uploaded in larger
|
||||||
parts.
|
parts.
|
||||||
@@ -826,8 +878,11 @@ A path from `--config` or `VAULTIK_CONFIG` must exist.
|
|||||||
- It must be at least four times `chunk_size`.
|
- It must be at least four times `chunk_size`.
|
||||||
- `compression_level`
|
- `compression_level`
|
||||||
- The zstd compression level, from `1` to `19`. Default: `3`.
|
- The zstd compression level, from `1` to `19`. Default: `3`.
|
||||||
- Levels `1` to `2`, `3` to `5`, `6` to `9` and `10` to `19` form four
|
- Levels in one of these groups compress the same way:
|
||||||
groups. Levels in one group compress the same way.
|
- `1` to `2`
|
||||||
|
- `3` to `5`
|
||||||
|
- `6` to `9`
|
||||||
|
- `10` to `19`
|
||||||
- `prune_grace_period`
|
- `prune_grace_period`
|
||||||
- How old unused data must be before `prune` deletes it. Default: `24h`.
|
- How old unused data must be before `prune` deletes it. Default: `24h`.
|
||||||
- It applies to a blob that no manifest lists. It also applies to every
|
- It applies to a blob that no manifest lists. It also applies to every
|
||||||
@@ -839,8 +894,8 @@ A path from `--config` or `VAULTIK_CONFIG` must exist.
|
|||||||
- Only the part before the first `.` is used.
|
- Only the part before the first `.` is used.
|
||||||
- `index_path`
|
- `index_path`
|
||||||
- The path of the local index.
|
- The path of the local index.
|
||||||
- Default: `index.sqlite` in the platform data directory. That is
|
- Default: `index.sqlite` in the platform data directory. By default that
|
||||||
`~/.local/share/vaultik/` on Linux and
|
is `~/.local/share/vaultik/` on Linux and
|
||||||
`~/Library/Application Support/vaultik/` on macOS.
|
`~/Library/Application Support/vaultik/` on macOS.
|
||||||
|
|
||||||
Sizes take a number of bytes or a unit. `MB` and `GB` are powers of 1000. `MiB`
|
Sizes take a number of bytes or a unit. `MB` and `GB` are powers of 1000. `MiB`
|
||||||
@@ -856,7 +911,9 @@ and `GiB` are powers of 1024.
|
|||||||
destination.
|
destination.
|
||||||
- `EDITOR`: the editor that `config edit` starts.
|
- `EDITOR`: the editor that `config edit` starts.
|
||||||
- `TMPDIR`: the directory for temporary files.
|
- `TMPDIR`: the directory for temporary files.
|
||||||
- `NO_COLOR`: when set, status lines have no color.
|
- `NO_COLOR`: a non-empty value turns off color in status lines.
|
||||||
|
- `XDG_CONFIG_HOME` and `XDG_DATA_HOME`: move the platform config and data
|
||||||
|
directories.
|
||||||
- `VAULTIK_CPUPROFILE`: write a CPU profile to this path. For debugging.
|
- `VAULTIK_CPUPROFILE`: write a CPU profile to this path. For debugging.
|
||||||
- `VAULTIK_MEMPROFILE`: write a heap profile to this path on exit. For
|
- `VAULTIK_MEMPROFILE`: write a heap profile to this path on exit. For
|
||||||
debugging.
|
debugging.
|
||||||
@@ -966,15 +1023,26 @@ A leftover `.partial` file is ignored. You can delete it.
|
|||||||
|
|
||||||
### Destination permissions
|
### Destination permissions
|
||||||
|
|
||||||
Each command needs different permissions at the destination.
|
Commands need these permissions at the destination:
|
||||||
|
|
||||||
- `snapshot create` needs only permission to write objects. A machine you back
|
- `snapshot create` without `--prune` needs only permission to write objects. A
|
||||||
up can therefore hold credentials that can neither read nor delete.
|
machine you back up can therefore hold credentials that can neither read nor
|
||||||
- `snapshot list`, `snapshot verify`, `snapshot restore` and `remote info` list
|
delete.
|
||||||
and read.
|
- These commands list and read:
|
||||||
- `snapshot purge`, `snapshot remove` and `remote nuke` list and delete.
|
- `snapshot list`
|
||||||
|
- `snapshot verify`
|
||||||
|
- `snapshot restore`
|
||||||
|
- `remote info`
|
||||||
|
- These commands list and delete:
|
||||||
|
- `snapshot purge`
|
||||||
|
- `snapshot remove`
|
||||||
|
- `remote nuke`
|
||||||
- `prune` and `snapshot create --prune` list, read and delete.
|
- `prune` and `snapshot create --prune` list, read and delete.
|
||||||
|
|
||||||
|
`snapshot verify` and `snapshot restore` list only when you give a remote key.
|
||||||
|
`snapshot remove --local-only` does not reach the destination.
|
||||||
|
`snapshot remove --dry-run` deletes nothing.
|
||||||
|
|
||||||
A machine that runs `snapshot create --prune` can delete its own backups. To
|
A machine that runs `snapshot create --prune` can delete its own backups. To
|
||||||
keep it to write-only credentials, run `prune` from a separate, trusted machine.
|
keep it to write-only credentials, run `prune` from a separate, trusted machine.
|
||||||
|
|
||||||
@@ -991,12 +1059,15 @@ unplugged volume leaves an empty directory at its mount point. Without the
|
|||||||
marker, that directory would look like a destination whose snapshots are all
|
marker, that directory would look like a destination whose snapshots are all
|
||||||
gone.
|
gone.
|
||||||
|
|
||||||
The check applies while the local index holds snapshots. When the destination
|
The check applies while the local index holds snapshots and belongs to the
|
||||||
then answers that the marker does not exist, commands treat the destination as
|
configured destination. When the destination then answers that the marker does
|
||||||
not present:
|
not exist, commands treat the destination as not present:
|
||||||
|
|
||||||
- `snapshot create` refuses to write to it.
|
- `snapshot create` refuses to write to it.
|
||||||
- `prune`, `snapshot purge` and `remote nuke` fail.
|
- These commands fail:
|
||||||
|
- `prune`
|
||||||
|
- `snapshot purge`
|
||||||
|
- `remote nuke`
|
||||||
- `snapshot list` shows the local index alone.
|
- `snapshot list` shows the local index alone.
|
||||||
- `snapshot remove` deletes nothing at the destination.
|
- `snapshot remove` deletes nothing at the destination.
|
||||||
|
|
||||||
@@ -1025,7 +1096,11 @@ different destination:
|
|||||||
- `remote nuke`
|
- `remote nuke`
|
||||||
|
|
||||||
The destination that is compared is `storage_url`. When `storage_url` is not
|
The destination that is compared is `storage_url`. When `storage_url` is not
|
||||||
set, it is `s3.endpoint`, `s3.bucket` and `s3.prefix`.
|
set, these three settings are compared instead:
|
||||||
|
|
||||||
|
- `s3.endpoint`
|
||||||
|
- `s3.bucket`
|
||||||
|
- `s3.prefix`
|
||||||
|
|
||||||
There are two ways out:
|
There are two ways out:
|
||||||
|
|
||||||
@@ -1057,8 +1132,7 @@ The new snapshot cannot be restored afterwards.
|
|||||||
|
|
||||||
## Locking
|
## Locking
|
||||||
|
|
||||||
Commands that change data take a lock, so that only one of them runs at a time.
|
Five commands take a lock, so that only one of them runs at a time:
|
||||||
These commands take it:
|
|
||||||
|
|
||||||
- `snapshot create`
|
- `snapshot create`
|
||||||
- `snapshot purge`
|
- `snapshot purge`
|
||||||
@@ -1068,9 +1142,9 @@ These commands take it:
|
|||||||
|
|
||||||
A second one does not wait. It exits at once with an "already running" error.
|
A second one does not wait. It exits at once with an "already running" error.
|
||||||
|
|
||||||
The lock is the file `vaultik.pid` in the platform data directory. That is
|
The lock is the file `vaultik.pid` in the platform data directory. By default
|
||||||
`~/.local/share/vaultik/` on Linux and `~/Library/Application Support/vaultik/`
|
that is `~/.local/share/vaultik/` on Linux and
|
||||||
on macOS.
|
`~/Library/Application Support/vaultik/` on macOS.
|
||||||
|
|
||||||
The lock covers one user on one machine. It does not stop another user or
|
The lock covers one user on one machine. It does not stop another user or
|
||||||
another machine from writing to the same destination. See
|
another machine from writing to the same destination. See
|
||||||
@@ -1111,7 +1185,8 @@ banner, log records and a final error stay on your terminal.
|
|||||||
Log records are colored lines when stderr is a terminal. They are JSON, one
|
Log records are colored lines when stderr is a terminal. They are JSON, one
|
||||||
object per line, when stderr is redirected.
|
object per line, when stderr is redirected.
|
||||||
|
|
||||||
Status lines are colored only on a terminal, and never when `NO_COLOR` is set.
|
Status lines are colored only on a terminal, and never when `NO_COLOR` has a
|
||||||
|
non-empty value.
|
||||||
|
|
||||||
### `--json`
|
### `--json`
|
||||||
|
|
||||||
@@ -1183,8 +1258,8 @@ destination, what it protects and the risks it accepts.
|
|||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
- **No extended attributes.** ACLs, macOS Finder metadata, quarantine flags and
|
- **No extended attributes.** Access control lists, macOS Finder metadata,
|
||||||
SELinux labels are not backed up.
|
quarantine flags and SELinux labels are not backed up.
|
||||||
- **No hard links.** Two hard links to one file are backed up as two files. The
|
- **No hard links.** Two hard links to one file are backed up as two files. The
|
||||||
content is stored once, but a restore creates two separate files.
|
content is stored once, but a restore creates two separate files.
|
||||||
- **No sparse files.** A sparse file is backed up at its full size, zeros
|
- **No sparse files.** A sparse file is backed up at its full size, zeros
|
||||||
@@ -1260,25 +1335,33 @@ call them.
|
|||||||
- `script/test`: runs the whole test suite, by building the `test` stage of the
|
- `script/test`: runs the whole test suite, by building the `test` stage of the
|
||||||
`Dockerfile`.
|
`Dockerfile`.
|
||||||
- `script/lint`: runs the linter, by building the `lint` stage of the
|
- `script/lint`: runs the linter, by building the `lint` stage of the
|
||||||
`Dockerfile`. The linter never runs on your machine.
|
`Dockerfile`. The linter runs only inside Docker.
|
||||||
- `script/lint-fix`: applies the linter's automatic fixes to your files. It
|
- `script/lint-fix`: applies the linter's automatic fixes to your files. It
|
||||||
needs a local Docker daemon. Run `script/lint` afterwards.
|
needs a local Docker daemon. Run `script/lint` afterwards.
|
||||||
- `script/fmt`: formats the Go code.
|
- `script/fmt`: formats the Go code.
|
||||||
- `script/fmt-check`: checks the formatting and changes nothing.
|
- `script/fmt-check`: checks the formatting and changes nothing.
|
||||||
- `script/check`: runs `script/test`, `script/lint` and `script/fmt-check`.
|
- `script/check`: runs these three scripts:
|
||||||
- `script/precommit`: runs `go mod tidy`, `go fmt` and `script/check`. The git
|
- `script/test`
|
||||||
pre-commit hook calls it.
|
- `script/lint`
|
||||||
|
- `script/fmt-check`
|
||||||
|
- `script/precommit`: the git pre-commit hook calls it. It runs these three:
|
||||||
|
- `go mod tidy`
|
||||||
|
- `go fmt`
|
||||||
|
- `script/check`
|
||||||
- `script/install-precommit`: installs that hook.
|
- `script/install-precommit`: installs that hook.
|
||||||
- `script/docker`: builds the Docker image. The build runs the linter and the
|
- `script/docker`: builds the Docker image. The build runs the linter and the
|
||||||
tests too.
|
tests too.
|
||||||
- `script/cibuild`: what CI runs on every push. It runs `script/bootstrap`,
|
- `script/cibuild`: what CI runs on every push. It runs these three:
|
||||||
`script/check` and the image build.
|
- `script/bootstrap`
|
||||||
|
- `script/check`
|
||||||
|
- the image build
|
||||||
- `script/version`: prints the version to build into the binary.
|
- `script/version`: prints the version to build into the binary.
|
||||||
- `script/projectname`: prints the project name, which tags the Docker image.
|
- `script/projectname`: prints the project name, which tags the Docker image.
|
||||||
- `script/install-go`: installs the Go version that `go.mod` names into
|
- `script/install-go`: installs the Go version that `go.mod` names into
|
||||||
`.tool/go`. It checks the download against a checksum in the script.
|
`.tool/go`. It checks the download against a checksum in the script.
|
||||||
- `script/install-goreleaser`: installs the pinned `goreleaser` into
|
- `script/install-goreleaser`: installs the pinned
|
||||||
`.tool/bin`, checked the same way.
|
[`goreleaser`](https://goreleaser.com/) into `.tool/bin`, checked the same
|
||||||
|
way.
|
||||||
- `script/release`: builds and publishes the release files.
|
- `script/release`: builds and publishes the release files.
|
||||||
- `script/release-snapshot`: builds the release files into `./dist` and
|
- `script/release-snapshot`: builds the release files into `./dist` and
|
||||||
publishes nothing.
|
publishes nothing.
|
||||||
@@ -1289,15 +1372,17 @@ skip the tests or the linter and still report success.
|
|||||||
The test suite has no separate integration target. Every test runs every time.
|
The test suite has no separate integration target. Every test runs every time.
|
||||||
Each test binary is stopped after 90 seconds.
|
Each test binary is stopped after 90 seconds.
|
||||||
|
|
||||||
To change the Go version, change these together: `go.mod`, the version and
|
To change the Go version, change these together:
|
||||||
checksums in `script/install-go`, and the two `golang` image digests in the
|
|
||||||
`Dockerfile`.
|
- `go.mod`
|
||||||
|
- the version and checksums in `script/install-go`
|
||||||
|
- the two `golang` image digests in the `Dockerfile`
|
||||||
|
|
||||||
### Messages to the user
|
### Messages to the user
|
||||||
|
|
||||||
Status lines, warnings and errors go through `internal/ui`. The data a command
|
Write status lines, warnings and errors through `internal/ui`. The data a
|
||||||
exists to print is written plain, with no marker and no color. That covers
|
command exists to print is written plain, with no marker and no color. That
|
||||||
tables, reports, JSON documents and `config get` values.
|
covers tables, reports, JSON documents and `config get` values.
|
||||||
|
|
||||||
`internal/ui` has these kinds of message:
|
`internal/ui` has these kinds of message:
|
||||||
|
|
||||||
@@ -1383,8 +1468,14 @@ version.
|
|||||||
`make release-snapshot` builds the same files into `./dist` and publishes
|
`make release-snapshot` builds the same files into `./dist` and publishes
|
||||||
nothing.
|
nothing.
|
||||||
|
|
||||||
Each archive holds the binary, `LICENSE` and `README.md`. The release files are
|
Each archive holds three files:
|
||||||
not signed, have no SBOM and are not built reproducibly.
|
|
||||||
|
- the binary
|
||||||
|
- `LICENSE`
|
||||||
|
- `README.md`
|
||||||
|
|
||||||
|
The release files are not signed, have no software bill of materials and are
|
||||||
|
not built reproducibly.
|
||||||
|
|
||||||
## license
|
## license
|
||||||
|
|
||||||
|
|||||||
+21
-15
@@ -3,7 +3,7 @@
|
|||||||
# defaults; where an option has a default, its comment gives it.
|
# defaults; where an option has a default, its comment gives it.
|
||||||
# Copy this file and change the values you need
|
# Copy this file and change the values you need
|
||||||
|
|
||||||
# Age recipient public keys for encryption
|
# Age recipient public keys for encryption (age: https://age-encryption.org/)
|
||||||
# Backups are encrypted to these public keys. snapshot create needs at least
|
# Backups are encrypted to these public keys. snapshot create needs at least
|
||||||
# one; listing, verifying and restoring do not
|
# one; listing, verifying and restoring do not
|
||||||
# Generate a key pair, and show its public key, with:
|
# Generate a key pair, and show its public key, with:
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
# grep 'public key' vaultik_backup_private_key.txt
|
# grep 'public key' vaultik_backup_private_key.txt
|
||||||
# Keep the private key file: restoring needs it. Do not pipe age-keygen
|
# Keep the private key file: restoring needs it. Do not pipe age-keygen
|
||||||
# straight into grep, which would discard the private key
|
# straight into grep, which would discard the private key
|
||||||
|
# Default: none
|
||||||
age_recipients:
|
age_recipients:
|
||||||
- age1cj2k2addawy294f6k2gr2mf9gps9r3syplryxca3nvxj3daqm96qfp84tz
|
- age1cj2k2addawy294f6k2gr2mf9gps9r3syplryxca3nvxj3daqm96qfp84tz
|
||||||
|
|
||||||
@@ -268,6 +269,7 @@ snapshots:
|
|||||||
- "**/vendor"
|
- "**/vendor"
|
||||||
|
|
||||||
# Global patterns to exclude from all backups
|
# Global patterns to exclude from all backups
|
||||||
|
# Default: none
|
||||||
exclude:
|
exclude:
|
||||||
- "*.tmp"
|
- "*.tmp"
|
||||||
|
|
||||||
@@ -276,8 +278,8 @@ exclude:
|
|||||||
# An s3:// URL also needs s3.access_key_id and s3.secret_access_key below
|
# An s3:// URL also needs s3.access_key_id and s3.secret_access_key below
|
||||||
# A file:// path must be absolute, and the directory must already exist:
|
# A file:// path must be absolute, and the directory must already exist:
|
||||||
# vaultik never creates it
|
# vaultik never creates it
|
||||||
# An rclone:// URL needs the remote to be defined in rclone's own config
|
# An rclone:// URL names a remote of rclone (https://rclone.org/); the remote
|
||||||
# (rclone config)
|
# must be defined in rclone's own config (rclone config)
|
||||||
storage_url: "rclone://myremote/path/to/backups"
|
storage_url: "rclone://myremote/path/to/backups"
|
||||||
|
|
||||||
# S3-compatible storage configuration
|
# S3-compatible storage configuration
|
||||||
@@ -310,7 +312,7 @@ storage_url: "rclone://myremote/path/to/backups"
|
|||||||
#
|
#
|
||||||
# # Part size for multipart uploads
|
# # Part size for multipart uploads
|
||||||
# # Minimum 5MiB, maximum 5GiB; affects memory usage during upload
|
# # Minimum 5MiB, maximum 5GiB; affects memory usage during upload
|
||||||
# # A blob too large for 10,000 parts of this size gets larger parts
|
# # An object too large for 10,000 parts of this size gets larger parts
|
||||||
# # Supports: 10MB, 16MiB, 100MiB, etc. (5MB is below the minimum)
|
# # Supports: 10MB, 16MiB, 100MiB, etc. (5MB is below the minimum)
|
||||||
# # Default: 5MiB
|
# # Default: 5MiB
|
||||||
# #part_size: 5MiB
|
# #part_size: 5MiB
|
||||||
@@ -323,8 +325,9 @@ storage_url: "rclone://myremote/path/to/backups"
|
|||||||
# Linux: ~/.local/share/vaultik/index.sqlite
|
# Linux: ~/.local/share/vaultik/index.sqlite
|
||||||
#index_path: /path/to/index.sqlite
|
#index_path: /path/to/index.sqlite
|
||||||
|
|
||||||
# Average chunk size for content-defined chunking
|
# Average size of a chunk, a piece of a file's content. Chunk boundaries
|
||||||
# Smaller chunks = better deduplication but more metadata
|
# depend on the file's content, not on fixed offsets
|
||||||
|
# Smaller chunks find more duplicate content and make the index larger
|
||||||
# Minimum 1MB
|
# Minimum 1MB
|
||||||
# Supports: 10MB, 5M, 1GB, 64MiB, etc.
|
# Supports: 10MB, 5M, 1GB, 64MiB, etc.
|
||||||
# Default: 10MB
|
# Default: 10MB
|
||||||
@@ -334,9 +337,10 @@ storage_url: "rclone://myremote/path/to/backups"
|
|||||||
# Multiple chunks are packed into blobs up to this size
|
# Multiple chunks are packed into blobs up to this size
|
||||||
# Must be at least four times chunk_size (the largest chunk the chunker can
|
# Must be at least four times chunk_size (the largest chunk the chunker can
|
||||||
# emit); a smaller limit would let a single-chunk blob exceed it.
|
# emit); a smaller limit would let a single-chunk blob exceed it.
|
||||||
# Chunking uses no secret (the FastCDC parameters are fixed and public). At a
|
# Chunking uses no secret (the parameters of the FastCDC chunking algorithm
|
||||||
# large limit a blob holds hundreds of chunks, so individual chunk lengths are
|
# are fixed and public). At a large limit a blob holds hundreds of chunks, so
|
||||||
# not visible in its size; lowering the limit toward chunk_size exposes them.
|
# individual chunk lengths are not visible in its size; lowering the limit
|
||||||
|
# toward chunk_size exposes them.
|
||||||
# A backup needs free temporary space, because each blob is written whole to
|
# A backup needs free temporary space, because each blob is written whole to
|
||||||
# a temporary file before it is uploaded (up to about blob_size_limit; an
|
# a temporary file before it is uploaded (up to about blob_size_limit; an
|
||||||
# rclone destination that cannot stream uploads needs about twice that) and
|
# rclone destination that cannot stream uploads needs about twice that) and
|
||||||
@@ -352,12 +356,14 @@ storage_url: "rclone://myremote/path/to/backups"
|
|||||||
# Default: 3
|
# Default: 3
|
||||||
compression_level: 5
|
compression_level: 5
|
||||||
|
|
||||||
# How old a blob that no snapshot lists must be before prune deletes it, and
|
# How old a blob that no snapshot lists must be before vaultik prune deletes
|
||||||
# how old every object in a directory under metadata/ with no manifest must
|
# it. Each snapshot has a directory under metadata/ at the destination, and
|
||||||
# be before prune deletes that directory
|
# its manifest there lists the blobs it uses. This is also how old every
|
||||||
# A backup uploads its blobs and its db.zst.age before its manifest, so on a
|
# object in a directory with no manifest must be before vaultik prune deletes
|
||||||
# destination shared between hosts or users this must be longer than the
|
# that directory
|
||||||
# longest backup run
|
# A backup uploads its blobs and its encrypted database (db.zst.age) before
|
||||||
|
# its manifest, so on a destination shared between hosts or users this must
|
||||||
|
# be longer than the longest backup run
|
||||||
# It does not protect a blob a backup reuses from an earlier run that stopped
|
# It does not protect a blob a backup reuses from an earlier run that stopped
|
||||||
# before its manifest: that blob keeps the time of its first upload, so a
|
# before its manifest: that blob keeps the time of its first upload, so a
|
||||||
# prune can delete it
|
# prune can delete it
|
||||||
|
|||||||
+51
-28
@@ -1,7 +1,8 @@
|
|||||||
# vaultik data model
|
# vaultik data model
|
||||||
|
|
||||||
`vaultik` keeps a SQLite database on the backed-up machine, called the local
|
`vaultik` keeps a SQLite database on the backed-up machine, called the local
|
||||||
index. This document describes its tables and how each command uses them.
|
index. This document describes its tables, and how a backup, a restore and
|
||||||
|
`prune` use them.
|
||||||
|
|
||||||
The local index has two jobs:
|
The local index has two jobs:
|
||||||
|
|
||||||
@@ -27,10 +28,11 @@ with the snapshot.
|
|||||||
|
|
||||||
## Where the index is
|
## Where the index is
|
||||||
|
|
||||||
The index is the file `index.sqlite` in the platform data directory. That is
|
The index is the file `index.sqlite` in the platform data directory. By default
|
||||||
`~/.local/share/vaultik/` on Linux and `~/Library/Application Support/vaultik/`
|
that is `~/.local/share/vaultik/` on Linux and
|
||||||
on macOS. The `index_path` setting and the `VAULTIK_INDEX_PATH` environment
|
`~/Library/Application Support/vaultik/` on macOS. The `index_path` setting and
|
||||||
variable change the path.
|
the `VAULTIK_INDEX_PATH` environment variable change the path. So does
|
||||||
|
`XDG_DATA_HOME`.
|
||||||
|
|
||||||
The file is readable only by its owner, because it lists every backed-up path.
|
The file is readable only by its owner, because it lists every backed-up path.
|
||||||
`vaultik` sets that mode each time it opens the index.
|
`vaultik` sets that mode each time it opens the index.
|
||||||
@@ -74,15 +76,15 @@ One row for each backed-up path: a regular file, a symlink or a directory.
|
|||||||
|
|
||||||
- `id`: a UUID. Primary key.
|
- `id`: a UUID. Primary key.
|
||||||
- `path`: the absolute path. Unique.
|
- `path`: the absolute path. Unique.
|
||||||
- `source_path`: the configured path that the scan found this entry under. A
|
- `source_path`: the configured path under which the scan found this entry,
|
||||||
restore does not read it.
|
made absolute with symlinks resolved. A restore does not read it.
|
||||||
- `mtime`: the modification time.
|
- `mtime`: the modification time.
|
||||||
- `mtime_nsec`: the nanoseconds within that second.
|
- `mtime_nsec`: the nanoseconds within that second.
|
||||||
- `size`: the size in bytes.
|
- `size`: the size in bytes.
|
||||||
- `mode`: the permission bits and the file type.
|
- `mode`: the permission bits and the file type.
|
||||||
- `uid`: the owner's user ID.
|
- `uid`: the owner's user ID.
|
||||||
- `gid`: the owner's group ID.
|
- `gid`: the owner's group ID.
|
||||||
- `link_target`: the target of a symlink. It is `NULL` for other entries.
|
- `link_target`: the target of a symlink. It is empty for other entries.
|
||||||
|
|
||||||
`idx_files_path` indexes `path`.
|
`idx_files_path` indexes `path`.
|
||||||
|
|
||||||
@@ -161,7 +163,8 @@ A restore finds the blob for each chunk through this table.
|
|||||||
One row for each snapshot.
|
One row for each snapshot.
|
||||||
|
|
||||||
- `id`: the snapshot ID. Primary key.
|
- `id`: the snapshot ID. Primary key.
|
||||||
- `hostname`: the hostname of the machine that made the backup.
|
- `hostname`: the `hostname` setting of the config. When that setting is empty,
|
||||||
|
it is the machine's hostname.
|
||||||
- `vaultik_version`: the `vaultik` version that made it.
|
- `vaultik_version`: the `vaultik` version that made it.
|
||||||
- `vaultik_git_revision`: the git commit of that version.
|
- `vaultik_git_revision`: the git commit of that version.
|
||||||
- `started_at`: when the backup started.
|
- `started_at`: when the backup started.
|
||||||
@@ -189,8 +192,8 @@ example is `server1_home_2025-06-01T12:00:00Z`. It has three parts:
|
|||||||
- the snapshot name
|
- the snapshot name
|
||||||
- the start time, in UTC
|
- the start time, in UTC
|
||||||
|
|
||||||
`snapshot list` shows `uncompressed_chunk_size` as `UNCOMPRESSED SIZE` and
|
The `snapshot list` table shows `uncompressed_chunk_size` as
|
||||||
`new_chunk_size` as `NEW CHUNK SIZE`.
|
`UNCOMPRESSED SIZE` and `new_chunk_size` as `NEW CHUNK SIZE`.
|
||||||
|
|
||||||
A snapshot with `completed_at` set to `NULL` is unfinished. `snapshot list`
|
A snapshot with `completed_at` set to `NULL` is unfinished. `snapshot list`
|
||||||
does not show it. The next backup deletes it, and so does `prune`.
|
does not show it. The next backup deletes it, and so does `prune`.
|
||||||
@@ -233,10 +236,10 @@ A directory is recorded here when all of these hold:
|
|||||||
- The backup found an entry in it that is not excluded.
|
- The backup found an entry in it that is not excluded.
|
||||||
- It is on a different filesystem from its parent directory.
|
- It is on a different filesystem from its parent directory.
|
||||||
|
|
||||||
The device number is read after the directory has been listed. Listing an
|
The device number is read after the directory has been listed. An automount
|
||||||
automount point mounts its volume, so the number is that of the volume. An
|
point is a directory whose volume is mounted when it is first listed, so the
|
||||||
automount point whose volume cannot be mounted is not recorded, because the
|
number is that of the volume. An automount point whose volume cannot be mounted
|
||||||
backup finds no entry in it.
|
is not recorded, because the backup finds no entry in it.
|
||||||
|
|
||||||
`snapshot create --prune` reads this table. It skips the prune when an earlier
|
`snapshot create --prune` reads this table. It skips the prune when an earlier
|
||||||
snapshot of the same name recorded a mount point that the new backup did not
|
snapshot of the same name recorded a mount point that the new backup did not
|
||||||
@@ -266,8 +269,14 @@ Settings that belong to this copy of the index.
|
|||||||
- `value`: its value.
|
- `value`: its value.
|
||||||
|
|
||||||
The only key is `storage_url`. It holds the destination that the index was
|
The only key is `storage_url`. It holds the destination that the index was
|
||||||
first used with. A command that changes data refuses to run when the config
|
first used with. These commands refuse to run when the config names a different
|
||||||
names a different destination.
|
destination:
|
||||||
|
|
||||||
|
- `snapshot create`
|
||||||
|
- `snapshot purge`
|
||||||
|
- `snapshot remove`
|
||||||
|
- `prune`
|
||||||
|
- `remote nuke`
|
||||||
|
|
||||||
### `schema_migrations`
|
### `schema_migrations`
|
||||||
|
|
||||||
@@ -282,13 +291,16 @@ The migrations that have run.
|
|||||||
|
|
||||||
A backup works on the tables in this order:
|
A backup works on the tables in this order:
|
||||||
|
|
||||||
|
- It saves the destination in `local_meta` when the index has none yet.
|
||||||
- It deletes unfinished snapshots, and the rows that no snapshot uses.
|
- It deletes unfinished snapshots, and the rows that no snapshot uses.
|
||||||
- It creates a row in `snapshots`, with `completed_at` set to `NULL`.
|
- It creates a row in `snapshots`, with `completed_at` set to `NULL`.
|
||||||
- It deletes the `blobs` rows that were never uploaded.
|
- It deletes the `blobs` rows that were never uploaded.
|
||||||
- It walks each path and compares every entry with its row in `files`.
|
- It walks each path. It compares each regular file and symlink with its row in
|
||||||
|
`files`.
|
||||||
- It links each unchanged file to the snapshot in `snapshot_files`.
|
- It links each unchanged file to the snapshot in `snapshot_files`.
|
||||||
- It records each mount point in `snapshot_mount_points`.
|
- It records each mount point in `snapshot_mount_points`.
|
||||||
- It reads each changed file and stores it. See below.
|
- It reads each changed regular file and stores it. See below. A changed symlink
|
||||||
|
is recorded without being read.
|
||||||
- It fills `snapshot_blobs` with every blob the snapshot uses.
|
- It fills `snapshot_blobs` with every blob the snapshot uses.
|
||||||
- It stores the snapshot's totals in `snapshots`.
|
- It stores the snapshot's totals in `snapshots`.
|
||||||
- It uploads the snapshot's metadata.
|
- It uploads the snapshot's metadata.
|
||||||
@@ -303,7 +315,8 @@ in any of these:
|
|||||||
- owner or group
|
- owner or group
|
||||||
- link target
|
- link target
|
||||||
|
|
||||||
A file is also read again when it lists a chunk that no uploaded blob holds.
|
A file is also read again when it lists a chunk that no uploaded blob holds. A
|
||||||
|
file with no row in `files` is read too.
|
||||||
|
|
||||||
Storing a changed file writes these rows:
|
Storing a changed file writes these rows:
|
||||||
|
|
||||||
@@ -316,8 +329,10 @@ Storing a changed file writes these rows:
|
|||||||
- `uploaded_ts` and `compressed_size` in `blobs`
|
- `uploaded_ts` and `compressed_size` in `blobs`
|
||||||
- a row in `snapshot_blobs`
|
- a row in `snapshot_blobs`
|
||||||
- a row in `uploads`, unless the blob was already at the destination
|
- a row in `uploads`, unless the blob was already at the destination
|
||||||
- The row in `files`, with its `file_chunks`, `chunk_files` and
|
- The row in `files`, and its rows in these tables:
|
||||||
`snapshot_files` rows.
|
- `file_chunks`
|
||||||
|
- `chunk_files`
|
||||||
|
- `snapshot_files`
|
||||||
|
|
||||||
The row in `files` is written last. It is written only after every blob that
|
The row in `files` is written last. It is written only after every blob that
|
||||||
holds the file's new chunks is stored.
|
holds the file's new chunks is stored.
|
||||||
@@ -334,31 +349,38 @@ The metadata of a snapshot is made from a copy of the index:
|
|||||||
uploaded database therefore names the destination, and lists the migrations
|
uploaded database therefore names the destination, and lists the migrations
|
||||||
that have run.
|
that have run.
|
||||||
- `VACUUM` rewrites the copy, so that no deleted row is left in the file.
|
- `VACUUM` rewrites the copy, so that no deleted row is left in the file.
|
||||||
- The copy is compressed with zstd and encrypted with age.
|
- The copy is compressed with [zstd](https://github.com/facebook/zstd) and
|
||||||
|
encrypted with [age](https://age-encryption.org/).
|
||||||
- The manifest is built from the copy's `snapshot_blobs` table.
|
- The manifest is built from the copy's `snapshot_blobs` table.
|
||||||
- The copy is uploaded as `metadata/<remote-key>/db.zst.age`.
|
- The copy is uploaded as `metadata/<remote-key>/db.zst.age`.
|
||||||
- The manifest is uploaded as `metadata/<remote-key>/manifest.json.zst`.
|
- The manifest is uploaded as `metadata/<remote-key>/manifest.json.zst`.
|
||||||
|
|
||||||
The copy is made through SQLite, not by copying the file. The index runs in WAL
|
The copy is made through SQLite, not by copying the file. The index runs in
|
||||||
mode, so rows that are already committed can still be in the `-wal` file beside
|
SQLite's write-ahead log (WAL) mode, so rows that are already committed can
|
||||||
it.
|
still be in the `-wal` file beside it.
|
||||||
|
|
||||||
### Restore
|
### Restore
|
||||||
|
|
||||||
A restore does not use the local index.
|
A restore reads no file, chunk or blob rows from the local index. Given a
|
||||||
|
remote key, it reads `snapshots` and `local_meta` there to check the destination
|
||||||
|
marker.
|
||||||
|
|
||||||
- It downloads and decrypts `db.zst.age` into a temporary directory.
|
- It downloads and decrypts `db.zst.age` into a temporary directory.
|
||||||
- It opens that file read-only. It applies no migration to it.
|
- It opens that file read-only. It applies no migration to it.
|
||||||
|
- It reads the snapshot's row in `snapshots`, to check that this is the right
|
||||||
|
database.
|
||||||
- It reads the files to restore from `files`.
|
- It reads the files to restore from `files`.
|
||||||
- It finds each file's chunks in `file_chunks`.
|
- It finds each file's chunks in `file_chunks`.
|
||||||
- It finds the blob and offset of each chunk in `blob_chunks`.
|
- It finds the blob and offset of each chunk in `blob_chunks`.
|
||||||
- It finds the name of each blob in `blobs`.
|
- It finds the name of each blob in `blobs`.
|
||||||
|
- With `--verify`, it reads each chunk's size from `chunks`.
|
||||||
|
|
||||||
It does not read the manifest.
|
It does not read the manifest.
|
||||||
|
|
||||||
### Prune
|
### Prune
|
||||||
|
|
||||||
`prune` changes the local index in two ways:
|
`prune` first saves the destination in `local_meta` when the index has none yet.
|
||||||
|
It then changes the local index in two ways:
|
||||||
|
|
||||||
- It deletes each snapshot whose manifest is gone from the destination.
|
- It deletes each snapshot whose manifest is gone from the destination.
|
||||||
- It deletes unfinished snapshots, and the rows that no snapshot uses.
|
- It deletes unfinished snapshots, and the rows that no snapshot uses.
|
||||||
@@ -389,6 +411,7 @@ Each repository type in `internal/database` serves one or more tables:
|
|||||||
- `snapshot_files`
|
- `snapshot_files`
|
||||||
- `snapshot_blobs`
|
- `snapshot_blobs`
|
||||||
- `snapshot_mount_points`
|
- `snapshot_mount_points`
|
||||||
|
- `uploads`, where it deletes a snapshot's rows
|
||||||
- `UploadRepository`: `uploads`
|
- `UploadRepository`: `uploads`
|
||||||
- `LocalMetaRepository`: `local_meta`
|
- `LocalMetaRepository`: `local_meta`
|
||||||
|
|
||||||
|
|||||||
+48
-31
@@ -4,8 +4,8 @@ This document describes what `vaultik` stores at the destination. It covers the
|
|||||||
layout, what the layout protects, and the risks it accepts.
|
layout, what the layout protects, and the risks it accepts.
|
||||||
|
|
||||||
The destination is the place the backups are stored. It is an S3 bucket and
|
The destination is the place the backups are stored. It is an S3 bucket and
|
||||||
prefix, a local directory or an rclone remote. The layout is the same on all
|
prefix, a local directory or a remote of [rclone](https://rclone.org/). The
|
||||||
three.
|
layout is the same on all three.
|
||||||
|
|
||||||
## Terms
|
## Terms
|
||||||
|
|
||||||
@@ -16,7 +16,8 @@ three.
|
|||||||
content.
|
content.
|
||||||
- **Blob**: one object under `blobs/`. It holds many chunks.
|
- **Blob**: one object under `blobs/`. It holds many chunks.
|
||||||
- **Local index**: the SQLite database on the backed-up machine.
|
- **Local index**: the SQLite database on the backed-up machine.
|
||||||
- **Recipient**: an age public key that backups are encrypted to.
|
- **Recipient**: an [age](https://age-encryption.org/) public key that backups
|
||||||
|
are encrypted to.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
@@ -49,12 +50,15 @@ unplugged volume leaves an empty directory at its mount point. Without the
|
|||||||
marker, that directory would look like a destination whose snapshots are all
|
marker, that directory would look like a destination whose snapshots are all
|
||||||
gone.
|
gone.
|
||||||
|
|
||||||
The check applies while the local index holds snapshots. When the destination
|
The check applies while the local index holds snapshots and belongs to the
|
||||||
then answers that the marker does not exist, commands treat the destination as
|
configured destination. When the destination then answers that the marker does
|
||||||
not present:
|
not exist, commands treat the destination as not present:
|
||||||
|
|
||||||
- `snapshot create` refuses to write to it.
|
- `snapshot create` refuses to write to it.
|
||||||
- `prune`, `snapshot purge` and `remote nuke` fail.
|
- These commands fail:
|
||||||
|
- `prune`
|
||||||
|
- `snapshot purge`
|
||||||
|
- `remote nuke`
|
||||||
- `snapshot list` shows the local index alone.
|
- `snapshot list` shows the local index alone.
|
||||||
- `snapshot remove` deletes nothing at the destination.
|
- `snapshot remove` deletes nothing at the destination.
|
||||||
|
|
||||||
@@ -73,8 +77,10 @@ A blob holds the content of many chunks, one after another.
|
|||||||
- **Path**: `blobs/<first 2 characters>/<next 2 characters>/<full hash>`.
|
- **Path**: `blobs/<first 2 characters>/<next 2 characters>/<full hash>`.
|
||||||
- **Example**:
|
- **Example**:
|
||||||
`blobs/ca/fe/cafebabe1234567890abcdef1234567890abcdef1234567890abcdef12345678`.
|
`blobs/ca/fe/cafebabe1234567890abcdef1234567890abcdef1234567890abcdef12345678`.
|
||||||
- **Format**: the chunk data, compressed with zstd and then encrypted with age.
|
- **Format**: the chunk data, compressed with
|
||||||
- **Recipients**: the public keys in `age_recipients` when the blob was written.
|
[zstd](https://github.com/facebook/zstd) and then encrypted with age.
|
||||||
|
- **Recipients**: the public keys in the `age_recipients` setting when the blob
|
||||||
|
was written.
|
||||||
|
|
||||||
The two directory levels keep any one directory from holding too many objects.
|
The two directory levels keep any one directory from holding too many objects.
|
||||||
|
|
||||||
@@ -257,9 +263,9 @@ therefore does not stop overwrites.
|
|||||||
An object's size follows exactly from the size of its compressed contents.
|
An object's size follows exactly from the size of its compressed contents.
|
||||||
|
|
||||||
All the new chunks in one blob share one zstd stream. The stream looks back 8
|
All the new chunks in one blob share one zstd stream. The stream looks back 8
|
||||||
MiB, or 4 MiB at compression levels 1 and 2. A blob is closed at
|
MiB, or 4 MiB at compression levels 1 and 2. A blob is closed at the
|
||||||
`blob_size_limit` and at the end of each configured path. A chunk that is
|
`blob_size_limit` setting and at the end of each configured path. A chunk that
|
||||||
already stored is never packed again.
|
the local index records as uploaded is never packed again.
|
||||||
|
|
||||||
Data that an outsider writes compresses smaller when it matches a secret near it
|
Data that an outsider writes compresses smaller when it matches a secret near it
|
||||||
in the same blob, so the blob gets smaller. The blob's size therefore shows the
|
in the same blob, so the blob gets smaller. The blob's size therefore shows the
|
||||||
@@ -277,14 +283,15 @@ Back up a directory that outsiders can write to as a snapshot of its own.
|
|||||||
|
|
||||||
### Chunk boundaries use no secret
|
### Chunk boundaries use no secret
|
||||||
|
|
||||||
The FastCDC parameters are fixed and public. `chunk_size` sets the average chunk
|
`vaultik` finds chunk boundaries with the FastCDC algorithm. Its parameters are
|
||||||
size. No chunk is larger than four times this size. Only a file's last chunk can
|
fixed and public. `chunk_size` sets the average chunk size. No chunk is larger
|
||||||
be smaller than a quarter of it. With the default of 10 MB, those limits are 40
|
than four times this size. Only a file's last chunk can be smaller than a
|
||||||
MB and 2.5 MB. A file of 2.5 MB or less is one chunk.
|
quarter of it. With the default of 10 MB, those limits are 40 MB and 2.5 MB. A
|
||||||
|
file of 2.5 MB or less is one chunk. An empty file has no chunk.
|
||||||
|
|
||||||
At the default `blob_size_limit` of 10 GB, a blob holds hundreds of chunks. The
|
At the default `blob_size_limit` of 10 GB, a full blob holds hundreds of
|
||||||
size of the blob then does not show the size of any one chunk. A limit close to
|
chunks. The size of the blob then does not show the size of any one chunk. A
|
||||||
`chunk_size` begins to show chunk sizes.
|
limit close to `chunk_size` begins to show chunk sizes.
|
||||||
|
|
||||||
### Decrypted data on the local disk
|
### Decrypted data on the local disk
|
||||||
|
|
||||||
@@ -307,14 +314,24 @@ Trust the temporary directory as much as the directory you restore into.
|
|||||||
|
|
||||||
### Commands need different permissions
|
### Commands need different permissions
|
||||||
|
|
||||||
`snapshot create` needs only permission to write objects. Other commands need
|
`snapshot create` without `--prune` needs only permission to write objects.
|
||||||
more:
|
Other commands need more:
|
||||||
|
|
||||||
- `snapshot list`, `snapshot verify`, `snapshot restore` and `remote info` list
|
- These commands list and read:
|
||||||
and read.
|
- `snapshot list`
|
||||||
- `snapshot purge`, `snapshot remove` and `remote nuke` list and delete.
|
- `snapshot verify`
|
||||||
|
- `snapshot restore`
|
||||||
|
- `remote info`
|
||||||
|
- These commands list and delete:
|
||||||
|
- `snapshot purge`
|
||||||
|
- `snapshot remove`
|
||||||
|
- `remote nuke`
|
||||||
- `prune` lists, reads and deletes.
|
- `prune` lists, reads and deletes.
|
||||||
|
|
||||||
|
`snapshot verify` and `snapshot restore` list only when they are given a remote
|
||||||
|
key. `snapshot remove --local-only` does not reach the destination.
|
||||||
|
`snapshot remove --dry-run` deletes nothing.
|
||||||
|
|
||||||
`snapshot create --prune` runs a prune on the backed-up machine. That machine
|
`snapshot create --prune` runs a prune on the backed-up machine. That machine
|
||||||
then holds credentials that can delete its own backups. To keep it to
|
then holds credentials that can delete its own backups. To keep it to
|
||||||
write-only credentials, run `prune` from a separate, trusted machine.
|
write-only credentials, run `prune` from a separate, trusted machine.
|
||||||
@@ -322,8 +339,8 @@ write-only credentials, run `prune` from a separate, trusted machine.
|
|||||||
### New recipients cannot read old data
|
### New recipients cannot read old data
|
||||||
|
|
||||||
Changing `age_recipients` does not re-encrypt what is already stored. A chunk
|
Changing `age_recipients` does not re-encrypt what is already stored. A chunk
|
||||||
that is already stored is not written again, so it stays encrypted to the
|
that the local index records as uploaded is not written again, so it stays
|
||||||
earlier recipients.
|
encrypted to the earlier recipients.
|
||||||
|
|
||||||
A new snapshot that uses such chunks cannot be restored with a new recipient's
|
A new snapshot that uses such chunks cannot be restored with a new recipient's
|
||||||
key alone. To make everything readable by a new key, run
|
key alone. To make everything readable by a new key, run
|
||||||
@@ -335,8 +352,8 @@ key alone. To make everything readable by a new key, run
|
|||||||
recipients.
|
recipients.
|
||||||
|
|
||||||
The encrypted data is held by a third party for a long time. There is no second
|
The encrypted data is held by a third party for a long time. There is no second
|
||||||
kind of recipient to fall back on if X25519 is broken, and no post-quantum
|
kind of recipient to fall back on if X25519 is broken, and no kind that resists
|
||||||
option.
|
a quantum computer.
|
||||||
|
|
||||||
## Order of writes
|
## Order of writes
|
||||||
|
|
||||||
@@ -353,12 +370,12 @@ therefore belongs to a backup that did not finish.
|
|||||||
|
|
||||||
The local index marks a snapshot complete only after both metadata files are
|
The local index marks a snapshot complete only after both metadata files are
|
||||||
uploaded. A backup that stops during that upload leaves the snapshot
|
uploaded. A backup that stops during that upload leaves the snapshot
|
||||||
unfinished. The next backup removes it from the local index and stores its data
|
unfinished. The next backup removes it from the local index and backs up its
|
||||||
again.
|
files again.
|
||||||
|
|
||||||
One short gap remains. A backup can stop after the upload and before the
|
One short gap remains. A backup can stop after the upload and before the
|
||||||
snapshot is marked complete. The destination then holds a snapshot that can be
|
snapshot is marked complete. The destination then holds a snapshot that can be
|
||||||
restored, and the local index does not list it. `snapshot list` shows it as
|
restored, and the local index marks it unfinished. `snapshot list` shows it as
|
||||||
remote only.
|
remote only.
|
||||||
|
|
||||||
An upload that is cut off leaves no partial object under the final name on S3
|
An upload that is cut off leaves no partial object under the final name on S3
|
||||||
|
|||||||
@@ -361,7 +361,8 @@ nuke --force' — it is the single supported entry point for that.`,
|
|||||||
|
|
||||||
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Skip confirmation prompt")
|
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Skip confirmation prompt")
|
||||||
cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false,
|
cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false,
|
||||||
"Show what would be removed without removing")
|
"Show what would be removed without removing; needs no permission "+
|
||||||
|
"to delete")
|
||||||
cmd.Flags().BoolVar(&opts.JSON, "json", false,
|
cmd.Flags().BoolVar(&opts.JSON, "json", false,
|
||||||
"Output result as JSON; skips the confirmation prompt, as --force does")
|
"Output result as JSON; skips the confirmation prompt, as --force does")
|
||||||
cmd.Flags().BoolVar(&opts.LocalOnly, "local-only", false,
|
cmd.Flags().BoolVar(&opts.LocalOnly, "local-only", false,
|
||||||
|
|||||||
Reference in new issue
Block a user