Fix the six findings of the review on config set, version and blob names
check / check (push) Successful in 11m16s

README.md: config set keeps comments and can remove blank lines; a
config that does not load yet also has a value that is not a size
refused; vaultik version reports a tag and adds a development-build
note to every other version. docs/REPOSTRUCTURE.md and README.md:
someone who has a blob's whole contents can compute its name and see
whether it is stored. docs/DATAMODEL.md: snapshot list shows an
unfinished snapshot as remote only once its manifest is at the
destination; closing a full blob commits the first chunk of the next
one. internal/cli/config.go: config set --help says the same about
comments and blank lines.

Model: fable-5-1
This commit is contained in:
clawbot committed 2026-11-11 08:28:40 +00:00
1 parent f65fd2870b
commit a8b9c57e93
4 files changed
+22 -15

No files matched your search

+9 -8
View File
@@ -64,9 +64,8 @@ It was written to meet these requirements together:
## Getting started ## Getting started
Installing needs Go 1.26.1 or later. `go install` builds the newest tagged Installing needs Go 1.26.1 or later. `go install` builds the newest tagged
version. `vaultik version` then reports that tag and calls the build a release. version. `vaultik version` then reports that tag. Generating a key pair needs
Generating a key pair needs `age-keygen`, which comes with `age-keygen`, which comes with [age](https://age-encryption.org/).
[age](https://age-encryption.org/).
```sh ```sh
# Install vaultik. # Install vaultik.
@@ -294,7 +293,7 @@ list or a map prints as YAML.
Sets one value in the config file. The key is a dotted path, as in Sets one value in the config file. The key is a dotted path, as in
`vaultik config set compression_level 9`. `vaultik config set compression_level 9`.
- Comments and formatting in the file are kept. - Comments in the file are kept. Blank lines can be removed.
- A key that no setting has is refused. A key that is not in the file yet is - A key that no setting has is refused. A key that is not in the file yet is
created. A missing parent becomes a list when the next part of the path is a created. A missing parent becomes a list when the next part of the path is a
number, and a map otherwise. number, and a map otherwise.
@@ -307,12 +306,13 @@ The command refuses a change that would stop the config from loading. An example
is `vaultik config set compression_level 99`. The file is then left unchanged. is `vaultik config set compression_level 99`. The file is then left unchanged.
A config that does not load yet is checked less strictly. An example is the file A config that does not load yet is checked less strictly. An example is the file
`config init` writes, which has no `storage_url`. There the command refuses only `config init` writes, which has no `storage_url`. There the command refuses
three things: these:
- an unknown key - an unknown key
- a value of the wrong type, such as a word where a number belongs - a value of the wrong type, such as a word where a number belongs
- a `${ENV:NAME}` value whose variable is unset or empty - a `${ENV:NAME}` value whose variable is unset or empty
- a value that is not a size, for a size setting
### `snapshot create` ### `snapshot create`
@@ -1292,6 +1292,7 @@ destination still learns these:
- when each backup finished - when each backup finished
- how many blobs each snapshot uses, and their sizes - how many blobs each snapshot uses, and their sizes
- which blobs two snapshots share - which blobs two snapshots share
- whether a blob holds data they already have in full
- whether a hostname and snapshot name they guess exist there - whether a hostname and snapshot name they guess exist there
The last point holds because a remote key is a plain hash of the snapshot ID, The last point holds because a remote key is a plain hash of the snapshot ID,
@@ -1503,8 +1504,8 @@ the version Go recorded in it:
`v`, so the tag `v1.0.0` gives `vaultik 1.0.0`. `script/release-snapshot` stamps `v`, so the tag `v1.0.0` gives `vaultik 1.0.0`. `script/release-snapshot` stamps
`dev-` followed by 12 characters of the commit. `dev-` followed by 12 characters of the commit.
`vaultik version` calls a build a release only when its version is a plain tag, `vaultik version` adds a development-build note to every version except a plain
such as `v1.0.0` or `1.0.0`. Every other version is a development build. tag, such as `v1.0.0` or `1.0.0`.
`make` stops with an error when `script/version` gives no version. `make` stops with an error when `script/version` gives no version.
+5 -3
View File
@@ -195,8 +195,9 @@ example is `server1_home_2025-06-01T12:00:00Z`. It has three parts:
The `snapshot list` table shows `uncompressed_chunk_size` as `UNCOMPRESSED SIZE` The `snapshot list` table shows `uncompressed_chunk_size` as `UNCOMPRESSED SIZE`
and `new_chunk_size` as `NEW CHUNK SIZE`. and `new_chunk_size` as `NEW CHUNK SIZE`.
A snapshot with `completed_at` set to `NULL` is unfinished. `snapshot list` does A snapshot with `completed_at` set to `NULL` is unfinished. `snapshot list`
not show it. The next backup deletes it, and so does `prune`. shows it only when its manifest is at the destination, and then as remote only.
The next backup deletes it, and so does `prune`.
### `snapshot_files` ### `snapshot_files`
@@ -324,7 +325,8 @@ Storing a changed file writes these rows:
- A row in `blobs`, when packing of a blob starts. - A row in `blobs`, when packing of a blob starts.
- When the blob is closed, in one transaction: - When the blob is closed, in one transaction:
- a row in `chunks` for each new chunk in the blob - a row in `chunks` for each chunk queued since the last blob was closed.
When a blob is full, that includes the first chunk of the next blob.
- the blob's rows in `blob_chunks` - the blob's rows in `blob_chunks`
- the blob's `blob_hash`, sizes and `finished_ts` - the blob's `blob_hash`, sizes and `finished_ts`
- When the blob is stored at the destination, in one transaction: - When the blob is stored at the destination, in one transaction:
+5 -1
View File
@@ -94,13 +94,16 @@ A blob's name is `hex(SHA256(SHA256(uncompressed contents)))`. The contents are
the chunk data, joined in order, before compression and encryption. The name is the chunk data, joined in order, before compression and encryption. The name is
not a hash of the stored bytes. not a hash of the stored bytes.
This has two consequences: This has three consequences:
- Checking a blob against its name needs the private key. The blob must be - Checking a blob against its name needs the private key. The blob must be
decrypted and decompressed first. `snapshot restore` and decrypted and decompressed first. `snapshot restore` and
`snapshot verify --deep` do this. `snapshot verify --deep` do this.
- Without the key, only a blob's presence and size can be checked. - Without the key, only a blob's presence and size can be checked.
`snapshot verify` does this. `snapshot verify` does this.
- Someone who has the whole contents of a blob can compute its name, and so see
whether it is stored. This works for a file they have that fills blobs on its
own.
`blobgen.DoubleSHA256` in `internal/blobgen` computes the name. `blobgen.DoubleSHA256` in `internal/blobgen` computes the name.
@@ -224,6 +227,7 @@ Someone who can read the destination therefore learns these:
- when each backup finished - when each backup finished
- how many blobs each snapshot uses, and how large they are - how many blobs each snapshot uses, and how large they are
- which blobs two snapshots share - which blobs two snapshots share
- whether a blob holds data they already have in full
- whether a snapshot ID they guess exists - whether a snapshot ID they guess exists
They do not learn these: They do not learn these:
+3 -3
View File
@@ -446,9 +446,9 @@ func newConfigSetCommand() *cobra.Command {
Use: "set <key> <value>", Use: "set <key> <value>",
Short: "Set a config value by dotted path (e.g. compression_level 5)", Short: "Set a config value by dotted path (e.g. compression_level 5)",
Long: `Sets a scalar config value addressed by dotted YAML path and writes Long: `Sets a scalar config value addressed by dotted YAML path and writes
the file back, preserving comments and formatting. Missing keys along the file back. Comments in the file are kept. Blank lines can be
the path are created: as a list when the next path element is a removed. Missing keys along the path are created: as a list when the
number, otherwise as a map. next path element is a number, otherwise as a map.
If the config loads, a change after which it would not, such as If the config loads, a change after which it would not, such as
compression_level 99, is refused and the file is left unchanged. A compression_level 99, is refused and the file is left unchanged. A