From a8b9c57e930dd50c7534a016bff2ceabfabd8979 Mon Sep 17 00:00:00 2001 From: clawbot Date: Wed, 11 Nov 2026 08:28:40 +0000 Subject: [PATCH] Fix the six findings of the review on config set, version and blob names 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 --- README.md | 17 +++++++++-------- docs/DATAMODEL.md | 8 +++++--- docs/REPOSTRUCTURE.md | 6 +++++- internal/cli/config.go | 6 +++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cfcc356..63cfa12 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,8 @@ It was written to meet these requirements together: ## Getting started 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. -Generating a key pair needs `age-keygen`, which comes with -[age](https://age-encryption.org/). +version. `vaultik version` then reports that tag. Generating a key pair needs +`age-keygen`, which comes with [age](https://age-encryption.org/). ```sh # 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 `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 created. A missing parent becomes a list when the next part of the path is a 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. 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 -three things: +`config init` writes, which has no `storage_url`. There the command refuses +these: - an unknown key - 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 value that is not a size, for a size setting ### `snapshot create` @@ -1292,6 +1292,7 @@ destination still learns these: - when each backup finished - how many blobs each snapshot uses, and their sizes - 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 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 `dev-` followed by 12 characters of the commit. -`vaultik version` calls a build a release only when its version is a plain tag, -such as `v1.0.0` or `1.0.0`. Every other version is a development build. +`vaultik version` adds a development-build note to every version except a plain +tag, such as `v1.0.0` or `1.0.0`. `make` stops with an error when `script/version` gives no version. diff --git a/docs/DATAMODEL.md b/docs/DATAMODEL.md index 25cdd25..e40a0b4 100644 --- a/docs/DATAMODEL.md +++ b/docs/DATAMODEL.md @@ -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` and `new_chunk_size` as `NEW CHUNK SIZE`. -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`. +A snapshot with `completed_at` set to `NULL` is unfinished. `snapshot list` +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` @@ -324,7 +325,8 @@ Storing a changed file writes these rows: - A row in `blobs`, when packing of a blob starts. - 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 `blob_hash`, sizes and `finished_ts` - When the blob is stored at the destination, in one transaction: diff --git a/docs/REPOSTRUCTURE.md b/docs/REPOSTRUCTURE.md index b530143..52bddf2 100644 --- a/docs/REPOSTRUCTURE.md +++ b/docs/REPOSTRUCTURE.md @@ -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 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 decrypted and decompressed first. `snapshot restore` and `snapshot verify --deep` do this. - Without the key, only a blob's presence and size can be checked. `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. @@ -224,6 +227,7 @@ Someone who can read the destination therefore learns these: - when each backup finished - how many blobs each snapshot uses, and how large they are - which blobs two snapshots share +- whether a blob holds data they already have in full - whether a snapshot ID they guess exists They do not learn these: diff --git a/internal/cli/config.go b/internal/cli/config.go index d21fcf0..254e9af 100644 --- a/internal/cli/config.go +++ b/internal/cli/config.go @@ -446,9 +446,9 @@ func newConfigSetCommand() *cobra.Command { Use: "set ", 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 -the file back, preserving comments and formatting. Missing keys along -the path are created: as a list when the next path element is a -number, otherwise as a map. +the file back. Comments in the file are kept. Blank lines can be +removed. Missing keys along the path are created: as a list when the +next path element is a number, otherwise as a map. 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