From a52a8a781c4aa0102c2596daad1efc73be57b6e6 Mon Sep 17 00:00:00 2001 From: sneak Date: Fri, 6 Nov 2026 19:34:06 +0000 Subject: [PATCH] Fix the review findings in README.md The first sentence now names the licence, the kind of program, the language and the author. The README gains a Design section, and its sections carry the names REPO_POLICIES.md asks for: Rationale and Getting started, which now holds the install command. Entrypoints opens with the Scripts to Rule Them All statement. Corrected: when a file is read again, the chunk size limits, and the first prune step, which looks for a snapshot's manifest. Two sentences that misparsed are reworded. A test comment that named the README quickstart follows the rename. Model: fable-5-1 --- README.md | 77 +++++++++++++++++++++++-------------- internal/cli/config_test.go | 4 +- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6f0bf6f..8b6ff63 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # vaultik (ваултик) -`vaultik` is an incremental backup tool. It encrypts your files with a public -key and uploads them to an S3-compatible object store, a local directory or an -rclone remote. +`vaultik` is an MIT-licensed command-line tool for incremental, encrypted +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 +directory or an rclone remote. The machine being backed up holds only the public key. It cannot decrypt its own backups. You keep the private key somewhere else and need it only to restore. -## Why +## Rationale Other backup tools, such as `restic`, `borg` and `duplicity`, are designed for machines that can hold a decryption key. `vaultik` is for machines that should @@ -26,7 +27,8 @@ It was written to meet these requirements together: - Encryption with [age](https://age-encryption.org/) (X25519 and ChaCha20-Poly1305), to one or more public keys. -- Incremental backups. A file is read again only when it has changed. +- Incremental backups. A file is read again only when it has changed, or when + part of its content has to be stored again. - Deduplication. Identical content is stored once, across files and across snapshots. - zstd compression at a level you choose. @@ -57,18 +59,15 @@ It was written to meet these requirements together: - **Remote key**: a hash of the snapshot ID. It names the snapshot's directory at the destination, so the destination never shows the snapshot ID. -## Install +## Getting started + +Installing needs Go 1.26.1 or later. Generating a key pair needs `age-keygen`, +which comes with [age](https://age-encryption.org/). ```sh +# Install vaultik. go install sneak.berlin/go/vaultik/cmd/vaultik@latest -``` -This needs Go 1.26.1 or later. Generating a key pair needs `age-keygen`, which -comes with [age](https://age-encryption.org/). - -## Quick start - -```sh # Write a default config file. The command prints the path it wrote. vaultik config init @@ -301,13 +300,15 @@ It needs: It does not need the private key. -A file is read again when any of these has changed since the last backup: +A file is read again when any of these holds: -- its size -- its modification time -- its mode -- its owner or group -- its link target +- Its size has changed since the last backup. +- Its modification time has changed. +- Its mode has changed. +- Its owner or group has changed. +- Its link target has changed. +- No uploaded blob holds one of its chunks. This happens when the only snapshot + that used the blob holding that chunk is removed. Every other file is recorded in the new snapshot without being read. @@ -596,7 +597,7 @@ destination. It needs no private key. It works in three steps: -- It drops each snapshot from the local index whose directory is gone from the +- It drops from the local index each snapshot whose manifest is gone from the destination. - It removes unfinished snapshots from the local index. It also removes the file, chunk and blob records that no snapshot uses. @@ -811,7 +812,8 @@ A path from `--config` or `VAULTIK_CONFIG` must exist. scheme. - `chunk_size` - The average size of a chunk. Default: `10MB`. Minimum: `1MB`. - - A chunk is between a quarter of this size and four times this size. + - No chunk is larger than four times this size. Only a file's last chunk can + be smaller than a quarter of it. - A smaller size finds more duplicate content and makes the index larger. - `blob_size_limit` - The largest size of a blob. Default: `10GB`. @@ -997,8 +999,8 @@ To fix this, mount the volume. If you emptied the destination on purpose, run The check is skipped when the look-up fails for any other reason. S3 refuses the look-up to credentials that can only write. A machine with such credentials -therefore does not notice an emptied destination. Its next backup would leave -out the data its local index records as stored. After you empty a destination, +therefore does not notice an emptied destination. Its next backup would not +upload data that its local index says is stored. After you empty a destination, run `vaultik database delete` on such a machine before it backs up again. ### Changing the destination @@ -1006,7 +1008,7 @@ run `vaultik database delete` on such a machine before it backs up again. The local index belongs to one destination. It records which blobs are stored there. -The first command that changes data records the configured destination in the +The first command that changes data saves the configured destination in the local index. Later, these commands refuse to run when the config names a different destination: @@ -1187,14 +1189,30 @@ destination, what it protects and the risks it accepts. destination is not affected. See [Schema migrations](docs/DATAMODEL.md#schema-migrations). -## Development +## Design -These documents are for developers: +A backup passes your files through four parts of the program: + +- The `Scanner` walks the configured paths and picks the files to read. +- The `Chunker` cuts each of those files into chunks. +- The `Packer` collects new chunks into blobs. It compresses and encrypts each + blob. +- The `Storer` uploads each blob to the destination. + +The backup then uploads the snapshot's database and its manifest. The database +is a trimmed, encrypted copy of the local index. + +These documents give the detail: - [ARCHITECTURE.md](ARCHITECTURE.md) describes how the code is organised. - [docs/DATAMODEL.md](docs/DATAMODEL.md) describes the local index. - [docs/REPOSTRUCTURE.md](docs/REPOSTRUCTURE.md) describes the layout of the destination. + +## Development + +Two documents give the rules for work on this repository: + - [`AGENTS.md`](AGENTS.md) holds the rules for AI agents. - [`REPO_POLICIES.md`](REPO_POLICIES.md) holds the repository policies. @@ -1216,9 +1234,10 @@ pass. ### Entrypoints -The scripts in `script/` are the entrypoints. The `Makefile` targets only call -them. The layout follows -[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all). +This repository follows the +[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) +standard. The scripts in `script/` are the entrypoints. The `Makefile` targets +only call them. - `script/bootstrap`: installs what development needs. It installs Go from `script/install-go` when the machine has none, and it fails without Docker. diff --git a/internal/cli/config_test.go b/internal/cli/config_test.go index 7dccfa0..c928501 100644 --- a/internal/cli/config_test.go +++ b/internal/cli/config_test.go @@ -61,8 +61,8 @@ func TestDefaultConfigTemplateParses(t *testing.T) { // testRecipient is a valid age public key. const testRecipient = "age1278m9q7dp3chsh2dcy82qk27v047zywyvtxwnj4cvt0z65jw6a7q5dqhfj" -// TestConfigSetRecipientOnFreshConfig follows the README quickstart: on the -// file `config init` writes, `config set age_recipients.0` and +// TestConfigSetRecipientOnFreshConfig follows the README's getting started +// steps: on the file `config init` writes, `config set age_recipients.0` and // `config set storage_url` give a config that loads with that recipient. // Neither is refused, although the config does not load until both are set; // once it loads, a recipient that is not a valid key is.